From b2425039c1ddcc1217d11b47dcea61c9282e91d8 Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 01:58:51 +0300 Subject: [PATCH 01/14] (TP-77) feat: add ForbiddenException --- .../dev/rentplace/exceptions/ForbiddenException.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 rentplace/src/main/java/kattsyn/dev/rentplace/exceptions/ForbiddenException.java diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/exceptions/ForbiddenException.java b/rentplace/src/main/java/kattsyn/dev/rentplace/exceptions/ForbiddenException.java new file mode 100644 index 0000000..b5720ba --- /dev/null +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/exceptions/ForbiddenException.java @@ -0,0 +1,11 @@ +package kattsyn.dev.rentplace.exceptions; + +import org.springframework.http.HttpStatus; + +public class ForbiddenException extends AppException { + + public ForbiddenException(String message) { + super(HttpStatus.FORBIDDEN.value(), "FORBIDDEN", message); + } + +} From 1263aca2c0846d44c4df5d3f8b3a91554e5289f0 Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 01:59:34 +0300 Subject: [PATCH 02/14] (TP-77) feat: change commission percents --- rentplace/src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rentplace/src/main/resources/application.yml b/rentplace/src/main/resources/application.yml index 35525b2..6d3b3e1 100644 --- a/rentplace/src/main/resources/application.yml +++ b/rentplace/src/main/resources/application.yml @@ -35,9 +35,9 @@ logging: com.sun.mail: DEBUG commission: for_renter: - in_percent: 3 + in_percent: 4 for_owner: - in_percent: 3 + in_percent: 4 jwt: expiration_time_in_minutes: access: 5 From 8fdc7df6236a35afe0577f8fa5cbe373e863ceb2 Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:00:17 +0300 Subject: [PATCH 03/14] (TP-77) feat: add getUserInfo method --- .../rentplace/controllers/AuthController.java | 19 ++++++++++--------- .../dev/rentplace/services/AuthService.java | 3 +++ .../services/impl/AuthServiceImpl.java | 13 ++++++++++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/AuthController.java b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/AuthController.java index 397bd0a..79c574a 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/AuthController.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/AuthController.java @@ -1,23 +1,16 @@ package kattsyn.dev.rentplace.controllers; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.security.auth.message.AuthException; -import jakarta.servlet.http.HttpServletResponse; -import kattsyn.dev.rentplace.dtos.CodeRequest; -import kattsyn.dev.rentplace.dtos.JwtRequest; -import kattsyn.dev.rentplace.dtos.JwtResponse; -import kattsyn.dev.rentplace.dtos.RefreshJwtRequest; +import kattsyn.dev.rentplace.dtos.*; import kattsyn.dev.rentplace.services.AuthService; import kattsyn.dev.rentplace.services.VerificationCodeService; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.time.Duration; - @RestController @RequestMapping("${api.path}/auth") @RequiredArgsConstructor @@ -118,4 +111,12 @@ public ResponseEntity refresh(/*@CookieValue(name = "refreshToken") .body(jwtResponse); } + + @GetMapping("/info") + @SecurityRequirement(name = "JWT") + @Operation(summary = "Получение информации о пользователе", description = "Возвращает информацию об авторизованном пользователе") + public UserDTO getUserInfo() throws AuthException { + return authService.getUserInfo(); + } + } diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/AuthService.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/AuthService.java index e3f5be5..b57d61e 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/AuthService.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/AuthService.java @@ -4,6 +4,7 @@ import kattsyn.dev.rentplace.auth.JwtAuthentication; import kattsyn.dev.rentplace.dtos.JwtRequest; import kattsyn.dev.rentplace.dtos.JwtResponse; +import kattsyn.dev.rentplace.dtos.UserDTO; public interface AuthService { @@ -15,4 +16,6 @@ public interface AuthService { JwtAuthentication getAuthInfo(); + UserDTO getUserInfo() throws AuthException; + } diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/AuthServiceImpl.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/AuthServiceImpl.java index 4d8818a..cdbdfd3 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/AuthServiceImpl.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/AuthServiceImpl.java @@ -6,6 +6,7 @@ import kattsyn.dev.rentplace.auth.JwtAuthentication; import kattsyn.dev.rentplace.dtos.JwtRequest; import kattsyn.dev.rentplace.dtos.JwtResponse; +import kattsyn.dev.rentplace.dtos.UserDTO; import kattsyn.dev.rentplace.entities.User; import kattsyn.dev.rentplace.services.AuthService; import kattsyn.dev.rentplace.services.UserService; @@ -13,6 +14,7 @@ import kattsyn.dev.rentplace.services.VerificationCodeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -34,7 +36,6 @@ public class AuthServiceImpl implements AuthService { public JwtResponse login(@NonNull JwtRequest authRequest) throws AuthException { final User user = userService.getUserByEmail(authRequest.getEmail()); - //todo: заменить на сгенерированный код if (verificationCodeService.validateCode(authRequest.getEmail(), authRequest.getCode())) { final String accessToken = jwtProvider.generateAccessToken(user); @@ -76,6 +77,16 @@ public JwtResponse refresh(@NonNull String refreshToken) throws AuthException { throw new AuthException("Невалидный JWT токен"); } + public UserDTO getUserInfo() throws AuthException { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated() || + "anonymousUser".equals(authentication.getPrincipal())) { + throw new AuthException("Пользователь не авторизован"); + } + String email = authentication.getName(); + return userService.getUserDTOByEmail(email); + } + public JwtAuthentication getAuthInfo() { return (JwtAuthentication) SecurityContextHolder.getContext().getAuthentication(); } From 81119134e8041edacd64ab8bade1fb2bbd515dcc Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:00:56 +0300 Subject: [PATCH 04/14] (TP-77) fix: delete extra ADMIN_URLS --- .../java/kattsyn/dev/rentplace/configs/SecurityConfig.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/configs/SecurityConfig.java b/rentplace/src/main/java/kattsyn/dev/rentplace/configs/SecurityConfig.java index 34a7339..d1daf87 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/configs/SecurityConfig.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/configs/SecurityConfig.java @@ -52,11 +52,8 @@ public void init() { }; ADMIN_URLS = new String[]{ - "/" + apiPath + "/reservations/{id}", - "/" + apiPath + "/properties/{id}", "/" + apiPath + "/categories/**", "/" + apiPath + "/facilities/**", - "/" + apiPath + "/users/{id}", "/" + apiPath + "/images/{id}" }; From 5b43cc8973d90a443bb92760496fd354e1c0485c Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:01:52 +0300 Subject: [PATCH 05/14] (TP-77) feat: change user variables --- .../java/kattsyn/dev/rentplace/dtos/UserCreateEditDTO.java | 3 +++ .../src/main/java/kattsyn/dev/rentplace/dtos/UserDTO.java | 3 +++ .../src/main/java/kattsyn/dev/rentplace/entities/User.java | 3 --- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserCreateEditDTO.java b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserCreateEditDTO.java index bd0eae3..af5e455 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserCreateEditDTO.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserCreateEditDTO.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import kattsyn.dev.rentplace.enums.Gender; +import kattsyn.dev.rentplace.enums.Role; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -30,6 +31,8 @@ public class UserCreateEditDTO { private String surname; @Schema(description = "Пол пользователя. MALE или FEMALE") private Gender gender; + @Schema(description = "Роль пользователя. ROLE_USER или ROLE_ADMIN") + private Role role; @Schema(description = "Дата рождения пользователя", example = "2004-02-22") private LocalDate birthDate; @Schema(description = "email пользователя", example = "ivanivanov@gmail.com") diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserDTO.java b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserDTO.java index dc6e2f3..8f7e774 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserDTO.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/UserDTO.java @@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import kattsyn.dev.rentplace.enums.Gender; +import kattsyn.dev.rentplace.enums.Role; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -33,6 +34,8 @@ public class UserDTO { private String surname; @Schema(description = "Пол пользователя. MALE или FEMALE") private Gender gender; + @Schema(description = "Роль пользователя. ROLE_USER или ROLE_ADMIN") + private Role role; @Schema(description = "Дата рождения пользователя", example = "2004-02-22") private LocalDate birthDate; @Schema(description = "email пользователя", example = "ivanivanov@gmail.com") diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/entities/User.java b/rentplace/src/main/java/kattsyn/dev/rentplace/entities/User.java index 95397c2..23d9b78 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/entities/User.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/entities/User.java @@ -6,7 +6,6 @@ import lombok.*; import java.time.LocalDate; -import java.util.List; @Entity @NoArgsConstructor @@ -43,6 +42,4 @@ public class User { @JoinColumn(name = "image_id") private Image image; - @OneToMany(mappedBy = "renter") - private List reservations; } From 2b15ecc678169c048cae01c675cdcd02dfe7c11c Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:06:22 +0300 Subject: [PATCH 06/14] (TP-77) feat: verify sender is user or admin before edit --- .../rentplace/controllers/UserController.java | 10 +++++--- .../dev/rentplace/services/UserService.java | 4 ++- .../services/impl/UserServiceImpl.java | 25 +++++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/UserController.java b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/UserController.java index d3fe1a5..8e492d1 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/UserController.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/UserController.java @@ -19,6 +19,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -51,8 +52,9 @@ public ResponseEntity uploadImage( content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE) ) @RequestParam("file") MultipartFile file, @PathVariable - @Parameter(description = "id пользователя", example = "10") long id) { - + @Parameter(description = "id пользователя", example = "10") long id, + Authentication authentication) { + userService.allowedToEditUser(id, authentication.getName()); return ResponseEntity.ok(userService.uploadImage(file, id)); } @@ -122,7 +124,9 @@ public ResponseEntity createUser(@ModelAttribute @Valid UserCreateEditD public ResponseEntity updateUser( @PathVariable @Parameter(description = "id пользователя", example = "1") long id, - @ModelAttribute @Valid UserCreateEditDTO userCreateEditDTO) { + @ModelAttribute @Valid UserCreateEditDTO userCreateEditDTO, + Authentication authentication) { + userService.allowedToEditUser(id, authentication.getName()); return ResponseEntity.ok(userService.update(id, userCreateEditDTO)); } diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/UserService.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/UserService.java index 7412c19..546e071 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/UserService.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/UserService.java @@ -4,7 +4,6 @@ import kattsyn.dev.rentplace.dtos.UserCreateEditDTO; import kattsyn.dev.rentplace.dtos.UserDTO; import kattsyn.dev.rentplace.entities.User; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -13,6 +12,7 @@ public interface UserService { List findAll(); User getUserByEmail(String email); + UserDTO getUserDTOByEmail(String email); UserDTO findById(Long id); User getUserById(Long id); UserDTO save(UserDTO userDTO); @@ -20,4 +20,6 @@ public interface UserService { void deleteById(long id); ImageDTO uploadImage(MultipartFile file, long id); UserDTO update(long id, UserCreateEditDTO userCreateEditDTO); + + boolean allowedToEditUser(long id, String email); } diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/UserServiceImpl.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/UserServiceImpl.java index 87a8af6..7e433d4 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/UserServiceImpl.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/UserServiceImpl.java @@ -7,6 +7,8 @@ import kattsyn.dev.rentplace.entities.Image; import kattsyn.dev.rentplace.entities.User; import kattsyn.dev.rentplace.enums.ImageType; +import kattsyn.dev.rentplace.enums.Role; +import kattsyn.dev.rentplace.exceptions.ForbiddenException; import kattsyn.dev.rentplace.exceptions.NotFoundException; import kattsyn.dev.rentplace.mappers.UserMapper; import kattsyn.dev.rentplace.repositories.UserRepository; @@ -14,8 +16,6 @@ import kattsyn.dev.rentplace.services.UserService; import kattsyn.dev.rentplace.utils.PathResolver; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -44,6 +44,12 @@ public User getUserByEmail(String email) { ); } + @Override + public UserDTO getUserDTOByEmail(String email) { + return userMapper.fromUser(userRepository.findByEmail(email) + .orElseThrow(() -> new NotFoundException(String.format("User with email %s not found", email)))); + } + @Transactional @Override public User getUserById(Long id) { @@ -122,6 +128,10 @@ public UserDTO update(long id, UserCreateEditDTO userCreateEditDTO) { user.setGender(userCreateEditDTO.getGender()); } + if (userCreateEditDTO.getRole() != null) { + user.setRole(userCreateEditDTO.getRole()); + } + if (userCreateEditDTO.getFile() != null && !userCreateEditDTO.getFile().isEmpty()) { return uploadImage(userCreateEditDTO.getFile(), user); } @@ -129,6 +139,17 @@ public UserDTO update(long id, UserCreateEditDTO userCreateEditDTO) { return userMapper.fromUser(userRepository.save(user)); } + @Override + @Transactional + public boolean allowedToEditUser(long id, String email) { + User user = getUserByEmail(email); + + if (user.getRole() == Role.ROLE_ADMIN || id == user.getUserId()) { + return true; + } + throw new ForbiddenException(String.format("FORBIDDEN. You are not allowed to edit user email: %s.", email)); + } + private UserDTO uploadImage(MultipartFile file, User user) { String path = PathResolver.resolvePath(ImageType.USER, user.getUserId()); From 104157ffe9b97d78bb2c3bae4cc4d6423d11e6eb Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:07:15 +0300 Subject: [PATCH 07/14] (TP-77) feat: delete extra comments --- .../main/java/kattsyn/dev/rentplace/entities/Reservation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Reservation.java b/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Reservation.java index bd711c6..6f5fa3c 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Reservation.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Reservation.java @@ -23,11 +23,11 @@ public class Reservation { private long reservationId; @ManyToOne - @JoinColumn(name = "property_id") // имя столбца в таблице reservations + @JoinColumn(name = "property_id") private Property property; @ManyToOne - @JoinColumn(name = "renter_id") // имя столбца в таблице reservations + @JoinColumn(name = "renter_id") private User renter; @Column(name = "start_date") From 3e8d796c45c9f0fb477f5e64f8af21f1a6a2a8dd Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:08:07 +0300 Subject: [PATCH 08/14] (TP-77) feat: validate if sender is renter or admin --- .../controllers/ReservationController.java | 9 ++++-- .../services/ReservationService.java | 4 +++ .../services/impl/ReservationServiceImpl.java | 28 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/ReservationController.java b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/ReservationController.java index 185cca8..dac6937 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/ReservationController.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/ReservationController.java @@ -15,6 +15,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -79,7 +80,9 @@ public ResponseEntity getReservation(@PathVariable @SecurityRequirement(name = "JWT") @SecurityRequirement(name = "JWT") @PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createReservation(@Valid @ModelAttribute ReservationCreateEditDTO reservationCreateEditDTO) { + public ResponseEntity createReservation(@Valid @ModelAttribute ReservationCreateEditDTO reservationCreateEditDTO, + Authentication authentication) { + reservationService.allowedToCreateReservationOrAdmin(reservationCreateEditDTO, authentication.getName()); return ResponseEntity.ok(reservationService.createReservation(reservationCreateEditDTO)); } @@ -118,8 +121,10 @@ public ResponseEntity updateReservation(@PathVariable @Parameter @SecurityRequirement(name = "JWT") public ResponseEntity deleteReservation( @PathVariable - @Valid @Parameter(description = "id бронирования", example = "1") long id + @Valid @Parameter(description = "id бронирования", example = "1") long id, + Authentication authentication ) { + reservationService.ownsReservationOrAdmin(id, authentication.getName()); return ResponseEntity.ok(reservationService.deleteById(id)); } diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/ReservationService.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/ReservationService.java index 26171e3..ecca812 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/ReservationService.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/ReservationService.java @@ -10,6 +10,10 @@ @Service public interface ReservationService { + boolean ownsReservationOrAdmin(long reservationId, String email); + + boolean allowedToCreateReservationOrAdmin(ReservationCreateEditDTO reservationCreateEditDTO, String email); + Reservation getReservationById(long reservationId); ReservationDTO getReservationDTOById(long reservationId); diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/ReservationServiceImpl.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/ReservationServiceImpl.java index d8f76a1..e4e0aa7 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/ReservationServiceImpl.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/ReservationServiceImpl.java @@ -1,13 +1,18 @@ package kattsyn.dev.rentplace.services.impl; +import jakarta.transaction.Transactional; import kattsyn.dev.rentplace.dtos.ReservationCreateEditDTO; import kattsyn.dev.rentplace.dtos.ReservationDTO; import kattsyn.dev.rentplace.entities.Reservation; +import kattsyn.dev.rentplace.entities.User; import kattsyn.dev.rentplace.enums.PaymentStatus; +import kattsyn.dev.rentplace.enums.Role; +import kattsyn.dev.rentplace.exceptions.ForbiddenException; import kattsyn.dev.rentplace.exceptions.NotFoundException; import kattsyn.dev.rentplace.mappers.ReservationMapper; import kattsyn.dev.rentplace.repositories.ReservationRepository; import kattsyn.dev.rentplace.services.ReservationService; +import kattsyn.dev.rentplace.services.UserService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -25,6 +30,29 @@ public class ReservationServiceImpl implements ReservationService { private final ReservationRepository reservationRepository; private final ReservationMapper reservationMapper; + private final UserService userService; + + @Override + @Transactional + public boolean ownsReservationOrAdmin(long reservationId, String email) { + User user = userService.getUserByEmail(email); + Reservation reservation = getReservationById(reservationId); + + if (user.getRole() == Role.ROLE_ADMIN || reservation.getRenter().getUserId() == user.getUserId()) { + return true; + } + throw new ForbiddenException(String.format("FORBIDDEN. You are not allowed to edit or delete reservation id: %s.", reservationId)); + } + + @Override + public boolean allowedToCreateReservationOrAdmin(ReservationCreateEditDTO reservationCreateEditDTO, String email) { + User user = userService.getUserByEmail(email); + + if (user.getRole() == Role.ROLE_ADMIN || user.getUserId() == reservationCreateEditDTO.getRenterId()) { + return true; + } + throw new ForbiddenException(String.format("FORBIDDEN. You are not allowed to rent for user email: %s.", email)); + } @Override public List findAllReservations() { From 084661095401fd79c8406777a9f035f5bb82938d Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:08:41 +0300 Subject: [PATCH 09/14] (TP-77) feat: delete reservations --- .../main/java/kattsyn/dev/rentplace/entities/Property.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Property.java b/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Property.java index e3aa8a7..57281a0 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Property.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/entities/Property.java @@ -6,7 +6,6 @@ import lombok.*; import java.util.HashSet; -import java.util.List; import java.util.Set; @Entity @@ -78,7 +77,7 @@ NOT PUBLISHED (не опубликовано) - объявление уже пр private int maxGuests; @Schema(description = "Владелец жилья") - @OneToOne(cascade = CascadeType.MERGE) + @ManyToOne(cascade = CascadeType.MERGE) @JoinColumn(name = "owner_id", referencedColumnName = "user_id") private User owner; @@ -108,6 +107,4 @@ NOT PUBLISHED (не опубликовано) - объявление уже пр ) private Set facilities = new HashSet<>(); - @OneToMany(mappedBy = "property") - private List reservations; } From d2223bbb075ce510cf6685cd11cbc38fd8c9ba3a Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:09:27 +0300 Subject: [PATCH 10/14] (TP-77) feat: add validation sender is owner or admin --- .../controllers/PropertyController.java | 27 ++++++++++------- .../rentplace/services/PropertyService.java | 4 +++ .../services/impl/PropertyServiceImpl.java | 30 ++++++++++++++++++- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/PropertyController.java b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/PropertyController.java index ff1eb2c..4281fd7 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/PropertyController.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/controllers/PropertyController.java @@ -18,6 +18,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -43,7 +44,7 @@ public class PropertyController { @ApiResponse(responseCode = "413", description = "Превышен максимальный размер запроса"), @ApiResponse(responseCode = "500", description = "Внутренняя ошибка сервера") }) - @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER')" ) + @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER')") @SecurityRequirement(name = "JWT") public ResponseEntity> uploadMultipleImages( @PathVariable @Parameter(description = "id объявления", example = "10") long id, @@ -51,9 +52,10 @@ public ResponseEntity> uploadMultipleImages( description = "Массив файлов фотографий", required = true, content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE, schema = @Schema(type = "string", format = "binary")) - ) @RequestPart("files") MultipartFile[] files) { - + ) @RequestPart("files") MultipartFile[] files, + Authentication authentication) { + propertyService.ownsPropertyOrAdmin(id, authentication.getName()); List savedImages = propertyService.uploadImages(files, id); return ResponseEntity.ok(savedImages); @@ -104,10 +106,11 @@ public ResponseEntity findById(@Valid @PathVariable @Parameter(desc @ApiResponse(responseCode = "422", description = "Ошибка валидации", content = @Content), @ApiResponse(responseCode = "500", description = "Непредвиденная ошибка со стороны сервера", content = @Content) }) - @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER')" ) + @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER')") @SecurityRequirement(name = "JWT") @PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createPropertyWithImage(@Valid @ModelAttribute PropertyCreateEditDTO propertyCreateEditDTO) { + public ResponseEntity createPropertyWithImage(@Valid @ModelAttribute PropertyCreateEditDTO propertyCreateEditDTO, Authentication authentication) { + propertyService.allowedToCreatePropertyOrAdmin(propertyCreateEditDTO, authentication.getName()); return new ResponseEntity<>(propertyService.createWithImages(propertyCreateEditDTO), HttpStatus.CREATED); } @@ -122,11 +125,13 @@ public ResponseEntity createPropertyWithImage(@Valid @ModelAttribut @ApiResponse(responseCode = "422", description = "Ошибка валидации", content = @Content), @ApiResponse(responseCode = "500", description = "Непредвиденная ошибка со стороны сервера", content = @Content) }) - @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER')" ) + @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER')") @SecurityRequirement(name = "JWT") @PatchMapping(path = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateProperty(@Valid @PathVariable @Parameter(description = "id объявления", example = "1") long id, - @Valid @ModelAttribute PropertyCreateEditDTO propertyCreateEditDTO) { + @Valid @ModelAttribute PropertyCreateEditDTO propertyCreateEditDTO, + Authentication authentication) { + propertyService.ownsPropertyOrAdmin(id, authentication.getName()); return ResponseEntity.ok(propertyService.update(id, propertyCreateEditDTO)); } @@ -141,11 +146,13 @@ public ResponseEntity updateProperty(@Valid @PathVariable @Paramete @ApiResponse(responseCode = "422", description = "Ошибка валидации", content = @Content), @ApiResponse(responseCode = "500", description = "Непредвиденная ошибка со стороны сервера", content = @Content) }) - @PreAuthorize("hasAuthority('ROLE_ADMIN')" ) + @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER')") @SecurityRequirement(name = "JWT") @DeleteMapping("/{id}") - public ResponseEntity deleteProperty(@Valid @PathVariable @Parameter(description = "id объявления", example = "10") long id) { - propertyService.deleteById(id); + public ResponseEntity deleteProperty(@Valid @PathVariable @Parameter(description = "id объявления", example = "10") long id, Authentication authentication) { + if (propertyService.ownsPropertyOrAdmin(id, authentication.getName())) { + propertyService.deleteById(id); + } return ResponseEntity.noContent().build(); } } diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/PropertyService.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/PropertyService.java index c62a3d0..a7d9cb9 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/PropertyService.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/PropertyService.java @@ -10,6 +10,10 @@ public interface PropertyService { + boolean ownsPropertyOrAdmin(long propertyId, String email); + + boolean allowedToCreatePropertyOrAdmin(PropertyCreateEditDTO propertyCreateEditDTO, String email); + List findAll(); PropertyDTO findById(long id); diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/PropertyServiceImpl.java b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/PropertyServiceImpl.java index d7b647f..44d75db 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/PropertyServiceImpl.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/services/impl/PropertyServiceImpl.java @@ -6,12 +6,17 @@ import kattsyn.dev.rentplace.dtos.PropertyDTO; import kattsyn.dev.rentplace.entities.Image; import kattsyn.dev.rentplace.entities.Property; +import kattsyn.dev.rentplace.entities.User; import kattsyn.dev.rentplace.enums.ImageType; +import kattsyn.dev.rentplace.enums.Role; +import kattsyn.dev.rentplace.exceptions.ForbiddenException; +import kattsyn.dev.rentplace.exceptions.NotFoundException; import kattsyn.dev.rentplace.mappers.ImageMapper; import kattsyn.dev.rentplace.mappers.PropertyMapper; import kattsyn.dev.rentplace.repositories.PropertyRepository; import kattsyn.dev.rentplace.services.ImageService; import kattsyn.dev.rentplace.services.PropertyService; +import kattsyn.dev.rentplace.services.UserService; import kattsyn.dev.rentplace.utils.PathResolver; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -29,6 +34,29 @@ public class PropertyServiceImpl implements PropertyService { private final PropertyMapper propertyMapper; private final ImageService imageService; private final ImageMapper imageMapper; + private final UserService userService; + + @Override + @Transactional + public boolean ownsPropertyOrAdmin(long propertyId, String email) { + User user = userService.getUserByEmail(email); + Property property = getPropertyById(propertyId); + + if (user.getRole() == Role.ROLE_ADMIN || property.getOwner().getUserId() == user.getUserId()) { + return true; + } + throw new ForbiddenException(String.format("FORBIDDEN. You are not allowed to edit or delete property id: %s.", propertyId)); + } + + @Override + public boolean allowedToCreatePropertyOrAdmin(PropertyCreateEditDTO propertyCreateEditDTO, String email) { + User user = userService.getUserByEmail(email); + + if (user.getRole() == Role.ROLE_ADMIN || user.getUserId() == propertyCreateEditDTO.getOwnerId()) { + return true; + } + throw new ForbiddenException(String.format("FORBIDDEN. You are not allowed to create property for user email: %s.", email)); + } @Transactional @Override @@ -40,7 +68,7 @@ public List findAll() { @Transactional public Property getPropertyById(long id) { return propertyRepository.findById(id).orElseThrow( - () -> new IllegalArgumentException(String.format("Property not found with id: %d", id)) + () -> new NotFoundException(String.format("Property not found with id: %d", id)) ); } From c47921eba3b623bf418f4d67e08afd5c978a24c3 Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:09:51 +0300 Subject: [PATCH 11/14] (TP-77) feat: add fields examples --- .../main/java/kattsyn/dev/rentplace/dtos/JwtResponse.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtResponse.java b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtResponse.java index f5a432a..f20c3c6 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtResponse.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtResponse.java @@ -1,6 +1,6 @@ package kattsyn.dev.rentplace.dtos; -import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,10 +12,13 @@ @NoArgsConstructor public class JwtResponse { + @Schema(example = "Bearer ") private final String type = "Bearer "; + @Schema(example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YXJzaGFyZDEzMzdAZ21haWwuY29tIiwiZXhwIjoxNzQ2MzA1ODEzLCJyb2xlIjoiUk9MRV9B211JTiIsIm5hbWUiOiJhZG1pbiJ9.4Rg7E39Y4baT9Eld_pkvH0D6S72eepmyd17Ch44K5Fikw32BSbXsnVq4EOnXJgXsQkmkhZrGDHZh-cSGg7pLPg") private String accessToken; //@JsonIgnore + @Schema(example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YXJzaGFyZDEzMzdAZ21haWwuY29tIiwiZXhwIjoxNzQ2MzA1ODEzLCJyb2xlIjoiUk9MRV9Bda1JTiIsIm5hbWUiOiJhZG1pbiJ9.4Rg7E39Y4baT9Eld_pkvH0D6S72eepmydCLCh44K5FikwkdBSbXsnVq4EOnXJgXsQkmkhZrGDHZh-cSGg7pLPg") private String refreshToken; } From 44f59b29a2309978798e6f492bc14d3a0ed84857 Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:10:18 +0300 Subject: [PATCH 12/14] (TP-77) feat: change code field example --- .../src/main/java/kattsyn/dev/rentplace/dtos/JwtRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtRequest.java b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtRequest.java index 79be672..c642856 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtRequest.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/JwtRequest.java @@ -14,7 +14,7 @@ public class JwtRequest { @Schema(description = "Почта пользователя", example = "warshard1337@gmail.com") private String email; - @Schema(description = "Код, который пользователь получил на почту", example = "123456") + @Schema(description = "Код, который пользователь получил на почту", example = "12345") private String code; } From 5152d67cbc14bd79c452320519b2741fc0fa3d1d Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:10:43 +0300 Subject: [PATCH 13/14] (TP-77) feat: change getName return email --- .../main/java/kattsyn/dev/rentplace/auth/JwtAuthentication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/auth/JwtAuthentication.java b/rentplace/src/main/java/kattsyn/dev/rentplace/auth/JwtAuthentication.java index 14a9fef..9568d98 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/auth/JwtAuthentication.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/auth/JwtAuthentication.java @@ -50,6 +50,6 @@ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentExce @Override public String getName() { - return name; + return email; } } From 5e4ff2bb97815dad54b4272cc370a1e4344aa39a Mon Sep 17 00:00:00 2001 From: Kattsyn Date: Sun, 4 May 2025 02:11:12 +0300 Subject: [PATCH 14/14] (TP-77) feat: add email Schema description and example --- .../src/main/java/kattsyn/dev/rentplace/dtos/CodeRequest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/CodeRequest.java b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/CodeRequest.java index 2ae1258..f17e1db 100644 --- a/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/CodeRequest.java +++ b/rentplace/src/main/java/kattsyn/dev/rentplace/dtos/CodeRequest.java @@ -1,5 +1,6 @@ package kattsyn.dev.rentplace.dtos; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,6 +14,7 @@ public class CodeRequest { @Email + @Schema(description = "Почта пользователя", example = "warshard1337@gmail.com") private String email; }