From a949fd779993d3ecd0903355f62fadf805ae722c Mon Sep 17 00:00:00 2001 From: Bruno Mendes Date: Wed, 3 May 2023 11:39:10 +0100 Subject: [PATCH] Add auth service methods for validating jwt roles --- .../website/backend/config/auth/AuthConfig.kt | 6 ++- .../backend/controller/AuthController.kt | 16 +++++--- .../ni/website/backend/service/AuthService.kt | 39 +++++++++---------- .../activity/AbstractActivityService.kt | 2 + 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/config/auth/AuthConfig.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/config/auth/AuthConfig.kt index cee55cae..322a9a58 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/config/auth/AuthConfig.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/config/auth/AuthConfig.kt @@ -6,7 +6,9 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet import org.springframework.beans.factory.annotation.Qualifier import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder @@ -23,6 +25,8 @@ import org.springframework.web.filter.CorsFilter import org.springframework.web.servlet.HandlerExceptionResolver @Configuration +@EnableWebSecurity +@EnableMethodSecurity class AuthConfig( val authConfigProperties: AuthConfigProperties, @Qualifier("handlerExceptionResolver") val exceptionResolver: HandlerExceptionResolver @@ -71,7 +75,7 @@ class AuthConfig( fun rolesConverter(): JwtAuthenticationConverter? { val authoritiesConverter = JwtGrantedAuthoritiesConverter() - authoritiesConverter.setAuthorityPrefix("ROLE_") + authoritiesConverter.setAuthorityPrefix("") val converter = JwtAuthenticationConverter() converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter) return converter diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt index fd0b778d..261c9173 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/AuthController.kt @@ -1,6 +1,7 @@ package pt.up.fe.ni.website.backend.controller import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.security.core.context.SecurityContextHolder import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -8,12 +9,13 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import pt.up.fe.ni.website.backend.dto.auth.LoginDto import pt.up.fe.ni.website.backend.dto.auth.TokenDto -import pt.up.fe.ni.website.backend.model.Account +import pt.up.fe.ni.website.backend.model.Project +import pt.up.fe.ni.website.backend.repository.ActivityRepository import pt.up.fe.ni.website.backend.service.AuthService @RestController @RequestMapping("/auth") -class AuthController(val authService: AuthService) { +class AuthController(val authService: AuthService, val repository: ActivityRepository) { @PostMapping("/new") fun getNewToken(@RequestBody loginDto: LoginDto): Map { val account = authService.authenticate(loginDto.email, loginDto.password) @@ -28,10 +30,14 @@ class AuthController(val authService: AuthService) { return mapOf("access_token" to accessToken) } + @PreAuthorize("isAuthenticated()") @GetMapping - @PreAuthorize("@authService.hasPermission(1)") - fun checkAuthentication(): Map { + fun checkAuthentication(): Map { + val authentication = SecurityContextHolder.getContext().authentication val account = authService.getAuthenticatedAccount() - return mapOf("authenticated_user" to account) + return mapOf( + "authenticated_user" to account, + "jwt_permissions" to authentication.authorities.map { it.toString() }.toList() + ) } } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/AuthService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/AuthService.kt index cc050ad6..06908818 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/AuthService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/AuthService.kt @@ -2,6 +2,7 @@ package pt.up.fe.ni.website.backend.service import java.time.Duration import java.time.Instant +import java.util.Locale import java.util.stream.Collectors import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.GrantedAuthority @@ -13,31 +14,40 @@ import org.springframework.security.oauth2.jwt.JwtDecoder import org.springframework.security.oauth2.jwt.JwtEncoder import org.springframework.security.oauth2.jwt.JwtEncoderParameters import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException -import org.springframework.stereotype.Component import org.springframework.stereotype.Service import pt.up.fe.ni.website.backend.config.auth.AuthConfigProperties import pt.up.fe.ni.website.backend.model.Account import pt.up.fe.ni.website.backend.model.Project -import pt.up.fe.ni.website.backend.model.permissions.Permission import pt.up.fe.ni.website.backend.repository.ActivityRepository +import pt.up.fe.ni.website.backend.service.activity.ActivityService @Service -@Component("authService") class AuthService( val accountService: AccountService, + val activityService: ActivityService, val authConfigProperties: AuthConfigProperties, val jwtEncoder: JwtEncoder, val jwtDecoder: JwtDecoder, private val passwordEncoder: PasswordEncoder, val repository: ActivityRepository ) { - fun hasPermission(permission: Permission): Boolean { - println("here") - val authorities = SecurityContextHolder.getContext().authentication.authorities - for (a in authorities) { - println(a) + fun hasPermission(permission: String): Boolean { + val authentication = SecurityContextHolder.getContext().authentication + return authentication.authorities.any { + it.toString() == permission + } + } + + fun hasActivityPermission(activityId: Long, permission: String): Boolean { + val authentication = SecurityContextHolder.getContext().authentication + + val activity = activityService.getActivityById(activityId) + val name = activity.title.filter { it.isLetterOrDigit() }.uppercase(Locale.getDefault()) + + return authentication.authorities.any { it -> + val payload = it.toString().split(":") + payload.size == 2 && payload[0] == name && payload[1].split("-").any { p -> p == permission } } - return false } fun authenticate(email: String, password: String): Account { @@ -87,17 +97,6 @@ class AuthService( } private fun generateAuthorities(account: Account): List { - /*val testRole = Role("MEMBER", Permissions(listOf(Permission.CREATE_ACCOUNT, Permission.CREATE_ACTIVITY)), false) - val testPerActivityRole = PerActivityRole( - Permissions(listOf(Permission.CREATE_ACCOUNT, Permission.CREATE_ACTIVITY)) - ) - val activity = Project("Test Activity", "Test Description", mutableListOf(), mutableListOf()) - testPerActivityRole.activity = activity - repository.save(activity) - testRole.associatedActivities.add(testPerActivityRole) - account.roles.add(testRole) - */ - return account.roles.map { it.toString().split(" ") }.flatten().distinct().map { diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index bda10799..c720308b 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -12,6 +12,8 @@ abstract class AbstractActivityService( protected val repository: ActivityRepository, protected val accountService: AccountService ) { + fun getAll() = repository.findAll().toList() + fun getActivityById(id: Long): T = repository.findByIdOrNull(id) ?: throw NoSuchElementException(ErrorMessages.activityNotFound(id))