/* * Copyright (c) 2013, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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.oauth2.token.handlers.grant; import org.apache.axiom.util.base64.Base64Utils; import org.apache.commons.io.Charsets; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.oltu.oauth2.common.error.OAuthError; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.oauth.cache.CacheKey; import org.wso2.carbon.identity.oauth.cache.OAuthCacheKey; import org.wso2.carbon.identity.oauth.common.OAuthConstants; import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.ResponseHeader; import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO; 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 java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.UUID; /** * Grant Type handler for Grant Type refresh_token which is used to get a new access token. */ public class RefreshGrantHandler extends AbstractAuthorizationGrantHandler { private static final String PREV_ACCESS_TOKEN = "previousAccessToken"; private static Log log = LogFactory.getLog(RefreshGrantHandler.class); @Override public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { if(!super.validateGrant(tokReqMsgCtx)){ return false; } OAuth2AccessTokenReqDTO tokenReqDTO = tokReqMsgCtx.getOauth2AccessTokenReqDTO(); String refreshToken = tokenReqDTO.getRefreshToken(); RefreshTokenValidationDataDO validationDataDO = tokenMgtDAO.validateRefreshToken( tokenReqDTO.getClientId(), refreshToken); if (validationDataDO.getAccessToken() == null) { log.debug("Invalid Refresh Token provided for Client with " + "Client Id : " + tokenReqDTO.getClientId()); return false; } if (validationDataDO.getRefreshTokenState() != null && !OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE.equals( validationDataDO.getRefreshTokenState()) && !OAuthConstants.TokenStates.TOKEN_STATE_EXPIRED.equals( validationDataDO.getRefreshTokenState())) { if(log.isDebugEnabled()) { log.debug("Access Token is not in 'ACTIVE' or 'EXPIRED' state for Client with " + "Client Id : " + tokenReqDTO.getClientId()); } return false; } String userStoreDomain = null; if (OAuth2Util.checkAccessTokenPartitioningEnabled() && OAuth2Util.checkUserNameAssertionEnabled()) { try { userStoreDomain = OAuth2Util.getUserStoreDomainFromUserId(validationDataDO.getAuthorizedUser().toString()); } catch (IdentityOAuth2Exception e) { String errorMsg = "Error occurred while getting user store domain for User ID : " + validationDataDO.getAuthorizedUser(); log.error(errorMsg, e); throw new IdentityOAuth2Exception(errorMsg, e); } } AccessTokenDO accessTokenDO = tokenMgtDAO.retrieveLatestAccessToken(tokenReqDTO.getClientId(), validationDataDO.getAuthorizedUser(), userStoreDomain, OAuth2Util.buildScopeString(validationDataDO.getScope()), true); if (accessTokenDO == null){ if(log.isDebugEnabled()){ log.debug("Error while retrieving the latest refresh token"); } return false; }else if(!refreshToken.equals(accessTokenDO.getRefreshToken())){ if(log.isDebugEnabled()){ log.debug("Refresh token is not the latest."); } return false; } if (log.isDebugEnabled()) { log.debug("Refresh token validation successful for " + "Client id : " + tokenReqDTO.getClientId() + ", Authorized User : " + validationDataDO.getAuthorizedUser() + ", Token Scope : " + OAuth2Util.buildScopeString(validationDataDO.getScope())); } tokReqMsgCtx.setAuthorizedUser(validationDataDO.getAuthorizedUser()); tokReqMsgCtx.setScope(validationDataDO.getScope()); // Store the old access token as a OAuthTokenReqMessageContext property, this is already // a preprocessed token. tokReqMsgCtx.addProperty(PREV_ACCESS_TOKEN, validationDataDO); return true; } @Override public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { OAuth2AccessTokenRespDTO tokenRespDTO = new OAuth2AccessTokenRespDTO(); OAuth2AccessTokenReqDTO oauth2AccessTokenReqDTO = tokReqMsgCtx.getOauth2AccessTokenReqDTO(); String scope = OAuth2Util.buildScopeString(tokReqMsgCtx.getScope()); String tokenId; String accessToken; String refreshToken; String userStoreDomain = null; String grantType; Timestamp refreshTokenIssuedTime = null; long refreshTokenValidityPeriodInMillis = 0; tokenId = UUID.randomUUID().toString(); grantType = oauth2AccessTokenReqDTO.getGrantType(); try { accessToken = oauthIssuerImpl.accessToken(); refreshToken = oauthIssuerImpl.refreshToken(); } catch (OAuthSystemException e) { throw new IdentityOAuth2Exception("Error when generating the tokens.", e); } boolean renew = OAuthServerConfiguration.getInstance().isRefreshTokenRenewalEnabled(); // an active or expired token will be returned. since we do the validation for active or expired token in // validateGrant() no need to do it here again RefreshTokenValidationDataDO refreshTokenValidationDataDO = tokenMgtDAO .validateRefreshToken(oauth2AccessTokenReqDTO.getClientId(), oauth2AccessTokenReqDTO.getRefreshToken()); long issuedTime = refreshTokenValidationDataDO.getIssuedTime().getTime(); long refreshValidity = refreshTokenValidationDataDO.getValidityPeriodInMillis(); long skew = OAuthServerConfiguration.getInstance().getTimeStampSkewInSeconds() * 1000; if (issuedTime + refreshValidity - (System.currentTimeMillis() + skew) > 1000) { if (!renew) { // if refresh token renewal not enabled, we use existing one else we issue a new refresh token refreshToken = oauth2AccessTokenReqDTO.getRefreshToken(); refreshTokenIssuedTime = refreshTokenValidationDataDO.getIssuedTime(); refreshTokenValidityPeriodInMillis = refreshTokenValidationDataDO.getValidityPeriodInMillis(); } } else { // todo add proper error message/error code return handleError(OAuthError.TokenResponse.INVALID_REQUEST, "Refresh token is expired."); } Timestamp timestamp = new Timestamp(new Date().getTime()); // if reusing existing refresh token, use its original issued time if (refreshTokenIssuedTime == null) { refreshTokenIssuedTime = timestamp; } // Default Validity Period (in seconds) long validityPeriodInMillis = OAuthServerConfiguration.getInstance() .getUserAccessTokenValidityPeriodInSeconds() * 1000; // if a VALID validity period is set through the callback, then use it long callbackValidityPeriod = tokReqMsgCtx.getValidityPeriod(); if (callbackValidityPeriod != OAuthConstants.UNASSIGNED_VALIDITY_PERIOD) { validityPeriodInMillis = callbackValidityPeriod * 1000; } // If issuing new refresh token, use default refresh token validity Period // otherwise use existing refresh token's validity period if (refreshTokenValidityPeriodInMillis == 0) { refreshTokenValidityPeriodInMillis = OAuthServerConfiguration.getInstance() .getRefreshTokenValidityPeriodInSeconds() * 1000; } String tokenType; if (isOfTypeApplicationUser()) { tokenType = OAuthConstants.UserType.APPLICATION_USER; } else { tokenType = OAuthConstants.UserType.APPLICATION; } String clientId = oauth2AccessTokenReqDTO.getClientId(); // set the validity period. this is needed by downstream handlers. // if this is set before - then this will override it by the calculated new value. tokReqMsgCtx.setValidityPeriod(validityPeriodInMillis); // set the refresh token validity period. this is needed by downstream handlers. // if this is set before - then this will override it by the calculated new value. tokReqMsgCtx.setRefreshTokenvalidityPeriod(refreshTokenValidityPeriodInMillis); // set access token issued time.this is needed by downstream handlers. tokReqMsgCtx.setAccessTokenIssuedTime(timestamp.getTime()); // set refresh token issued time.this is needed by downstream handlers. tokReqMsgCtx.setRefreshTokenIssuedTime(refreshTokenIssuedTime.getTime()); if (OAuth2Util.checkUserNameAssertionEnabled()) { String userName = tokReqMsgCtx.getAuthorizedUser().toString(); // use ':' for token & userStoreDomain separation String accessTokenStrToEncode = accessToken + ":" + userName; accessToken = Base64Utils.encode(accessTokenStrToEncode.getBytes(Charsets.UTF_8)); String refreshTokenStrToEncode = refreshToken + ":" + userName; refreshToken = Base64Utils.encode(refreshTokenStrToEncode.getBytes(Charsets.UTF_8)); // logic to store access token into different tables when multiple user stores are configured. if (OAuth2Util.checkAccessTokenPartitioningEnabled()) { userStoreDomain = OAuth2Util.getUserStoreDomainFromUserId(userName); } } AccessTokenDO accessTokenDO = new AccessTokenDO(clientId, tokReqMsgCtx.getAuthorizedUser(), tokReqMsgCtx.getScope(), timestamp, refreshTokenIssuedTime, validityPeriodInMillis, refreshTokenValidityPeriodInMillis, tokenType); accessTokenDO.setTokenState(OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE); accessTokenDO.setRefreshToken(refreshToken); accessTokenDO.setTokenId(tokenId); accessTokenDO.setAccessToken(accessToken); accessTokenDO.setGrantType(grantType); RefreshTokenValidationDataDO oldAccessToken = (RefreshTokenValidationDataDO) tokReqMsgCtx.getProperty(PREV_ACCESS_TOKEN); String authorizedUser = tokReqMsgCtx.getAuthorizedUser().toString(); // set the previous access token state to "INACTIVE" and store new access token in single db connection tokenMgtDAO.invalidateAndCreateNewToken(oldAccessToken.getTokenId(), "INACTIVE", clientId, UUID.randomUUID().toString(), accessTokenDO, userStoreDomain); //remove the previous access token from cache and add the new access token info to the cache, // if it's enabled. if (cacheEnabled) { // Remove the old access token from the OAuthCache boolean isUsernameCaseSensitive = IdentityUtil.isUserStoreInUsernameCaseSensitive(authorizedUser); String cacheKeyString; if (isUsernameCaseSensitive) { cacheKeyString = clientId + ":" + authorizedUser + ":" + scope; } else { cacheKeyString = clientId + ":" + authorizedUser.toLowerCase() + ":" + scope; } OAuthCacheKey oauthCacheKey = new OAuthCacheKey(cacheKeyString); oauthCache.clearCacheEntry(oauthCacheKey); // Remove the old access token from the AccessTokenCache OAuthCacheKey accessTokenCacheKey = new OAuthCacheKey(oldAccessToken.getAccessToken()); oauthCache.clearCacheEntry(accessTokenCacheKey); // Add new access token to the OAuthCache oauthCache.addToCache(oauthCacheKey, accessTokenDO); // Add new access token to the AccessTokenCache accessTokenCacheKey = new OAuthCacheKey(accessToken); oauthCache.addToCache(accessTokenCacheKey, accessTokenDO); if (log.isDebugEnabled()) { log.debug("Access Token info for the refresh token was added to the cache for " + "the client id : " + clientId + ". Old access token entry was " + "also removed from the cache."); } } if (log.isDebugEnabled()) { log.debug("Persisted an access token for the refresh token, " + "Client ID : " + clientId + "authorized user : " + tokReqMsgCtx.getAuthorizedUser() + "timestamp : " + timestamp + "validity period (s) : " + accessTokenDO.getValidityPeriod() + "scope : " + OAuth2Util.buildScopeString(tokReqMsgCtx.getScope()) + "Token State : " + OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE + "User Type : " + tokenType); } tokenRespDTO.setAccessToken(accessToken); tokenRespDTO.setRefreshToken(refreshToken); if (validityPeriodInMillis > 0) { tokenRespDTO.setExpiresIn(accessTokenDO.getValidityPeriod()); tokenRespDTO.setExpiresInMillis(accessTokenDO.getValidityPeriodInMillis()); } else { tokenRespDTO.setExpiresIn(Long.MAX_VALUE); tokenRespDTO.setExpiresInMillis(Long.MAX_VALUE); } tokenRespDTO.setAuthorizedScopes(scope); ArrayList<ResponseHeader> respHeaders = new ArrayList<ResponseHeader>(); ResponseHeader header = new ResponseHeader(); header.setKey("DeactivatedAccessToken"); header.setValue(oldAccessToken.getAccessToken()); respHeaders.add(header); tokReqMsgCtx.addProperty("RESPONSE_HEADERS", respHeaders.toArray( new ResponseHeader[respHeaders.size()])); return tokenRespDTO; } private OAuth2AccessTokenRespDTO handleError(String errorCode, String errorMsg) { OAuth2AccessTokenRespDTO tokenRespDTO; tokenRespDTO = new OAuth2AccessTokenRespDTO(); tokenRespDTO.setError(true); tokenRespDTO.setErrorCode(errorCode); tokenRespDTO.setErrorMsg(errorMsg); return tokenRespDTO; } }