Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,10 @@
<artifactId>dotenv-java</artifactId>
<version>2.3.1</version>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/edu/eci/cvds/prometeo/config/JwtRequestFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package edu.eci.cvds.prometeo.config;

import edu.eci.cvds.prometeo.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

private final JwtUtil jwtUtil;

public JwtRequestFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");

System.out.println("🔍 Checking Authorization header...");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
System.out.println("✅ Authorization header found: " + authHeader);

try {
var claims = jwtUtil.extractClaims(authHeader);

String username = claims.get("userName", String.class);
String role = claims.get("role", String.class).toUpperCase();
String name = claims.get("name", String.class);
String idCard = claims.get("id", String.class);

// Log extracted claims
System.out.println("✅ JWT Claims extracted:");
System.out.println("username = " + username);
System.out.println("role = " + role);
System.out.println("name = " + name);
System.out.println("idCard = " + idCard);

// Save attributes in the request
request.setAttribute("username", username);
request.setAttribute("role", role);
request.setAttribute("name", name);
request.setAttribute("institutionalId", idCard);

// Set authentication in SecurityContext
var authorities = List.of(new SimpleGrantedAuthority("ROLE_" + role));
var auth = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);

} catch (Exception e) {
System.out.println("❌ Error extracting JWT claims: " + e.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
return;
}
} else {
System.out.println("⚠️ Authorization header is missing or does not start with 'Bearer '");
}

chain.doFilter(request, response);
System.out.println("🔍 Post-filter role: " + request.getAttribute("role"));
}
}
24 changes: 24 additions & 0 deletions src/main/java/edu/eci/cvds/prometeo/config/LoggingFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package edu.eci.cvds.prometeo.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class LoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
System.out.println("🔍 Request URI: " + request.getRequestURI());
Copy link

Copilot AI May 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider replacing System.out.println with a logging framework (e.g., SLF4J) to improve performance and manageability of logs in production.

Copilot uses AI. Check for mistakes.
System.out.println("🔍 Method: " + request.getMethod());
System.out.println("🔍 All Attributes: ");
request.getAttributeNames().asIterator().forEachRemaining(attr ->
System.out.println(attr + " = " + request.getAttribute(attr))
);
filterChain.doFilter(request, response);
}
}
32 changes: 23 additions & 9 deletions src/main/java/edu/eci/cvds/prometeo/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
package edu.eci.cvds.prometeo.config;

import lombok.extern.slf4j.Slf4j;
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.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {


private final JwtRequestFilter jwtRequestFilter;

public SecurityConfig(JwtRequestFilter jwtRequestFilter) {
this.jwtRequestFilter = jwtRequestFilter;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Configuración que desactiva toda la seguridad
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/**").permitAll()
)
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable());

.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth

.requestMatchers("/api/users/create").authenticated()

.requestMatchers("/api/users/trainer/**").hasRole("TRAINER")

.anyRequest().hasAnyRole("TRAINER", "STUDENT", "ADMIN")
)
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable())
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new LoggingFilter(), JwtRequestFilter.class);
return http.build();
}
}
59 changes: 50 additions & 9 deletions src/main/java/edu/eci/cvds/prometeo/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import edu.eci.cvds.prometeo.repository.RoutineRepository;
import edu.eci.cvds.prometeo.service.*;
import edu.eci.cvds.prometeo.dto.*;
import jakarta.servlet.http.HttpServletRequest;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
Expand Down Expand Up @@ -116,6 +117,53 @@ public ResponseEntity<List<User>> getUsersByRole(
@Parameter(description = "Role name") @PathVariable String role) {
return ResponseEntity.ok(userService.getUsersByRole(role));
}

@PostMapping("/create")
@Operation(summary = "Create user from JWT", description = "Creates a new user using data from the JWT token")
@ApiResponse(responseCode = "201", description = "User created successfully",
content = @Content(schema = @Schema(implementation = User.class)))
@ApiResponse(responseCode = "400", description = "Invalid input data")
@ApiResponse(responseCode = "409", description = "User already exists")
public ResponseEntity<User> createUser(HttpServletRequest request) {
try {
String institutionalId = (String) request.getAttribute("institutionalId");
String username = (String) request.getAttribute("username");
String name = (String) request.getAttribute("name");
String role = (String) request.getAttribute("role");

// Log extracted attributes
System.out.println("🔍 Extracted attributes:");
System.out.println("institutionalId = " + institutionalId);
System.out.println("username = " + username);
System.out.println("name = " + name);
System.out.println("role = " + role);

// Validate attributes
if (institutionalId == null || name == null || role == null) {
System.out.println("❌ Missing required attributes");
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

// Check if user already exists
if (userService.userExistsByInstitutionalId(institutionalId)) {
System.out.println("⚠️ User with institutionalId " + institutionalId + " already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(null);
}

// Create user
UserDTO userDTO = new UserDTO();
userDTO.setInstitutionalId(institutionalId);
userDTO.setName(name);
userDTO.setRole(role);

User createdUser = userService.createUser(userDTO);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);

} catch (Exception e) {
System.out.println("❌ Error creating user: " + e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
}

@PutMapping("/{id}")
@Operation(summary = "Update user", description = "Updates a user's basic information")
Expand All @@ -127,15 +175,6 @@ public ResponseEntity<User> updateUser(
return ResponseEntity.ok(userService.updateUser(id, userDTO));
}

@PostMapping
@Operation(summary = "Create user", description = "Creates a new user in the system")
@ApiResponse(responseCode = "201", description = "User created successfully", content = @Content(schema = @Schema(implementation = User.class)))
public ResponseEntity<User> createUser(
@Parameter(description = "User data") @RequestBody UserDTO userDTO) {
User createdUser = userService.createUser(userDTO);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}

@DeleteMapping("/{id}")
@Operation(summary = "Delete user", description = "Deletes a user from the system")
@ApiResponse(responseCode = "200", description = "User deleted successfully")
Expand Down Expand Up @@ -835,6 +874,8 @@ public ResponseEntity<List<Object>> getTrainerSessions(
@Parameter(description = "Trainer ID") @PathVariable UUID trainerId) {

List<Object> sessions = gymSessionService.getSessionsByTrainer(trainerId);
System.out.println("🔍 Accessing /trainer/{trainerId}/sessions endpoint");
System.out.println("🔍 Trainer ID: " + trainerId);
return ResponseEntity.ok(sessions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public interface UserRepository extends JpaRepository<User, UUID> {
List<User> findByRole(String role);
Optional<User> findByInstitutionalId(String institutionalId);

boolean existsByInstitutionalId(String institutionalId);

/**
* Finds all users assigned to a specific trainer.
*
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/edu/eci/cvds/prometeo/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,21 @@
public interface UserService {

// ------------- Operaciones básicas de usuario -------------

/**
* Obtener usuario por ID
* @param id ID del usuario
* @return Entidad de usuario
*/
User getUserById(String institutionalId);

boolean userExistsByInstitutionalId(String institutionalId);
/**
* Obtener usuario por ID de tarjeta de identificación
* @param idCard ID de tarjeta de identificación del usuario
* @return Entidad de usuario
*/

/**
* Obtener usuario por ID institucional
* @param institutionalId ID institucional del usuario
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public User getUserById(String institutionalId) {
.orElseThrow(() -> new RuntimeException("User not found with id: " + institutionalId));
}

@Override
public boolean userExistsByInstitutionalId(String institutionalId){
return userRepository.existsByInstitutionalId(institutionalId);
}
@Override
public User getUserByInstitutionalId(String institutionalId) {
return userRepository.findByInstitutionalId(institutionalId)
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/edu/eci/cvds/prometeo/util/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package edu.eci.cvds.prometeo.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.stereotype.Component;

@Component
public class JwtUtil {

private final String SECRET_KEY = "supersecretpassword1234567891011121314"; // Debe ser la misma que usa el microservicio de usuarios

public Claims extractClaims(String token) {
Comment on lines +10 to +12
Copy link

Copilot AI May 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hard-coding secret keys in the code; instead, externalize it using environment variables or secure configuration management.

Suggested change
private final String SECRET_KEY = "supersecretpassword1234567891011121314"; // Debe ser la misma que usa el microservicio de usuarios
public Claims extractClaims(String token) {
private final String SECRET_KEY = System.getenv("JWT_SECRET_KEY");
public JwtUtil() {
if (SECRET_KEY == null || SECRET_KEY.isEmpty()) {
throw new IllegalStateException("Environment variable JWT_SECRET_KEY is not set or is empty.");
}
}
public Claims extractClaims(String token) {

Copilot uses AI. Check for mistakes.
return Jwts.parser()
.setSigningKey(SECRET_KEY.getBytes())
.parseClaimsJws(token.replace("Bearer ", ""))
.getBody();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import edu.eci.cvds.prometeo.repository.RoutineExerciseRepository;
import edu.eci.cvds.prometeo.repository.RoutineRepository;
import edu.eci.cvds.prometeo.service.*;

import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -117,17 +117,52 @@ public void testGetUsersByRole() {
assertEquals(users, response.getBody());
verify(userService).getUsersByRole("STUDENT");
}
@Test
void testCreateUser() {
// Use the exact object instead of any()
when(userService.createUser(userDTO)).thenReturn(testUser);

ResponseEntity<User> response = userController.createUser(userDTO);


@Test
void createUserSuccessfully() {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getAttribute("institutionalId")).thenReturn("A12345");
when(request.getAttribute("username")).thenReturn("testuser");
when(request.getAttribute("name")).thenReturn("Test User");
when(request.getAttribute("role")).thenReturn("USER");

UserDTO userDTO = new UserDTO();
userDTO.setInstitutionalId("A12345");
userDTO.setName("Test User");
userDTO.setRole("USER");

User createdUser = new User();
createdUser.setInstitutionalId("A12345");
createdUser.setName("Test User");
createdUser.setRole("USER");

when(userService.userExistsByInstitutionalId("A12345")).thenReturn(false);
when(userService.createUser(userDTO)).thenReturn(createdUser);

ResponseEntity<User> response = userController.createUser(request);

assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertEquals(testUser, response.getBody());
assertEquals(createdUser, response.getBody());
verify(userService).userExistsByInstitutionalId("A12345");
verify(userService).createUser(userDTO);
}

@Test
void createUserFailsWhenAttributesAreMissing() {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getAttribute("institutionalId")).thenReturn(null);
when(request.getAttribute("name")).thenReturn("Test User");
when(request.getAttribute("role")).thenReturn("USER");

ResponseEntity<User> response = userController.createUser(request);

assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
verify(userService, never()).userExistsByInstitutionalId(anyString());
verify(userService, never()).createUser(any(UserDTO.class));
}



@Test
void testUpdateUser() {
// Use exact matches instead of any()
Expand Down