Repository: syncope Updated Branches: refs/heads/master a99618d94 -> f17be3cf9
[SYNCOPE-880] Identity Recertification Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/2191c595 Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/2191c595 Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/2191c595 Branch: refs/heads/master Commit: 2191c595f5a7a42e26e7658c80d959f0cb9af330 Parents: a99618d Author: Nicola Scendoni <[email protected]> Authored: Thu Jun 23 12:45:11 2016 +0200 Committer: Francesco Chicchiriccò <[email protected]> Committed: Mon Sep 5 12:10:28 2016 +0200 ---------------------------------------------------------------------- .../core/persistence/api/entity/user/User.java | 8 ++ .../persistence/jpa/entity/user/JPAUser.java | 30 ++++++ .../main/resources/domains/MasterContent.xml | 3 + .../persistence/jpa/inner/PlainSchemaTest.java | 2 +- .../core/persistence/jpa/inner/TaskTest.java | 2 +- .../test/resources/domains/MasterContent.xml | 13 ++- .../java/job/IdentityRecertification.java | 105 +++++++++++++++++++ .../activiti/ActivitiUserWorkflowAdapter.java | 20 +++- .../core/workflow/activiti/task/Recertify.java | 59 +++++++++++ .../src/main/resources/userWorkflow.bpmn20.xml | 57 +++++++++- .../core/workflow/api/UserWorkflowAdapter.java | 5 + .../java/DefaultUserWorkflowAdapter.java | 5 + .../src/main/resources/userWorkflow.bpmn20.xml | 55 +++++++++- .../syncope/fit/core/RecertificationITCase.java | 51 +++++++++ pom.xml | 10 ++ .../configurationparameters.adoc | 4 +- .../identityrecertification.adoc | 50 +++++++++ .../systemadministration.adoc | 2 + 18 files changed, 473 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java ---------------------------------------------------------------------- diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java index fa81fd1..d3259e7 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java @@ -96,4 +96,12 @@ public interface User extends List<? extends Role> getRoles(); + String getLastRecertificator(); + + void setLastRecertificator(String lastRecertificator); + + Date getLastRecertification(); + + void setLastRecertification(Date lastRecertificion); + } http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java index 7969824..1cb4d86 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java @@ -136,6 +136,7 @@ public class JPAUser @Temporal(TemporalType.TIMESTAMP) private Date lastLoginDate; + /** * Change password date. */ @@ -184,6 +185,14 @@ public class JPAUser @Column(nullable = true) private String securityAnswer; + @Column(nullable = true) + private String lastRecertificator; + + @Column(nullable = true) + @Temporal(TemporalType.TIMESTAMP) + private Date lastRecertification; + + @Override public AnyType getType() { return ApplicationContextProvider.getBeanFactory().getBean(AnyTypeDAO.class).findUser(); @@ -489,4 +498,25 @@ public class JPAUser public List<? extends UMembership> getMemberships() { return memberships; } + + @Override + public String getLastRecertificator() { + return lastRecertificator; + } + + @Override + public void setLastRecertificator(final String lastRecertificator) { + this.lastRecertificator = lastRecertificator; + } + + @Override + public Date getLastRecertification() { + return lastRecertification; + } + + @Override + public void setLastRecertification(final Date lastRecertification) { + this.lastRecertification = lastRecertification; + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/persistence-jpa/src/main/resources/domains/MasterContent.xml ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml index c534a44..e730aed 100644 --- a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml +++ b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml @@ -138,6 +138,9 @@ under the License. <PlainSchema id="email" type="String" anyTypeClass_id="BaseUser" mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0" validatorClass="org.apache.syncope.core.persistence.jpa.attrvalue.validation.EmailAddressValidator"/> + + <Task DTYPE="SchedTask" id="e95555d2-1b09-42c8-b25b-f4c4ec598989" name="Identity Recertification Task" active="1" + jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.IdentityRecertification" cronExpression="0 0 0 0 0 0"/> <!-- Password reset notifications --> <MailTemplate id="requestPasswordReset" http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java index 722eb5f..918f9fc 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java @@ -47,7 +47,7 @@ public class PlainSchemaTest extends AbstractTest { @Test public void findAll() { List<PlainSchema> schemas = plainSchemaDAO.findAll(); - assertEquals(38, schemas.size()); + assertEquals(39, schemas.size()); } @Test http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java index ca11c3f..c77b312 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java @@ -97,7 +97,7 @@ public class TaskTest extends AbstractTest { public void findAll() { assertEquals(5, taskDAO.findAll(TaskType.PROPAGATION).size()); assertEquals(1, taskDAO.findAll(TaskType.NOTIFICATION).size()); - assertEquals(1, taskDAO.findAll(TaskType.SCHEDULED).size()); + assertEquals(2, taskDAO.findAll(TaskType.SCHEDULED).size()); assertEquals(10, taskDAO.findAll(TaskType.PULL).size()); assertEquals(11, taskDAO.findAll(TaskType.PUSH).size()); } http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/persistence-jpa/src/test/resources/domains/MasterContent.xml ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml index 610c1cf..1fae96b 100644 --- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml @@ -125,6 +125,15 @@ under the License. <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f779" attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f6493a" booleanValue="0"/> + <!-- Identity Recertification interval in days --> + <SyncopeSchema id="identity.recertification.day.interval"/> + <PlainSchema id="identity.recertification.day.interval" type="Long" + mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> + <CPlainAttr id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" + owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" schema_id="identity.recertification.day.interval"/> + <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f77b" + attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" longValue="365"/> + <!-- sample policies --> <PullPolicy id="66691e96-285f-4464-bc19-e68384ea4c85" description="a pull policy" specification='{"conflictResolutionAction":"IGNORE"}'/> @@ -461,7 +470,7 @@ under the License. <SyncopeSchema id="mderToBePropagated"/> <DerSchema id="mderToBePropagated" expression="mderived_sx + '-' + mderived_dx" anyTypeClass_id="generic membership"/> - + <SyncopeSchema id="model"/> <PlainSchema id="model" type="String" anyTypeClass_id="minimal printer" mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> @@ -1052,6 +1061,8 @@ under the License. template='{"@class":"org.apache.syncope.common.lib.to.GroupTO","creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":null,"type":"GROUP","realm":null,"status":null,"name":null,"userOwner":null,"groupOwner":null,"udynMembershipCond":null,"auxClasses":[],"derAttrs":[],"virAttrs":[],"resources":[],"plainAttrs":[]}'/> <Task DTYPE="SchedTask" id="e95555d2-1b09-42c8-b25b-f4c4ec597979" name="SampleJob Task" active="1" jobDelegateClassName="org.apache.syncope.fit.core.reference.TestSampleJobDelegate" cronExpression="0 0 0 1 * ?"/> + <Task DTYPE="SchedTask" id="e95555d2-1b09-42c8-b25b-f4c4ec598989" name="Identity Recertification Task" active="1" + jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.IdentityRecertification" cronExpression="0 0 0 1 * ?"/> <Task DTYPE="PropagationTask" id="d6c2d6d3-6329-44c1-9187-f1469ead1cfa" operation="UPDATE" objectClassName="__ACCOUNT__" resource_id="ws-target-resource-nopropagation" anyTypeKind="USER" entityKey="1417acbe-cbf6-4277-9372-e75e04f97000" attributes='[{"name":"__PASSWORD__","value":[{"readOnly":false,"disposed":false,"encryptedBytes":"m9nh2US0Sa6m+cXccCq0Xw==","base64SHA1Hash":"GFJ69qfjxEOdrmt+9q+0Cw2uz60="}]},{"name":"__NAME__","value":["userId"],"nameValue":"userId"},{"name":"fullname","value":["fullname"]},{"name":"type","value":["type"]}]'/> http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java new file mode 100755 index 0000000..463495e --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java @@ -0,0 +1,105 @@ +/* + * 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.java.job; + +import java.util.Date; +import org.apache.syncope.core.persistence.api.dao.ConfDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.workflow.api.UserWorkflowAdapter; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; + +public class IdentityRecertification extends AbstractSchedTaskJobDelegate { + + @Autowired + private UserDAO userDao; + + @Autowired + private UserWorkflowAdapter engine; + + @Autowired + private ConfDAO confDAO; + + private long recertificationTimeLong = -1; + + public static final String RECERTIFICATION_TIME = "identity.recertification.day.interval"; + + @Override + protected String doExecute(final boolean dryRun) throws JobExecutionException { + + LOG.info("TestIdentityRecertification {} running [SchedTask {}]", (dryRun + ? "dry " + : ""), task.getKey()); + init(); + + if (recertificationTimeLong == -1) { + LOG.debug("Identity Recertification disabled"); + return ("IDENTITY RECERT DISABLED"); + } + + for (User u :userDao.findAll()) { + LOG.debug("Processing user: {}", u.getUsername()); + + if (u.getWorkflowId() != null && !u.getWorkflowId().equals("") + && toBeRecertified(u) && !dryRun) { + engine.requestCertify(u); + } else { + LOG.warn("Workflow for user: {} is null or empty", u.getUsername()); + } + } + + return (dryRun + ? "DRY " + : "") + "RUNNING"; + } + + public void init() { + CPlainAttr recertificationTime = confDAO.find(RECERTIFICATION_TIME); + if (recertificationTime == null || recertificationTime.getValues().get(0).getLongValue() == null) { + recertificationTimeLong = -1; + return; + } + recertificationTimeLong = recertificationTime.getValues().get(0).getLongValue() * 1000 * 60 * 60 * 24; + } + + public boolean toBeRecertified(final User user) { + + Date lastCertificationDate = user.getLastRecertification(); + + if (lastCertificationDate != null) { + if (lastCertificationDate.getTime() + recertificationTimeLong < System.currentTimeMillis()) { + LOG.debug("User: {} to be recertified", user.getUsername()); + return true; + } else { + LOG.debug("User: {} do not needs to be recertified", user.getUsername()); + return false; + } + } + return true; + } + + @Override + protected boolean hasToBeRegistered(final TaskExec execution) { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java ---------------------------------------------------------------------- diff --git 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 index e74aade..27b377f 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 @@ -195,7 +195,7 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter { * Saves resources to be propagated and password for later - after form submission - propagation. * * @param user user - * @param password pasword + * @param password password * @param propByRes current propagation actions against resources */ protected void saveForFormSubmit(final User user, final String password, final PropagationByResource propByRes) { @@ -349,11 +349,29 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter { } @Override + public WorkflowResult<String> requestCertify(final User user) { + String authUser = AuthContextUtils.getUsername(); + engine.getRuntimeService().setVariable(user.getWorkflowId(), FORM_SUBMITTER, authUser); + + LOG.debug("Executing request-certify"); + Set<String> performedTasks = doExecuteTask(user, "request-certify", null); + + PropagationByResource propByRes = engine.getRuntimeService().getVariable( + user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class); + + saveForFormSubmit( + user, null, propByRes); + + return new WorkflowResult<>(user.getKey(), null, performedTasks); + } + + @Override protected WorkflowResult<String> 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); } http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Recertify.java ---------------------------------------------------------------------- diff --git a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Recertify.java b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Recertify.java new file mode 100644 index 0000000..13908b2 --- /dev/null +++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Recertify.java @@ -0,0 +1,59 @@ +/* + * 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 java.util.Date; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.workflow.activiti.ActivitiUserWorkflowAdapter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class Recertify extends AbstractActivitiServiceTask { + + @Autowired + protected UserDAO userDAO; + + @Autowired + protected EntityFactory entityFactory; + + @Autowired + protected AnyUtilsFactory anyUtilsFactory; + + @Override + protected void doExecute(final String executionId) { + + LOG.debug("Processing Recertification {}", executionId); + User user = engine.getRuntimeService(). + getVariable(executionId, ActivitiUserWorkflowAdapter.USER, User.class); + String submitter = engine.getRuntimeService(). + getVariable(executionId, ActivitiUserWorkflowAdapter.FORM_SUBMITTER, String.class); + + LOG.debug("Saving Recertification information for user {}", user.getUsername()); + + user.setLastRecertificator(submitter); + user.setLastRecertification(new Date(System.currentTimeMillis())); + + userDAO.save(user); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/workflow-activiti/src/main/resources/userWorkflow.bpmn20.xml ---------------------------------------------------------------------- diff --git a/core/workflow-activiti/src/main/resources/userWorkflow.bpmn20.xml b/core/workflow-activiti/src/main/resources/userWorkflow.bpmn20.xml index 510ce9f..f33f958 100644 --- a/core/workflow-activiti/src/main/resources/userWorkflow.bpmn20.xml +++ b/core/workflow-activiti/src/main/resources/userWorkflow.bpmn20.xml @@ -82,12 +82,47 @@ under the License. <serviceTask id="delete" name="Delete" activiti:expression="#{delete.execute(execution.processInstanceId)}"/> <sequenceFlow id="flow99" sourceRef="delete" targetRef="theEnd"/> + <!-- Recertification tasks --> + <userTask id="RecertificationRequest" name="Recertification Request" activiti:formKey="recertify"> + <extensionElements> + <activiti:formProperty id="fullname" name="Identity" type="string" expression="${user.getPlainAttr('fullname').getUniqueValue().getStringValue()}" writable="false"/> + <activiti:formProperty id="username" name="Username" type="string" expression="${user.username}" writable="false"/> + <activiti:formProperty id="approve" name="Recertify?" type="boolean" required="true"/> + <activiti:formProperty id="rejectReason" name="Reason for not recertifying" type="string" variable="rejectReason"/> + </extensionElements> + </userTask> + <serviceTask id="recertify-task" name="Recertify" activiti:expression="#{recertify.execute(execution.processInstanceId)}"/> + <sequenceFlow id="recert-request-start-flow" sourceRef="activeGw" targetRef="RecertificationRequest"> + <conditionExpression xsi:type="tFormalExpression"><![CDATA[${task == 'request-certify'}]]></conditionExpression> + </sequenceFlow> + <exclusiveGateway id="recert-condition"/> + <sequenceFlow id="recert-flow1" sourceRef="RecertificationRequest" targetRef="recertify-task"/> + <sequenceFlow id="recert-flow2" sourceRef="recertify-task" targetRef="recert-condition"/> + <sequenceFlow id="recert-approved-flow" sourceRef="recert-condition" targetRef="active"> + <conditionExpression xsi:type="tFormalExpression"><![CDATA[${approve}]]></conditionExpression> + </sequenceFlow> + <sequenceFlow id="recert-denied-flow" sourceRef="recert-condition" targetRef="suspend"> + <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!approve}]]></conditionExpression> + </sequenceFlow> + <!-- End Recertification flow --> <endEvent id="theEnd"/> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_userWorkflow"> <bpmndi:BPMNPlane bpmnElement="userWorkflow" id="BPMNPlane_userWorkflow"> - <bpmndi:BPMNShape bpmnElement="theStart" id="BPMNShape_theStart"> + <bpmndi:BPMNShape bpmnElement="RecertificationRequest" id="BPMNShape_RecertificationRequest"> + <omgdc:Bounds height="80.0" width="100.0" x="1370.0" y="375.0"/> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape bpmnElement="recertify-task" id="BPMNShape_recertify-task"> + <omgdc:Bounds height="80.0" width="100.0" x="1230.0" y="375.0"/> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape bpmnElement="theEnd" id="BPMNShape_theEnd"> + <omgdc:Bounds height="28.0" width="28.0" x="2080.0" y="451.0"/> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape bpmnElement="recert-condition" id="BPMNShape_recert-condition"> + <omgdc:Bounds height="39.99999999999994" width="40.0" x="1178.1817939458806" y="475.4545368832992"/> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape bpmnElement="theStart" id="BPMNShape_theStart"> <omgdc:Bounds height="30.0" width="30.0" x="540.0" y="525.0"/> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="create" id="BPMNShape_create"> @@ -135,6 +170,26 @@ under the License. <bpmndi:BPMNShape bpmnElement="activate" id="BPMNShape_activate"> <omgdc:Bounds height="80.0" width="100.0" x="828.286878319943" y="500.0"/> </bpmndi:BPMNShape> + <bpmndi:BPMNEdge bpmnElement="recert-approved-flow" id="BPMNEdge_recert-approved-flow"> + <omgdi:waypoint x="1194.0489013105641" y="511.3216442479827"/> + <omgdi:waypoint x="1194.0489013105641" y="541.0"/> + <omgdi:waypoint x="1130.0" y="541.0"/> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-flow2" id="BPMNEdge_recert-flow2"> + <omgdi:waypoint x="1280.0" y="455.0"/> + <omgdi:waypoint x="1280.0" y="495.45453688329917"/> + <omgdi:waypoint x="1218.1817939458806" y="495.45453688329917"/> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-flow1" id="BPMNEdge_recert-flow1"> + <omgdi:waypoint x="1370.0" y="415.0"/> + <omgdi:waypoint x="1330.0" y="415.0"/> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-denied-flow" id="BPMNEdge_recert-denied-flow"> + <omgdi:waypoint x="1198.1817939458806" y="475.4545368832992"/> + <omgdi:waypoint x="1198.1817939458806" y="313.6363529725508"/> + <omgdi:waypoint x="1540.0" y="313.6363529725508"/> + <omgdi:waypoint x="1540.0" y="370.0"/> + </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow12" id="BPMNEdge_flow12"> <omgdi:waypoint x="1990.0" y="290.0"/> <omgdi:waypoint x="1990.0" y="261.0"/> http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java ---------------------------------------------------------------------- diff --git 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 index 91487ee..6a02d50 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 @@ -22,6 +22,7 @@ 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; +import org.apache.syncope.core.persistence.api.entity.user.User; /** * Interface for calling underlying workflow implementations. @@ -60,6 +61,10 @@ public interface UserWorkflowAdapter extends WorkflowAdapter { WorkflowResult<Pair<String, Boolean>> create( UserTO userTO, boolean disablePwdPolicyCheck, final Boolean enabled, boolean storePassword); + + WorkflowResult<String> requestCertify(final User user); + + /** * Execute a task on an user. * http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java ---------------------------------------------------------------------- diff --git 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 index 2df0b77..878fe1b 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 @@ -207,4 +207,9 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter { throw new WorkflowException(new UnsupportedOperationException("Not supported.")); } + @Override + public WorkflowResult<String> requestCertify(final User user) { + throw new UnsupportedOperationException("Not supported."); + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/fit/core-reference/src/main/resources/userWorkflow.bpmn20.xml ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/main/resources/userWorkflow.bpmn20.xml b/fit/core-reference/src/main/resources/userWorkflow.bpmn20.xml index 0c1dd97..521affe 100644 --- a/fit/core-reference/src/main/resources/userWorkflow.bpmn20.xml +++ b/fit/core-reference/src/main/resources/userWorkflow.bpmn20.xml @@ -187,6 +187,29 @@ try { <sequenceFlow id="flow8ter" sourceRef="rejectUpdate" targetRef="active"/> <serviceTask id="update" name="Update" activiti:expression="#{update.execute(execution.processInstanceId)}"/> <sequenceFlow id="flow9" sourceRef="update" targetRef="active"/> + <!-- Recertification tasks --> + <userTask id="RecertificationRequest" name="Recertification Request" activiti:candidateGroups="managingDirector" activiti:formKey="recertify"> + <extensionElements> + <activiti:formProperty id="fullname" name="Identity" type="string" expression="${user.getPlainAttr('fullname').getUniqueValue().getStringValue()}" writable="false"/> + <activiti:formProperty id="username" name="Username" type="string" expression="${user.username}" writable="false"/> + <activiti:formProperty id="approve" name="Recertify?" type="boolean" required="true"/> + <activiti:formProperty id="rejectReason" name="Reason for not recertifying" type="string" variable="rejectReason"/> + </extensionElements> + </userTask> + <serviceTask id="recertify-task" name="Recertify" activiti:expression="#{recertify.execute(execution.processInstanceId)}"/> + <sequenceFlow id="recert-request-start-flow" sourceRef="activeGw" targetRef="RecertificationRequest"> + <conditionExpression xsi:type="tFormalExpression"><![CDATA[${task == 'request-certify'}]]></conditionExpression> + </sequenceFlow> + <exclusiveGateway id="recert-condition"/> + <sequenceFlow id="recert-flow1" sourceRef="RecertificationRequest" targetRef="recertify-task"/> + <sequenceFlow id="recert-flow2" sourceRef="recertify-task" targetRef="recert-condition"/> + <sequenceFlow id="recert-approved-flow" sourceRef="recert-condition" targetRef="active"> + <conditionExpression xsi:type="tFormalExpression"><![CDATA[${approve}]]></conditionExpression> + </sequenceFlow> + <sequenceFlow id="recert-denied-flow" sourceRef="recert-condition" targetRef="suspend"> + <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!approve}]]></conditionExpression> + </sequenceFlow> + <!-- End Recertification flow --> <serviceTask id="suspend" name="Suspend" activiti:expression="#{suspend.execute(execution.processInstanceId)}"/> <sequenceFlow id="flow10" sourceRef="suspend" targetRef="suspended"/> <userTask id="suspended" name="Suspended"/> @@ -255,6 +278,15 @@ try { <bpmndi:BPMNDiagram id="BPMNDiagram_userWorkflow"> <bpmndi:BPMNPlane bpmnElement="userWorkflow" id="BPMNPlane_userWorkflow"> + <bpmndi:BPMNShape bpmnElement="recert-condition" id="BPMNShape_recert-condition"> + <omgdc:Bounds height="40.0" width="40.0" x="1439.4825360864054" y="12.176664483986166"/> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape bpmnElement="recertify-task" id="BPMNShape_recertify-task"> + <omgdc:Bounds height="80.0" width="100.0" x="1110.0" y="15.0"/> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape bpmnElement="RecertificationRequest" id="BPMNShape_RecertificationRequest"> + <omgdc:Bounds height="80.0" width="100.0" x="930.0" y="15.0"/> + </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="theStart" id="BPMNShape_theStart"> <omgdc:Bounds height="30.0" width="30.0" x="0.0" y="512.0"/> </bpmndi:BPMNShape> @@ -648,6 +680,27 @@ try { <omgdi:waypoint x="2040.0" y="388.0"/> <omgdi:waypoint x="2078.1893792531678" y="388.0"/> </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-flow2" id="BPMNEdge_recert-flow2"> + <omgdi:waypoint x="1210.0" y="51.18953815900822"/> + <omgdi:waypoint x="1440.8987892500566" y="33.59291764763734"/> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-flow1" id="BPMNEdge_recert-flow1"> + <omgdi:waypoint x="1030.0" y="55.0"/> + <omgdi:waypoint x="1110.0" y="55.0"/> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-denied-flow" id="BPMNEdge_recert-denied-flow"> + <omgdi:waypoint x="1479.438749881016" y="32.13287827859691"/> + <omgdi:waypoint x="1540.0" y="32.0"/> + <omgdi:waypoint x="1540.0" y="100.0"/> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-approved-flow" id="BPMNEdge_recert-approved-flow"> + <omgdi:waypoint x="1450.9385750878846" y="43.63270348546535"/> + <omgdi:waypoint x="1102.3741233704363" y="511.0"/> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge bpmnElement="recert-request-start-flow" id="BPMNEdge_recert-request-start-flow"> + <omgdi:waypoint x="1410.4864864864865" y="529.5135135135135"/> + <omgdi:waypoint x="1016.2886597938144" y="95.0"/> + </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> -</definitions> +</definitions> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RecertificationITCase.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RecertificationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RecertificationITCase.java new file mode 100755 index 0000000..9416d79 --- /dev/null +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RecertificationITCase.java @@ -0,0 +1,51 @@ +/* + * 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.fit.core; + +import java.util.List; +import org.apache.syncope.common.lib.to.WorkflowFormPropertyTO; +import org.apache.syncope.common.lib.to.WorkflowFormTO; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.JVM) +public class RecertificationITCase extends AbstractTaskITCase{ + + @Test + public void recertification() { + execTask(taskService, "e95555d2-1b09-42c8-b25b-f4c4ec598989", "JOB_FIRED", 10, false); + + List<WorkflowFormTO> forms = userWorkflowService.getForms(); + assertFalse(forms.isEmpty()); + for (WorkflowFormTO f : forms) { + userWorkflowService.claimForm(f.getTaskId()); + WorkflowFormPropertyTO w = f.getPropertyMap().get("approve"); + w.setValue("true"); + userWorkflowService.submitForm(f); + } + + forms = userWorkflowService.getForms(); + assertTrue(forms.isEmpty()); + + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 4978f52..b79667f 100644 --- a/pom.xml +++ b/pom.xml @@ -197,6 +197,16 @@ under the License. <url>http://people.apache.org/~anzelld/</url> </developer> <developer> + <id>nscendoni</id> + <name>Nicola Scendoni</name> + <organization>The Apache Software Foundation</organization> + <organizationUrl>http://www.apache.org/</organizationUrl> + <roles> + <role>committer</role> + </roles> + <url></url> + </developer> + <developer> <id>coheigea</id> <name>Colm O hEigeartaigh</name> <organization>Talend</organization> http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/configurationparameters.adoc ---------------------------------------------------------------------- diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/configurationparameters.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/configurationparameters.adoc index 3fb6d15..96a6ca7 100644 --- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/configurationparameters.adoc +++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/configurationparameters.adoc @@ -39,6 +39,6 @@ application) is allowed; * `authentication.statuses` - the list of <<workflow,workflow>> statuses for which users are allowed to authenticate; * `log.lastlogindate` - whether the system updates the `lastLoginDate` field of users upon authentication; * `tasks.interruptMaxRetries` - how many attempts shall be made when interrupting a running <<task,task>>; -* `return.password.value` - whether the hashed password value shall be returned when reading users. - +* `return.password.value` - whether the hashed password value shall be returned when reading users; +* `identity.recertification.day.interval` - Number of days bewteen identity recertifications. Besides this default set, new configuration parameters can be defined to support <<customization,custom>> code. http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/identityrecertification.adoc ---------------------------------------------------------------------- diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/identityrecertification.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/identityrecertification.adoc new file mode 100644 index 0000000..138ad51 --- /dev/null +++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/identityrecertification.adoc @@ -0,0 +1,50 @@ +// +// 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. +// +==== Identity Recertification + +Recertification is the process to review all the identities and access within one organization. This is a tipical countermeasure against the __privilege creep__, that is the rentention of accounts during time even when the user change his role, or even his job. Ths good practice is recomendded and often required by international or organizational standards. + +Tipically every "object" within one IDM system needs to be periodically re-certified: + +* Identities: Is the user still valid for the organization? +* Accounts: Is thie user still using this account? +* Group Membership/Entitlements: Does the user require this functionality? + +One simple scheduled job to implement Identity Recertification is provided. The class name is: __org.apache.syncope.core.provisioning.java.job.IdentityRecertification__ . +It can be used as base to implement more complex recertification processes. +It go through all the users and check if they have been recertified the past __identity.recertification.day.interval__ days. This is a global Parameter that can be set using Syncope Console or REST webservices. +The recertificaton task implemented by default set two attributes for each users: +- lastRertificator: the name of the user that re-certified the user +- lastRecertiication: the date when the re-certification is performed +This task is implement by the class: __org.apache.syncope.core.workflow.activiti.task.Recertify__ +The certificator can be defined modifying the task: "Create Recertification". Actviti is very flexible, and allows to define approver user, groups, or even to use expression language to define approvers from attributes from the user, for example the user manager. + +Example 1: Certifier users are members of managingDirector group: +===================================================================== + <userTask id="createApproval" name="Create approval" + activiti:candidateGroups="managingDirector" + activiti:formKey="createApproval"> +===================================================================== + +Example 2: Certifier user is the user manager defined in the user attribute: __lastRecertificator__ +===================================================================== + < userTask id = "createApproval" name = "Create Recertification" + activiti:candidateGroups="${user.lastRecertificator}" + activiti:formKey="createApproval"> +===================================================================== http://git-wip-us.apache.org/repos/asf/syncope/blob/2191c595/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/systemadministration.adoc ---------------------------------------------------------------------- diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/systemadministration.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/systemadministration.adoc index 016c391..e7e61d8 100644 --- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/systemadministration.adoc +++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/systemadministration.adoc @@ -70,4 +70,6 @@ include::connectorbundles.adoc[] include::emailconfiguration.adoc[] +include::identityrecertification.adoc[] + include::configurationparameters.adoc[]
