From 3cd3ada04681ad467553273611b828f8f0760bf5 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Wed, 12 Jan 2022 11:51:51 +0200 Subject: [PATCH] feat: added ability remove all tokens per client by token type https://github.com/JanssenProject/jans-auth-server/issues/372 --- .../revoke/RevokeRestWebServiceImpl.java | 110 +++++++++++++----- 1 file changed, 83 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/io/jans/as/server/revoke/RevokeRestWebServiceImpl.java b/server/src/main/java/io/jans/as/server/revoke/RevokeRestWebServiceImpl.java index 338265586c8..d762496983a 100644 --- a/server/src/main/java/io/jans/as/server/revoke/RevokeRestWebServiceImpl.java +++ b/server/src/main/java/io/jans/as/server/revoke/RevokeRestWebServiceImpl.java @@ -10,6 +10,7 @@ import io.jans.as.model.common.ComponentType; import io.jans.as.model.common.TokenTypeHint; import io.jans.as.model.config.Constants; +import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.model.token.TokenRevocationErrorResponseType; import io.jans.as.server.audit.ApplicationAuditLogger; @@ -17,13 +18,16 @@ import io.jans.as.server.model.audit.OAuth2AuditLog; import io.jans.as.server.model.common.AuthorizationGrant; import io.jans.as.server.model.common.AuthorizationGrantList; +import io.jans.as.server.model.common.ExecutionContext; +import io.jans.as.server.model.ldap.TokenEntity; +import io.jans.as.server.model.ldap.TokenType; import io.jans.as.server.model.session.SessionClient; import io.jans.as.server.security.Identity; import io.jans.as.server.service.ClientService; import io.jans.as.server.service.GrantService; import io.jans.as.server.service.external.ExternalRevokeTokenService; -import io.jans.as.server.service.external.context.RevokeTokenContext; import io.jans.as.server.util.ServerUtil; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -35,6 +39,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; +import java.util.List; /** * Provides interface for token revocation REST web services @@ -69,14 +74,17 @@ public class RevokeRestWebServiceImpl implements RevokeRestWebService { @Inject private ExternalRevokeTokenService externalRevokeTokenService; + @Inject + private AppConfiguration appConfiguration; + @Override - public Response requestAccessToken(String token, String tokenTypeHint, String clientId, + public Response requestAccessToken(String tokenString, String tokenTypeHint, String clientId, HttpServletRequest request, HttpServletResponse response, SecurityContext sec) { - log.debug("Attempting to revoke token: token = {}, tokenTypeHint = {}, isSecure = {}", token, tokenTypeHint, sec.isSecure()); + log.debug("Attempting to revoke token: token = {}, tokenTypeHint = {}, isSecure = {}", tokenString, tokenTypeHint, sec.isSecure()); errorResponseFactory.validateComponentEnabled(ComponentType.REVOKE_TOKEN); OAuth2AuditLog oAuth2AuditLog = new OAuth2AuditLog(ServerUtil.getIpAddress(request), Action.TOKEN_REVOCATION); - validateToken(token); + validateToken(tokenString); Response.ResponseBuilder builder = Response.ok(); SessionClient sessionClient = identity.getSessionClient(); @@ -96,42 +104,90 @@ public Response requestAccessToken(String token, String tokenTypeHint, String cl oAuth2AuditLog.setClientId(client.getClientId()); + ExecutionContext executionContext = new ExecutionContext(request, response); + executionContext.setClient(client); + executionContext.setResponseBuilder(builder); + + final boolean scriptResult = externalRevokeTokenService.revokeTokenMethods(executionContext); + if (!scriptResult) { + log.trace("Revoke is forbidden by 'Revoke Token' custom script (method returned false). Exit without revoking."); + return response(builder, oAuth2AuditLog); + } + TokenTypeHint tth = TokenTypeHint.getByValue(tokenTypeHint); - AuthorizationGrant authorizationGrant = null; + boolean isAll = Constants.ALL.equalsIgnoreCase(tokenString) && appConfiguration.getAllowAllValueForRevokeEndpoint(); + if (isAll) { + removeAllTokens(tth, executionContext); + return response(builder, oAuth2AuditLog); + } - if (tth == TokenTypeHint.ACCESS_TOKEN) { - authorizationGrant = authorizationGrantList.getAuthorizationGrantByAccessToken(token); - } else if (tth == TokenTypeHint.REFRESH_TOKEN) { - authorizationGrant = authorizationGrantList.getAuthorizationGrantByRefreshToken(client.getClientId(), token); - } else { - // Since the hint about the type of the token submitted for revocation is optional. Jans Auth will - // search it as Access Token then as Refresh Token. - authorizationGrant = authorizationGrantList.getAuthorizationGrantByAccessToken(token); - if (authorizationGrant == null) { - authorizationGrant = authorizationGrantList.getAuthorizationGrantByRefreshToken(client.getClientId(), token); + String[] tokens = tokenString.split(" "); + if (ArrayUtils.isEmpty(tokens)) { + throw new WebApplicationException(Response + .status(Response.Status.BAD_REQUEST.getStatusCode()) + .type(MediaType.APPLICATION_JSON_TYPE) + .entity(errorResponseFactory.errorAsJson(TokenRevocationErrorResponseType.INVALID_REQUEST, "Failed to validate token.")) + .build()); + } + + boolean isSingle = tokens.length == 1; + for (String token : tokens) { + final Response removeTokenResponse = removeToken(token, executionContext, tth, oAuth2AuditLog, isSingle); + if (removeTokenResponse != null) { + return removeTokenResponse; } } - if (authorizationGrant == null) { + return response(builder, oAuth2AuditLog); + } + + private Response removeToken(String token, ExecutionContext executionContext, TokenTypeHint tth, OAuth2AuditLog oAuth2AuditLog, boolean single) { + final Client client = executionContext.getClient(); + AuthorizationGrant authorizationGrant = findAuthorizationGrant(client, token, tth); + + if (authorizationGrant == null && !single) { log.trace("Unable to find token."); - return response(builder, oAuth2AuditLog); + return response(executionContext.getResponseBuilder(), oAuth2AuditLog); } - if (!authorizationGrant.getClientId().equals(client.getClientId())) { + + if (!single && !authorizationGrant.getClientId().equals(client.getClientId())) { log.trace("Token was issued with client {} but revoke is requested with client {}. Skip revoking.", authorizationGrant.getClientId(), client.getClientId()); - return response(builder, oAuth2AuditLog); + return response(executionContext.getResponseBuilder(), oAuth2AuditLog); } - RevokeTokenContext revokeTokenContext = new RevokeTokenContext(request, client, authorizationGrant, builder); - final boolean scriptResult = externalRevokeTokenService.revokeTokenMethods(revokeTokenContext); - if (!scriptResult) { - log.trace("Revoke is forbidden by 'Revoke Token' custom script (method returned false). Exit without revoking."); - return response(builder, oAuth2AuditLog); + if (authorizationGrant != null) { + grantService.removeAllByGrantId(authorizationGrant.getGrantId()); + log.trace("Revoked successfully token {}", token); } - grantService.removeAllByGrantId(authorizationGrant.getGrantId()); - log.trace("Revoked successfully."); + return null; + } - return response(builder, oAuth2AuditLog); + private void removeAllTokens(TokenTypeHint tth, ExecutionContext executionContext) { + final List tokens = grantService.getGrantsOfClient(executionContext.getClient().getClientId()); + for (TokenEntity token : tokens) { + if (tth == null || + (tth == TokenTypeHint.ACCESS_TOKEN && token.getTokenTypeEnum() == TokenType.ACCESS_TOKEN) || + (tth == TokenTypeHint.REFRESH_TOKEN && token.getTokenTypeEnum() == TokenType.REFRESH_TOKEN)) { + grantService.removeSilently(token); + } + } + } + + private AuthorizationGrant findAuthorizationGrant(Client client, String token, TokenTypeHint tth) { + if (tth == TokenTypeHint.ACCESS_TOKEN) { + return authorizationGrantList.getAuthorizationGrantByAccessToken(token); + } else if (tth == TokenTypeHint.REFRESH_TOKEN) { + return authorizationGrantList.getAuthorizationGrantByRefreshToken(client.getClientId(), token); + } else { + // Since the hint about the type of the token submitted for revocation is optional. Jans Auth will + // search it as Access Token then as Refresh Token. + AuthorizationGrant authorizationGrant = authorizationGrantList.getAuthorizationGrantByAccessToken(token); + if (authorizationGrant == null) { + authorizationGrant = authorizationGrantList.getAuthorizationGrantByRefreshToken(client.getClientId(), token); + } + return authorizationGrant; + } } private void validateToken(String token) {