/*
* 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.application.authentication.framework.handler.provisioning.impl;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.CarbonException;
import org.wso2.carbon.core.util.AnonymousSessionUtil;
import org.wso2.carbon.core.util.PermissionUpdateUtil;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.ProvisioningHandler;
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceComponent;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.user.core.UserCoreConstants;
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.service.RealmService;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DefaultProvisioningHandler implements ProvisioningHandler {
private static final Log log = LogFactory.getLog(DefaultProvisioningHandler.class);
private static volatile DefaultProvisioningHandler instance;
private SecureRandom random = new SecureRandom();
public static DefaultProvisioningHandler getInstance() {
if (instance == null) {
synchronized (DefaultProvisioningHandler.class) {
if (instance == null) {
instance = new DefaultProvisioningHandler();
}
}
}
return instance;
}
@Override
public void handle(List<String> roles, String subject, Map<String, String> attributes,
String provisioningUserStoreId, String tenantDomain) throws FrameworkException {
RegistryService registryService = FrameworkServiceComponent.getRegistryService();
RealmService realmService = FrameworkServiceComponent.getRealmService();
try {
int tenantId = realmService.getTenantManager().getTenantId(tenantDomain);
UserRealm realm = AnonymousSessionUtil.getRealmByTenantDomain(registryService,
realmService, tenantDomain);
String userStoreDomain = getUserStoreDomain(provisioningUserStoreId, realm);
String username = MultitenantUtils.getTenantAwareUsername(subject);
UserStoreManager userStoreManager = getUserStoreManager(realm, userStoreDomain);
// Remove userStoreManager domain from username if the userStoreDomain is not primary
if (realm.getUserStoreManager().getRealmConfiguration().isPrimary()) {
username = UserCoreUtil.removeDomainFromName(username);
}
String[] newRoles = new String[]{};
if (roles != null) {
roles = removeDomainFromNamesExcludeInternal(roles, userStoreManager.getTenantId());
newRoles = roles.toArray(new String[roles.size()]);
}
if (log.isDebugEnabled()) {
log.debug("User " + username + " contains roles : " + Arrays.toString(newRoles)
+ " going to be provisioned");
}
// addingRoles = newRoles AND allExistingRoles
Collection<String> addingRoles = getRolesToAdd(userStoreManager, newRoles);
Map<String, String> userClaims = prepareClaimMappings(attributes);
if (userStoreManager.isExistingUser(username)) {
if (roles != null && !roles.isEmpty()) {
// Update user
Collection<String> currentRolesList = Arrays.asList(userStoreManager
.getRoleListOfUser(username));
// addingRoles = (newRoles AND existingRoles) - currentRolesList)
addingRoles.removeAll(currentRolesList);
Collection<String> deletingRoles = new ArrayList<String>();
deletingRoles.addAll(currentRolesList);
// deletingRoles = currentRolesList - newRoles
deletingRoles.removeAll(Arrays.asList(newRoles));
// Exclude Internal/everyonerole from deleting role since its cannot be deleted
deletingRoles.remove(realm.getRealmConfiguration().getEveryOneRoleName());
// TODO : Does it need to check this?
// Check for case whether superadmin login
handleFederatedUserNameEqualsToSuperAdminUserName(realm, username, userStoreManager, deletingRoles);
updateUserWithNewRoleSet(username, userStoreManager, newRoles, addingRoles, deletingRoles);
}
if (!userClaims.isEmpty()) {
userStoreManager.setUserClaimValues(username, userClaims, null);
}
} else {
userStoreManager.addUser(username, generatePassword(), addingRoles.toArray(
new String[addingRoles.size()]), userClaims, null);
if (log.isDebugEnabled()) {
log.debug("Federated user: " + username
+ " is provisioned by authentication framework with roles : "
+ Arrays.toString(addingRoles.toArray(new String[addingRoles.size()])));
}
}
PermissionUpdateUtil.updatePermissionTree(tenantId);
} catch (org.wso2.carbon.user.api.UserStoreException | CarbonException e) {
throw new FrameworkException("Error while provisioning user : " + subject, e);
}
}
private void updateUserWithNewRoleSet(String username, UserStoreManager userStoreManager, String[] newRoles,
Collection<String> addingRoles, Collection<String> deletingRoles)
throws UserStoreException {
if (log.isDebugEnabled()) {
log.debug("Deleting roles : "
+ Arrays.toString(deletingRoles.toArray(new String[deletingRoles.size()]))
+ " and Adding roles : "
+ Arrays.toString(addingRoles.toArray(new String[addingRoles.size()])));
}
userStoreManager.updateRoleListOfUser(username, deletingRoles.toArray(new String[deletingRoles
.size()]),
addingRoles.toArray(new String[addingRoles.size()]));
if (log.isDebugEnabled()) {
log.debug("Federated user: " + username
+ " is updated by authentication framework with roles : "
+ Arrays.toString(newRoles));
}
}
private void handleFederatedUserNameEqualsToSuperAdminUserName(UserRealm realm, String username,
UserStoreManager userStoreManager,
Collection<String> deletingRoles)
throws UserStoreException, FrameworkException {
if (userStoreManager.getRealmConfiguration().isPrimary()
&& username.equals(realm.getRealmConfiguration().getAdminUserName())) {
if (log.isDebugEnabled()) {
log.debug("Federated user's username is equal to super admin's username of local IdP.");
}
// Whether superadmin login without superadmin role is permitted
if (deletingRoles
.contains(realm.getRealmConfiguration().getAdminRoleName())) {
if (log.isDebugEnabled()) {
log.debug("Federated user doesn't have super admin role. Unable to sync roles, since" +
" super admin role cannot be unassigned from super admin user");
}
throw new FrameworkException(
"Federated user which having same username to super admin username of local IdP," +
" trying login without having super admin role assigned");
}
}
}
private Map<String, String> prepareClaimMappings(Map<String, String> attributes) {
Map<String, String> userClaims = new HashMap<>();
if (attributes != null && !attributes.isEmpty()) {
for (Map.Entry<String, String> entry : attributes.entrySet()) {
String claimURI = entry.getKey();
String claimValue = entry.getValue();
if (!(StringUtils.isEmpty(claimURI) || StringUtils.isEmpty(claimValue))) {
userClaims.put(claimURI, claimValue);
}
}
}
return userClaims;
}
private Collection<String> getRolesToAdd(UserStoreManager userStoreManager, String[] newRoles)
throws UserStoreException {
Collection<String> addingRoles = new ArrayList<>();
Collections.addAll(addingRoles, newRoles);
Collection<String> allExistingRoles = removeDomainFromNamesExcludeInternal(
Arrays.asList(userStoreManager.getRoleNames()), userStoreManager.getTenantId());
addingRoles.retainAll(allExistingRoles);
return addingRoles;
}
private UserStoreManager getUserStoreManager(UserRealm realm, String userStoreDomain)
throws UserStoreException, FrameworkException {
UserStoreManager userStoreManager;
if (userStoreDomain != null && !userStoreDomain.isEmpty()) {
userStoreManager = realm.getUserStoreManager().getSecondaryUserStoreManager(
userStoreDomain);
} else {
userStoreManager = realm.getUserStoreManager();
}
if (userStoreManager == null) {
throw new FrameworkException("Specified user store is invalid");
}
return userStoreManager;
}
/**
* Compute the user store which user to be provisioned
*
* @return
* @throws UserStoreException
*/
private String getUserStoreDomain(String userStoreDomain, UserRealm realm)
throws FrameworkException, UserStoreException {
// If the any of above value is invalid, keep it empty to use primary userstore
if (userStoreDomain != null
&& realm.getUserStoreManager().getSecondaryUserStoreManager(userStoreDomain) == null) {
throw new FrameworkException("Specified user store domain " + userStoreDomain
+ " is not valid.");
}
return userStoreDomain;
}
/**
* Generates (random) password for user to be provisioned
*
* @return
*/
protected String generatePassword() {
return RandomStringUtils.randomNumeric(12);
}
/**
* remove user store domain from names except the domain 'Internal'
*
* @param names
* @return
*/
private List<String> removeDomainFromNamesExcludeInternal(List<String> names, int tenantId) {
List<String> nameList = new ArrayList<String>();
for (String name : names) {
String userStoreDomain = IdentityUtil.extractDomainFromName(name);
if (UserCoreConstants.INTERNAL_DOMAIN.equalsIgnoreCase(userStoreDomain)) {
nameList.add(name);
} else {
nameList.add(UserCoreUtil.removeDomainFromName(name));
}
}
return nameList;
}
}