You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by fm...@apache.org on 2015/02/20 11:46:48 UTC

[1/3] syncope git commit: [SYNCOPE-646] fix on branch 1_2_X

Repository: syncope
Updated Branches:
  refs/heads/master 9cbeffd39 -> fecf301a8


[SYNCOPE-646] fix on branch 1_2_X


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/1012e616
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/1012e616
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/1012e616

Branch: refs/heads/master
Commit: 1012e616598d3ff8e42460d4a2eb8f0eeca6504e
Parents: 583e839
Author: fmartelli <fa...@gmail.com>
Authored: Fri Feb 20 10:51:59 2015 +0100
Committer: fmartelli <fa...@gmail.com>
Committed: Fri Feb 20 10:51:59 2015 +0100

----------------------------------------------------------------------
 .../propagation/impl/PropagationManager.java    |  2 +-
 .../activiti/ActivitiUserWorkflowAdapter.java   | 11 ++++++++
 .../syncope/core/rest/UserSelfTestITCase.java   |  2 +-
 .../syncope/core/rest/UserTestITCase.java       | 28 ++++++++++++++++++++
 4 files changed, 41 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/1012e616/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java b/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java
index fcd7110..4ec1964 100644
--- a/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java
+++ b/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java
@@ -305,7 +305,7 @@ public class PropagationManager {
         List<PropagationTask> tasks = new ArrayList<PropagationTask>();
         if (userMod.getPwdPropRequest() == null) {
             // a. no specific password propagation request: generate propagation tasks for any resource associated
-            tasks = getUserUpdateTaskIds(wfResult, true, null);
+            tasks = getUserUpdateTaskIds(wfResult, false, null);
         } else {
             // b. generate the propagation task list in two phases: first the ones containing password,
             // the the rest (with no password)

http://git-wip-us.apache.org/repos/asf/syncope/blob/1012e616/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/ActivitiUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/ActivitiUserWorkflowAdapter.java b/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/ActivitiUserWorkflowAdapter.java
index f6cc322..00fa956 100644
--- a/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/ActivitiUserWorkflowAdapter.java
+++ b/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/ActivitiUserWorkflowAdapter.java
@@ -65,6 +65,7 @@ import org.apache.syncope.common.types.ResourceOperation;
 import org.apache.syncope.common.types.WorkflowFormPropertyType;
 import org.apache.syncope.common.util.BeanUtils;
 import org.apache.syncope.common.SyncopeClientException;
+import org.apache.syncope.common.mod.StatusMod;
 import org.apache.syncope.core.persistence.beans.user.SyncopeUser;
 import org.apache.syncope.core.persistence.dao.NotFoundException;
 import org.apache.syncope.core.persistence.validation.attrvalue.ParsingValidationException;
@@ -879,6 +880,16 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
             userMod = new UserMod();
             userMod.setId(updated.getId());
             userMod.setPassword(clearPassword);
+
+            if (propByRes != null) {
+                final StatusMod st = new StatusMod();
+                userMod.setPwdPropRequest(st);
+
+                st.setOnSyncope(true);
+                for (String res : propByRes.get(ResourceOperation.CREATE)) {
+                    st.getResourceNames().add(res);
+                }
+            }
         }
 
         return new WorkflowResult<UserMod>(userMod, propByRes, postTasks);

http://git-wip-us.apache.org/repos/asf/syncope/blob/1012e616/core/src/test/java/org/apache/syncope/core/rest/UserSelfTestITCase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/syncope/core/rest/UserSelfTestITCase.java b/core/src/test/java/org/apache/syncope/core/rest/UserSelfTestITCase.java
index 3fa50e2..aaf5347 100644
--- a/core/src/test/java/org/apache/syncope/core/rest/UserSelfTestITCase.java
+++ b/core/src/test/java/org/apache/syncope/core/rest/UserSelfTestITCase.java
@@ -93,7 +93,7 @@ public class UserSelfTestITCase extends AbstractTest {
         membership.setRoleId(3L);
         userTO.getMemberships().add(membership);
         userTO.getResources().add(RESOURCE_NAME_TESTDB);
-
+        
         SyncopeClient anonClient = clientFactory.createAnonymous();
         userTO = anonClient.getService(UserSelfService.class).
                 create(userTO, true).

http://git-wip-us.apache.org/repos/asf/syncope/blob/1012e616/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java b/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java
index 3a91aa0..a17e75b 100644
--- a/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java
+++ b/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java
@@ -1069,6 +1069,11 @@ public class UserTestITCase extends AbstractTest {
         userMod.setId(userTO.getId());
         userMod.setPassword("123password");
         userMod.getResourcesToAdd().add(RESOURCE_NAME_TESTDB);
+        
+        final StatusMod st = new StatusMod();
+        st.setOnSyncope(false);
+        st.getResourceNames().add(RESOURCE_NAME_TESTDB);
+        userMod.setPwdPropRequest(st);
 
         userTO = updateUser(userMod);
         assertNotNull(userTO);
@@ -1558,6 +1563,11 @@ public class UserTestITCase extends AbstractTest {
         UserMod userMod = new UserMod();
         userMod.setId(userTO.getId());
         userMod.getResourcesToAdd().add(RESOURCE_NAME_WS1);
+        
+        final StatusMod st = new StatusMod();
+        st.setOnSyncope(false);
+        st.getResourceNames().add(RESOURCE_NAME_WS1);
+        userMod.setPwdPropRequest(st);        
 
         userTO = updateUser(userMod);
         assertNotNull(userTO);
@@ -1588,6 +1598,12 @@ public class UserTestITCase extends AbstractTest {
         UserMod userMod = new UserMod();
         userMod.setId(userTO.getId());
         userMod.getResourcesToAdd().add(RESOURCE_NAME_LDAP);
+
+        final StatusMod st = new StatusMod();
+        st.setOnSyncope(false);
+        st.getResourceNames().add(RESOURCE_NAME_LDAP);
+        userMod.setPwdPropRequest(st);
+        
         userTO = updateUser(userMod);
         assertNotNull(userTO);
 
@@ -2302,6 +2318,12 @@ public class UserTestITCase extends AbstractTest {
         UserMod userMod = new UserMod();
         userMod.setId(user.getId());
         userMod.getResourcesToAdd().add(RESOURCE_NAME_TESTDB);
+
+        final StatusMod st = new StatusMod();
+        st.setOnSyncope(false);
+        st.getResourceNames().add(RESOURCE_NAME_TESTDB);
+        userMod.setPwdPropRequest(st);
+        
         user = updateUser(userMod);
         assertNotNull(user);
         assertEquals(1, user.getResources().size());
@@ -2339,6 +2361,12 @@ public class UserTestITCase extends AbstractTest {
         UserMod userMod = new UserMod();
         userMod.setId(user.getId());
         userMod.getResourcesToAdd().add(RESOURCE_NAME_LDAP);
+        
+        final StatusMod st = new StatusMod();
+        st.setOnSyncope(false);
+        st.getResourceNames().add(RESOURCE_NAME_LDAP);
+        userMod.setPwdPropRequest(st);
+
         user = updateUser(userMod);
         assertNotNull(user);
         assertEquals(1, user.getResources().size());


[2/3] syncope git commit: [SYNCOPE-646] merge from 1_2_X

Posted by fm...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/fecf301a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
----------------------------------------------------------------------
diff --cc fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
index 1177ee4,0000000..9271efc
mode 100644,000000..100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
@@@ -1,2442 -1,0 +1,2470 @@@
 +/*
 + * 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.reference;
 +
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertNotEquals;
 +import static org.junit.Assert.assertNotNull;
 +import static org.junit.Assert.assertNull;
 +import static org.junit.Assert.assertTrue;
 +import static org.junit.Assert.fail;
 +
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.security.AccessControlException;
 +import java.text.SimpleDateFormat;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.Date;
 +import java.util.List;
 +import javax.naming.NamingException;
 +import javax.ws.rs.core.EntityTag;
 +import javax.ws.rs.core.Response;
 +import org.apache.commons.lang3.StringUtils;
 +import org.apache.cxf.common.util.Base64Utility;
 +import org.apache.cxf.helpers.IOUtils;
 +import org.apache.syncope.client.lib.SyncopeClient;
 +import org.apache.syncope.common.lib.AttributableOperations;
 +import org.apache.syncope.common.lib.SyncopeClientException;
 +import org.apache.syncope.common.lib.mod.AttrMod;
 +import org.apache.syncope.common.lib.mod.MembershipMod;
 +import org.apache.syncope.common.lib.mod.ResourceAssociationMod;
 +import org.apache.syncope.common.lib.mod.StatusMod;
 +import org.apache.syncope.common.lib.mod.UserMod;
 +import org.apache.syncope.common.lib.to.AttrTO;
 +import org.apache.syncope.common.lib.to.BulkAction;
 +import org.apache.syncope.common.lib.to.BulkActionResult;
 +import org.apache.syncope.common.lib.to.BulkActionResult.Status;
 +import org.apache.syncope.common.lib.to.ConnObjectTO;
 +import org.apache.syncope.common.lib.to.MappingItemTO;
 +import org.apache.syncope.common.lib.to.MappingTO;
 +import org.apache.syncope.common.lib.to.MembershipTO;
 +import org.apache.syncope.common.lib.to.PagedResult;
 +import org.apache.syncope.common.lib.to.PasswordPolicyTO;
 +import org.apache.syncope.common.lib.to.PropagationStatus;
 +import org.apache.syncope.common.lib.to.PropagationTaskTO;
 +import org.apache.syncope.common.lib.to.ResourceTO;
 +import org.apache.syncope.common.lib.to.RoleTO;
 +import org.apache.syncope.common.lib.to.UserTO;
 +import org.apache.syncope.common.lib.types.CipherAlgorithm;
 +import org.apache.syncope.common.lib.types.ClientExceptionType;
 +import org.apache.syncope.common.lib.types.MappingPurpose;
 +import org.apache.syncope.common.lib.types.PolicyType;
 +import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
 +import org.apache.syncope.common.lib.types.ResourceAssociationActionType;
 +import org.apache.syncope.common.lib.types.ResourceDeassociationActionType;
 +import org.apache.syncope.common.lib.types.SubjectType;
 +import org.apache.syncope.common.lib.types.TaskType;
 +import org.apache.syncope.common.lib.wrap.ResourceName;
 +import org.apache.syncope.common.rest.api.CollectionWrapper;
 +import org.apache.syncope.common.rest.api.Preference;
 +import org.apache.syncope.common.rest.api.RESTHeaders;
 +import org.apache.syncope.common.rest.api.service.PolicyService;
 +import org.apache.syncope.common.rest.api.service.ResourceService;
 +import org.apache.syncope.common.rest.api.service.UserSelfService;
 +import org.apache.syncope.common.rest.api.service.UserService;
 +import org.apache.syncope.core.misc.security.Encryptor;
 +import org.apache.syncope.core.provisioning.java.propagation.DBPasswordPropagationActions;
 +import org.apache.syncope.core.provisioning.java.propagation.LDAPPasswordPropagationActions;
 +import org.identityconnectors.framework.common.objects.Name;
 +import org.identityconnectors.framework.common.objects.OperationalAttributes;
 +import org.junit.Assume;
 +import org.junit.FixMethodOrder;
 +import org.junit.Test;
 +import org.junit.runners.MethodSorters;
 +import org.springframework.dao.EmptyResultDataAccessException;
 +import org.springframework.jdbc.core.JdbcTemplate;
 +
 +@FixMethodOrder(MethodSorters.JVM)
 +public class UserITCase extends AbstractITCase {
 +
 +    private String getStringAttribute(final ConnObjectTO connObjectTO, final String attrName) {
 +        return connObjectTO.getPlainAttrMap().get(attrName).getValues().get(0);
 +    }
 +
 +    private boolean getBooleanAttribute(final ConnObjectTO connObjectTO, final String attrName) {
 +        return Boolean.parseBoolean(getStringAttribute(connObjectTO, attrName));
 +    }
 +
 +    public static UserTO getUniqueSampleTO(final String email) {
 +        return getSampleTO(getUUIDString() + email);
 +    }
 +
 +    public static UserTO getSampleTO(final String email) {
 +        String uid = email;
 +        UserTO userTO = new UserTO();
 +        userTO.setPassword("password123");
 +        userTO.setUsername(uid);
 +
 +        userTO.getPlainAttrs().add(attrTO("fullname", uid));
 +        userTO.getPlainAttrs().add(attrTO("firstname", uid));
 +        userTO.getPlainAttrs().add(attrTO("surname", "surname"));
 +        userTO.getPlainAttrs().add(attrTO("type", "a type"));
 +        userTO.getPlainAttrs().add(attrTO("userId", uid));
 +        userTO.getPlainAttrs().add(attrTO("email", uid));
 +        userTO.getPlainAttrs().add(attrTO("loginDate", new SimpleDateFormat("yyyy-MM-dd").format(new Date())));
 +        userTO.getDerAttrs().add(attrTO("cn", null));
 +        userTO.getVirAttrs().add(attrTO("virtualdata", "virtualvalue"));
 +        return userTO;
 +    }
 +
 +    @Test
 +    @SuppressWarnings("unchecked")
 +    public void createUserWithNoPropagation() {
 +        // get task list
 +        PagedResult<PropagationTaskTO> tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +        assertNotNull(tasks);
 +        assertFalse(tasks.getResult().isEmpty());
 +
 +        long maxKey = tasks.getResult().iterator().next().getKey();
 +
 +        // create a new user
 +        UserTO userTO = getUniqueSampleTO("xxx@xxx.xxx");
 +
 +        userTO.setPassword("password123");
 +        userTO.getResources().add(RESOURCE_NAME_NOPROPAGATION);
 +
 +        createUser(userTO);
 +
 +        // get the new task list
 +        tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +        assertNotNull(tasks);
 +        assertFalse(tasks.getResult().isEmpty());
 +
 +        long newMaxId = tasks.getResult().iterator().next().getKey();
 +
 +        assertTrue(newMaxId > maxKey);
 +
 +        // get last task
 +        PropagationTaskTO taskTO = taskService.read(newMaxId);
 +
 +        assertNotNull(taskTO);
 +        assertTrue(taskTO.getExecutions().isEmpty());
 +    }
 +
 +    @Test
 +    public void issue172() {
 +        List<PasswordPolicyTO> policies = policyService.list(PolicyType.GLOBAL_PASSWORD);
 +        for (PasswordPolicyTO policyTO : policies) {
 +            policyService.delete(policyTO.getKey());
 +        }
 +
 +        try {
 +            UserTO userTO = getUniqueSampleTO("issue172@syncope.apache.org");
 +            createUser(userTO);
 +        } finally {
 +            for (PasswordPolicyTO policyTO : policies) {
 +                Response response = policyService.create(policyTO);
 +                PasswordPolicyTO cPolicyTO = getObject(
 +                        response.getLocation(), PolicyService.class, PasswordPolicyTO.class);
 +                assertNotNull(cPolicyTO);
 +            }
 +        }
 +    }
 +
 +    @Test
 +    public void issue186() {
 +        // 1. create an user with strict mandatory attributes only
 +        UserTO userTO = new UserTO();
 +        String userId = getUUIDString() + "issue186@syncope.apache.org";
 +        userTO.setUsername(userId);
 +        userTO.setPassword("password");
 +
 +        userTO.getPlainAttrs().add(attrTO("userId", userId));
 +        userTO.getPlainAttrs().add(attrTO("fullname", userId));
 +        userTO.getPlainAttrs().add(attrTO("surname", userId));
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertTrue(userTO.getResources().isEmpty());
 +
 +        // 2. update assigning a resource forcing mandatory constraints: must fail with RequiredValuesMissing
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("newPassword");
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_WS2);
 +
 +        try {
 +            userTO = updateUser(userMod);
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
 +        }
 +
 +        // 3. update assigning a resource NOT forcing mandatory constraints
 +        // AND primary: must fail with PropagationException
 +        userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("newPassword");
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_WS1);
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO.getPropagationStatusTOs().get(0).getFailureReason());
 +
 +        // 4. update assigning a resource NOT forcing mandatory constraints
 +        // BUT not primary: must succeed
 +        userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("newPassword123456");
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_CSV);
 +        updateUser(userMod);
 +    }
 +
 +    @Test
 +    public void enforceMandatoryCondition() {
 +        UserTO userTO = getUniqueSampleTO("enforce@apache.org");
 +        userTO.getResources().add(RESOURCE_NAME_WS2);
 +        userTO.setPassword("newPassword");
 +
 +        AttrTO type = null;
 +        for (AttrTO attr : userTO.getPlainAttrs()) {
 +            if ("type".equals(attr.getSchema())) {
 +                type = attr;
 +            }
 +        }
 +        assertNotNull(type);
 +        userTO.getPlainAttrs().remove(type);
 +
 +        try {
 +            userTO = createUser(userTO);
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
 +        }
 +
 +        userTO.getPlainAttrs().add(type);
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +    }
 +
 +    @Test
 +    public void enforceMandatoryConditionOnDerived() {
 +        ResourceTO resourceTO = resourceService.read(RESOURCE_NAME_CSV);
 +        assertNotNull(resourceTO);
 +        resourceTO.setKey("resource-csv-enforcing");
 +        resourceTO.setEnforceMandatoryCondition(true);
 +
 +        Response response = resourceService.create(resourceTO);
 +        resourceTO = getObject(response.getLocation(), ResourceService.class, ResourceTO.class);
 +        assertNotNull(resourceTO);
 +
 +        UserTO userTO = getUniqueSampleTO("syncope222@apache.org");
 +        userTO.getResources().add(resourceTO.getKey());
 +        userTO.setPassword("newPassword");
 +
 +        try {
 +            userTO = createUser(userTO);
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
 +        }
 +
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertEquals(Collections.singleton("resource-csv-enforcing"), userTO.getResources());
 +    }
 +
 +    @Test
 +    public void createUserWithDbPropagation() {
 +        UserTO userTO = getUniqueSampleTO("yyy@yyy.yyy");
 +        userTO.getResources().add(RESOURCE_NAME_TESTDB);
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertEquals(1, userTO.getPropagationStatusTOs().size());
 +        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
 +    }
 +
 +    @Test(expected = SyncopeClientException.class)
 +    public void createWithInvalidPassword() {
 +        UserTO userTO = getSampleTO("invalidpasswd@syncope.apache.org");
 +        userTO.setPassword("pass");
 +        createUser(userTO);
 +    }
 +
 +    @Test(expected = SyncopeClientException.class)
 +    public void createWithInvalidUsername() {
 +        UserTO userTO = getSampleTO("invalidusername@syncope.apache.org");
 +        userTO.setUsername("us");
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(7L);
 +
 +        userTO.getMemberships().add(membershipTO);
 +
 +        createUser(userTO);
 +    }
 +
 +    @Test(expected = SyncopeClientException.class)
 +    public void createWithInvalidPasswordByRes() {
 +        UserTO userTO = getSampleTO("invalidPwdByRes@passwd.com");
 +
 +        // configured to be minLength=16
 +        userTO.setPassword("password1");
 +        userTO.getResources().add(RESOURCE_NAME_NOPROPAGATION);
 +        createUser(userTO);
 +    }
 +
 +    @Test(expected = SyncopeClientException.class)
 +    public void createWithInvalidPasswordByRole() {
 +        UserTO userTO = getSampleTO("invalidPwdByRole@passwd.com");
 +
 +        // configured to be minLength=16
 +        userTO.setPassword("password1");
 +
 +        final MembershipTO membership = new MembershipTO();
 +        membership.setRoleId(8L);
 +
 +        userTO.getMemberships().add(membership);
 +
 +        createUser(userTO);
 +    }
 +
 +    @Test(expected = SyncopeClientException.class)
 +    public void createWithException() {
 +        UserTO newUserTO = new UserTO();
 +        newUserTO.getPlainAttrs().add(attrTO("userId", "userId@nowhere.org"));
 +        createUser(newUserTO);
 +    }
 +
 +    @Test
 +    public void create() {
 +        // get task list
 +        PagedResult<PropagationTaskTO> tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +        assertNotNull(tasks);
 +        assertFalse(tasks.getResult().isEmpty());
 +
 +        long maxKey = tasks.getResult().iterator().next().getKey();
 +        PropagationTaskTO taskTO = taskService.read(maxKey);
 +
 +        assertNotNull(taskTO);
 +        int maxTaskExecutions = taskTO.getExecutions().size();
 +
 +        UserTO userTO = getUniqueSampleTO("a.b@c.com");
 +
 +        // add a membership
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(8L);
 +        userTO.getMemberships().add(membershipTO);
 +
 +        // add an attribute with no values: must be ignored
 +        membershipTO.getPlainAttrs().add(attrTO("subscriptionDate", null));
 +
 +        // add an attribute with a non-existing schema: must be ignored
 +        AttrTO attrWithInvalidSchemaTO = attrTO("invalid schema", "a value");
 +        userTO.getPlainAttrs().add(attrWithInvalidSchemaTO);
 +
 +        // add an attribute with null value: must be ignored
 +        userTO.getPlainAttrs().add(attrTO("activationDate", null));
 +
 +        // 1. create user
 +        UserTO newUserTO = createUser(userTO);
 +
 +        assertNotNull(newUserTO);
 +
 +        // issue SYNCOPE-15
 +        assertNotNull(newUserTO.getCreationDate());
 +        assertNotNull(newUserTO.getCreator());
 +        assertNotNull(newUserTO.getLastChangeDate());
 +        assertNotNull(newUserTO.getLastModifier());
 +        assertEquals(newUserTO.getCreationDate(), newUserTO.getLastChangeDate());
 +
 +        assertFalse(newUserTO.getPlainAttrs().contains(attrWithInvalidSchemaTO));
 +
 +        // check for changePwdDate
 +        assertNotNull(newUserTO.getCreationDate());
 +
 +        // 2. check for virtual attribute value
 +        newUserTO = userService.read(newUserTO.getKey());
 +        assertNotNull(newUserTO);
 +
 +        assertNotNull(newUserTO.getVirAttrMap());
 +        assertNotNull(newUserTO.getVirAttrMap().get("virtualdata").getValues());
 +        assertFalse(newUserTO.getVirAttrMap().get("virtualdata").getValues().isEmpty());
 +        assertEquals("virtualvalue", newUserTO.getVirAttrMap().get("virtualdata").getValues().get(0));
 +
 +        // get the new task list
 +        tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +        assertNotNull(tasks);
 +        assertFalse(tasks.getResult().isEmpty());
 +
 +        long newMaxKey = tasks.getResult().iterator().next().getKey();
 +
 +        // default configuration for ws-target-resource2:
 +        // only failed executions have to be registered
 +        // --> no more tasks/executions should be added
 +        assertEquals(newMaxKey, maxKey);
 +
 +        // get last task
 +        taskTO = taskService.read(newMaxKey);
 +
 +        assertNotNull(taskTO);
 +        assertEquals(maxTaskExecutions, taskTO.getExecutions().size());
 +
 +        // 3. verify password
 +        UserSelfService userSelfService1 = clientFactory.create(
 +                newUserTO.getUsername(), "password123").getService(UserSelfService.class);
 +        try {
 +            UserTO user = userSelfService1.read();
 +            assertNotNull(user);
 +        } catch (AccessControlException e) {
 +            fail("Credentials should be valid and not cause AccessControlException");
 +        }
 +
 +        UserSelfService userSelfService2 = clientFactory.create(
 +                newUserTO.getUsername(), "passwordXX").getService(UserSelfService.class);
 +        try {
 +            userSelfService2.read();
 +            fail("Credentials are invalid, thus request should raise AccessControlException");
 +        } catch (AccessControlException e) {
 +            assertNotNull(e);
 +        }
 +
 +        // 4. try (and fail) to create another user with same (unique) values
 +        userTO = getSampleTO(userTO.getUsername());
 +        AttrTO userIdAttr = userTO.getPlainAttrMap().get("userId");
 +        userIdAttr.getValues().clear();
 +        userIdAttr.getValues().add("a.b@c.com");
 +
 +        try {
 +            createUser(userTO);
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.DataIntegrityViolation, e.getType());
 +        }
 +    }
 +
 +    @Test
 +    public void createWithRequiredValueMissing() {
 +        UserTO userTO = getSampleTO("a.b@c.it");
 +
 +        AttrTO type = userTO.getPlainAttrMap().get("type");
 +        userTO.getPlainAttrs().remove(type);
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(8L);
 +        userTO.getMemberships().add(membershipTO);
 +
 +        // 1. create user without type (mandatory by UserSchema)
 +        try {
 +            createUser(userTO);
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
 +        }
 +
 +        userTO.getPlainAttrs().add(attrTO("type", "F"));
 +
 +        AttrTO surname = userTO.getPlainAttrMap().get("surname");
 +        userTO.getPlainAttrs().remove(surname);
 +
 +        // 2. create user without surname (mandatory when type == 'F')
 +        try {
 +            createUser(userTO);
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
 +        }
 +    }
 +
 +    @Test
 +    public void delete() {
 +        try {
 +            userService.delete(0L);
 +        } catch (SyncopeClientException e) {
 +            assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
 +        }
 +
 +        UserTO userTO = getSampleTO("qqgf.z@nn.com");
 +
 +        // specify a propagation
 +        userTO.getResources().add(RESOURCE_NAME_TESTDB);
 +
 +        userTO = createUser(userTO);
 +
 +        long id = userTO.getKey();
 +
 +        userTO = deleteUser(id);
 +
 +        assertNotNull(userTO);
 +        assertEquals(id, userTO.getKey());
 +        assertTrue(userTO.getPlainAttrs().isEmpty());
 +
 +        // check for propagation result
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
 +
 +        try {
 +            userService.delete(userTO.getKey());
 +        } catch (SyncopeClientException e) {
 +            assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
 +        }
 +    }
 +
 +    @Test
 +    public void deleteByUsername() {
 +        UserTO userTO = getSampleTO("delete.by.username@apache.org");
 +
 +        // specify a propagation
 +        userTO.getResources().add(RESOURCE_NAME_TESTDB);
 +
 +        userTO = createUser(userTO);
 +
 +        long id = userTO.getKey();
 +        userTO = userService.read(id);
 +        userTO = deleteUser(userTO.getKey());
 +
 +        assertNotNull(userTO);
 +        assertEquals(id, userTO.getKey());
 +        assertTrue(userTO.getPlainAttrs().isEmpty());
 +
 +        // check for propagation result
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
 +
 +        try {
 +            userService.read(userTO.getKey());
 +        } catch (SyncopeClientException e) {
 +            assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
 +        }
 +    }
 +
 +    @Test
 +    public void list() {
 +        PagedResult<UserTO> users = userService.list();
 +        assertNotNull(users);
 +        assertFalse(users.getResult().isEmpty());
 +
 +        for (UserTO user : users.getResult()) {
 +            assertNotNull(user);
 +        }
 +    }
 +
 +    @Test
 +    public void paginatedList() {
 +        PagedResult<UserTO> users = userService.list(1, 2);
 +        assertNotNull(users);
 +        assertFalse(users.getResult().isEmpty());
 +        assertEquals(2, users.getResult().size());
 +
 +        for (UserTO user : users.getResult()) {
 +            assertNotNull(user);
 +        }
 +
 +        users = userService.list(2, 2);
 +        assertNotNull(users);
 +        assertFalse(users.getResult().isEmpty());
 +        assertEquals(2, users.getResult().size());
 +
 +        users = userService.list(100, 2);
 +        assertNotNull(users);
 +        assertTrue(users.getResult().isEmpty());
 +    }
 +
 +    @Test
 +    public void read() {
 +        UserTO userTO = userService.read(1L);
 +
 +        assertNotNull(userTO);
 +        assertNotNull(userTO.getPlainAttrs());
 +        assertFalse(userTO.getPlainAttrs().isEmpty());
 +    }
 +
 +    @Test
 +    public void readWithMailAddressAsUserName() {
 +        UserTO userTO = createUser(getUniqueSampleTO("mail@domain.org"));
 +        userTO = userService.read(userTO.getKey());
 +        assertNotNull(userTO);
 +    }
 +
 +    @Test
 +    public void updateWithouPassword() {
 +        UserTO userTO = getUniqueSampleTO("updatewithout@password.com");
 +
 +        userTO = createUser(userTO);
 +
 +        assertNotNull(userTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.getDerAttrsToRemove().add("cn");
 +
 +        userTO = updateUser(userMod);
 +
 +        assertNotNull(userTO);
 +        assertNotNull(userTO.getDerAttrMap());
 +        assertFalse(userTO.getDerAttrMap().containsKey("cn"));
 +    }
 +
 +    @Test(expected = SyncopeClientException.class)
 +    public void updateInvalidPassword() {
 +        UserTO userTO = getSampleTO("updateinvalid@password.com");
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("pass");
 +
 +        userService.update(userMod.getKey(), userMod);
 +    }
 +
 +    @Test(expected = SyncopeClientException.class)
 +    public void updateSamePassword() {
 +        UserTO userTO = getSampleTO("updatesame@password.com");
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("password123");
 +
 +        userService.update(userMod.getKey(), userMod);
 +    }
 +
 +    @Test
 +    public void update() {
 +        UserTO userTO = getUniqueSampleTO("g.h@t.com");
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(8L);
 +        membershipTO.getPlainAttrs().add(attrTO("subscriptionDate", "2009-08-18T16:33:12.203+0200"));
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO = createUser(userTO);
 +
 +        assertFalse(userTO.getDerAttrs().isEmpty());
 +        assertEquals(1, userTO.getMemberships().size());
 +
 +        MembershipMod membershipMod = new MembershipMod();
 +        membershipMod.setRole(8L);
 +        membershipMod.getPlainAttrsToUpdate().add(attrMod("subscriptionDate", "2010-08-18T16:33:12.203+0200"));
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("new2Password");
 +
 +        userMod.getPlainAttrsToRemove().add("userId");
 +        String newUserId = getUUIDString() + "t.w@spre.net";
 +        userMod.getPlainAttrsToUpdate().add(attrMod("userId", newUserId));
 +
 +        userMod.getPlainAttrsToRemove().add("fullname");
 +        String newFullName = getUUIDString() + "g.h@t.com";
 +        userMod.getPlainAttrsToUpdate().add(attrMod("fullname", newFullName));
 +
 +        userMod.getDerAttrsToAdd().add("cn");
 +        userMod.getMembershipsToAdd().add(membershipMod);
 +        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getKey());
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +
 +        // issue SYNCOPE-15
 +        assertNotNull(userTO.getCreationDate());
 +        assertNotNull(userTO.getCreator());
 +        assertNotNull(userTO.getLastChangeDate());
 +        assertNotNull(userTO.getLastModifier());
 +        assertTrue(userTO.getCreationDate().before(userTO.getLastChangeDate()));
 +
 +        assertEquals(1, userTO.getMemberships().size());
 +        assertEquals(1, userTO.getMemberships().iterator().next().getPlainAttrs().size());
 +        assertFalse(userTO.getDerAttrs().isEmpty());
 +
 +        AttrTO userIdAttr = userTO.getPlainAttrMap().get("userId");
 +        assertEquals(Collections.singletonList(newUserId), userIdAttr.getValues());
 +
 +        AttrTO fullNameAttr = userTO.getPlainAttrMap().get("fullname");
 +        assertEquals(Collections.singletonList(newFullName), fullNameAttr.getValues());
 +    }
 +
 +    @Test
 +    public void updatePasswordOnly() {
 +        int beforeTasks = taskService.list(TaskType.PROPAGATION, 1, 1).getTotalCount();
 +        assertFalse(beforeTasks <= 0);
 +
 +        UserTO userTO = getUniqueSampleTO("pwdonly@t.com");
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(8L);
 +        membershipTO.getPlainAttrs().add(attrTO("subscriptionDate", "2009-08-18T16:33:12.203+0200"));
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO = createUser(userTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("newPassword123");
 +
 +        userTO = updateUser(userMod);
 +
 +        // check for changePwdDate
 +        assertNotNull(userTO.getChangePwdDate());
 +
 +        int afterTasks = taskService.list(TaskType.PROPAGATION, 1, 1).getTotalCount();
 +        assertFalse(beforeTasks <= 0);
 +
 +        assertTrue(beforeTasks < afterTasks);
 +    }
 +
 +    @SuppressWarnings("unchecked")
 +    @Test
 +    public void verifyTaskRegistration() {
 +        // get task list
 +        PagedResult<PropagationTaskTO> tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +        assertNotNull(tasks);
 +        assertFalse(tasks.getResult().isEmpty());
 +
 +        long maxKey = tasks.getResult().iterator().next().getKey();
 +
 +        // --------------------------------------
 +        // Create operation
 +        // --------------------------------------
 +        UserTO userTO = getUniqueSampleTO("t@p.mode");
 +
 +        // add a membership
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(8L);
 +        userTO.getMemberships().add(membershipTO);
 +
 +        // 1. create user
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        // get the new task list
 +        tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +        assertNotNull(tasks);
 +        assertFalse(tasks.getResult().isEmpty());
 +
 +        long newMaxKey = tasks.getResult().iterator().next().getKey();
 +
 +        // default configuration for ws-target-resource2:
 +        // only failed executions have to be registered
 +        // --> no more tasks/executions should be added
 +        assertEquals(newMaxKey, maxKey);
 +
 +        // --------------------------------------
 +        // Update operation
 +        // --------------------------------------
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +
 +        userMod.getPlainAttrsToUpdate().add(attrMod("surname", "surname"));
 +
 +        userTO = updateUser(userMod);
 +
 +        assertNotNull(userTO);
 +
 +        // get the new task list
 +        tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +
 +        maxKey = newMaxKey;
 +        newMaxKey = tasks.getResult().iterator().next().getKey();
 +
 +        // default configuration for ws-target-resource2:
 +        // all update executions have to be registered
 +        assertTrue(newMaxKey > maxKey);
 +
 +        final PropagationTaskTO taskTO = taskService.read(newMaxKey);
 +
 +        assertNotNull(taskTO);
 +        assertEquals(1, taskTO.getExecutions().size());
 +
 +        // --------------------------------------
 +        // Delete operation
 +        // --------------------------------------
 +        userService.delete(userTO.getKey());
 +
 +        // get the new task list
 +        tasks = taskService.list(TaskType.PROPAGATION, 1, 1);
 +
 +        maxKey = newMaxKey;
 +        newMaxKey = tasks.getResult().iterator().next().getKey();
 +
 +        // default configuration for ws-target-resource2: no delete executions have to be registered
 +        // --> no more tasks/executions should be added
 +        assertEquals(newMaxKey, maxKey);
 +    }
 +
 +    @Test
 +    public void createActivate() {
 +        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers(syncopeService));
 +
 +        UserTO userTO = getUniqueSampleTO("createActivate@syncope.apache.org");
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(11L);
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO = createUser(userTO);
 +
 +        assertNotNull(userTO);
 +        assertNotNull(userTO.getToken());
 +        assertNotNull(userTO.getTokenExpireTime());
 +
 +        assertEquals("created", userTO.getStatus());
 +
 +        StatusMod statusMod = new StatusMod();
 +        statusMod.setType(StatusMod.ModType.ACTIVATE);
 +        statusMod.setToken(userTO.getToken());
 +        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
 +
 +        assertNotNull(userTO);
 +        assertNull(userTO.getToken());
 +        assertNull(userTO.getTokenExpireTime());
 +        assertEquals("active", userTO.getStatus());
 +    }
 +
 +    @Test
 +    public void suspendReactivate() {
 +        UserTO userTO = getUniqueSampleTO("suspendReactivate@syncope.apache.org");
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(7L);
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO = createUser(userTO);
 +
 +        assertNotNull(userTO);
 +        assertEquals(ActivitiDetector.isActivitiEnabledForUsers(syncopeService)
 +                ? "active"
 +                : "created", userTO.getStatus());
 +
 +        StatusMod statusMod = new StatusMod();
 +        statusMod.setType(StatusMod.ModType.SUSPEND);
 +        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
 +        assertNotNull(userTO);
 +        assertEquals("suspended", userTO.getStatus());
 +
 +        statusMod = new StatusMod();
 +        statusMod.setType(StatusMod.ModType.REACTIVATE);
 +        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
 +        assertNotNull(userTO);
 +        assertEquals("active", userTO.getStatus());
 +    }
 +
 +    @Test
 +    public void suspendReactivateOnResource() {
 +        // Assert resources are present
 +        ResourceTO dbTable = resourceService.read(RESOURCE_NAME_TESTDB);
 +        assertNotNull(dbTable);
 +        ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
 +        assertNotNull(ldap);
 +
 +        // Create user with reference to resources
 +        UserTO userTO = getUniqueSampleTO("suspreactonresource@syncope.apache.org");
 +        userTO.getMemberships().clear();
 +        userTO.getResources().clear();
 +        userTO.getResources().add(RESOURCE_NAME_TESTDB);
 +        userTO.getResources().add(RESOURCE_NAME_LDAP);
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertEquals(ActivitiDetector.isActivitiEnabledForUsers(syncopeService)
 +                ? "active"
 +                : "created", userTO.getStatus());
 +        long userId = userTO.getKey();
 +
 +        // Suspend with effect on syncope, ldap and db => user should be suspended in syncope and all resources
 +        StatusMod statusMod = new StatusMod();
 +        statusMod.setType(StatusMod.ModType.SUSPEND);
 +        statusMod.setOnSyncope(true);
 +        statusMod.getResourceNames().add(RESOURCE_NAME_TESTDB);
 +        statusMod.getResourceNames().add(RESOURCE_NAME_LDAP);
 +        userTO = userService.status(userId, statusMod).readEntity(UserTO.class);
 +        assertNotNull(userTO);
 +        assertEquals("suspended", userTO.getStatus());
 +
 +        ConnObjectTO connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userId);
 +        assertFalse(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
 +
 +        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.USER, userId);
 +        assertNotNull(connObjectTO);
 +
 +        // Suspend and reactivate only on ldap => db and syncope should still show suspended
 +        statusMod = new StatusMod();
 +        statusMod.setType(StatusMod.ModType.SUSPEND);
 +        statusMod.setOnSyncope(false);
 +        statusMod.getResourceNames().add(RESOURCE_NAME_LDAP);
 +        userService.status(userId, statusMod);
 +        statusMod.setType(StatusMod.ModType.REACTIVATE);
 +        userTO = userService.status(userId, statusMod).readEntity(UserTO.class);
 +        assertNotNull(userTO);
 +        assertEquals("suspended", userTO.getStatus());
 +
 +        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userId);
 +        assertFalse(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
 +
 +        // Reactivate on syncope and db => syncope and db should show the user as active
 +        statusMod = new StatusMod();
 +        statusMod.setType(StatusMod.ModType.REACTIVATE);
 +        statusMod.setOnSyncope(true);
 +        statusMod.getResourceNames().add(RESOURCE_NAME_TESTDB);
 +
 +        userTO = userService.status(userId, statusMod).readEntity(UserTO.class);
 +        assertNotNull(userTO);
 +        assertEquals("active", userTO.getStatus());
 +
 +        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userId);
 +        assertTrue(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
 +    }
 +
 +    public void updateMultivalueAttribute() {
 +        UserTO userTO = getSampleTO("multivalue@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        AttrTO loginDate = userTO.getPlainAttrMap().get("loginDate");
 +        assertNotNull(loginDate);
 +        assertEquals(1, loginDate.getValues().size());
 +
 +        UserMod userMod = new UserMod();
 +
 +        AttrMod loginDateMod = new AttrMod();
 +        loginDateMod.getValuesToBeAdded().add("2000-01-01");
 +
 +        userMod.setKey(userTO.getKey());
 +        userMod.getPlainAttrsToUpdate().add(loginDateMod);
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +
 +        loginDate = userTO.getPlainAttrMap().get("loginDate");
 +        assertNotNull(loginDate);
 +        assertEquals(2, loginDate.getValues().size());
 +    }
 +
 +    @Test(expected = EmptyResultDataAccessException.class)
 +    public void issue213() {
 +        UserTO userTO = getUniqueSampleTO("issue213@syncope.apache.org");
 +        userTO.getResources().add(RESOURCE_NAME_TESTDB);
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertEquals(1, userTO.getResources().size());
 +
 +        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
 +
 +        String username = jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?", String.class,
 +                userTO.getUsername());
 +
 +        assertEquals(userTO.getUsername(), username);
 +
 +        UserMod userMod = new UserMod();
 +
 +        userMod.setKey(userTO.getKey());
 +        userMod.getResourcesToRemove().add(RESOURCE_NAME_TESTDB);
 +
 +        userTO = updateUser(userMod);
 +        assertTrue(userTO.getResources().isEmpty());
 +
 +        jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?", String.class, userTO.getUsername());
 +    }
 +
 +    @Test
 +    public void issue234() {
 +        UserTO inUserTO = getUniqueSampleTO("issue234@syncope.apache.org");
 +        inUserTO.getResources().add(RESOURCE_NAME_LDAP);
 +
 +        UserTO userTO = createUser(inUserTO);
 +        assertNotNull(userTO);
 +
 +        UserMod userMod = new UserMod();
 +
 +        userMod.setKey(userTO.getKey());
 +        userMod.setUsername("1" + userTO.getUsername());
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +        assertEquals("1" + inUserTO.getUsername(), userTO.getUsername());
 +    }
 +
 +    @Test
 +    public void issue270() {
 +        // 1. create a new user without virtual attributes
 +        UserTO original = getUniqueSampleTO("issue270@syncope.apache.org");
 +        // be sure to remove all virtual attributes
 +        original.getVirAttrs().clear();
 +
 +        original = createUser(original);
 +
 +        assertNotNull(original);
 +
 +        assertTrue(original.getVirAttrs().isEmpty());
 +
 +        UserTO toBeUpdated = userService.read(original.getKey());
 +
 +        AttrTO virtual = attrTO("virtualdata", "virtualvalue");
 +        toBeUpdated.getVirAttrs().add(virtual);
 +
 +        // 2. try to update by adding a resource, but no password: must fail
 +        UserMod userMod = AttributableOperations.diff(toBeUpdated, original);
 +        assertNotNull(userMod);
 +
 +        toBeUpdated = updateUser(userMod);
 +        assertNotNull(toBeUpdated);
 +
 +        assertFalse(toBeUpdated.getVirAttrs().isEmpty());
 +        assertNotNull(toBeUpdated.getVirAttrs().get(0));
 +
 +        assertEquals(virtual.getSchema(), toBeUpdated.getVirAttrs().get(0).getSchema());
 +    }
 +
 +    @Test
 +    public final void issue280() {
 +        UserTO userTO = getUniqueSampleTO("issue280@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("123password");
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_TESTDB);
 +
++        final StatusMod st = new StatusMod();
++        st.setOnSyncope(false);
++        st.getResourceNames().add(RESOURCE_NAME_TESTDB);
++        userMod.setPwdPropRequest(st);
++
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +
 +        final List<PropagationStatus> propagations = userTO.getPropagationStatusTOs();
 +
 +        assertNotNull(propagations);
 +        assertEquals(1, propagations.size());
 +
 +        final PropagationTaskExecStatus status = propagations.get(0).getStatus();
 +        final String resource = propagations.get(0).getResource();
 +
 +        assertNotNull(status);
 +        assertEquals(RESOURCE_NAME_TESTDB, resource);
 +        assertTrue(status.isSuccessful());
 +    }
 +
 +    @Test
 +    public void issue281() {
 +        UserTO userTO = getUniqueSampleTO("issue281@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        final List<PropagationStatus> propagations = userTO.getPropagationStatusTOs();
 +
 +        assertNotNull(propagations);
 +        assertEquals(1, propagations.size());
 +
 +        final PropagationTaskExecStatus status = propagations.get(0).getStatus();
 +        final String resource = propagations.get(0).getResource();
 +
 +        assertNotNull(status);
 +        assertEquals(RESOURCE_NAME_CSV, resource);
 +        assertFalse(status.isSuccessful());
 +    }
 +
 +    @Test
 +    public void issue288() {
 +        UserTO userTO = getSampleTO("issue288@syncope.apache.org");
 +        userTO.getPlainAttrs().add(attrTO("aLong", "STRING"));
 +
 +        try {
 +            createUser(userTO);
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.InvalidValues, e.getType());
 +        }
 +    }
 +
 +    @Test
 +    public void roleAttrPropagation() {
 +        UserTO userTO = getUniqueSampleTO("checkRoleAttrPropagation@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(1L);
 +
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertNotNull(actual.getDerAttrMap().get("csvuserid"));
 +
 +        ConnObjectTO connObjectTO =
 +                resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +        assertNotNull(connObjectTO);
 +        assertEquals("sx-dx", connObjectTO.getPlainAttrMap().get("ROLE").getValues().get(0));
 +    }
 +
 +    @Test
 +    public void membershipAttrPropagation() {
 +        UserTO userTO = getUniqueSampleTO("checkMembAttrPropagation@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(1L);
 +        membershipTO.getPlainAttrs().add(attrTO("mderived_sx", "sx"));
 +        membershipTO.getPlainAttrs().add(attrTO("mderived_dx", "dx"));
 +        membershipTO.getDerAttrs().add(attrTO("mderToBePropagated", null));
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertNotNull(actual.getDerAttrMap().get("csvuserid"));
 +
 +        ConnObjectTO connObjectTO =
 +                resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +        assertNotNull(connObjectTO);
 +        assertEquals("sx-dx", connObjectTO.getPlainAttrMap().get("MEMBERSHIP").getValues().get(0));
 +    }
 +
 +    @Test
 +    public void noContent() throws IOException {
 +        SyncopeClient noContentclient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
 +        UserService noContentService = noContentclient.prefer(UserService.class, Preference.RETURN_NO_CONTENT);
 +
 +        UserTO user = getUniqueSampleTO("nocontent@syncope.apache.org");
 +
 +        Response response = noContentService.create(user, true);
 +        assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
 +        assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED));
 +        assertEquals(StringUtils.EMPTY, IOUtils.toString((InputStream) response.getEntity()));
 +
 +        user = getObject(response.getLocation(), UserService.class, UserTO.class);
 +        assertNotNull(user);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setPassword("password321");
 +
 +        response = noContentService.update(user.getKey(), userMod);
 +        assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
 +        assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED));
 +        assertEquals(StringUtils.EMPTY, IOUtils.toString((InputStream) response.getEntity()));
 +
 +        response = noContentService.delete(user.getKey());
 +        assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
 +        assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED));
 +        assertEquals(StringUtils.EMPTY, IOUtils.toString((InputStream) response.getEntity()));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE108() {
 +        UserTO userTO = getUniqueSampleTO("syncope108@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        MembershipTO memb12 = new MembershipTO();
 +        memb12.setRoleId(12L);
 +
 +        userTO.getMemberships().add(memb12);
 +
 +        MembershipTO memb13 = new MembershipTO();
 +        memb13.setRoleId(13L);
 +
 +        userTO.getMemberships().add(memb13);
 +
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertEquals(2, actual.getMemberships().size());
 +        assertEquals(1, actual.getResources().size());
 +
 +        ConnObjectTO connObjectTO =
 +                resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +        assertNotNull(connObjectTO);
 +
 +        // -----------------------------------
 +        // Remove the first membership: de-provisioning shouldn't happen
 +        // -----------------------------------
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(actual.getKey());
 +
 +        userMod.getMembershipsToRemove().add(actual.getMemberships().get(0).getKey());
 +
 +        actual = updateUser(userMod);
 +        assertNotNull(actual);
 +        assertEquals(1, actual.getMemberships().size());
 +
 +        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +        assertNotNull(connObjectTO);
 +        // -----------------------------------
 +
 +        // -----------------------------------
 +        // Remove the resource assigned directly: de-provisioning shouldn't happen
 +        // -----------------------------------
 +        userMod = new UserMod();
 +        userMod.setKey(actual.getKey());
 +
 +        userMod.getResourcesToRemove().add(actual.getResources().iterator().next());
 +
 +        actual = updateUser(userMod);
 +        assertNotNull(actual);
 +        assertEquals(1, actual.getMemberships().size());
 +        assertFalse(actual.getResources().isEmpty());
 +
 +        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +        assertNotNull(connObjectTO);
 +        // -----------------------------------
 +
 +        // -----------------------------------
 +        // Remove the first membership: de-provisioning should happen
 +        // -----------------------------------
 +        userMod = new UserMod();
 +        userMod.setKey(actual.getKey());
 +
 +        userMod.getMembershipsToRemove().add(actual.getMemberships().get(0).getKey());
 +
 +        actual = updateUser(userMod);
 +        assertNotNull(actual);
 +        assertTrue(actual.getMemberships().isEmpty());
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail("Read should not succeeed");
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.NotFound, e.getType());
 +        }
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE111() {
 +        UserTO userTO = getUniqueSampleTO("syncope111@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        MembershipTO memb12 = new MembershipTO();
 +        memb12.setRoleId(12L);
 +        memb12.getPlainAttrs().add(attrTO("postalAddress", "postalAddress"));
 +        userTO.getMemberships().add(memb12);
 +
 +        MembershipTO memb13 = new MembershipTO();
 +        memb13.setRoleId(13L);
 +        userTO.getMemberships().add(memb13);
 +
 +        userTO.getResources().add(RESOURCE_NAME_LDAP);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertEquals(2, actual.getMemberships().size());
 +
 +        ConnObjectTO connObjectTO =
 +                resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.USER, actual.getKey());
 +        assertNotNull(connObjectTO);
 +
 +        AttrTO postalAddress = connObjectTO.getPlainAttrMap().get("postalAddress");
 +        assertNotNull(postalAddress);
 +        assertEquals(1, postalAddress.getValues().size());
 +        assertEquals("postalAddress", postalAddress.getValues().get(0));
 +
 +        AttrTO title = connObjectTO.getPlainAttrMap().get("title");
 +        assertNotNull(title);
 +        assertEquals(2, title.getValues().size());
 +        assertTrue(title.getValues().contains("r12") && title.getValues().contains("r13"));
 +
 +        // -----------------------------------
 +        // Remove the first membership and check for membership attr propagation and role attr propagation
 +        // -----------------------------------
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(actual.getKey());
 +
 +        MembershipTO membershipTO = actual.getMemberships().get(0).getRoleId() == 12L
 +                ? actual.getMemberships().get(0)
 +                : actual.getMemberships().get(1);
 +
 +        userMod.getMembershipsToRemove().add(membershipTO.getKey());
 +
 +        actual = updateUser(userMod);
 +        assertNotNull(actual);
 +        assertEquals(1, actual.getMemberships().size());
 +
 +        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.USER, actual.getKey());
 +        assertNotNull(connObjectTO);
 +
 +        postalAddress = connObjectTO.getPlainAttrMap().get("postalAddress");
 +        assertTrue(postalAddress == null || postalAddress.getValues().isEmpty()
 +                || StringUtils.isNotBlank(postalAddress.getValues().get(0)));
 +
 +        title = connObjectTO.getPlainAttrMap().get("title");
 +        assertNotNull(title);
 +        assertEquals(1, title.getValues().size());
 +        assertTrue(title.getValues().contains("r13"));
 +        // -----------------------------------
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE185() {
 +        // 1. create user with LDAP resource, succesfully propagated
 +        UserTO userTO = getSampleTO("syncope185@syncope.apache.org");
 +        userTO.getVirAttrs().clear();
 +        userTO.getResources().add(RESOURCE_NAME_LDAP);
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertEquals(RESOURCE_NAME_LDAP, userTO.getPropagationStatusTOs().get(0).getResource());
 +        assertEquals(PropagationTaskExecStatus.SUCCESS, userTO.getPropagationStatusTOs().get(0).getStatus());
 +
 +        // 2. delete this user
 +        userService.delete(userTO.getKey());
 +
 +        // 3. try (and fail) to find this user on the external LDAP resource
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.USER, userTO.getKey());
 +            fail("This entry should not be present on this resource");
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.NotFound, e.getType());
 +        }
 +    }
 +
 +    @Test()
 +    public void issueSYNCOPE51() {
 +        AttrTO defaultCA = configurationService.read("password.cipher.algorithm");
 +        final String originalCAValue = defaultCA.getValues().get(0);
 +        defaultCA.getValues().set(0, "MD5");
 +        configurationService.set(defaultCA.getSchema(), defaultCA);
 +
 +        AttrTO newCA = configurationService.read(defaultCA.getSchema());
 +        assertEquals(defaultCA, newCA);
 +
 +        UserTO userTO = getSampleTO("syncope51@syncope.apache.org");
 +        userTO.setPassword("password");
 +
 +        try {
 +            createUser(userTO);
 +            fail("Create user should not succeed");
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.NotFound, e.getType());
 +            assertTrue(e.getElements().iterator().next().contains("MD5"));
 +        }
 +
 +        defaultCA.getValues().set(0, originalCAValue);
 +        configurationService.set(defaultCA.getSchema(), defaultCA);
 +
 +        AttrTO oldCA = configurationService.read(defaultCA.getSchema());
 +        assertEquals(defaultCA, oldCA);
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE267() {
 +        // ----------------------------------
 +        // create user and check virtual attribute value propagation
 +        // ----------------------------------
 +        UserTO userTO = getUniqueSampleTO("syncope267@apache.org");
 +        userTO.getResources().clear();
 +        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertEquals(RESOURCE_NAME_DBVIRATTR, userTO.getPropagationStatusTOs().get(0).getResource());
 +        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
 +
 +        ConnObjectTO connObjectTO =
 +                resourceService.getConnectorObject(RESOURCE_NAME_DBVIRATTR, SubjectType.USER, userTO.getKey());
 +        assertNotNull(connObjectTO);
 +        assertEquals("virtualvalue", connObjectTO.getPlainAttrMap().get("USERNAME").getValues().get(0));
 +        // ----------------------------------
 +
 +        userTO = userService.read(userTO.getKey());
 +
 +        assertNotNull(userTO);
 +        assertEquals(1, userTO.getVirAttrs().size());
 +        assertEquals("virtualvalue", userTO.getVirAttrs().get(0).getValues().get(0));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE266() {
 +        UserTO userTO = getUniqueSampleTO("syncope266@apache.org");
 +        userTO.getResources().clear();
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +
 +        // this resource has not a mapping for Password
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_UPDATE);
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE279() {
 +        UserTO userTO = getUniqueSampleTO("syncope279@apache.org");
 +        userTO.getResources().clear();
 +        userTO.getResources().add(RESOURCE_NAME_TIMEOUT);
 +        userTO = createUser(userTO);
 +        assertEquals(RESOURCE_NAME_TIMEOUT, userTO.getPropagationStatusTOs().get(0).getResource());
 +        assertNotNull(userTO.getPropagationStatusTOs().get(0).getFailureReason());
 +        assertEquals(PropagationTaskExecStatus.UNSUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE122() {
 +        // 1. create user on testdb and testdb2
 +        UserTO userTO = getUniqueSampleTO("syncope122@apache.org");
 +        userTO.getResources().clear();
 +
 +        userTO.getResources().add(RESOURCE_NAME_TESTDB);
 +        userTO.getResources().add(RESOURCE_NAME_TESTDB2);
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertTrue(userTO.getResources().contains(RESOURCE_NAME_TESTDB));
 +        assertTrue(userTO.getResources().contains(RESOURCE_NAME_TESTDB2));
 +
 +        final String pwdOnSyncope = userTO.getPassword();
 +
 +        ConnObjectTO userOnDb = resourceService.getConnectorObject(
 +                RESOURCE_NAME_TESTDB, SubjectType.USER, userTO.getKey());
 +        final AttrTO pwdOnTestDbAttr = userOnDb.getPlainAttrMap().get(OperationalAttributes.PASSWORD_NAME);
 +        assertNotNull(pwdOnTestDbAttr);
 +        assertNotNull(pwdOnTestDbAttr.getValues());
 +        assertFalse(pwdOnTestDbAttr.getValues().isEmpty());
 +        final String pwdOnTestDb = pwdOnTestDbAttr.getValues().iterator().next();
 +
 +        ConnObjectTO userOnDb2 = resourceService.getConnectorObject(
 +                RESOURCE_NAME_TESTDB2, SubjectType.USER, userTO.getKey());
 +        final AttrTO pwdOnTestDb2Attr = userOnDb2.getPlainAttrMap().get(OperationalAttributes.PASSWORD_NAME);
 +        assertNotNull(pwdOnTestDb2Attr);
 +        assertNotNull(pwdOnTestDb2Attr.getValues());
 +        assertFalse(pwdOnTestDb2Attr.getValues().isEmpty());
 +        final String pwdOnTestDb2 = pwdOnTestDb2Attr.getValues().iterator().next();
 +
 +        // 2. request to change password only on testdb (no Syncope, no testdb2)
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword(getUUIDString());
 +        StatusMod pwdPropRequest = new StatusMod();
 +        pwdPropRequest.setOnSyncope(false);
 +        pwdPropRequest.getResourceNames().add(RESOURCE_NAME_TESTDB);
 +        userMod.setPwdPropRequest(pwdPropRequest);
 +
 +        userTO = updateUser(userMod);
 +
 +        // 3a. Chech that only a single propagation took place
 +        assertNotNull(userTO.getPropagationStatusTOs());
 +        assertEquals(1, userTO.getPropagationStatusTOs().size());
 +        assertEquals(RESOURCE_NAME_TESTDB, userTO.getPropagationStatusTOs().iterator().next().getResource());
 +
 +        // 3b. verify that password hasn't changed on Syncope
 +        assertEquals(pwdOnSyncope, userTO.getPassword());
 +
 +        // 3c. verify that password *has* changed on testdb
 +        userOnDb = resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userTO.getKey());
 +        final AttrTO pwdOnTestDbAttrAfter = userOnDb.getPlainAttrMap().get(OperationalAttributes.PASSWORD_NAME);
 +        assertNotNull(pwdOnTestDbAttrAfter);
 +        assertNotNull(pwdOnTestDbAttrAfter.getValues());
 +        assertFalse(pwdOnTestDbAttrAfter.getValues().isEmpty());
 +        assertNotEquals(pwdOnTestDb, pwdOnTestDbAttrAfter.getValues().iterator().next());
 +
 +        // 3d. verify that password hasn't changed on testdb2
 +        userOnDb2 = resourceService.getConnectorObject(RESOURCE_NAME_TESTDB2, SubjectType.USER, userTO.getKey());
 +        final AttrTO pwdOnTestDb2AttrAfter = userOnDb2.getPlainAttrMap().get(OperationalAttributes.PASSWORD_NAME);
 +        assertNotNull(pwdOnTestDb2AttrAfter);
 +        assertNotNull(pwdOnTestDb2AttrAfter.getValues());
 +        assertFalse(pwdOnTestDb2AttrAfter.getValues().isEmpty());
 +        assertEquals(pwdOnTestDb2, pwdOnTestDb2AttrAfter.getValues().iterator().next());
 +    }
 +
 +    @Test
 +    public void isseSYNCOPE136AES() {
 +        // 1. read configured cipher algorithm in order to be able to restore it at the end of test
 +        AttrTO pwdCipherAlgo = configurationService.read("password.cipher.algorithm");
 +        final String origpwdCipherAlgo = pwdCipherAlgo.getValues().get(0);
 +
 +        // 2. set AES password cipher algorithm
 +        pwdCipherAlgo.getValues().set(0, "AES");
 +        configurationService.set(pwdCipherAlgo.getSchema(), pwdCipherAlgo);
 +
 +        // 3. create user with no resources
 +        UserTO userTO = getUniqueSampleTO("syncope136_AES@apache.org");
 +        userTO.getResources().clear();
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        // 4. update user, assign a propagation primary resource but don't provide any password
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_WS1);
++        
++        final StatusMod st = new StatusMod();
++        st.setOnSyncope(false);
++        st.getResourceNames().add(RESOURCE_NAME_WS1);
++        userMod.setPwdPropRequest(st); 
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +
 +        // 5. verify that propagation was successful
 +        List<PropagationStatus> props = userTO.getPropagationStatusTOs();
 +        assertNotNull(props);
 +        assertEquals(1, props.size());
 +        PropagationStatus prop = props.iterator().next();
 +        assertNotNull(prop);
 +        assertEquals(RESOURCE_NAME_WS1, prop.getResource());
 +        assertEquals(PropagationTaskExecStatus.SUBMITTED, prop.getStatus());
 +
 +        // 6. restore initial cipher algorithm
 +        pwdCipherAlgo.getValues().set(0, origpwdCipherAlgo);
 +        configurationService.set(pwdCipherAlgo.getSchema(), pwdCipherAlgo);
 +    }
 +
 +    @Test
 +    public void isseSYNCOPE136Random() {
 +        // 1. create user with no resources
 +        UserTO userTO = getUniqueSampleTO("syncope136_Random@apache.org");
 +        userTO.getResources().clear();
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        // 2. update user, assign a propagation primary resource but don't provide any password
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_LDAP);
++        
++        final StatusMod st = new StatusMod();
++        st.setOnSyncope(false);
++        st.getResourceNames().add(RESOURCE_NAME_LDAP);
++        userMod.setPwdPropRequest(st);
++
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +
 +        // 3. verify that propagation was successful
 +        List<PropagationStatus> props = userTO.getPropagationStatusTOs();
 +        assertNotNull(props);
 +        assertEquals(1, props.size());
 +        PropagationStatus prop = props.iterator().next();
 +        assertNotNull(prop);
 +        assertEquals(RESOURCE_NAME_LDAP, prop.getResource());
 +        assertEquals(PropagationTaskExecStatus.SUCCESS, prop.getStatus());
 +    }
 +
 +    @Test
 +    public void mappingPurpose() {
 +        UserTO userTO = getUniqueSampleTO("mpurpose@apache.org");
 +
 +        AttrTO csvuserid = new AttrTO();
 +        csvuserid.setSchema("csvuserid");
 +        userTO.getDerAttrs().add(csvuserid);
 +
 +        userTO.getResources().clear();
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +
 +        ConnObjectTO connObjectTO =
 +                resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +        assertNull(connObjectTO.getPlainAttrMap().get("email"));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE265() {
 +        for (long i = 1; i <= 5; i++) {
 +            UserMod userMod = new UserMod();
 +            userMod.setKey(i);
 +
 +            AttrMod attributeMod = new AttrMod();
 +            attributeMod.setSchema("type");
 +            attributeMod.getValuesToBeAdded().add("a type");
 +
 +            userMod.getPlainAttrsToRemove().add("type");
 +            userMod.getPlainAttrsToUpdate().add(attributeMod);
 +
 +            UserTO userTO = updateUser(userMod);
 +            assertEquals("a type", userTO.getPlainAttrMap().get("type").getValues().get(0));
 +        }
 +    }
 +
 +    @Test
 +    public void bulkActions() {
 +        final BulkAction bulkAction = new BulkAction();
 +
 +        for (int i = 0; i < 10; i++) {
 +            UserTO userTO = getUniqueSampleTO("bulk_" + i + "@apache.org");
 +            bulkAction.getTargets().add(String.valueOf(createUser(userTO).getKey()));
 +        }
 +
 +        // check for a fail
 +        bulkAction.getTargets().add(String.valueOf(Long.MAX_VALUE));
 +
 +        assertEquals(11, bulkAction.getTargets().size());
 +
 +        bulkAction.setOperation(BulkAction.Type.SUSPEND);
 +        BulkActionResult res = userService.bulk(bulkAction);
 +        assertEquals(10, res.getResultByStatus(Status.SUCCESS).size());
 +        assertEquals(1, res.getResultByStatus(Status.FAILURE).size());
 +        assertEquals("suspended", userService.read(
 +                Long.parseLong(res.getResultByStatus(Status.SUCCESS).get(3).toString())).getStatus());
 +
 +        bulkAction.setOperation(BulkAction.Type.REACTIVATE);
 +        res = userService.bulk(bulkAction);
 +        assertEquals(10, res.getResultByStatus(Status.SUCCESS).size());
 +        assertEquals(1, res.getResultByStatus(Status.FAILURE).size());
 +        assertEquals("active", userService.read(
 +                Long.parseLong(res.getResultByStatus(Status.SUCCESS).get(3).toString())).getStatus());
 +
 +        bulkAction.setOperation(BulkAction.Type.DELETE);
 +        res = userService.bulk(bulkAction);
 +        assertEquals(10, res.getResultByStatus(Status.SUCCESS).size());
 +        assertEquals(1, res.getResultByStatus(Status.FAILURE).size());
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE354() {
 +        // change resource-ldap role mapping for including uniqueMember (need for assertions below)
 +        ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
 +        for (MappingItemTO item : ldap.getRmapping().getItems()) {
 +            if ("description".equals(item.getExtAttrName())) {
 +                item.setExtAttrName("uniqueMember");
 +            }
 +        }
 +        resourceService.update(ldap.getKey(), ldap);
 +
 +        // 1. create role with LDAP resource
 +        RoleTO roleTO = new RoleTO();
 +        roleTO.setName("SYNCOPE354-" + getUUIDString());
 +        roleTO.setParent(8L);
 +        roleTO.getResources().add(RESOURCE_NAME_LDAP);
 +
 +        roleTO = createRole(roleTO);
 +        assertNotNull(roleTO);
 +
 +        // 2. create user with LDAP resource and membership of the above role
 +        UserTO userTO = getUniqueSampleTO("syncope354@syncope.apache.org");
 +        userTO.getResources().add(RESOURCE_NAME_LDAP);
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(roleTO.getKey());
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO = createUser(userTO);
 +        assertTrue(userTO.getResources().contains(RESOURCE_NAME_LDAP));
 +
 +        // 3. read role on resource, check that user DN is included in uniqueMember
 +        ConnObjectTO connObj = resourceService.getConnectorObject(
 +                RESOURCE_NAME_LDAP, SubjectType.ROLE, roleTO.getKey());
 +        assertNotNull(connObj);
 +        assertTrue(connObj.getPlainAttrMap().get("uniqueMember").getValues().
 +                contains("uid=" + userTO.getUsername() + ",ou=people,o=isp"));
 +
 +        // 4. remove membership
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getKey());
 +
 +        userTO = updateUser(userMod);
 +        assertTrue(userTO.getResources().contains(RESOURCE_NAME_LDAP));
 +
 +        // 5. read role on resource, check that user DN was removed from uniqueMember
 +        connObj = resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.ROLE, roleTO.getKey());
 +        assertNotNull(connObj);
 +        assertFalse(connObj.getPlainAttrMap().get("uniqueMember").getValues().
 +                contains("uid=" + userTO.getUsername() + ",ou=people,o=isp"));
 +
 +        // 6. restore original resource-ldap role mapping
 +        for (MappingItemTO item : ldap.getRmapping().getItems()) {
 +            if ("uniqueMember".equals(item.getExtAttrName())) {
 +                item.setExtAttrName("description");
 +            }
 +        }
 +        resourceService.update(ldap.getKey(), ldap);
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE357() throws IOException {
 +        // 1. create role with LDAP resource
 +        RoleTO roleTO = new RoleTO();
 +        roleTO.setName("SYNCOPE357-" + getUUIDString());
 +        roleTO.setParent(8L);
 +        roleTO.getResources().add(RESOURCE_NAME_LDAP);
 +
 +        roleTO = createRole(roleTO);
 +        assertNotNull(roleTO);
 +
 +        // 2. create user with membership of the above role
 +        UserTO userTO = getUniqueSampleTO("syncope357@syncope.apache.org");
 +        userTO.getPlainAttrs().add(attrTO("obscure", "valueToBeObscured"));
 +        userTO.getPlainAttrs().add(attrTO("photo",
 +                Base64Utility.encode(IOUtils.readBytesFromStream(getClass().getResourceAsStream("/favicon.jpg")))));
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(roleTO.getKey());
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO = createUser(userTO);
 +        assertTrue(userTO.getResources().contains(RESOURCE_NAME_LDAP));
 +        assertNotNull(userTO.getPlainAttrMap().get("obscure"));
 +        assertNotNull(userTO.getPlainAttrMap().get("photo"));
 +
 +        // 3. read user on resource
 +        ConnObjectTO connObj = resourceService.getConnectorObject(
 +                RESOURCE_NAME_LDAP, SubjectType.USER, userTO.getKey());
 +        assertNotNull(connObj);
 +        AttrTO registeredAddress = connObj.getPlainAttrMap().get("registeredAddress");
 +        assertNotNull(registeredAddress);
 +        assertEquals(userTO.getPlainAttrMap().get("obscure").getValues(), registeredAddress.getValues());
 +        AttrTO jpegPhoto = connObj.getPlainAttrMap().get("jpegPhoto");
 +        assertNotNull(jpegPhoto);
 +        assertEquals(userTO.getPlainAttrMap().get("photo").getValues(), jpegPhoto.getValues());
 +
 +        // 4. remove role
 +        roleService.delete(roleTO.getKey());
 +
 +        // 5. try to read user on resource: fail
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.USER, userTO.getKey());
 +            fail();
 +        } catch (SyncopeClientException e) {
 +            assertEquals(ClientExceptionType.NotFound, e.getType());
 +        }
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE383() {
 +        // 1. create user without resources
 +        UserTO userTO = getUniqueSampleTO("syncope383@apache.org");
 +        userTO.getResources().clear();
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        // 2. assign resource without specifying a new pwd and check propagation failure
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_TESTDB);
 +        userTO = updateUser(userMod);
 +        assertEquals(RESOURCE_NAME_TESTDB, userTO.getResources().iterator().next());
 +        assertFalse(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
 +        assertNotNull(userTO.getPropagationStatusTOs().get(0).getFailureReason());
 +
 +        // 3. request to change password only on testdb
 +        userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword(getUUIDString());
 +        StatusMod pwdPropRequest = new StatusMod();
 +        pwdPropRequest.getResourceNames().add(RESOURCE_NAME_TESTDB);
 +        userMod.setPwdPropRequest(pwdPropRequest);
 +
 +        userTO = updateUser(userMod);
 +        assertEquals(RESOURCE_NAME_TESTDB, userTO.getResources().iterator().next());
 +        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE402() {
 +        // 1. create an user with strict mandatory attributes only
 +        UserTO userTO = new UserTO();
 +        String userId = getUUIDString() + "syncope402@syncope.apache.org";
 +        userTO.setUsername(userId);
 +        userTO.setPassword("password");
 +
 +        userTO.getPlainAttrs().add(attrTO("userId", userId));
 +        userTO.getPlainAttrs().add(attrTO("fullname", userId));
 +        userTO.getPlainAttrs().add(attrTO("surname", userId));
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertTrue(userTO.getResources().isEmpty());
 +
 +        //2. update assigning a resource NOT forcing mandatory constraints
 +        // AND primary: must fail with PropagationException
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setPassword("newPassword");
 +
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_WS1);
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_TESTDB);
 +        userTO = updateUser(userMod);
 +
 +        List<PropagationStatus> propagationStatuses = userTO.getPropagationStatusTOs();
 +        PropagationStatus ws1PropagationStatus = null;
 +        if (propagationStatuses != null) {
 +            for (PropagationStatus propStatus : propagationStatuses) {
 +                if (RESOURCE_NAME_WS1.equals(propStatus.getResource())) {
 +                    ws1PropagationStatus = propStatus;
 +                    break;
 +                }
 +            }
 +        }
 +        assertNotNull(ws1PropagationStatus);
 +        assertEquals(RESOURCE_NAME_WS1, ws1PropagationStatus.getResource());
 +        assertNotNull(ws1PropagationStatus.getFailureReason());
 +        assertEquals(PropagationTaskExecStatus.UNSUBMITTED, ws1PropagationStatus.getStatus());
 +    }
 +
 +    @Test
 +    public void unlink() {
 +        UserTO userTO = getUniqueSampleTO("unlink@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey()));
 +
 +        assertNotNull(userService.bulkDeassociation(actual.getKey(),
 +                ResourceDeassociationActionType.UNLINK,
 +                CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
 +                readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey()));
 +    }
 +
 +    @Test
 +    public void link() {
 +        UserTO userTO = getUniqueSampleTO("link@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +
 +        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
 +        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
 +
 +        assertNotNull(userService.bulkAssociation(
 +                actual.getKey(), ResourceAssociationActionType.LINK, associationMod).readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertFalse(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +    }
 +
 +    @Test
 +    public void unassign() {
 +        UserTO userTO = getUniqueSampleTO("unassign@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey()));
 +
 +        assertNotNull(userService.bulkDeassociation(actual.getKey(),
 +                ResourceDeassociationActionType.UNASSIGN,
 +                CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
 +                readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +    }
 +
 +    @Test
 +    public void assign() {
 +        UserTO userTO = getUniqueSampleTO("assign@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +
 +        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
 +        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
 +        associationMod.setChangePwd(true);
 +        associationMod.setPassword("password");
 +
 +        assertNotNull(userService.bulkAssociation(actual.getKey(), ResourceAssociationActionType.ASSIGN, associationMod)
 +                .readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertFalse(actual.getResources().isEmpty());
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey()));
 +    }
 +
 +    @Test
 +    public void deprovision() {
 +        UserTO userTO = getUniqueSampleTO("deprovision@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +        userTO.getResources().add(RESOURCE_NAME_CSV);
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey()));
 +
 +        assertNotNull(userService.bulkDeassociation(actual.getKey(),
 +                ResourceDeassociationActionType.DEPROVISION,
 +                CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
 +                readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertFalse(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +    }
 +
 +    @Test
 +    public void provision() {
 +        UserTO userTO = getUniqueSampleTO("provision@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +
 +        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
 +        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
 +        associationMod.setChangePwd(true);
 +        associationMod.setPassword("password");
 +
 +        assertNotNull(userService.bulkAssociation(actual.getKey(), ResourceAssociationActionType.PROVISION,
 +                associationMod)
 +                .readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey()));
 +    }
 +
 +    @Test
 +    public void deprovisionUnlinked() {
 +        UserTO userTO = getUniqueSampleTO("provision@syncope.apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +
 +        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
 +        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
 +        associationMod.setChangePwd(true);
 +        associationMod.setPassword("password");
 +
 +        assertNotNull(userService.bulkAssociation(actual.getKey(), ResourceAssociationActionType.PROVISION,
 +                associationMod)
 +                .readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey()));
 +
 +        assertNotNull(userService.bulkDeassociation(actual.getKey(),
 +                ResourceDeassociationActionType.DEPROVISION,
 +                CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
 +                readEntity(BulkActionResult.class));
 +
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertTrue(actual.getResources().isEmpty());
 +
 +        try {
 +            resourceService.getConnectorObject(RESOURCE_NAME_CSV, SubjectType.USER, actual.getKey());
 +            fail();
 +        } catch (Exception e) {
 +            assertNotNull(e);
 +        }
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE420() {
 +        UserTO userTO = getUniqueSampleTO("syncope420@syncope.apache.org");
 +        userTO.getPlainAttrs().add(attrTO("makeItDouble", "3"));
 +
 +        userTO = createUser(userTO);
 +        assertEquals("6", userTO.getPlainAttrMap().get("makeItDouble").getValues().get(0));
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.getPlainAttrsToRemove().add("makeItDouble");
 +        userMod.getPlainAttrsToUpdate().add(attrMod("makeItDouble", "7"));
 +
 +        userTO = updateUser(userMod);
 +        assertEquals("14", userTO.getPlainAttrMap().get("makeItDouble").getValues().get(0));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE426() {
 +        UserTO userTO = getUniqueSampleTO("syncope426@syncope.apache.org");
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setPassword("anotherPassword123");
 +        userTO = userService.update(userTO.getKey(), userMod).readEntity(UserTO.class);
 +        assertNotNull(userTO);
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE435() {
 +        // 1. create user without password
 +        UserTO userTO = getUniqueSampleTO("syncope435@syncope.apache.org");
 +        userTO.setPassword(null);
 +        userTO = createUser(userTO, false);
 +        assertNotNull(userTO);
 +
 +        // 2. try to update user by subscribing a resource - works but propagation is not even attempted
 +        UserMod userMod = new UserMod();
 +        userMod.getResourcesToAdd().add(RESOURCE_NAME_WS1);
 +
 +        userTO = userService.update(userTO.getKey(), userMod).readEntity(UserTO.class);
 +        assertEquals(Collections.singleton(RESOURCE_NAME_WS1), userTO.getResources());
 +        assertFalse(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
 +        assertTrue(userTO.getPropagationStatusTOs().get(0).getFailureReason().
 +                startsWith("Not attempted because there are mandatory attributes without value(s): [__PASSWORD__]"));
 +    }
 +
 +    @Test
 +    public void ifMatch() {
 +        UserTO userTO = userService.create(getUniqueSampleTO("ifmatch@syncope.apache.org"), true).
 +                readEntity(UserTO.class);
 +        assertNotNull(userTO);
 +        assertNotNull(userTO.getKey());
 +
 +        EntityTag etag = adminClient.getLatestEntityTag(userService);
 +        assertNotNull(etag);
 +        assertTrue(StringUtils.isNotBlank(etag.getValue()));
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.setUsername(userTO.g

<TRUNCATED>

[3/3] syncope git commit: [SYNCOPE-646] merge from 1_2_X

Posted by fm...@apache.org.
[SYNCOPE-646] merge from 1_2_X


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/fecf301a
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/fecf301a
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/fecf301a

Branch: refs/heads/master
Commit: fecf301a8a4f4a53913a60a86ac13d92cc195423
Parents: 9cbeffd 1012e61
Author: fmartelli <fa...@gmail.com>
Authored: Fri Feb 20 11:46:36 2015 +0100
Committer: fmartelli <fa...@gmail.com>
Committed: Fri Feb 20 11:46:36 2015 +0100

----------------------------------------------------------------------
 .../propagation/PropagationManagerImpl.java     |  2 +-
 .../activiti/ActivitiUserWorkflowAdapter.java   | 10 +++++++
 .../syncope/fit/core/reference/UserITCase.java  | 28 ++++++++++++++++++++
 3 files changed, 39 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/fecf301a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
index 500dfa8,0000000..ec4cc0e
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
@@@ -1,772 -1,0 +1,772 @@@
 +/*
 + * 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.propagation;
 +
 +import java.util.ArrayList;
 +import java.util.Collection;
 +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 org.apache.syncope.common.lib.mod.AttrMod;
 +import org.apache.syncope.common.lib.mod.MembershipMod;
 +import org.apache.syncope.common.lib.mod.UserMod;
 +import org.apache.syncope.common.lib.to.AttrTO;
 +import org.apache.syncope.common.lib.to.MembershipTO;
 +import org.apache.syncope.common.lib.types.AttributableType;
 +import org.apache.syncope.common.lib.types.MappingPurpose;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 +import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 +import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 +import org.apache.syncope.core.persistence.api.dao.UserDAO;
 +import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
 +import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory;
 +import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 +import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 +import org.apache.syncope.core.persistence.api.entity.MappingItem;
 +import org.apache.syncope.core.persistence.api.entity.Subject;
 +import org.apache.syncope.core.persistence.api.entity.VirAttr;
 +import org.apache.syncope.core.persistence.api.entity.membership.Membership;
 +import org.apache.syncope.core.persistence.api.entity.role.Role;
 +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 +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.propagation.PropagationManager;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.apache.syncope.core.provisioning.java.VirAttrHandler;
 +import org.apache.syncope.core.misc.security.UnauthorizedRoleException;
 +import org.apache.syncope.core.misc.ConnObjectUtil;
 +import org.apache.syncope.core.misc.MappingUtil;
 +import org.apache.syncope.core.misc.jexl.JexlUtil;
 +import org.identityconnectors.framework.common.objects.Attribute;
 +import org.identityconnectors.framework.common.objects.AttributeBuilder;
 +import org.identityconnectors.framework.common.objects.AttributeUtil;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.transaction.annotation.Transactional;
 +
 +/**
 + * Manage the data propagation to external resources.
 + */
 +@Transactional(rollbackFor = { Throwable.class })
 +public class PropagationManagerImpl implements PropagationManager {
 +
 +    /**
 +     * Logger.
 +     */
 +    protected static final Logger LOG = LoggerFactory.getLogger(PropagationManager.class);
 +
 +    /**
 +     * User DAO.
 +     */
 +    @Autowired
 +    protected UserDAO userDAO;
 +
 +    /**
 +     * Role DAO.
 +     */
 +    @Autowired
 +    protected RoleDAO roleDAO;
 +
 +    /**
 +     * Resource DAO.
 +     */
 +    @Autowired
 +    protected ExternalResourceDAO resourceDAO;
 +
 +    @Autowired
 +    protected EntityFactory entityFactory;
 +
 +    /**
 +     * ConnObjectUtil.
 +     */
 +    @Autowired
 +    protected ConnObjectUtil connObjectUtil;
 +
 +    @Autowired
 +    protected AttributableUtilFactory attrUtilFactory;
 +
 +    @Autowired
 +    protected VirAttrHandler virAttrHandler;
 +
 +    /**
 +     * Create the user on every associated resource.
 +     *
 +     * @param wfResult user to be propagated (and info associated), as per result from workflow
 +     * @param password to be set
 +     * @param vAttrs virtual attributes to be set
 +     * @param membershipTOs user memberships
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
 +     */
 +    @Override
 +    public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
 +            final String password, final List<AttrTO> vAttrs, final List<MembershipTO> membershipTOs)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getUserCreateTaskIds(wfResult, password, vAttrs, null, membershipTOs);
 +    }
 +
 +    /**
 +     * Create the user on every associated resource.
 +     *
 +     * @param wfResult user to be propagated (and info associated), as per result from workflow
 +     * @param password to be set
 +     * @param vAttrs virtual attributes to be set
 +     * @param noPropResourceNames external resources not to be considered for propagation
 +     * @param membershipTOs user memberships
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
 +     */
 +    @Override
 +    public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
 +            final String password, final Collection<AttrTO> vAttrs,
 +            final Set<String> noPropResourceNames, final List<MembershipTO> membershipTOs)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getUserCreateTaskIds(
 +                wfResult.getResult().getKey(),
 +                wfResult.getResult().getValue(),
 +                wfResult.getPropByRes(),
 +                password,
 +                vAttrs,
 +                membershipTOs,
 +                noPropResourceNames);
 +    }
 +
 +    @Override
 +    public List<PropagationTask> getUserCreateTaskIds(
 +            final Long key,
 +            final Boolean enabled,
 +            final PropagationByResource propByRes,
 +            final String password,
 +            final Collection<AttrTO> vAttrs,
 +            final Collection<MembershipTO> membershipTOs,
 +            final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        User user = userDAO.authFetch(key);
 +        if (vAttrs != null && !vAttrs.isEmpty()) {
 +            virAttrHandler.fillVirtual(user, vAttrs, attrUtilFactory.getInstance(AttributableType.USER));
 +
 +        }
 +        for (Membership membership : user.getMemberships()) {
 +            MembershipTO membershipTO;
 +            if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) {
 +                membershipTO = findMembershipTO(membership, membershipTOs);
 +                if (membershipTO != null) {
 +                    virAttrHandler.fillVirtual(membership,
 +                            membershipTO.getVirAttrs(), attrUtilFactory.getInstance(AttributableType.MEMBERSHIP));
 +                }
 +            }
 +        }
 +        return getCreateTaskIds(user, password, enabled, propByRes, noPropResourceNames);
 +    }
 +
 +    /**
 +     * Create the role on every associated resource.
 +     *
 +     * @param wfResult user to be propagated (and info associated), as per result from workflow
 +     * @param vAttrs virtual attributes to be set
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleCreateTaskIds(final WorkflowResult<Long> wfResult, final List<AttrTO> vAttrs)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getRoleCreateTaskIds(wfResult, vAttrs, null);
 +    }
 +
 +    /**
 +     * Create the role on every associated resource.
 +     *
 +     * @param wfResult role to be propagated (and info associated), as per result from workflow
 +     * @param vAttrs virtual attributes to be set
 +     * @param noPropResourceNames external resources performing not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleCreateTaskIds(
 +            final WorkflowResult<Long> wfResult,
 +            final Collection<AttrTO> vAttrs,
 +            final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getRoleCreateTaskIds(wfResult.getResult(), vAttrs, wfResult.getPropByRes(), noPropResourceNames);
 +    }
 +
 +    /**
 +     * Create the role on every associated resource.
 +     *
 +     * @param key role key
 +     * @param vAttrs virtual attributes to be set
 +     * @param propByRes operation to be performed per resource
 +     * @param noPropResourceNames external resources performing not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleCreateTaskIds(
 +            final Long key,
 +            final Collection<AttrTO> vAttrs,
 +            final PropagationByResource propByRes,
 +            final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        Role role = roleDAO.authFetch(key);
 +        if (vAttrs != null && !vAttrs.isEmpty()) {
 +            virAttrHandler.fillVirtual(role, vAttrs, attrUtilFactory.getInstance(AttributableType.ROLE));
 +        }
 +
 +        return getCreateTaskIds(role, null, null, propByRes, noPropResourceNames);
 +    }
 +
 +    protected List<PropagationTask> getCreateTaskIds(final Subject<?, ?, ?> subject,
 +            final String password, final Boolean enable,
 +            final PropagationByResource propByRes,
 +            final Collection<String> noPropResourceNames) {
 +
 +        if (propByRes == null || propByRes.isEmpty()) {
 +            return Collections.<PropagationTask>emptyList();
 +        }
 +
 +        if (noPropResourceNames != null) {
 +            propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceNames);
 +        }
 +
 +        return createTasks(subject, password, true, null, null, null, null, enable, false, propByRes);
 +    }
 +
 +    /**
 +     * Performs update on each resource associated to the user excluding the specified into 'resourceNames' parameter.
 +     *
 +     * @param user to be propagated
 +     * @param enable whether user must be enabled or not
 +     * @param noPropResourceNames external resource names not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     */
 +    @Override
 +    public List<PropagationTask> getUserUpdateTaskIds(final User user, final Boolean enable,
 +            final Set<String> noPropResourceNames) throws NotFoundException {
 +
 +        return getUpdateTaskIds(
 +                user, // user to be updated on external resources
 +                null, // no password
 +                false,
 +                enable, // status to be propagated
 +                Collections.<String>emptySet(), // no virtual attributes to be managed
 +                Collections.<AttrMod>emptySet(), // no virtual attributes to be managed
 +                null, // no propagation by resources
 +                noPropResourceNames,
 +                Collections.<MembershipMod>emptySet());
 +    }
 +
 +    /**
 +     * Performs update on each resource associated to the user.
 +     *
 +     * @param wfResult user to be propagated (and info associated), as per result from workflow
 +     * @param changePwd whether password should be included for propagation attributes or not
 +     * @param noPropResourceNames external resources not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
 +     */
 +    @Override
 +    public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult,
 +            final boolean changePwd, final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        User user = userDAO.authFetch(wfResult.getResult().getKey().getKey());
 +        return getUpdateTaskIds(user,
 +                wfResult.getResult().getKey().getPassword(),
 +                changePwd,
 +                wfResult.getResult().getValue(),
 +                wfResult.getResult().getKey().getVirAttrsToRemove(),
 +                wfResult.getResult().getKey().getVirAttrsToUpdate(),
 +                wfResult.getPropByRes(),
 +                noPropResourceNames,
 +                wfResult.getResult().getKey().getMembershipsToAdd());
 +    }
 +
 +    @Override
 +    public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult) {
 +        UserMod userMod = wfResult.getResult().getKey();
 +
 +        // Propagate password update only to requested resources
 +        List<PropagationTask> tasks = new ArrayList<>();
 +        if (userMod.getPwdPropRequest() == null) {
 +            // a. no specific password propagation request: generate propagation tasks for any resource associated
-             tasks = getUserUpdateTaskIds(wfResult, true, null);
++            tasks = getUserUpdateTaskIds(wfResult, false, null);
 +        } else {
 +            // b. generate the propagation task list in two phases: first the ones containing password,
 +            // the the rest (with no password)
 +            final PropagationByResource origPropByRes = new PropagationByResource();
 +            origPropByRes.merge(wfResult.getPropByRes());
 +
 +            Set<String> pwdResourceNames = new HashSet<>(userMod.getPwdPropRequest().getResourceNames());
 +            Set<String> currentResourceNames = userDAO.authFetch(userMod.getKey()).getResourceNames();
 +            pwdResourceNames.retainAll(currentResourceNames);
 +            PropagationByResource pwdPropByRes = new PropagationByResource();
 +            pwdPropByRes.addAll(ResourceOperation.UPDATE, pwdResourceNames);
 +            if (!pwdPropByRes.isEmpty()) {
 +                Set<String> toBeExcluded = new HashSet<>(currentResourceNames);
 +                toBeExcluded.addAll(userMod.getResourcesToAdd());
 +                toBeExcluded.removeAll(pwdResourceNames);
 +                tasks.addAll(getUserUpdateTaskIds(wfResult, true, toBeExcluded));
 +            }
 +
 +            final PropagationByResource nonPwdPropByRes = new PropagationByResource();
 +            nonPwdPropByRes.merge(origPropByRes);
 +            nonPwdPropByRes.removeAll(pwdResourceNames);
 +            nonPwdPropByRes.purge();
 +            if (!nonPwdPropByRes.isEmpty()) {
 +                tasks.addAll(getUserUpdateTaskIds(wfResult, false, pwdResourceNames));
 +            }
 +        }
 +
 +        return tasks;
 +    }
 +
 +    /**
 +     * Performs update on each resource associated to the role.
 +     *
 +     * @param wfResult role to be propagated (and info associated), as per result from workflow
 +     * @param vAttrsToBeRemoved virtual attributes to be removed
 +     * @param vAttrsToBeUpdated virtual attributes to be added
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleUpdateTaskIds(final WorkflowResult<Long> wfResult,
 +            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getRoleUpdateTaskIds(wfResult, vAttrsToBeRemoved, vAttrsToBeUpdated, null);
 +    }
 +
 +    /**
 +     * Performs update on each resource associated to the role.
 +     *
 +     * @param wfResult role to be propagated (and info associated), as per result from workflow
 +     * @param vAttrsToBeRemoved virtual attributes to be removed
 +     * @param vAttrsToBeUpdated virtual attributes to be added
 +     * @param noPropResourceNames external resource names not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    public List<PropagationTask> getRoleUpdateTaskIds(final WorkflowResult<Long> wfResult,
 +            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
 +            final Set<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        Role role = roleDAO.authFetch(wfResult.getResult());
 +        return getUpdateTaskIds(role, null, false, null,
 +                vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames,
 +                Collections.<MembershipMod>emptySet());
 +    }
 +
 +    @Override
 +    public List<PropagationTask> getUpdateTaskIds(final Subject<?, ?, ?> subject,
 +            final String password, final boolean changePwd, final Boolean enable,
 +            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
 +            final PropagationByResource propByRes, final Collection<String> noPropResourceNames,
 +            final Set<MembershipMod> membershipsToAdd)
 +            throws NotFoundException {
 +
 +        PropagationByResource localPropByRes = virAttrHandler.fillVirtual(subject, vAttrsToBeRemoved == null
 +                ? Collections.<String>emptySet()
 +                : vAttrsToBeRemoved, vAttrsToBeUpdated == null
 +                        ? Collections.<AttrMod>emptySet()
 +                        : vAttrsToBeUpdated, attrUtilFactory.getInstance(subject));
 +
 +        // SYNCOPE-458 fill membership virtual attributes
 +        if (subject instanceof User) {
 +            final User user = (User) subject;
 +            for (Membership membership : user.getMemberships()) {
 +                if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) {
 +                    final MembershipMod membershipMod = findMembershipMod(membership, membershipsToAdd);
 +                    if (membershipMod != null) {
 +                        virAttrHandler.fillVirtual(membership, membershipMod.getVirAttrsToRemove() == null
 +                                ? Collections.<String>emptySet()
 +                                : membershipMod.getVirAttrsToRemove(),
 +                                membershipMod.getVirAttrsToUpdate() == null ? Collections.<AttrMod>emptySet()
 +                                        : membershipMod.getVirAttrsToUpdate(), attrUtilFactory.getInstance(
 +                                        AttributableType.MEMBERSHIP));
 +                    }
 +                }
 +            }
 +        }
 +
 +        if (propByRes == null || propByRes.isEmpty()) {
 +            localPropByRes.addAll(ResourceOperation.UPDATE, subject.getResourceNames());
 +        } else {
 +            localPropByRes.merge(propByRes);
 +        }
 +
 +        if (noPropResourceNames != null) {
 +            localPropByRes.removeAll(noPropResourceNames);
 +        }
 +
 +        Map<String, AttrMod> vAttrsToBeUpdatedMap = null;
 +        if (vAttrsToBeUpdated != null) {
 +            vAttrsToBeUpdatedMap = new HashMap<>();
 +            for (AttrMod attrMod : vAttrsToBeUpdated) {
 +                vAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod);
 +            }
 +        }
 +
 +        // SYNCOPE-458 fill membership virtual attributes to be updated map
 +        Map<String, AttrMod> membVAttrsToBeUpdatedMap = new HashMap<>();
 +        for (MembershipMod membershipMod : membershipsToAdd) {
 +            for (AttrMod attrMod : membershipMod.getVirAttrsToUpdate()) {
 +                membVAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod);
 +            }
 +        }
 +
 +        // SYNCOPE-458 fill membership virtual attributes to be removed set
 +        final Set<String> membVAttrsToBeRemoved = new HashSet<>();
 +        for (MembershipMod membershipMod : membershipsToAdd) {
 +            membVAttrsToBeRemoved.addAll(membershipMod.getVirAttrsToRemove());
 +        }
 +
 +        return createTasks(subject, password, changePwd,
 +                vAttrsToBeRemoved, vAttrsToBeUpdatedMap, membVAttrsToBeRemoved, membVAttrsToBeUpdatedMap, enable, false,
 +                localPropByRes);
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param userKey to be deleted
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
 +     */
 +    @Override
 +    public List<PropagationTask> getUserDeleteTaskIds(final Long userKey)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getUserDeleteTaskIds(userKey, Collections.<String>emptySet());
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param userKey to be deleted
 +     * @param noPropResourceName name of external resource not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
 +     */
 +    @Override
 +    public List<PropagationTask> getUserDeleteTaskIds(final Long userKey, final String noPropResourceName)
 +            throws NotFoundException, UnauthorizedRoleException {
 +        return getUserDeleteTaskIds(userKey, Collections.<String>singleton(noPropResourceName));
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param userKey to be deleted
 +     * @param noPropResourceNames name of external resources not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
 +     */
 +    @Override
 +    public List<PropagationTask> getUserDeleteTaskIds(final Long userKey, final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        User user = userDAO.authFetch(userKey);
 +        return getDeleteTaskIds(user, user.getResourceNames(), noPropResourceNames);
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param userKey to be deleted
 +     * @param resourceNames resource from which user is to be deleted
 +     * @param noPropResourceNames name of external resources not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if user is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
 +     */
 +    @Override
 +    public List<PropagationTask> getUserDeleteTaskIds(
 +            final Long userKey, final Set<String> resourceNames, final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        User user = userDAO.authFetch(userKey);
 +        return getDeleteTaskIds(user, resourceNames, noPropResourceNames);
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param wfResult user to be propagated (and info associated), as per result from workflow
 +     * @return list of propagation tasks
 +     */
 +    @Override
 +    public List<PropagationTask> getUserDeleteTaskIds(final WorkflowResult<Long> wfResult) {
 +        User user = userDAO.authFetch(wfResult.getResult());
 +        return createTasks(user, null, false, null, null, null, null, false, true, wfResult.getPropByRes());
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param roleKey to be deleted
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getRoleDeleteTaskIds(roleKey, Collections.<String>emptySet());
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param roleKey to be deleted
 +     * @param noPropResourceName name of external resource not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey, final String noPropResourceName)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        return getRoleDeleteTaskIds(roleKey, Collections.<String>singleton(noPropResourceName));
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param roleKey to be deleted
 +     * @param noPropResourceNames name of external resources not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey, final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        Role role = roleDAO.authFetch(roleKey);
 +        return getDeleteTaskIds(role, role.getResourceNames(), noPropResourceNames);
 +    }
 +
 +    /**
 +     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
 +     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
 +     * the creation fails onto a mandatory resource.
 +     *
 +     * @param roleKey to be deleted
 +     * @param resourceNames resource from which role is to be deleted
 +     * @param noPropResourceNames name of external resources not to be considered for propagation
 +     * @return list of propagation tasks
 +     * @throws NotFoundException if role is not found
 +     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
 +     */
 +    @Override
 +    public List<PropagationTask> getRoleDeleteTaskIds(
 +            final Long roleKey, final Set<String> resourceNames, final Collection<String> noPropResourceNames)
 +            throws NotFoundException, UnauthorizedRoleException {
 +
 +        Role role = roleDAO.authFetch(roleKey);
 +        return getDeleteTaskIds(role, resourceNames, noPropResourceNames);
 +    }
 +
 +    protected List<PropagationTask> getDeleteTaskIds(
 +            final Subject<?, ?, ?> subject,
 +            final Set<String> resourceNames,
 +            final Collection<String> noPropResourceNames) {
 +
 +        final PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.set(ResourceOperation.DELETE, resourceNames);
 +        if (noPropResourceNames != null && !noPropResourceNames.isEmpty()) {
 +            propByRes.get(ResourceOperation.DELETE).removeAll(noPropResourceNames);
 +        }
 +        return createTasks(subject, null, false, null, null, null, null, false, true, propByRes);
 +    }
 +
 +    /**
 +     * Create propagation tasks.
 +     *
 +     * @param subject user / role to be provisioned
 +     * @param password cleartext password to be provisioned
 +     * @param changePwd whether password should be included for propagation attributes or not
 +     * @param vAttrsToBeRemoved virtual attributes to be removed
 +     * @param vAttrsToBeUpdated virtual attributes to be added
 +     * @param membVAttrsToBeRemoved membership virtual attributes to be removed
 +     * @param membVAttrsToBeUpdatedMap membership virtual attributes to be added
 +     * @param enable whether user must be enabled or not
 +     * @param deleteOnResource whether user / role must be deleted anyway from external resource or not
 +     * @param propByRes operation to be performed per resource
 +     * @return list of propagation tasks created
 +     */
 +    protected List<PropagationTask> createTasks(final Subject<?, ?, ?> subject,
 +            final String password, final boolean changePwd,
 +            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated,
 +            final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdatedMap,
 +            final Boolean enable, final boolean deleteOnResource, final PropagationByResource propByRes) {
 +
 +        LOG.debug("Provisioning subject {}:\n{}", subject, propByRes);
 +
 +        final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject);
 +
 +        if (!propByRes.get(ResourceOperation.CREATE).isEmpty()
 +                && vAttrsToBeRemoved != null && vAttrsToBeUpdated != null) {
 +
 +            connObjectUtil.retrieveVirAttrValues(subject, attrUtil);
 +
 +            // update vAttrsToBeUpdated as well
 +            for (VirAttr virAttr : subject.getVirAttrs()) {
 +                final String schema = virAttr.getSchema().getKey();
 +
 +                final AttrMod attributeMod = new AttrMod();
 +                attributeMod.setSchema(schema);
 +                attributeMod.getValuesToBeAdded().addAll(virAttr.getValues());
 +
 +                vAttrsToBeUpdated.put(schema, attributeMod);
 +            }
 +        }
 +
 +        // Avoid duplicates - see javadoc
 +        propByRes.purge();
 +        LOG.debug("After purge: {}", propByRes);
 +
 +        final List<PropagationTask> tasks = new ArrayList<>();
 +
 +        for (ResourceOperation operation : ResourceOperation.values()) {
 +            for (String resourceName : propByRes.get(operation)) {
 +                final ExternalResource resource = resourceDAO.find(resourceName);
 +                if (resource == null) {
 +                    LOG.error("Invalid resource name specified: {}, ignoring...", resourceName);
 +                } else if (attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION).isEmpty()) {
 +                    LOG.warn("Requesting propagation for {} but no propagation mapping provided for {}",
 +                            attrUtil.getType(), resource);
 +                } else {
 +                    PropagationTask task = entityFactory.newEntity(PropagationTask.class);
 +                    task.setResource(resource);
 +                    task.setObjectClassName(connObjectUtil.fromSubject(subject).getObjectClassValue());
 +                    task.setSubjectType(attrUtil.getType());
 +                    if (!deleteOnResource) {
 +                        task.setSubjectKey(subject.getKey());
 +                    }
 +                    task.setPropagationOperation(operation);
 +                    task.setPropagationMode(resource.getPropagationMode());
 +                    task.setOldAccountId(propByRes.getOldAccountId(resource.getKey()));
 +
 +                    Map.Entry<String, Set<Attribute>> preparedAttrs = MappingUtil.prepareAttributes(attrUtil, subject,
 +                            password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved,
 +                            membVAttrsToBeUpdatedMap, enable, resource);
 +                    task.setAccountId(preparedAttrs.getKey());
 +
 +                    // Check if any of mandatory attributes (in the mapping) is missing or not received any value: 
 +                    // if so, add special attributes that will be evaluated by PropagationTaskExecutor
 +                    List<String> mandatoryMissing = new ArrayList<>();
 +                    List<String> mandatoryNullOrEmpty = new ArrayList<>();
 +                    for (MappingItem item : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
 +                        if (!item.isAccountid()
 +                                && JexlUtil.evaluateMandatoryCondition(item.getMandatoryCondition(), subject)) {
 +
 +                            Attribute attr = AttributeUtil.find(item.getExtAttrName(), preparedAttrs.getValue());
 +                            if (attr == null) {
 +                                mandatoryMissing.add(item.getExtAttrName());
 +                            } else if (attr.getValue() == null || attr.getValue().isEmpty()) {
 +                                mandatoryNullOrEmpty.add(item.getExtAttrName());
 +                            }
 +                        }
 +                    }
 +                    if (!mandatoryMissing.isEmpty()) {
 +                        preparedAttrs.getValue().add(AttributeBuilder.build(
 +                                PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME, mandatoryMissing));
 +                    }
 +                    if (!mandatoryNullOrEmpty.isEmpty()) {
 +                        preparedAttrs.getValue().add(AttributeBuilder.build(
 +                                PropagationTaskExecutor.MANDATORY_NULL_OR_EMPTY_ATTR_NAME, mandatoryNullOrEmpty));
 +                    }
 +
 +                    task.setAttributes(preparedAttrs.getValue());
 +                    tasks.add(task);
 +
 +                    LOG.debug("PropagationTask created: {}", task);
 +                }
 +            }
 +        }
 +
 +        return tasks;
 +    }
 +
 +    protected MembershipTO findMembershipTO(final Membership membership, final Collection<MembershipTO> memberships) {
 +        for (MembershipTO membershipTO : memberships) {
 +            if (membershipTO.getRoleId() == membership.getRole().getKey()) {
 +                return membershipTO;
 +            }
 +        }
 +        LOG.error("No MembershipTO found for membership {}", membership);
 +        return null;
 +    }
 +
 +    protected MembershipMod findMembershipMod(final Membership membership, final Set<MembershipMod> membershipMods) {
 +        for (MembershipMod membershipMod : membershipMods) {
 +            if (membershipMod.getRole() == membership.getRole().getKey()) {
 +                return membershipMod;
 +            }
 +        }
 +        LOG.error("No MembershipMod found for membership {}", membership);
 +        return null;
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fecf301a/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 4b3d38d,0000000..7b532b4
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,892 -1,0 +1,902 @@@
 +/*
 + * 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.AbstractMap.SimpleEntry;
 +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.FormService;
 +import org.activiti.engine.HistoryService;
 +import org.activiti.engine.RepositoryService;
 +import org.activiti.engine.RuntimeService;
 +import org.activiti.engine.TaskService;
 +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.syncope.common.lib.SyncopeClientException;
++import org.apache.syncope.common.lib.mod.StatusMod;
 +import org.apache.syncope.common.lib.mod.UserMod;
 +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.AuthContextUtil;
 +import org.apache.syncope.core.misc.security.UnauthorizedRoleException;
 +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.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.transaction.annotation.Transactional;
 +
 +/**
 + * Activiti (http://www.activiti.org/) based implementation.
 + */
 +public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
 +
 +    /**
 +     * Logger.
 +     */
 +    private static final Logger LOG = LoggerFactory.getLogger(ActivitiUserWorkflowAdapter.class);
 +
 +    private 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_MOD = "userMod";
 +
 +    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")
 +    private String adminUser;
 +
 +    @Autowired
 +    private RuntimeService runtimeService;
 +
 +    @Autowired
 +    private TaskService taskService;
 +
 +    @Autowired
 +    private FormService formService;
 +
 +    @Autowired
 +    private HistoryService historyService;
 +
 +    @Autowired
 +    private RepositoryService repositoryService;
 +
 +    @Autowired
 +    private ActivitiImportUtils importUtils;
 +
 +    @Autowired
 +    private UserDataBinder userDataBinder;
 +
 +    @Override
 +    public String getPrefix() {
 +        return "ACT_";
 +    }
 +
 +    private 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);
 +    }
 +
 +    private void updateStatus(final User user) {
 +        List<Task> tasks = taskService.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());
 +        }
 +    }
 +
 +    private String getFormTask(final User user) {
 +        String result = null;
 +
 +        List<Task> tasks = taskService.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 = formService.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;
 +    }
 +
 +    private Set<String> getPerformedTasks(final User user) {
 +        final Set<String> result = new HashSet<>();
 +
 +        for (HistoricActivityInstance task
 +                : historyService.createHistoricActivityInstanceQuery().executionId(user.getWorkflowId()).list()) {
 +
 +            result.add(task.getActivityId());
 +        }
 +
 +        return result;
 +    }
 +
 +    /**
 +     * Saves resources to be propagated and password for later - after form submission - propagation.
 +     */
 +    private 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()
 +            taskService.setVariableLocal(formTaskId, TASK_IS_FORM, Boolean.TRUE);
 +            runtimeService.setVariable(user.getWorkflowId(), PROP_BY_RESOURCE, propByRes);
 +            if (propByRes != null) {
 +                propByRes.clear();
 +            }
 +
 +            if (StringUtils.isNotBlank(password)) {
 +                runtimeService.setVariable(user.getWorkflowId(), ENCRYPTED_PWD, encrypt(password));
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public WorkflowResult<Map.Entry<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck,
 +            final boolean storePassword) throws WorkflowException {
 +
 +        return create(userTO, disablePwdPolicyCheck, null, storePassword);
 +    }
 +
 +    @Override
 +    public WorkflowResult<Map.Entry<Long, Boolean>> create(UserTO userTO, boolean storePassword) throws
 +            UnauthorizedRoleException, WorkflowException {
 +
 +        return create(userTO, false, storePassword);
 +    }
 +
 +    @Override
 +    public WorkflowResult<Map.Entry<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck,
 +            final Boolean enabled, final boolean storePassword) throws WorkflowException {
 +
 +        final Map<String, Object> variables = new HashMap<>();
 +        variables.put(WF_EXECUTOR, AuthContextUtil.getAuthenticatedUsername());
 +        variables.put(USER_TO, userTO);
 +        variables.put(ENABLED, enabled);
 +        variables.put(STORE_PASSWORD, storePassword);
 +
 +        ProcessInstance processInstance = null;
 +        try {
 +            processInstance = runtimeService.startProcessInstanceByKey(WF_PROCESS_ID, variables);
 +        } catch (ActivitiException e) {
 +            throwException(e, "While starting " + WF_PROCESS_ID + " instance");
 +        }
 +
 +        User user =
 +                runtimeService.getVariable(processInstance.getProcessInstanceId(), USER, User.class);
 +
 +        Boolean updatedEnabled =
 +                runtimeService.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 =
 +                runtimeService.getVariable(processInstance.getProcessInstanceId(), PROPAGATE_ENABLE, Boolean.class);
 +        if (propagateEnable == null) {
 +            propagateEnable = enabled;
 +        }
 +
 +        PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.set(ResourceOperation.CREATE, user.getResourceNames());
 +
 +        saveForFormSubmit(user, userTO.getPassword(), propByRes);
 +
 +        return new WorkflowResult<Map.Entry<Long, Boolean>>(
 +                new SimpleEntry<>(user.getKey(), propagateEnable), propByRes, getPerformedTasks(user));
 +    }
 +
 +    private Set<String> doExecuteTask(final User user, final String task,
 +            final Map<String, Object> moreVariables) throws WorkflowException {
 +
 +        Set<String> preTasks = getPerformedTasks(user);
 +
 +        final Map<String, Object> variables = new HashMap<>();
 +        variables.put(WF_EXECUTOR, AuthContextUtil.getAuthenticatedUsername());
 +        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 = taskService.createTaskQuery().processInstanceId(user.getWorkflowId()).list();
 +        if (tasks.size() == 1) {
 +            try {
 +                taskService.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)
 +            throws WorkflowException {
 +
 +        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<Map.Entry<UserMod, Boolean>> doUpdate(final User user, final UserMod userMod)
 +            throws WorkflowException {
 +
 +        Set<String> tasks = doExecuteTask(user, "update", Collections.singletonMap(USER_MOD, (Object) userMod));
 +
 +        updateStatus(user);
 +        User updated = userDAO.save(user);
 +
 +        PropagationByResource propByRes =
 +                runtimeService.getVariable(user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class);
 +
 +        saveForFormSubmit(updated, userMod.getPassword(), propByRes);
 +
 +        Boolean propagateEnable = runtimeService.getVariable(user.getWorkflowId(), PROPAGATE_ENABLE, Boolean.class);
 +
 +        return new WorkflowResult<Map.Entry<UserMod, Boolean>>(
 +                new SimpleEntry<>(userMod, propagateEnable), propByRes, tasks);
 +    }
 +
 +    @Override
 +    @Transactional(rollbackFor = { Throwable.class })
 +    protected WorkflowResult<Long> doSuspend(final User user) throws WorkflowException {
 +        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) throws WorkflowException {
 +        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) throws WorkflowException {
 +        Map<String, Object> variables = new HashMap<>(2);
 +        variables.put(USER_TO, userDataBinder.getUserTO(user));
 +        variables.put(EVENT, "requestPasswordReset");
 +
 +        doExecuteTask(user, "requestPasswordReset", variables);
 +        userDAO.save(user);
 +    }
 +
 +    @Override
 +    protected void doConfirmPasswordReset(final User user, final String token, final String password)
 +            throws WorkflowException {
 +
 +        Map<String, Object> variables = new HashMap<>(4);
 +        variables.put(TOKEN, token);
 +        variables.put(PASSWORD, password);
 +        variables.put(USER_TO, userDataBinder.getUserTO(user));
 +        variables.put(EVENT, "confirmPasswordReset");
 +
 +        doExecuteTask(user, "confirmPasswordReset", variables);
 +        userDAO.save(user);
 +    }
 +
 +    @Override
 +    protected void doDelete(final User user) throws WorkflowException {
 +        doExecuteTask(user, "delete", null);
 +
 +        PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.set(ResourceOperation.DELETE, user.getResourceNames());
 +
 +        saveForFormSubmit(user, null, propByRes);
 +
 +        if (runtimeService.createProcessInstanceQuery().
 +                processInstanceId(user.getWorkflowId()).active().list().isEmpty()) {
 +
 +            userDAO.delete(user.getKey());
 +
 +            if (!historyService.createHistoricProcessInstanceQuery().
 +                    processInstanceId(user.getWorkflowId()).list().isEmpty()) {
 +
 +                historyService.deleteHistoricProcessInstance(user.getWorkflowId());
 +            }
 +        } else {
 +            updateStatus(user);
 +            userDAO.save(user);
 +        }
 +    }
 +
 +    @Override
 +    public WorkflowResult<Long> execute(final UserTO userTO, final String taskId)
 +            throws UnauthorizedRoleException, WorkflowException {
 +
 +        User user = userDAO.authFetch(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 repositoryService.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 = repositoryService.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 = repositoryService.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(repositoryService.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)
 +            throws WorkflowException {
 +
 +        switch (format) {
 +            case JSON:
 +                exportProcessModel(os);
 +                break;
 +
 +            case XML:
 +            default:
 +                exportProcessResource(WF_PROCESS_RESOURCE, os);
 +        }
 +    }
 +
 +    @Override
 +    public void exportDiagram(final OutputStream os) throws WorkflowException {
 +        exportProcessResource(WF_DGRM_RESOURCE, os);
 +    }
 +
 +    @Override
 +    public void importDefinition(final WorkflowDefinitionFormat format, final String definition)
 +            throws WorkflowException {
 +
 +        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);
 +                    importUtils.fromXML(new BpmnXMLConverter().convertToXML(bpmnModel));
 +                } catch (Exception e) {
 +                    throw new WorkflowException("While updating process "
 +                            + ActivitiUserWorkflowAdapter.WF_PROCESS_RESOURCE, e);
 +                }
 +
 +                importUtils.fromJSON(definitionNode.toString().getBytes(), getProcessDefinition(), model);
 +                break;
 +
 +            case XML:
 +            default:
 +                importUtils.fromXML(definition.getBytes());
 +
 +                importUtils.fromJSON(getProcessDefinition(), model);
 +        }
 +    }
 +
 +    private 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;
 +    }
 +
 +    private WorkflowFormTO getFormTO(final Task task) {
 +        return getFormTO(task, formService.getTaskFormData(task.getId()));
 +    }
 +
 +    private 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;
 +    }
 +
 +    private WorkflowFormTO getFormTO(final HistoricTaskInstance task) {
 +        final List<HistoricFormPropertyEntity> props = new ArrayList<>();
 +
 +        for (HistoricDetail historicDetail : historyService.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 = historyService.createHistoricActivityInstanceQuery().
 +                executionId(task.getExecutionId()).activityType("userTask").activityName(task.getName()).singleResult();
 +
 +        if (historicActivityInstance != null) {
 +            formTO.setCreateTime(historicActivityInstance.getStartTime());
 +            formTO.setDueDate(historicActivityInstance.getEndTime());
 +        }
 +
 +        return formTO;
 +    }
 +
 +    private 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")
 +    private 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<>();
 +
 +        final String authUser = AuthContextUtil.getAuthenticatedUsername();
 +        if (adminUser.equals(authUser)) {
 +            forms.addAll(getForms(taskService.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(taskService.createTaskQuery().
 +                    taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE).
 +                    taskCandidateOrAssigned(user.getKey().toString())));
 +
 +            List<String> candidateGroups = new ArrayList<>();
 +            for (Long roleId : user.getRoleKeys()) {
 +                candidateGroups.add(roleId.toString());
 +            }
 +            if (!candidateGroups.isEmpty()) {
 +                forms.addAll(getForms(taskService.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(
 +                taskService.createTaskQuery().processInstanceId(workflowId).taskName(name).
 +                taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE));
 +
 +        forms.addAll(getForms(historyService.createHistoricTaskInstanceQuery().taskName(name).
 +                taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE)));
 +
 +        return forms;
 +    }
 +
 +    private <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)
 +            throws NotFoundException, WorkflowException {
 +
 +        Task task;
 +        try {
 +            task = taskService.createTaskQuery().processInstanceId(workflowId).singleResult();
 +        } catch (ActivitiException e) {
 +            throw new WorkflowException("While reading form for workflow instance " + workflowId, e);
 +        }
 +
 +        TaskFormData formData;
 +        try {
 +            formData = formService.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;
 +    }
 +
 +    private Map.Entry<Task, TaskFormData> checkTask(final String taskId, final String authUser) {
 +        Task task;
 +        try {
 +            task = taskService.createTaskQuery().taskId(taskId).singleResult();
 +        } catch (ActivitiException e) {
 +            throw new NotFoundException("Activiti Task " + taskId, e);
 +        }
 +
 +        TaskFormData formData;
 +        try {
 +            formData = formService.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 SimpleEntry<>(task, formData);
 +    }
 +
 +    @Transactional
 +    @Override
 +    public WorkflowFormTO claimForm(final String taskId)
 +            throws WorkflowException {
 +
 +        final String authUser = AuthContextUtil.getAuthenticatedUsername();
 +        Map.Entry<Task, TaskFormData> checked = checkTask(taskId, authUser);
 +
 +        if (!adminUser.equals(authUser)) {
 +            List<Task> tasksForUser = taskService.createTaskQuery().taskId(taskId).taskCandidateUser(authUser).list();
 +            if (tasksForUser.isEmpty()) {
 +                throw new WorkflowException(
 +                        new IllegalArgumentException(authUser + " is not candidate for task " + taskId));
 +            }
 +        }
 +
 +        Task task;
 +        try {
 +            taskService.setOwner(taskId, authUser);
 +            task = taskService.createTaskQuery().taskId(taskId).singleResult();
 +        } catch (ActivitiException e) {
 +            throw new WorkflowException("While reading task " + taskId, e);
 +        }
 +
 +        return getFormTO(task, checked.getValue());
 +    }
 +
 +    @Transactional
 +    @Override
 +    public WorkflowResult<UserMod> submitForm(final WorkflowFormTO form)
 +            throws WorkflowException {
 +
 +        final String authUser = AuthContextUtil.getAuthenticatedUsername();
 +        Map.Entry<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 {
 +            formService.submitTaskFormData(form.getTaskId(), form.getPropertiesForSubmit());
 +            runtimeService.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 =
 +                runtimeService.getVariable(user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class);
 +
 +        // fetch - if available - the encrypted password
 +        String clearPassword = null;
 +        String encryptedPwd = runtimeService.getVariable(user.getWorkflowId(), ENCRYPTED_PWD, String.class);
 +        if (StringUtils.isNotBlank(encryptedPwd)) {
 +            clearPassword = decrypt(encryptedPwd);
 +        }
 +
 +        // supports approval chains
 +        saveForFormSubmit(user, clearPassword, propByRes);
 +
 +        UserMod userMod = runtimeService.getVariable(user.getWorkflowId(), USER_MOD, UserMod.class);
 +        if (userMod == null) {
 +            userMod = new UserMod();
 +            userMod.setKey(updated.getKey());
 +            userMod.setPassword(clearPassword);
++            
++          if (propByRes != null) {
++                final StatusMod st = new StatusMod();
++                userMod.setPwdPropRequest(st);
++                st.setOnSyncope(true);
++                for (String res : propByRes.get(ResourceOperation.CREATE)) {
++                    st.getResourceNames().add(res);
++                }
++            }
 +        }
 +
 +        return new WorkflowResult<>(userMod, propByRes, postTasks);
 +    }
 +}