http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java ---------------------------------------------------------------------- diff --cc core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java index 6b4f838,0000000..48092ee mode 100644,000000..100644 --- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java +++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java @@@ -1,853 -1,0 +1,866 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.workflow.activiti; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Resource; +import javax.ws.rs.NotFoundException; +import org.activiti.bpmn.converter.BpmnXMLConverter; +import org.activiti.bpmn.model.BpmnModel; +import org.activiti.editor.constants.ModelDataJsonConstants; +import org.activiti.editor.language.json.converter.BpmnJsonConverter; +import org.activiti.engine.ActivitiException; +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.form.FormProperty; +import org.activiti.engine.form.FormType; +import org.activiti.engine.form.TaskFormData; +import org.activiti.engine.history.HistoricActivityInstance; +import org.activiti.engine.history.HistoricDetail; +import org.activiti.engine.history.HistoricTaskInstance; +import org.activiti.engine.impl.persistence.entity.HistoricFormPropertyEntity; +import org.activiti.engine.query.Query; +import org.activiti.engine.repository.Model; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.ProcessInstance; +import org.activiti.engine.task.Task; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.to.WorkflowFormPropertyTO; +import org.apache.syncope.common.lib.to.WorkflowFormTO; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.common.lib.types.WorkflowFormPropertyType; +import org.apache.syncope.core.misc.security.AuthContextUtils; +import org.apache.syncope.core.misc.spring.BeanUtils; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException; +import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.workflow.api.WorkflowDefinitionFormat; +import org.apache.syncope.core.workflow.api.WorkflowException; +import org.apache.syncope.core.workflow.java.AbstractUserWorkflowAdapter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * Activiti {@link http://www.activiti.org/} based implementation. + */ +public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter { + + protected static final String[] PROPERTY_IGNORE_PROPS = { "type" }; + + public static final String WF_PROCESS_ID = "userWorkflow"; + + public static final String WF_PROCESS_RESOURCE = "userWorkflow.bpmn20.xml"; + + public static final String WF_DGRM_RESOURCE = "userWorkflow.userWorkflow.png"; + + public static final String USER = "user"; + + public static final String WF_EXECUTOR = "wfExecutor"; + + public static final String FORM_SUBMITTER = "formSubmitter"; + + public static final String USER_TO = "userTO"; + + public static final String ENABLED = "enabled"; + + public static final String USER_PATCH = "userPatch"; + + public static final String EMAIL_KIND = "emailKind"; + + public static final String TASK = "task"; + + public static final String TOKEN = "token"; + + public static final String PASSWORD = "password"; + + public static final String PROP_BY_RESOURCE = "propByResource"; + + public static final String PROPAGATE_ENABLE = "propagateEnable"; + + public static final String ENCRYPTED_PWD = "encryptedPwd"; + + public static final String TASK_IS_FORM = "taskIsForm"; + + public static final String MODEL_DATA_JSON_MODEL = "model"; + + public static final String STORE_PASSWORD = "storePassword"; + + public static final String EVENT = "event"; + + @Resource(name = "adminUser") + protected String adminUser; + + @Autowired + protected ProcessEngine engine; + + @Autowired + protected UserDataBinder userDataBinder; + + @Override + public String getPrefix() { + return "ACT_"; + } + + protected void throwException(final ActivitiException e, final String defaultMessage) { + if (e.getCause() != null) { + if (e.getCause().getCause() instanceof SyncopeClientException) { + throw (SyncopeClientException) e.getCause().getCause(); + } else if (e.getCause().getCause() instanceof ParsingValidationException) { + throw (ParsingValidationException) e.getCause().getCause(); + } else if (e.getCause().getCause() instanceof InvalidEntityException) { + throw (InvalidEntityException) e.getCause().getCause(); + } + } + + throw new WorkflowException(defaultMessage, e); + } + + protected void updateStatus(final User user) { + List<Task> tasks = engine.getTaskService().createTaskQuery().processInstanceId(user.getWorkflowId()).list(); + if (tasks.isEmpty() || tasks.size() > 1) { + LOG.warn("While setting user status: unexpected task number ({})", tasks.size()); + } else { + user.setStatus(tasks.get(0).getTaskDefinitionKey()); + } + } + + protected String getFormTask(final User user) { + String result = null; + + List<Task> tasks = engine.getTaskService().createTaskQuery().processInstanceId(user.getWorkflowId()).list(); + if (tasks.isEmpty() || tasks.size() > 1) { + LOG.warn("While checking if form task: unexpected task number ({})", tasks.size()); + } else { + try { + TaskFormData formData = engine.getFormService().getTaskFormData(tasks.get(0).getId()); + if (formData != null && !formData.getFormProperties().isEmpty()) { + result = tasks.get(0).getId(); + } + } catch (ActivitiException e) { + LOG.warn("Could not get task form data", e); + } + } + + return result; + } + + protected Set<String> getPerformedTasks(final User user) { + final Set<String> result = new HashSet<>(); + + for (HistoricActivityInstance task : engine.getHistoryService().createHistoricActivityInstanceQuery(). + executionId(user.getWorkflowId()).list()) { + + result.add(task.getActivityId()); + } + + return result; + } + + /** + * Saves resources to be propagated and password for later - after form submission - propagation. + */ + protected void saveForFormSubmit(final User user, final String password, final PropagationByResource propByRes) { + String formTaskId = getFormTask(user); + if (formTaskId != null) { + // SYNCOPE-238: This is needed to simplify the task query in this.getForms() + engine.getTaskService().setVariableLocal(formTaskId, TASK_IS_FORM, Boolean.TRUE); + engine.getRuntimeService().setVariable(user.getWorkflowId(), PROP_BY_RESOURCE, propByRes); + if (propByRes != null) { + propByRes.clear(); + } + + if (StringUtils.isNotBlank(password)) { + engine.getRuntimeService().setVariable(user.getWorkflowId(), ENCRYPTED_PWD, encrypt(password)); + } + } + } + + @Override + public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck, + final boolean storePassword) { + + return create(userTO, disablePwdPolicyCheck, null, storePassword); + } + + @Override + public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean storePassword) { + return create(userTO, false, storePassword); + } + + @Override + public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck, + final Boolean enabled, final boolean storePassword) { + + Map<String, Object> variables = new HashMap<>(); + variables.put(WF_EXECUTOR, AuthContextUtils.getUsername()); + variables.put(USER_TO, userTO); + variables.put(ENABLED, enabled); + variables.put(STORE_PASSWORD, storePassword); + + ProcessInstance processInstance = null; + try { + processInstance = engine.getRuntimeService().startProcessInstanceByKey(WF_PROCESS_ID, variables); + } catch (ActivitiException e) { + throwException(e, "While starting " + WF_PROCESS_ID + " instance"); + } + + User user = engine.getRuntimeService().getVariable(processInstance.getProcessInstanceId(), USER, User.class); + + Boolean updatedEnabled = + engine.getRuntimeService().getVariable(processInstance.getProcessInstanceId(), ENABLED, Boolean.class); + if (updatedEnabled != null) { + user.setSuspended(!updatedEnabled); + } + + // this will make UserValidator not to consider password policies at all + if (disablePwdPolicyCheck) { + user.removeClearPassword(); + } + + updateStatus(user); + user = userDAO.save(user); + + Boolean propagateEnable = engine.getRuntimeService().getVariable( + processInstance.getProcessInstanceId(), PROPAGATE_ENABLE, Boolean.class); + if (propagateEnable == null) { + propagateEnable = enabled; + } + + PropagationByResource propByRes = new PropagationByResource(); + propByRes.set(ResourceOperation.CREATE, userDAO.findAllResourceNames(user)); + + saveForFormSubmit(user, userTO.getPassword(), propByRes); + + return new WorkflowResult<Pair<Long, Boolean>>( + new ImmutablePair<>(user.getKey(), propagateEnable), propByRes, getPerformedTasks(user)); + } + + protected Set<String> doExecuteTask(final User user, final String task, final Map<String, Object> moreVariables) { + Set<String> preTasks = getPerformedTasks(user); + + Map<String, Object> variables = new HashMap<>(); + variables.put(WF_EXECUTOR, AuthContextUtils.getUsername()); + variables.put(TASK, task); + + // using BeanUtils to access all user's properties and trigger lazy loading - we are about to + // serialize a User instance for availability within workflow tasks, and this breaks transactions + BeanUtils.copyProperties(user, entityFactory.newEntity(User.class)); + variables.put(USER, user); + + if (moreVariables != null && !moreVariables.isEmpty()) { + variables.putAll(moreVariables); + } + + if (StringUtils.isBlank(user.getWorkflowId())) { + throw new WorkflowException(new NotFoundException("Empty workflow id for " + user)); + } + + List<Task> tasks = engine.getTaskService().createTaskQuery().processInstanceId(user.getWorkflowId()).list(); + if (tasks.size() == 1) { + try { + engine.getTaskService().complete(tasks.get(0).getId(), variables); + } catch (ActivitiException e) { + throwException(e, "While completing task '" + tasks.get(0).getName() + "' for " + user); + } + } else { + LOG.warn("Expected a single task, found {}", tasks.size()); + } + + Set<String> postTasks = getPerformedTasks(user); + postTasks.removeAll(preTasks); + postTasks.add(task); + return postTasks; + } + + @Override + protected WorkflowResult<Long> doActivate(final User user, final String token) { + Set<String> tasks = doExecuteTask(user, "activate", Collections.singletonMap(TOKEN, (Object) token)); + + updateStatus(user); + User updated = userDAO.save(user); + + return new WorkflowResult<>(updated.getKey(), null, tasks); + } + + @Override + protected WorkflowResult<Pair<UserPatch, Boolean>> doUpdate(final User user, final UserPatch userPatch) { + Set<String> tasks = doExecuteTask(user, "update", Collections.singletonMap(USER_PATCH, (Object) userPatch)); + + updateStatus(user); + User updated = userDAO.save(user); + + PropagationByResource propByRes = engine.getRuntimeService().getVariable( + user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class); + UserPatch updatedPatch = engine.getRuntimeService().getVariable( + user.getWorkflowId(), USER_PATCH, UserPatch.class); + + saveForFormSubmit( + updated, updatedPatch.getPassword() == null ? null : updatedPatch.getPassword().getValue(), propByRes); + + Boolean propagateEnable = engine.getRuntimeService().getVariable( + user.getWorkflowId(), PROPAGATE_ENABLE, Boolean.class); + + return new WorkflowResult<Pair<UserPatch, Boolean>>( + new ImmutablePair<>(updatedPatch, propagateEnable), propByRes, tasks); + } + + @Override + protected WorkflowResult<Long> doSuspend(final User user) { + Set<String> performedTasks = doExecuteTask(user, "suspend", null); + updateStatus(user); + User updated = userDAO.save(user); + + return new WorkflowResult<>(updated.getKey(), null, performedTasks); + } + + @Override + protected WorkflowResult<Long> doReactivate(final User user) { + Set<String> performedTasks = doExecuteTask(user, "reactivate", null); + updateStatus(user); + + User updated = userDAO.save(user); + + return new WorkflowResult<>(updated.getKey(), null, performedTasks); + } + + @Override + protected void doRequestPasswordReset(final User user) { + Map<String, Object> variables = new HashMap<>(2); + variables.put(USER_TO, userDataBinder.getUserTO(user, true)); + variables.put(EVENT, "requestPasswordReset"); + + doExecuteTask(user, "requestPasswordReset", variables); + userDAO.save(user); + } + + @Override - protected void doConfirmPasswordReset(final User user, final String token, final String password) { ++ protected WorkflowResult<Pair<UserPatch, Boolean>> doConfirmPasswordReset( ++ final User user, final String token, final String password) { ++ + Map<String, Object> variables = new HashMap<>(4); + variables.put(TOKEN, token); + variables.put(PASSWORD, password); + variables.put(USER_TO, userDataBinder.getUserTO(user, true)); + variables.put(EVENT, "confirmPasswordReset"); + - doExecuteTask(user, "confirmPasswordReset", variables); ++ Set<String> tasks = doExecuteTask(user, "confirmPasswordReset", variables); ++ + userDAO.save(user); ++ ++ PropagationByResource propByRes = engine.getRuntimeService().getVariable( ++ user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class); ++ UserPatch updatedPatch = engine.getRuntimeService().getVariable( ++ user.getWorkflowId(), USER_PATCH, UserPatch.class); ++ Boolean propagateEnable = engine.getRuntimeService().getVariable( ++ user.getWorkflowId(), PROPAGATE_ENABLE, Boolean.class); ++ ++ return new WorkflowResult<Pair<UserPatch, Boolean>>( ++ new ImmutablePair<>(updatedPatch, propagateEnable), propByRes, tasks); + } + + @Override + protected void doDelete(final User user) { + doExecuteTask(user, "delete", null); + + PropagationByResource propByRes = new PropagationByResource(); + propByRes.set(ResourceOperation.DELETE, userDAO.findAllResourceNames(user)); + + saveForFormSubmit(user, null, propByRes); + + if (engine.getRuntimeService().createProcessInstanceQuery(). + processInstanceId(user.getWorkflowId()).active().list().isEmpty()) { + + userDAO.delete(user.getKey()); + + if (!engine.getHistoryService().createHistoricProcessInstanceQuery(). + processInstanceId(user.getWorkflowId()).list().isEmpty()) { + + engine.getHistoryService().deleteHistoricProcessInstance(user.getWorkflowId()); + } + } else { + updateStatus(user); + userDAO.save(user); + } + } + + @Override + public WorkflowResult<Long> execute(final UserTO userTO, final String taskId) { + User user = userDAO.authFind(userTO.getKey()); + + final Map<String, Object> variables = new HashMap<>(); + variables.put(USER_TO, userTO); + + Set<String> performedTasks = doExecuteTask(user, taskId, variables); + updateStatus(user); + User updated = userDAO.save(user); + + return new WorkflowResult<>(updated.getKey(), null, performedTasks); + } + + protected ProcessDefinition getProcessDefinition() { + try { + return engine.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey( + ActivitiUserWorkflowAdapter.WF_PROCESS_ID).latestVersion().singleResult(); + } catch (ActivitiException e) { + throw new WorkflowException("While accessing process " + ActivitiUserWorkflowAdapter.WF_PROCESS_ID, e); + } + + } + + protected Model getModel(final ProcessDefinition procDef) { + try { + Model model = engine.getRepositoryService().createModelQuery(). + deploymentId(procDef.getDeploymentId()).singleResult(); + if (model == null) { + throw new NotFoundException("Could not find Model for deployment " + procDef.getDeploymentId()); + } + return model; + } catch (Exception e) { + throw new WorkflowException("While accessing process " + ActivitiUserWorkflowAdapter.WF_PROCESS_ID, e); + } + } + + protected void exportProcessResource(final String resourceName, final OutputStream os) { + ProcessDefinition procDef = getProcessDefinition(); + + InputStream procDefIS = engine.getRepositoryService().getResourceAsStream(procDef.getDeploymentId(), + resourceName); + try { + IOUtils.copy(procDefIS, os); + } catch (IOException e) { + LOG.error("While exporting workflow definition {}", procDef.getKey(), e); + } finally { + IOUtils.closeQuietly(procDefIS); + } + } + + protected void exportProcessModel(final OutputStream os) { + Model model = getModel(getProcessDefinition()); + + ObjectMapper objectMapper = new ObjectMapper(); + try { + ObjectNode modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo()); + modelNode.put(ModelDataJsonConstants.MODEL_ID, model.getId()); + modelNode.replace(MODEL_DATA_JSON_MODEL, + objectMapper.readTree(engine.getRepositoryService().getModelEditorSource(model.getId()))); + + os.write(modelNode.toString().getBytes()); + } catch (IOException e) { + LOG.error("While exporting workflow definition {}", model.getId(), e); + } + } + + @Override + public void exportDefinition(final WorkflowDefinitionFormat format, final OutputStream os) { + switch (format) { + case JSON: + exportProcessModel(os); + break; + + case XML: + default: + exportProcessResource(WF_PROCESS_RESOURCE, os); + } + } + + @Override + public void exportDiagram(final OutputStream os) { + exportProcessResource(WF_DGRM_RESOURCE, os); + } + + @Override + public void importDefinition(final WorkflowDefinitionFormat format, final String definition) { + Model model = getModel(getProcessDefinition()); + switch (format) { + case JSON: + JsonNode definitionNode; + try { + definitionNode = new ObjectMapper().readTree(definition); + if (definitionNode.has(MODEL_DATA_JSON_MODEL)) { + definitionNode = definitionNode.get(MODEL_DATA_JSON_MODEL); + } + if (!definitionNode.has(BpmnJsonConverter.EDITOR_CHILD_SHAPES)) { + throw new IllegalArgumentException( + "Could not find JSON node " + BpmnJsonConverter.EDITOR_CHILD_SHAPES); + } + + BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(definitionNode); + ActivitiImportUtils.fromXML(engine, new BpmnXMLConverter().convertToXML(bpmnModel)); + } catch (Exception e) { + throw new WorkflowException("While updating process " + + ActivitiUserWorkflowAdapter.WF_PROCESS_RESOURCE, e); + } + + ActivitiImportUtils.fromJSON( + engine, definitionNode.toString().getBytes(), getProcessDefinition(), model); + break; + + case XML: + default: + ActivitiImportUtils.fromXML(engine, definition.getBytes()); + + ActivitiImportUtils.fromJSON(engine, getProcessDefinition(), model); + } + } + + protected WorkflowFormPropertyType fromActivitiFormType(final FormType activitiFormType) { + WorkflowFormPropertyType result = WorkflowFormPropertyType.String; + + if ("string".equals(activitiFormType.getName())) { + result = WorkflowFormPropertyType.String; + } + if ("long".equals(activitiFormType.getName())) { + result = WorkflowFormPropertyType.Long; + } + if ("enum".equals(activitiFormType.getName())) { + result = WorkflowFormPropertyType.Enum; + } + if ("date".equals(activitiFormType.getName())) { + result = WorkflowFormPropertyType.Date; + } + if ("boolean".equals(activitiFormType.getName())) { + result = WorkflowFormPropertyType.Boolean; + } + + return result; + } + + protected WorkflowFormTO getFormTO(final Task task) { + return getFormTO(task, engine.getFormService().getTaskFormData(task.getId())); + } + + protected WorkflowFormTO getFormTO(final Task task, final TaskFormData fd) { + final WorkflowFormTO formTO = + getFormTO(task.getProcessInstanceId(), task.getId(), fd.getFormKey(), fd.getFormProperties()); + + BeanUtils.copyProperties(task, formTO); + return formTO; + } + + protected WorkflowFormTO getFormTO(final HistoricTaskInstance task) { + final List<HistoricFormPropertyEntity> props = new ArrayList<>(); + + for (HistoricDetail historicDetail + : engine.getHistoryService().createHistoricDetailQuery().taskId(task.getId()).list()) { + + if (historicDetail instanceof HistoricFormPropertyEntity) { + props.add((HistoricFormPropertyEntity) historicDetail); + } + } + + final WorkflowFormTO formTO = getHistoricFormTO( + task.getProcessInstanceId(), task.getId(), task.getFormKey(), props); + BeanUtils.copyProperties(task, formTO); + + final HistoricActivityInstance historicActivityInstance = engine.getHistoryService(). + createHistoricActivityInstanceQuery(). + executionId(task.getExecutionId()).activityType("userTask").activityName(task.getName()).singleResult(); + + if (historicActivityInstance != null) { + formTO.setCreateTime(historicActivityInstance.getStartTime()); + formTO.setDueDate(historicActivityInstance.getEndTime()); + } + + return formTO; + } + + protected WorkflowFormTO getHistoricFormTO( + final String processInstanceId, + final String taskId, + final String formKey, + final List<HistoricFormPropertyEntity> props) { + + WorkflowFormTO formTO = new WorkflowFormTO(); + + User user = userDAO.findByWorkflowId(processInstanceId); + if (user == null) { + throw new NotFoundException("User with workflow id " + processInstanceId); + } + formTO.setUserKey(user.getKey()); + + formTO.setTaskId(taskId); + formTO.setKey(formKey); + + for (HistoricFormPropertyEntity prop : props) { + WorkflowFormPropertyTO propertyTO = new WorkflowFormPropertyTO(); + propertyTO.setId(prop.getPropertyId()); + propertyTO.setName(prop.getPropertyId()); + propertyTO.setValue(prop.getPropertyValue()); + formTO.addProperty(propertyTO); + } + + return formTO; + } + + @SuppressWarnings("unchecked") + protected WorkflowFormTO getFormTO( + final String processInstanceId, + final String taskId, + final String formKey, + final List<FormProperty> properties) { + + WorkflowFormTO formTO = new WorkflowFormTO(); + + User user = userDAO.findByWorkflowId(processInstanceId); + if (user == null) { + throw new NotFoundException("User with workflow id " + processInstanceId); + } + formTO.setUserKey(user.getKey()); + + formTO.setTaskId(taskId); + formTO.setKey(formKey); + + for (FormProperty fProp : properties) { + WorkflowFormPropertyTO propertyTO = new WorkflowFormPropertyTO(); + BeanUtils.copyProperties(fProp, propertyTO, PROPERTY_IGNORE_PROPS); + propertyTO.setType(fromActivitiFormType(fProp.getType())); + + if (propertyTO.getType() == WorkflowFormPropertyType.Date) { + propertyTO.setDatePattern((String) fProp.getType().getInformation("datePattern")); + } + if (propertyTO.getType() == WorkflowFormPropertyType.Enum) { + propertyTO.getEnumValues().putAll((Map<String, String>) fProp.getType().getInformation("values")); + } + + formTO.addProperty(propertyTO); + } + + return formTO; + } + + @Transactional(readOnly = true) + @Override + public List<WorkflowFormTO> getForms() { + List<WorkflowFormTO> forms = new ArrayList<>(); + + String authUser = AuthContextUtils.getUsername(); + if (adminUser.equals(authUser)) { + forms.addAll(getForms(engine.getTaskService().createTaskQuery(). + taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE))); + } else { + User user = userDAO.find(authUser); + if (user == null) { + throw new NotFoundException("Syncope User " + authUser); + } + + forms.addAll(getForms(engine.getTaskService().createTaskQuery(). + taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE). + taskCandidateOrAssigned(user.getKey().toString()))); + + List<String> candidateGroups = new ArrayList<>(); + for (Long groupId : userDAO.findAllGroupKeys(user)) { + candidateGroups.add(groupId.toString()); + } + if (!candidateGroups.isEmpty()) { + forms.addAll(getForms(engine.getTaskService().createTaskQuery(). + taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE). + taskCandidateGroupIn(candidateGroups))); + } + } + + return forms; + } + + @Override + public List<WorkflowFormTO> getForms(final String workflowId, final String name) { + List<WorkflowFormTO> forms = getForms( + engine.getTaskService().createTaskQuery().processInstanceId(workflowId).taskName(name). + taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE)); + + forms.addAll(getForms(engine.getHistoryService().createHistoricTaskInstanceQuery().taskName(name). + taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE))); + + return forms; + } + + protected <T extends Query<?, ?>, U extends Object> List<WorkflowFormTO> getForms(final Query<T, U> query) { + List<WorkflowFormTO> forms = new ArrayList<>(); + + for (U obj : query.list()) { + try { + if (obj instanceof HistoricTaskInstance) { + forms.add(getFormTO((HistoricTaskInstance) obj)); + } else if (obj instanceof Task) { + forms.add(getFormTO((Task) obj)); + } else { + throw new ActivitiException( + "Failure retrieving form", new IllegalArgumentException("Invalid task type")); + } + } catch (ActivitiException e) { + LOG.debug("No form found for task {}", obj, e); + } + } + + return forms; + } + + @Override + public WorkflowFormTO getForm(final String workflowId) { + Task task; + try { + task = engine.getTaskService().createTaskQuery().processInstanceId(workflowId).singleResult(); + } catch (ActivitiException e) { + throw new WorkflowException("While reading form for workflow instance " + workflowId, e); + } + + TaskFormData formData; + try { + formData = engine.getFormService().getTaskFormData(task.getId()); + } catch (ActivitiException e) { + LOG.debug("No form found for task {}", task.getId(), e); + formData = null; + } + + WorkflowFormTO result = null; + if (formData != null && !formData.getFormProperties().isEmpty()) { + result = getFormTO(task); + } + + return result; + } + + protected Pair<Task, TaskFormData> checkTask(final String taskId, final String authUser) { + Task task; + try { + task = engine.getTaskService().createTaskQuery().taskId(taskId).singleResult(); + } catch (ActivitiException e) { + throw new NotFoundException("Activiti Task " + taskId, e); + } + + TaskFormData formData; + try { + formData = engine.getFormService().getTaskFormData(task.getId()); + } catch (ActivitiException e) { + throw new NotFoundException("Form for Activiti Task " + taskId, e); + } + + if (!adminUser.equals(authUser)) { + User user = userDAO.find(authUser); + if (user == null) { + throw new NotFoundException("Syncope User " + authUser); + } + } + + return new ImmutablePair<>(task, formData); + } + + @Override + public WorkflowFormTO claimForm(final String taskId) { + String authUser = AuthContextUtils.getUsername(); + Pair<Task, TaskFormData> checked = checkTask(taskId, authUser); + + if (!adminUser.equals(authUser)) { + List<Task> tasksForUser = engine.getTaskService().createTaskQuery().taskId(taskId).taskCandidateUser( + authUser).list(); + if (tasksForUser.isEmpty()) { + throw new WorkflowException( + new IllegalArgumentException(authUser + " is not candidate for task " + taskId)); + } + } + + Task task; + try { + engine.getTaskService().setOwner(taskId, authUser); + task = engine.getTaskService().createTaskQuery().taskId(taskId).singleResult(); + } catch (ActivitiException e) { + throw new WorkflowException("While reading task " + taskId, e); + } + + return getFormTO(task, checked.getValue()); + } + + @Override + public WorkflowResult<UserPatch> submitForm(final WorkflowFormTO form) { + String authUser = AuthContextUtils.getUsername(); + Pair<Task, TaskFormData> checked = checkTask(form.getTaskId(), authUser); + + if (!checked.getKey().getOwner().equals(authUser)) { + throw new WorkflowException(new IllegalArgumentException("Task " + form.getTaskId() + " assigned to " + + checked.getKey().getOwner() + " but submitted by " + authUser)); + } + + User user = userDAO.findByWorkflowId(checked.getKey().getProcessInstanceId()); + if (user == null) { + throw new NotFoundException("User with workflow id " + checked.getKey().getProcessInstanceId()); + } + + Set<String> preTasks = getPerformedTasks(user); + try { + engine.getFormService().submitTaskFormData(form.getTaskId(), form.getPropertiesForSubmit()); + engine.getRuntimeService().setVariable(user.getWorkflowId(), FORM_SUBMITTER, authUser); + } catch (ActivitiException e) { + throwException(e, "While submitting form for task " + form.getTaskId()); + } + + Set<String> postTasks = getPerformedTasks(user); + postTasks.removeAll(preTasks); + postTasks.add(form.getTaskId()); + + updateStatus(user); + User updated = userDAO.save(user); + + // see if there is any propagation to be done + PropagationByResource propByRes = engine.getRuntimeService().getVariable( + user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class); + + // fetch - if available - the encrypted password + String clearPassword = null; + String encryptedPwd = engine.getRuntimeService().getVariable(user.getWorkflowId(), ENCRYPTED_PWD, String.class); + if (StringUtils.isNotBlank(encryptedPwd)) { + clearPassword = decrypt(encryptedPwd); + } + + // supports approval chains + saveForFormSubmit(user, clearPassword, propByRes); + + UserPatch userPatch = engine.getRuntimeService().getVariable(user.getWorkflowId(), USER_PATCH, UserPatch.class); + if (userPatch == null) { + userPatch = new UserPatch(); + userPatch.setKey(updated.getKey()); + userPatch.setPassword(new PasswordPatch.Builder().onSyncope(true).value(clearPassword).build()); + + if (propByRes != null) { + userPatch.getPassword().getResources().addAll(propByRes.get(ResourceOperation.CREATE)); + } + } + + return new WorkflowResult<>(userPatch, propByRes, postTasks); + } +}
http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java ---------------------------------------------------------------------- diff --cc core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java index 10e9afe,0000000..91b4bf4 mode 100644,000000..100644 --- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java +++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java @@@ -1,47 -1,0 +1,69 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.workflow.activiti.task; + ++import org.apache.syncope.common.lib.patch.PasswordPatch; ++import org.apache.syncope.common.lib.patch.UserPatch; ++import org.apache.syncope.common.lib.types.PropagationByResource; ++import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.user.User; ++import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.workflow.api.WorkflowException; +import org.apache.syncope.core.workflow.activiti.ActivitiUserWorkflowAdapter; ++import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class PasswordReset extends AbstractActivitiServiceTask { + ++ @Autowired ++ private UserDAO userDAO; ++ ++ @Autowired ++ private UserDataBinder dataBinder; ++ + @Override + protected void doExecute(final String executionId) { + User user = engine.getRuntimeService(). + getVariable(executionId, ActivitiUserWorkflowAdapter.USER, User.class); + String token = engine.getRuntimeService(). + getVariable(executionId, ActivitiUserWorkflowAdapter.TOKEN, String.class); + String password = engine.getRuntimeService(). + getVariable(executionId, ActivitiUserWorkflowAdapter.PASSWORD, String.class); + + if (!user.checkToken(token)) { + throw new WorkflowException(new IllegalArgumentException("Wrong token: " + token + " for " + user)); + } + + user.removeToken(); - user.setPassword(password, user.getCipherAlgorithm()); ++ ++ UserPatch userPatch = new UserPatch(); ++ userPatch.setKey(user.getKey()); ++ userPatch.setPassword(new PasswordPatch.Builder(). ++ onSyncope(true).resources(userDAO.findAllResourceNames(user)).value(password).build()); ++ ++ PropagationByResource propByRes = dataBinder.update(user, userPatch); ++ ++ // report updated user and propagation by resource as result + engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER, user); ++ engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, userPatch); ++ engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.PROP_BY_RESOURCE, propByRes); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java ---------------------------------------------------------------------- diff --cc core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java index 21498ac,0000000..4448938 mode 100644,000000..100644 --- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java +++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java @@@ -1,66 -1,0 +1,49 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.workflow.activiti.task; + - import org.apache.commons.lang3.SerializationUtils; - import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.workflow.activiti.ActivitiUserWorkflowAdapter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class Update extends AbstractActivitiServiceTask { + + @Autowired + private UserDataBinder dataBinder; + + @Override + protected void doExecute(final String executionId) { + User user = engine.getRuntimeService(). + getVariable(executionId, ActivitiUserWorkflowAdapter.USER, User.class); + UserPatch userPatch = engine.getRuntimeService(). + getVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, UserPatch.class); + - // update password internally only if required - UserPatch updatedPatch = SerializationUtils.clone(userPatch); - PasswordPatch updatedPwd = updatedPatch.getPassword(); - if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().isOnSyncope()) { - updatedPatch.setPassword(null); - } - // update user - PropagationByResource propByRes = dataBinder.update(user, updatedPatch); - if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().getResources().isEmpty()) { - if (updatedPwd == null) { - updatedPwd = updatedPatch.getPassword(); - } else { - updatedPwd.getResources().addAll(updatedPatch.getPassword().getResources()); - } - } - updatedPatch.setPassword(updatedPwd); ++ PropagationByResource propByRes = dataBinder.update(user, userPatch); + + // report updated user and propagation by resource as result + engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER, user); - engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, updatedPatch); ++ engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, userPatch); + engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.PROP_BY_RESOURCE, propByRes); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java ---------------------------------------------------------------------- diff --cc core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java index 51c3e77,0000000..91a83aa mode 100644,000000..100644 --- a/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java +++ b/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java @@@ -1,135 -1,0 +1,136 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.workflow.api; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.common.lib.to.UserTO; + +/** + * Interface for calling underlying workflow implementations. + */ +public interface UserWorkflowAdapter extends WorkflowAdapter { + + /** + * Create an user. + * + * @param userTO user to be created and whether to propagate it as active + * @param storePassword whether password shall be stored into the internal storage + * @return user just created + */ + WorkflowResult<Pair<Long, Boolean>> create(UserTO userTO, boolean storePassword); + + /** + * Create an user, optionally disabling password policy check. + * + * @param userTO user to be created and whether to propagate it as active + * @param disablePwdPolicyCheck disable password policy check? + * @param storePassword whether password shall be stored into the internal storage + * @return user just created + */ + WorkflowResult<Pair<Long, Boolean>> create( + UserTO userTO, boolean disablePwdPolicyCheck, boolean storePassword); + + /** + * Create an user, optionally disabling password policy check. + * + * @param userTO user to be created and whether to propagate it as active + * @param disablePwdPolicyCheck disable password policy check? + * @param enabled specify true/false to force active/supended status + * @param storePassword whether password shall be stored into the internal storage + * @return user just created + */ + WorkflowResult<Pair<Long, Boolean>> create( + UserTO userTO, boolean disablePwdPolicyCheck, final Boolean enabled, boolean storePassword); + + /** + * Execute a task on an user. + * + * @param userTO user to be subject to task + * @param taskId to be executed + * @return user just updated + */ + WorkflowResult<Long> execute(UserTO userTO, String taskId); + + /** + * Activate an user. + * + * @param userKey user to be activated + * @param token to be verified for activation + * @return user just updated + */ + WorkflowResult<Long> activate(Long userKey, String token); + + /** + * Update an user. + * + * @param userPatch modification set to be performed + * @return user just updated and propagations to be performed + */ + WorkflowResult<Pair<UserPatch, Boolean>> update(UserPatch userPatch); + + /** + * Suspend an user. + * + * @param key to be suspended + * @return user just suspended + */ + WorkflowResult<Long> suspend(Long key); + + /** + * Suspend an user (used by internal authentication process) + * + * @param key to be suspended + * @return user just suspended and information whether to propagate suspension + */ + Pair<WorkflowResult<Long>, Boolean> internalSuspend(Long key); + + /** + * Reactivate an user. + * + * @param userKey user to be reactivated + * @return user just reactivated + */ + WorkflowResult<Long> reactivate(Long userKey); + + /** + * Request password reset for an user. + * + * @param userKey user requesting password reset + */ + void requestPasswordReset(Long userKey); + + /** + * Confirm password reset for an user. + * + * @param userKey user confirming password reset + * @param token security token + * @param password new password value ++ * @return user just updated and propagations to be performed + */ - void confirmPasswordReset(Long userKey, String token, String password); ++ WorkflowResult<Pair<UserPatch, Boolean>> confirmPasswordReset(Long userKey, String token, String password); + + /** + * Delete an user. + * + * @param userKey user to be deleted + */ + void delete(Long userKey); +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java ---------------------------------------------------------------------- diff --cc core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java index 4236cff,0000000..716090f mode 100644,000000..100644 --- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java +++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java @@@ -1,150 -1,0 +1,153 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.workflow.java; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.workflow.api.UserWorkflowAdapter; +import org.identityconnectors.common.Base64; +import org.identityconnectors.common.security.EncryptorFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { Throwable.class }) +public abstract class AbstractUserWorkflowAdapter implements UserWorkflowAdapter { + + protected static final Logger LOG = LoggerFactory.getLogger(UserWorkflowAdapter.class); + + @Autowired + protected UserDataBinder dataBinder; + + @Autowired + protected UserDAO userDAO; + + @Autowired + protected EntityFactory entityFactory; + + public static String encrypt(final String clear) { + byte[] encryptedBytes = EncryptorFactory.getInstance().getDefaultEncryptor().encrypt(clear.getBytes()); + return Base64.encode(encryptedBytes); + } + + public static String decrypt(final String crypted) { + byte[] decryptedBytes = EncryptorFactory.getInstance().getDefaultEncryptor().decrypt(Base64.decode(crypted)); + return new String(decryptedBytes); + } + + @Override + public String getPrefix() { + return null; + } + + protected abstract WorkflowResult<Long> doActivate(User user, String token); + + @Override + public WorkflowResult<Long> activate(final Long key, final String token) { + return doActivate(userDAO.authFind(key), token); + } + + protected abstract WorkflowResult<Pair<UserPatch, Boolean>> doUpdate(User user, UserPatch userPatch); + + @Override + public WorkflowResult<Pair<UserPatch, Boolean>> update(final UserPatch userPatch) { + return doUpdate(userDAO.authFind(userPatch.getKey()), userPatch); + } + + protected abstract WorkflowResult<Long> doSuspend(User user); + + @Override + public WorkflowResult<Long> suspend(final Long key) { + User user = userDAO.authFind(key); + + // set suspended flag + user.setSuspended(Boolean.TRUE); + + return doSuspend(user); + } + + @Override + public Pair<WorkflowResult<Long>, Boolean> internalSuspend(final Long key) { + User user = userDAO.authFind(key); + + Pair<WorkflowResult<Long>, Boolean> result = null; + + Pair<Boolean, Boolean> enforce = userDAO.enforcePolicies(user); + if (enforce.getKey()) { + LOG.debug("User {} {} is over the max failed logins", user.getKey(), user.getUsername()); + + // reduce failed logins number to avoid multiple request + user.setFailedLogins(user.getFailedLogins() - 1); + + // set suspended flag + user.setSuspended(Boolean.TRUE); + + result = ImmutablePair.of(doSuspend(user), enforce.getValue()); + } + + return result; + } + + protected abstract WorkflowResult<Long> doReactivate(User user); + + @Override + public WorkflowResult<Long> reactivate(final Long key) { + User user = userDAO.authFind(key); + + // reset failed logins + user.setFailedLogins(0); + + // reset suspended flag + user.setSuspended(Boolean.FALSE); + + return doReactivate(user); + } + + protected abstract void doRequestPasswordReset(User user); + + @Override + public void requestPasswordReset(final Long key) { + doRequestPasswordReset(userDAO.authFind(key)); + } + - protected abstract void doConfirmPasswordReset(User user, String token, String password); ++ protected abstract WorkflowResult<Pair<UserPatch, Boolean>> doConfirmPasswordReset( ++ User user, String token, String password); + + @Override - public void confirmPasswordReset(final Long key, final String token, final String password) { - doConfirmPasswordReset(userDAO.authFind(key), token, password); ++ public WorkflowResult<Pair<UserPatch, Boolean>> confirmPasswordReset( ++ final Long key, final String token, final String password) { ++ ++ return doConfirmPasswordReset(userDAO.authFind(key), token, password); + } + + protected abstract void doDelete(User user); + + @Override + public void delete(final Long key) { + doDelete(userDAO.authFind(key)); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java ---------------------------------------------------------------------- diff --cc core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java index 1d70394,0000000..5592eef mode 100644,000000..100644 --- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java +++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java @@@ -1,218 -1,0 +1,209 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.workflow.java; + +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; - import org.apache.commons.lang3.SerializationUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.to.WorkflowFormTO; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.dao.ConfDAO; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.workflow.api.WorkflowDefinitionFormat; +import org.apache.syncope.core.workflow.api.WorkflowException; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Simple implementation basically not involving any workflow engine. + */ +public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter { + + @Autowired + private ConfDAO confDAO; + + @Override + public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean storePassword) { + return create(userTO, false, true); + } + + @Override + public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck, + final boolean storePassword) { + + return create(userTO, disablePwdPolicyCheck, null, storePassword); + } + + @Override + public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck, + final Boolean enabled, final boolean storePassword) { + + User user = entityFactory.newEntity(User.class); + dataBinder.create(user, userTO, storePassword); + + // this will make UserValidator not to consider password policies at all + if (disablePwdPolicyCheck) { + user.removeClearPassword(); + } + + String status; + boolean propagateEnable; + if (enabled == null) { + status = "created"; + propagateEnable = true; + } else { + status = enabled + ? "active" + : "suspended"; + propagateEnable = enabled; + user.setSuspended(!enabled); + } + + user.setStatus(status); + user = userDAO.save(user); + + final PropagationByResource propByRes = new PropagationByResource(); + propByRes.set(ResourceOperation.CREATE, userDAO.findAllResourceNames(user)); + + return new WorkflowResult<Pair<Long, Boolean>>( + new ImmutablePair<>(user.getKey(), propagateEnable), propByRes, "create"); + } + + @Override + protected WorkflowResult<Long> doActivate(final User user, final String token) { + if (!user.checkToken(token)) { + throw new WorkflowException(new IllegalArgumentException("Wrong token: " + token + " for " + user)); + } + + user.removeToken(); + user.setStatus("active"); + User updated = userDAO.save(user); + + return new WorkflowResult<>(updated.getKey(), null, "activate"); + } + + @Override + protected WorkflowResult<Pair<UserPatch, Boolean>> doUpdate(final User user, final UserPatch userPatch) { - // update password internally only if required - UserPatch updatedPatch = SerializationUtils.clone(userPatch); - PasswordPatch updatedPwd = updatedPatch.getPassword(); - if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().isOnSyncope()) { - updatedPatch.setPassword(null); - } - // update User - PropagationByResource propByRes = dataBinder.update(user, updatedPatch); - if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().getResources().isEmpty()) { - if (updatedPwd == null) { - updatedPwd = updatedPatch.getPassword(); - } else { - updatedPwd.getResources().addAll(updatedPatch.getPassword().getResources()); - } - } - updatedPatch.setPassword(updatedPwd); ++ PropagationByResource propByRes = dataBinder.update(user, userPatch); + + userDAO.save(user); + + return new WorkflowResult<Pair<UserPatch, Boolean>>( - new ImmutablePair<>(updatedPatch, !user.isSuspended()), propByRes, "update"); ++ new ImmutablePair<>(userPatch, !user.isSuspended()), propByRes, "update"); + } + + @Override + protected WorkflowResult<Long> doSuspend(final User user) { + user.setStatus("suspended"); + User updated = userDAO.save(user); + + return new WorkflowResult<>(updated.getKey(), null, "suspend"); + } + + @Override + protected WorkflowResult<Long> doReactivate(final User user) { + user.setStatus("active"); + User updated = userDAO.save(user); + + return new WorkflowResult<>(updated.getKey(), null, "reactivate"); + } + + @Override + protected void doRequestPasswordReset(final User user) { + user.generateToken( + confDAO.find("token.length", "256").getValues().get(0).getLongValue().intValue(), + confDAO.find("token.expireTime", "60").getValues().get(0).getLongValue().intValue()); + userDAO.save(user); + } + + @Override - protected void doConfirmPasswordReset(final User user, final String token, final String password) { ++ protected WorkflowResult<Pair<UserPatch, Boolean>> doConfirmPasswordReset( ++ final User user, final String token, final String password) { ++ + if (!user.checkToken(token)) { + throw new WorkflowException(new IllegalArgumentException("Wrong token: " + token + " for " + user)); + } + + user.removeToken(); - user.setPassword(password, user.getCipherAlgorithm()); - userDAO.save(user); ++ ++ UserPatch userPatch = new UserPatch(); ++ userPatch.setKey(user.getKey()); ++ userPatch.setPassword(new PasswordPatch.Builder(). ++ onSyncope(true).resources(userDAO.findAllResourceNames(user)).value(password).build()); ++ ++ return doUpdate(user, userPatch); + } + + @Override + protected void doDelete(final User user) { + userDAO.delete(user); + } + + @Override + public WorkflowResult<Long> execute(final UserTO userTO, final String taskId) { + throw new WorkflowException(new UnsupportedOperationException("Not supported.")); + } + + @Override + public void exportDefinition(final WorkflowDefinitionFormat format, final OutputStream os) { + throw new WorkflowException(new UnsupportedOperationException("Not supported.")); + } + + @Override + public void exportDiagram(final OutputStream os) { + throw new WorkflowException(new UnsupportedOperationException("Not supported.")); + } + + @Override + public void importDefinition(final WorkflowDefinitionFormat format, final String definition) { + throw new WorkflowException(new UnsupportedOperationException("Not supported.")); + } + + @Override + public List<WorkflowFormTO> getForms() { + return Collections.emptyList(); + } + + @Override + public List<WorkflowFormTO> getForms(final String workflowId, final String name) { + return Collections.emptyList(); + } + + @Override + public WorkflowFormTO getForm(final String workflowId) { + return null; + } + + @Override + public WorkflowFormTO claimForm(final String taskId) { + throw new WorkflowException(new UnsupportedOperationException("Not supported.")); + } + + @Override + public WorkflowResult<UserPatch> submitForm(final WorkflowFormTO form) { + throw new WorkflowException(new UnsupportedOperationException("Not supported.")); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java ---------------------------------------------------------------------- diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java index 978388d,0000000..158cc09 mode 100644,000000..100644 --- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java +++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java @@@ -1,102 -1,0 +1,95 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.provisioning.camel.processor; + - import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.syncope.common.lib.patch.AnyObjectPatch; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.provisioning.api.VirAttrHandler; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class AnyObjectUpdateProcessor implements Processor { + + private static final Logger LOG = LoggerFactory.getLogger(UserUpdateProcessor.class); + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Autowired + protected VirAttrHandler virtAttrHandler; + + @SuppressWarnings("unchecked") + @Override + public void process(final Exchange exchange) { + WorkflowResult<Long> updated = (WorkflowResult) exchange.getIn().getBody(); + AnyObjectPatch anyObjectPatch = exchange.getProperty("anyPatch", AnyObjectPatch.class); + Set<String> excludedResources = exchange.getProperty("excludedResources", Set.class); + ++ // SYNCOPE-459: take care of user virtual attributes ... ++ PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( ++ updated.getResult(), ++ AnyTypeKind.ANY_OBJECT, ++ anyObjectPatch.getVirAttrs()); ++ if (updated.getPropByRes() == null) { ++ updated.setPropByRes(propByResVirAttr); ++ } else { ++ updated.getPropByRes().merge(propByResVirAttr); ++ } ++ + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.ANY_OBJECT, + updated.getResult(), + false, + null, + updated.getPropByRes(), + anyObjectPatch.getVirAttrs(), + excludedResources); - if (tasks.isEmpty()) { - // SYNCOPE-459: take care of user virtual attributes ... - PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( - updated.getResult(), - AnyTypeKind.ANY_OBJECT, - anyObjectPatch.getVirAttrs()); - tasks.addAll(!propByResVirAttr.isEmpty() - ? propagationManager.getUpdateTasks( - AnyTypeKind.ANY_OBJECT, - updated.getResult(), - false, - null, - updated.getPropByRes(), - anyObjectPatch.getVirAttrs(), - null) - : Collections.<PropagationTask>emptyList()); - } + + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + exchange.getOut().setBody(new ImmutablePair<>(updated.getResult(), propagationReporter.getStatuses())); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java ---------------------------------------------------------------------- diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java index ad9ed63,0000000..22868bf mode 100644,000000..100644 --- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java +++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java @@@ -1,102 -1,0 +1,95 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.provisioning.camel.processor; + - import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.syncope.common.lib.patch.GroupPatch; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.provisioning.api.VirAttrHandler; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class GroupUpdateProcessor implements Processor { + + private static final Logger LOG = LoggerFactory.getLogger(UserUpdateProcessor.class); + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Autowired + protected VirAttrHandler virtAttrHandler; + + @SuppressWarnings("unchecked") + @Override + public void process(final Exchange exchange) { + WorkflowResult<Long> updated = (WorkflowResult) exchange.getIn().getBody(); + GroupPatch groupPatch = exchange.getProperty("anyPatch", GroupPatch.class); + Set<String> excludedResources = exchange.getProperty("excludedResources", Set.class); + ++ // SYNCOPE-459: take care of user virtual attributes ... ++ PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( ++ updated.getResult(), ++ AnyTypeKind.GROUP, ++ groupPatch.getVirAttrs()); ++ if (updated.getPropByRes() == null) { ++ updated.setPropByRes(propByResVirAttr); ++ } else { ++ updated.getPropByRes().merge(propByResVirAttr); ++ } ++ + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.GROUP, + updated.getResult(), + false, + null, + updated.getPropByRes(), + groupPatch.getVirAttrs(), + excludedResources); - if (tasks.isEmpty()) { - // SYNCOPE-459: take care of user virtual attributes ... - PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( - updated.getResult(), - AnyTypeKind.GROUP, - groupPatch.getVirAttrs()); - tasks.addAll(!propByResVirAttr.isEmpty() - ? propagationManager.getUpdateTasks( - AnyTypeKind.GROUP, - updated.getResult(), - false, - null, - updated.getPropByRes(), - groupPatch.getVirAttrs(), - excludedResources) - : Collections.<PropagationTask>emptyList()); - } + + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + exchange.getOut().setBody(new ImmutablePair<>(updated.getResult(), propagationReporter.getStatuses())); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java ---------------------------------------------------------------------- diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java index 1e02187,0000000..8a19609 mode 100644,000000..100644 --- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java +++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java @@@ -1,73 -1,0 +1,65 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.provisioning.camel.processor; + +import java.util.List; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; - import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; - import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class UserConfirmPwdResetProcessor implements Processor { + + private static final Logger LOG = LoggerFactory.getLogger(UserConfirmPwdResetProcessor.class); + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Override + public void process(final Exchange exchange) { - Long key = exchange.getProperty("userKey", Long.class); ++ @SuppressWarnings("unchecked") ++ WorkflowResult<Pair<UserPatch, Boolean>> updated = (WorkflowResult) exchange.getIn().getBody(); + - UserPatch userPatch = new UserPatch(); - userPatch.setKey(key); - userPatch.setPassword(new PasswordPatch.Builder(). - value(exchange.getProperty("password", String.class)).build()); ++ List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(updated); + - List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( - new WorkflowResult<Pair<UserPatch, Boolean>>( - new ImmutablePair<UserPatch, Boolean>(userPatch, null), null, "confirmPasswordReset"), - true, null); + PropagationReporter propReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propReporter.onPrimaryResourceFailure(tasks); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java ---------------------------------------------------------------------- diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java index da6e745,0000000..068f003 mode 100644,000000..100644 --- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java +++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java @@@ -1,87 -1,0 +1,86 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.syncope.core.provisioning.camel.processor; + - import java.util.Collection; +import java.util.List; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; - import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.syncope.common.lib.patch.StatusPatch; +import org.apache.syncope.common.lib.types.AnyTypeKind; ++import org.apache.syncope.common.lib.types.PropagationByResource; ++import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.common.lib.types.StatusPatchType; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class UserStatusPropagationProcessor implements Processor { + + private static final Logger LOG = LoggerFactory.getLogger(UserStatusPropagationProcessor.class); + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Autowired + protected UserDAO userDAO; + + @SuppressWarnings("unchecked") + @Override + public void process(final Exchange exchange) { + WorkflowResult<Long> updated = (WorkflowResult) exchange.getIn().getBody(); + - Long key = exchange.getProperty("userKey", Long.class); + StatusPatch statusPatch = exchange.getProperty("statusPatch", StatusPatch.class); + - Collection<String> noPropResourceNames = CollectionUtils.removeAll( - userDAO.findAllResourceNames(userDAO.find(key)), statusPatch.getResources()); - ++ PropagationByResource propByRes = new PropagationByResource(); ++ propByRes.addAll(ResourceOperation.UPDATE, statusPatch.getResources()); + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.USER, + statusPatch.getKey(), + false, + statusPatch.getType() != StatusPatchType.SUSPEND, ++ propByRes, + null, - null, - noPropResourceNames); ++ null); ++ + PropagationReporter propReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propReporter.onPrimaryResourceFailure(tasks); + } + + exchange.getOut().setBody(new ImmutablePair<>(updated.getResult(), propReporter.getStatuses())); + } +}
