/*
* 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.openidconnect;
import com.nimbusds.jwt.JWTClaimsSet;
import net.minidev.json.JSONArray;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
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.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
import org.wso2.carbon.claim.mgt.ClaimManagementException;
import org.wso2.carbon.claim.mgt.ClaimManagerHandler;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.base.IdentityConstants;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.util.IdentityCoreConstants;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
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.oauth2.authz.OAuthAuthzReqMessageContext;
import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder;
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
import org.wso2.carbon.user.api.RealmConfiguration;
import org.wso2.carbon.user.core.UserRealm;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.UserStoreManager;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* Returns the claims of the SAML assertion
*/
public class SAMLAssertionClaimsCallback implements CustomClaimsCallbackHandler {
private final static Log log = LogFactory.getLog(SAMLAssertionClaimsCallback.class);
private final static String INBOUND_AUTH2_TYPE = "oauth2";
private final static String SP_DIALECT = "http://wso2.org/oidc/claim";
private String userAttributeSeparator = IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR_DEFAULT;
@Override
public void handleCustomClaims(JWTClaimsSet jwtClaimsSet, OAuthTokenReqMessageContext requestMsgCtx) {
// reading the token set in the same grant
Assertion assertion = (Assertion) requestMsgCtx.getProperty(OAuthConstants.OAUTH_SAML2_ASSERTION);
if (assertion != null) {
List<AttributeStatement> list = assertion.getAttributeStatements();
if (CollectionUtils.isNotEmpty(list)) {
Iterator<Attribute> attributeIterator = list.get(0).getAttributes()
.iterator();
while (attributeIterator.hasNext()) {
Attribute attribute = attributeIterator.next();
String value = attribute.getAttributeValues().get(0).getDOM().getTextContent();
jwtClaimsSet.setClaim(attribute.getName(), value);
if (log.isDebugEnabled()) {
log.debug("Attribute: " + attribute.getName() + ", Value: " + value);
}
}
} else {
log.debug("No AttributeStatement found! ");
}
} else {
if (log.isDebugEnabled()) {
log.debug("Adding claims for user " + requestMsgCtx.getAuthorizedUser() + " to id token.");
}
try {
JSONArray values;
Map<String, Object> claims = getResponse(requestMsgCtx);
Object claimSeparator = claims.get(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
if (claimSeparator != null) {
String claimSeparatorString = (String) claimSeparator;
if(StringUtils.isNotBlank(claimSeparatorString)) {
userAttributeSeparator = (String) claimSeparator;
}
claims.remove(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
}
for (Map.Entry<String, Object> entry : claims.entrySet()) {
String value = entry.getValue().toString();
values = new JSONArray();
if (userAttributeSeparator != null && value.contains(userAttributeSeparator)) {
StringTokenizer st = new StringTokenizer(value, userAttributeSeparator);
while (st.hasMoreElements()) {
String attributeValue = st.nextElement().toString();
if (StringUtils.isNotBlank(attributeValue)) {
values.add(attributeValue);
}
}
} else {
values.add(value);
}
jwtClaimsSet.setClaim(entry.getKey(), values.toJSONString());
}
} catch (OAuthSystemException e) {
log.error("Error occurred while adding claims of " + requestMsgCtx.getAuthorizedUser() +
" to id token.", e);
}
}
}
@Override
public void handleCustomClaims(JWTClaimsSet jwtClaimsSet, OAuthAuthzReqMessageContext requestMsgCtx) {
if (log.isDebugEnabled()) {
log.debug("Adding claims for user " + requestMsgCtx.getAuthorizationReqDTO().getUser() + " to id token.");
}
try {
JSONArray values;
Map<String, Object> claims = getResponse(requestMsgCtx);
Object claimSeparator = claims.get(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
if (claimSeparator != null) {
String claimSeparatorString = (String) claimSeparator;
if(StringUtils.isNotBlank(claimSeparatorString)) {
userAttributeSeparator = (String) claimSeparator;
}
claims.remove(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
}
for (Map.Entry<String, Object> entry : claims.entrySet()) {
String value = entry.getValue().toString();
values = new JSONArray();
if (userAttributeSeparator != null && value.contains(userAttributeSeparator)) {
StringTokenizer st = new StringTokenizer(value, userAttributeSeparator);
while (st.hasMoreElements()) {
String attributeValue = st.nextElement().toString();
if (StringUtils.isNotBlank(attributeValue)) {
values.add(attributeValue);
}
}
} else {
values.add(value);
}
jwtClaimsSet.setClaim(entry.getKey(), values.toJSONString());
}
} catch (OAuthSystemException e) {
log.error("Error occurred while adding claims of " + requestMsgCtx.getAuthorizationReqDTO().getUser() +
" to id token.", e);
}
}
/**
* Get response map
*
* @param requestMsgCtx Token request message context
* @return Mapped claimed
* @throws OAuthSystemException
*/
private Map<String, Object> getResponse(OAuthTokenReqMessageContext requestMsgCtx)
throws OAuthSystemException {
Map<ClaimMapping, String> userAttributes =
getUserAttributesFromCache(requestMsgCtx.getProperty(OAuthConstants.ACCESS_TOKEN).toString());
Map<String, Object> claims = Collections.emptyMap();
if (userAttributes.isEmpty() && requestMsgCtx.getProperty(OAuthConstants.AUTHZ_CODE) != null) {
userAttributes =
getUserAttributesFromCache(requestMsgCtx.getProperty(OAuthConstants.AUTHZ_CODE).toString());
}
// If subject claim uri is null, we get the actual user name of the logged in user.
if (MapUtils.isEmpty(userAttributes) && (getSubjectClaimUri(requestMsgCtx) == null)) {
if (log.isDebugEnabled()) {
log.debug("User attributes not found in cache. Trying to retrieve attribute for user " + requestMsgCtx
.getAuthorizedUser());
}
try {
claims = getClaimsFromUserStore(requestMsgCtx);
} catch (UserStoreException | IdentityApplicationManagementException | IdentityException e) {
log.error("Error occurred while getting claims for user " + requestMsgCtx.getAuthorizedUser(), e);
}
} else {
claims = getClaimsMap(userAttributes);
}
return claims;
}
private Map<String, Object> getResponse(OAuthAuthzReqMessageContext requestMsgCtx)
throws OAuthSystemException {
Map<ClaimMapping, String> userAttributes =
getUserAttributesFromCache(requestMsgCtx.getProperty(OAuthConstants.ACCESS_TOKEN).toString());
Map<String, Object> claims = Collections.emptyMap();
// If subject claim uri is null, we get the actual user name of the logged in user.
if (MapUtils.isEmpty(userAttributes) && (getSubjectClaimUri(requestMsgCtx) == null)) {
if (log.isDebugEnabled()) {
log.debug("User attributes not found in cache. Trying to retrieve attribute for user " + requestMsgCtx
.getAuthorizationReqDTO().getUser());
}
try {
claims = getClaimsFromUserStore(requestMsgCtx);
} catch (UserStoreException | IdentityApplicationManagementException | IdentityException e) {
log.error("Error occurred while getting claims for user " + requestMsgCtx.getAuthorizationReqDTO().getUser(),
e);
}
} else {
claims = getClaimsMap(userAttributes);
}
return claims;
}
/**
* Get claims map
*
* @param userAttributes User Attributes
* @return User attribute map
*/
private Map<String, Object> getClaimsMap(Map<ClaimMapping, String> userAttributes) {
Map<String, Object> claims = new HashMap();
if (MapUtils.isNotEmpty(userAttributes)) {
for (Map.Entry<ClaimMapping, String> entry : userAttributes.entrySet()) {
claims.put(entry.getKey().getRemoteClaim().getClaimUri(), entry.getValue());
}
}
return claims;
}
/**
* Get claims from user store
*
* @param requestMsgCtx Token request message context
* @return Users claim map
* @throws Exception
*/
private static Map<String, Object> getClaimsFromUserStore(OAuthTokenReqMessageContext requestMsgCtx)
throws UserStoreException, IdentityApplicationManagementException, IdentityException {
String username = requestMsgCtx.getAuthorizedUser().toString();
String tenantDomain = requestMsgCtx.getAuthorizedUser().getTenantDomain();
UserRealm realm;
List<String> claimURIList = new ArrayList<String>();
Map<String, Object> mappedAppClaims = new HashMap<String, Object>();
ApplicationManagementService applicationMgtService = OAuth2ServiceComponentHolder.getApplicationMgtService();
String spName = applicationMgtService
.getServiceProviderNameByClientId(requestMsgCtx.getOauth2AccessTokenReqDTO().getClientId(),
INBOUND_AUTH2_TYPE, tenantDomain);
ServiceProvider serviceProvider = applicationMgtService.getApplicationExcludingFileBasedSPs(spName,
tenantDomain);
if (serviceProvider == null) {
return mappedAppClaims;
}
realm = IdentityTenantUtil.getRealm(tenantDomain, username);
if (realm == null) {
log.warn("No valid tenant domain provider. Empty claim returned back for tenant " + tenantDomain
+ " and user " + username);
return new HashMap<>();
}
Map<String, String> spToLocalClaimMappings;
UserStoreManager userStoreManager = realm.getUserStoreManager();
ClaimMapping[] requestedLocalClaimMap = serviceProvider.getClaimConfig().getClaimMappings();
if (requestedLocalClaimMap != null && requestedLocalClaimMap.length > 0) {
for (ClaimMapping mapping : requestedLocalClaimMap) {
if (mapping.isRequested()) {
claimURIList.add(mapping.getLocalClaim().getClaimUri());
}
}
if (log.isDebugEnabled()) {
log.debug("Requested number of local claims: " + claimURIList.size());
}
spToLocalClaimMappings = ClaimManagerHandler.getInstance().getMappingsMapFromOtherDialectToCarbon(
SP_DIALECT, null, tenantDomain, false);
Map<String, String> userClaims = null;
try {
userClaims = userStoreManager.getUserClaimValues(
MultitenantUtils.getTenantAwareUsername(username),
claimURIList.toArray(new String[claimURIList.size()]), null);
} catch (UserStoreException e) {
if (e.getMessage().contains("UserNotFound")) {
if (log.isDebugEnabled()) {
log.debug("User " + username + " not found in user store");
}
} else {
throw e;
}
}
if (log.isDebugEnabled()) {
log.debug("Number of user claims retrieved from user store: " + userClaims.size());
}
if (MapUtils.isEmpty(userClaims)) {
return new HashMap<>();
}
for (Iterator<Map.Entry<String, String>> iterator = spToLocalClaimMappings.entrySet().iterator(); iterator
.hasNext(); ) {
Map.Entry<String, String> entry = iterator.next();
String value = userClaims.get(entry.getValue());
if (value != null) {
mappedAppClaims.put(entry.getKey(), value);
if (log.isDebugEnabled() &&
IdentityUtil.isTokenLoggable(IdentityConstants.IdentityTokens.USER_CLAIMS)) {
log.debug("Mapped claim: key - " + entry.getKey() + " value -" + value);
}
}
}
String domain = IdentityUtil.extractDomainFromName(username);
RealmConfiguration realmConfiguration = userStoreManager.getSecondaryUserStoreManager(domain)
.getRealmConfiguration();
String claimSeparator = realmConfiguration.getUserStoreProperty(
IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
if (StringUtils.isNotBlank(claimSeparator)) {
mappedAppClaims.put(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR, claimSeparator);
}
}
return mappedAppClaims;
}
private static Map<String, Object> getClaimsFromUserStore(OAuthAuthzReqMessageContext requestMsgCtx)
throws IdentityApplicationManagementException, IdentityException, UserStoreException,
ClaimManagementException {
AuthenticatedUser user = requestMsgCtx.getAuthorizationReqDTO().getUser();
String tenantDomain = requestMsgCtx.getAuthorizationReqDTO().getUser().getTenantDomain();
UserRealm realm;
List<String> claimURIList = new ArrayList<String>();
Map<String, Object> mappedAppClaims = new HashMap<String, Object>();
ApplicationManagementService applicationMgtService = OAuth2ServiceComponentHolder.getApplicationMgtService();
String spName = applicationMgtService
.getServiceProviderNameByClientId(requestMsgCtx.getAuthorizationReqDTO().getConsumerKey(),
INBOUND_AUTH2_TYPE, tenantDomain);
ServiceProvider serviceProvider = applicationMgtService.getApplicationExcludingFileBasedSPs(spName,
tenantDomain);
if (serviceProvider == null) {
return mappedAppClaims;
}
realm = IdentityTenantUtil.getRealm(tenantDomain, user.toString());
if (realm == null) {
log.warn("No valid tenant domain provider. Empty claim returned back for tenant " + tenantDomain
+ " and user " + user);
return new HashMap<>();
}
Map<String, String> spToLocalClaimMappings;
UserStoreManager userStoreManager = realm.getUserStoreManager();
ClaimMapping[] requestedLocalClaimMap = serviceProvider.getClaimConfig().getClaimMappings();
if (requestedLocalClaimMap != null && requestedLocalClaimMap.length > 0) {
for (ClaimMapping mapping : requestedLocalClaimMap) {
if (mapping.isRequested()) {
claimURIList.add(mapping.getLocalClaim().getClaimUri());
}
}
if (log.isDebugEnabled()) {
log.debug("Requested number of local claims: " + claimURIList.size());
}
spToLocalClaimMappings = ClaimManagerHandler.getInstance().getMappingsMapFromOtherDialectToCarbon(
SP_DIALECT, null, tenantDomain, false);
Map<String, String> userClaims = null;
try {
userClaims = userStoreManager.getUserClaimValues(UserCoreUtil.addDomainToName(user.getUserName(),
user.getUserStoreDomain()), claimURIList.toArray(new String[claimURIList.size()]),null);
} catch (UserStoreException e) {
if (e.getMessage().contains("UserNotFound")) {
if (log.isDebugEnabled()) {
log.debug("User " + user + " not found in user store");
}
} else {
throw e;
}
}
if (log.isDebugEnabled()) {
log.debug("Number of user claims retrieved from user store: " + userClaims.size());
}
if (MapUtils.isEmpty(userClaims)) {
return new HashMap<>();
}
for (Iterator<Map.Entry<String, String>> iterator = spToLocalClaimMappings.entrySet().iterator(); iterator
.hasNext(); ) {
Map.Entry<String, String> entry = iterator.next();
String value = userClaims.get(entry.getValue());
if (value != null) {
mappedAppClaims.put(entry.getKey(), value);
if (log.isDebugEnabled() &&
IdentityUtil.isTokenLoggable(IdentityConstants.IdentityTokens.USER_CLAIMS)) {
log.debug("Mapped claim: key - " + entry.getKey() + " value -" + value);
}
}
}
RealmConfiguration realmConfiguration = userStoreManager.getSecondaryUserStoreManager(user.getUserStoreDomain())
.getRealmConfiguration();
String claimSeparator = realmConfiguration.getUserStoreProperty(
IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
if (StringUtils.isNotBlank(claimSeparator)) {
mappedAppClaims.put(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR, claimSeparator);
}
}
return mappedAppClaims;
}
/**
* Get user attribute from cache
*
* @param accessToken Access token
* @return User attributes
*/
private Map<ClaimMapping, String> getUserAttributesFromCache(String accessToken) {
AuthorizationGrantCacheKey cacheKey = new AuthorizationGrantCacheKey(accessToken);
AuthorizationGrantCacheEntry cacheEntry = (AuthorizationGrantCacheEntry) AuthorizationGrantCache.
getInstance().getValueFromCacheByToken(cacheKey);
if (cacheEntry == null) {
return new HashMap<ClaimMapping, String>();
}
return cacheEntry.getUserAttributes();
}
private String getSubjectClaimUri(OAuthTokenReqMessageContext request) {
ApplicationManagementService applicationMgtService = OAuth2ServiceComponentHolder
.getApplicationMgtService();
ServiceProvider serviceProvider = null;
try {
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
String spName = applicationMgtService.getServiceProviderNameByClientId(request.getOauth2AccessTokenReqDTO()
.getClientId(),
INBOUND_AUTH2_TYPE, tenantDomain);
serviceProvider = applicationMgtService.getApplicationExcludingFileBasedSPs(spName, tenantDomain);
if (serviceProvider != null) {
return serviceProvider.getLocalAndOutBoundAuthenticationConfig().getSubjectClaimUri();
}
} catch (IdentityApplicationManagementException ex) {
log.error("Error while getting service provider information.", ex);
}
return null;
}
private String getSubjectClaimUri(OAuthAuthzReqMessageContext request) {
ApplicationManagementService applicationMgtService = OAuth2ServiceComponentHolder
.getApplicationMgtService();
ServiceProvider serviceProvider = null;
try {
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
String spName = applicationMgtService.getServiceProviderNameByClientId(request.getAuthorizationReqDTO()
.getConsumerKey(),
INBOUND_AUTH2_TYPE, tenantDomain);
serviceProvider = applicationMgtService.getApplicationExcludingFileBasedSPs(spName, tenantDomain);
if (serviceProvider != null) {
return serviceProvider.getLocalAndOutBoundAuthenticationConfig().getSubjectClaimUri();
}
} catch (IdentityApplicationManagementException ex) {
log.error("Error while getting service provider information.", ex);
}
return null;
}
}