diff --git a/src/main/kotlin/eu/fosil/okupamicoche/spring/conf/WebSecurityConfig.kt b/src/main/kotlin/eu/fosil/okupamicoche/spring/conf/WebSecurityConfig.kt index b1fb31c..084e941 100644 --- a/src/main/kotlin/eu/fosil/okupamicoche/spring/conf/WebSecurityConfig.kt +++ b/src/main/kotlin/eu/fosil/okupamicoche/spring/conf/WebSecurityConfig.kt @@ -2,6 +2,7 @@ package eu.fosil.okupamicoche.spring.conf import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.ServerHttpSecurity import org.springframework.security.web.server.SecurityWebFilterChain @@ -9,6 +10,7 @@ import org.springframework.web.reactive.config.CorsRegistry import org.springframework.web.reactive.config.EnableWebFlux import org.springframework.web.reactive.config.WebFluxConfigurer + @EnableWebFluxSecurity class SecurityConfig { @Bean @@ -17,6 +19,7 @@ class SecurityConfig { .and() .csrf().disable() .authorizeExchange() + .pathMatchers(HttpMethod.OPTIONS).permitAll() .pathMatchers("/_matrix/**").permitAll() .pathMatchers("/api/public/**").permitAll() .pathMatchers("/api/user/**").authenticated() @@ -34,7 +37,7 @@ class CorsGlobalConfiguration : WebFluxConfigurer { override fun addCorsMappings(corsRegistry: CorsRegistry) { corsRegistry.addMapping("/**") .allowedOrigins("http://localhost:4200") -// .allowedMethods("PUT") + .allowedMethods("*") // .maxAge(3600) } } diff --git a/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateTravelRestController.kt b/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateTravelRestController.kt index 80fc4a2..07a62fa 100644 --- a/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateTravelRestController.kt +++ b/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateTravelRestController.kt @@ -12,10 +12,8 @@ import eu.fosil.okupamicoche.usecases.travel.* import mu.KotlinLogging import org.springframework.data.repository.findByIdOrNull import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.RequestBody -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.bind.annotation.* +import java.security.Principal @RestController @RequestMapping("/api/travel") @@ -28,9 +26,9 @@ class PrivateTravelRestController( private val logger = KotlinLogging.logger {} @RequestMapping("/create") - suspend fun createTravel(@RequestBody @Validated travel: TravelDto): ApiResponse { + suspend fun createTravel(@RequestBody @Validated travel: TravelDto, principal: Principal): ApiResponse { return response { - val driver = userRepository.findByIdOrNull(authService.currentUser().id) + val driver = userRepository.findByIdOrNull(authService.currentUser(principal).id) ?: throw UserIdNotFoundException("Current user not found.") if (travel.id != null && (travelRepository.findByIdOrNull(travel.id) != null)) throw CannotDuplicateIdException("Travel id already exists.") @@ -42,33 +40,34 @@ class PrivateTravelRestController( } @RequestMapping("/cancel") - suspend fun cancelTravel(@RequestParam @Validated travelId: TravelId): ApiResponse { + suspend fun cancelTravel(@RequestParam @Validated travelId: TravelId, principal: Principal): ApiResponse { return response { - throwErrorIfCannotEditTravel(travelId) + throwErrorIfCannotEditTravel(travelId, principal) CancelTravel(travelRepository).cancelTravel(travelId) } } @RequestMapping("/delete") - suspend fun deleteTravel(@RequestParam @Validated travelId: TravelId): ApiResponse { + suspend fun deleteTravel(@RequestParam @Validated travelId: TravelId, principal: Principal): ApiResponse { return response { - throwErrorIfCannotEditTravel(travelId) + throwErrorIfCannotEditTravel(travelId, principal) DeleteTravel(travelRepository).deleteTravel(travelId) } } @RequestMapping("/edit") - suspend fun editTravel(@RequestBody @Validated travel: TravelDto): ApiResponse { + suspend fun editTravel(@RequestBody @Validated travel: TravelDto, principal: Principal): ApiResponse { return response { - throwErrorIfCannotEditTravel(travel.id) + throwErrorIfCannotEditTravel(travel.id, principal) EditTravel(travelRepository).editTravel(travel.toTravel(userRepository)) } } @RequestMapping("/listusertravels") - suspend fun listUserTravels(): ApiResponse> { + suspend fun listUserTravels(principal: Principal): ApiResponse> { + logger.debug { "principal=$principal" } return response { - val userId = authService.currentUser().id + val userId = authService.currentUser(principal).id val useCase = ListUserTravels(travelRepository) val travels = useCase.listUserTravels(userId).map { t -> TravelDto(t) } logger.debug { "travels=$travels" } @@ -77,17 +76,17 @@ class PrivateTravelRestController( } @RequestMapping("/join") - suspend fun join(@RequestParam @Validated travelId: TravelId): ApiResponse { + suspend fun join(@RequestParam @Validated travelId: TravelId, principal: Principal): ApiResponse { return response { - val userId = authService.currentUser().id + val userId = authService.currentUser(principal).id AddTraveler(userRepository, travelRepository).addTraveler(travelId, userId) } } @RequestMapping("/leave") - suspend fun leave(@RequestParam @Validated travelId: TravelId): ApiResponse { + suspend fun leave(@RequestParam @Validated travelId: TravelId, principal: Principal): ApiResponse { return response { - val userId = authService.currentUser().id + val userId = authService.currentUser(principal).id RemoveTraveler(userRepository, travelRepository).removeTraveler(travelId, userId) } } @@ -95,10 +94,11 @@ class PrivateTravelRestController( @RequestMapping("/addtraveler") suspend fun addTraveler( @RequestParam @Validated travelId: TravelId, - @RequestParam @Validated userId: UserId + @RequestParam @Validated userId: UserId, + principal: Principal ): ApiResponse { return response { - throwErrorIfCannotEditTravel(travelId) + throwErrorIfCannotEditTravel(travelId,principal) AddTraveler(userRepository, travelRepository).addTraveler(travelId, userId) } } @@ -106,19 +106,21 @@ class PrivateTravelRestController( @RequestMapping("/removetraveler") suspend fun removeTraveler( @RequestParam @Validated travelId: TravelId, - @RequestParam @Validated userId: UserId + @RequestParam @Validated userId: UserId, + principal: Principal ): ApiResponse { return response { - throwErrorIfCannotEditTravel(travelId) + throwErrorIfCannotEditTravel(travelId, principal) RemoveTraveler(userRepository, travelRepository).removeTraveler(travelId, userId) } } private fun throwErrorIfCannotEditTravel( travelId: TravelId?, + principal: Principal, message: String = "Only admins and travel driver can modify this travel." ) { - if (!authService.canEditTravel(travelId)) + if (!authService.canEditTravel(travelId, principal)) throw InsufficientPermissions(message) } } \ No newline at end of file diff --git a/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateUserRestController.kt b/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateUserRestController.kt index af7f3ae..82db2e5 100644 --- a/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateUserRestController.kt +++ b/src/main/kotlin/eu/fosil/okupamicoche/spring/controller/PrivateUserRestController.kt @@ -13,6 +13,7 @@ import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import java.security.Principal @RestController @RequestMapping("/api/user") @@ -22,9 +23,9 @@ class PrivateUserRestController( ) : ApiRestController { @RequestMapping("/user") - suspend fun getCurrentUserCreateIfNeeded(): ApiResponse { + suspend fun getCurrentUserCreateIfNeeded(principal: Principal): ApiResponse { return response { - val userKeycloak = authService.currentUser() + val userKeycloak = authService.currentUser(principal) var user = userRepository.findByIdOrNull(userKeycloak.id) if (user == null) { @@ -37,9 +38,12 @@ class PrivateUserRestController( } @RequestMapping("/create") - suspend fun createUser(@RequestBody @Validated createUserDto: CreateUserDto): ApiResponse { + suspend fun createUser( + @RequestBody @Validated createUserDto: CreateUserDto, + principal: Principal + ): ApiResponse { return response { - if (!authService.isAdmin()) + if (!authService.isAdmin(principal)) throw InsufficientPermissions("Only admins can create users.") CreateUser(userRepository).createUser(createUserDto.toUser()) } @@ -53,17 +57,17 @@ class PrivateUserRestController( } @RequestMapping("/delete") - suspend fun deleteUser(@RequestBody @Validated userId: UserId): ApiResponse { + suspend fun deleteUser(@RequestBody @Validated userId: UserId, principal: Principal): ApiResponse { return response { - throwErrorIfCannotEditUser(userId) + throwErrorIfCannotEditUser(userId, principal) DeleteUser(userRepository).deleteUser(userId) } } @RequestMapping("/edit") - suspend fun editUser(@RequestBody @Validated userDto: UserDto): ApiResponse { + suspend fun editUser(@RequestBody @Validated userDto: UserDto, principal: Principal): ApiResponse { return response { - throwErrorIfCannotEditUser(userDto.id) + throwErrorIfCannotEditUser(userDto.id, principal) EditUser(userRepository).editUser(userDto.toUser(userRepository)) } } @@ -77,9 +81,10 @@ class PrivateUserRestController( private fun throwErrorIfCannotEditUser( userId: UserId?, + principal: Principal, message: String = "Only admins and travel driver can modify this user." ) { - if (!authService.canEditUser(userId)) + if (!authService.canEditUser(userId, principal)) throw InsufficientPermissions(message) } } \ No newline at end of file diff --git a/src/main/kotlin/eu/fosil/okupamicoche/spring/services/AuthService.kt b/src/main/kotlin/eu/fosil/okupamicoche/spring/services/AuthService.kt index 4b3398f..906d91e 100644 --- a/src/main/kotlin/eu/fosil/okupamicoche/spring/services/AuthService.kt +++ b/src/main/kotlin/eu/fosil/okupamicoche/spring/services/AuthService.kt @@ -1,41 +1,40 @@ package eu.fosil.okupamicoche.spring.services -import eu.fosil.okupamicoche.entities.* +import eu.fosil.okupamicoche.entities.TravelId +import eu.fosil.okupamicoche.entities.UserId +import eu.fosil.okupamicoche.entities.UserIdNotFoundException +import eu.fosil.okupamicoche.entities.UserKeycloak import eu.fosil.okupamicoche.repositories.TravelRepository -import eu.fosil.okupamicoche.repositories.UserRepository import org.springframework.data.repository.findByIdOrNull -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.jwt.Jwt +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken import org.springframework.stereotype.Service +import java.security.Principal @Service class AuthService( - private val userRepository: UserRepository, private val travelRepository: TravelRepository ) { /** * Devuelve el id del usuario actual. */ - fun currentUser(): UserKeycloak { - val authentication = SecurityContextHolder.getContext().authentication - if (authentication.principal is Jwt) { - val jwt = authentication.principal as Jwt - return UserKeycloak(jwt.claims) + fun currentUser(principal: Principal): UserKeycloak { + if (principal is JwtAuthenticationToken) { + return UserKeycloak(principal.token.claims) } throw UserIdNotFoundException() } - fun isAdmin(): Boolean { - return currentUser().admin + fun isAdmin(principal: Principal): Boolean { + return currentUser(principal).admin } - fun canEditTravel(travelId: TravelId?): Boolean { + fun canEditTravel(travelId: TravelId?, principal: Principal): Boolean { val travel = travelRepository.findByIdOrNull(travelId) ?: return false - return currentUser().admin || currentUser().id == travel.driver.id + return currentUser(principal).admin || currentUser(principal).id == travel.driver.id } - fun canEditUser(userId: UserId?): Boolean { + fun canEditUser(userId: UserId?, principal: Principal): Boolean { if (userId == null) return false - return currentUser().admin || currentUser().id == userId + return currentUser(principal).admin || currentUser(principal).id == userId } } \ No newline at end of file