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[]

Reply via email to