diff --git a/pom.xml b/pom.xml index f676de058c4adbbe3345246460cf29092b4bf0bb..8bc8ffd845da4b073a79e7f03ca8484b0359b81e 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,26 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + diff --git a/src/main/java/com/example/gestionstagesbackend/Config/SecurityConfig.java b/src/main/java/com/example/gestionstagesbackend/Config/SecurityConfig.java index 83a9bc2ef3b5a0e5d668d430fc46ed6eb4436063..781796f43f3be8fea9cca37c8dbf7bfdcd555268 100644 --- a/src/main/java/com/example/gestionstagesbackend/Config/SecurityConfig.java +++ b/src/main/java/com/example/gestionstagesbackend/Config/SecurityConfig.java @@ -2,18 +2,46 @@ package com.example.gestionstagesbackend.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.authentication.AuthenticationManager; @Configuration +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) // Enables @PreAuthorize public class SecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf.disable()) // ✅ Disable CSRF protection + .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth - .anyRequest().permitAll() // ✅ Allow all requests (for testing) - ); + .requestMatchers("/api/auth/**").permitAll() // Public routes + .requestMatchers("/api/students/**").hasAnyRole("SUPERVISEUR", "ADMIN") + .requestMatchers("/api/stages/**").hasAnyRole("ENTREPRISE", "ADMIN") + .requestMatchers("/api/enterprises/**").hasRole("ADMIN") + .requestMatchers("/api/candidacies/**").hasAnyRole("ETUDIANT", "ADMIN") + .anyRequest().authenticated() + ) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + return http.build(); } } diff --git a/src/main/java/com/example/gestionstagesbackend/controllers/AuthController.java b/src/main/java/com/example/gestionstagesbackend/controllers/AuthController.java new file mode 100644 index 0000000000000000000000000000000000000000..19f6cd703458d1fc5c700b7a17675972634e4fbb --- /dev/null +++ b/src/main/java/com/example/gestionstagesbackend/controllers/AuthController.java @@ -0,0 +1,54 @@ +package com.example.gestionstagesbackend.controllers; + +import com.example.gestionstagesbackend.model.User; +import com.example.gestionstagesbackend.services.UserService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +@CrossOrigin(origins = "*") +public class AuthController { + private final UserService userService; + private final AuthenticationManager authenticationManager; + + public AuthController(UserService userService, AuthenticationManager authenticationManager) { + this.userService = userService; + this.authenticationManager = authenticationManager; + } + + @PostMapping("/register") + public ResponseEntity<?> registerUser(@RequestBody User user) { + if (userService.findByUsername(user.getUsername()).isPresent()) { + return ResponseEntity.badRequest().body(Map.of("message", "Username is already taken")); + } + + if (user.getRoles() == null || user.getRoles().isEmpty()) { + user.setRoles(Collections.singleton("ROLE_ETUDIANT")); // Default role + } + + User newUser = userService.registerUser(user); + return ResponseEntity.ok(newUser); + } + + + @PostMapping("/login") + public ResponseEntity<?> loginUser(@RequestBody Map<String, String> loginRequest) { + String username = loginRequest.get("username"); + String password = loginRequest.get("password"); + + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(username, password) + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + return ResponseEntity.ok(Map.of("message", "Login successful")); + } +} diff --git a/src/main/java/com/example/gestionstagesbackend/controllers/CandidacyController.java b/src/main/java/com/example/gestionstagesbackend/controllers/CandidacyController.java index 807aacdd223e0d0bebbb654e4a9da7242de93233..94a57462885201636c36f4f010db134089b4b862 100644 --- a/src/main/java/com/example/gestionstagesbackend/controllers/CandidacyController.java +++ b/src/main/java/com/example/gestionstagesbackend/controllers/CandidacyController.java @@ -105,19 +105,38 @@ public class CandidacyController { } Candidacy candidacy = existingCandidacy.get(); + + // Update Student if (updatedCandidacy.getStudent() != null && updatedCandidacy.getStudent().getId() != null) { candidacy.setStudent(updatedCandidacy.getStudent()); } + + // Update Stage if (updatedCandidacy.getStage() != null && updatedCandidacy.getStage().getId() != null) { candidacy.setStage(updatedCandidacy.getStage()); } + + // Update UnivSupervisor if (updatedCandidacy.getUnivSupervisor() != null) { candidacy.setUnivSupervisor(updatedCandidacy.getUnivSupervisor()); } + // Update Request State (FirstRequest, Accepted, Declined) + if (updatedCandidacy.getRequestState() != null) { + candidacy.setRequestState(updatedCandidacy.getRequestState()); + } + + // Update Accepted State (wait, inProgress, terminated) + if (updatedCandidacy.getAcceptedState() != null) { + candidacy.setAcceptedState(updatedCandidacy.getAcceptedState()); + } + + // Save the updated candidacy Candidacy savedCandidacy = candidacyService.saveCandidacy(candidacy); return ResponseEntity.ok(savedCandidacy); } + + } diff --git a/src/main/java/com/example/gestionstagesbackend/enums/Role.java b/src/main/java/com/example/gestionstagesbackend/enums/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..54b761a6726aba102aa3ce8e8d5e3436e85754c4 --- /dev/null +++ b/src/main/java/com/example/gestionstagesbackend/enums/Role.java @@ -0,0 +1,8 @@ +package com.example.gestionstagesbackend.enums; + +public enum Role { + ROLE_ETUDIANT, // voir liste étudiant, voir liste stages + ROLE_ENTREPRISE, // CRUD stage + ROLE_SUPERVISEUR, // CRUD étudiant, voir liste stages + ROLE_ADMIN // Tous les droits +} diff --git a/src/main/java/com/example/gestionstagesbackend/model/Candidacy.java b/src/main/java/com/example/gestionstagesbackend/model/Candidacy.java index 4bd2a554db955e36ecce11f8e27822f8c65e4c32..3b6d2936e94a863eacefda78e3a587382431434f 100644 --- a/src/main/java/com/example/gestionstagesbackend/model/Candidacy.java +++ b/src/main/java/com/example/gestionstagesbackend/model/Candidacy.java @@ -1,5 +1,7 @@ package com.example.gestionstagesbackend.model; +import com.example.gestionstagesbackend.enums.AcceptedCandidacyStateKind; +import com.example.gestionstagesbackend.enums.CandidacyRequestState; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; @@ -17,7 +19,7 @@ public class Candidacy { @ManyToOne @JoinColumn(name = "stage_id", nullable = false) - @JsonIgnoreProperties({"candidacies", "enterprise"}) // Ensures that Candidacy includes Stage but avoids loops + @JsonIgnoreProperties({"candidacies"}) // Ensures that Candidacy includes Stage but avoids loops private Stage stage; // suppression de @JsonIgnoreProperties pour inclure Stage in response @@ -27,12 +29,22 @@ public class Candidacy { @JsonIgnoreProperties({"supervisedStages"}) // Prevents recursion private UnivSupervisor univSupervisor; + // Enum to store the request state (FirstRequest, Accepted, Declined) + @Enumerated(EnumType.STRING) + private CandidacyRequestState requestState = CandidacyRequestState.FirstRequest; + + // Enum to store the accepted candidacy state (wait, inProgress, terminated) + @Enumerated(EnumType.STRING) + private AcceptedCandidacyStateKind acceptedState = AcceptedCandidacyStateKind.wait; + public Candidacy() {} public Candidacy(Student student, Stage stage, UnivSupervisor univSupervisor) { this.student = student; this.stage = stage; this.univSupervisor = univSupervisor; + this.requestState = CandidacyRequestState.FirstRequest; // Default state + this.acceptedState = AcceptedCandidacyStateKind.wait; // Default state } public Long getId() { return id; } @@ -46,4 +58,11 @@ public class Candidacy { public UnivSupervisor getUnivSupervisor() { return univSupervisor; } public void setUnivSupervisor(UnivSupervisor univSupervisor) { this.univSupervisor = univSupervisor; } + + public CandidacyRequestState getRequestState() { return requestState; } + public void setRequestState(CandidacyRequestState requestState) { this.requestState = requestState; } + + public AcceptedCandidacyStateKind getAcceptedState() { return acceptedState; } + public void setAcceptedState(AcceptedCandidacyStateKind acceptedState) { this.acceptedState = acceptedState; } + } diff --git a/src/main/java/com/example/gestionstagesbackend/model/Enterprise.java b/src/main/java/com/example/gestionstagesbackend/model/Enterprise.java index 4f5f64942bd8c53b90c0f77ddd7b2732adb09b4b..b9758204734dd825ff25ba8e06cfafe10063dfd6 100644 --- a/src/main/java/com/example/gestionstagesbackend/model/Enterprise.java +++ b/src/main/java/com/example/gestionstagesbackend/model/Enterprise.java @@ -3,7 +3,7 @@ package com.example.gestionstagesbackend.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import java.util.List; -import com.fasterxml.jackson.annotation.JsonManagedReference; +import java.util.stream.Collectors; @Entity public class Enterprise { @@ -14,9 +14,18 @@ public class Enterprise { private String address; @OneToMany(mappedBy = "enterprise", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - //@JsonManagedReference // Ensures that stages are included in Enterprise JSON responses + @JsonIgnoreProperties({"enterprise"}) // Avoid infinite recursion private List<Stage> stages; + /*@Transient // This field is not stored in the database + public List<Candidacy> getCandidacies() { + if (stages != null) { + return stages.stream() + .flatMap(stage -> stage.getCandidacies().stream()) // Collect all candidacies + .collect(Collectors.toList()); + } + return List.of(); // Return an empty list if no stages + }*/ public Enterprise() {} diff --git a/src/main/java/com/example/gestionstagesbackend/model/User.java b/src/main/java/com/example/gestionstagesbackend/model/User.java new file mode 100644 index 0000000000000000000000000000000000000000..085a5920831fccf6ea38e8f3a4abf8d30f107284 --- /dev/null +++ b/src/main/java/com/example/gestionstagesbackend/model/User.java @@ -0,0 +1,40 @@ +package com.example.gestionstagesbackend.model; + +import jakarta.persistence.*; +import java.util.Set; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String username; + private String email; + private String password; + + @ElementCollection(fetch = FetchType.EAGER) + private Set<String> roles; // ["ROLE_ETUDIANT", "ROLE_ENTREPRISE", "ROLE_SUPERVISEUR", "ROLE_ADMIN"] + + public User() {} + + public User(String username, String email, String password, Set<String> roles) { + this.username = username; + this.email = email; + this.password = password; + this.roles = roles; + } + + public Long getId() { return id; } + public String getUsername() { return username; } + public String getEmail() { return email; } + public String getPassword() { return password; } + public Set<String> getRoles() { return roles; } + + public void setId(Long id) { this.id = id; } + public void setUsername(String username) { this.username = username; } + public void setEmail(String email) { this.email = email; } + public void setPassword(String password) { this.password = password; } + public void setRoles(Set<String> roles) { this.roles = roles; } +} diff --git a/src/main/java/com/example/gestionstagesbackend/repositories/UserRepository.java b/src/main/java/com/example/gestionstagesbackend/repositories/UserRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..80e7e38735f8a8ace296d41e9e47c87fab54e0af --- /dev/null +++ b/src/main/java/com/example/gestionstagesbackend/repositories/UserRepository.java @@ -0,0 +1,11 @@ +package com.example.gestionstagesbackend.repositories; + +import com.example.gestionstagesbackend.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +public interface UserRepository extends JpaRepository<User, Long> { + Optional<User> findByUsername(String username); + boolean existsByUsername(String username); + boolean existsByEmail(String email); +} diff --git a/src/main/java/com/example/gestionstagesbackend/services/UserService.java b/src/main/java/com/example/gestionstagesbackend/services/UserService.java new file mode 100644 index 0000000000000000000000000000000000000000..8617474750f9ee9b2595e93d7ccff5191c12258b --- /dev/null +++ b/src/main/java/com/example/gestionstagesbackend/services/UserService.java @@ -0,0 +1,27 @@ +package com.example.gestionstagesbackend.services; + +import com.example.gestionstagesbackend.model.User; +import com.example.gestionstagesbackend.repositories.UserRepository; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import java.util.Optional; + +@Service +public class UserService { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + public User registerUser(User user) { + user.setPassword(passwordEncoder.encode(user.getPassword())); // Hash password + return userRepository.save(user); + } + + public Optional<User> findByUsername(String username) { + return userRepository.findByUsername(username); + } +}