diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/ApiExceptionHandler.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/ApiExceptionHandler.java index 5fce002..6c0bc5e 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/ApiExceptionHandler.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/ApiExceptionHandler.java @@ -6,6 +6,7 @@ import jakarta.validation.ConstraintViolationException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -33,6 +34,13 @@ public class ApiExceptionHandler extends ResponseEntityExceptionHandler { new HttpHeaders(), HttpStatus.UNAUTHORIZED, request); } + @ExceptionHandler(AccessDeniedException.class) + protected ResponseEntity handleAccessDeniedException(AccessDeniedException ex, WebRequest request) { + return handleExceptionInternal(ex, + new ErrorTo(List.of(ex.getMessage()), HttpStatus.FORBIDDEN.value()), + new HttpHeaders(), HttpStatus.FORBIDDEN, request); + } + @ExceptionHandler(UsernameNotFoundException.class) protected ResponseEntity handleUsernameNotFoundException(UsernameNotFoundException ex, WebRequest request) { return handleExceptionInternal(ex, diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java index 90d3499..b2fc680 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java @@ -26,29 +26,29 @@ public class ChatController { } @GetMapping(path = "/by-id/{id}") - public ChatTo get(Authentication authentication, @PathVariable Long id) { - return chatService.getById(authentication, id); + public ChatTo get(@PathVariable Long id) { + return chatService.getById(id); } @DeleteMapping(path = "/by-id/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(Authentication authentication, @PathVariable Long id) { - chatService.deleteById(authentication, id); + public void delete(@PathVariable Long id) { + chatService.deleteById(id); } @PatchMapping(path = "/by-id/{id}") public ChatTo update(Authentication authentication, @PathVariable Long id, @RequestBody ChatCreateTo chatCreateTo) { return chatService.update(authentication, id, chatCreateTo); } - + @GetMapping(path = "/my") public Collection getMy(Authentication authentication) { return chatService.getMy(authentication); } @GetMapping(path = "/by-id/{id}/members") - public Collection getMembers(Authentication authentication, @PathVariable Long id) { - return chatService.getMembers(authentication, id); + public Collection getMembers(@PathVariable Long id) { + return chatService.getMembers(id); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java index 713837d..b38a38a 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java @@ -21,7 +21,7 @@ public class MessageController { @GetMapping(path = "/by-chat/{chatTd}") public Collection get(Authentication authentication, @PathVariable Long chatTd) { - return messageService.getByChat(authentication, chatTd); + return messageService.getByChat(chatTd); } @PostMapping(path = "/by-chat/{chatId}") @@ -30,19 +30,19 @@ public class MessageController { } @PatchMapping(path = "/by-id/{id}") - public MessageTo update(Authentication authentication, @PathVariable long id, @RequestBody MessageCreateTo messageCreateTo) { - return messageService.update(authentication, id, messageCreateTo); + public MessageTo update(@PathVariable long id, @RequestBody MessageCreateTo messageCreateTo) { + return messageService.update(id, messageCreateTo); } @DeleteMapping(path = "/by-id/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(Authentication authentication, @PathVariable long id) { - messageService.delete(authentication, id); + public void delete(@PathVariable long id) { + messageService.delete(id); } @GetMapping - public Collection getAll(Authentication authentication) { - return messageService.readAll(authentication); + public Collection getAll() { + return messageService.readAll(); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java index 13c989d..96497eb 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java @@ -54,8 +54,8 @@ public class PersonController { @DeleteMapping(path = "/by-uuid/{uuid}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteByUuid(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { - personService.deleteByUuid(authentication, uuid); + public void deleteByUuid(@PathVariable String uuid) throws UserNotFoundException { + personService.deleteByUuid(uuid); } @@ -81,14 +81,14 @@ public class PersonController { @PutMapping(path = "/admins/{uuid}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void addAdmin(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { - personService.addAdmin(authentication, uuid); + public void addAdmin(@PathVariable String uuid) throws UserNotFoundException { + personService.addAdmin(uuid); } @DeleteMapping(path = "/admins/{uuid}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteAdmin(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { - personService.removeAdmin(authentication, uuid); + public void deleteAdmin(@PathVariable String uuid) throws UserNotFoundException { + personService.removeAdmin(uuid); } @PutMapping(path = "/following/{uuid}") diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java index 2dba43b..813f4a7 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java @@ -47,19 +47,19 @@ public class PostController { } @PatchMapping(path = "/{id}") - public PostTo update(Authentication authentication, @PathVariable long id, @RequestBody PostCreateTo postCreateTo) { - return postService.updatePost(authentication, id, postCreateTo); + public PostTo update(@PathVariable long id, @RequestBody PostCreateTo postCreateTo) { + return postService.updatePost(id, postCreateTo); } @DeleteMapping(path = "/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(Authentication authentication, @PathVariable long id) { - postService.deletePost(authentication, id); + public void delete(@PathVariable long id) { + postService.deletePost(id); } @GetMapping - public Collection getAll(Authentication authentication) { - return postService.readAll(authentication); + public Collection getAll() { + return postService.readAll(); } } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Chat.java b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Chat.java index 193cf6c..b7c02ae 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Chat.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Chat.java @@ -17,7 +17,7 @@ import java.util.Collection; @NoArgsConstructor @ToString @Accessors(chain = true) -public class Chat implements EntityWithId { +public class Chat { @Id @GeneratedValue private Long id; diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/entity/EntityWithId.java b/server/src/main/java/com/usatiuk/tjv/y/server/entity/EntityWithId.java deleted file mode 100644 index 2fb5701..0000000 --- a/server/src/main/java/com/usatiuk/tjv/y/server/entity/EntityWithId.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.usatiuk.tjv.y.server.entity; - -public interface EntityWithId { - ID getId(); -} diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Message.java b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Message.java index 9f4487b..c8a9ef3 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Message.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Message.java @@ -17,7 +17,7 @@ import java.time.Instant; @NoArgsConstructor @ToString @Accessors(chain = true) -public class Message implements EntityWithId { +public class Message { @Id @GeneratedValue private Long id; diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java index 2911a2c..f192c87 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java @@ -18,7 +18,7 @@ import java.util.Collection; @NoArgsConstructor @ToString @Accessors(chain = true) -public class Person implements EntityWithId { +public class Person { @Id @GeneratedValue(strategy = GenerationType.UUID) private String uuid; @@ -57,8 +57,4 @@ public class Person implements EntityWithId { @ManyToMany(mappedBy = "members") private Collection chats; - @Override - public String getId() { - return uuid; - } } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java index 487dbfa..9fdc910 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java @@ -17,7 +17,7 @@ import java.time.Instant; @NoArgsConstructor @ToString @Accessors(chain = true) -public class Post implements EntityWithId { +public class Post { @Id @GeneratedValue private Long id; @@ -31,9 +31,4 @@ public class Post implements EntityWithId { @CreationTimestamp private Instant createdAt; - - @Override - public Long getId() { - return id; - } } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java b/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java index 96c8c29..e6cc909 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java @@ -10,6 +10,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -31,6 +32,7 @@ import java.util.List; @Configuration @EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class WebSecurityConfig { private final JwtRequestFilter jwtRequestFilter; diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatService.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatService.java index a71690c..5e9ff38 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatService.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatService.java @@ -3,20 +3,27 @@ package com.usatiuk.tjv.y.server.service; import com.usatiuk.tjv.y.server.dto.ChatCreateTo; import com.usatiuk.tjv.y.server.dto.ChatTo; import com.usatiuk.tjv.y.server.dto.PersonTo; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import java.util.Collection; public interface ChatService { ChatTo create(Authentication authentication, ChatCreateTo chatCreateTo); + + @PreAuthorize("@chatService.isCreatorOf(authentication.principal.username, #id)") ChatTo update(Authentication authentication, Long id, ChatCreateTo chatCreateTo); Collection getMy(Authentication authentication); - ChatTo getById(Authentication authentication, Long id); - void deleteById(Authentication authentication, Long id); + @PreAuthorize("@chatService.isMemberOf(authentication.principal.username, #id)") + ChatTo getById(Long id); + @PreAuthorize("@chatService.isMemberOf(authentication.principal.username, #id)") + void deleteById(Long id); - Collection getMembers(Authentication authentication, Long id); + @PreAuthorize("@chatService.isMemberOf(authentication.principal.username, #id)") + Collection getMembers(Long id); boolean isMemberOf(String personUuid, Long chatId); + boolean isCreatorOf(String personUuid, Long chatId); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatServiceImpl.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatServiceImpl.java index 91f6759..08fde29 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatServiceImpl.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/ChatServiceImpl.java @@ -19,7 +19,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Objects; -@Service +@Service("chatService") public class ChatServiceImpl implements ChatService { private final ChatRepository chatRepository; @@ -57,8 +57,6 @@ public class ChatServiceImpl implements ChatService { @Override public ChatTo update(Authentication authentication, Long id, ChatCreateTo chatCreateTo) { var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - if (!Objects.equals(chat.getCreator().getUuid(), authentication.getName())) - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't creator of the chat"); if (Arrays.stream(chatCreateTo.memberUuids()).noneMatch(n -> Objects.equals(n, authentication.getName()))) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Creator of chat must be its member"); @@ -81,29 +79,20 @@ public class ChatServiceImpl implements ChatService { } @Override - public ChatTo getById(Authentication authentication, Long id) { + public ChatTo getById(Long id) { var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - var userRef = entityManager.getReference(Person.class, authentication.getName()); - if (!chat.getMembers().contains(userRef)) - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); - return chatMapper.makeDto(chat); } @Override - public void deleteById(Authentication authentication, Long id) { + public void deleteById(Long id) { var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - if (!Objects.equals(chat.getCreator().getUuid(), authentication.getName())) - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't creator of the chat"); chatRepository.delete(chat); } @Override - public Collection getMembers(Authentication authentication, Long id) { + public Collection getMembers(Long id) { var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - var userRef = entityManager.getReference(Person.class, authentication.getName()); - if (!chat.getMembers().contains(userRef)) - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); return chat.getMembers().stream().map(personMapper::makeDto).toList(); } @@ -113,4 +102,10 @@ public class ChatServiceImpl implements ChatService { var userRef = entityManager.getReference(Person.class, personUuid); return chat.getMembers().contains(userRef); } + + @Override + public boolean isCreatorOf(String personUuid, Long chatId) { + var chat = chatRepository.findById(chatId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); + return Objects.equals(chat.getCreator().getUuid(), personUuid); + } } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageService.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageService.java index 5ad91b1..5d22f6d 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageService.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageService.java @@ -2,16 +2,25 @@ package com.usatiuk.tjv.y.server.service; import com.usatiuk.tjv.y.server.dto.MessageCreateTo; import com.usatiuk.tjv.y.server.dto.MessageTo; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import java.util.Collection; public interface MessageService { - Collection getByChat(Authentication authentication, Long chatId); + @PreAuthorize("@chatService.isMemberOf(authentication.principal.username, #chatId)") + Collection getByChat(Long chatId); + @PreAuthorize("@chatService.isMemberOf(authentication.principal.username, #chatId)") MessageTo addToChat(Authentication authentication, Long chatId, MessageCreateTo messageCreateTo); - MessageTo update(Authentication authentication, Long id, MessageCreateTo msg); - void delete(Authentication authentication, Long id); + @PreAuthorize("@messageService.isAuthorOf(authentication.principal.username, #id)") + MessageTo update(Long id, MessageCreateTo msg); + @PreAuthorize("@messageService.isAuthorOf(authentication.principal.username, #id)") + void delete(Long id); - Collection readAll(Authentication authentication); + @Secured({"ROLE_ADMIN"}) + Collection readAll(); + + boolean isAuthorOf(String userUuid, Long messageId); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageServiceImpl.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageServiceImpl.java index 1865478..c9eb49e 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageServiceImpl.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/MessageServiceImpl.java @@ -7,11 +7,9 @@ import com.usatiuk.tjv.y.server.entity.Chat; import com.usatiuk.tjv.y.server.entity.Message; import com.usatiuk.tjv.y.server.entity.Person; import com.usatiuk.tjv.y.server.repository.MessageRepository; -import com.usatiuk.tjv.y.server.security.UserRoles; import jakarta.persistence.EntityManager; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -19,34 +17,26 @@ import java.util.Collection; import java.util.Objects; import java.util.stream.StreamSupport; -@Service +@Service("messageService") public class MessageServiceImpl implements MessageService { private final MessageRepository messageRepository; private final MessageMapper messageMapper; - private final ChatService chatService; private final EntityManager entityManager; - public MessageServiceImpl(MessageRepository messageRepository, MessageMapper messageMapper, ChatService chatService, EntityManager entityManager) { + public MessageServiceImpl(MessageRepository messageRepository, MessageMapper messageMapper, EntityManager entityManager) { this.messageRepository = messageRepository; this.messageMapper = messageMapper; - this.chatService = chatService; this.entityManager = entityManager; } @Override - public Collection getByChat(Authentication authentication, Long chatId) { - if (!chatService.isMemberOf(authentication.getName(), chatId)) - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); - + public Collection getByChat(Long chatId) { return messageRepository.findByChat_Id(chatId).stream().map(messageMapper::makeDto).toList(); } @Override public MessageTo addToChat(Authentication authentication, Long chatId, MessageCreateTo messageCreateTo) { - if (!chatService.isMemberOf(authentication.getName(), chatId)) - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); - Message message = new Message().setChat(entityManager.getReference(Chat.class, chatId)) .setAuthor(entityManager.getReference(Person.class, authentication.getName())) .setContents(messageCreateTo.contents()); @@ -56,30 +46,28 @@ public class MessageServiceImpl implements MessageService { } @Override - public MessageTo update(Authentication authentication, Long id, MessageCreateTo msg) { + public MessageTo update(Long id, MessageCreateTo msg) { var message = messageRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - if (!Objects.equals(message.getAuthor().getUuid(), authentication.getName())) - throw new ResponseStatusException(HttpStatus.FORBIDDEN); message.setContents(msg.contents()); messageRepository.save(message); return messageMapper.makeDto(message); } @Override - public void delete(Authentication authentication, Long id) { + public void delete(Long id) { var read = messageRepository.findById(id); if (read.isEmpty()) return; - if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN); - } messageRepository.delete(read.get()); } @Override - public Collection readAll(Authentication authentication) { - if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); - + public Collection readAll() { return StreamSupport.stream(messageRepository.findAll().spliterator(), false).map(messageMapper::makeDto).toList(); } + + @Override + public boolean isAuthorOf(String userUuid, Long messageId) { + var msg = messageRepository.findById(messageId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); + return Objects.equals(msg.getAuthor().getUuid(), userUuid); + } } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java index 153fe9c..969bab2 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java @@ -3,6 +3,7 @@ package com.usatiuk.tjv.y.server.service; import com.usatiuk.tjv.y.server.dto.PersonCreateTo; import com.usatiuk.tjv.y.server.dto.PersonTo; import com.usatiuk.tjv.y.server.entity.Person; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import java.util.Collection; @@ -20,7 +21,8 @@ public interface PersonService { PersonTo update(Authentication authentication, PersonCreateTo person); void deleteSelf(Authentication authentication); - void deleteByUuid(Authentication authentication, String uuid); + @PreAuthorize("hasRole('ROLE_ADMIN') or authentication.principal.username == #uuid") + void deleteByUuid(String uuid); Collection readAll(); @@ -31,7 +33,9 @@ public interface PersonService { void removeFollower(Authentication authentication, String followee); Collection getAdmins(); - void addAdmin(Authentication caller, String uuid); - void removeAdmin(Authentication caller, String uuid); + @PreAuthorize("hasRole('ROLE_ADMIN') ") + void addAdmin(String uuid); + @PreAuthorize("hasRole('ROLE_ADMIN') ") + void removeAdmin(String uuid); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java index ba32c57..d778e98 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java @@ -6,19 +6,17 @@ import com.usatiuk.tjv.y.server.dto.converters.PersonMapper; import com.usatiuk.tjv.y.server.entity.Chat; import com.usatiuk.tjv.y.server.entity.Person; import com.usatiuk.tjv.y.server.repository.PersonRepository; -import com.usatiuk.tjv.y.server.security.UserRoles; import com.usatiuk.tjv.y.server.service.exceptions.UserAlreadyExistsException; import com.usatiuk.tjv.y.server.service.exceptions.UserNotFoundException; import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; import java.util.Collection; -import java.util.Objects; import java.util.Optional; import java.util.stream.StreamSupport; @@ -89,32 +87,25 @@ public class PersonServiceImpl implements PersonService { return personMapper.makeDto(found); } - private void deleteByUuid(String uuid) { + @Transactional + public void deleteByUuid(String uuid) { var person = personRepository.findById(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); for (Chat c : person.getChats()) { c.getMembers().remove(person); } - for (Person p : person.getFollowers()) { p.getFollowing().remove(person); - personRepository.save(p); } personRepository.delete(person); } @Override + @Transactional public void deleteSelf(Authentication authentication) { deleteByUuid(authentication.getName()); } - @Override - public void deleteByUuid(Authentication authentication, String uuid) { - if ((!Objects.equals(authentication.getName(), uuid)) && - !authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); - deleteByUuid(uuid); - } @Override public Collection readAll() { @@ -148,24 +139,15 @@ public class PersonServiceImpl implements PersonService { } @Override - public void addAdmin(Authentication caller, String uuid) { - if (!caller.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); - + public void addAdmin(String uuid) { var person = personRepository.findById(uuid).orElseThrow(UserNotFoundException::new); person.setAdmin(true); personRepository.save(person); } @Override - public void removeAdmin(Authentication caller, String uuid) { - if (!caller.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); - + public void removeAdmin(String uuid) { var person = personRepository.findById(uuid).orElseThrow(UserNotFoundException::new); - // TODO - if (personRepository.findByAdminIsTrue().size() == 1) return; - person.setAdmin(false); personRepository.save(person); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java index 8390356..743a243 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java @@ -2,14 +2,18 @@ package com.usatiuk.tjv.y.server.service; import com.usatiuk.tjv.y.server.dto.PostCreateTo; import com.usatiuk.tjv.y.server.dto.PostTo; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import java.util.Collection; public interface PostService { PostTo createPost(Authentication authentication, PostCreateTo postCreateTo); - PostTo updatePost(Authentication authentication, Long id, PostCreateTo postCreateTo); - void deletePost(Authentication authentication, Long id); + @PreAuthorize("@postService.isAuthorOf(authentication.principal.username, #id)") + PostTo updatePost(Long id, PostCreateTo postCreateTo); + @PreAuthorize("@postService.isAuthorOf(authentication.principal.username, #id)") + void deletePost(Long id); PostTo readById(Long id); @@ -18,5 +22,8 @@ public interface PostService { Collection readByPersonFollowees(Authentication authentication); - Collection readAll(Authentication authentication); + @Secured({"ROLE_ADMIN"}) + Collection readAll(); + + boolean isAuthorOf(String userUuid, Long postId); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java index d7f63db..2077b19 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java @@ -6,11 +6,9 @@ import com.usatiuk.tjv.y.server.dto.converters.PostMapper; import com.usatiuk.tjv.y.server.entity.Person; import com.usatiuk.tjv.y.server.entity.Post; import com.usatiuk.tjv.y.server.repository.PostRepository; -import com.usatiuk.tjv.y.server.security.UserRoles; import jakarta.persistence.EntityManager; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -18,7 +16,7 @@ import java.util.Collection; import java.util.Objects; import java.util.stream.StreamSupport; -@Service +@Service("postService") public class PostServiceImpl implements PostService { private final PostRepository postRepository; private final PostMapper postMapper; @@ -60,30 +58,28 @@ public class PostServiceImpl implements PostService { } @Override - public PostTo updatePost(Authentication authentication, Long id, PostCreateTo postCreateTo) { + public PostTo updatePost(Long id, PostCreateTo postCreateTo) { var post = postRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - if (!Objects.equals(post.getAuthor().getUuid(), authentication.getName())) - throw new ResponseStatusException(HttpStatus.FORBIDDEN); post.setText(postCreateTo.text()); postRepository.save(post); return postMapper.makeDto(post); } @Override - public void deletePost(Authentication authentication, Long id) { + public void deletePost(Long id) { var read = postRepository.findById(id); if (read.isEmpty()) return; - if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN); - } postRepository.delete(read.get()); } @Override - public Collection readAll(Authentication authentication) { - if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); - + public Collection readAll() { return StreamSupport.stream(postRepository.findAll().spliterator(), false).map(postMapper::makeDto).toList(); } + + @Override + public boolean isAuthorOf(String userUuid, Long postId) { + var p = postRepository.findById(postId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); + return Objects.equals(p.getAuthor().getUuid(), userUuid); + } } diff --git a/server/src/test/java/com/usatiuk/tjv/y/server/controller/DemoDataDbTest.java b/server/src/test/java/com/usatiuk/tjv/y/server/controller/DemoDataDbTest.java index f24c3d0..665dd0a 100644 --- a/server/src/test/java/com/usatiuk/tjv/y/server/controller/DemoDataDbTest.java +++ b/server/src/test/java/com/usatiuk/tjv/y/server/controller/DemoDataDbTest.java @@ -82,7 +82,7 @@ public abstract class DemoDataDbTest { new Person() .setUsername("person1") .setFullName("Person 1") - .setPassword(passwordEncoder.encode(person1Password))); + .setPassword(passwordEncoder.encode(person1Password)).setAdmin(true)); person1Auth = new TokenResponseTo(jwtTokenService.generateToken(person1.getUuid())); person2 = personRepository.save( new Person() diff --git a/server/src/test/java/com/usatiuk/tjv/y/server/controller/MessageControllerTest.java b/server/src/test/java/com/usatiuk/tjv/y/server/controller/MessageControllerTest.java new file mode 100644 index 0000000..7c1332b --- /dev/null +++ b/server/src/test/java/com/usatiuk/tjv/y/server/controller/MessageControllerTest.java @@ -0,0 +1,47 @@ +package com.usatiuk.tjv.y.server.controller; + +import com.usatiuk.tjv.y.server.dto.ErrorTo; +import com.usatiuk.tjv.y.server.dto.MessageTo; +import com.usatiuk.tjv.y.server.repository.MessageRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; + +public class MessageControllerTest extends DemoDataDbTest { + + @Autowired + MessageRepository messageRepository; + + @Test + void shouldGetMessagesIfAdmin() { + var response = restTemplate.exchange(addr + "/message", HttpMethod.GET, + new HttpEntity<>(createAuthHeaders(person1Auth)), + MessageTo[].class); + + Assertions.assertNotNull(response); + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); + + var toResponse = response.getBody(); + Assertions.assertNotNull(toResponse); + + Assertions.assertEquals(toResponse.length, messageRepository.findAll().spliterator().estimateSize()); + } + + @Test + void shouldNotGetMessagesIfNotAdmin() { + var response = restTemplate.exchange(addr + "/message", HttpMethod.GET, + new HttpEntity<>(createAuthHeaders(person2Auth)), + ErrorTo.class); + + Assertions.assertNotNull(response); + Assertions.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + + var toResponse = response.getBody(); + Assertions.assertNotNull(toResponse); + Assertions.assertEquals(toResponse.code(), HttpStatus.FORBIDDEN.value()); + } + +} diff --git a/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java b/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java index fab4659..81b8d6f 100644 --- a/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java +++ b/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java @@ -68,6 +68,17 @@ public class PersonControllerTest extends DemoDataDbTest { Assertions.assertEquals(personToResponse.fullName(), person1.getFullName()); } + @Test + void shouldDeleteSelf() { + var response = restTemplate.exchange(addr + "/person/self", + HttpMethod.DELETE, new HttpEntity<>(createAuthHeaders(person2Auth)), Object.class); + + Assertions.assertNotNull(response); + Assertions.assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + + Assertions.assertFalse(personRepository.existsById(person2.getUuid())); + } + @Test void shouldGetFollowers() { var response = restTemplate.exchange(addr + "/person/followers",