fix: Check for subject not authorized party

This commit is contained in:
2025-08-11 08:25:06 +02:00
parent e00870c739
commit c587289ab3
6 changed files with 25 additions and 25 deletions
@@ -63,9 +63,8 @@ class UserController(
)
]) body: User
): ResponseEntity<User> {
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 {
@@ -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
}
@@ -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
)
@@ -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<MethodInvocation>, MethodAuthorizationDeniedHandler {
class SubjectAuthorizationManager : AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
@Deprecated("Deprecated in Spring Security")
override fun check(authentication: Supplier<Authentication?>?, 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<MethodInvocation>, 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")
}
}
@@ -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",
@@ -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",