/* * Copyright (c) 2010, 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.scim.provider.impl; import org.apache.commons.collections.MapUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.CarbonConstants; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.scim.common.utils.SCIMCommonConstants; import org.wso2.carbon.identity.scim.common.utils.SCIMCommonUtils; import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.claim.ClaimManager; import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.user.core.util.UserCoreUtil; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import org.wso2.charon.core.encoder.Decoder; import org.wso2.charon.core.encoder.Encoder; import org.wso2.charon.core.encoder.json.JSONDecoder; import org.wso2.charon.core.encoder.json.JSONEncoder; import org.wso2.charon.core.exceptions.CharonException; import org.wso2.charon.core.exceptions.FormatNotSupportedException; import org.wso2.charon.core.exceptions.UnauthorizedException; import org.wso2.charon.core.extensions.AuthenticationHandler; import org.wso2.charon.core.extensions.AuthenticationInfo; import org.wso2.charon.core.extensions.CharonManager; import org.wso2.charon.core.extensions.TenantDTO; import org.wso2.charon.core.extensions.TenantManager; import org.wso2.charon.core.extensions.UserManager; import org.wso2.charon.core.protocol.ResponseCodeConstants; import org.wso2.charon.core.protocol.endpoints.AbstractResourceEndpoint; import org.wso2.charon.core.schema.SCIMConstants; import java.util.HashMap; import java.util.Map; public class IdentitySCIMManager implements CharonManager { private static final String INSTANCE = "instance"; //private TenantManager tenantManager; private static Log log = LogFactory.getLog(IdentitySCIMManager.class); private static volatile IdentitySCIMManager identitySCIMManager; private static Map<String, Encoder> encoderMap = new HashMap<String, Encoder>(); private static Map<String, Decoder> decoderMap = new HashMap<String, Decoder>(); private static Map<String, Map> authenticators = new HashMap<String, Map>(); private static Map<String, String> endpointURLs = new HashMap<String, String>(); private IdentitySCIMManager() throws CharonException { init(); } /** * Should return the static instance of CharonManager implementation. * Read the config and initialize extensions as specified in the config. * * @return */ public static IdentitySCIMManager getInstance() throws CharonException { if (identitySCIMManager == null) { synchronized (IdentitySCIMManager.class) { if (identitySCIMManager == null) { identitySCIMManager = new IdentitySCIMManager(); return identitySCIMManager; } else { return identitySCIMManager; } } } else { return identitySCIMManager; } } /** * Perform initialization at the deployment of the webapp. */ private void init() throws CharonException { //TODO:read config and init stuff, if nothing in config, make sure to initialize default stuff. //if no encoder/decoders provided by the configuration, register defaults. encoderMap.put(SCIMConstants.JSON, new JSONEncoder()); decoderMap.put(SCIMConstants.JSON, new JSONDecoder()); //register encoder,decoders in AbstractResourceEndpoint, since they are called with in the API registerCoders(); //Define endpoint urls to be used in Location Header endpointURLs.put(SCIMConstants.USER_ENDPOINT, SCIMCommonUtils.getSCIMUserURL()); endpointURLs.put(SCIMConstants.GROUP_ENDPOINT, SCIMCommonUtils.getSCIMGroupURL()); //register endpoint URLs in AbstractResourceEndpoint since they are called with in the API registerEndpointURLs(); } @Override public Encoder getEncoder(String format) throws FormatNotSupportedException { if (!encoderMap.containsKey(format)) { //Error is logged by the caller. throw new FormatNotSupportedException(ResponseCodeConstants.CODE_FORMAT_NOT_SUPPORTED, ResponseCodeConstants.DESC_FORMAT_NOT_SUPPORTED); } return encoderMap.get(format); } @Override public Decoder getDecoder(String format) throws FormatNotSupportedException { if (!decoderMap.containsKey(format)) { //Error is logged by the caller. throw new FormatNotSupportedException(ResponseCodeConstants.CODE_FORMAT_NOT_SUPPORTED, ResponseCodeConstants.DESC_FORMAT_NOT_SUPPORTED); } return decoderMap.get(format); } /*This method is no longer used.*/ @Override public AuthenticationHandler getAuthenticationHandler(String authMechanism) throws CharonException { if (MapUtils.isNotEmpty(authenticators)) { Map authenticatorProperties = authenticators.get(authMechanism); if (MapUtils.isNotEmpty(authenticatorProperties)) { return (AuthenticationHandler) authenticatorProperties.get(INSTANCE); } } String error = "Requested authentication mechanism is not supported."; throw new CharonException(error); } @Override public UserManager getUserManager(String userName) throws CharonException { SCIMUserManager scimUserManager = null; String tenantDomain = MultitenantUtils.getTenantDomain(userName); String tenantLessUserName = MultitenantUtils.getTenantAwareUsername(userName); try { //get super tenant context and get realm service which is an osgi service RealmService realmService = (RealmService) PrivilegedCarbonContext.getThreadLocalCarbonContext().getOSGiService(RealmService.class); if (realmService != null) { int tenantId = realmService.getTenantManager().getTenantId(tenantDomain); //get tenant's user realm UserRealm userRealm = realmService.getTenantUserRealm(tenantId); ClaimManager claimManager; if (userRealm != null) { //get claim manager for manipulating attributes claimManager = (ClaimManager) userRealm.getClaimManager(); //if tenantless username doesn't contain a domain, add domain to user name in order to comply with multiple user store feature. if (tenantLessUserName.indexOf(CarbonConstants.DOMAIN_SEPARATOR) < 0) { String domain = UserCoreUtil.getDomainFromThreadLocal(); if (domain != null) { tenantLessUserName = domain + CarbonConstants.DOMAIN_SEPARATOR + tenantLessUserName; } } //check whether the user who is trying to obtain the realm is authorized boolean isUserAuthorized = userRealm.getAuthorizationManager().isUserAuthorized( tenantLessUserName, SCIMCommonConstants.PROVISIONING_ADMIN_PERMISSION, SCIMCommonConstants.RESOURCE_TO_BE_AUTHORIZED); if (!isUserAuthorized) { String error = "User is not authorized to perform provisioning"; log.error(error); throw new CharonException(error); } /*if the authenticated & authorized user is not set in the carbon context, set it, coz we are going to refer it later to identify the SCIM providers registered for a particular consumer.*/ String authenticatedUser = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); if (authenticatedUser == null) { PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(tenantLessUserName); if (log.isDebugEnabled()) { log.debug("User read from carbon context is null, hence setting " + "authenticated user: " + tenantLessUserName); } } scimUserManager = new SCIMUserManager((UserStoreManager) userRealm.getUserStoreManager(), userName, claimManager); } } else { String error = "Can not obtain carbon realm service.."; throw new CharonException(error); } //get user store manager } catch (UserStoreException e) { String error = "Error obtaining user realm for the user: " + userName; throw new CharonException(error, e); } return scimUserManager; } public UserManager getUserManager(String userName, String accessPermission) throws CharonException { SCIMUserManager scimUserManager = null; String tenantDomain = MultitenantUtils.getTenantDomain(userName); String tenantLessUserName = MultitenantUtils.getTenantAwareUsername(userName); try { //get super tenant context and get realm service which is an osgi service RealmService realmService = (RealmService) PrivilegedCarbonContext.getThreadLocalCarbonContext().getOSGiService(RealmService.class); if (realmService != null) { int tenantId = realmService.getTenantManager().getTenantId(tenantDomain); //get tenant's user realm UserRealm userRealm = realmService.getTenantUserRealm(tenantId); ClaimManager claimManager; if (userRealm != null) { //get claim manager for manipulating attributes claimManager = (ClaimManager) userRealm.getClaimManager(); //if tenantless username doesn't contain a domain, add domain to user name in order to comply with multiple user store feature. if (tenantLessUserName.indexOf(CarbonConstants.DOMAIN_SEPARATOR) < 0) { String domain = UserCoreUtil.getDomainFromThreadLocal(); if (domain != null) { tenantLessUserName = domain + CarbonConstants.DOMAIN_SEPARATOR + tenantLessUserName; } } //check whether the user who is trying to obtain the realm is authorized boolean isUserAuthorized = userRealm.getAuthorizationManager().isUserAuthorized( tenantLessUserName, accessPermission, SCIMCommonConstants.RESOURCE_TO_BE_AUTHORIZED); if (!isUserAuthorized) { String error = "User is not authorized to perform provisioning"; log.error(error); throw new CharonException(error); } /*if the authenticated & authorized user is not set in the carbon context, set it, coz we are going to refer it later to identify the SCIM providers registered for a particular consumer.*/ String authenticatedUser = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); if (authenticatedUser == null) { PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(tenantLessUserName); if (log.isDebugEnabled()) { log.debug("User read from carbon context is null, hence setting " + "authenticated user: " + tenantLessUserName); } } scimUserManager = new SCIMUserManager((UserStoreManager) userRealm.getUserStoreManager(), userName, claimManager); } } else { String error = "Can not obtain carbon realm service.."; throw new CharonException(error); } //get user store manager } catch (UserStoreException e) { String error = "Error obtaining user realm for the user: " + userName; throw new CharonException(error, e); } return scimUserManager; } @Override public TenantManager getTenantManager() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public AuthenticationInfo registerTenant(TenantDTO tenantDTO) throws CharonException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isAuthenticationSupported(String s) { return false; //To change body of implemented methods use File | Settings | File Templates. } /*This method is no longer used..*/ @Override public AuthenticationInfo handleAuthentication(Map<String, String> authHeaderMap) throws UnauthorizedException { try { String authType = identifyAuthType(authHeaderMap); Map authPropertyMap = authenticators.get(authType); if (authHeaderMap != null) { AuthenticationHandler authHandler = (AuthenticationHandler) authPropertyMap.get(INSTANCE); if (authHandler != null) { authHandler.setCharonManager(this); authHandler.isAuthenticated(authHeaderMap); return authHandler.getAuthenticationInfo(); } } } catch (CharonException e) { if (log.isDebugEnabled()) { log.debug("CharonException in handle authentication. ", e); } throw new UnauthorizedException("Error in handling authentication"); } throw new UnauthorizedException(); } /** * Register encoders and decoders in AbstractResourceEndpoint. */ private void registerCoders() throws CharonException { if (!encoderMap.isEmpty()) { for (Map.Entry<String, Encoder> encoderEntry : encoderMap.entrySet()) { AbstractResourceEndpoint.registerEncoder(encoderEntry.getKey(), encoderEntry.getValue()); } } if (!encoderMap.isEmpty()) { for (Map.Entry<String, Decoder> decoderEntry : decoderMap.entrySet()) { AbstractResourceEndpoint.registerDecoder(decoderEntry.getKey(), decoderEntry.getValue()); } } } private void registerEndpointURLs() { if (MapUtils.isNotEmpty(endpointURLs)) { AbstractResourceEndpoint.registerResourceEndpointURLs(endpointURLs); } } /** * Identify the authentication mechanism, given the http headers sent in the SCIM API access request. * * @param authHeaders * @return * @throws CharonException */ public String identifyAuthType(Map<String, String> authHeaders) throws CharonException, UnauthorizedException { String authorizationHeader = authHeaders.get(SCIMConstants.AUTHORIZATION_HEADER); String authenticationType = null; if (authorizationHeader != null) { authenticationType = authorizationHeader.split(" ")[0]; } else { String error = "No Authorization header found"; log.error(error); throw new UnauthorizedException(); } if (SCIMConstants.AUTH_TYPE_BASIC.equals(authenticationType)) { return SCIMConstants.AUTH_TYPE_BASIC; } else if (SCIMConstants.AUTH_TYPE_OAUTH.equals(authenticationType)) { return SCIMConstants.AUTH_TYPE_OAUTH; } else if (authHeaders.get(SCIMConstants.AUTHENTICATION_TYPE_HEADER) != null) { return authHeaders.get(SCIMConstants.AUTHENTICATION_TYPE_HEADER); } else { String error = "Provided authentication headers do not contain supported authentication headers."; throw new CharonException(error); } } }