From c587289ab3841c8b5aa542fabbc4fba6d85d447e Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Mon, 11 Aug 2025 08:25:06 +0200 Subject: [PATCH] fix: Check for subject not authorized party --- .../de/twomartens/timetable/auth/UserController.kt | 7 +++---- .../AuthorizationManagerConfiguration.kt | 8 ++++---- .../roles/{CheckParty.kt => CheckSubject.kt} | 2 +- ...ionManager.kt => SubjectAuthorizationManager.kt} | 13 +++++++------ .../timetable/formation/FormationController.kt | 10 +++++----- .../twomartens/timetable/route/RouteController.kt | 10 +++++----- 6 files changed, 25 insertions(+), 25 deletions(-) rename module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/{CheckParty.kt => CheckSubject.kt} (90%) rename module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/{PartyAuthorizationManager.kt => SubjectAuthorizationManager.kt} (80%) diff --git a/module-server/src/main/kotlin/de/twomartens/timetable/auth/UserController.kt b/module-server/src/main/kotlin/de/twomartens/timetable/auth/UserController.kt index 3d00ccc..be0e3f9 100644 --- a/module-server/src/main/kotlin/de/twomartens/timetable/auth/UserController.kt +++ b/module-server/src/main/kotlin/de/twomartens/timetable/auth/UserController.kt @@ -63,9 +63,8 @@ class UserController( ) ]) body: User ): ResponseEntity { - val authentication = SecurityContextHolder.getContext().authentication - val jwt = authentication as JwtAuthenticationToken - val authorizedParty = jwt.token.getClaimAsString("azp") + val authentication = SecurityContextHolder.getContext().authentication as JwtAuthenticationToken + val subject = authentication.token.getClaimAsString("sub") ?: authentication.name var created = false val userIdConverted = UserId.of(NonEmptyString(userId)) @@ -73,7 +72,7 @@ class UserController( if (user == null) { created = true user = mapper.mapToDB(body) - } else if (authorizedParty == userId) { + } else if (subject == userId) { user.name = NonEmptyString(body.name) user.email = Email.of(NonEmptyString(body.email)) } else { diff --git a/module-server/src/main/kotlin/de/twomartens/timetable/configuration/AuthorizationManagerConfiguration.kt b/module-server/src/main/kotlin/de/twomartens/timetable/configuration/AuthorizationManagerConfiguration.kt index 008695e..4c12730 100644 --- a/module-server/src/main/kotlin/de/twomartens/timetable/configuration/AuthorizationManagerConfiguration.kt +++ b/module-server/src/main/kotlin/de/twomartens/timetable/configuration/AuthorizationManagerConfiguration.kt @@ -1,7 +1,7 @@ package de.twomartens.timetable.configuration -import de.twomartens.timetable.configuration.roles.CheckParty -import de.twomartens.timetable.configuration.roles.PartyAuthorizationManager +import de.twomartens.timetable.configuration.roles.CheckSubject +import de.twomartens.timetable.configuration.roles.SubjectAuthorizationManager import org.springframework.aop.support.annotation.AnnotationMatchingPointcut import org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE import org.springframework.context.annotation.Bean @@ -16,8 +16,8 @@ class AuthorizationManagerConfiguration { @Bean @Role(ROLE_INFRASTRUCTURE) fun checkPartyInterceptor(): AuthorizationManagerBeforeMethodInterceptor { - val pointcut = AnnotationMatchingPointcut(null, CheckParty::class.java, true) - val interceptor = AuthorizationManagerBeforeMethodInterceptor(pointcut, PartyAuthorizationManager()) + val pointcut = AnnotationMatchingPointcut(null, CheckSubject::class.java, true) + val interceptor = AuthorizationManagerBeforeMethodInterceptor(pointcut, SubjectAuthorizationManager()) interceptor.order = AuthorizationInterceptorsOrder.PRE_AUTHORIZE.order return interceptor } diff --git a/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/CheckParty.kt b/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/CheckSubject.kt similarity index 90% rename from module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/CheckParty.kt rename to module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/CheckSubject.kt index 74e5ce8..392ecaa 100644 --- a/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/CheckParty.kt +++ b/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/CheckSubject.kt @@ -6,6 +6,6 @@ import java.lang.annotation.Inherited @Retention(AnnotationRetention.RUNTIME) @Inherited @MustBeDocumented -annotation class CheckParty( +annotation class CheckSubject( val partyParamName: String // Name of method argument holding the party ID ) \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/PartyAuthorizationManager.kt b/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/SubjectAuthorizationManager.kt similarity index 80% rename from module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/PartyAuthorizationManager.kt rename to module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/SubjectAuthorizationManager.kt index 5a05208..ae02bac 100644 --- a/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/PartyAuthorizationManager.kt +++ b/module-server/src/main/kotlin/de/twomartens/timetable/configuration/roles/SubjectAuthorizationManager.kt @@ -7,20 +7,21 @@ import org.springframework.security.authorization.AuthorizationManager import org.springframework.security.authorization.AuthorizationResult import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler import org.springframework.security.core.Authentication +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken import java.util.function.Supplier -class PartyAuthorizationManager : AuthorizationManager, MethodAuthorizationDeniedHandler { +class SubjectAuthorizationManager : AuthorizationManager, MethodAuthorizationDeniedHandler { @Deprecated("Deprecated in Spring Security") override fun check(authentication: Supplier?, invocation: MethodInvocation?): AuthorizationDecision { if (authentication == null || authentication.get() == null || !authentication.get()!!.isAuthenticated) { return AuthorizationDecision(false) } - val annotation = invocation!!.method.getAnnotation(CheckParty::class.java) - ?: invocation.method.declaringClass.getAnnotation(CheckParty::class.java) + val annotation = invocation!!.method.getAnnotation(CheckSubject::class.java) + ?: invocation.method.declaringClass.getAnnotation(CheckSubject::class.java) ?: return AuthorizationDecision(true) // No annotation, allow - val authentication = authentication.get()!! + val authentication = authentication.get()!! as JwtAuthenticationToken val partyParamName = annotation.partyParamName // 1. Authorized party check @@ -31,13 +32,13 @@ class PartyAuthorizationManager : AuthorizationManager, Method } val requestedPartyId = invocation.arguments[argIndex]?.toString() - val tokenPartyId = (authentication.details as? Map<*, *>)?.get("azp")?.toString() + val tokenPartyId = authentication.token.getClaimAsString("sub") ?: authentication.name // fallback return AuthorizationDecision(requestedPartyId == tokenPartyId) } override fun handleDeniedInvocation(methodInvocation: MethodInvocation?, authorizationResult: AuthorizationResult?): Any? { - throw AuthorizationDeniedException("The requested resources are not accessible to the authorized party") + throw AuthorizationDeniedException("The requested resources are not accessible for the subject") } } \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/timetable/formation/FormationController.kt b/module-server/src/main/kotlin/de/twomartens/timetable/formation/FormationController.kt index 2c123bb..2d7de20 100644 --- a/module-server/src/main/kotlin/de/twomartens/timetable/formation/FormationController.kt +++ b/module-server/src/main/kotlin/de/twomartens/timetable/formation/FormationController.kt @@ -1,7 +1,7 @@ package de.twomartens.timetable.formation import de.twomartens.timetable.auth.UserRepository -import de.twomartens.timetable.configuration.roles.CheckParty +import de.twomartens.timetable.configuration.roles.CheckSubject import de.twomartens.timetable.model.common.FormationId import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.dto.Formation @@ -45,7 +45,7 @@ class FormationController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @GetMapping("/{userId}/") fun getFormations( @PathVariable @Parameter(description = "The id of the user", @@ -98,7 +98,7 @@ class FormationController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @GetMapping("/{userId}/{id}") fun getFormation( @PathVariable @Parameter(description = "The id of the user", @@ -147,7 +147,7 @@ class FormationController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @PutMapping("/{userId}/{id}") fun putFormation( @PathVariable @Parameter(description = "The id of the user", @@ -240,7 +240,7 @@ class FormationController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @DeleteMapping("/{userId}/{id}") fun deleteFormation( @PathVariable @Parameter(description = "The id of the user", diff --git a/module-server/src/main/kotlin/de/twomartens/timetable/route/RouteController.kt b/module-server/src/main/kotlin/de/twomartens/timetable/route/RouteController.kt index 3b17111..7493871 100644 --- a/module-server/src/main/kotlin/de/twomartens/timetable/route/RouteController.kt +++ b/module-server/src/main/kotlin/de/twomartens/timetable/route/RouteController.kt @@ -1,7 +1,7 @@ package de.twomartens.timetable.route import de.twomartens.timetable.auth.UserRepository -import de.twomartens.timetable.configuration.roles.CheckParty +import de.twomartens.timetable.configuration.roles.CheckSubject import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.dto.TswRoute @@ -44,7 +44,7 @@ class RouteController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @GetMapping("/{userId}/") fun getRoutes( @PathVariable @Parameter(description = "The id of the user", @@ -89,7 +89,7 @@ class RouteController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @GetMapping("/{userId}/{id}") fun getRoute( @PathVariable @Parameter(description = "The id of the user", @@ -133,7 +133,7 @@ class RouteController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @PutMapping("/{userId}/{id}") fun putRoute( @PathVariable @Parameter(description = "The id of the user", @@ -199,7 +199,7 @@ class RouteController( ) @SecurityRequirement(name = "bearer") @SecurityRequirement(name = "oauth2") - @CheckParty(partyParamName = "userId") + @CheckSubject(partyParamName = "userId") @DeleteMapping("/{userId}/{id}") fun deleteRoute( @PathVariable @Parameter(description = "The id of the user",