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 ]) body: User
): ResponseEntity<User> { ): ResponseEntity<User> {
val authentication = SecurityContextHolder.getContext().authentication val authentication = SecurityContextHolder.getContext().authentication as JwtAuthenticationToken
val jwt = authentication as JwtAuthenticationToken val subject = authentication.token.getClaimAsString("sub") ?: authentication.name
val authorizedParty = jwt.token.getClaimAsString("azp")
var created = false var created = false
val userIdConverted = UserId.of(NonEmptyString(userId)) val userIdConverted = UserId.of(NonEmptyString(userId))
@@ -73,7 +72,7 @@ class UserController(
if (user == null) { if (user == null) {
created = true created = true
user = mapper.mapToDB(body) user = mapper.mapToDB(body)
} else if (authorizedParty == userId) { } else if (subject == userId) {
user.name = NonEmptyString(body.name) user.name = NonEmptyString(body.name)
user.email = Email.of(NonEmptyString(body.email)) user.email = Email.of(NonEmptyString(body.email))
} else { } else {
@@ -1,7 +1,7 @@
package de.twomartens.timetable.configuration package de.twomartens.timetable.configuration
import de.twomartens.timetable.configuration.roles.CheckParty import de.twomartens.timetable.configuration.roles.CheckSubject
import de.twomartens.timetable.configuration.roles.PartyAuthorizationManager import de.twomartens.timetable.configuration.roles.SubjectAuthorizationManager
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut import org.springframework.aop.support.annotation.AnnotationMatchingPointcut
import org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE import org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
@@ -16,8 +16,8 @@ class AuthorizationManagerConfiguration {
@Bean @Bean
@Role(ROLE_INFRASTRUCTURE) @Role(ROLE_INFRASTRUCTURE)
fun checkPartyInterceptor(): AuthorizationManagerBeforeMethodInterceptor { fun checkPartyInterceptor(): AuthorizationManagerBeforeMethodInterceptor {
val pointcut = AnnotationMatchingPointcut(null, CheckParty::class.java, true) val pointcut = AnnotationMatchingPointcut(null, CheckSubject::class.java, true)
val interceptor = AuthorizationManagerBeforeMethodInterceptor(pointcut, PartyAuthorizationManager()) val interceptor = AuthorizationManagerBeforeMethodInterceptor(pointcut, SubjectAuthorizationManager())
interceptor.order = AuthorizationInterceptorsOrder.PRE_AUTHORIZE.order interceptor.order = AuthorizationInterceptorsOrder.PRE_AUTHORIZE.order
return interceptor return interceptor
} }
@@ -6,6 +6,6 @@ import java.lang.annotation.Inherited
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@Inherited @Inherited
@MustBeDocumented @MustBeDocumented
annotation class CheckParty( annotation class CheckSubject(
val partyParamName: String // Name of method argument holding the party ID 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.AuthorizationResult
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
import java.util.function.Supplier import java.util.function.Supplier
class PartyAuthorizationManager : AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler { class SubjectAuthorizationManager : AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
@Deprecated("Deprecated in Spring Security") @Deprecated("Deprecated in Spring Security")
override fun check(authentication: Supplier<Authentication?>?, invocation: MethodInvocation?): AuthorizationDecision { override fun check(authentication: Supplier<Authentication?>?, invocation: MethodInvocation?): AuthorizationDecision {
if (authentication == null || authentication.get() == null || !authentication.get()!!.isAuthenticated) { if (authentication == null || authentication.get() == null || !authentication.get()!!.isAuthenticated) {
return AuthorizationDecision(false) return AuthorizationDecision(false)
} }
val annotation = invocation!!.method.getAnnotation(CheckParty::class.java) val annotation = invocation!!.method.getAnnotation(CheckSubject::class.java)
?: invocation.method.declaringClass.getAnnotation(CheckParty::class.java) ?: invocation.method.declaringClass.getAnnotation(CheckSubject::class.java)
?: return AuthorizationDecision(true) // No annotation, allow ?: return AuthorizationDecision(true) // No annotation, allow
val authentication = authentication.get()!! val authentication = authentication.get()!! as JwtAuthenticationToken
val partyParamName = annotation.partyParamName val partyParamName = annotation.partyParamName
// 1. Authorized party check // 1. Authorized party check
@@ -31,13 +32,13 @@ class PartyAuthorizationManager : AuthorizationManager<MethodInvocation>, Method
} }
val requestedPartyId = invocation.arguments[argIndex]?.toString() val requestedPartyId = invocation.arguments[argIndex]?.toString()
val tokenPartyId = (authentication.details as? Map<*, *>)?.get("azp")?.toString() val tokenPartyId = authentication.token.getClaimAsString("sub")
?: authentication.name // fallback ?: authentication.name // fallback
return AuthorizationDecision(requestedPartyId == tokenPartyId) return AuthorizationDecision(requestedPartyId == tokenPartyId)
} }
override fun handleDeniedInvocation(methodInvocation: MethodInvocation?, authorizationResult: AuthorizationResult?): Any? { 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 package de.twomartens.timetable.formation
import de.twomartens.timetable.auth.UserRepository 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.FormationId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.model.dto.Formation import de.twomartens.timetable.model.dto.Formation
@@ -45,7 +45,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@GetMapping("/{userId}/") @GetMapping("/{userId}/")
fun getFormations( fun getFormations(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -98,7 +98,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@GetMapping("/{userId}/{id}") @GetMapping("/{userId}/{id}")
fun getFormation( fun getFormation(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -147,7 +147,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@PutMapping("/{userId}/{id}") @PutMapping("/{userId}/{id}")
fun putFormation( fun putFormation(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -240,7 +240,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@DeleteMapping("/{userId}/{id}") @DeleteMapping("/{userId}/{id}")
fun deleteFormation( fun deleteFormation(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -1,7 +1,7 @@
package de.twomartens.timetable.route package de.twomartens.timetable.route
import de.twomartens.timetable.auth.UserRepository 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.RouteId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.model.dto.TswRoute import de.twomartens.timetable.model.dto.TswRoute
@@ -44,7 +44,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@GetMapping("/{userId}/") @GetMapping("/{userId}/")
fun getRoutes( fun getRoutes(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -89,7 +89,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@GetMapping("/{userId}/{id}") @GetMapping("/{userId}/{id}")
fun getRoute( fun getRoute(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -133,7 +133,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@PutMapping("/{userId}/{id}") @PutMapping("/{userId}/{id}")
fun putRoute( fun putRoute(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -199,7 +199,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId") @CheckSubject(partyParamName = "userId")
@DeleteMapping("/{userId}/{id}") @DeleteMapping("/{userId}/{id}")
fun deleteRoute( fun deleteRoute(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",