Skip to content

Commit

Permalink
feat: added ability remove all tokens per client by token type
Browse files Browse the repository at this point in the history
  • Loading branch information
yuriyz committed Jan 12, 2022
1 parent e1ba0b4 commit 5e8fad9
Showing 1 changed file with 83 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@
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;
import io.jans.as.server.model.audit.Action;
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;

Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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<TokenEntity> 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) {
Expand Down

0 comments on commit 5e8fad9

Please sign in to comment.