/*
* 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.oauth;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.core.AbstractAdmin;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.common.model.User;
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.OAuthCache;
import org.wso2.carbon.identity.oauth.cache.OAuthCacheKey;
import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes;
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.oauth.dto.OAuthConsumerAppDTO;
import org.wso2.carbon.identity.oauth.dto.OAuthRevocationRequestDTO;
import org.wso2.carbon.identity.oauth.dto.OAuthRevocationResponseDTO;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.dao.TokenMgtDAO;
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class OAuthAdminService extends AbstractAdmin {
public static final String IMPLICIT = "implicit";
public static final String AUTHORIZATION_CODE = "authorization_code";
private static List<String> allowedGrants = null;
protected Log log = LogFactory.getLog(OAuthAdminService.class);
private AppInfoCache appInfoCache = AppInfoCache.getInstance();
/**
* Registers an consumer secret against the logged in user. A given user can only have a single
* consumer secret at a time. Calling this method again and again will update the existing
* consumer secret key.
*
* @return An array containing the consumer key and the consumer secret correspondingly.
* @throws Exception Error when persisting the data in the persistence store.
*/
public String[] registerOAuthConsumer() throws IdentityOAuthAdminException {
String loggedInUser = CarbonContext.getThreadLocalCarbonContext().getUsername();
if (log.isDebugEnabled()) {
log.debug("Adding a consumer secret for the logged in user " + loggedInUser);
}
String tenantUser = MultitenantUtils.getTenantAwareUsername(loggedInUser);
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
String userDomain = IdentityUtil.extractDomainFromName(loggedInUser);
OAuthAppDAO dao = new OAuthAppDAO();
return dao.addOAuthConsumer(UserCoreUtil.removeDomainFromName(tenantUser), tenantId, userDomain);
}
/**
* Get all registered OAuth applications for the logged in user.
*
* @return An array of <code>OAuthConsumerAppDTO</code> objecting containing the application
* information of the user
* @throws Exception Error when reading the data from the persistence store.
*/
public OAuthConsumerAppDTO[] getAllOAuthApplicationData() throws IdentityOAuthAdminException {
String userName = CarbonContext.getThreadLocalCarbonContext().getUsername();
OAuthConsumerAppDTO[] dtos = new OAuthConsumerAppDTO[0];
if (userName == null) {
if (log.isErrorEnabled()) {
log.debug("User not logged in");
}
throw new IdentityOAuthAdminException("User not logged in");
}
String tenantUser = MultitenantUtils.getTenantAwareUsername(userName);
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
OAuthAppDAO dao = new OAuthAppDAO();
OAuthAppDO[] apps = dao.getOAuthConsumerAppsOfUser(tenantUser, tenantId);
if (apps != null && apps.length > 0) {
dtos = new OAuthConsumerAppDTO[apps.length];
OAuthConsumerAppDTO dto = null;
OAuthAppDO app = null;
for (int i = 0; i < apps.length; i++) {
app = apps[i];
dto = new OAuthConsumerAppDTO();
dto.setApplicationName(app.getApplicationName());
dto.setCallbackUrl(app.getCallbackUrl());
dto.setOauthConsumerKey(app.getOauthConsumerKey());
dto.setOauthConsumerSecret(app.getOauthConsumerSecret());
dto.setOAuthVersion(app.getOauthVersion());
dto.setGrantTypes(app.getGrantTypes());
dto.setUsername(app.getUser().toString());
dtos[i] = dto;
}
}
return dtos;
}
/**
* Get OAuth application data by the consumer key.
*
* @param consumerKey Consumer Key
* @return <code>OAuthConsumerAppDTO</code> with application information
* @throws Exception Error when reading application information from persistence store.
*/
public OAuthConsumerAppDTO getOAuthApplicationData(String consumerKey) throws IdentityOAuthAdminException {
OAuthConsumerAppDTO dto = new OAuthConsumerAppDTO();
OAuthAppDAO dao = new OAuthAppDAO();
try {
OAuthAppDO app = dao.getAppInformation(consumerKey);
if (app != null) {
dto.setApplicationName(app.getApplicationName());
dto.setCallbackUrl(app.getCallbackUrl());
dto.setOauthConsumerKey(app.getOauthConsumerKey());
dto.setOauthConsumerSecret(app.getOauthConsumerSecret());
dto.setOAuthVersion(app.getOauthVersion());
dto.setGrantTypes(app.getGrantTypes());
}
return dto;
} catch (InvalidOAuthClientException | IdentityOAuth2Exception e) {
throw new IdentityOAuthAdminException("Error while retrieving the app information using consumer key", e);
}
}
/**
* Get OAuth application data by the application name.
*
* @param appName OAuth application name
* @return <code>OAuthConsumerAppDTO</code> with application information
* @throws Exception Error when reading application information from persistence store.
*/
public OAuthConsumerAppDTO getOAuthApplicationDataByAppName(String appName) throws IdentityOAuthAdminException {
OAuthConsumerAppDTO dto = new OAuthConsumerAppDTO();
OAuthAppDAO dao = new OAuthAppDAO();
try {
OAuthAppDO app = dao.getAppInformationByAppName(appName);
if (app != null) {
dto.setApplicationName(app.getApplicationName());
dto.setCallbackUrl(app.getCallbackUrl());
dto.setOauthConsumerKey(app.getOauthConsumerKey());
dto.setOauthConsumerSecret(app.getOauthConsumerSecret());
dto.setOAuthVersion(app.getOauthVersion());
dto.setGrantTypes(app.getGrantTypes());
}
return dto;
}catch (InvalidOAuthClientException | IdentityOAuth2Exception e){
throw new IdentityOAuthAdminException("Error while retrieving the app information by app name", e);
}
}
/**
* Registers an OAuth consumer application.
*
* @param application <code>OAuthConsumerAppDTO</code> with application information
* @throws Exception Error when persisting the application information to the persistence store
*/
public void registerOAuthApplicationData(OAuthConsumerAppDTO application) throws IdentityOAuthAdminException{
String userName = CarbonContext.getThreadLocalCarbonContext().getUsername();
if (userName != null) {
String tenantUser = MultitenantUtils.getTenantAwareUsername(userName);
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain();
OAuthAppDAO dao = new OAuthAppDAO();
OAuthAppDO app = new OAuthAppDO();
if (application != null) {
app.setApplicationName(application.getApplicationName());
if ((application.getGrantTypes().contains(AUTHORIZATION_CODE) || application.getGrantTypes()
.contains(IMPLICIT)) && StringUtils.isEmpty(application.getCallbackUrl())) {
throw new IdentityOAuthAdminException("Callback Url is required for Code or Implicit grant types");
}
app.setCallbackUrl(application.getCallbackUrl());
if (application.getOauthConsumerKey() == null) {
app.setOauthConsumerKey(OAuthUtil.getRandomNumber());
app.setOauthConsumerSecret(OAuthUtil.getRandomNumber());
} else {
app.setOauthConsumerKey(application.getOauthConsumerKey());
app.setOauthConsumerSecret(application.getOauthConsumerSecret());
}
String applicationUser = application.getUsername();
if (applicationUser != null && applicationUser.trim().length() > 0) {
try {
if (CarbonContext.getThreadLocalCarbonContext().getUserRealm().
getUserStoreManager().isExistingUser(application.getUsername())) {
tenantUser = applicationUser;
} else {
log.warn("OAuth application registrant user name " + applicationUser +
" does not exist in the user store. Using logged-in user name " + tenantUser +
" as registrant name");
}
} catch (UserStoreException e) {
throw new IdentityOAuthAdminException("Error while retrieving the user store manager", e);
}
}
AuthenticatedUser user = new AuthenticatedUser();
user.setUserName(UserCoreUtil.removeDomainFromName(tenantUser));
user.setTenantDomain(tenantDomain);
user.setUserStoreDomain(IdentityUtil.extractDomainFromName(userName));
app.setUser(user);
if (application.getOAuthVersion() != null) {
app.setOauthVersion(application.getOAuthVersion());
} else { // by default, assume OAuth 2.0, if it is not set.
app.setOauthVersion(OAuthConstants.OAuthVersions.VERSION_2);
}
if (OAuthConstants.OAuthVersions.VERSION_2.equals(application.getOAuthVersion())) {
List<String> allowedGrants = new ArrayList<>(Arrays.asList(getAllowedGrantTypes()));
String[] requestGrants = application.getGrantTypes().split("\\s");
for (String requestedGrant : requestGrants) {
if (StringUtils.isBlank(requestedGrant)){
continue;
}
if (!allowedGrants.contains(requestedGrant)) {
throw new IdentityOAuthAdminException(requestedGrant + " not allowed");
}
}
app.setGrantTypes(application.getGrantTypes());
}
dao.addOAuthApplication(app);
if (OAuthServerConfiguration.getInstance().isCacheEnabled()) {
appInfoCache.addToCache(app.getOauthConsumerKey(), app);
}
}
}
}
/**
* Update existing consumer application.
*
* @param consumerAppDTO <code>OAuthConsumerAppDTO</code> with updated application information
* @throws IdentityOAuthAdminException Error when updating the underlying identity persistence store.
*/
public void updateConsumerApplication(OAuthConsumerAppDTO consumerAppDTO) throws IdentityOAuthAdminException {
String userName = CarbonContext.getThreadLocalCarbonContext().getUsername();
String tenantAwareUsername = MultitenantUtils.getTenantAwareUsername(userName);
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain();
OAuthAppDAO dao = new OAuthAppDAO();
OAuthAppDO oauthappdo = new OAuthAppDO();
AuthenticatedUser user = new AuthenticatedUser();
user.setUserName(UserCoreUtil.removeDomainFromName(tenantAwareUsername));
user.setTenantDomain(tenantDomain);
user.setUserStoreDomain(IdentityUtil.extractDomainFromName(userName));
oauthappdo.setUser(user);
oauthappdo.setOauthConsumerKey(consumerAppDTO.getOauthConsumerKey());
oauthappdo.setOauthConsumerSecret(consumerAppDTO.getOauthConsumerSecret());
oauthappdo.setCallbackUrl(consumerAppDTO.getCallbackUrl());
oauthappdo.setApplicationName(consumerAppDTO.getApplicationName());
if (OAuthConstants.OAuthVersions.VERSION_2.equals(consumerAppDTO.getOAuthVersion())) {
List<String> allowedGrants = new ArrayList<>(Arrays.asList(getAllowedGrantTypes()));
String[] requestGrants = consumerAppDTO.getGrantTypes().split("\\s");
for (String requestedGrant : requestGrants) {
if (StringUtils.isBlank(requestedGrant)) {
continue;
}
if (!allowedGrants.contains(requestedGrant)) {
throw new IdentityOAuthAdminException(requestedGrant + " not allowed");
}
}
oauthappdo.setGrantTypes(consumerAppDTO.getGrantTypes());
}
dao.updateConsumerApplication(oauthappdo);
if (OAuthServerConfiguration.getInstance().isCacheEnabled()) {
appInfoCache.addToCache(oauthappdo.getOauthConsumerKey(), oauthappdo);
}
}
/**
* Removes an OAuth consumer application.
*
* @param consumerKey Consumer Key
* @throws Exception Error when removing the consumer information from the database.
*/
public void removeOAuthApplicationData(String consumerKey) throws IdentityOAuthAdminException {
OAuthAppDAO dao = new OAuthAppDAO();
dao.removeConsumerApplication(consumerKey);
// remove client credentials from cache
if (OAuthServerConfiguration.getInstance().isCacheEnabled()) {
OAuthCache.getInstance().clearCacheEntry(new OAuthCacheKey(consumerKey));
appInfoCache.clearCacheEntry(consumerKey);
if (log.isDebugEnabled()) {
log.debug("Client credentials are removed from the cache.");
}
}
}
/**
* Get apps that are authorized by the given user
*
* @return OAuth applications authorized by the user that have tokens in ACTIVE or EXPIRED state
*/
public OAuthConsumerAppDTO[] getAppsAuthorizedByUser() throws IdentityOAuthAdminException {
TokenMgtDAO tokenMgtDAO = new TokenMgtDAO();
OAuthAppDAO appDAO = new OAuthAppDAO();
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
String tenantAwareUserName = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername();
AuthenticatedUser authenticatedUser = new AuthenticatedUser();
authenticatedUser.setUserName(UserCoreUtil.removeDomainFromName(tenantAwareUserName));
authenticatedUser.setUserStoreDomain(UserCoreUtil.extractDomainFromName(tenantAwareUserName));
authenticatedUser.setTenantDomain(tenantDomain);
String username = UserCoreUtil.addTenantDomainToEntry(tenantAwareUserName, tenantDomain);
String userStoreDomain = null;
if (OAuth2Util.checkAccessTokenPartitioningEnabled() && OAuth2Util.checkUserNameAssertionEnabled()) {
try {
userStoreDomain = OAuth2Util.getUserStoreDomainFromUserId(username);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while getting user store domain for User ID : " + username;
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
}
Set<String> clientIds = null;
try {
clientIds = tokenMgtDAO.getAllTimeAuthorizedClientIds(authenticatedUser);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while retrieving apps authorized by User ID : " + username;
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
Set<OAuthConsumerAppDTO> appDTOs = new HashSet<OAuthConsumerAppDTO>();
for (String clientId : clientIds) {
Set<AccessTokenDO> accessTokenDOs = null;
try {
accessTokenDOs = tokenMgtDAO.retrieveAccessTokens(clientId, authenticatedUser, userStoreDomain, true);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while retrieving access tokens issued for " +
"Client ID : " + clientId + ", User ID : " + username;
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
if (!accessTokenDOs.isEmpty()) {
Set<String> distinctClientUserScopeCombo = new HashSet<String>();
for (AccessTokenDO accessTokenDO : accessTokenDOs) {
AccessTokenDO scopedToken = null;
String scopeString = OAuth2Util.buildScopeString(accessTokenDO.getScope());
try {
scopedToken = tokenMgtDAO.retrieveLatestAccessToken(
clientId, authenticatedUser, userStoreDomain, scopeString, true);
if(scopedToken != null && !distinctClientUserScopeCombo.contains(clientId+":"+username)){
OAuthConsumerAppDTO appDTO = new OAuthConsumerAppDTO();
OAuthAppDO appDO;
try {
appDO = appDAO.getAppInformation(scopedToken.getConsumerKey());
appDTO.setOauthConsumerKey(scopedToken.getConsumerKey());
appDTO.setApplicationName(appDO.getApplicationName());
appDTO.setUsername(appDO.getUser().toString());
appDTO.setGrantTypes(appDO.getGrantTypes());
appDTOs.add(appDTO);
} catch (InvalidOAuthClientException e) {
String errorMsg = "Invalid Client ID : " + scopedToken.getConsumerKey();
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while retrieving app information " +
"for Client ID : " + scopedToken.getConsumerKey();
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg);
}
distinctClientUserScopeCombo.add(clientId+":"+username);
}
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while retrieving latest access token issued for Client ID :" +
" " + clientId + ", User ID : " + username + " and Scope : " + scopeString;
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
}
}
}
return appDTOs.toArray(new OAuthConsumerAppDTO[appDTOs.size()]);
}
/**
* Revoke authorization for OAuth apps by resource owners
*
* @param revokeRequestDTO DTO representing authorized user and apps[]
* @return revokeRespDTO DTO representing success or failure message
*/
public OAuthRevocationResponseDTO revokeAuthzForAppsByResoureOwner(
OAuthRevocationRequestDTO revokeRequestDTO) throws IdentityOAuthAdminException {
TokenMgtDAO tokenMgtDAO = new TokenMgtDAO();
if (revokeRequestDTO.getApps() != null && revokeRequestDTO.getApps().length > 0) {
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
String tenantAwareUserName = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername();
AuthenticatedUser user = new AuthenticatedUser();
user.setUserName(UserCoreUtil.removeDomainFromName(tenantAwareUserName));
user.setUserStoreDomain(UserCoreUtil.extractDomainFromName(tenantAwareUserName));
user.setTenantDomain(tenantDomain);
String userName = UserCoreUtil.addTenantDomainToEntry(tenantAwareUserName, tenantDomain);
String userStoreDomain = null;
if (OAuth2Util.checkAccessTokenPartitioningEnabled() &&
OAuth2Util.checkUserNameAssertionEnabled()) {
try {
userStoreDomain = OAuth2Util.getUserStoreDomainFromUserId(userName);
} catch (IdentityOAuth2Exception e) {
throw new IdentityOAuthAdminException(
"Error occurred while getting user store domain from User ID : " + userName, e);
}
}
OAuthConsumerAppDTO[] appDTOs = getAppsAuthorizedByUser();
for (String appName : revokeRequestDTO.getApps()) {
for (OAuthConsumerAppDTO appDTO : appDTOs) {
if (appDTO.getApplicationName().equals(appName)) {
Set<AccessTokenDO> accessTokenDOs = null;
try {
// retrieve all ACTIVE or EXPIRED access tokens for particular client authorized by this user
accessTokenDOs = tokenMgtDAO.retrieveAccessTokens(
appDTO.getOauthConsumerKey(), user, userStoreDomain, true);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while retrieving access tokens issued for " +
"Client ID : " + appDTO.getOauthConsumerKey() + ", User ID : " + userName;
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
User authzUser;
for (AccessTokenDO accessTokenDO : accessTokenDOs) {
//Clear cache with AccessTokenDO
authzUser = accessTokenDO.getAuthzUser();
OAuthUtil.clearOAuthCache(accessTokenDO.getConsumerKey(), authzUser,
OAuth2Util.buildScopeString(accessTokenDO.getScope()));
OAuthUtil.clearOAuthCache(accessTokenDO.getConsumerKey(), authzUser);
OAuthUtil.clearOAuthCache(accessTokenDO.getAccessToken());
AccessTokenDO scopedToken = null;
try {
// retrieve latest access token for particular client, user and scope combination if its ACTIVE or EXPIRED
scopedToken = tokenMgtDAO.retrieveLatestAccessToken(
appDTO.getOauthConsumerKey(), user, userStoreDomain,
OAuth2Util.buildScopeString(accessTokenDO.getScope()), true);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while retrieving latest " +
"access token issued for Client ID : " +
appDTO.getOauthConsumerKey() + ", User ID : " + userName +
" and Scope : " + OAuth2Util.buildScopeString(accessTokenDO.getScope());
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
if (scopedToken != null) {
//Revoking token from database
try {
tokenMgtDAO.revokeTokens(new String[]{scopedToken.getAccessToken()});
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while revoking " + "Access Token : " +
scopedToken.getAccessToken();
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
}
}
try {
tokenMgtDAO.revokeOAuthConsentByApplicationAndUser(userName, appName);
} catch (IdentityOAuth2Exception e) {
String errorMsg = "Error occurred while removing OAuth Consent of Application " + appName +
" of user " + userName;
log.error(errorMsg, e);
throw new IdentityOAuthAdminException(errorMsg, e);
}
}
}
}
} else {
OAuthRevocationResponseDTO revokeRespDTO = new OAuthRevocationResponseDTO();
revokeRespDTO.setError(true);
revokeRespDTO.setErrorCode(OAuth2ErrorCodes.INVALID_REQUEST);
revokeRespDTO.setErrorMsg("Invalid revocation request");
return revokeRespDTO;
}
return new OAuthRevocationResponseDTO();
}
public String[] getAllowedGrantTypes() {
if (allowedGrants == null) {
synchronized (OAuthAdminService.class) {
if (allowedGrants == null) {
Set<String> allowedGrantSet =
OAuthServerConfiguration.getInstance().getSupportedGrantTypes().keySet();
Set<String> modifiableGrantSet = new HashSet(allowedGrantSet);
if (OAuthServerConfiguration.getInstance().getSupportedResponseTypes().containsKey("token")) {
modifiableGrantSet.add(IMPLICIT);
}
allowedGrants = new ArrayList<>(modifiableGrantSet);
}
}
}
return allowedGrants.toArray(new String[allowedGrants.size()]);
}
}