Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi client and profile security #58

Merged
merged 2 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.advswesome.advswesome.controller;

import com.advswesome.advswesome.security.UserPrincipal;
import com.advswesome.advswesome.repository.document.Consent;
import com.advswesome.advswesome.service.ConsentService;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import com.advswesome.advswesome.repository.document.Profile;
import com.advswesome.advswesome.service.ProfileService;
Expand All @@ -10,36 +12,63 @@
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.file.AccessDeniedException;
import java.util.List;

@RestController
@RequestMapping("/profiles")
public class ProfileController {
private final ProfileService profileService;
private final ConsentService consentService;

@Autowired
public ProfileController(ProfileService profileService) {
public ProfileController(ProfileService profileService, ConsentService consentService) {
this.profileService = profileService;
this.consentService = consentService;
}

@PostMapping
public ResponseEntity<Profile> createProfile(
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Profile profile)
{
Mono<Profile> profileMono = profileService.createProfile(profile);
Profile newProfile = profileMono.block();
return ResponseEntity.status(HttpStatus.OK).body(newProfile);
if (profile.getUserId().equals(principal.getUserId())) {
Mono<Profile> profileMono = profileService.createProfile(profile);
Profile newProfile = profileMono.block();
return ResponseEntity.status(HttpStatus.OK).body(newProfile);
} else {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}

}

@GetMapping("/{profileId}")
public ResponseEntity<Profile> getProfileById(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String profileId)
{
Mono<Consent> consentMono = consentService.getConsentByProfileId(profileId);
Consent foundConsent = consentMono.block();

Mono<Profile> profileMono = profileService.getProfileById(profileId);
Profile foundProfile = profileMono.block();
return ResponseEntity.status(HttpStatus.OK).body(foundProfile);
//return ResponseEntity.status(HttpStatus.OK).body(foundProfile);

if (foundProfile != null) {
// Check if the current user is the profile owner or has consent
boolean isOwner = foundProfile.getUserId().equals(principal.getUserId());
boolean hasConsent = foundConsent != null && foundConsent.isPermission() && foundConsent.getUserId().equals(principal.getUserId());

if (isOwner || hasConsent) {
return ResponseEntity.ok(foundProfile);
} else {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
} else {
return ResponseEntity.notFound().build();
}

}

@PutMapping("/{profileId}")
Expand All @@ -49,10 +78,22 @@ public ResponseEntity<Profile> updateProfile(
@RequestBody Profile profile)
{
// TODO: updated the dates??
// return profileService.getProfileById(profileId)
// .flatMap(existing -> {
// profile.setProfileId(profileId);
// return profileService.updateProfile(profile);
// })
// .map(ResponseEntity::ok)
// .defaultIfEmpty(ResponseEntity.notFound().build())
// .block();
return profileService.getProfileById(profileId)
.flatMap(existing -> {
profile.setProfileId(profileId);
return profileService.updateProfile(profile);
.flatMap(existingProfile -> {
if (existingProfile != null && existingProfile.getUserId().equals(principal.getUserId())) {
profile.setProfileId(profileId);
return profileService.updateProfile(profile);
} else {
return Mono.error(new AccessDeniedException("Access denied"));
}
})
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build())
Expand All @@ -64,9 +105,19 @@ public ResponseEntity<Void> deleteProfile(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String profileId
) {
Mono<Void> profileMono = profileService.deleteProfile(profileId);
var deletedProfile = profileMono.block();
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(deletedProfile);
// Mono<Void> profileMono = profileService.deleteProfile(profileId);
// var deletedProfile = profileMono.block();
// return ResponseEntity.status(HttpStatus.NO_CONTENT).body(deletedProfile);
Profile existingProfile = profileService.getProfileById(profileId).block();

if (existingProfile != null && existingProfile.getUserId().equals(principal.getUserId())) {
profileService.deleteProfile(profileId).block(); // Blocking call
return ResponseEntity.noContent().build();
} else if (existingProfile == null) {
return ResponseEntity.notFound().build();
} else {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
}

@GetMapping("/user/{userId}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import lombok.RequiredArgsConstructor;
import com.advswesome.advswesome.service.AuthService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

Expand All @@ -33,8 +35,14 @@ public Mono<User> createUser(@RequestBody User user) {
}

@PostMapping("/auth/login")
public LoginResponse login(@RequestBody @Validated LoginRequest request) throws IllegalArgumentException, JWTCreationException, UnsupportedEncodingException {
return authService.attemptLogin(request.getEmail(), request.getPassword());
public ResponseEntity<LoginResponse> login(@RequestBody @Validated LoginRequest request) throws IllegalArgumentException, JWTCreationException, UnsupportedEncodingException {
User user = userService.getUserByEmail(request.getEmail());
if (user.getClientId().equals(request.getClientId())) {
LoginResponse loginResponse= authService.attemptLogin(request.getEmail(), request.getPassword());
return ResponseEntity.ok(loginResponse);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}

@GetMapping("/{id}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@Getter
@Builder
public class LoginRequest {
private final String clientId;
private final String email;
private final String password;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@Builder
public class LoginResponse {
private String token;
}

public LoginResponse(String token) {
this.token = token;
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ openai.api.url=https://api.openai.com/v1/chat/completions

openai.max-completions=1
openai.temperature=0
openai.max_tokens=100
openai.max_tokens=100
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package com.advswesome.advswesome;

import com.advswesome.advswesome.repository.document.LoginRequest;
import com.advswesome.advswesome.repository.document.LoginResponse;
import com.advswesome.advswesome.repository.document.Profile;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MultiClientIntegrationTest {

@Autowired
private WebTestClient webTestClient;

private static String client1Id;
private static String client2Id;
private static String user1email;
private static String user1password;
private static String user1Id;

private static String user1Token;

private static String user2email;
private static String user2password;
private static String user2Id;
private static String user2Token;

private static String user1ProfileId;

private static String user2ProfileId;



@BeforeAll
static void setup() {
client1Id = "uO6alpM2c9OPQl1bNYwu";
client2Id = "LnvxTLc1DqotgvcTbnMQ";
user1email = "mtc-testing-user-1@example.com";
user1password = "12345678";
user1Id = "6umzJtEPCSbKEWPgj61m";
user2email = "mtc-testing-user-2@example.com";
user2password = "12345678";
user2Id = "b537rLk3ehZEI0epwdiP";
}

@Test
void testUserDataIsolation() {
LoginResponse loginResponse1 = webTestClient.post()
.uri("/users/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(LoginRequest.builder().email(user1email).password(user1password).clientId(client1Id).build())
.exchange()
.expectStatus().isOk()
.returnResult(LoginResponse.class)
.getResponseBody()
.doOnNext(response -> System.out.println("Login Response: " + response))
.blockFirst();

user1Token = loginResponse1.getToken();

LoginResponse loginResponse2 = webTestClient.post()
.uri("/users/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(LoginRequest.builder().email(user2email).password(user2password).clientId(client2Id).build())
.exchange()
.expectStatus().isOk()
.returnResult(LoginResponse.class)
.getResponseBody()
.blockFirst();

user2Token = loginResponse2.getToken();

user1ProfileId = createProfileAndGetId(user1Id, user1Token);
user2ProfileId = createProfileAndGetId(user2Id, user2Token);

// Test if User 1 can access User 1's profile (should be able to)
webTestClient.get()
.uri("/profiles/" + user1ProfileId)
.header("Authorization", "Bearer " + user1Token)
.exchange()
.expectStatus().isOk();

// Test if User 1 can access User 2's profile (should not be able to)
webTestClient.get()
.uri("/profiles/" + user2ProfileId)
.header("Authorization", "Bearer " + user1Token)
.exchange()
.expectStatus().isForbidden();

// Test if User 2 can access User 2's profile (should be able to)
webTestClient.get()
.uri("/profiles/" + user2ProfileId)
.header("Authorization", "Bearer " + user2Token)
.exchange()
.expectStatus().isOk();

// Test if User 2 can access User 1's profile (should not be able to)
webTestClient.get()
.uri("/profiles/" + user1ProfileId)
.header("Authorization", "Bearer " + user2Token)
.exchange()
.expectStatus().isForbidden();
}

@Test
void testClientDataIsolation() {
// User 1 should not login with client 2
webTestClient.post()
.uri("/users/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(LoginRequest.builder().email(user1email).password(user1password).clientId(client2Id).build())
.exchange()
.expectStatus().isUnauthorized();

// User 2 should not login with client 1
webTestClient.post()
.uri("/users/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(LoginRequest.builder().email(user2email).password(user2password).clientId(client1Id).build())
.exchange()
.expectStatus().isUnauthorized();
}


@AfterEach
void cleanup() {
// Delete User 1's profile
if (user1Token != null && user1ProfileId != null) {
webTestClient.delete()
.uri("/profiles/" + user1ProfileId)
.header("Authorization", "Bearer " + user1Token);
}

// Delete User 2's profile
if (user2Token != null && user2ProfileId != null) {
webTestClient.delete()
.uri("/profiles/" + user2ProfileId)
.header("Authorization", "Bearer " + user2Token);
}
}

private String createProfileAndGetId(String userId, String token) {
Profile profile = new Profile();
profile.setUserId(userId);
return webTestClient.post()
.uri("/profiles")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + token)
.bodyValue(profile)
.exchange()
.expectStatus().isOk()
.returnResult(Profile.class)
.getResponseBody()
.map(Profile::getProfileId)
.blockFirst();
}
}
Loading
Loading