From ef2c80055ec29096a1158ddabddc4077901a4509 Mon Sep 17 00:00:00 2001 From: John Niang Date: Tue, 20 Feb 2024 10:14:43 +0800 Subject: [PATCH] Do not set claim 'roles' for new PAT Signed-off-by: John Niang --- ...esAndRolesGrantedAuthoritiesConverter.java | 53 ------------------- .../pat/PatAuthenticationManager.java | 53 ++++++++++++------- .../pat/impl/UserScopedPatHandlerImpl.java | 2 - 3 files changed, 34 insertions(+), 74 deletions(-) delete mode 100644 application/src/main/java/run/halo/app/security/authentication/jwt/JwtScopesAndRolesGrantedAuthoritiesConverter.java diff --git a/application/src/main/java/run/halo/app/security/authentication/jwt/JwtScopesAndRolesGrantedAuthoritiesConverter.java b/application/src/main/java/run/halo/app/security/authentication/jwt/JwtScopesAndRolesGrantedAuthoritiesConverter.java deleted file mode 100644 index e4807b2c9e..0000000000 --- a/application/src/main/java/run/halo/app/security/authentication/jwt/JwtScopesAndRolesGrantedAuthoritiesConverter.java +++ /dev/null @@ -1,53 +0,0 @@ -package run.halo.app.security.authentication.jwt; - -import static run.halo.app.security.authorization.AuthorityUtils.ANONYMOUS_ROLE_NAME; -import static run.halo.app.security.authorization.AuthorityUtils.AUTHENTICATED_ROLE_NAME; -import static run.halo.app.security.authorization.AuthorityUtils.ROLE_PREFIX; - -import java.util.ArrayList; -import java.util.Collection; -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; -import reactor.core.publisher.Flux; - -/** - * GrantedAuthorities converter for SCOPE_ and ROLE_ prefixes. - * - * @author johnniang - */ -public class JwtScopesAndRolesGrantedAuthoritiesConverter - implements Converter> { - - private final Converter> delegate; - - public JwtScopesAndRolesGrantedAuthoritiesConverter() { - delegate = new JwtGrantedAuthoritiesConverter(); - } - - @Override - public Flux convert(Jwt jwt) { - var grantedAuthorities = new ArrayList(); - - // add default roles - grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + ANONYMOUS_ROLE_NAME)); - grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + AUTHENTICATED_ROLE_NAME)); - - var delegateAuthorities = delegate.convert(jwt); - if (delegateAuthorities != null) { - grantedAuthorities.addAll(delegateAuthorities); - } - var roles = jwt.getClaimAsStringList("roles"); - if (roles != null) { - roles.stream() - .map(role -> ROLE_PREFIX + role) - .map(SimpleGrantedAuthority::new) - .forEach(grantedAuthorities::add); - } - - return Flux.fromIterable(grantedAuthorities); - } - -} diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java b/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java index 13f0db9b61..019caffdad 100644 --- a/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java +++ b/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java @@ -3,27 +3,31 @@ import static org.apache.commons.lang3.StringUtils.removeStart; import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.withJwkSource; import static run.halo.app.security.authentication.pat.PatServerWebExchangeMatcher.PAT_TOKEN_PREFIX; +import static run.halo.app.security.authorization.AuthorityUtils.ANONYMOUS_ROLE_NAME; +import static run.halo.app.security.authorization.AuthorityUtils.AUTHENTICATED_ROLE_NAME; +import static run.halo.app.security.authorization.AuthorityUtils.ROLE_PREFIX; import com.nimbusds.jwt.JWTClaimNames; import java.time.Clock; import java.time.Duration; +import java.util.ArrayList; import java.util.Objects; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager; -import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; import run.halo.app.extension.ExtensionUtil; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.security.PersonalAccessToken; -import run.halo.app.security.authentication.jwt.JwtScopesAndRolesGrantedAuthoritiesConverter; +import run.halo.app.security.authorization.AuthorityUtils; public class PatAuthenticationManager implements ReactiveAuthenticationManager { @@ -44,15 +48,10 @@ public PatAuthenticationManager(ReactiveExtensionClient client, PatJwkSupplier j this.clock = Clock.systemDefaultZone(); } - private ReactiveAuthenticationManager getDelegate(PatJwkSupplier jwkSupplier) { + private static ReactiveAuthenticationManager getDelegate(PatJwkSupplier jwkSupplier) { var jwtDecoder = withJwkSource(signedJWT -> Flux.just(jwkSupplier.getJwk())) .build(); - var jwtAuthManager = new JwtReactiveAuthenticationManager(jwtDecoder); - var jwtAuthConverter = new ReactiveJwtAuthenticationConverter(); - jwtAuthConverter.setJwtGrantedAuthoritiesConverter( - new JwtScopesAndRolesGrantedAuthoritiesConverter()); - jwtAuthManager.setJwtAuthenticationConverter(jwtAuthConverter); - return jwtAuthManager; + return new JwtReactiveAuthenticationManager(jwtDecoder); } public void setClock(Clock clock) { @@ -61,10 +60,11 @@ public void setClock(Clock clock) { @Override public Mono authenticate(Authentication authentication) { - return delegate.authenticate(clearPrefix(authentication)) - .transformDeferred(auth -> auth.filter(a -> a instanceof JwtAuthenticationToken) - .cast(JwtAuthenticationToken.class) - .flatMap(jwtAuthToken -> checkAvailability(jwtAuthToken).thenReturn(jwtAuthToken))); + return Mono.just(authentication) + .map(this::clearPrefix) + .flatMap(delegate::authenticate) + .cast(JwtAuthenticationToken.class) + .flatMap(this::checkAndRebuild); } private Authentication clearPrefix(Authentication authentication) { @@ -75,18 +75,33 @@ private Authentication clearPrefix(Authentication authentication) { return authentication; } - private Mono checkAvailability(JwtAuthenticationToken jwtAuthToken) { - var jwt = jwtAuthToken.getToken(); + private Mono checkAndRebuild(JwtAuthenticationToken jat) { + var jwt = jat.getToken(); var patName = jwt.getClaimAsString("pat_name"); var jwtId = jwt.getClaimAsString(JWTClaimNames.JWT_ID); if (patName == null || jwtId == null) { - // Skip if the JWT token is not a PAT. - return Mono.empty(); + // Not a valid PAT + return Mono.error(new InvalidBearerTokenException("Missing claim pat_name or jti")); } return client.fetch(PersonalAccessToken.class, patName) .switchIfEmpty( - Mono.error(() -> new DisabledException("Personal access token has been deleted."))) - .flatMap(pat -> patChecks(pat, jwtId).and(updateLastUsed(patName))); + Mono.error(() -> new DisabledException("Personal access token has been deleted.")) + ) + .flatMap(pat -> patChecks(pat, jwtId).and(updateLastUsed(patName)).thenReturn(pat)) + .map(pat -> { + // Make sure the authorities modifiable + var authorities = new ArrayList<>(jat.getAuthorities()); + authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + ANONYMOUS_ROLE_NAME)); + authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + AUTHENTICATED_ROLE_NAME)); + var roles = pat.getSpec().getRoles(); + if (roles != null) { + roles.stream() + .map(role -> AuthorityUtils.ROLE_PREFIX + role) + .map(SimpleGrantedAuthority::new) + .forEach(authorities::add); + } + return new JwtAuthenticationToken(jat.getToken(), authorities, jat.getName()); + }); } private Mono updateLastUsed(String patName) { diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/impl/UserScopedPatHandlerImpl.java b/application/src/main/java/run/halo/app/security/authentication/pat/impl/UserScopedPatHandlerImpl.java index 833862c5a6..2dc914bb42 100644 --- a/application/src/main/java/run/halo/app/security/authentication/pat/impl/UserScopedPatHandlerImpl.java +++ b/application/src/main/java/run/halo/app/security/authentication/pat/impl/UserScopedPatHandlerImpl.java @@ -138,7 +138,6 @@ public Mono create(ServerRequest request) { .id(tokenId) .subject(auth.getName()) .issuedAt(clock.instant()) - .claim("roles", roles) .claim("pat_name", createdPat.getMetadata().getName()); var expiresAt = createdPat.getSpec().getExpiresAt(); if (expiresAt != null) { @@ -149,7 +148,6 @@ public Mono create(ServerRequest request) { var jwt = patEncoder.encode(JwtEncoderParameters.from( headerBuilder.build(), claimsBuilder.build())); - // TODO Create PAT for the token. var annotations = createdPat.getMetadata().getAnnotations(); if (annotations == null) {