Skip to content

Commit

Permalink
Merge pull request #399 from JanssenProject/yuriyz_fixes2
Browse files Browse the repository at this point in the history
feat(jans-auth-server): revoke refresh tokens for client
  • Loading branch information
yuriyz authored Jan 12, 2022
2 parents 9cb0cfd + 2e07cd9 commit 1523aec
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.jans.orm.annotation.AttributeEnum;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -71,6 +72,9 @@ public static TokenTypeHint fromString(String param) {
}

public static TokenTypeHint getByValue(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
return mapByValues.get(value);
}

Expand Down
1 change: 1 addition & 0 deletions model/src/main/java/io/jans/as/model/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class Constants {
private Constants() {
}

public static final String ALL = "all";
public static final String SERVER_KEY_OF_CONFIGURATION_ENTRY = "jansAuth_ConfigurationEntryDN";

public static final String BASE_PROPERTIES_FILE_NAME = "jans.properties";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public class AppConfiguration implements Configuration {
private Boolean requireRequestObjectEncryption = false;
private Boolean requirePkce = false;

private Boolean allowAllValueForRevokeEndpoint = false;

private int sectorIdentifierCacheLifetimeInMinutes = 1440;

private String umaConfigurationEndpoint;
Expand Down Expand Up @@ -319,6 +321,15 @@ public void setRequireRequestObjectEncryption(Boolean requireRequestObjectEncryp
this.requireRequestObjectEncryption = requireRequestObjectEncryption;
}

public Boolean getAllowAllValueForRevokeEndpoint() {
if (allowAllValueForRevokeEndpoint == null) allowAllValueForRevokeEndpoint = false;
return allowAllValueForRevokeEndpoint;
}

public void setAllowAllValueForRevokeEndpoint(Boolean allowAllValueForRevokeEndpoint) {
this.allowAllValueForRevokeEndpoint = allowAllValueForRevokeEndpoint;
}

public Boolean getRequirePkce() {
if (requirePkce == null) requirePkce = false;
return requirePkce;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -32,6 +33,8 @@ public class ExecutionContext {
private HttpServletRequest httpRequest;
private HttpServletResponse httpResponse;

private Response.ResponseBuilder responseBuilder;

private Client client;
private AuthorizationGrant grant;

Expand Down Expand Up @@ -239,4 +242,12 @@ public List<SessionId> getUserSessions() {
public void setUserSessions(List<SessionId> userSessions) {
this.userSessions = userSessions;
}

public Response.ResponseBuilder getResponseBuilder() {
return responseBuilder;
}

public void setResponseBuilder(Response.ResponseBuilder responseBuilder) {
this.responseBuilder = responseBuilder;
}
}
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.jans.as.server.service.external;

import io.jans.as.server.service.external.context.RevokeTokenContext;
import io.jans.as.server.model.common.ExecutionContext;
import io.jans.model.custom.script.CustomScriptType;
import io.jans.model.custom.script.conf.CustomScriptConfiguration;
import io.jans.model.custom.script.type.revoke.RevokeTokenType;
Expand All @@ -22,7 +22,7 @@ public ExternalRevokeTokenService() {
super(CustomScriptType.REVOKE_TOKEN);
}

public boolean revokeToken(CustomScriptConfiguration script, RevokeTokenContext context) {
public boolean revokeToken(CustomScriptConfiguration script, ExecutionContext context) {
try {
log.trace("Executing python 'revokeToken' method, context: {}", context);
context.setScript(script);
Expand All @@ -38,12 +38,10 @@ public boolean revokeToken(CustomScriptConfiguration script, RevokeTokenContext
return false;
}

public boolean revokeTokenMethods(RevokeTokenContext context) {
public boolean revokeTokenMethods(ExecutionContext context) {
for (CustomScriptConfiguration script : this.customScriptConfigurations) {
if (script.getExternalType().getApiVersion() > 1) {
if (!revokeToken(script, context)) {
return false;
}
if (script.getExternalType().getApiVersion() > 1 && !revokeToken(script, context)) {
return false;
}
}
return true;
Expand Down

This file was deleted.

0 comments on commit 1523aec

Please sign in to comment.