Skip to content

Commit

Permalink
authorized token revocation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thumimku committed Feb 9, 2024
1 parent 56ecdcb commit fc2a939
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.application.mgt.AuthorizedAPIManagementService;
import org.wso2.carbon.identity.cors.mgt.core.CORSManagementService;
import org.wso2.carbon.identity.oauth.OAuthAdminServiceImpl;
import org.wso2.carbon.identity.oauth.OauthInboundAuthConfigHandler;
Expand Down Expand Up @@ -74,6 +75,7 @@ public class OAuthComponentServiceHolder {
private OauthInboundAuthConfigHandler oauthInboundAuthConfigHandler;
private CORSManagementService corsManagementService;

private AuthorizedAPIManagementService authorizedAPIManagementService;

/**
* Get the list of scope validator implementations available.
Expand Down Expand Up @@ -445,4 +447,24 @@ public void setCorsManagementService(CORSManagementService corsManagementService

this.corsManagementService = corsManagementService;
}

/**
* Returns the authorized API management service.
*
* @return The authorized API management service.
*/
public AuthorizedAPIManagementService getAuthorizedAPIManagementService() {

return authorizedAPIManagementService;
}

/**
* Sets the authorized API management service.
*
* @param authorizedAPIManagementService The authorized API management service to set.
*/
public void setAuthorizedAPIManagementService(AuthorizedAPIManagementService authorizedAPIManagementService) {

this.authorizedAPIManagementService = authorizedAPIManagementService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.wso2.carbon.identity.application.authentication.framework.UserSessionManagementService;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.application.mgt.AuthorizedAPIManagementService;
import org.wso2.carbon.identity.application.mgt.inbound.protocol.ApplicationInboundAuthConfigHandler;
import org.wso2.carbon.identity.core.util.IdentityCoreInitializedEvent;
import org.wso2.carbon.identity.cors.mgt.core.CORSManagementService;
Expand Down Expand Up @@ -450,4 +451,33 @@ protected void unsetApplicationCORSManagementService(CORSManagementService corsM

OAuthComponentServiceHolder.getInstance().setCorsManagementService(null);
}

@Reference(
name = "identity.authorized.api.management.component",
service = AuthorizedAPIManagementService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetAuthorizedAPIManagementService"
)
protected void setAuthorizedAPIManagementService(AuthorizedAPIManagementService authorizedAPIManagementService) {

if (log.isDebugEnabled()) {
log.debug("Setting AuthorizedAPIManagementService Service");
}
OAuthComponentServiceHolder.getInstance().
setAuthorizedAPIManagementService(authorizedAPIManagementService);
}

/**
* Unsets AuthorizedAPIManagementService Service.
*
* @param authorizedAPIManagementService An instance of AuthorizedAPIManagementService
*/
protected void unsetAuthorizedAPIManagementService(AuthorizedAPIManagementService authorizedAPIManagementService) {

if (log.isDebugEnabled()) {
log.debug("Unsetting AuthorizedAPIManagementService.");
}
OAuthComponentServiceHolder.getInstance().setAuthorizedAPIManagementService(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.UserSessionException;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.AuthorizedAPI;
import org.wso2.carbon.identity.application.common.model.Scope;
import org.wso2.carbon.identity.base.IdentityRuntimeException;
import org.wso2.carbon.identity.core.bean.context.MessageContext;
import org.wso2.carbon.identity.core.handler.InitConfig;
Expand All @@ -36,6 +39,7 @@
import org.wso2.carbon.identity.event.handler.AbstractEventHandler;
import org.wso2.carbon.identity.oauth.OAuthUtil;
import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder;
import org.wso2.carbon.identity.role.mgt.core.GroupBasicInfo;
import org.wso2.carbon.identity.role.mgt.core.IdentityRoleManagementException;
Expand All @@ -51,6 +55,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
* This is an event handler listening for some of the core user management operations.
Expand Down Expand Up @@ -194,6 +199,52 @@ public void handleEvent(Event event) throws IdentityEventException {
String errorMsg = "Invalid role id :" + roleId + "in tenant domain " + tenantDomain;
throw new IdentityEventException(errorMsg);
}
} else if (IdentityEventConstants.Event.PRE_UPDATE_AUTHORIZED_API_FOR_APPLICATION_EVENT
.equals(event.getEventName())) {

String appId = (String) event.getEventProperties().get(IdentityEventConstants.EventProperty.APPLICATION_ID);
String apiId = (String) event.getEventProperties().get(IdentityEventConstants.EventProperty.API_ID);
List<String> removedScopes = (List<String>) event.getEventProperties().get(IdentityEventConstants.
EventProperty.DELETED_SCOPES);
String tenantDomain = (String) event.getEventProperties().get(IdentityEventConstants.
EventProperty.TENANT_DOMAIN);
if (!removedScopes.isEmpty()) {
try {
OAuth2ServiceComponentHolder.getInstance()
.getRevocationProcessor().revokeTokens(appId, apiId, removedScopes, tenantDomain);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while revoking access token " +
"for application resource id: " + appId;
log.error(errorMsg, e);
throw new IdentityEventException(errorMsg);
}
}
} else if (IdentityEventConstants.Event.PRE_DELETE_AUTHORIZED_API_FOR_APPLICATION_EVENT
.equals(event.getEventName())) {

String appId = (String) event.getEventProperties().get(IdentityEventConstants.EventProperty.APPLICATION_ID);
String apiId = (String) event.getEventProperties().get(IdentityEventConstants.EventProperty.API_ID);
String tenantDomain = (String) event.getEventProperties().get(IdentityEventConstants.
EventProperty.TENANT_DOMAIN);
try {
AuthorizedAPI authorizedAPI = OAuthComponentServiceHolder.getInstance()
.getAuthorizedAPIManagementService()
.getAuthorizedAPI(appId, apiId, tenantDomain);
List<String> removedScopes = new ArrayList<>();
removedScopes.addAll(authorizedAPI.getScopes().stream()
.map(Scope::getName).filter(scope ->
!removedScopes.contains(scope)).collect(Collectors.toList()));

if (!removedScopes.isEmpty()) {
OAuth2ServiceComponentHolder.getInstance()
.getRevocationProcessor().revokeTokens(appId, apiId, removedScopes, tenantDomain);
}
} catch (IdentityOAuth2Exception | IdentityApplicationManagementException e) {
String errorMsg = "Error occurred while revoking access token " +
"for application resource id: " + appId;
log.error(errorMsg, e);
throw new IdentityEventException(errorMsg);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,40 @@

package org.wso2.carbon.identity.oauth.tokenprocessor;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.InboundAuthenticationRequestConfig;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.oauth.OAuthUtil;
import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.dao.OAuthTokenPersistenceFactory;
import org.wso2.carbon.identity.oauth2.dto.OAuthRevocationRequestDTO;
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO;
import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.UserStoreManager;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import static org.wso2.carbon.identity.oauth.common.OAuthConstants.Scope.OAUTH2;

/**
* DefaultOAuth2RevocationProcessor is responsible for handling OAuth2 token revocation
* when a persistence layer is in use. It provides methods to revoke access tokens and
* refresh tokens, as well as a mechanism to revoke tokens associated with a specific user.
*/
public class DefaultOAuth2RevocationProcessor implements OAuth2RevocationProcessor {

public static final Log LOG = LogFactory.getLog(DefaultOAuth2RevocationProcessor.class);

@Override
public void revokeAccessToken(OAuthRevocationRequestDTO revokeRequestDTO, AccessTokenDO accessTokenDO)
throws IdentityOAuth2Exception {
Expand Down Expand Up @@ -63,4 +81,90 @@ public boolean revokeTokens(String username, UserStoreManager userStoreManager,

return OAuthUtil.revokeTokens(username, userStoreManager, roleId);
}

/**
* Revoke tokens associated with the specified application ID, API ID, removed scopes, and tenant domain.
*
* @param appId The ID of the application.
* @param apiId The ID of the API.
* @param removedScopes The list of removed scopes.
* @param tenantDomain The tenant domain.
* @throws IdentityOAuth2Exception If an error occurs while revoking tokens.
*/
@Override
public void revokeTokens(String appId, String apiId, List<String> removedScopes,
String tenantDomain) throws IdentityOAuth2Exception {

// Calling ApplicationManagementService to get client Id for the app Id.
ApplicationManagementService applicationManagementService =
OAuthComponentServiceHolder.getInstance().getApplicationManagementService();
String clientId = null;
try {
// Retrieving application by resource ID.
ServiceProvider application = applicationManagementService.getApplicationByResourceId(appId, tenantDomain);
if (application == null ||
application.getInboundAuthenticationConfig() == null ||
application.getInboundAuthenticationConfig().getInboundAuthenticationRequestConfigs() == null) {
// If application or authentication configurations are null, do nothing.
return;
}

// Retrieving client ID from inbound authentication configurations.
for (InboundAuthenticationRequestConfig oauth2config :
application.getInboundAuthenticationConfig().getInboundAuthenticationRequestConfigs()) {
if (StringUtils.equals(OAUTH2, oauth2config.getInboundAuthType())) {
clientId = oauth2config.getInboundAuthKey();
break;
}
}
if (clientId == null) {
// If client ID is not found, log error and throw exception.
LOG.error(String.format("Invalid client of application : %s , ", application.getApplicationName()));
throw new IdentityOAuth2Exception(String.format("Invalid client of application : %s , "
, application.getApplicationName()));
}

// Retrieve active access tokens for the given client ID and removed scopes.
Set<AccessTokenDO> accessTokenDOSet = OAuthTokenPersistenceFactory.getInstance()
.getAccessTokenDAO().getActiveTokenSetWithTokenIdByConsumerKeyAndScope(clientId, removedScopes);

// Iterate through the retrieved access tokens and revoke them.
for (AccessTokenDO accessTokenDO: accessTokenDOSet) {
revokeTokens(clientId, accessTokenDO.getAuthzUser(), accessTokenDO,
accessTokenDO.getTokenBinding().getBindingReference());
}
} catch (IdentityApplicationManagementException e) {
LOG.error("Error occurred while retrieving app by app ID : " + appId, e);
throw new IdentityOAuth2Exception("Error occurred while retrieving app by app ID : " + appId, e);
}
}

/**
* Revokes access tokens associated with the specified consumer key, user, access token data object,
* and token binding reference.
*
* @param consumerKey The consumer key of the application.
* @param user The authenticated user.
* @param accessTokenDO The access token data object.
* @param tokenBindingReference The token binding reference.
* @throws IdentityOAuth2Exception If an error occurs while revoking access tokens.
*/
private void revokeTokens(String consumerKey, AuthenticatedUser user, AccessTokenDO accessTokenDO,
String tokenBindingReference) throws IdentityOAuth2Exception {

if (LOG.isDebugEnabled()) {
LOG.debug("Revoking tokens for the application with consumerKey:" + consumerKey + " for the user: "
+ user.getLoggableUserId());
}
OAuthUtil.clearOAuthCache(consumerKey, user, OAuth2Util.buildScopeString
(accessTokenDO.getScope()), tokenBindingReference);
OAuthUtil.clearOAuthCache(consumerKey, user, OAuth2Util.buildScopeString
(accessTokenDO.getScope()));
OAuthUtil.clearOAuthCache(consumerKey, user);
OAuthUtil.clearOAuthCache(accessTokenDO);
OAuthUtil.invokePreRevocationBySystemListeners(accessTokenDO, Collections.emptyMap());
OAuthTokenPersistenceFactory.getInstance().getAccessTokenDAO()
.revokeAccessTokens(new String[]{accessTokenDO.getAccessToken()}, OAuth2Util.isHashEnabled());
OAuthUtil.invokePostRevocationBySystemListeners(accessTokenDO, Collections.emptyMap());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.UserStoreManager;

import java.util.List;

/**
* Abstraction layer between OAuth2Service and persistence layer to handle revocation logic during token persistence
* and non-persistence scenarios.
Expand Down Expand Up @@ -73,4 +75,19 @@ void revokeRefreshToken(OAuthRevocationRequestDTO revokeRequestDTO,
* @throws UserStoreException If an error occurs while revoking tokens for users.
*/
boolean revokeTokens(String username, UserStoreManager userStoreManager, String roleId) throws UserStoreException;

/**
* Revoke tokens associated with the specified application ID, API ID, removed scopes, and tenant domain.
*
* @param appId The ID of the application.
* @param apiId The ID of the API.
* @param removedScopes The list of removed scopes.
* @param tenantDomain The tenant domain.
* @throws IdentityOAuth2Exception If an error occurs while revoking tokens.
*/
default void revokeTokens(String appId, String apiId,
List<String> removedScopes,
String tenantDomain) throws IdentityOAuth2Exception {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,27 @@ public static class RoleBasedScope {

public static final String APIM_SERVICE_CATALOG_PREFIX = "service_catalog:";
}

/**
* This class define static variables for column names in db.
*/
public static class OAuthColumnName {

public static final String ACCESS_TOKEN = "ACCESS_TOKEN";
public static final String TOKEN_SCOPE = "TOKEN_SCOPE";
public static final String REFRESH_TOKEN = "REFRESH_TOKEN";
public static final String TOKEN_ID = "TOKEN_ID";
public static final String TENANT_ID = "TENANT_ID";
public static final String AUTHZ_USER = "AUTHZ_USER";
public static final String SUBJECT_IDENTIFIER = "SUBJECT_IDENTIFIER";
public static final String USER_DOMAIN = "USER_DOMAIN";
public static final String AUTHENTICATED_IDP_NAME = "NAME";
public static final String AUTHORIZED_ORGANIZATION = "AUTHORIZED_ORGANIZATION";
public static final String TOKEN_BINDING_REF = "TOKEN_BINDING_REF";
public static final String TIME_CREATED = "TIME_CREATED";
public static final String REFRESH_TOKEN_TIME_CREATED = "REFRESH_TOKEN_TIME_CREATED";
public static final String VALIDITY_PERIOD = "VALIDITY_PERIOD";
public static final String REFRESH_TOKEN_VALIDITY_PERIOD = "REFRESH_TOKEN_VALIDITY_PERIOD";
public static final String USER_TYPE = "USER_TYPE";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ default Set<AccessTokenDO> getActiveTokenSetWithTokenIdByConsumerKeyForOpenidSco
return Collections.emptySet();
}

default Set<AccessTokenDO> getActiveTokenSetWithTokenIdByConsumerKeyAndScope(String consumerKey,
List<String> scopes)
throws IdentityOAuth2Exception {

return Collections.emptySet();
}

/**
* Retrieve the active access tokens of a given user with a given access token binding reference.
*
Expand Down
Loading

0 comments on commit fc2a939

Please sign in to comment.