/*
* 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;
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.message.types.GrantType;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.oauth.dao.OAuthAppDO;
import org.wso2.carbon.identity.oauth.cache.AppInfoCache;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCache;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheEntry;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheKey;
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.ResponseHeader;
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO;
import org.wso2.carbon.identity.oauth2.token.handlers.clientauth.ClientAuthenticationHandler;
import org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationGrantHandler;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.identity.openidconnect.IDTokenBuilder;
import org.wso2.carbon.utils.CarbonUtils;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
/**
* This class is used to issue access tokens and refresh tokens.
*/
public class AccessTokenIssuer {
private static AccessTokenIssuer instance;
private static Log log = LogFactory.getLog(AccessTokenIssuer.class);
private Map<String, AuthorizationGrantHandler> authzGrantHandlers =
new Hashtable<String, AuthorizationGrantHandler>();
private List<ClientAuthenticationHandler> clientAuthenticationHandlers =
new ArrayList<ClientAuthenticationHandler>();
private AppInfoCache appInfoCache;
/**
* Private constructor which will not allow to create objects of this class from outside
*/
private AccessTokenIssuer() throws IdentityOAuth2Exception {
authzGrantHandlers = OAuthServerConfiguration.getInstance().getSupportedGrantTypes();
clientAuthenticationHandlers = OAuthServerConfiguration.getInstance().getSupportedClientAuthHandlers();
appInfoCache = AppInfoCache.getInstance();
if (appInfoCache != null) {
if (log.isDebugEnabled()) {
log.debug("Successfully created AppInfoCache under " + OAuthConstants.OAUTH_CACHE_MANAGER);
}
} else {
log.error("Error while creating AppInfoCache");
}
}
/**
* Singleton method
*
* @return AccessTokenIssuer
*/
public static AccessTokenIssuer getInstance() throws IdentityOAuth2Exception {
CarbonUtils.checkSecurity();
if (instance == null) {
synchronized (AccessTokenIssuer.class) {
if (instance == null) {
instance = new AccessTokenIssuer();
}
}
}
return instance;
}
/**
* Issue access token using the respective grant handler and client authentication handler.
*
* @param tokenReqDTO
* @return access token response
* @throws IdentityException
* @throws InvalidOAuthClientException
*/
public OAuth2AccessTokenRespDTO issue(OAuth2AccessTokenReqDTO tokenReqDTO)
throws IdentityException, InvalidOAuthClientException {
String grantType = tokenReqDTO.getGrantType();
OAuth2AccessTokenRespDTO tokenRespDTO;
AuthorizationGrantHandler authzGrantHandler = authzGrantHandlers.get(grantType);
OAuthTokenReqMessageContext tokReqMsgCtx = new OAuthTokenReqMessageContext(tokenReqDTO);
// If multiple client authentication methods have been used the authorization server must reject the request
int authenticatorHandlerIndex = -1;
for (int i = 0; i < clientAuthenticationHandlers.size(); i++) {
if (clientAuthenticationHandlers.get(i).canAuthenticate(tokReqMsgCtx)) {
if (authenticatorHandlerIndex > -1) {
log.debug("Multiple Client Authentication Methods used for client id : " +
tokenReqDTO.getClientId());
tokenRespDTO = handleError(
OAuthConstants.OAuthError.TokenResponse.UNSUPPORTED_CLIENT_AUTHENTICATION_METHOD,
"Unsupported Client Authentication Method!", tokenReqDTO);
setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
return tokenRespDTO;
}
authenticatorHandlerIndex = i;
}
}
if (authenticatorHandlerIndex < 0 && authzGrantHandler.isConfidentialClient()) {
log.debug("Confidential client cannot be authenticated for client id : " +
tokenReqDTO.getClientId());
tokenRespDTO = handleError(
OAuthConstants.OAuthError.TokenResponse.UNSUPPORTED_CLIENT_AUTHENTICATION_METHOD,
"Unsupported Client Authentication Method!", tokenReqDTO);
setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
return tokenRespDTO;
}
ClientAuthenticationHandler clientAuthHandler = null;
if (authenticatorHandlerIndex > -1) {
clientAuthHandler = clientAuthenticationHandlers.get(authenticatorHandlerIndex);
}
boolean isAuthenticated;
if (clientAuthHandler != null) {
isAuthenticated = clientAuthHandler.authenticateClient(tokReqMsgCtx);
} else {
isAuthenticated = true;
}
if (!isAuthenticated) {
if(log.isDebugEnabled()) {
log.debug("Client Authentication failed for client Id: " + tokenReqDTO.getClientId());
}
tokenRespDTO = handleError(OAuthError.TokenResponse.INVALID_CLIENT,
"Client credentials are invalid.", tokenReqDTO);
setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
return tokenRespDTO;
}
// loading the stored application data
OAuthAppDO oAuthAppDO = getAppInformation(tokenReqDTO);
if (!authzGrantHandler.isOfTypeApplicationUser()) {
tokReqMsgCtx.setAuthorizedUser(oAuthAppDO.getUser());
}
boolean isValidGrant = false;
String error = "Provided Authorization Grant is invalid";
try {
isValidGrant = authzGrantHandler.validateGrant(tokReqMsgCtx);
} catch (IdentityOAuth2Exception e) {
if(log.isDebugEnabled()){
log.debug("Error occurred while validating grant", e);
}
error = e.getMessage();
}
if (!isValidGrant) {
if (log.isDebugEnabled()) {
log.debug("Invalid Grant provided by the client Id: " + tokenReqDTO.getClientId());
}
tokenRespDTO = handleError(OAuthError.TokenResponse.INVALID_GRANT, error, tokenReqDTO);
setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
return tokenRespDTO;
}
boolean isAuthorized = authzGrantHandler.authorizeAccessDelegation(tokReqMsgCtx);
if (!isAuthorized) {
if(log.isDebugEnabled()) {
log.debug("Invalid authorization for client Id = " + tokenReqDTO.getClientId());
}
tokenRespDTO = handleError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT,
"Unauthorized Client!", tokenReqDTO);
setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
return tokenRespDTO;
}
boolean isValidScope = authzGrantHandler.validateScope(tokReqMsgCtx);
if (!isValidScope) {
if(log.isDebugEnabled()) {
log.debug("Invalid scope provided by client Id: " + tokenReqDTO.getClientId());
}
tokenRespDTO = handleError(OAuthError.TokenResponse.INVALID_SCOPE, "Invalid Scope!", tokenReqDTO);
setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
return tokenRespDTO;
}
try {
// set the token request context to be used by downstream handlers. This is introduced as a fix for
// IDENTITY-4111.
OAuth2Util.setTokenRequestContext(tokReqMsgCtx);
tokenRespDTO = authzGrantHandler.issue(tokReqMsgCtx);
} finally {
// clears the token request context.
OAuth2Util.clearTokenRequestContext();
}
tokenRespDTO.setCallbackURI(oAuthAppDO.getCallbackUrl());
String[] scopes = tokReqMsgCtx.getScope();
if (scopes != null && scopes.length > 0) {
StringBuilder scopeString = new StringBuilder("");
for (String scope : scopes) {
scopeString.append(scope);
scopeString.append(" ");
}
tokenRespDTO.setAuthorizedScopes(scopeString.toString().trim());
}
setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
//Do not change this log format as these logs use by external applications
if (log.isDebugEnabled()) {
log.debug("Access token issued to client Id: " + tokenReqDTO.getClientId() + " username: " +
tokReqMsgCtx.getAuthorizedUser() + " and scopes: " + tokenRespDTO.getAuthorizedScopes());
}
if (tokReqMsgCtx.getScope() != null && OAuth2Util.isOIDCAuthzRequest(tokReqMsgCtx.getScope())) {
IDTokenBuilder builder = OAuthServerConfiguration.getInstance().getOpenIDConnectIDTokenBuilder();
tokenRespDTO.setIDToken(builder.buildIDToken(tokReqMsgCtx, tokenRespDTO));
}
if (tokenReqDTO.getGrantType().equals(GrantType.AUTHORIZATION_CODE.toString())) {
addUserAttributesToCache(tokenReqDTO, tokenRespDTO);
}
return tokenRespDTO;
}
/**
* Add user attributes to cache.
*
* @param tokenReqDTO
* @param tokenRespDTO
*/
private void addUserAttributesToCache(OAuth2AccessTokenReqDTO tokenReqDTO, OAuth2AccessTokenRespDTO tokenRespDTO) {
AuthorizationGrantCacheKey oldCacheKey = new AuthorizationGrantCacheKey(tokenReqDTO.getAuthorizationCode());
//checking getUserAttributesId value of cacheKey before retrieve entry from cache as it causes to NPE
if (oldCacheKey.getUserAttributesId() != null) {
AuthorizationGrantCacheEntry authorizationGrantCacheEntry = AuthorizationGrantCache.getInstance().getValueFromCacheByCode(oldCacheKey);
AuthorizationGrantCacheKey newCacheKey = new AuthorizationGrantCacheKey(tokenRespDTO.getAccessToken());
authorizationGrantCacheEntry.setTokenId(tokenRespDTO.getTokenId());
if (AuthorizationGrantCache.getInstance().getValueFromCacheByToken(newCacheKey) == null) {
if(log.isDebugEnabled()){
log.debug("No AuthorizationGrantCache entry found for the access token:"+ newCacheKey.getUserAttributesId()+
", hence adding to cache");
}
AuthorizationGrantCache.getInstance().addToCacheByToken(newCacheKey, authorizationGrantCacheEntry);
AuthorizationGrantCache.getInstance().clearCacheEntryByCode(oldCacheKey);
} else{
//if the user attributes are already saved for access token, no need to add again.
}
}
}
/**
* Get Oauth application information
*
* @param tokenReqDTO
* @return Oauth app information
* @throws IdentityOAuth2Exception
* @throws InvalidOAuthClientException
*/
private OAuthAppDO getAppInformation(OAuth2AccessTokenReqDTO tokenReqDTO) throws IdentityOAuth2Exception, InvalidOAuthClientException {
OAuthAppDO oAuthAppDO = appInfoCache.getValueFromCache(tokenReqDTO.getClientId());
if (oAuthAppDO != null) {
return oAuthAppDO;
} else {
oAuthAppDO = new OAuthAppDAO().getAppInformation(tokenReqDTO.getClientId());
appInfoCache.addToCache(tokenReqDTO.getClientId(), oAuthAppDO);
return oAuthAppDO;
}
}
/**
* Handle error scenarios in issueing the access token.
*
* @param errorCode
* @param errorMsg
* @param tokenReqDTO
* @return Access token response DTO
*/
private OAuth2AccessTokenRespDTO handleError(String errorCode,
String errorMsg,
OAuth2AccessTokenReqDTO tokenReqDTO) {
if (log.isDebugEnabled()) {
log.debug("OAuth-Error-Code=" + errorCode + " client-id=" + tokenReqDTO.getClientId()
+ " grant-type=" + tokenReqDTO.getGrantType()
+ " scope=" + OAuth2Util.buildScopeString(tokenReqDTO.getScope()));
}
OAuth2AccessTokenRespDTO tokenRespDTO;
tokenRespDTO = new OAuth2AccessTokenRespDTO();
tokenRespDTO.setError(true);
tokenRespDTO.setErrorCode(errorCode);
tokenRespDTO.setErrorMsg(errorMsg);
return tokenRespDTO;
}
/**
* Set headers in OAuth2AccessTokenRespDTO
* @param tokReqMsgCtx
* @param tokenRespDTO
*/
private void setResponseHeaders(OAuthTokenReqMessageContext tokReqMsgCtx,
OAuth2AccessTokenRespDTO tokenRespDTO) {
if (tokReqMsgCtx.getProperty("RESPONSE_HEADERS") != null) {
tokenRespDTO.setResponseHeaders((ResponseHeader[]) tokReqMsgCtx.getProperty("RESPONSE_HEADERS"));
}
}
}