diff --git a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/rest/advice/RestControllerAdvice.java b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/rest/advice/RestControllerAdvice.java new file mode 100644 index 0000000..7f20a4c --- /dev/null +++ b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/rest/advice/RestControllerAdvice.java @@ -0,0 +1,40 @@ +package dev.earlspilner.auth.rest.advice; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import static org.springframework.http.HttpStatus.*; + +/** + * @author Alexander Dudkin + */ +@ControllerAdvice +public class RestControllerAdvice { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage()); + return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail); + } + + @ExceptionHandler(BadUserCredentialsException.class) + public ResponseEntity handleBadUserCredentialsException(BadUserCredentialsException e) { + ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage()); + return ResponseEntity.status(BAD_REQUEST).body(problemDetail); + } + + @ExceptionHandler(UsernameNotFoundException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage()); + return ResponseEntity.status(NOT_FOUND).body(problemDetail); + } + + private ProblemDetail createProblemDetail(HttpStatus status, String title) { + return ProblemDetail.forStatusAndDetail(status, title); + } + +} diff --git a/library-api-books-service/src/main/java/dev/earlspilner/books/rest/advice/RestControllerAdvice.java b/library-api-books-service/src/main/java/dev/earlspilner/books/rest/advice/RestControllerAdvice.java new file mode 100644 index 0000000..d3f613a --- /dev/null +++ b/library-api-books-service/src/main/java/dev/earlspilner/books/rest/advice/RestControllerAdvice.java @@ -0,0 +1,51 @@ +package dev.earlspilner.books.rest.advice; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import static org.springframework.http.HttpStatus.*; + +/** + * @author Alexander Dudkin + */ +@ControllerAdvice +public class RestControllerAdvice { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage()); + return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail); + } + + @ExceptionHandler(UnauthorizedOperationException.class) + public ResponseEntity handleUnauthorizedOperationException(UnauthorizedOperationException e) { + ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail); + } + + @ExceptionHandler(BookExistsException.class) + public ResponseEntity handleBookExistsException(BookExistsException e) { + ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage()); + return ResponseEntity.status(BAD_REQUEST).body(problemDetail); + } + + @ExceptionHandler(BookNotFoundException.class) + public ResponseEntity handleBookNotFoundException(BookNotFoundException e) { + ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage()); + return ResponseEntity.status(NOT_FOUND).body(problemDetail); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage()); + return ResponseEntity.status(BAD_REQUEST).body(problemDetail); + } + + private ProblemDetail createProblemDetail(HttpStatus status, String title) { + return ProblemDetail.forStatusAndDetail(status, title); + } + +} diff --git a/library-api-books-service/src/main/java/dev/earlspilner/books/rest/advice/UnauthorizedOperationException.java b/library-api-books-service/src/main/java/dev/earlspilner/books/rest/advice/UnauthorizedOperationException.java new file mode 100644 index 0000000..995ed5c --- /dev/null +++ b/library-api-books-service/src/main/java/dev/earlspilner/books/rest/advice/UnauthorizedOperationException.java @@ -0,0 +1,7 @@ +package dev.earlspilner.books.rest.advice; + +public class UnauthorizedOperationException extends RuntimeException { + public UnauthorizedOperationException(String message) { + super(message); + } +} diff --git a/library-api-library-service/src/main/java/dev/earlspilner/library/rest/advice/RestControllerAdvice.java b/library-api-library-service/src/main/java/dev/earlspilner/library/rest/advice/RestControllerAdvice.java new file mode 100644 index 0000000..ad55a80 --- /dev/null +++ b/library-api-library-service/src/main/java/dev/earlspilner/library/rest/advice/RestControllerAdvice.java @@ -0,0 +1,45 @@ +package dev.earlspilner.library.rest.advice; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import static org.springframework.http.HttpStatus.*; + +/** + * @author Alexander Dudkin + */ +@ControllerAdvice +public class RestControllerAdvice { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage()); + return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail); + } + + @ExceptionHandler(UnauthorizedOperationException.class) + public ResponseEntity handleUnauthorizedOperationException(UnauthorizedOperationException e) { + ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail); + } + + @ExceptionHandler(BookRecordNotFoundException.class) + public ResponseEntity handleBookRecordNotFoundException(BookRecordNotFoundException e) { + ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage()); + return ResponseEntity.status(NOT_FOUND).body(problemDetail); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage()); + return ResponseEntity.status(BAD_REQUEST).body(problemDetail); + } + + private ProblemDetail createProblemDetail(HttpStatus status, String title) { + return ProblemDetail.forStatusAndDetail(status, title); + } + +} diff --git a/library-api-library-service/src/main/java/dev/earlspilner/library/rest/advice/UnauthorizedOperationException.java b/library-api-library-service/src/main/java/dev/earlspilner/library/rest/advice/UnauthorizedOperationException.java new file mode 100644 index 0000000..8a1bc23 --- /dev/null +++ b/library-api-library-service/src/main/java/dev/earlspilner/library/rest/advice/UnauthorizedOperationException.java @@ -0,0 +1,7 @@ +package dev.earlspilner.library.rest.advice; + +public class UnauthorizedOperationException extends RuntimeException { + public UnauthorizedOperationException(String message) { + super(message); + } +} diff --git a/library-api-loan-service/src/main/java/dev/earlspilner/loans/rest/advice/RestControllerAdvice.java b/library-api-loan-service/src/main/java/dev/earlspilner/loans/rest/advice/RestControllerAdvice.java new file mode 100644 index 0000000..bc27f40 --- /dev/null +++ b/library-api-loan-service/src/main/java/dev/earlspilner/loans/rest/advice/RestControllerAdvice.java @@ -0,0 +1,45 @@ +package dev.earlspilner.loans.rest.advice; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import static org.springframework.http.HttpStatus.*; + +/** + * @author Alexander Dudkin + */ +@ControllerAdvice +public class RestControllerAdvice { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage()); + return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail); + } + + @ExceptionHandler(UnauthorizedOperationException.class) + public ResponseEntity handleUnauthorizedOperationException(UnauthorizedOperationException e) { + ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail); + } + + @ExceptionHandler(LoanNotFoundException.class) + public ResponseEntity handleLoanNotFoundException(LoanNotFoundException e) { + ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage()); + return ResponseEntity.status(NOT_FOUND).body(problemDetail); + } + + @ExceptionHandler(UnsupportedOperationException.class) + public ResponseEntity handleUnsupportedOperationException(UnsupportedOperationException e) { + ProblemDetail problemDetail = createProblemDetail(UNPROCESSABLE_ENTITY, e.getMessage()); + return ResponseEntity.status(UNPROCESSABLE_ENTITY).body(problemDetail); + } + + private ProblemDetail createProblemDetail(HttpStatus status, String title) { + return ProblemDetail.forStatusAndDetail(status, title); + } + +} diff --git a/library-api-loan-service/src/main/java/dev/earlspilner/loans/rest/advice/UnauthorizedOperationException.java b/library-api-loan-service/src/main/java/dev/earlspilner/loans/rest/advice/UnauthorizedOperationException.java new file mode 100644 index 0000000..5f91015 --- /dev/null +++ b/library-api-loan-service/src/main/java/dev/earlspilner/loans/rest/advice/UnauthorizedOperationException.java @@ -0,0 +1,7 @@ +package dev.earlspilner.loans.rest.advice; + +public class UnauthorizedOperationException extends RuntimeException { + public UnauthorizedOperationException(String message) { + super(message); + } +} diff --git a/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/RestControllerAdvice.java b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/RestControllerAdvice.java new file mode 100644 index 0000000..163712c --- /dev/null +++ b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/RestControllerAdvice.java @@ -0,0 +1,45 @@ +package dev.earlspilner.users.rest.advice; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import static org.springframework.http.HttpStatus.*; + +/** + * @author Alexander Dudkin + */ +@ControllerAdvice +public class RestControllerAdvice { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage()); + return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail); + } + + @ExceptionHandler(UnauthorizedOperationException.class) + public ResponseEntity handleUnauthorizedOperationException(UnauthorizedOperationException e) { + ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail); + } + + @ExceptionHandler(UserExistsException.class) + public ResponseEntity handleUserExistsException(UserExistsException e) { + ProblemDetail problemDetail = createProblemDetail(CONFLICT, e.getMessage()); + return ResponseEntity.status(CONFLICT).body(problemDetail); + } + + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handleUserNotFoundException(UserNotFoundException e) { + ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage()); + return ResponseEntity.status(NOT_FOUND).body(problemDetail); + } + + private ProblemDetail createProblemDetail(HttpStatus status, String title) { + return ProblemDetail.forStatusAndDetail(status, title); + } + +} diff --git a/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UnauthorizedOperationException.java b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UnauthorizedOperationException.java new file mode 100644 index 0000000..572c97f --- /dev/null +++ b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UnauthorizedOperationException.java @@ -0,0 +1,7 @@ +package dev.earlspilner.users.rest.advice; + +public class UnauthorizedOperationException extends RuntimeException { + public UnauthorizedOperationException(String message) { + super(message); + } +} diff --git a/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserExistsException.java b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserExistsException.java index 372008e..b8e25a9 100644 --- a/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserExistsException.java +++ b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserExistsException.java @@ -6,7 +6,7 @@ @ResponseStatus(BAD_REQUEST) public class UserExistsException extends RuntimeException { - public UserExistsException(String email) { - super("User already exists with email: " + email); + public UserExistsException(String message) { + super(message); } } diff --git a/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserNotFoundException.java b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserNotFoundException.java index 6aefa15..e45fa3a 100644 --- a/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserNotFoundException.java +++ b/library-api-users-service/src/main/java/dev/earlspilner/users/rest/advice/UserNotFoundException.java @@ -5,7 +5,7 @@ @ResponseStatus(HttpStatus.NOT_FOUND) public class UserNotFoundException extends RuntimeException { - public UserNotFoundException(String id) { - super("User not found with ID: " + id); + public UserNotFoundException(String message) { + super(message); } } diff --git a/library-api-users-service/src/main/java/dev/earlspilner/users/service/UserServiceImpl.java b/library-api-users-service/src/main/java/dev/earlspilner/users/service/UserServiceImpl.java index 35837d8..46a9da3 100644 --- a/library-api-users-service/src/main/java/dev/earlspilner/users/service/UserServiceImpl.java +++ b/library-api-users-service/src/main/java/dev/earlspilner/users/service/UserServiceImpl.java @@ -4,11 +4,14 @@ import dev.earlspilner.users.entity.User; import dev.earlspilner.users.mapper.UserMapper; import dev.earlspilner.users.repository.UserRepository; +import dev.earlspilner.users.rest.advice.UnauthorizedOperationException; import dev.earlspilner.users.rest.advice.UserExistsException; import dev.earlspilner.users.rest.advice.UserNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,8 +36,11 @@ public UserServiceImpl(UserMapper userMapper, UserRepository userRepository, Pas @Override @Transactional public UserDto saveUser(UserDto dto) { - if (userRepository.existsByUsername(dto.username()) || userRepository.existsByEmail(dto.email())) - throw new UserExistsException("User already exists"); + if (userRepository.existsByUsername(dto.username())) + throw new UserExistsException("User already exists with username: " + dto.username()); + + if (userRepository.existsByEmail(dto.email())) + throw new UserExistsException("User already exists with email: " + dto.email()); User user = userMapper.toUserEntity(dto); user.setPassword(passwordEncoder.encode(dto.password())); @@ -63,6 +69,12 @@ public UserDto updateUser(String username, UserDto dto) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UserNotFoundException("User not found with username: " + username)); + String authenticatedUsername = getAuthenticatedUsername(); + + if (!authenticatedUsername.equals(username)) { + throw new UnauthorizedOperationException("You are not allowed to update this user."); + } + user.setName(dto.name()); user.setUsername(dto.username()); user.setEmail(dto.email()); @@ -74,6 +86,21 @@ public UserDto updateUser(String username, UserDto dto) { @Override @Transactional public void deleteUser(Integer id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found with id: " + id)); + + String authenticatedUsername = getAuthenticatedUsername(); + + if (!authenticatedUsername.equals(user.getUsername())) { + throw new UnauthorizedOperationException("You are not allowed to delete this user."); + } + userRepository.deleteById(id); } + + private String getAuthenticatedUsername() { + UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return principal.getUsername(); + } + }