/*
* 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.authz.handlers;
import org.apache.axiom.util.base64.Base64Utils;
import org.apache.commons.io.Charsets;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.ResponseType;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.util.IdentityUtil;
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.authz.OAuthAuthzReqMessageContext;
import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO;
import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeRespDTO;
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.identity.openidconnect.IDTokenBuilder;
import java.sql.Timestamp;
import java.util.Date;
import java.util.UUID;
public class TokenResponseTypeHandler extends AbstractResponseTypeHandler {
private static Log log = LogFactory.getLog(TokenResponseTypeHandler.class);
@Override
public OAuth2AuthorizeRespDTO issue(OAuthAuthzReqMessageContext oauthAuthzMsgCtx)
throws IdentityOAuth2Exception {
OAuth2AuthorizeRespDTO respDTO = new OAuth2AuthorizeRespDTO();
OAuth2AuthorizeReqDTO authorizationReqDTO = oauthAuthzMsgCtx.getAuthorizationReqDTO();
String scope = OAuth2Util.buildScopeString(oauthAuthzMsgCtx.getApprovedScope());
respDTO.setCallbackURI(authorizationReqDTO.getCallbackUrl());
String consumerKey = authorizationReqDTO.getConsumerKey();
String authorizedUser = authorizationReqDTO.getUser().toString();
String oAuthCacheKeyString;
String responseType = oauthAuthzMsgCtx.getAuthorizationReqDTO().getResponseType();
String grantType;
if (StringUtils.contains(responseType, OAuthConstants.GrantTypes.TOKEN)) {
grantType = OAuthConstants.GrantTypes.IMPLICIT;
} else {
grantType = responseType;
}
boolean isUsernameCaseSensitive = IdentityUtil.isUserStoreInUsernameCaseSensitive(authorizedUser);
if (isUsernameCaseSensitive) {
oAuthCacheKeyString = consumerKey + ":" + authorizedUser + ":" + scope;
} else {
oAuthCacheKeyString = consumerKey + ":" + authorizedUser.toLowerCase() + ":" + scope;
}
OAuthCacheKey cacheKey = new OAuthCacheKey(oAuthCacheKeyString);
String userStoreDomain = null;
// Select the user store domain when multiple user stores are configured.
if (OAuth2Util.checkAccessTokenPartitioningEnabled() &&
OAuth2Util.checkUserNameAssertionEnabled()) {
userStoreDomain = OAuth2Util.getUserStoreDomainFromUserId(authorizedUser);
}
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 accessTokenDO = (AccessTokenDO) oauthCache.getValueFromCache(cacheKey);
if (accessTokenDO != null) {
if (log.isDebugEnabled()) {
log.debug("Retrieved active Access Token" +
" for Client Id : " + consumerKey + ", User ID :" + authorizedUser +
" and Scope : " + scope + " from cache");
}
long expireTime = OAuth2Util.getTokenExpireTimeMillis(accessTokenDO);
if (expireTime > 0 || expireTime < 0) {
if (log.isDebugEnabled()) {
if(expireTime > 0) {
log.debug("Access Token"+
" is valid for another " + expireTime + "ms");
} else {
log.debug("Infinite lifetime Access Token found in cache");
}
}
respDTO.setAccessToken(accessTokenDO.getAccessToken());
if (expireTime > 0) {
respDTO.setValidityPeriod(expireTime/1000);
} else {
respDTO.setValidityPeriod(Long.MAX_VALUE/1000);
}
respDTO.setScope(oauthAuthzMsgCtx.getApprovedScope());
respDTO.setTokenType(accessTokenDO.getTokenType());
buildIdToken(oauthAuthzMsgCtx, respDTO);
return respDTO;
} else {
long refreshTokenExpiryTime = OAuth2Util.getRefreshTokenExpireTimeMillis(accessTokenDO);
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 = accessTokenDO.getRefreshToken();
refreshTokenIssuedTime = accessTokenDO.getRefreshTokenIssuedTime();
refreshTokenValidityPeriodInMillis = accessTokenDO.getRefreshTokenValidityPeriodInMillis();
}
// Token is expired. Clear it from cache
oauthCache.clearCacheEntry(cacheKey);
if (log.isDebugEnabled()) {
log.debug("Access Token is expired. Therefore cleared it from cache and marked it" +
" as expired in database");
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("No active access token found in cache for Client ID : " + consumerKey +
", User ID : " + authorizedUser + " and Scope : " + scope);
}
}
}
// check if the last issued access token is still active and valid in the database
AccessTokenDO existingAccessTokenDO = tokenMgtDAO.retrieveLatestAccessToken(
consumerKey, authorizationReqDTO.getUser(), userStoreDomain, scope, false);
if (existingAccessTokenDO != null) {
if (log.isDebugEnabled()) {
log.debug("Retrieved latest Access Token" +
" for Client ID : " + consumerKey + ", User ID :" + authorizedUser +
" and Scope : " + scope + " from database");
}
long expiryTime = OAuth2Util.getTokenExpireTimeMillis(existingAccessTokenDO);
long refreshTokenExpiryTime = OAuth2Util.getRefreshTokenExpireTimeMillis(existingAccessTokenDO);
if (OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE.equals(
existingAccessTokenDO.getTokenState()) && (expiryTime > 0 || expiryTime < 0)) {
// token is active and valid
if (log.isDebugEnabled()) {
if(expiryTime > 0) {
log.debug("Access token is valid for another " + expiryTime + "ms");
} else {
log.debug("Infinite lifetime Access Token found in cache");
}
}
if (cacheEnabled) {
oauthCache.addToCache(cacheKey, existingAccessTokenDO);
if (log.isDebugEnabled()) {
log.debug("Access Token was added to cache for cache key : "
+ cacheKey.getCacheKeyString());
}
}
respDTO.setAccessToken(existingAccessTokenDO.getAccessToken());
if(expiryTime > 0) {
respDTO.setValidityPeriod(expiryTime / 1000);
} else {
respDTO.setValidityPeriod(Long.MAX_VALUE / 1000);
}
respDTO.setScope(oauthAuthzMsgCtx.getApprovedScope());
respDTO.setTokenType(existingAccessTokenDO.getTokenType());
buildIdToken(oauthAuthzMsgCtx, respDTO);
return respDTO;
} else {
if (log.isDebugEnabled()) {
log.debug("Access Token is " + existingAccessTokenDO.getTokenState());
}
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 Access Token as expired");
}
} else {
//Token is revoked or inactive
if (log.isDebugEnabled()) {
log.debug("Access Token is " + existingAccessTokenDO.getTokenState());
}
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("No access token found in database for Client ID : " + consumerKey +
", User ID : " + authorizedUser + " and Scope : " + scope +
". Therefore issuing new access token");
}
}
Timestamp timestamp = new Timestamp(new Date().getTime());
// if reusing existing refresh token, use its original issued time
if(refreshTokenIssuedTime == null) {
refreshTokenIssuedTime = timestamp;
}
// Default token validity Period
long validityPeriodInMillis = OAuthServerConfiguration.getInstance().
getUserAccessTokenValidityPeriodInSeconds() * 1000;
// if a VALID validity period is set through the callback, then use it
long callbackValidityPeriod = oauthAuthzMsgCtx.getValidityPeriod();
if ((callbackValidityPeriod != OAuthConstants.UNASSIGNED_VALIDITY_PERIOD)
&& callbackValidityPeriod > 0) {
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;
}
// issue a new access token
String accessToken;
// 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.
oauthAuthzMsgCtx.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.
oauthAuthzMsgCtx.setRefreshTokenvalidityPeriod(refreshTokenValidityPeriodInMillis);
// set access token issued time.this is needed by downstream handlers.
oauthAuthzMsgCtx.setAccessTokenIssuedTime(timestamp.getTime());
// set refresh token issued time.this is needed by downstream handlers.
oauthAuthzMsgCtx.setRefreshTokenIssuedTime(refreshTokenIssuedTime.getTime());
try {
accessToken = oauthIssuerImpl.accessToken();
// regenerate only if refresh token is null
if (refreshToken == null) {
refreshToken = oauthIssuerImpl.refreshToken();
}
} catch (OAuthSystemException e) {
throw new IdentityOAuth2Exception("Error occurred while generating access token and refresh token", e);
}
if (OAuth2Util.checkUserNameAssertionEnabled()) {
String userName = oauthAuthzMsgCtx.getAuthorizationReqDTO().getUser().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));
}
AccessTokenDO newAccessTokenDO = new AccessTokenDO(consumerKey, authorizationReqDTO.getUser(), oauthAuthzMsgCtx.getApprovedScope(), timestamp,
refreshTokenIssuedTime, validityPeriodInMillis, refreshTokenValidityPeriodInMillis,
OAuthConstants.UserType.APPLICATION_USER);
newAccessTokenDO.setAccessToken(accessToken);
newAccessTokenDO.setRefreshToken(refreshToken);
newAccessTokenDO.setTokenState(OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE);
newAccessTokenDO.setTokenId(UUID.randomUUID().toString());
newAccessTokenDO.setGrantType(grantType);
// Persist the access token in database
try {
tokenMgtDAO.storeAccessToken(accessToken, authorizationReqDTO.getConsumerKey(),
newAccessTokenDO, existingAccessTokenDO, userStoreDomain);
} catch (IdentityException e) {
throw new IdentityOAuth2Exception(
"Error occurred while storing new access token : " + accessToken, e);
}
if (log.isDebugEnabled()) {
log.debug("Persisted Access Token for " +
"Client ID : " + authorizationReqDTO.getConsumerKey() +
", Authorized User : " + authorizationReqDTO.getUser() +
", Timestamp : " + timestamp +
", Validity period (s) : " + newAccessTokenDO.getValidityPeriod() +
", Scope : " + OAuth2Util.buildScopeString(oauthAuthzMsgCtx.getApprovedScope()) +
", Callback URL : " + authorizationReqDTO.getCallbackUrl() +
", Token State : " + OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE +
" and User Type : " + OAuthConstants.UserType.APPLICATION_USER);
}
// Add the access token to the cache.
if (cacheEnabled) {
oauthCache.addToCache(cacheKey, newAccessTokenDO);
if (log.isDebugEnabled()) {
log.debug("Access Token was added to OAuthCache for " +
"cache key : " + cacheKey.getCacheKeyString());
}
}
if(StringUtils.contains(responseType, ResponseType.TOKEN.toString())) {
respDTO.setAccessToken(accessToken);
if (validityPeriodInMillis > 0) {
respDTO.setValidityPeriod(newAccessTokenDO.getValidityPeriod());
} else {
respDTO.setValidityPeriod(Long.MAX_VALUE / 1000);
}
respDTO.setScope(newAccessTokenDO.getScope());
respDTO.setTokenType(newAccessTokenDO.getTokenType());
}
}
buildIdToken(oauthAuthzMsgCtx, respDTO);
return respDTO;
}
private void buildIdToken(OAuthAuthzReqMessageContext msgCtx, OAuth2AuthorizeRespDTO authzRespDTO)
throws IdentityOAuth2Exception{
if (StringUtils.contains(msgCtx.getAuthorizationReqDTO().getResponseType(), "id_token") &&
msgCtx.getApprovedScope() != null && OAuth2Util.isOIDCAuthzRequest(msgCtx.getApprovedScope())) {
IDTokenBuilder builder = OAuthServerConfiguration.getInstance().getOpenIDConnectIDTokenBuilder();
authzRespDTO.setIdToken(builder.buildIDToken(msgCtx, authzRespDTO));
}
}
}