forked from wso2-extensions/identity-inbound-auth-oauth
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
front port fixes from wso2-extensions#2091.
- Loading branch information
Showing
10 changed files
with
723 additions
and
138 deletions.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
.../java/org/wso2/carbon/identity/oauth/tokenprocessor/DefaultOAuth2RevocationProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). | ||
* | ||
* WSO2 LLC. licenses this file to you under the Apache License, | ||
* Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.wso2.carbon.identity.oauth.tokenprocessor; | ||
|
||
import org.apache.commons.lang3.StringUtils; | ||
import org.apache.oltu.oauth2.common.message.types.GrantType; | ||
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; | ||
|
||
/** | ||
* Handles oauth2 token revocation when persistence layer exists. | ||
*/ | ||
public class DefaultOAuth2RevocationProcessor implements OAuth2RevocationProcessor { | ||
|
||
@Override | ||
public void revokeAccessToken(OAuthRevocationRequestDTO revokeRequestDTO, AccessTokenDO accessTokenDO) | ||
throws IdentityOAuth2Exception { | ||
|
||
OAuthTokenPersistenceFactory.getInstance().getAccessTokenDAO() | ||
.revokeAccessTokens(new String[]{accessTokenDO.getAccessToken()}); | ||
} | ||
|
||
@Override | ||
public void revokeRefreshToken(OAuthRevocationRequestDTO revokeRequestDTO, | ||
RefreshTokenValidationDataDO refreshTokenDO) throws IdentityOAuth2Exception { | ||
|
||
OAuthTokenPersistenceFactory.getInstance().getAccessTokenDAO() | ||
.revokeAccessTokens(new String[]{refreshTokenDO.getAccessToken()}); | ||
} | ||
|
||
@Override | ||
public RefreshTokenValidationDataDO getRevocableRefreshToken(OAuthRevocationRequestDTO revokeRequestDTO) | ||
throws IdentityOAuth2Exception { | ||
|
||
return OAuthTokenPersistenceFactory.getInstance().getTokenManagementDAO() | ||
.validateRefreshToken(revokeRequestDTO.getConsumerKey(), revokeRequestDTO.getToken()); | ||
} | ||
|
||
@Override | ||
public AccessTokenDO getRevocableAccessToken(OAuthRevocationRequestDTO revokeRequestDTO) | ||
throws IdentityOAuth2Exception { | ||
|
||
return OAuth2Util.findAccessToken(revokeRequestDTO.getToken(), true); | ||
} | ||
|
||
@Override | ||
public boolean isRefreshTokenType(OAuthRevocationRequestDTO revokeRequestDTO) { | ||
|
||
return StringUtils.equals(GrantType.REFRESH_TOKEN.toString(), revokeRequestDTO.getTokenType()); | ||
} | ||
} |
187 changes: 187 additions & 0 deletions
187
...java/org/wso2/carbon/identity/oauth/tokenprocessor/DefaultRefreshTokenGrantProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
/* | ||
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). | ||
* | ||
* WSO2 LLC. licenses this file to you under the Apache License, | ||
* Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.wso2.carbon.identity.oauth.tokenprocessor; | ||
|
||
import org.apache.commons.codec.digest.DigestUtils; | ||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
import org.wso2.carbon.identity.base.IdentityConstants; | ||
import org.wso2.carbon.identity.core.util.IdentityUtil; | ||
import org.wso2.carbon.identity.oauth.common.OAuthConstants; | ||
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; | ||
import org.wso2.carbon.identity.oauth2.dao.OAuthTokenPersistenceFactory; | ||
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; | ||
import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; | ||
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; | ||
import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO; | ||
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; | ||
import org.wso2.carbon.identity.oauth2.util.OAuth2Util; | ||
import org.wso2.carbon.identity.openidconnect.OIDCClaimUtil; | ||
|
||
import java.sql.Timestamp; | ||
import java.util.Date; | ||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
/** | ||
* Default implementation of @RefreshTokenProcessor responsible for handling refresh token persistence logic. | ||
*/ | ||
public class DefaultRefreshTokenGrantProcessor implements RefreshTokenGrantProcessor { | ||
|
||
private static final Log log = LogFactory.getLog(DefaultRefreshTokenGrantProcessor.class); | ||
public static final String PREV_ACCESS_TOKEN = "previousAccessToken"; | ||
public static final int LAST_ACCESS_TOKEN_RETRIEVAL_LIMIT = 10; | ||
|
||
@Override | ||
public RefreshTokenValidationDataDO validateRefreshToken(OAuthTokenReqMessageContext tokenReqMessageContext) | ||
throws IdentityOAuth2Exception { | ||
|
||
OAuth2AccessTokenReqDTO tokenReq = tokenReqMessageContext.getOauth2AccessTokenReqDTO(); | ||
RefreshTokenValidationDataDO validationBean = OAuthTokenPersistenceFactory.getInstance().getTokenManagementDAO() | ||
.validateRefreshToken(tokenReq.getClientId(), tokenReq.getRefreshToken()); | ||
validatePersistedAccessToken(validationBean, tokenReq.getClientId()); | ||
return validationBean; | ||
} | ||
|
||
@Override | ||
public void persistNewToken(OAuthTokenReqMessageContext tokenReqMessageContext, AccessTokenDO accessTokenBean, | ||
String userStoreDomain, String clientId) throws IdentityOAuth2Exception { | ||
|
||
RefreshTokenValidationDataDO oldAccessToken = | ||
(RefreshTokenValidationDataDO) tokenReqMessageContext.getProperty(PREV_ACCESS_TOKEN); | ||
if (log.isDebugEnabled()) { | ||
if (IdentityUtil.isTokenLoggable(IdentityConstants.IdentityTokens.ACCESS_TOKEN)) { | ||
log.debug(String.format("Previous access token (hashed): %s", DigestUtils.sha256Hex( | ||
oldAccessToken.getAccessToken()))); | ||
} | ||
} | ||
// set the previous access token state to "INACTIVE" and store new access token in single db connection | ||
OAuthTokenPersistenceFactory.getInstance().getAccessTokenDAO() | ||
.invalidateAndCreateNewAccessToken(oldAccessToken.getTokenId(), | ||
OAuthConstants.TokenStates.TOKEN_STATE_INACTIVE, clientId, | ||
UUID.randomUUID().toString(), accessTokenBean, userStoreDomain, oldAccessToken.getGrantType()); | ||
} | ||
|
||
@Override | ||
public AccessTokenDO createAccessTokenBean(OAuthTokenReqMessageContext tokReqMsgCtx, | ||
OAuth2AccessTokenReqDTO tokenReq, | ||
RefreshTokenValidationDataDO validationBean, String tokenType) | ||
throws IdentityOAuth2Exception { | ||
|
||
Timestamp timestamp = new Timestamp(new Date().getTime()); | ||
String tokenId = UUID.randomUUID().toString(); | ||
|
||
AccessTokenDO accessTokenDO = new AccessTokenDO(); | ||
accessTokenDO.setConsumerKey(tokenReq.getClientId()); | ||
accessTokenDO.setAuthzUser(tokReqMsgCtx.getAuthorizedUser()); | ||
accessTokenDO.setScope(tokReqMsgCtx.getScope()); | ||
accessTokenDO.setTokenType(tokenType); | ||
accessTokenDO.setTokenState(OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE); | ||
accessTokenDO.setTokenId(tokenId); | ||
accessTokenDO.setGrantType(tokenReq.getGrantType()); | ||
accessTokenDO.setIssuedTime(timestamp); | ||
accessTokenDO.setTokenBinding(tokReqMsgCtx.getTokenBinding()); | ||
|
||
if (OAuth2ServiceComponentHolder.isConsentedTokenColumnEnabled()) { | ||
String previousGrantType = validationBean.getGrantType(); | ||
// Check if the previous grant type is consent refresh token type or not. | ||
if (!OAuthConstants.GrantTypes.REFRESH_TOKEN.equals(previousGrantType)) { | ||
// If the previous grant type is not a refresh token, then check if it's a consent token or not. | ||
if (OIDCClaimUtil.isConsentBasedClaimFilteringApplicable(previousGrantType)) { | ||
accessTokenDO.setIsConsentedToken(true); | ||
} | ||
} else { | ||
/* When previousGrantType == refresh_token, we need to check whether the original grant type | ||
is consented or not. */ | ||
AccessTokenDO accessTokenDOFromTokenIdentifier = OAuth2Util.getAccessTokenDOFromTokenIdentifier( | ||
validationBean.getAccessToken(), false); | ||
accessTokenDO.setIsConsentedToken(accessTokenDOFromTokenIdentifier.isConsentedToken()); | ||
} | ||
|
||
if (accessTokenDO.isConsentedToken()) { | ||
tokReqMsgCtx.setConsentedToken(true); | ||
} | ||
} | ||
return accessTokenDO; | ||
} | ||
|
||
private boolean validatePersistedAccessToken(RefreshTokenValidationDataDO validationBean, String clientId) | ||
throws IdentityOAuth2Exception { | ||
|
||
if (validationBean.getAccessToken() == null) { | ||
if (log.isDebugEnabled()) { | ||
log.debug(String.format("Invalid Refresh Token provided for Client with Client Id : %s", clientId)); | ||
} | ||
throw new IdentityOAuth2Exception("Persisted access token data not found"); | ||
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isLatestRefreshToken(OAuth2AccessTokenReqDTO tokenReq, RefreshTokenValidationDataDO validationBean, | ||
String userStoreDomain) throws IdentityOAuth2Exception { | ||
|
||
if (log.isDebugEnabled()) { | ||
if (IdentityUtil.isTokenLoggable(IdentityConstants.IdentityTokens.REFRESH_TOKEN)) { | ||
log.debug(String.format("Evaluating refresh token. Token value(hashed): %s, Token state: %s", | ||
DigestUtils.sha256Hex(tokenReq.getRefreshToken()), validationBean.getRefreshTokenState())); | ||
} else { | ||
log.debug(String.format("Evaluating refresh token. Token state: %s", | ||
validationBean.getRefreshTokenState())); | ||
} | ||
} | ||
if (!OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE.equals(validationBean.getRefreshTokenState())) { | ||
/* if refresh token is not in active state, check whether there is an access token issued with the same | ||
* refresh token. | ||
*/ | ||
List<AccessTokenDO> accessTokenBeans = getAccessTokenBeans(tokenReq, validationBean, userStoreDomain); | ||
for (AccessTokenDO token : accessTokenBeans) { | ||
if (tokenReq.getRefreshToken() != null && tokenReq.getRefreshToken().equals(token.getRefreshToken()) | ||
&& (OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE.equals(token.getTokenState()) | ||
|| OAuthConstants.TokenStates.TOKEN_STATE_EXPIRED.equals(token.getTokenState()))) { | ||
return true; | ||
} | ||
} | ||
if (log.isDebugEnabled()) { | ||
log.debug(String.format("Refresh token: %s is not the latest", tokenReq.getRefreshToken())); | ||
} | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
private List<AccessTokenDO> getAccessTokenBeans(OAuth2AccessTokenReqDTO tokenReq, | ||
RefreshTokenValidationDataDO validationBean, String userStoreDomain) | ||
throws IdentityOAuth2Exception { | ||
|
||
List<AccessTokenDO> accessTokenBeans = OAuthTokenPersistenceFactory.getInstance().getAccessTokenDAO() | ||
.getLatestAccessTokens(tokenReq.getClientId(), validationBean.getAuthorizedUser(), userStoreDomain, | ||
OAuth2Util.buildScopeString(validationBean.getScope()), | ||
validationBean.getTokenBindingReference(), true, LAST_ACCESS_TOKEN_RETRIEVAL_LIMIT); | ||
if (accessTokenBeans == null || accessTokenBeans.isEmpty()) { | ||
if (log.isDebugEnabled()) { | ||
log.debug(String.format("No previous access tokens found. User: %s, client: %s, scope: %s", | ||
validationBean.getAuthorizedUser(), tokenReq.getClientId(), | ||
OAuth2Util.buildScopeString(validationBean.getScope()))); | ||
} | ||
throw new IdentityOAuth2Exception("No previous access tokens found"); | ||
} | ||
return accessTokenBeans; | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
...rc/main/java/org/wso2/carbon/identity/oauth/tokenprocessor/OAuth2RevocationProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). | ||
* | ||
* WSO2 LLC. licenses this file to you under the Apache License, | ||
* Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.wso2.carbon.identity.oauth.tokenprocessor; | ||
|
||
import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; | ||
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; | ||
import org.wso2.carbon.identity.oauth2.dto.OAuthRevocationRequestDTO; | ||
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; | ||
import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO; | ||
|
||
/** | ||
* Abstraction layer between OAuth2Service and persistence layer to handle | ||
* revocation logic during token persistence and non-persistence scenarios. | ||
*/ | ||
public interface OAuth2RevocationProcessor { | ||
|
||
/** | ||
* Revoke access token. | ||
* | ||
* @param revokeRequestDTO Metadata containing revoke token request. | ||
* @param accessTokenDO {@link AccessTokenDO} instance. | ||
* @throws IdentityOAuth2Exception If an error occurs while revoking the access token. | ||
* @throws UserIdNotFoundException If the user id is not found. | ||
*/ | ||
void revokeAccessToken(OAuthRevocationRequestDTO revokeRequestDTO, AccessTokenDO accessTokenDO) | ||
throws IdentityOAuth2Exception, UserIdNotFoundException; | ||
|
||
/** | ||
* Revoke refresh token. | ||
* | ||
* @param revokeRequestDTO Metadata containing revoke token request. | ||
* @param refreshTokenDO {@link RefreshTokenValidationDataDO} instance. | ||
* @throws IdentityOAuth2Exception If an error occurs while revoking the refresh token. | ||
*/ | ||
void revokeRefreshToken(OAuthRevocationRequestDTO revokeRequestDTO, | ||
RefreshTokenValidationDataDO refreshTokenDO) throws IdentityOAuth2Exception; | ||
|
||
/** | ||
* Validate and return the refresh token metadata. | ||
* | ||
* @param revokeRequestDTO Metadata containing revoke token request. | ||
* @return RefreshTokenValidationDataDO {@link RefreshTokenValidationDataDO} instance. | ||
* @throws IdentityOAuth2Exception If an error occurs while validating the refresh token. | ||
*/ | ||
RefreshTokenValidationDataDO getRevocableRefreshToken(OAuthRevocationRequestDTO revokeRequestDTO) | ||
throws IdentityOAuth2Exception; | ||
|
||
/** | ||
* Validate and return the access token metadata. | ||
* | ||
* @param revokeRequestDTO Metadata containing revoke token request. | ||
* @return AccessTokenDO {@link AccessTokenDO} instance. | ||
* @throws IdentityOAuth2Exception If an error occurs while validating the access token. | ||
*/ | ||
AccessTokenDO getRevocableAccessToken(OAuthRevocationRequestDTO revokeRequestDTO) | ||
throws IdentityOAuth2Exception; | ||
|
||
/** | ||
* Check whether revoke request is related to access token or revoke token. | ||
* | ||
* @param revokeRequestDTO Metadata containing revoke token request. | ||
* @return boolean whether it is a refresh token request or not | ||
*/ | ||
boolean isRefreshTokenType(OAuthRevocationRequestDTO revokeRequestDTO); | ||
} |
Oops, something went wrong.