/* * 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 java.sql.Timestamp; import java.util.Date; import java.util.UUID; 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.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.wso2.carbon.identity.base.IdentityException; import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.oauth.cache.AppInfoCache; import org.wso2.carbon.identity.oauth.cache.CacheEntry; import org.wso2.carbon.identity.oauth.cache.OAuthCache; import org.wso2.carbon.identity.oauth.cache.OAuthCacheKey; import org.wso2.carbon.identity.oauth.callback.OAuthCallback; import org.wso2.carbon.identity.oauth.callback.OAuthCallbackManager; import org.wso2.carbon.identity.oauth.common.OAuthConstants; import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException; import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth.dao.OAuthAppDAO; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.dao.TokenMgtDAO; 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.token.OAuthTokenReqMessageContext; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; public abstract class AbstractAuthorizationGrantHandler implements AuthorizationGrantHandler { private static Log log = LogFactory.getLog(AbstractAuthorizationGrantHandler.class); protected OAuthIssuer oauthIssuerImpl = OAuthServerConfiguration.getInstance().getOAuthTokenGenerator(); protected TokenMgtDAO tokenMgtDAO; protected OAuthCallbackManager callbackManager; protected boolean cacheEnabled; protected OAuthCache oauthCache; @Override public void init() throws IdentityOAuth2Exception { tokenMgtDAO = new TokenMgtDAO(); callbackManager = new OAuthCallbackManager(); // Set the cache instance if caching is enabled. if (OAuthServerConfiguration.getInstance().isCacheEnabled()) { cacheEnabled = true; oauthCache = OAuthCache.getInstance(); } } @Override public boolean isConfidentialClient() throws IdentityOAuth2Exception { return true; } @Override public boolean issueRefreshToken() throws IdentityOAuth2Exception { return true; } @Override public boolean isOfTypeApplicationUser() throws IdentityOAuth2Exception { return true; } @Override public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { OAuth2AccessTokenRespDTO tokenRespDTO; OAuth2AccessTokenReqDTO oAuth2AccessTokenReqDTO = tokReqMsgCtx.getOauth2AccessTokenReqDTO(); String scope = OAuth2Util.buildScopeString(tokReqMsgCtx.getScope()); String consumerKey = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(); String authorizedUser = tokReqMsgCtx.getAuthorizedUser().toString(); boolean isUsernameCaseSensitive = IdentityUtil.isUserStoreInUsernameCaseSensitive(authorizedUser); String cacheKeyString; if (isUsernameCaseSensitive) { cacheKeyString = consumerKey + ":" + authorizedUser + ":" + scope; } else { cacheKeyString = consumerKey + ":" + authorizedUser.toLowerCase() + ":" + scope; } OAuthCacheKey cacheKey = new OAuthCacheKey(cacheKeyString); String userStoreDomain = null; //select the user store domain when multiple user stores are configured. if (OAuth2Util.checkAccessTokenPartitioningEnabled() && OAuth2Util.checkUserNameAssertionEnabled()) { userStoreDomain = tokReqMsgCtx.getAuthorizedUser().getUserStoreDomain(); } String tokenType; if (isOfTypeApplicationUser()) { tokenType = OAuthConstants.UserType.APPLICATION_USER; } else { tokenType = OAuthConstants.UserType.APPLICATION; } String refreshToken = null; Timestamp refreshTokenIssuedTime = null; long refreshTokenValidityPeriodInMillis = 0; synchronized ((consumerKey + ":" + authorizedUser + ":" + scope).intern()) { // check if valid access token exists in cache if (cacheEnabled) { AccessTokenDO existingAccessTokenDO = null; CacheEntry cacheEntry = oauthCache.getValueFromCache(cacheKey); if (cacheEntry != null && cacheEntry instanceof AccessTokenDO) { existingAccessTokenDO = (AccessTokenDO) cacheEntry; if (log.isDebugEnabled()) { log.debug("Retrieved active access token : " + existingAccessTokenDO.getAccessToken() + " for client Id " + consumerKey + ", user " + authorizedUser + " and scope " + scope + " from cache"); } long expireTime = OAuth2Util.getTokenExpireTimeMillis(existingAccessTokenDO); if (expireTime > 0 || expireTime < 0) { if (log.isDebugEnabled()) { if(expireTime > 0) { log.debug("Access Token " + existingAccessTokenDO.getAccessToken() + " is still valid"); } else { log.debug("Infinite lifetime Access Token " + existingAccessTokenDO.getAccessToken() + " found in cache"); } } tokenRespDTO = new OAuth2AccessTokenRespDTO(); tokenRespDTO.setAccessToken(existingAccessTokenDO.getAccessToken()); tokenRespDTO.setTokenId(existingAccessTokenDO.getTokenId()); if (issueRefreshToken() && OAuthServerConfiguration.getInstance().getSupportedGrantTypes().containsKey( GrantType.REFRESH_TOKEN.toString())) { tokenRespDTO.setRefreshToken(existingAccessTokenDO.getRefreshToken()); } if(expireTime > 0){ tokenRespDTO.setExpiresIn(expireTime/1000); tokenRespDTO.setExpiresInMillis(expireTime); } else { tokenRespDTO.setExpiresIn(Long.MAX_VALUE/1000); tokenRespDTO.setExpiresInMillis(Long.MAX_VALUE); } return tokenRespDTO; } else { long refreshTokenExpiryTime = OAuth2Util.getRefreshTokenExpireTimeMillis(existingAccessTokenDO); if (refreshTokenExpiryTime < 0 || refreshTokenExpiryTime > 0) { log.debug("Access token has expired, But refresh token is still valid. User existing " + "refresh token."); refreshToken = existingAccessTokenDO.getRefreshToken(); refreshTokenIssuedTime = existingAccessTokenDO.getRefreshTokenIssuedTime(); refreshTokenValidityPeriodInMillis = existingAccessTokenDO.getRefreshTokenValidityPeriodInMillis(); } //Token is expired. Clear it from cache. oauthCache.clearCacheEntry(cacheKey); if (log.isDebugEnabled()) { log.debug("Access token " + existingAccessTokenDO.getAccessToken() + " is expired. Therefore cleared it from cache and marked it" + " as expired in database"); } } } } //Check if the last issued access token is still active and valid in database AccessTokenDO existingAccessTokenDO = tokenMgtDAO.retrieveLatestAccessToken( oAuth2AccessTokenReqDTO.getClientId(), tokReqMsgCtx.getAuthorizedUser(), userStoreDomain, scope, false); if (existingAccessTokenDO != null) { if (log.isDebugEnabled()) { log.debug("Retrieved latest access token : " + existingAccessTokenDO.getAccessToken() + " for client Id " + consumerKey + ", user " + authorizedUser + " and scope " + scope + " from database"); } long expireTime = OAuth2Util.getTokenExpireTimeMillis(existingAccessTokenDO); long refreshTokenExpiryTime = OAuth2Util.getRefreshTokenExpireTimeMillis(existingAccessTokenDO); if(OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE.equals( existingAccessTokenDO.getTokenState()) && (expireTime > 0 || expireTime < 0)) { // token is active and valid if (log.isDebugEnabled()) { if(expireTime > 0){ log.debug("Access token " + existingAccessTokenDO.getAccessToken() + " is valid for another " + expireTime + "ms"); } else { log.debug("Infinite lifetime Access Token " + existingAccessTokenDO.getAccessToken() + " found in cache"); } } tokenRespDTO = new OAuth2AccessTokenRespDTO(); tokenRespDTO.setAccessToken(existingAccessTokenDO.getAccessToken()); tokenRespDTO.setTokenId(existingAccessTokenDO.getTokenId()); if (issueRefreshToken() && OAuthServerConfiguration.getInstance().getSupportedGrantTypes().containsKey( GrantType.REFRESH_TOKEN.toString())) { tokenRespDTO.setRefreshToken(existingAccessTokenDO.getRefreshToken()); } if(expireTime > 0) { tokenRespDTO.setExpiresIn(expireTime / 1000); tokenRespDTO.setExpiresInMillis(expireTime); } else { tokenRespDTO.setExpiresIn(Long.MAX_VALUE/1000); tokenRespDTO.setExpiresInMillis(Long.MAX_VALUE); } if (cacheEnabled) { oauthCache.addToCache(cacheKey, existingAccessTokenDO); if (log.isDebugEnabled()) { log.debug("Access Token info was added to the cache for the cache key : " + cacheKey.getCacheKeyString()); } } return tokenRespDTO; } else { if (log.isDebugEnabled()) { log.debug("Access token + " + existingAccessTokenDO.getAccessToken() + " is not valid anymore"); } String tokenState = existingAccessTokenDO.getTokenState(); if (OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE.equals(tokenState)) { // Token is expired. If refresh token is still valid, use it. if (refreshTokenExpiryTime > 0 || refreshTokenExpiryTime < 0) { if (log.isDebugEnabled()) { log.debug("Access token has expired, But refresh token is still valid. User existing " + "refresh token."); } refreshToken = existingAccessTokenDO.getRefreshToken(); refreshTokenIssuedTime = existingAccessTokenDO.getRefreshTokenIssuedTime(); refreshTokenValidityPeriodInMillis = existingAccessTokenDO.getRefreshTokenValidityPeriodInMillis(); } if (log.isDebugEnabled()) { log.debug("Marked token " + existingAccessTokenDO.getAccessToken() + " as expired"); } } else { //Token is revoked or inactive if (log.isDebugEnabled()) { log.debug("Token " + existingAccessTokenDO.getAccessToken() + " is " + existingAccessTokenDO.getTokenState()); } } } } else { if (log.isDebugEnabled()) { log.debug("No access token found in database for client Id " + consumerKey + ", user " + authorizedUser + " and scope " + scope + ". Therefore issuing new token"); } } // issue a new access token. if (log.isDebugEnabled()) { log.debug("Issuing a new access token for " + consumerKey + " AuthorizedUser : " + authorizedUser); } 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(). getApplicationAccessTokenValidityPeriodInSeconds() * 1000; if(isOfTypeApplicationUser()){ 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; } if (tokReqMsgCtx.getOauth2AccessTokenReqDTO() == null || tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType() == null) { throw new IdentityOAuth2Exception("Error while retrieving the grant type"); } String grantType = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType(); AccessTokenDO newAccessTokenDO = new AccessTokenDO(consumerKey, tokReqMsgCtx.getAuthorizedUser(), tokReqMsgCtx.getScope(), timestamp, refreshTokenIssuedTime, validityPeriodInMillis, refreshTokenValidityPeriodInMillis, tokenType); String newAccessToken; try { String userName = tokReqMsgCtx.getAuthorizedUser().toString(); // 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()); newAccessToken = oauthIssuerImpl.accessToken(); if (OAuth2Util.checkUserNameAssertionEnabled()) { //use ':' for token & userStoreDomain separation String accessTokenStrToEncode = newAccessToken + ":" + userName; newAccessToken = Base64Utils.encode(accessTokenStrToEncode.getBytes(Charsets.UTF_8)); } // regenerate only if refresh token is null if (refreshToken == null) { refreshToken = oauthIssuerImpl.refreshToken(); if (OAuth2Util.checkUserNameAssertionEnabled()) { //use ':' for token & userStoreDomain separation String refreshTokenStrToEncode = refreshToken + ":" + userName; refreshToken = Base64Utils.encode(refreshTokenStrToEncode.getBytes(Charsets.UTF_8)); } } } catch (OAuthSystemException e) { throw new IdentityOAuth2Exception( "Error occurred while generating access token and refresh token", e); } newAccessTokenDO.setAccessToken(newAccessToken); newAccessTokenDO.setRefreshToken(refreshToken); newAccessTokenDO.setTokenState(OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE); String tenantDomain = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getTenantDomain(); newAccessTokenDO.setTenantID(OAuth2Util.getTenantId(tenantDomain)); newAccessTokenDO.setTokenId(UUID.randomUUID().toString()); newAccessTokenDO.setGrantType(grantType); // Persist the access token in database storeAccessToken(oAuth2AccessTokenReqDTO, userStoreDomain, newAccessTokenDO, newAccessToken, existingAccessTokenDO); if (log.isDebugEnabled()) { log.debug("Persisted Access Token for " + "Client ID : " + oAuth2AccessTokenReqDTO.getClientId() + ", Authorized User : " + tokReqMsgCtx.getAuthorizedUser() + ", Timestamp : " + timestamp + ", Validity period (s) : " + newAccessTokenDO.getValidityPeriod() + ", Scope : " + OAuth2Util.buildScopeString(tokReqMsgCtx.getScope()) + " and Token State : " + OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE); } //update cache with newly added token if (cacheEnabled) { oauthCache.addToCache(cacheKey, newAccessTokenDO); if (log.isDebugEnabled()) { log.debug("Access token was added to OAuthCache for cache key : " + cacheKey.getCacheKeyString()); } } tokenRespDTO = new OAuth2AccessTokenRespDTO(); tokenRespDTO.setAccessToken(newAccessToken); tokenRespDTO.setTokenId(newAccessTokenDO.getTokenId()); if (issueRefreshToken() && OAuthServerConfiguration.getInstance().getSupportedGrantTypes().containsKey( GrantType.REFRESH_TOKEN.toString())) { tokenRespDTO.setRefreshToken(refreshToken); } if (validityPeriodInMillis > 0) { tokenRespDTO.setExpiresInMillis(newAccessTokenDO.getValidityPeriodInMillis()); tokenRespDTO.setExpiresIn(newAccessTokenDO.getValidityPeriod()); } else { tokenRespDTO.setExpiresInMillis(Long.MAX_VALUE); tokenRespDTO.setExpiresIn(Long.MAX_VALUE); } tokenRespDTO.setAuthorizedScopes(scope); return tokenRespDTO; } } protected void storeAccessToken(OAuth2AccessTokenReqDTO oAuth2AccessTokenReqDTO, String userStoreDomain, AccessTokenDO newAccessTokenDO, String newAccessToken, AccessTokenDO existingAccessTokenDO) throws IdentityOAuth2Exception { try { tokenMgtDAO.storeAccessToken(newAccessToken, oAuth2AccessTokenReqDTO.getClientId(), newAccessTokenDO, existingAccessTokenDO, userStoreDomain); } catch (IdentityException e) { throw new IdentityOAuth2Exception( "Error occurred while storing new access token : " + newAccessToken, e); } } @Override public boolean authorizeAccessDelegation(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { OAuthCallback authzCallback = new OAuthCallback(tokReqMsgCtx.getAuthorizedUser(), tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(), OAuthCallback.OAuthCallbackType.ACCESS_DELEGATION_TOKEN); authzCallback.setRequestedScope(tokReqMsgCtx.getScope()); if (tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType().equals( org.wso2.carbon.identity.oauth.common.GrantType.SAML20_BEARER.toString())) { authzCallback.setCarbonGrantType(org.wso2.carbon.identity.oauth.common.GrantType.valueOf( OAuthConstants.OAUTH_SAML2_BEARER_GRANT_ENUM.toString())); } else if (tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType().equals( org.wso2.carbon.identity.oauth.common.GrantType.IWA_NTLM.toString())) { authzCallback.setCarbonGrantType(org.wso2.carbon.identity.oauth.common.GrantType.valueOf( OAuthConstants.OAUTH_IWA_NTLM_GRANT_ENUM.toString())); } else { authzCallback.setGrantType(tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType()); } callbackManager.handleCallback(authzCallback); tokReqMsgCtx.setValidityPeriod(authzCallback.getValidityPeriod()); return authzCallback.isAuthorized(); } @Override public boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { OAuthCallback scopeValidationCallback = new OAuthCallback(tokReqMsgCtx.getAuthorizedUser(), tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(), OAuthCallback.OAuthCallbackType .SCOPE_VALIDATION_TOKEN); scopeValidationCallback.setRequestedScope(tokReqMsgCtx.getScope()); if (tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType().equals( org.wso2.carbon.identity.oauth.common.GrantType.SAML20_BEARER.toString())) { scopeValidationCallback.setCarbonGrantType(org.wso2.carbon.identity.oauth.common.GrantType.valueOf( OAuthConstants.OAUTH_SAML2_BEARER_GRANT_ENUM.toString())); } else if (tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType().equals( org.wso2.carbon.identity.oauth.common.GrantType.IWA_NTLM.toString())) { scopeValidationCallback.setCarbonGrantType(org.wso2.carbon.identity.oauth.common.GrantType.valueOf( OAuthConstants.OAUTH_IWA_NTLM_GRANT_ENUM.toString())); } else { scopeValidationCallback.setGrantType(tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType()); } callbackManager.handleCallback(scopeValidationCallback); tokReqMsgCtx.setValidityPeriod(scopeValidationCallback.getValidityPeriod()); tokReqMsgCtx.setScope(scopeValidationCallback.getApprovedScope()); return scopeValidationCallback.isValidScope(); } @Override public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { OAuth2AccessTokenReqDTO tokenReqDTO = tokReqMsgCtx.getOauth2AccessTokenReqDTO(); String grantType = tokenReqDTO.getGrantType(); // Load application data from the cache AppInfoCache appInfoCache = AppInfoCache.getInstance(); OAuthAppDO oAuthAppDO = appInfoCache.getValueFromCache(tokenReqDTO.getClientId()); if (oAuthAppDO == null) { try { oAuthAppDO = new OAuthAppDAO().getAppInformation(tokenReqDTO.getClientId()); appInfoCache.addToCache(tokenReqDTO.getClientId(), oAuthAppDO); } catch (InvalidOAuthClientException e) { throw new IdentityOAuth2Exception(e.getMessage(), e); } } // If the application has defined a limited set of grant types, then check the grant if (oAuthAppDO.getGrantTypes() != null && !oAuthAppDO.getGrantTypes().contains(grantType)) { if (log.isDebugEnabled()) { //Do not change this log format as these logs use by external applications log.debug("Unsupported Grant Type : " + grantType + " for client id : " + tokenReqDTO.getClientId()); } return false; } return true; } }