diff --git a/java/services/auth/src/main/java/com/uva/api/auth/api/UserAPI.java b/java/services/auth/src/main/java/com/uva/api/auth/api/UserAPI.java index b41d97f2763444174087dd536a05b6775f0e5522..da67a0353174a2d2fe816a5925a378a91702d8df 100644 --- a/java/services/auth/src/main/java/com/uva/api/auth/api/UserAPI.java +++ b/java/services/auth/src/main/java/com/uva/api/auth/api/UserAPI.java @@ -34,7 +34,7 @@ public class UserAPI { */ public User getUserByEmail(String email) { String url = USER_API_URL + "?email={email}"; - System.err.println(url); + try { ResponseEntity<User> userResponse = restTemplate.getForEntity(url, User.class, email); return userResponse.getBody(); diff --git a/java/services/auth/src/main/java/com/uva/api/auth/models/jwt/JwtData.java b/java/services/auth/src/main/java/com/uva/api/auth/models/jwt/JwtData.java index adc3e5919dacdc4cc79610778b3c35eff931b22c..463b01198f93095a772809105367aa85b0acad66 100644 --- a/java/services/auth/src/main/java/com/uva/api/auth/models/jwt/JwtData.java +++ b/java/services/auth/src/main/java/com/uva/api/auth/models/jwt/JwtData.java @@ -31,14 +31,6 @@ public class JwtData { private Date expiresAt; public JwtData(DecodedJWT decoded, long ttl) { - - subject = decoded.getSubject(); - if (decoded.getAudience() != null && !decoded.getAudience().isEmpty()) - audience = decoded.getAudience().get(0); - this.ttl = ttl; - issuedAt = decoded.getIssuedAt(); - expiresAt = decoded.getExpiresAt(); - for (Field field : this.getClass().getDeclaredFields()) { field.setAccessible(true); @@ -60,7 +52,17 @@ public class JwtData { e.printStackTrace(); } } + } + + if (decoded.getAudience() != null && !decoded.getAudience().isEmpty()) + audience = decoded.getAudience().get(0); + + this.ttl = ttl; + issuedAt = decoded.getIssuedAt(); + expiresAt = decoded.getExpiresAt(); + + System.out.println("\nDECODED TOKEN: " + this + "\n"); } public boolean isAdmin() { diff --git a/java/services/auth/src/main/java/com/uva/api/auth/models/remote/UserRol.java b/java/services/auth/src/main/java/com/uva/api/auth/models/remote/UserRol.java index 2840aff9dad9a48276102f1ddd993f7318698821..88df13615b669afcf7cad909136f4d229fcc3e80 100644 --- a/java/services/auth/src/main/java/com/uva/api/auth/models/remote/UserRol.java +++ b/java/services/auth/src/main/java/com/uva/api/auth/models/remote/UserRol.java @@ -1,5 +1,5 @@ package com.uva.api.auth.models.remote; public enum UserRol { - ADMIN, HOTEL_ADMIN, CLIENT + ADMIN, MANAGER, CLIENT } diff --git a/java/services/auth/src/main/java/com/uva/api/auth/utils/JwtUtil.java b/java/services/auth/src/main/java/com/uva/api/auth/utils/JwtUtil.java index b083106acaace81d20f6aee63b34021b90e46635..f99023238b67aca0922169ee1138d8fb3b54e8b0 100644 --- a/java/services/auth/src/main/java/com/uva/api/auth/utils/JwtUtil.java +++ b/java/services/auth/src/main/java/com/uva/api/auth/utils/JwtUtil.java @@ -48,6 +48,7 @@ public class JwtUtil { public String generateInternalToken(String service) { String email = service.toLowerCase() + "@internal.com"; + service = service.toUpperCase(); Algorithm algorithm = Algorithm.HMAC256(secretKey); return JWT diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/JwtData.java b/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/JwtData.java index 8d9805347a96034f926d0a78f41f76def818ac5d..269e9c8020b8cbdc8277747a1d0be8bd6bb363f1 100644 --- a/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/JwtData.java +++ b/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/JwtData.java @@ -17,7 +17,7 @@ public class JwtData { private String token; - private Integer id; + private int id = -1; private String name; private String email; private UserRol rol; diff --git a/java/services/hotels/src/main/java/com/uva/api/hotels/models/external/JwtData.java b/java/services/hotels/src/main/java/com/uva/api/hotels/models/external/JwtData.java index 1e6de609ee573a6c74066a5a885df798f7f83083..7041acc775b48282a84e1c6fac18ad536096e2c5 100644 --- a/java/services/hotels/src/main/java/com/uva/api/hotels/models/external/JwtData.java +++ b/java/services/hotels/src/main/java/com/uva/api/hotels/models/external/JwtData.java @@ -17,7 +17,7 @@ public class JwtData { private String token; - private Integer id; + private int id = -1; private String name; private String email; private UserRol rol; diff --git a/java/services/users/src/main/java/com/uva/api/users/api/BookingAPI.java b/java/services/users/src/main/java/com/uva/api/users/api/BookingAPI.java index 2ff83c6b430709200791371f0cf1d354d123e867..60d0800b0b8581162bd4fbd509dc762dfd263dac 100644 --- a/java/services/users/src/main/java/com/uva/api/users/api/BookingAPI.java +++ b/java/services/users/src/main/java/com/uva/api/users/api/BookingAPI.java @@ -1,15 +1,10 @@ package com.uva.api.users.api; -import java.util.Arrays; -import java.util.List; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; -import com.uva.api.users.models.remote.Booking; - @Component public class BookingAPI { @@ -23,12 +18,4 @@ public class BookingAPI { String url = BOOKING_API_URL + "?userId={id}"; restTemplate.delete(url, id); } - - public List<Booking> getAllByUserId(int id) { - String url = BOOKING_API_URL + "?userId={id}"; - Booking[] bookings = restTemplate.getForObject(url, Booking[].class, id); - - return Arrays.asList(bookings); - } - } diff --git a/java/services/users/src/main/java/com/uva/api/users/api/TokenAPI.java b/java/services/users/src/main/java/com/uva/api/users/api/TokenAPI.java index fcb72f8090897e5302c8544fda28a9011876ce54..7c22b94f15695567190b301f4959b3e85e7d79d0 100644 --- a/java/services/users/src/main/java/com/uva/api/users/api/TokenAPI.java +++ b/java/services/users/src/main/java/com/uva/api/users/api/TokenAPI.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.JsonNode; -import com.uva.api.users.models.remote.JwtData; +import com.uva.api.users.models.remote.jwt.JwtData; @Component public class TokenAPI { diff --git a/java/services/users/src/main/java/com/uva/api/users/config/JwtAuthenticationFilter.java b/java/services/users/src/main/java/com/uva/api/users/config/JwtAuthenticationFilter.java index af31ba3b5d07750f687004720a873c348f81a9d1..662165a44e1749a15fb6e6c7dde5f1c0f2493242 100644 --- a/java/services/users/src/main/java/com/uva/api/users/config/JwtAuthenticationFilter.java +++ b/java/services/users/src/main/java/com/uva/api/users/config/JwtAuthenticationFilter.java @@ -1,7 +1,5 @@ package com.uva.api.users.config; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; @@ -9,7 +7,8 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS import org.springframework.stereotype.Component; import com.uva.api.users.models.UserRol; -import com.uva.api.users.models.remote.JwtData; +import com.uva.api.users.models.remote.jwt.JwtData; +import com.uva.api.users.models.remote.jwt.Service; import com.uva.api.users.services.TokenService; import jakarta.servlet.FilterChain; @@ -17,13 +16,11 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.Filter; import java.io.IOException; import java.time.LocalDateTime; import java.util.Collections; -import java.util.Map; @Component public class JwtAuthenticationFilter implements Filter { @@ -47,16 +44,12 @@ public class JwtAuthenticationFilter implements Filter { return service.decodeToken(token); } catch (Exception ex) { System.err.println( - "[" + LocalDateTime.now().toString() + "] Error de verificación del token"); + "[" + LocalDateTime.now().toString() + "] Error de verificación del token\n"); ex.printStackTrace(System.err); return null; } } - private String formatRole(UserRol rol) { - return String.format("ROLE_%s", rol.toString()); - } - @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -64,38 +57,47 @@ public class JwtAuthenticationFilter implements Filter { HttpServletRequest httpRequest = (HttpServletRequest) request; String token = getTokenFromRequest(httpRequest); - System.out.println("[" + LocalDateTime.now().toString() + "] TOKEN: " + token); + System.out.println("[" + LocalDateTime.now().toString() + "] TOKEN: " + token + "\n"); if (token != null) { JwtData jwt = validateAndDecodeToken(token); if (jwt != null) { - System.out.println("-->" + jwt + "<--"); + String email = jwt.getEmail(); + UserRol role = jwt.getRol(); + Service service = jwt.getService(); + String audience = jwt.getAudience(); + + System.out.println("[" + LocalDateTime.now().toString() + "] email=" + email + " role=" + role + + " service=" + service + " audience=" + audience + "\n"); + + if (audience != null) { + // Definimos la autoridad + String authorityValue = null; + if (audience.equals("INTERNAL") && service != null) { + authorityValue = service.toString(); + } else if (audience.equals("EXTERNAL") && role != null) { + authorityValue = String.format("ROLE_%s", role); + } + + if (authorityValue != null && + SecurityContextHolder.getContext().getAuthentication() == null) { + + // Crear la autoridad con la autoridad oportuna + SimpleGrantedAuthority authority = new SimpleGrantedAuthority(authorityValue); + + // Crear autenticación + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + email, + null, Collections.singletonList(authority)); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest)); + + // Establecer autenticación en el contexto de seguridad + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } } } - // String email = getEmailFromToken(jwt); - // UserRol role = getRoleFromToken(jwt); - // System.out.print(" email=" + email + " role=" + role + " "); - - // if (email != null && role != null && - // SecurityContextHolder.getContext().getAuthentication() == null) { - // // Crear la autoridad con el rol del token - // SimpleGrantedAuthority authority = new - // SimpleGrantedAuthority(formatRole(role)); - - // // Crear autenticación - // UsernamePasswordAuthenticationToken authentication = new - // UsernamePasswordAuthenticationToken(email, - // null, Collections.singletonList(authority)); - // authentication.setDetails(new - // WebAuthenticationDetailsSource().buildDetails(httpRequest)); - - // // Establecer autenticación en el contexto de seguridad - // SecurityContextHolder.getContext().setAuthentication(authentication); - // } - // } - // } - // Continuar con el resto de filtros chain.doFilter(request, response); } diff --git a/java/services/users/src/main/java/com/uva/api/users/config/SecurityConfig.java b/java/services/users/src/main/java/com/uva/api/users/config/SecurityConfig.java index 356fa79a2370e580a8808e725732614ca376d76f..c775e9fba594bf230b448595dcb266ff827b89c0 100644 --- a/java/services/users/src/main/java/com/uva/api/users/config/SecurityConfig.java +++ b/java/services/users/src/main/java/com/uva/api/users/config/SecurityConfig.java @@ -2,15 +2,16 @@ package com.uva.api.users.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; +import static org.springframework.http.HttpMethod.*; 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; -import com.uva.api.users.api.TokenAPI; import com.uva.api.users.models.UserRol; -import com.uva.api.users.services.TokenService; +import com.uva.api.users.models.remote.jwt.Service; +import static com.uva.api.users.models.remote.jwt.Service.*; +import static com.uva.api.users.models.UserRol.*; @Configuration @EnableWebSecurity @@ -22,32 +23,66 @@ public class SecurityConfig { this.jwtAuthenticationFilter = jwtAuthenticationFilter; } + private final String[] SERVICES = flat(Service.values()); + + private String[] flat(UserRol... roles) { + return java.util.Arrays.stream(roles) + .map(Enum::toString) + .map(role -> String.format("ROLE_%s", role)) + .toArray(String[]::new); + } + + private String[] flat(Service... services) { + return java.util.Arrays.stream(services) + .map(Enum::toString) + .toArray(String[]::new); + } + + private String[] join(String[]... authority) { + return java.util.Arrays.stream(authority) + .flatMap(java.util.Arrays::stream) + .toArray(String[]::new); + } + + /** + * All services and specified roles + * + * @param roles + * @return + */ + private String[] anyService(UserRol... roles) { + return join(flat(roles), SERVICES); + } + @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + String id = "{id:\\d+}"; + http.csrf(csrf -> csrf.disable()) - // .authorizeHttpRequests(authorize -> authorize - // // Permitir OPTIONS sin autenticación - // .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() - // // Acceso restringido a usuarios y administradores - // .requestMatchers("users", "users/**").hasAnyRole( - // UserRol.CLIENT.toString(), - // UserRol.HOTEL_ADMIN.toString(), - // UserRol.ADMIN.toString()) - // // Acceso restringido a gestores de hoteles y administradores - // .requestMatchers(HttpMethod.GET, "hotels", "hotels/*").hasAnyRole( - // UserRol.CLIENT.toString(), - // UserRol.HOTEL_ADMIN.toString(), - // UserRol.ADMIN.toString()) - - // .requestMatchers("hotels", "hotels/**") - // .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString()) - // // Acceso restringido a cualquier usuario del sistema - // .requestMatchers("bookings", "bookings/**") - // .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString(), - // UserRol.CLIENT.toString()) - // // Rechazar el resto - // .anyRequest().denyAll()) - // // Registra el filtro antes del filtro estándar de autenticación + .authorizeHttpRequests(authorize -> authorize + // Permitir OPTIONS sin autenticación + .requestMatchers(OPTIONS, "/**").permitAll() + // Restringir acceso + // Solo permitimos actualizar el estado al servicio de reservas + .requestMatchers(PATCH, "/users/clients/" + id) + .hasAuthority(BOOKINGS.toString()) + + .requestMatchers("/users/clients/**") + .hasAnyAuthority(anyService(ADMIN, CLIENT)) + + .requestMatchers("/users/managers/**") + .hasAnyAuthority(anyService(ADMIN, MANAGER)) + + .requestMatchers("/users*", "/users/clients", "/users/managers") + .hasAnyAuthority(anyService(ADMIN)) + + // Para las operaciones concretas de los usuarios se permite el acceso + // a los que estén autentificados se limita el acceso en el servicio + .requestMatchers("/users/" + id + "/**").authenticated() + + // Rechazar el resto + .anyRequest().denyAll()) + // Registra el filtro antes del filtro estándar de autenticación .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); diff --git a/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java b/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java index e2b92bac5560947f245eda30fc6603e425d84be0..8342e85dc554754391fd4d319c79ea72c6d92f19 100644 --- a/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java +++ b/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java @@ -11,12 +11,14 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException; import com.uva.api.users.models.ClientStatus; import com.uva.api.users.services.ClientService; +import com.uva.api.users.utils.Utils; @RestController @RequestMapping("users/clients") @@ -31,12 +33,15 @@ public class ClientController { return clientService.findAll(); } - @GetMapping("/{id}") - public ResponseEntity<?> getClientById(@PathVariable int id) { - return clientService.findById(id); + @GetMapping("/{id:\\d+}") + public ResponseEntity<?> getClientById( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id) { + String token = Utils.getToken(authorization); + return clientService.findById(token, id); } - @PatchMapping("/{id}") + @PatchMapping("/{id:\\d+}") public ResponseEntity<?> updateClientState(@PathVariable int id, @RequestBody Map<String, String> json) { String strStatus = json.get("status"); @@ -51,8 +56,11 @@ public class ClientController { } } - @DeleteMapping("/{id}") - public ResponseEntity<?> deleteClient(@PathVariable Integer id) { - return clientService.deleteById(id); + @DeleteMapping("/{id:\\d+}") + public ResponseEntity<?> deleteClient( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id) { + String token = Utils.getToken(authorization); + return clientService.deleteById(token, id); } } diff --git a/java/services/users/src/main/java/com/uva/api/users/controllers/ManagerController.java b/java/services/users/src/main/java/com/uva/api/users/controllers/ManagerController.java index e2763673696656ecf7b255270f2b98502c85effe..11bcf432d6a6810191c540b3b8cdeff587450734 100644 --- a/java/services/users/src/main/java/com/uva/api/users/controllers/ManagerController.java +++ b/java/services/users/src/main/java/com/uva/api/users/controllers/ManagerController.java @@ -6,10 +6,12 @@ import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.uva.api.users.services.ManagerService; +import com.uva.api.users.utils.Utils; @RestController @RequestMapping("users/managers") @@ -24,14 +26,20 @@ public class ManagerController { return managerService.findAll(); } - @GetMapping("/{id}") - public ResponseEntity<?> getHotelManagerById(@PathVariable Integer id) { - return managerService.findById(id); + @GetMapping("/{id:\\d+}") + public ResponseEntity<?> getHotelManagerById( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id) { + String token = Utils.getToken(authorization); + return managerService.findById(token, id); } - @DeleteMapping("/{id}") - public ResponseEntity<?> deleteHotelManager(@PathVariable Integer id) { - return managerService.deleteById(id); + @DeleteMapping("/{id:\\d+}") + public ResponseEntity<?> deleteHotelManager( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id) { + String token = Utils.getToken(authorization); + return managerService.deleteById(token, id); } } diff --git a/java/services/users/src/main/java/com/uva/api/users/controllers/UserController.java b/java/services/users/src/main/java/com/uva/api/users/controllers/UserController.java index bfa14ccc74cb5f694aa983b232c453ef23fe69ac..a47dcd3ebdabe792e44c8c11b42aac06f6ab2fb1 100644 --- a/java/services/users/src/main/java/com/uva/api/users/controllers/UserController.java +++ b/java/services/users/src/main/java/com/uva/api/users/controllers/UserController.java @@ -12,12 +12,14 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException; import com.fasterxml.jackson.databind.JsonNode; + import com.uva.api.users.models.AuthDTO; import com.uva.api.users.services.UserService; import com.uva.api.users.utils.Utils; @@ -35,69 +37,33 @@ public class UserController { return userService.registerNewUser(body); } - @PutMapping("/{id}") - public ResponseEntity<?> updateUserData(@PathVariable int id, @RequestBody Map<String, String> json) { - + @PutMapping("/{id:\\d+}") + public ResponseEntity<?> updateUserData( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id, @RequestBody Map<String, String> json) { + String token = Utils.getToken(authorization); String name = json.get("name"); String email = json.get("email"); if (!Utils.notEmptyStrings(name, email)) throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing required fields"); - return userService.updateUserData(id, name, email); + return userService.updateUserData(token, id, name, email); } - @PutMapping("/{id}/password") - public ResponseEntity<?> updatePassword(@PathVariable int id, @RequestBody JsonNode json) { + @PutMapping("/{id:\\d+}/password") + public ResponseEntity<?> updatePassword( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id, @RequestBody JsonNode json) { String password = json.get("password").asText(); + String token = Utils.getToken(authorization); - if (!Utils.notEmptyStrings(password)) + if (!Utils.notEmptyStrings(token, password)) throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing required fields"); - return userService.changePassword(id, password); + return userService.changePassword(token, id, password); } - // TODO aplicarr verificación - // @Autowired - // private TokenService ser; - - // private String validate(String token) { - // JWTData decoded = ser.decodeToken(token); - // if (decoded == null) { - // return "Invalid token format"; - // } - // UserRol rol = decoded.getRol(); - // String audience = decoded.getAudience(); - // boolean a = (rol == null || rol != UserRol.ADMIN); - // boolean b = (audience == null || !audience.equals("INTERNAL")); - // if (a && b) { - // return "Invalid " + a + " " + b; - // } - // return null; - - // } - - // @GetMapping(params = { "email" }) - // public ResponseEntity<?> getUserByEmail(@RequestParam String email, - // @RequestHeader(value = "Authorization", required = true) String - // authorization) { - // try { - // if (authorization == null) { - // return new ResponseEntity<String>("Missing required fields", - // HttpStatus.BAD_REQUEST); - // } - // String m = validate(authorization.substring(7)); - // if (m != null) { - // return new ResponseEntity<String>(m, HttpStatus.BAD_REQUEST); - // } - // return ResponseEntity.ok(userService.getUserByEmail(email)); - // } catch (HttpClientErrorException e) { - // if (e.getStatusCode() == HttpStatus.NOT_FOUND) - // return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - // throw e; - // } - // } - @GetMapping(params = { "email" }) public ResponseEntity<?> getUserByEmail(@RequestParam String email) { return userService.getUserByEmail(email); @@ -108,13 +74,19 @@ public class UserController { return userService.getAllUsers(); } - @GetMapping("/{id}") - public ResponseEntity<?> getUserById(@PathVariable int id) { - return userService.getUserById(id); + @GetMapping("/{id:\\d+}") + public ResponseEntity<?> getUserById( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id) { + String token = Utils.getToken(authorization); + return userService.getUserById(token, id); } - @DeleteMapping("/{id}") - public ResponseEntity<?> deleteUser(@PathVariable int id) { - return userService.deleteUserById(id); + @DeleteMapping("/{id:\\d+}") + public ResponseEntity<?> deleteUser( + @RequestHeader(value = "Authorization", required = true) String authorization, + @PathVariable int id) { + String token = Utils.getToken(authorization); + return userService.deleteUserById(token, id); } } diff --git a/java/services/users/src/main/java/com/uva/api/users/models/Client.java b/java/services/users/src/main/java/com/uva/api/users/models/Client.java index 08123c2700d996de628184c997b3868909159b4c..e28cca3ee9b4179dbc93ad8d0f6650df5ab21436 100644 --- a/java/services/users/src/main/java/com/uva/api/users/models/Client.java +++ b/java/services/users/src/main/java/com/uva/api/users/models/Client.java @@ -1,9 +1,5 @@ package com.uva.api.users.models; -import java.util.List; - -import com.uva.api.users.models.remote.Booking; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -30,8 +26,7 @@ public class Client extends User { @Enumerated(EnumType.STRING) private ClientStatus status = ClientStatus.NO_BOOKINGS; - public Client(int id, String name, String email, String password, ClientStatus status, - List<Booking> bookings) { + public Client(int id, String name, String email, String password, ClientStatus status) { super(id, name, email, password, UserRol.CLIENT); setStatus(status); } diff --git a/java/services/users/src/main/java/com/uva/api/users/models/Manager.java b/java/services/users/src/main/java/com/uva/api/users/models/Manager.java index eb9fa3107a4a7ad342114c2821e2d6f42ec3a0c9..6e86898d2914f45fa3df8a20e26f77540f8667dc 100644 --- a/java/services/users/src/main/java/com/uva/api/users/models/Manager.java +++ b/java/services/users/src/main/java/com/uva/api/users/models/Manager.java @@ -28,7 +28,7 @@ public class Manager extends User { private JsonNode hotels; public Manager(int id, String name, String email, String password, JsonNode hotels) { - super(id, name, email, password, UserRol.HOTEL_ADMIN); + super(id, name, email, password, UserRol.MANAGER); setHotels(hotels); } } diff --git a/java/services/users/src/main/java/com/uva/api/users/models/UserRol.java b/java/services/users/src/main/java/com/uva/api/users/models/UserRol.java index c08fa995db0daeaf7b32fb16cf759b4e20e36adb..bfc26e25c5fcc73767d01109be40173704d59138 100644 --- a/java/services/users/src/main/java/com/uva/api/users/models/UserRol.java +++ b/java/services/users/src/main/java/com/uva/api/users/models/UserRol.java @@ -1,5 +1,5 @@ package com.uva.api.users.models; public enum UserRol { - ADMIN, HOTEL_ADMIN, CLIENT + ADMIN, MANAGER, CLIENT } diff --git a/java/services/users/src/main/java/com/uva/api/users/models/remote/Booking.java b/java/services/users/src/main/java/com/uva/api/users/models/remote/Booking.java deleted file mode 100644 index 6309bbee3002a7a36a2d088478dd09044004b5d0..0000000000000000000000000000000000000000 --- a/java/services/users/src/main/java/com/uva/api/users/models/remote/Booking.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.uva.api.users.models.remote; - -import java.time.LocalDate; - -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@Setter -@Getter -@NoArgsConstructor -@AllArgsConstructor -@ToString -@EqualsAndHashCode -public class Booking { - private LocalDate startDate; - private LocalDate endDate; -} diff --git a/java/services/users/src/main/java/com/uva/api/users/models/remote/JwtData.java b/java/services/users/src/main/java/com/uva/api/users/models/remote/jwt/JwtData.java similarity index 84% rename from java/services/users/src/main/java/com/uva/api/users/models/remote/JwtData.java rename to java/services/users/src/main/java/com/uva/api/users/models/remote/jwt/JwtData.java index 159db9c7dd58b8639ee64e8f8f6d0c23e300ac35..79e30f71ce4fa51acda13b62ed309a6d80ee62c4 100644 --- a/java/services/users/src/main/java/com/uva/api/users/models/remote/JwtData.java +++ b/java/services/users/src/main/java/com/uva/api/users/models/remote/jwt/JwtData.java @@ -1,4 +1,4 @@ -package com.uva.api.users.models.remote; +package com.uva.api.users.models.remote.jwt; import java.util.Date; @@ -17,11 +17,11 @@ public class JwtData { private String token; - private Integer id; + private int id = -1; private String name; private String email; private UserRol rol; - private String service; + private Service service; private String subject; private String audience; diff --git a/java/services/users/src/main/java/com/uva/api/users/models/remote/jwt/Service.java b/java/services/users/src/main/java/com/uva/api/users/models/remote/jwt/Service.java new file mode 100644 index 0000000000000000000000000000000000000000..debdbba4389ce6631969b6e3bcb887d113722e03 --- /dev/null +++ b/java/services/users/src/main/java/com/uva/api/users/models/remote/jwt/Service.java @@ -0,0 +1,8 @@ +package com.uva.api.users.models.remote.jwt; + +public enum Service { + USERS, + HOTELS, + BOOKINGS, + AUTHENTICATION +} \ No newline at end of file diff --git a/java/services/users/src/main/java/com/uva/api/users/services/ClientService.java b/java/services/users/src/main/java/com/uva/api/users/services/ClientService.java index 84f2826176cdd31a13b18d0559c72e9cf7b84ab2..abd7157e90687c3eb7efb967205bea4c5e6d52d6 100644 --- a/java/services/users/src/main/java/com/uva/api/users/services/ClientService.java +++ b/java/services/users/src/main/java/com/uva/api/users/services/ClientService.java @@ -1,6 +1,5 @@ package com.uva.api.users.services; -import java.time.LocalDate; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -11,7 +10,6 @@ import com.uva.api.users.api.BookingAPI; import com.uva.api.users.models.Client; import com.uva.api.users.models.ClientStatus; import com.uva.api.users.models.UserRol; -import com.uva.api.users.models.remote.Booking; import com.uva.api.users.repositories.ClientRepository; import com.uva.api.users.utils.Utils; @@ -24,16 +22,21 @@ public class ClientService { @Autowired private BookingAPI bookingAPI; + @Autowired + private TokenService tokenService; + public ResponseEntity<List<Client>> findAll() { return ResponseEntity.ok(clientRepository.findAll()); } - public ResponseEntity<Client> findById(int id) { + public ResponseEntity<Client> findById(String token, int id) { + tokenService.assertPermission(token, id); Client client = Utils.assertUser(clientRepository.findById(id)); return ResponseEntity.ok(client); } - public ResponseEntity<Client> deleteById(int id) { + public ResponseEntity<Client> deleteById(String token, int id) { + tokenService.assertPermission(token, id); Client client = Utils.assertUser(clientRepository.findById(id)); bookingAPI.deleteAllByUserId(id); clientRepository.delete(client); @@ -47,42 +50,10 @@ public class ClientService { return ResponseEntity.ok(client); } - // TODO No entiendo donde deberÃa ir esto public ResponseEntity<?> updateClientStatus(int id, ClientStatus status) { Client client = Utils.assertUser(clientRepository.findById(id)); - - List<Booking> bookings = bookingAPI.getAllByUserId(id); - - boolean activeBookings = bookings.stream() - .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // reserva >= ahora - boolean inactiveBookings = bookings.stream() - .anyMatch(booking -> booking.getEndDate().isBefore(LocalDate.now())); // reserva < ahora - - switch (status) { - case NO_BOOKINGS: - if (!bookings.isEmpty()) - throw new IllegalArgumentException("Invalid State: The user has at least one booking"); - break; - case WITH_ACTIVE_BOOKINGS: - if (bookings.isEmpty()) - throw new IllegalArgumentException("Invalid State: The user don't has bookings"); - if (!activeBookings) - throw new IllegalArgumentException("Invalid State: The user don't has active bookings"); - break; - case WITH_INACTIVE_BOOKINGS: - if (bookings.isEmpty()) - throw new IllegalArgumentException("Invalid State: The user don't has bookings"); - if (!inactiveBookings) - throw new IllegalArgumentException("Invalid State: The user don't has inactive bookings"); - break; - default: - break; - } - client.setStatus(status); - client = clientRepository.save(client); - return ResponseEntity.ok(client); } } diff --git a/java/services/users/src/main/java/com/uva/api/users/services/ManagerService.java b/java/services/users/src/main/java/com/uva/api/users/services/ManagerService.java index de135d238b2fb46a98553479c32786133f8ea34a..d47c5fe1cc105941f491701059c703c6249239a0 100644 --- a/java/services/users/src/main/java/com/uva/api/users/services/ManagerService.java +++ b/java/services/users/src/main/java/com/uva/api/users/services/ManagerService.java @@ -19,6 +19,9 @@ public class ManagerService { @Autowired private ManagerRepository managerRepository; + @Autowired + private TokenService tokenService; + public ResponseEntity<Manager> save(Manager manager) { manager = managerRepository.save(manager); return ResponseEntity.ok(manager); @@ -29,12 +32,14 @@ public class ManagerService { return ResponseEntity.ok(managers); } - public ResponseEntity<Manager> findById(int id) { + public ResponseEntity<Manager> findById(String token, int id) { + tokenService.assertPermission(token, id); Manager manager = Utils.assertUser(managerRepository.findById(id)); return ResponseEntity.ok(manager); } - public ResponseEntity<Manager> deleteById(Integer id) { + public ResponseEntity<Manager> deleteById(String token, Integer id) { + tokenService.assertPermission(token, id); Manager manager = Utils.assertUser(managerRepository.findById(id)); hotelApi.deleteAllByManagerId(id); managerRepository.delete(manager); diff --git a/java/services/users/src/main/java/com/uva/api/users/services/TokenService.java b/java/services/users/src/main/java/com/uva/api/users/services/TokenService.java index f5887b926b9404308c5dd64dd429863b36c4503d..583e5733a22fb9cfdd31ca11f02e33e3d130fbd7 100644 --- a/java/services/users/src/main/java/com/uva/api/users/services/TokenService.java +++ b/java/services/users/src/main/java/com/uva/api/users/services/TokenService.java @@ -3,10 +3,12 @@ package com.uva.api.users.services; import java.util.HashMap; import java.util.Map; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; import com.uva.api.users.api.TokenAPI; -import com.uva.api.users.models.remote.JwtData; +import com.uva.api.users.models.remote.jwt.JwtData; @Service public class TokenService { @@ -26,25 +28,56 @@ public class TokenService { public String getServiceToken() { if (ownToken == null || expireSoon(ownToken)) { - System.out.println("Generando token"); + System.out.println("\nGenerando token"); long s = System.currentTimeMillis(); ownToken = api.getServiceToken(); long t = System.currentTimeMillis() - s; - System.out.println("Token Generando en " + t + " ms"); + System.out.println("Token Generando en " + t + " ms\n"); } return ownToken.getToken(); } public JwtData decodeToken(String token) { - if (cache.containsKey(token)) - return cache.get(token); - System.out.println("Actualizando token"); + JwtData decoded; + if (cache.containsKey(token)) { + decoded = cache.get(token); + if (!expireSoon(decoded)) + return cache.get(token); + } + System.out.println("\nActualizando token"); long s = System.currentTimeMillis(); - JwtData decoded = api.decodeToken(token); + decoded = api.decodeToken(token); long t = System.currentTimeMillis() - s; - System.out.println("Actualizando token en " + t + " ms"); + System.out.println("Actualizando token en " + t + " ms\n"); cache.put(token, decoded); return decoded; } + /** + * Valida que la entidad representada con el token tenga permisos de + * administrador, sea un servicio o sea el dueño del recurso (idExpected) + * + * @param token + * @param idExpected + */ + public void assertPermission(String token, int idExpected) { + JwtData decoded = decodeToken(token); + boolean isOwner = decoded.getId() == idExpected; + if (!isOwner) + assertPermission(token); + } + + /** + * Valida que la entidad representada con el token tenga permisos de + * administrador o sea un servicio + * + * @param token + */ + public void assertPermission(String token) { + JwtData decoded = decodeToken(token); + boolean isAdmin = decoded.isAdmin(); + boolean isService = decoded.getService() != null && decoded.getAudience().equals("INTERNAL"); + if (!isAdmin && !isService) + throw new HttpClientErrorException(HttpStatus.FORBIDDEN); + } } diff --git a/java/services/users/src/main/java/com/uva/api/users/services/UserService.java b/java/services/users/src/main/java/com/uva/api/users/services/UserService.java index a78f939b2523e099ecd5aba8ca1a882679d3916f..8874535c43ef8afd07d4eb782202bf643e2efb8f 100644 --- a/java/services/users/src/main/java/com/uva/api/users/services/UserService.java +++ b/java/services/users/src/main/java/com/uva/api/users/services/UserService.java @@ -29,6 +29,9 @@ public class UserService { @Autowired private ManagerService managerService; + @Autowired + private TokenService tokenService; + public ResponseEntity<List<User>> getAllUsers() { List<User> users = userRepository.findAll(); return ResponseEntity.ok(users); @@ -38,7 +41,8 @@ public class UserService { return Utils.assertUser(userRepository.findById(id)); } - public ResponseEntity<User> getUserById(int id) { + public ResponseEntity<User> getUserById(String token, int id) { + tokenService.assertPermission(token, id); User user = assertUserById(id); return ResponseEntity.ok(user); } @@ -68,7 +72,7 @@ public class UserService { user = userRepository.save(admin); break; - case HOTEL_ADMIN: + case MANAGER: Manager manager = new Manager(); BeanUtils.copyProperties(request, manager); user = managerService.save(manager).getBody(); @@ -84,7 +88,8 @@ public class UserService { return ResponseEntity.ok(user); } - public ResponseEntity<User> updateUserData(int id, String name, String email) { + public ResponseEntity<User> updateUserData(String token, int id, String name, String email) { + tokenService.assertPermission(token, id); User user = assertUserById(id); user.setName(name); user.setEmail(email); @@ -92,21 +97,23 @@ public class UserService { return ResponseEntity.ok(user); } - public ResponseEntity<User> changePassword(int id, String password) { + public ResponseEntity<User> changePassword(String token, int id, String password) { + tokenService.assertPermission(token, id); User user = assertUserById(id); user.setPassword(password); user = userRepository.save(user); return ResponseEntity.ok(user); } - public ResponseEntity<User> deleteUserById(int id) { + public ResponseEntity<User> deleteUserById(String token, int id) { + tokenService.assertPermission(token, id); User user = assertUserById(id); switch (user.getRol()) { case CLIENT: - clientService.deleteById(id); + clientService.deleteById(token, id); break; - case HOTEL_ADMIN: - managerService.deleteById(id); + case MANAGER: + managerService.deleteById(token, id); break; case ADMIN: default: diff --git a/java/services/users/src/main/java/com/uva/api/users/utils/Utils.java b/java/services/users/src/main/java/com/uva/api/users/utils/Utils.java index 219bfe4c3e4921c81ca2e1a26d02e65dc067d6ab..b730e9c98366fc4f56bdbc0b1cb9660746315915 100644 --- a/java/services/users/src/main/java/com/uva/api/users/utils/Utils.java +++ b/java/services/users/src/main/java/com/uva/api/users/utils/Utils.java @@ -2,14 +2,23 @@ package com.uva.api.users.utils; import java.util.Optional; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.HttpClientErrorException; + import com.uva.api.users.exceptions.UserNotFoundException; -import com.uva.api.users.models.User; public class Utils { - public static <T extends User> T assertUser(Optional<T> opUser) { + public static <T> T assertUser(Optional<T> opUser) { return opUser.orElseThrow(() -> new UserNotFoundException()); } + public static String getToken(String authorization) { + String prefix = "Bearer "; + if (!authorization.startsWith(prefix)) + throw new HttpClientErrorException(HttpStatus.FORBIDDEN); + return authorization.substring(prefix.length()); + } + public static boolean notEmptyStrings(String... values) { for (String value : values) { if (value == null || value.isEmpty()) { diff --git a/poblate/index.js b/poblate/index.js index 1a95be42b34a0cedb0dd753a3a0a6b316451f299..31b83e1b2ef1a1908cb42af154f1d48711e515af 100644 --- a/poblate/index.js +++ b/poblate/index.js @@ -23,6 +23,15 @@ const loj = (data) => { console.log(JSON.stringify(data, null, 2)); }; +const showError = (error) => { + debug( + "ERROR:", + ERROR + ? error + : error.response?.data ?? error.response?.error ?? error.cause ?? error, + "\n" + ); +}; // Función para calcular fechas pareadas function genDates(ref = new Date()) { // before @@ -62,7 +71,7 @@ const savePost = async (data, first, second = "") => { return await axios.post(first, data); } catch (error) { debug("Trying to log user", data); - debug("ERROR:", ERROR ? error : error.data ?? error.cause); + showError(error); debug("ERROR Al REGISTRO, SE PROCEDE A INTENTAR ACCEDER"); const response = await axios.post(second, data); if (!FORCE) { @@ -74,7 +83,7 @@ const savePost = async (data, first, second = "") => { } catch (error) { console.error("ERROR Al LOGIN"); console.error("\nNo se ha podido comunicar con el servicio de auth"); - debug("ERROR:", ERROR ? error : error.data ?? error.cause); + showError(error); process.exit(-1); } }; @@ -85,8 +94,10 @@ async function register(user) { `${authApi}/register`, `${authApi}/login` ); - debug("User identified successful"); const decoded = jwtDecode(data.token); + debug( + `User identified successful with id=${decoded.id} and token={${data.token}}` + ); user.id = decoded.id; user.token = data.token; return user; @@ -99,7 +110,7 @@ const addUsers = async () => { } const admins = users.filter((u) => u.rol === "ADMIN"); - const managers = users.filter((u) => u.rol === "HOTEL_ADMIN"); + const managers = users.filter((u) => u.rol === "MANAGER"); const clients = users.filter((u) => u.rol === "CLIENT"); return { admins, managers, clients }; @@ -121,7 +132,7 @@ const insertHotel = async ({ manager, hotel }) => { return data; } catch (error) { console.error("ERROR Al INSERTAR HOTEL"); - debug("ERROR:", ERROR ? error : error.data ?? error.cause); + showError(error); process.exit(-1); } }; @@ -130,6 +141,7 @@ async function addHotels(managers) { const hotels = []; for await (const hotel of mockedHotels) { const select = getRandomItem(managers); + hotels.push(await insertHotel({ hotel, manager: select })); } return hotels; @@ -147,7 +159,7 @@ const insertBookings = async (booking, token) => { return data; } catch (error) { console.error("ERROR Al INSERTAR RESERVA"); - debug("ERROR:", ERROR ? error : error.data ?? error.cause); + showError(error); process.exit(-1); } }; @@ -185,12 +197,12 @@ async function init() { debug("ENV:", env, "\n"); const { managers, clients } = await addUsers(); const time = 2; - debug("USUARIOS REGISTRADOS O IDENTIFICADOS"); + debug("USUARIOS REGISTRADOS O IDENTIFICADOS\n\n"); if (DEBUG) { await sleep(time * 1000); } const hotels = await addHotels(managers, 3); - debug("HOTELES REGISTRADOS"); + debug("HOTELES REGISTRADOS\n\n"); if (DEBUG) { await sleep(time * 1000); } diff --git a/poblate/mocks/users.json b/poblate/mocks/users.json index 78d33d9f8ac4af88b3f5dd1c37ec59f15d6cc664..8f377a617dbf39ff254b8ad35ff1b2c3ffc3d3e9 100644 --- a/poblate/mocks/users.json +++ b/poblate/mocks/users.json @@ -9,13 +9,13 @@ "name": "Hotel 1", "email": "hotel1@dev.com", "password": "123", - "rol": "HOTEL_ADMIN" + "rol": "MANAGER" }, { "name": "Hotel 2", "email": "hotel2@dev.com", "password": "123", - "rol": "HOTEL_ADMIN" + "rol": "MANAGER" }, { "name": "Client 1", diff --git a/poblate/package.json b/poblate/package.json index 21775f0b7229718bae14650299fd92f3754cc6f8..d937baf702ebd72781e329477c28467a770e22c6 100644 --- a/poblate/package.json +++ b/poblate/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "node index.js", "prod": "node index.js -- --prod", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node test.js" }, "keywords": [], "author": "", diff --git a/poblate/test.js b/poblate/test.js new file mode 100644 index 0000000000000000000000000000000000000000..f3df7a1989b3508414b6b73fdc90e312e7378f5f --- /dev/null +++ b/poblate/test.js @@ -0,0 +1,17 @@ +const { jwtDecode } = require("jwt-decode"); +const axios = require("axios"); +const args = process.argv; + +const main = async (params) => { + console.log("Peitción"); + + const response = await axios.post("http://localhost:8101/token/service", { + service: "User", + }); + + const r = jwtDecode(response.data.token); + // console.log(JSON.stringify(r, null, 2)); + console.log(JSON.stringify(response.data.data, null, 2)); +}; + +main();