diff --git a/src/main/java/com/guams/review/configuration/Advice.java b/src/main/java/com/guams/review/configuration/Advice.java index 6e12a5d..9eec97a 100644 --- a/src/main/java/com/guams/review/configuration/Advice.java +++ b/src/main/java/com/guams/review/configuration/Advice.java @@ -3,7 +3,7 @@ package com.guams.review.configuration; import com.guams.review.exception.AlreadyExistsException; import com.guams.review.exception.InvalidNameOrPasswordException; import com.guams.review.exception.NotFoundException; -import com.guams.review.exception.UnauthorizedExecption; +import com.guams.review.exception.ForbiddenExecption; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -24,11 +24,11 @@ public class Advice { @ExceptionHandler(value = InvalidNameOrPasswordException.class) public ResponseEntity handleInvalidNameOrPassword(InvalidNameOrPasswordException exception) { - return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN); } - @ExceptionHandler(value = UnauthorizedExecption.class) - public ResponseEntity handleUnauthorizedExecption(UnauthorizedExecption exception) { - return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED); + @ExceptionHandler(value = ForbiddenExecption.class) + public ResponseEntity handleUnauthorizedExecption(ForbiddenExecption exception) { + return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN); } } diff --git a/src/main/java/com/guams/review/configuration/JwtAuthenticationFilter.java b/src/main/java/com/guams/review/configuration/JwtAuthenticationFilter.java index 25dd174..524fad3 100644 --- a/src/main/java/com/guams/review/configuration/JwtAuthenticationFilter.java +++ b/src/main/java/com/guams/review/configuration/JwtAuthenticationFilter.java @@ -1,5 +1,4 @@ package com.guams.review.configuration; -import com.guams.review.model.dao.Author; import com.guams.review.service.AuthorService; import io.jsonwebtoken.JwtException; import jakarta.servlet.FilterChain; diff --git a/src/main/java/com/guams/review/configuration/SpringSecurityConfig.java b/src/main/java/com/guams/review/configuration/SpringSecurityConfig.java index 0ab54af..4f341b6 100644 --- a/src/main/java/com/guams/review/configuration/SpringSecurityConfig.java +++ b/src/main/java/com/guams/review/configuration/SpringSecurityConfig.java @@ -3,6 +3,7 @@ import com.guams.review.service.AuthorService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -12,6 +13,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; @Configuration @@ -25,15 +31,36 @@ public class SpringSecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { return http .csrf(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // Ajout de la configuration CORS .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/authors/login", "/api/authors/register").permitAll() - .requestMatchers("/api/authors/me").authenticated() // Autorise les utilisateurs authentifiés - .anyRequest().authenticated() + .requestMatchers(HttpMethod.GET, + "/api/authors", + "/api/authors/{id}", + "/api/authors/{id}/posts", + "/api/posts", + "/api/posts/{id}").permitAll() // Autorise les GET sur ces routes + .requestMatchers("/api/authors/login", "/api/authors/register").permitAll() // Autorise sans authentification + .requestMatchers("/api/authors/me").authenticated() // Requiert authentification + .anyRequest().authenticated() // Toutes les autres routes nécessitent une authentification ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Ajoute le filtre JWT .build(); } + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("http://localhost:4200")); // N'autorise que localhost:4200 + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); // Spécifie les méthodes autorisées + configuration.setAllowedHeaders(List.of("Authorization", "Content-Type")); // Limite les en-têtes autorisés + configuration.setAllowCredentials(true); // Autorise l'utilisation des cookies ou des tokens + configuration.setMaxAge(3600L); // Cache la configuration CORS pendant 1 heure + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); // Applique les règles à toutes les routes + return source; + } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); diff --git a/src/main/java/com/guams/review/configuration/WebConfig.java b/src/main/java/com/guams/review/configuration/WebConfig.java deleted file mode 100644 index 38057c4..0000000 --- a/src/main/java/com/guams/review/configuration/WebConfig.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.guams.review.configuration; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@EnableWebMvc -public class WebConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**"); // autorise toutes les requêtes externe - } -} \ No newline at end of file diff --git a/src/main/java/com/guams/review/controller/AuthorController.java b/src/main/java/com/guams/review/controller/AuthorController.java index dd67868..2c27513 100644 --- a/src/main/java/com/guams/review/controller/AuthorController.java +++ b/src/main/java/com/guams/review/controller/AuthorController.java @@ -4,13 +4,14 @@ import com.guams.review.configuration.JwtTokenUtil; import com.guams.review.exception.AlreadyExistsException; import com.guams.review.exception.InvalidNameOrPasswordException; import com.guams.review.exception.NotFoundException; -import com.guams.review.exception.UnauthorizedExecption; +import com.guams.review.exception.ForbiddenExecption; import com.guams.review.model.AuthorRepository; import com.guams.review.model.dao.Author; +import com.guams.review.model.dao.AuthorToken; import com.guams.review.model.dao.Post; import com.guams.review.service.AuthorService; import com.guams.review.service.mapper.Mapper; -import com.guams.review.service.mapper.AuthorWithToken; +import com.guams.review.service.mapper.ReturnableAuthor; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -41,12 +42,12 @@ public class AuthorController { private final Mapper mapper; @GetMapping - public List getUsers() { + public List getUsers() { return authorService.list(); } @GetMapping("/{id}") - public AuthorWithToken findUser(@PathVariable UUID id) { + public ReturnableAuthor findUser(@PathVariable UUID id) { Author author = authorService.findById(id).orElseThrow(() -> new NotFoundException("Author not found")); return mapper.mapAuthor(author); } @@ -82,14 +83,14 @@ public class AuthorController { } @PostMapping("/login") - public AuthorWithToken authorLogin(@RequestBody Author author) { + public AuthorToken authorLogin(@RequestBody Author author) { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(author.getName(), author.getPassword()) ); String token = jwtTokenUtil.generateToken(author.getName()); - return mapper.mapAuthor(author).setToken(token); + return new AuthorToken().setToken(token); } catch (Exception e) { throw new InvalidNameOrPasswordException(e.getMessage()); } @@ -105,10 +106,10 @@ public class AuthorController { return new ResponseEntity<>(authorRepository.save(author.setRole("USER")).setPassword(""), HttpStatus.CREATED); } - @GetMapping("/me") + @GetMapping(value = "/me", produces = "application/json") public Author getAuthenticatedUser(Authentication authentication) { if (authentication == null || !authentication.isAuthenticated()) { - throw new UnauthorizedExecption("You are not authorized to access this resource"); + throw new ForbiddenExecption("You are not authorized to access this resource"); } String username = authentication.getName(); @@ -120,5 +121,4 @@ public class AuthorController { return author.setPassword(""); } - } diff --git a/src/main/java/com/guams/review/controller/PostController.java b/src/main/java/com/guams/review/controller/PostController.java index a1499f7..cdd91da 100644 --- a/src/main/java/com/guams/review/controller/PostController.java +++ b/src/main/java/com/guams/review/controller/PostController.java @@ -1,12 +1,17 @@ package com.guams.review.controller; import com.guams.review.exception.NotFoundException; +import com.guams.review.exception.ForbiddenExecption; +import com.guams.review.model.AuthorRepository; +import com.guams.review.model.dao.Author; import com.guams.review.model.dao.Post; +import com.guams.review.service.AuthorService; import com.guams.review.service.PostService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -21,6 +26,8 @@ import java.util.List; public class PostController { private final PostService postService; + private final AuthorService authorService; + private final AuthorRepository authorRepository; @GetMapping public List listPosts() { @@ -33,30 +40,59 @@ public class PostController { } @PutMapping("/{id}") - public void updatePost(@PathVariable Long id, @RequestBody Post updatedPost) { - Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); - postService.insert(updatedPost - .setId(postToUpdate.getId()) - .setIllustration(postToUpdate.getIllustration()) - .setPublicationDate(postToUpdate.getPublicationDate())); + public void updatePost(@PathVariable Long id, @RequestBody Post updatedPost, Authentication authentication) { + if (authentication == null) { + throw new ForbiddenExecption("You have to login to do that"); + } + Author authenticatedAuthor = authorRepository.findByName(authentication.getName()); + //Si l'user authent possède ce post + if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) { + Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); + postService.insert(updatedPost + .setId(postToUpdate.getId()) + .setIllustration(postToUpdate.getIllustration()) + .setPublicationDate(postToUpdate.getPublicationDate())); + } else { + throw new ForbiddenExecption("You do not have permission to update this post"); + } } @PutMapping(value = "{id}/illustration", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) - public void updateIllustration(@PathVariable Long id, @RequestPart MultipartFile illustration) throws IOException { - Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); - postService.insert(postToUpdate.setIllustration(illustration.getBytes())); + public void updateIllustration(@PathVariable Long id, @RequestPart MultipartFile illustration, Authentication authentication) throws IOException { + if (authentication == null) { + throw new ForbiddenExecption("You have to login to do that"); + } + Author authenticatedAuthor = authorRepository.findByName(authentication.getName()); + if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) { + Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); + postService.insert(postToUpdate.setIllustration(illustration.getBytes())); + } else { + throw new ForbiddenExecption("You do not have permission to update this post"); + } } @PostMapping - public ResponseEntity addPost(@RequestBody Post postToCreate) { + public ResponseEntity addPost(@RequestBody Post postToCreate, Authentication authentication) { Assert.isNull(postToCreate.getId(), "Post id must be null"); + if (authentication == null) { + throw new ForbiddenExecption("You have to login to do that"); + } return new ResponseEntity<>(postService.insert(postToCreate.setPublicationDate(LocalDate.now())), HttpStatus.CREATED); } @DeleteMapping("{id}") - public void deletePost(@PathVariable Long id) { - Post postToDelete = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); - postService.delete(postToDelete.getId()); + public void deletePost(@PathVariable Long id, Authentication authentication) { + if (authentication == null) { + throw new ForbiddenExecption("You have to login to do that"); + } + Author authenticatedAuthor = authorRepository.findByName(authentication.getName()); + if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) { + Post postToDelete = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); + postService.delete(postToDelete.getId()); + } else { + throw new ForbiddenExecption("You do not have permission to delete this post"); + } + } } diff --git a/src/main/java/com/guams/review/exception/ForbiddenExecption.java b/src/main/java/com/guams/review/exception/ForbiddenExecption.java new file mode 100644 index 0000000..d10ebf5 --- /dev/null +++ b/src/main/java/com/guams/review/exception/ForbiddenExecption.java @@ -0,0 +1,7 @@ +package com.guams.review.exception; + +public class ForbiddenExecption extends RuntimeException { + public ForbiddenExecption(String message) { + super(message); + } +} diff --git a/src/main/java/com/guams/review/exception/UnauthorizedExecption.java b/src/main/java/com/guams/review/exception/UnauthorizedExecption.java deleted file mode 100644 index 579779a..0000000 --- a/src/main/java/com/guams/review/exception/UnauthorizedExecption.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.guams.review.exception; - -public class UnauthorizedExecption extends RuntimeException { - public UnauthorizedExecption(String message) { - super(message); - } -} diff --git a/src/main/java/com/guams/review/model/dao/AuthorToken.java b/src/main/java/com/guams/review/model/dao/AuthorToken.java new file mode 100644 index 0000000..ae2c0ab --- /dev/null +++ b/src/main/java/com/guams/review/model/dao/AuthorToken.java @@ -0,0 +1,12 @@ +package com.guams.review.model.dao; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Getter +@Setter +@Accessors(chain = true) +public class AuthorToken { + String token; +} diff --git a/src/main/java/com/guams/review/service/AuthorService.java b/src/main/java/com/guams/review/service/AuthorService.java index 506247c..ee266a7 100644 --- a/src/main/java/com/guams/review/service/AuthorService.java +++ b/src/main/java/com/guams/review/service/AuthorService.java @@ -1,13 +1,13 @@ package com.guams.review.service; import com.guams.review.exception.NotFoundException; -import com.guams.review.exception.UnauthorizedExecption; +import com.guams.review.exception.ForbiddenExecption; import com.guams.review.model.AuthorRepository; import com.guams.review.model.PostRepository; import com.guams.review.model.dao.Author; import com.guams.review.model.dao.Post; import com.guams.review.service.mapper.Mapper; -import com.guams.review.service.mapper.AuthorWithToken; +import com.guams.review.service.mapper.ReturnableAuthor; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -28,7 +28,7 @@ public class AuthorService implements UserDetailsService private final AuthorRepository authorRepository; private final PostRepository postRepository; - public List list() { + public List list() { return authorRepository.findAll().stream() .map(mapper::mapAuthor) .toList(); @@ -42,7 +42,7 @@ public class AuthorService implements UserDetailsService return authorRepository.findById(id); } - public AuthorWithToken insert(Author author) { + public ReturnableAuthor insert(Author author) { return mapper.mapAuthor(authorRepository.save(author)); } @@ -58,15 +58,15 @@ public class AuthorService implements UserDetailsService public Author verifyIfUserIsAuthorized(Authentication authentication, UUID id) { if (authentication == null || !authentication.isAuthenticated()) { - throw new UnauthorizedExecption("You have to login first"); + throw new ForbiddenExecption("You have to login first"); } - Author authorToUpdate = findById(id).orElseThrow(() -> new NotFoundException("Author not found")); + Author author = findById(id).orElseThrow(() -> new NotFoundException("Author not found")); String username = authentication.getName(); Author authorAuthenticated = authorRepository.findByName(username); - if (authorAuthenticated.getId() != authorToUpdate.getId() && !authorAuthenticated.getRole().equals("ADMIN")) { - throw new UnauthorizedExecption("Specified Author is not authorized to do that"); + if (authorAuthenticated.getId().compareTo(author.getId()) != 0 && !authorAuthenticated.getRole().equals("ADMIN")) { + throw new ForbiddenExecption("Specified Author is not authorized to do that"); } - return authorToUpdate; + return author; } public void delete(Author author) { diff --git a/src/main/java/com/guams/review/service/mapper/Mapper.java b/src/main/java/com/guams/review/service/mapper/Mapper.java index 341f671..e467766 100644 --- a/src/main/java/com/guams/review/service/mapper/Mapper.java +++ b/src/main/java/com/guams/review/service/mapper/Mapper.java @@ -8,8 +8,8 @@ public class Mapper { public Mapper() {} - public AuthorWithToken mapAuthor(Author author) { - return new AuthorWithToken() + public ReturnableAuthor mapAuthor(Author author) { + return new ReturnableAuthor() .setId(author.getId()) .setName(author.getName()) .setRole(author.getRole()) diff --git a/src/main/java/com/guams/review/service/mapper/AuthorWithToken.java b/src/main/java/com/guams/review/service/mapper/ReturnableAuthor.java similarity index 83% rename from src/main/java/com/guams/review/service/mapper/AuthorWithToken.java rename to src/main/java/com/guams/review/service/mapper/ReturnableAuthor.java index c7e8587..26b6c02 100644 --- a/src/main/java/com/guams/review/service/mapper/AuthorWithToken.java +++ b/src/main/java/com/guams/review/service/mapper/ReturnableAuthor.java @@ -9,10 +9,9 @@ import java.util.UUID; @Getter @Setter @Accessors(chain = true) -public class AuthorWithToken { +public class ReturnableAuthor { private UUID id; private String name; private byte[] profilePicture; private String role; - private String token; }