/*
* 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.validators;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.common.model.User;
import org.wso2.carbon.identity.oauth.cache.CacheEntry;
import org.wso2.carbon.identity.oauth.cache.CacheKey;
import org.wso2.carbon.identity.oauth.cache.OAuthCache;
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.authcontext.AuthorizationContextTokenGenerator;
import org.wso2.carbon.identity.oauth2.dao.TokenMgtDAO;
import org.wso2.carbon.identity.oauth2.dto.OAuth2ClientApplicationDTO;
import org.wso2.carbon.identity.oauth2.dto.OAuth2IntrospectionResponseDTO;
import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationRequestDTO;
import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationResponseDTO;
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.user.core.util.UserCoreUtil;
/**
* Handles the token validation by invoking the proper validation handler by looking at the token
* type.
*/
public class TokenValidationHandler {
private static TokenValidationHandler instance = null;
AuthorizationContextTokenGenerator tokenGenerator = null;
private Log log = LogFactory.getLog(TokenValidationHandler.class);
private Map<String, OAuth2TokenValidator> tokenValidators = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private TokenMgtDAO tokenMgtDAO = new TokenMgtDAO();
private TokenValidationHandler() {
tokenValidators.put(DefaultOAuth2TokenValidator.TOKEN_TYPE, new DefaultOAuth2TokenValidator());
for (Map.Entry<String, String> entry : OAuthServerConfiguration.getInstance().getTokenValidatorClassNames().entrySet()) {
String className = null;
try {
String type = entry.getKey();
className = entry.getValue();
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(entry.getValue());
OAuth2TokenValidator tokenValidator = (OAuth2TokenValidator) clazz.newInstance();
tokenValidators.put(type, tokenValidator);
} catch (ClassNotFoundException e) {
log.error("Class not in build path " + className, e);
} catch (InstantiationException e) {
log.error("Class initialization error " + className, e);
} catch (IllegalAccessException e) {
log.error("Class access error " + className, e);
}
}
// setting up the JWT if required
if (OAuthServerConfiguration.getInstance().isAuthContextTokGenEnabled()) {
try {
Class clazz = this.getClass().getClassLoader().loadClass(OAuthServerConfiguration.getInstance().getTokenGeneratorImplClass());
tokenGenerator = (AuthorizationContextTokenGenerator) clazz.newInstance();
tokenGenerator.init();
if (log.isDebugEnabled()) {
log.debug("An instance of " + OAuthServerConfiguration.getInstance().getTokenGeneratorImplClass() +
" is created for OAuthServerConfiguration.");
}
} catch (ClassNotFoundException e) {
String errorMsg = "Class not found: " +
OAuthServerConfiguration.getInstance().getTokenGeneratorImplClass();
log.error(errorMsg, e);
} catch (InstantiationException e) {
String errorMsg = "Error while instantiating: " +
OAuthServerConfiguration.getInstance().getTokenGeneratorImplClass();
log.error(errorMsg, e);
} catch (IllegalAccessException e) {
String errorMsg = "Illegal access to: " +
OAuthServerConfiguration.getInstance().getTokenGeneratorImplClass();
log.error(errorMsg, e);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error while initializing: " +
OAuthServerConfiguration.getInstance().getTokenGeneratorImplClass();
log.error(errorMsg, e);
}
}
}
public static TokenValidationHandler getInstance() {
if (instance == null) {
synchronized (TokenValidationHandler.class) {
if (instance == null) {
instance = new TokenValidationHandler();
}
}
}
return instance;
}
public void addTokenValidator(String type, OAuth2TokenValidator handler) {
tokenValidators.put(type, handler);
}
/**
* @param requestDTO
* @return
* @throws IdentityOAuth2Exception
*/
public OAuth2TokenValidationResponseDTO validate(OAuth2TokenValidationRequestDTO requestDTO)
throws IdentityOAuth2Exception {
OAuth2ClientApplicationDTO appToken = findOAuthConsumerIfTokenIsValid(requestDTO);
return appToken.getAccessTokenValidationResponse();
}
/**
* this is method is deprecated now. any new implementations use buildIntrospectionResponse.
* @param requestDTO
* @return
* @throws IdentityOAuth2Exception
*/
@Deprecated
public OAuth2ClientApplicationDTO findOAuthConsumerIfTokenIsValid(OAuth2TokenValidationRequestDTO requestDTO)
throws IdentityOAuth2Exception {
OAuth2ClientApplicationDTO clientApp = new OAuth2ClientApplicationDTO();
OAuth2TokenValidationResponseDTO responseDTO = new OAuth2TokenValidationResponseDTO();
OAuth2TokenValidationMessageContext messageContext =
new OAuth2TokenValidationMessageContext(requestDTO, responseDTO);
OAuth2TokenValidationRequestDTO.OAuth2AccessToken accessToken = requestDTO.getAccessToken();
OAuth2TokenValidator tokenValidator = null;
AccessTokenDO accessTokenDO = null;
try {
tokenValidator = findAccessTokenValidator(accessToken);
} catch (IllegalArgumentException e) {
// access token not provided.
return buildClientAppErrorResponse(e.getMessage());
}
try {
accessTokenDO = findAccessToken(requestDTO.getAccessToken().getIdentifier());
} catch (IllegalArgumentException e) {
// Access token not found in the system.
return buildClientAppErrorResponse(e.getMessage());
}
if ( hasAcessTokenExpired(accessTokenDO)) {
return buildClientAppErrorResponse("Access token expired");
}
// Set the token expiration time
responseDTO.setExpiryTime(getAccessTokenExpirationTime(accessTokenDO));
// Adding the AccessTokenDO as a context property for further use
messageContext.addProperty("AccessTokenDO", accessTokenDO);
if (!tokenValidator.validateAccessDelegation(messageContext)) {
return buildClientAppErrorResponse("Invalid access delegation");
}
if (!tokenValidator.validateScope(messageContext)) {
return buildClientAppErrorResponse("Scope validation failed");
}
if (!tokenValidator.validateAccessToken(messageContext)) {
return buildClientAppErrorResponse("OAuth2 access token validation failed");
}
responseDTO.setAuthorizedUser(getAuthzUser(accessTokenDO));
responseDTO.setScope(accessTokenDO.getScope());
responseDTO.setValid(true);
if (tokenGenerator != null) {
tokenGenerator.generateToken(messageContext);
if (log.isDebugEnabled()) {
log.debug(tokenGenerator.getClass().getName() + "generated token set to response : " +
responseDTO.getAuthorizationContextToken().getTokenString());
}
}
clientApp.setAccessTokenValidationResponse(responseDTO);
clientApp.setConsumerKey(accessTokenDO.getConsumerKey());
return clientApp;
}
/**
* returns back the introspection response, which is compatible with RFC 7662.
*
* @param validationRequest
* @return
* @throws IdentityOAuth2Exception
*/
public OAuth2IntrospectionResponseDTO buildIntrospectionResponse(OAuth2TokenValidationRequestDTO validationRequest)
throws IdentityOAuth2Exception {
OAuth2TokenValidationResponseDTO responseDTO = new OAuth2TokenValidationResponseDTO();
OAuth2IntrospectionResponseDTO introResp = new OAuth2IntrospectionResponseDTO();
OAuth2TokenValidationMessageContext messageContext =
new OAuth2TokenValidationMessageContext(validationRequest, responseDTO);
OAuth2TokenValidationRequestDTO.OAuth2AccessToken accessToken = validationRequest.getAccessToken();
OAuth2TokenValidator tokenValidator = null;
AccessTokenDO accessTokenDO = null;
try {
tokenValidator = findAccessTokenValidator(accessToken);
} catch (IllegalArgumentException e) {
// access token not provided.
return buildIntrospectionErrorResponse(e.getMessage());
}
if (!tokenValidator.validateAccessToken(messageContext)) {
return buildIntrospectionErrorResponse("Access token validation failed");
}
if (messageContext.getProperty(OAuth2Util.REMOTE_ACCESS_TOKEN) != null
&& "true".equalsIgnoreCase((String) messageContext.getProperty(OAuth2Util.REMOTE_ACCESS_TOKEN))) {
// this can be a self-issued JWT or any access token issued by a trusted OAuth authorization server.
// should be in seconds
if (messageContext.getProperty(OAuth2Util.EXP) != null) {
introResp.setExp(Long.parseLong((String) messageContext.getProperty(OAuth2Util.EXP)));
}
// should be in seconds
if (messageContext.getProperty(OAuth2Util.IAT) != null) {
introResp.setIat(Long.parseLong((String) messageContext.getProperty(OAuth2Util.IAT)));
}
// token scopes - space delimited
if (messageContext.getProperty(OAuth2Util.SCOPE) != null) {
introResp.setScope((String) messageContext.getProperty(OAuth2Util.SCOPE));
}
// set user-name
if (messageContext.getProperty(OAuth2Util.USERNAME) != null) {
introResp.setUsername((String) messageContext.getProperty(OAuth2Util.USERNAME));
}
// set client-id
if (messageContext.getProperty(OAuth2Util.CLIENT_ID) != null) {
introResp.setClientId((String) messageContext.getProperty(OAuth2Util.CLIENT_ID));
}
} else {
try {
accessTokenDO = findAccessToken(validationRequest.getAccessToken().getIdentifier());
} catch (IllegalArgumentException e) {
// access token not found in the system.
return buildIntrospectionErrorResponse(e.getMessage());
}
if (hasAcessTokenExpired(accessTokenDO)) {
// token is not active. we do not need to worry about other details.
introResp.setActive(false);
return introResp;
}
// should be in seconds
introResp
.setExp((accessTokenDO.getValidityPeriodInMillis() + accessTokenDO.getIssuedTime().getTime()) / 1000);
// should be in seconds
introResp.setIat(accessTokenDO.getIssuedTime().getTime() / 1000);
// token scopes
introResp.setScope(OAuth2Util.buildScopeString((accessTokenDO.getScope())));
// set user-name
introResp.setUsername(getAuthzUser(accessTokenDO));
// add client id
introResp.setClientId(accessTokenDO.getConsumerKey());
// adding the AccessTokenDO as a context property for further use
messageContext.addProperty("AccessTokenDO", accessTokenDO);
}
if (!tokenValidator.validateAccessDelegation(messageContext)) {
return buildIntrospectionErrorResponse("Invalid access delegation");
}
if (!tokenValidator.validateScope(messageContext)) {
return buildIntrospectionErrorResponse("Scope validation failed");
}
if (messageContext.getProperty(OAuth2Util.JWT_ACCESS_TOKEN) != null
&& "true".equalsIgnoreCase((String) messageContext.getProperty(OAuth2Util.JWT_ACCESS_TOKEN))) {
// attributes only related JWT access tokens.
if (messageContext.getProperty(OAuth2Util.SUB) != null) {
introResp.setSub((String) messageContext.getProperty(OAuth2Util.SUB));
}
if (messageContext.getProperty(OAuth2Util.ISS) != null) {
introResp.setIss((String) messageContext.getProperty(OAuth2Util.ISS));
}
if (messageContext.getProperty(OAuth2Util.AUD) != null) {
introResp.setAud((String) messageContext.getProperty(OAuth2Util.AUD));
}
if (messageContext.getProperty(OAuth2Util.JTI) != null) {
introResp.setJti((String) messageContext.getProperty(OAuth2Util.JTI));
}
// set the token not to be used before time in seconds
if (messageContext.getProperty(OAuth2Util.NBF) != null) {
introResp.setNbf(Long.parseLong((String) messageContext.getProperty(OAuth2Util.NBF)));
}
}
// all set. mark the token active.
introResp.setActive(true);
if (tokenGenerator != null) {
// add user attributes to the introspection response.
tokenGenerator.generateToken(messageContext);
if (log.isDebugEnabled()) {
log.debug(tokenGenerator.getClass().getName() + "generated token set to response.");
}
if (responseDTO.getAuthorizationContextToken() != null) {
introResp.setUserContext(responseDTO.getAuthorizationContextToken().getTokenString());
}
}
return introResp;
}
/**
*
* @param accessTokenDO
* @return
*/
private String getAuthzUser(AccessTokenDO accessTokenDO) {
User user = accessTokenDO.getAuthzUser();
String authzUser = UserCoreUtil.addDomainToName(user.getUserName(), user.getUserStoreDomain());
return UserCoreUtil.addTenantDomainToEntry(authzUser, user.getTenantDomain());
}
/**
*
* @param errorMessage
* @return
*/
private OAuth2ClientApplicationDTO buildClientAppErrorResponse(String errorMessage) {
OAuth2TokenValidationResponseDTO responseDTO = new OAuth2TokenValidationResponseDTO();
OAuth2ClientApplicationDTO clientApp = new OAuth2ClientApplicationDTO();
if (log.isDebugEnabled()) {
log.debug(errorMessage);
}
responseDTO.setValid(false);
responseDTO.setErrorMsg(errorMessage);
clientApp.setAccessTokenValidationResponse(responseDTO);
return clientApp;
}
/**
*
* @param errorMessage
* @return
*/
private OAuth2IntrospectionResponseDTO buildIntrospectionErrorResponse(String errorMessage) {
OAuth2IntrospectionResponseDTO response = new OAuth2IntrospectionResponseDTO();
if (log.isDebugEnabled()) {
log.debug(errorMessage);
}
response.setActive(false);
response.setError(errorMessage);
return response;
}
/**
*
* @param accessToken
* @return
* @throws IdentityOAuth2Exception
*/
private OAuth2TokenValidator findAccessTokenValidator(OAuth2TokenValidationRequestDTO.OAuth2AccessToken accessToken)
throws IdentityOAuth2Exception {
// incomplete token validation request
if (accessToken == null) {
throw new IllegalArgumentException("Access token is not present in the validation request");
}
String accessTokenIdentifier = accessToken.getIdentifier();
// incomplete token validation request
if (accessTokenIdentifier == null) {
throw new IllegalArgumentException("Access token identifier is not present in the validation request");
}
OAuth2TokenValidator tokenValidator = tokenValidators.get(accessToken.getTokenType());
// There is no token validator for the provided token type.
if (tokenValidator == null) {
throw new IdentityOAuth2Exception("Unsupported access token type: " + accessToken.getTokenType());
}
return tokenValidator;
}
/**
*
* @param accessTokenDO
* @return
*/
private long getAccessTokenExpirationTime(AccessTokenDO accessTokenDO) {
long expiryTime = OAuth2Util.getAccessTokenExpireMillis(accessTokenDO);
if (OAuthConstants.UserType.APPLICATION_USER.equals(accessTokenDO.getTokenType())
&& OAuthServerConfiguration.getInstance().getUserAccessTokenValidityPeriodInSeconds() < 0) {
return Long.MAX_VALUE;
} else if (OAuthConstants.UserType.APPLICATION.equals(accessTokenDO.getTokenType())
&& OAuthServerConfiguration.getInstance().getApplicationAccessTokenValidityPeriodInSeconds() < 0) {
return Long.MAX_VALUE;
} else if (expiryTime < 0) {
return Long.MAX_VALUE;
}
return expiryTime / 1000;
}
/**
*
* @param accessTokenDO
* @return
* @throws IdentityOAuth2Exception
*/
private boolean hasAcessTokenExpired(AccessTokenDO accessTokenDO) {
// check whether the grant is expired
if (accessTokenDO.getValidityPeriod() < 0) {
if (log.isDebugEnabled()) {
log.debug("Access Token has infinite lifetime");
}
} else {
if (OAuth2Util.getAccessTokenExpireMillis(accessTokenDO) == 0) {
if (log.isDebugEnabled()) {
log.debug("Access Token has expired");
}
return true;
}
}
return false;
}
/**
*
* @param tokenIdentifier
* @return
* @throws IdentityOAuth2Exception
*/
private AccessTokenDO findAccessToken(String tokenIdentifier) throws IdentityOAuth2Exception {
boolean cacheHit = false;
AccessTokenDO accessTokenDO = null;
// check the cache, if caching is enabled.
if (OAuthServerConfiguration.getInstance().isCacheEnabled()) {
OAuthCache oauthCache = OAuthCache.getInstance();
OAuthCacheKey cacheKey = new OAuthCacheKey(tokenIdentifier);
CacheEntry result = oauthCache.getValueFromCache(cacheKey);
// cache hit, do the type check.
if (result instanceof AccessTokenDO) {
accessTokenDO = (AccessTokenDO) result;
cacheHit = true;
}
}
// cache miss, load the access token info from the database.
if (accessTokenDO == null) {
accessTokenDO = tokenMgtDAO.retrieveAccessToken(tokenIdentifier, false);
}
if (accessTokenDO == null) {
throw new IllegalArgumentException("Invalid access token");
}
// add the token back to the cache in the case of a cache miss
if (OAuthServerConfiguration.getInstance().isCacheEnabled() && !cacheHit) {
OAuthCache oauthCache = OAuthCache.getInstance();
OAuthCacheKey cacheKey = new OAuthCacheKey(tokenIdentifier);
oauthCache.addToCache(cacheKey, accessTokenDO);
if (log.isDebugEnabled()) {
log.debug("Access Token Info object was added back to the cache.");
}
}
return accessTokenDO;
}
}