/* * Copyright (c) 2015, 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.user.mgt.workflow.userstore; 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.identity.workflow.mgt.WorkflowManagementService; import org.wso2.carbon.identity.workflow.mgt.bean.Entity; import org.wso2.carbon.identity.workflow.mgt.exception.InternalWorkflowException; import org.wso2.carbon.identity.workflow.mgt.exception.WorkflowException; import org.wso2.carbon.identity.workflow.mgt.extension.AbstractWorkflowRequestHandler; import org.wso2.carbon.identity.workflow.mgt.util.WorkflowDataType; import org.wso2.carbon.identity.workflow.mgt.util.WorkflowRequestStatus; import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.common.AbstractUserStoreManager; import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.user.core.util.UserCoreUtil; import org.wso2.carbon.user.mgt.workflow.internal.IdentityWorkflowDataHolder; import org.wso2.carbon.user.mgt.workflow.util.UserStoreWFConstants; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; public class DeleteUserWFRequestHandler extends AbstractWorkflowRequestHandler { private static final String FRIENDLY_NAME = "Delete User"; private static final String FRIENDLY_DESCRIPTION = "Triggered when a user is removed."; private static final String USERNAME = "Username"; private static final String USER_STORE_DOMAIN = "User Store Domain"; private static final Map<String, String> PARAM_DEFINITION; private static Log log = LogFactory.getLog(DeleteUserWFRequestHandler.class); static { PARAM_DEFINITION = new LinkedHashMap<>(); PARAM_DEFINITION.put(USERNAME, WorkflowDataType.STRING_TYPE); PARAM_DEFINITION.put(USER_STORE_DOMAIN, WorkflowDataType.STRING_TYPE); } public boolean startDeleteUserFlow(String userStoreDomain, String userName) throws WorkflowException { WorkflowManagementService workflowService = IdentityWorkflowDataHolder.getInstance().getWorkflowService(); int tenant = CarbonContext.getThreadLocalCarbonContext().getTenantId(); String fullyQualifiedName = UserCoreUtil.addDomainToName(userName, userStoreDomain); Entity userEntity = new Entity(fullyQualifiedName, UserStoreWFConstants.ENTITY_TYPE_USER, tenant); Map<String, Object> wfParams = new HashMap<>(); Map<String, Object> nonWfParams = new HashMap<>(); wfParams.put(USERNAME, userName); wfParams.put(USER_STORE_DOMAIN, userStoreDomain); String uuid = UUID.randomUUID().toString(); if (workflowService.isEventAssociated(UserStoreWFConstants.DELETE_USER_EVENT) && !Boolean.TRUE.equals (getWorkFlowCompleted()) && !isValidOperation(new Entity[]{userEntity})) { throw new WorkflowException("Operation is not valid"); } boolean state = startWorkFlow(wfParams, nonWfParams, uuid).getExecutorResultState().state(); //WF_REQUEST_ENTITY_RELATIONSHIP table has foreign key to WF_REQUEST, so need to run this after WF_REQUEST is // updated if (!Boolean.TRUE.equals(getWorkFlowCompleted()) && !state) { //ToDo: Add thread local to handle scenarios where workflow is not associated with the event. try { workflowService.addRequestEntityRelationships(uuid, new Entity[]{userEntity}); } catch (InternalWorkflowException e) { //debug exception which occurs at DB level since no workflows associated with event if (log.isDebugEnabled()) { log.debug("No workflow associated with the operation.", e); } } } return state; } @Override public void onWorkflowCompletion(String status, Map<String, Object> requestParams, Map<String, Object> responseAdditionalParams, int tenantId) throws WorkflowException { String userName; Object requestUsername = requestParams.get(USERNAME); if (requestUsername == null || !(requestUsername instanceof String)) { throw new WorkflowException("Callback request for delete user received without the mandatory " + "parameter 'username'"); } String userStoreDomain = (String) requestParams.get(USER_STORE_DOMAIN); if (StringUtils.isNotBlank(userStoreDomain)) { userName = userStoreDomain + "/" + requestUsername; } else { userName = (String) requestUsername; } if (WorkflowRequestStatus.APPROVED.toString().equals(status) || WorkflowRequestStatus.SKIPPED.toString().equals(status)) { try { RealmService realmService = IdentityWorkflowDataHolder.getInstance().getRealmService(); UserRealm userRealm = realmService.getTenantUserRealm(tenantId); userRealm.getUserStoreManager().deleteUser(userName); } catch (UserStoreException e) { // Sending e.getMessage() since it is required to give error message to end user. throw new WorkflowException(e.getMessage(), e); } } else { if (retryNeedAtCallback()) { //unset threadlocal variable unsetWorkFlowCompleted(); } if (log.isDebugEnabled()) { log.debug("Deleting user is aborted for user '" + userName + "', Reason: Workflow response was " + status); } } } @Override public boolean retryNeedAtCallback() { return true; } @Override public String getEventId() { return UserStoreWFConstants.DELETE_USER_EVENT; } @Override public Map<String, String> getParamDefinitions() { return PARAM_DEFINITION; } @Override public String getFriendlyName() { return FRIENDLY_NAME; } @Override public String getDescription() { return FRIENDLY_DESCRIPTION; } @Override public String getCategory() { return UserStoreWFConstants.CATEGORY_USERSTORE_OPERATIONS; } @Override public boolean isValidOperation(Entity[] entities) throws WorkflowException { UserRealm userRealm; AbstractUserStoreManager userStoreManager; WorkflowManagementService workflowService = IdentityWorkflowDataHolder.getInstance().getWorkflowService(); RealmService realmService = IdentityWorkflowDataHolder.getInstance().getRealmService(); try { userRealm = realmService.getTenantUserRealm(PrivilegedCarbonContext.getThreadLocalCarbonContext() .getTenantId()); userStoreManager = (AbstractUserStoreManager) userRealm.getUserStoreManager(); } catch (UserStoreException e) { throw new WorkflowException("Error while retrieving user realm.", e); } for (int i = 0; i < entities.length; i++) { try { if (UserStoreWFConstants.ENTITY_TYPE_USER.equals(entities[i].getEntityType()) && workflowService .entityHasPendingWorkflows(entities[i])) { throw new WorkflowException("User has pending workflows which blocks this operation."); } else if (UserStoreWFConstants.ENTITY_TYPE_USER.equals(entities[i].getEntityType()) && !userStoreManager.isExistingUser(entities[i].getEntityId())) { throw new WorkflowException("User does not exist."); } } catch (InternalWorkflowException | org.wso2.carbon.user.core.UserStoreException e) { throw new WorkflowException(e.getMessage(), e); } } return true; } }