Wallah l'auth elle est censée taffer, mais pt'être y'a un douille, check ça mon reuf 👀

This commit is contained in:
Guams 2024-12-20 15:45:17 +01:00
parent d14ae93f95
commit c3dd337c7d
12 changed files with 124 additions and 67 deletions

View File

@ -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<Object> handleInvalidNameOrPassword(InvalidNameOrPasswordException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED);
return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
}
@ExceptionHandler(value = UnauthorizedExecption.class)
public ResponseEntity<Object> handleUnauthorizedExecption(UnauthorizedExecption exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED);
@ExceptionHandler(value = ForbiddenExecption.class)
public ResponseEntity<Object> handleUnauthorizedExecption(ForbiddenExecption exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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
}
}

View File

@ -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<AuthorWithToken> getUsers() {
public List<ReturnableAuthor> 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("");
}
}

View File

@ -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<Post> 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<Post> addPost(@RequestBody Post postToCreate) {
public ResponseEntity<Post> 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");
}
}
}

View File

@ -0,0 +1,7 @@
package com.guams.review.exception;
public class ForbiddenExecption extends RuntimeException {
public ForbiddenExecption(String message) {
super(message);
}
}

View File

@ -1,7 +0,0 @@
package com.guams.review.exception;
public class UnauthorizedExecption extends RuntimeException {
public UnauthorizedExecption(String message) {
super(message);
}
}

View File

@ -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;
}

View File

@ -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<AuthorWithToken> list() {
public List<ReturnableAuthor> 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) {

View File

@ -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())

View File

@ -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;
}