Skip to content
Snippets Groups Projects
Commit 444efb46 authored by migudel's avatar migudel :speech_balloon:
Browse files

Corrección de leves fallos, seguridad agregada a reservas y mejora del poblador

parent cec15f27
No related branches found
No related tags found
2 merge requests!36Develop,!35Dev/sercure services
This commit is part of merge request !35. Comments created here will be created in the context of that merge request.
Showing
with 278 additions and 132 deletions
......@@ -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.bookings.models.external.JwtData;
import com.uva.api.bookings.models.external.jwt.JwtData;
@Component
public class TokenAPI {
......
......
......@@ -12,7 +12,7 @@ public class MyScheduledTasks {
@Autowired
private BookingService bookingService;
@Scheduled(cron = "0 30 0 * * *") // Se ejecuta cada día a medianoche
@Scheduled(cron = "0 30 0 * * *") // Se ejecuta cada día media hora después de medianoche
public void updateInactiveBookings() {
System.out.println(
"Iniciando proceso de actualizar comunicación de cambio de estado para usuarios cuyas reservas finalizaron el dia de hoy");
......@@ -20,6 +20,6 @@ public class MyScheduledTasks {
long updatedUsers = bookingService.performDailyClientsStateUpdate();
long time = System.currentTimeMillis() - start;
System.out.println(updatedUsers + " clients updated in " + time + " ml");
System.out.println("Task Complete! " + updatedUsers + " clients updated in " + time + " ms");
}
}
\ No newline at end of file
......@@ -2,14 +2,16 @@ package com.uva.api.bookings.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.bookings.filter.JwtAuthenticationFilter;
import com.uva.api.bookings.models.external.jwt.Service;
import com.uva.api.bookings.models.external.users.UserRol;
import static com.uva.api.bookings.models.external.users.UserRol.*;
@Configuration
@EnableWebSecurity
......@@ -21,34 +23,60 @@ 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 {
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
// .addFilterBefore(jwtAuthenticationFilter,
// UsernamePasswordAuthenticationFilter.class);
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorize -> authorize
// Permitir OPTIONS sin autenticación
.requestMatchers(OPTIONS, "/**").permitAll()
// Restring acceso
.requestMatchers(GET, "/bookings*").authenticated()
.requestMatchers(POST, "/bookings*")
.hasAnyAuthority(flat(ADMIN, CLIENT))
.requestMatchers(DELETE, "/bookings*")
.hasAnyAuthority(anyService(ADMIN))
.requestMatchers("/bookings/**")
.hasAnyAuthority(anyService(ADMIN, CLIENT))
// Rechazar el resto
.anyRequest().denyAll())
// Registra el filtro antes del filtro estándar de autenticación
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
......
......
......@@ -6,10 +6,9 @@ import org.springframework.web.bind.annotation.*;
import com.uva.api.bookings.models.Booking;
import com.uva.api.bookings.services.BookingService;
import com.uva.api.bookings.utils.Utils;
import java.time.LocalDate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@RestController
@RequestMapping("/bookings")
......@@ -21,13 +20,15 @@ public class BookingController {
@GetMapping
public ResponseEntity<?> getAllBookings(
@RequestHeader(value = "Authorization", required = true) String authorization,
@RequestParam(required = false) LocalDate start,
@RequestParam(required = false) LocalDate end,
@RequestParam(required = false) Integer hotelId,
@RequestParam(required = false) Integer roomId,
@RequestParam(required = false) Integer userId,
@RequestParam(required = false) Integer managerId) {
return bookingService.getBookings(start, end, hotelId, roomId, userId, managerId);
String token = Utils.getToken(authorization);
return bookingService.getBookings(token, start, end, hotelId, roomId, userId, managerId);
}
@PostMapping
......@@ -35,11 +36,6 @@ public class BookingController {
return bookingService.createBooking(booking);
}
@GetMapping("/{id:\\d+}")
public ResponseEntity<?> getBookingById(@PathVariable Integer id) {
return bookingService.getBookingById(id);
}
@DeleteMapping
public ResponseEntity<?> deleteBooking(
@RequestParam(required = false) Integer hotelId,
......@@ -48,8 +44,19 @@ public class BookingController {
return bookingService.deleteBookings(hotelId, managerId, userId);
}
@GetMapping("/{id:\\d+}")
public ResponseEntity<?> getBookingById(
@RequestHeader(value = "Authorization", required = true) String authorization,
@PathVariable Integer id) {
String token = Utils.getToken(authorization);
return bookingService.getBookingById(token, id);
}
@DeleteMapping("/{id:\\d+}")
public ResponseEntity<?> deleteBooking(@PathVariable Integer id) {
return bookingService.deleteBooking(id);
public ResponseEntity<?> deleteBooking(
@RequestHeader(value = "Authorization", required = true) String authorization,
@PathVariable Integer id) {
String token = Utils.getToken(authorization);
return bookingService.deleteBooking(token, id);
}
}
package com.uva.api.bookings.filter;
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;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import com.uva.api.bookings.models.external.JwtData;
import com.uva.api.bookings.models.external.jwt.JwtData;
import com.uva.api.bookings.models.external.jwt.Service;
import com.uva.api.bookings.models.external.users.UserRol;
import com.uva.api.bookings.services.TokenService;
......@@ -42,16 +42,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 {
......@@ -59,37 +55,45 @@ 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);
}
// 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);
// }
// }
// }
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);
}
}
}
}
// Continuar con el resto de filtros
chain.doFilter(request, response);
......
......
package com.uva.api.bookings.models.external;
package com.uva.api.bookings.models.external.jwt;
import java.util.Date;
......@@ -21,7 +21,7 @@ public class JwtData {
private String name;
private String email;
private UserRol rol;
private String service;
private Service service;
private String subject;
private String audience;
......
......
package com.uva.api.bookings.models.external.jwt;
public enum Service {
USERS,
HOTELS,
BOOKINGS,
AUTHENTICATION
}
......@@ -18,7 +18,8 @@ import com.uva.api.bookings.repositories.BookingRepository;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import java.util.function.Consumer;
import java.util.function.Function;
@Service
public class BookingService {
......@@ -26,31 +27,29 @@ public class BookingService {
@Autowired
private BookingRepository bookingRepository;
@Autowired
private TokenService tokenService;
@Autowired
private HotelApi hotelApi;
@Autowired
private UserApi userApi;
/**
* Consulta por bloques filtrados
* - fechas
* - roomId/hotelId
* - userId
*
* @param start
* @param end
* @param hotelId
* @param roomId
* @param userId
* @return
*/
public ResponseEntity<?> getBookings(
String token,
LocalDate start, LocalDate end,
Integer hotelId, Integer roomId,
Integer userId, Integer managerId) {
List<Booking> bookings = null;
if (hotelId != null)
tokenService.assertPermission(token, hotelId);
if (userId != null)
tokenService.assertPermission(token, userId);
if (managerId != null)
tokenService.assertPermission(token, managerId);
if (start != null && end != null) {
if (start.isAfter(end))
throw new InvalidDateRangeException("Start can't be before than end");
......@@ -141,13 +140,23 @@ public class BookingService {
return ResponseEntity.ok(booking);
}
public Booking findById(Integer id) {
return bookingRepository.findById(id)
/**
* Consulta una reserva por id y asegura que la entidad que la consulte sea un
* servicio/administrador o el dueño (cliente)
*
* @param token
* @param id
* @return
*/
public Booking findById(String token, Integer id) {
Booking b = bookingRepository.findById(id)
.orElseThrow(() -> new BookingNotFoundException(id));
tokenService.assertPermission(token, b.getUserId());
return b;
}
public ResponseEntity<?> getBookingById(Integer id) {
Booking booking = findById(id);
public ResponseEntity<?> getBookingById(String token, Integer id) {
Booking booking = findById(token, id);
bookingRepository.deleteById(id);
return ResponseEntity.ok(booking);
}
......@@ -174,8 +183,8 @@ public class BookingService {
return status;
}
public ResponseEntity<?> deleteBooking(Integer id) {
Booking booking = findById(id);
public ResponseEntity<?> deleteBooking(String token, Integer id) {
Booking booking = findById(token, id);
bookingRepository.deleteById(id);
ClientStatus status = calculateClientStatus(id);
......@@ -185,36 +194,40 @@ public class BookingService {
return ResponseEntity.ok(booking);
}
public List<Booking> deleteAllByHotelId(int hotelId) {
// Extraer reservas realizadas al hotel
List<Booking> bookings = bookingRepository.findAllByHotelId(hotelId);
private List<Booking> deleteAll(int id,
Function<Integer, List<Booking>> findAction,
Consumer<Integer> deleteAction) {
List<Booking> bookings = findAction.apply(id);
if (bookings.isEmpty()) {
return new ArrayList<>();
}
bookingRepository.deleteAllByHotelId(hotelId);
deleteAction.accept(id);
return bookings;
}
public List<Booking> deleteAllByManagerId(int managerId) {
List<Booking> bookings = bookingRepository.findAllByManagerId(managerId);
if (bookings.isEmpty()) {
return new ArrayList<>();
}
bookingRepository.deleteAllByManagerId(managerId);
return bookings;
private List<Booking> deleteAllByHotelId(Integer userId) {
return deleteAll(userId,
bookingRepository::findAllByHotelId,
bookingRepository::deleteAllByHotelId);
}
public List<Booking> deleteAllByUserId(Integer userId) {
List<Booking> bookings = bookingRepository.findAllByUserId(userId);
if (bookings.isEmpty()) {
return new ArrayList<>();
private List<Booking> deleteAllByManagerId(Integer userId) {
return deleteAll(userId,
bookingRepository::findAllByManagerId,
bookingRepository::deleteAllByManagerId);
}
bookingRepository.deleteAllByUserId(userId);
return bookings;
private List<Booking> deleteAllByUserId(Integer userId) {
return deleteAll(userId,
bookingRepository::findAllByUserId,
bookingRepository::deleteAllByUserId);
}
public ResponseEntity<?> deleteBookings(
Integer hotelId, Integer managerId, Integer userId) {
Integer hotelId,
Integer managerId, Integer userId) {
List<Booking> bookings;
String message;
if (managerId != null) {
......@@ -235,17 +248,21 @@ public class BookingService {
return ResponseEntity.ok(bookings);
}
/**
* Obtiene los ids de los cliente cuyas reservas finalizaron el dia anterior y
* actualiza su estado al nuevo
*
* @return
*/
public long performDailyClientsStateUpdate() {
LocalDate yesterday = LocalDate.now().minusDays(1);
List<Booking> passedBookings = bookingRepository.findAllPassed(yesterday);
Stream<Integer> userIds = passedBookings.stream().map(b -> b.getUserId()).distinct();
userIds.forEach(userId -> {
return passedBookings.stream().map(Booking::getUserId).distinct().map(userId -> {
ClientStatus status = calculateClientStatus(userId);
userApi.updateClientState(userId, status);
});
return userIds.count();
return userId;
}).count();
}
}
......@@ -4,10 +4,12 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import com.uva.api.bookings.api.TokenAPI;
import com.uva.api.bookings.models.external.JwtData;
import com.uva.api.bookings.models.external.jwt.JwtData;
@Service
public class TokenService {
......@@ -24,25 +26,57 @@ 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))
JwtData decoded;
if (cache.containsKey(token)) {
decoded = cache.get(token);
if (!expireSoon(decoded))
return cache.get(token);
System.out.println("Actualizando 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);
}
}
package com.uva.api.bookings.utils;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpClientErrorException;
public class Utils {
public static String getToken(String authorization) {
String prefix = "Bearer ";
if (!authorization.startsWith(prefix))
throw new HttpClientErrorException(HttpStatus.FORBIDDEN);
return authorization.substring(prefix.length());
}
}
package com.uva.api.users.models;
import java.time.LocalDate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
......
......
......@@ -5,14 +5,50 @@ const { jwtDecode } = require("jwt-decode");
const dev = require("./environments/env");
const prod = require("./environments/env.production");
const getKongHots = (host) => {
const defaultHost = "localhost";
const defaultPort = 8000;
if (host.match(/^(\d{1,3}\.){3}[\d]{1,3}:\d{1,5}$/)) {
return host; // host completo
} else if (host.match(/^([\d]{1,3}\.){3}[\d]{1,3}$/)) {
return host + `:${defaultPort}`; // hostname, agregar puerto por defecto
} else if (host.match(/^:\d{1,5}/)) {
return defaultHost + host; // puerto, agregar hostname por defecto
}
return `${defaultHost}:${defaultPort}`;
};
const getKongEnv = (env) => {
const { authApi, hotelsApi, bookingsApi } = env;
const apply = (api) =>
KONG ? api.replace(/:\/\/[\w.-]+(:\d+)?/, `://${KONG_SERVICE}`) : api;
return {
authApi: apply(authApi),
hotelsApi: apply(hotelsApi),
bookingsApi: apply(bookingsApi),
};
};
// Environments consts
const args = process.argv;
const isProduction = args.includes("--prod");
const DEBUG = args.includes("--debug") || args.includes("-d");
const FORCE = args.includes("--force") || args.includes("-f");
const ERROR = args.includes("--error") || args.includes("-e");
const kongLow = args.indexOf("-k");
const kongIndex = isProduction
? kongLow != -1
? kongLow
: args.indexOf("--kong")
: -1;
const KONG = kongIndex !== -1;
const KONG_SERVICE = getKongHots(
args.length > kongIndex ? args[kongIndex + 1] ?? "" : ""
);
const env = (isProduction ? prod : dev).env;
const env = getKongEnv((isProduction ? prod : dev).env);
const { authApi, hotelsApi, bookingsApi } = env;
const debug = (...values) => {
......@@ -128,7 +164,7 @@ const insertHotel = async ({ manager, hotel }) => {
Authorization: `Bearer ${manager.token}`,
},
});
debug("Hotel added successful");
debug("Hotel added successful, identified by id " + data.id);
return data;
} catch (error) {
console.error("ERROR Al INSERTAR HOTEL");
......@@ -155,7 +191,7 @@ const insertBookings = async (booking, token) => {
Authorization: `Bearer ${token}`,
},
});
debug("Booking added successful");
debug("Booking added successful, identified by id " + data.id);
return data;
} catch (error) {
console.error("ERROR Al INSERTAR RESERVA");
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment