<commit_before>/*
 * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed 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.inbound.provisioning.scim2.common.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.SCIMClaimResolver;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.SCIMCommonConstants;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.SCIMCommonUtils;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.claim.ClaimMapper;
import org.wso2.carbon.identity.mgt.IdentityStore;
import org.wso2.carbon.identity.mgt.bean.GroupBean;
import org.wso2.carbon.identity.mgt.bean.UserBean;
import org.wso2.carbon.identity.mgt.claim.Claim;
import org.wso2.carbon.identity.mgt.claim.MetaClaim;
import org.wso2.carbon.identity.mgt.exception.GroupNotFoundException;
import org.wso2.carbon.identity.mgt.exception.IdentityStoreException;
import org.wso2.carbon.identity.mgt.exception.UserNotFoundException;
import org.wso2.charon3.core.attributes.Attribute;
import org.wso2.charon3.core.attributes.ComplexAttribute;
import org.wso2.charon3.core.attributes.MultiValuedAttribute;
import org.wso2.charon3.core.attributes.SimpleAttribute;
import org.wso2.charon3.core.config.SCIMUserSchemaExtensionBuilder;
import org.wso2.charon3.core.exceptions.BadRequestException;
import org.wso2.charon3.core.exceptions.CharonException;
import org.wso2.charon3.core.exceptions.ConflictException;
import org.wso2.charon3.core.exceptions.NotFoundException;
import org.wso2.charon3.core.exceptions.NotImplementedException;
import org.wso2.charon3.core.extensions.UserManager;
import org.wso2.charon3.core.objects.Group;
import org.wso2.charon3.core.objects.User;
import org.wso2.charon3.core.schema.SCIMConstants;
import org.wso2.charon3.core.utils.codeutils.ExpressionNode;
import org.wso2.charon3.core.utils.codeutils.Node;
import org.wso2.charon3.core.utils.codeutils.SearchRequest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.PasswordCallback;
import static org.wso2.carbon.kernel.utils.StringUtils.isNullOrEmpty;

/**
 * This class is to deal with the carbon user core API. This uses identityStore defined API for
 * user, group based identity provisioning operations.
 */

public class CarbonUserManager implements UserManager {

    private static Logger log = LoggerFactory.getLogger(CarbonUserManager.class);
    private static final String ADMIN_USERNAME = "admin";
    private static final String USERNAME_CLAIM = "http://wso2.org/claims/username";

    IdentityStore identityStore = null;

    public CarbonUserManager(IdentityStore identityStore) {
        this.identityStore = identityStore;
    }

    @Override
    public User createUser(User user, Map<String, Boolean> requiredAttributes) throws CharonException,
            ConflictException, BadRequestException {
        try {

            if (log.isDebugEnabled()) {
                log.debug("Creating user: " + user.toString());
            }

            String userStoreDomain = SCIMCommonUtils.extractDomainFromName(user.getUserName(), identityStore);
            user.setUserName(SCIMCommonUtils.removeDomainFromName(user.getUserName()));

            //get the groups attribute as we are going to explicitly store the info of the user's groups
            MultiValuedAttribute groupsAttribute = (MultiValuedAttribute) (
                    user.getAttribute(SCIMConstants.UserSchemaConstants.GROUPS));

            Map<String, String> claimsMap = SCIMClaimResolver.getClaimsMap(user);

            //create user model as that is what need to send to identity store api.
            UserBean userBean = getUserBeanFromClaims(claimsMap);
            //TODO this is a temporary method. need to remove this once the claim management is completed.
            userBean = ClaimMapper.getInstance().convertMetaToWso2Dialect(userBean);

            if (identityStore.isUserExist(userBean.getClaims(), userStoreDomain)) {
                throw new ConflictException("User with the name: " + user.getUserName() +
                        " already exists in the system.");
            }
            Attribute passwordAttribute = user.getAttribute(SCIMConstants.UserSchemaConstants.PASSWORD);
            if (passwordAttribute != null) {

                char[] password = ((SimpleAttribute) (user.getAttribute(SCIMConstants.UserSchemaConstants.PASSWORD))).
                        getStringValue().toCharArray();

                PasswordCallback passwordCallback = new PasswordCallback(SCIMConstants.UserSchemaConstants.PASSWORD,
                        false);
                passwordCallback.setPassword(password);
                List<Callback> callbackList = new ArrayList<>();
                callbackList.add(passwordCallback);
                userBean.setCredentials(callbackList);
            }
            org.wso2.carbon.identity.mgt.User userStoreUser = identityStore.addUser(userBean, userStoreDomain);
            // list to store the group ids which will be used to create the group attribute in scim user.
            List<String> groupIds = new ArrayList<>();

            if (groupsAttribute != null) {
                List<Attribute> subValues = groupsAttribute.getAttributeValues();

                if (subValues != null && subValues.size() != 0) {
                    for (Attribute subValue : subValues) {
                        SimpleAttribute valueAttribute =
                            (SimpleAttribute) ((subValue)).getSubAttribute(SCIMConstants.CommonSchemaConstants.VALUE);
                        groupIds.add((String) valueAttribute.getValue());
                    }
                }
            }
            //need to add users groups if it is available in the request
            if (groupIds.size() != 0) {
                //now add the user's groups explicitly.
                identityStore.updateGroupsOfUser(userStoreUser.getUniqueUserId(), groupIds);
            }
            if (log.isDebugEnabled()) {
                log.debug("User: " + user.getUserName() + " is created through SCIM.");
            }
            //get the user again from the user store and send it to client.
            return this.getUser(userStoreUser.getUniqueUserId(), requiredAttributes);

        } catch (IdentityStoreException e) {
            String errMsg = "Error occurred while adding user:" + user.getUserName() + "to user store";
            //Charon wrap exception to SCIMResponse and does not log exceptions
            log.error(errMsg, e);
            throw new ConflictException(errMsg);
        } catch (NotFoundException e) {
            String errMsg = "Error in retrieving newly added user:" + user.getUserName() + " from user store";
            //Charon wrap exception to SCIMResponse and does not log exceptions
            log.error(errMsg, e);
            throw new CharonException(errMsg, e);
        }
    }

    @Override
    public User getUser(String userId, Map<String, Boolean> requiredAttributes) throws CharonException,
            BadRequestException, NotFoundException {

        if (log.isDebugEnabled()) {
            log.debug("Retrieving user: " + userId);
        }
        try {
            org.wso2.carbon.identity.mgt.User userStoreUser = identityStore.getUser(userId);

            //TODO:We need to pass the scim claim dialect for this method
            List<Claim> claimList = userStoreUser.getClaims();
            //TODO this is a temporary method. need to remove this once the claim management is completed.
            claimList = ClaimMapper.getInstance().convertToScimDialect(claimList);

            User scimUser = getSCIMUser(userStoreUser, claimList);

            if (log.isDebugEnabled()) {
                log.debug("User: " + scimUser.getUserName() + " is retrieved through SCIM.");
            }

            return scimUser;

        } catch (IdentityStoreException e) {
            throw new CharonException("Error in getting user from the userid :" + userId, e);
        } catch (UserNotFoundException e) {
            throw new NotFoundException("User not found with the given userid :" + userId);
        }

    }

    @Override
    public void deleteUser(String userId) throws NotFoundException, CharonException, NotImplementedException,
            BadRequestException {

        if (log.isDebugEnabled()) {
            log.debug("Deleting user: " + userId);
        }
        try {
            org.wso2.carbon.identity.mgt.User user = identityStore.getUser(userId);
            List<Claim> claims = user.getClaims();
            for (Claim claim : claims) {
                if (USERNAME_CLAIM.equals(claim.getClaimUri())) {
                    if (ADMIN_USERNAME.equals(claim.getValue())) {
                        throw new CharonException("Cannot Delete admin user from the System");
                    }
                }
            }
            identityStore.deleteUser(userId);
            if (log.isDebugEnabled()) {
                log.debug("User with the id : " + userId + " is deleted through SCIM.");
            }

        } catch (UserNotFoundException e) {
            throw new NotFoundException("User with the user id : " + userId + " does not exists.");

        } catch (IdentityStoreException e) {
            throw new CharonException("Error in deleting the user with the id: " + userId, e);

        }
    }

    @Override
    public List<Object> listUsersWithGET(Node rootNode, int startIndex, int count, String sortBy,
                                         String sortOrder, Map<String, Boolean> requiredAttributes)
            throws CharonException, NotImplementedException, BadRequestException {

        // check if it is a pagination and filter combination.
        if (sortOrder == null && sortBy == null && rootNode != null) {

            return listUsersWithPaginationAndFilter(rootNode, startIndex, count);

        } //check if it is a pagination only request.<commit_msg>Improve Iteration with java8 streams<commit_after>/*
 * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed 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.inbound.provisioning.scim2.common.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.SCIMClaimResolver;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.SCIMCommonConstants;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.SCIMCommonUtils;
import org.wso2.carbon.identity.inbound.provisioning.scim2.common.utils.claim.ClaimMapper;
import org.wso2.carbon.identity.mgt.IdentityStore;
import org.wso2.carbon.identity.mgt.bean.GroupBean;
import org.wso2.carbon.identity.mgt.bean.UserBean;
import org.wso2.carbon.identity.mgt.claim.Claim;
import org.wso2.carbon.identity.mgt.claim.MetaClaim;
import org.wso2.carbon.identity.mgt.exception.GroupNotFoundException;
import org.wso2.carbon.identity.mgt.exception.IdentityStoreException;
import org.wso2.carbon.identity.mgt.exception.UserNotFoundException;
import org.wso2.charon3.core.attributes.Attribute;
import org.wso2.charon3.core.attributes.ComplexAttribute;
import org.wso2.charon3.core.attributes.MultiValuedAttribute;
import org.wso2.charon3.core.attributes.SimpleAttribute;
import org.wso2.charon3.core.config.SCIMUserSchemaExtensionBuilder;
import org.wso2.charon3.core.exceptions.BadRequestException;
import org.wso2.charon3.core.exceptions.CharonException;
import org.wso2.charon3.core.exceptions.ConflictException;
import org.wso2.charon3.core.exceptions.NotFoundException;
import org.wso2.charon3.core.exceptions.NotImplementedException;
import org.wso2.charon3.core.extensions.UserManager;
import org.wso2.charon3.core.objects.Group;
import org.wso2.charon3.core.objects.User;
import org.wso2.charon3.core.schema.SCIMConstants;
import org.wso2.charon3.core.utils.codeutils.ExpressionNode;
import org.wso2.charon3.core.utils.codeutils.Node;
import org.wso2.charon3.core.utils.codeutils.SearchRequest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.PasswordCallback;
import static org.wso2.carbon.kernel.utils.StringUtils.isNullOrEmpty;

/**
 * This class is to deal with the carbon user core API. This uses identityStore defined API for
 * user, group based identity provisioning operations.
 */

public class CarbonUserManager implements UserManager {

    private static Logger log = LoggerFactory.getLogger(CarbonUserManager.class);
    private static final String ADMIN_USERNAME = "admin";
    private static final String USERNAME_CLAIM = "http://wso2.org/claims/username";

    IdentityStore identityStore = null;

    public CarbonUserManager(IdentityStore identityStore) {
        this.identityStore = identityStore;
    }

    @Override
    public User createUser(User user, Map<String, Boolean> requiredAttributes) throws CharonException,
            ConflictException, BadRequestException {
        try {

            if (log.isDebugEnabled()) {
                log.debug("Creating user: " + user.toString());
            }

            String userStoreDomain = SCIMCommonUtils.extractDomainFromName(user.getUserName(), identityStore);
            user.setUserName(SCIMCommonUtils.removeDomainFromName(user.getUserName()));

            //get the groups attribute as we are going to explicitly store the info of the user's groups
            MultiValuedAttribute groupsAttribute = (MultiValuedAttribute) (
                    user.getAttribute(SCIMConstants.UserSchemaConstants.GROUPS));

            Map<String, String> claimsMap = SCIMClaimResolver.getClaimsMap(user);

            //create user model as that is what need to send to identity store api.
            UserBean userBean = getUserBeanFromClaims(claimsMap);
            //TODO this is a temporary method. need to remove this once the claim management is completed.
            userBean = ClaimMapper.getInstance().convertMetaToWso2Dialect(userBean);

            if (identityStore.isUserExist(userBean.getClaims(), userStoreDomain)) {
                throw new ConflictException("User with the name: " + user.getUserName() +
                        " already exists in the system.");
            }
            Attribute passwordAttribute = user.getAttribute(SCIMConstants.UserSchemaConstants.PASSWORD);
            if (passwordAttribute != null) {

                char[] password = ((SimpleAttribute) (user.getAttribute(SCIMConstants.UserSchemaConstants.PASSWORD))).
                        getStringValue().toCharArray();

                PasswordCallback passwordCallback = new PasswordCallback(SCIMConstants.UserSchemaConstants.PASSWORD,
                        false);
                passwordCallback.setPassword(password);
                List<Callback> callbackList = new ArrayList<>();
                callbackList.add(passwordCallback);
                userBean.setCredentials(callbackList);
            }
            org.wso2.carbon.identity.mgt.User userStoreUser = identityStore.addUser(userBean, userStoreDomain);
            // list to store the group ids which will be used to create the group attribute in scim user.
            List<String> groupIds = new ArrayList<>();

            if (groupsAttribute != null) {
                List<Attribute> subValues = groupsAttribute.getAttributeValues();

                if (subValues != null && subValues.size() != 0) {
                    for (Attribute subValue : subValues) {
                        SimpleAttribute valueAttribute =
                            (SimpleAttribute) ((subValue)).getSubAttribute(SCIMConstants.CommonSchemaConstants.VALUE);
                        groupIds.add((String) valueAttribute.getValue());
                    }
                }
            }
            //need to add users groups if it is available in the request
            if (groupIds.size() != 0) {
                //now add the user's groups explicitly.
                identityStore.updateGroupsOfUser(userStoreUser.getUniqueUserId(), groupIds);
            }
            if (log.isDebugEnabled()) {
                log.debug("User: " + user.getUserName() + " is created through SCIM.");
            }
            //get the user again from the user store and send it to client.
            return this.getUser(userStoreUser.getUniqueUserId(), requiredAttributes);

        } catch (IdentityStoreException e) {
            String errMsg = "Error occurred while adding user:" + user.getUserName() + "to user store";
            //Charon wrap exception to SCIMResponse and does not log exceptions
            log.error(errMsg, e);
            throw new ConflictException(errMsg);
        } catch (NotFoundException e) {
            String errMsg = "Error in retrieving newly added user:" + user.getUserName() + " from user store";
            //Charon wrap exception to SCIMResponse and does not log exceptions
            log.error(errMsg, e);
            throw new CharonException(errMsg, e);
        }
    }

    @Override
    public User getUser(String userId, Map<String, Boolean> requiredAttributes) throws CharonException,
            BadRequestException, NotFoundException {

        if (log.isDebugEnabled()) {
            log.debug("Retrieving user: " + userId);
        }
        try {
            org.wso2.carbon.identity.mgt.User userStoreUser = identityStore.getUser(userId);

            //TODO:We need to pass the scim claim dialect for this method
            List<Claim> claimList = userStoreUser.getClaims();
            //TODO this is a temporary method. need to remove this once the claim management is completed.
            claimList = ClaimMapper.getInstance().convertToScimDialect(claimList);

            User scimUser = getSCIMUser(userStoreUser, claimList);

            if (log.isDebugEnabled()) {
                log.debug("User: " + scimUser.getUserName() + " is retrieved through SCIM.");
            }

            return scimUser;

        } catch (IdentityStoreException e) {
            throw new CharonException("Error in getting user from the userid :" + userId, e);
        } catch (UserNotFoundException e) {
            throw new NotFoundException("User not found with the given userid :" + userId);
        }

    }

    @Override
    public void deleteUser(String userId) throws NotFoundException, CharonException, NotImplementedException,
            BadRequestException {

        if (log.isDebugEnabled()) {
            log.debug("Deleting user: " + userId);
        }
        try {
            org.wso2.carbon.identity.mgt.User user = identityStore.getUser(userId);
            List<Claim> claims = user.getClaims();

            boolean isAdminUser = claims.stream().anyMatch(claim -> USERNAME_CLAIM.equals(claim.getClaimUri()) &&
                    ADMIN_USERNAME.equals(claim.getValue()));

            if (isAdminUser) {
                throw new CharonException("Cannot Delete admin user from the System");
            }

            identityStore.deleteUser(userId);
            if (log.isDebugEnabled()) {
                log.debug("User with the id : " + userId + " is deleted through SCIM.");
            }

        } catch (UserNotFoundException e) {
            throw new NotFoundException("User with the user id : " + userId + " does not exists.");

        } catch (IdentityStoreException e) {
            throw new CharonException("Error in deleting the user with the id: " + userId, e);

        }
    }

    @Override
    public List<Object> listUsersWithGET(Node rootNode, int startIndex, int count, String sortBy,
                                         String sortOrder, Map<String, Boolean> requiredAttributes)
            throws CharonException, NotImplementedException, BadRequestException {

        // check if it is a pagination and filter combination.
        if (sortOrder == null && sortBy == null && rootNode != null) {

            return listUsersWithPaginationAndFilter(rootNode, startIndex, count);

        } //check if it is a pagination only request.
{'fraction_non_alphanumeric': 0.09145972138098123, 'fraction_numerical': 0.006755812328192704, 'mean_word_length': 2.74851554313657, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1096295', 'n_tokens_mistral': 5774, 'n_tokens_neox': 5595, 'n_words': 1440}