You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/01/23 17:41:31 UTC

[09/15] syncope git commit: FIT server integration tests

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserSelfITCase.java
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserSelfITCase.java b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserSelfITCase.java
new file mode 100644
index 0000000..8795f06
--- /dev/null
+++ b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserSelfITCase.java
@@ -0,0 +1,342 @@
+/*
+ * 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.server.reference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.util.Map;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.syncope.client.lib.SyncopeClient;
+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.StatusMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.MembershipTO;
+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.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.common.rest.api.Preference;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.UserSelfService;
+import org.apache.syncope.common.rest.api.service.UserService;
+import org.junit.Assume;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.JVM)
+public class UserSelfITCase extends AbstractITCase {
+
+    @Test
+    public void selfRegistrationAllowed() {
+        assertTrue(clientFactory.createAnonymous().isSelfRegAllowed());
+    }
+
+    @Test
+    public void create() {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        // 1. self-registration as admin: failure
+        try {
+            userSelfService.create(UserITCase.getUniqueSampleTO("anonymous@syncope.apache.org"), true);
+            fail();
+        } catch (AccessControlException e) {
+            assertNotNull(e);
+        }
+
+        // 2. self-registration as anonymous: works
+        SyncopeClient anonClient = clientFactory.createAnonymous();
+        UserTO self = anonClient.getService(UserSelfService.class).
+                create(UserITCase.getUniqueSampleTO("anonymous@syncope.apache.org"), true).
+                readEntity(UserTO.class);
+        assertNotNull(self);
+        assertEquals("createApproval", self.getStatus());
+    }
+
+    @Test
+    public void createAndApprove() {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        // self-create user with membership: goes 'createApproval' with resources and membership but no propagation
+        UserTO userTO = UserITCase.getUniqueSampleTO("anonymous@syncope.apache.org");
+        MembershipTO membership = new MembershipTO();
+        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).
+                readEntity(UserTO.class);
+        assertNotNull(userTO);
+        assertEquals("createApproval", userTO.getStatus());
+        assertFalse(userTO.getMemberships().isEmpty());
+        assertFalse(userTO.getResources().isEmpty());
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userTO.getKey());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+
+        // now approve and verify that propagation has happened
+        WorkflowFormTO form = userWorkflowService.getFormForUser(userTO.getKey());
+        form = userWorkflowService.claimForm(form.getTaskId());
+        Map<String, WorkflowFormPropertyTO> props = form.getPropertyMap();
+        props.get("approve").setValue(Boolean.TRUE.toString());
+        form.getProperties().clear();
+        form.getProperties().addAll(props.values());
+        userTO = userWorkflowService.submitForm(form);
+        assertNotNull(userTO);
+        assertEquals("active", userTO.getStatus());
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, userTO.getKey()));
+    }
+
+    @Test
+    public void read() {
+        UserService userService2 = clientFactory.create("rossini", ADMIN_PWD).getService(UserService.class);
+
+        try {
+            userService2.read(1L);
+            fail();
+        } catch (AccessControlException e) {
+            assertNotNull(e);
+        }
+
+        UserSelfService userSelfService2 = clientFactory.create("rossini", ADMIN_PWD).getService(UserSelfService.class);
+        UserTO userTO = userSelfService2.read();
+        assertEquals("rossini", userTO.getUsername());
+    }
+
+    @Test
+    public void updateWithoutApproval() {
+        // 1. create user as admin
+        UserTO created = createUser(UserITCase.getUniqueSampleTO("anonymous@syncope.apache.org"));
+        assertNotNull(created);
+        assertFalse(created.getUsername().endsWith("XX"));
+
+        // 2. self-update (username) - works
+        UserMod userMod = new UserMod();
+        userMod.setUsername(created.getUsername() + "XX");
+
+        SyncopeClient authClient = clientFactory.create(created.getUsername(), "password123");
+        UserTO updated = authClient.getService(UserSelfService.class).update(created.getKey(), userMod).
+                readEntity(UserTO.class);
+        assertNotNull(updated);
+        assertEquals(ActivitiDetector.isActivitiEnabledForUsers() ? "active" : "created", updated.getStatus());
+        assertTrue(updated.getUsername().endsWith("XX"));
+    }
+
+    @Test
+    public void updateWitApproval() {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        // 1. create user as admin
+        UserTO created = createUser(UserITCase.getUniqueSampleTO("anonymous@syncope.apache.org"));
+        assertNotNull(created);
+        assertFalse(created.getUsername().endsWith("XX"));
+
+        // 2. self-update (username + memberships + resource) - works but needs approval
+        MembershipMod membershipMod = new MembershipMod();
+        membershipMod.setRole(7L);
+        AttrMod testAttrMod = new AttrMod();
+        testAttrMod.setSchema("testAttribute");
+        testAttrMod.getValuesToBeAdded().add("a value");
+        membershipMod.getPlainAttrsToUpdate().add(testAttrMod);
+
+        UserMod userMod = new UserMod();
+        userMod.setUsername(created.getUsername() + "XX");
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getResourcesToAdd().add(RESOURCE_NAME_TESTDB);
+        userMod.setPassword("newPassword123");
+        StatusMod statusMod = new StatusMod();
+        statusMod.setOnSyncope(false);
+        statusMod.getResourceNames().add(RESOURCE_NAME_TESTDB);
+        userMod.setPwdPropRequest(statusMod);
+
+        SyncopeClient authClient = clientFactory.create(created.getUsername(), "password123");
+        UserTO updated = authClient.getService(UserSelfService.class).update(created.getKey(), userMod).
+                readEntity(UserTO.class);
+        assertNotNull(updated);
+        assertEquals("updateApproval", updated.getStatus());
+        assertFalse(updated.getUsername().endsWith("XX"));
+        assertTrue(updated.getMemberships().isEmpty());
+
+        // no propagation happened
+        assertTrue(updated.getResources().isEmpty());
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, updated.getKey());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+
+        // 3. approve self-update as admin
+        WorkflowFormTO form = userWorkflowService.getFormForUser(updated.getKey());
+        form = userWorkflowService.claimForm(form.getTaskId());
+        Map<String, WorkflowFormPropertyTO> props = form.getPropertyMap();
+        props.get("approve").setValue(Boolean.TRUE.toString());
+        form.getProperties().clear();
+        form.getProperties().addAll(props.values());
+        updated = userWorkflowService.submitForm(form);
+        assertNotNull(updated);
+        assertEquals("active", updated.getStatus());
+        assertTrue(updated.getUsername().endsWith("XX"));
+        assertEquals(1, updated.getMemberships().size());
+
+        // check that propagation also happened
+        assertTrue(updated.getResources().contains(RESOURCE_NAME_TESTDB));
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_TESTDB, SubjectType.USER, updated.getKey()));
+    }
+
+    @Test
+    public void delete() {
+        UserTO created = createUser(UserITCase.getUniqueSampleTO("anonymous@syncope.apache.org"));
+        assertNotNull(created);
+
+        SyncopeClient authClient = clientFactory.create(created.getUsername(), "password123");
+        UserTO deleted = authClient.getService(UserSelfService.class).delete().readEntity(UserTO.class);
+        assertNotNull(deleted);
+        assertEquals(ActivitiDetector.isActivitiEnabledForUsers() ? "deleteApproval" : null, deleted.getStatus());
+    }
+
+    @Test
+    public void issueSYNCOPE373() {
+        UserTO userTO = userSelfService.read();
+        assertEquals(ADMIN_UNAME, userTO.getUsername());
+    }
+
+    @Test
+    public void noContent() throws IOException {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        SyncopeClient anonClient = clientFactory.createAnonymous();
+        UserSelfService noContentService = anonClient.prefer(UserSelfService.class, Preference.RETURN_NO_CONTENT);
+
+        UserTO user = UserITCase.getUniqueSampleTO("nocontent-anonymous@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()));
+    }
+
+    @Test
+    public void passwordReset() {
+        // 1. create an user with security question and answer
+        UserTO user = UserITCase.getUniqueSampleTO("pwdReset@syncope.apache.org");
+        user.setSecurityQuestion(1L);
+        user.setSecurityAnswer("Rossi");
+        createUser(user);
+
+        // 2. verify that new user is able to authenticate
+        SyncopeClient authClient = clientFactory.create(user.getUsername(), "password123");
+        UserTO read = authClient.getService(UserSelfService.class).read();
+        assertNotNull(read);
+
+        // 3. request password reset (as anonymous) providing the expected security answer
+        SyncopeClient anonClient = clientFactory.createAnonymous();
+        try {
+            anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "WRONG");
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.InvalidSecurityAnswer, e.getType());
+        }
+        anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "Rossi");
+
+        // 4. get token (normally sent via e-mail, now reading as admin)
+        String token = userService.read(read.getKey()).getToken();
+        assertNotNull(token);
+
+        // 5. confirm password reset
+        try {
+            anonClient.getService(UserSelfService.class).confirmPasswordReset("WRONG TOKEN", "newPassword");
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+            assertTrue(e.getMessage().contains("WRONG TOKEN"));
+        }
+        anonClient.getService(UserSelfService.class).confirmPasswordReset(token, "newPassword");
+
+        // 6. verify that password was reset and token removed
+        authClient = clientFactory.create(user.getUsername(), "newPassword");
+        read = authClient.getService(UserSelfService.class).read();
+        assertNotNull(read);
+        assertNull(read.getToken());
+    }
+
+    @Test
+    public void passwordResetWithoutSecurityQuestion() {
+        // 0. disable security question for password reset
+        configurationService.set("passwordReset.securityQuestion",
+                attrTO("passwordReset.securityQuestion", "false"));
+
+        // 1. create an user with security question and answer
+        UserTO user = UserITCase.getUniqueSampleTO("pwdResetNoSecurityQuestion@syncope.apache.org");
+        createUser(user);
+
+        // 2. verify that new user is able to authenticate
+        SyncopeClient authClient = clientFactory.create(user.getUsername(), "password123");
+        UserTO read = authClient.getService(UserSelfService.class).read();
+        assertNotNull(read);
+
+        // 3. request password reset (as anonymous) with no security answer
+        SyncopeClient anonClient = clientFactory.createAnonymous();
+        anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), null);
+
+        // 4. get token (normally sent via e-mail, now reading as admin)
+        String token = userService.read(read.getKey()).getToken();
+        assertNotNull(token);
+
+        // 5. confirm password reset
+        try {
+            anonClient.getService(UserSelfService.class).confirmPasswordReset("WRONG TOKEN", "newPassword");
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+            assertTrue(e.getMessage().contains("WRONG TOKEN"));
+        }
+        anonClient.getService(UserSelfService.class).confirmPasswordReset(token, "newPassword");
+
+        // 6. verify that password was reset and token removed
+        authClient = clientFactory.create(user.getUsername(), "newPassword");
+        read = authClient.getService(UserSelfService.class).read();
+        assertNotNull(read);
+        assertNull(read.getToken());
+
+        // 7. re-enable security question for password reset
+        configurationService.set("passwordReset.securityQuestion",
+                attrTO("passwordReset.securityQuestion", "true"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserWorkflowITCase.java
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserWorkflowITCase.java b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserWorkflowITCase.java
new file mode 100644
index 0000000..9c81d0f
--- /dev/null
+++ b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/UserWorkflowITCase.java
@@ -0,0 +1,299 @@
+/*
+ * 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.server.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.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.MembershipTO;
+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.ClientExceptionType;
+import org.apache.syncope.common.rest.api.service.UserWorkflowService;
+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 UserWorkflowITCase extends AbstractITCase {
+
+    @Test
+    public void createWithReject() {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        UserTO userTO = UserITCase.getUniqueSampleTO("createWithReject@syncope.apache.org");
+        userTO.getResources().add(RESOURCE_NAME_TESTDB);
+
+        // User with role 9 are defined in workflow as subject to approval
+        MembershipTO membershipTO = new MembershipTO();
+        membershipTO.setRoleId(9L);
+        userTO.getMemberships().add(membershipTO);
+
+        // 1. create user with role 9
+        userTO = createUser(userTO);
+        assertNotNull(userTO);
+        assertEquals(1, userTO.getMemberships().size());
+        assertEquals(9, userTO.getMemberships().get(0).getRoleId());
+        assertEquals("createApproval", userTO.getStatus());
+
+        // 2. request if there is any pending task for user just created
+        WorkflowFormTO form = userWorkflowService.getFormForUser(userTO.getKey());
+        assertNotNull(form);
+        assertNotNull(form.getUserKey());
+        assertEquals(userTO.getKey(), form.getUserKey());
+        assertNotNull(form.getTaskId());
+        assertNull(form.getOwner());
+
+        // 3. claim task from rossini, not in role 7 (designated for approval in workflow definition): fail
+        UserWorkflowService userService2 = clientFactory.create(
+                "rossini", ADMIN_PWD).getService(UserWorkflowService.class);
+
+        try {
+            userService2.claimForm(form.getTaskId());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.Workflow, e.getType());
+        }
+
+        // 4. claim task from bellini, in role 7
+        UserWorkflowService userService3 = clientFactory.create(
+                "bellini", ADMIN_PWD).getService(UserWorkflowService.class);
+
+        form = userService3.claimForm(form.getTaskId());
+        assertNotNull(form);
+        assertNotNull(form.getTaskId());
+        assertNotNull(form.getOwner());
+
+        // 5. reject user
+        Map<String, WorkflowFormPropertyTO> props = form.getPropertyMap();
+        props.get("approve").setValue(Boolean.FALSE.toString());
+        props.get("rejectReason").setValue("I don't like him.");
+        form.getProperties().clear();
+        form.getProperties().addAll(props.values());
+        userTO = userService3.submitForm(form);
+        assertNotNull(userTO);
+        assertEquals("rejected", userTO.getStatus());
+
+        // 6. check that rejected user was not propagated to external resource (SYNCOPE-364)
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+        Exception exception = null;
+        try {
+            jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?",
+                    new String[] { userTO.getUsername() }, Integer.class);
+        } catch (EmptyResultDataAccessException e) {
+            exception = e;
+        }
+        assertNotNull(exception);
+    }
+
+    @Test
+    public void createWithApproval() {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        // read forms *before* any operation
+        List<WorkflowFormTO> forms = userWorkflowService.getForms();
+        assertNotNull(forms);
+        int preForms = forms.size();
+
+        UserTO userTO = UserITCase.getUniqueSampleTO("createWithApproval@syncope.apache.org");
+        userTO.getResources().add(RESOURCE_NAME_TESTDB);
+
+        // User with role 9 are defined in workflow as subject to approval
+        MembershipTO membershipTO = new MembershipTO();
+        membershipTO.setRoleId(9L);
+        userTO.getMemberships().add(membershipTO);
+
+        // 1. create user with role 9 (and verify that no propagation occurred)
+        userTO = createUser(userTO);
+        assertNotNull(userTO);
+        assertEquals(1, userTO.getMemberships().size());
+        assertEquals(9, userTO.getMemberships().get(0).getRoleId());
+        assertEquals("createApproval", userTO.getStatus());
+        assertEquals(Collections.singleton(RESOURCE_NAME_TESTDB), userTO.getResources());
+
+        assertTrue(userTO.getPropagationStatusTOs().isEmpty());
+
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+
+        Exception exception = null;
+        try {
+            jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?",
+                    new String[] { userTO.getUsername() }, Integer.class);
+        } catch (EmptyResultDataAccessException e) {
+            exception = e;
+        }
+        assertNotNull(exception);
+
+        // 2. request if there is any pending form for user just created
+        forms = userWorkflowService.getForms();
+        assertNotNull(forms);
+        assertEquals(preForms + 1, forms.size());
+
+        WorkflowFormTO form = userWorkflowService.getFormForUser(userTO.getKey());
+        assertNotNull(form);
+        assertNotNull(form.getTaskId());
+        assertNull(form.getOwner());
+
+        // 4. claim task (from admin)
+        form = userWorkflowService.claimForm(form.getTaskId());
+        assertNotNull(form);
+        assertNotNull(form.getTaskId());
+        assertNotNull(form.getOwner());
+
+        // 5. approve user (and verify that propagation occurred)
+        Map<String, WorkflowFormPropertyTO> props = form.getPropertyMap();
+        props.get("approve").setValue(Boolean.TRUE.toString());
+        form.getProperties().clear();
+        form.getProperties().addAll(props.values());
+        userTO = userWorkflowService.submitForm(form);
+        assertNotNull(userTO);
+        assertEquals("active", userTO.getStatus());
+        assertEquals(Collections.singleton(RESOURCE_NAME_TESTDB), userTO.getResources());
+
+        exception = null;
+        try {
+            final String username = jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?", String.class,
+                    userTO.getUsername());
+            assertEquals(userTO.getUsername(), username);
+        } catch (EmptyResultDataAccessException e) {
+            exception = e;
+        }
+        assertNull(exception);
+
+        // 6. update user
+        UserMod userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.setPassword("anotherPassword123");
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+    }
+
+    @Test
+    public void issueSYNCOPE15() {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        // read forms *before* any operation
+        List<WorkflowFormTO> forms = userWorkflowService.getForms();
+        assertNotNull(forms);
+        int preForms = forms.size();
+
+        UserTO userTO = UserITCase.getUniqueSampleTO("issueSYNCOPE15@syncope.apache.org");
+        userTO.getResources().clear();
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getMemberships().clear();
+
+        // User with role 9 are defined in workflow as subject to approval
+        MembershipTO membershipTO = new MembershipTO();
+        membershipTO.setRoleId(9L);
+        userTO.getMemberships().add(membershipTO);
+
+        // 1. create user with role 9 (and verify that no propagation occurred)
+        userTO = createUser(userTO);
+        assertNotNull(userTO);
+        assertNotEquals(0L, userTO.getKey());
+        assertNotNull(userTO.getCreationDate());
+        assertNotNull(userTO.getCreator());
+        assertNotNull(userTO.getLastChangeDate());
+        assertNotNull(userTO.getLastModifier());
+        assertEquals(userTO.getCreationDate(), userTO.getLastChangeDate());
+
+        // 2. request if there is any pending form for user just created
+        forms = userWorkflowService.getForms();
+        assertEquals(preForms + 1, forms.size());
+
+        WorkflowFormTO form = userWorkflowService.getFormForUser(userTO.getKey());
+        assertNotNull(form);
+
+        // 3. first claim ny bellini ....
+        UserWorkflowService userService3 = clientFactory.create(
+                "bellini", ADMIN_PWD).getService(UserWorkflowService.class);
+
+        form = userService3.claimForm(form.getTaskId());
+        assertNotNull(form);
+        assertNotNull(form.getTaskId());
+        assertNotNull(form.getOwner());
+
+        // 4. second claim task by admin
+        form = userWorkflowService.claimForm(form.getTaskId());
+        assertNotNull(form);
+
+        // 5. approve user
+        final Map<String, WorkflowFormPropertyTO> props = form.getPropertyMap();
+        props.get("approve").setValue(Boolean.TRUE.toString());
+        form.getProperties().clear();
+        form.getProperties().addAll(props.values());
+
+        // 6. submit approve
+        userTO = userWorkflowService.submitForm(form);
+        assertNotNull(userTO);
+        assertEquals(preForms, userWorkflowService.getForms().size());
+        assertNull(userWorkflowService.getFormForUser(userTO.getKey()));
+
+        // 7. search approval into the history as well
+        forms = userWorkflowService.getFormsByName(userTO.getKey(), "Create approval");
+        assertFalse(forms.isEmpty());
+
+        int count = 0;
+        for (WorkflowFormTO hform : forms) {
+            if (form.getTaskId().equals(hform.getTaskId())) {
+                count++;
+
+                assertEquals("createApproval", hform.getKey());
+                assertNotNull(hform.getCreateTime());
+                assertNotNull(hform.getDueDate());
+                assertTrue(Boolean.parseBoolean(hform.getPropertyMap().get("approve").getValue()));
+                assertNull(hform.getPropertyMap().get("rejectReason").getValue());
+            }
+        }
+        assertEquals(1, count);
+
+        userService.delete(userTO.getKey());
+
+        try {
+            userService.read(userTO.getKey());
+            fail();
+        } catch (Exception ignore) {
+            assertNotNull(ignore);
+        }
+
+        try {
+            userWorkflowService.getFormsByName(userTO.getKey(), "Create approval");
+            fail();
+        } catch (Exception ignore) {
+            assertNotNull(ignore);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirAttrITCase.java
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirAttrITCase.java b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirAttrITCase.java
new file mode 100644
index 0000000..1dc6f20
--- /dev/null
+++ b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirAttrITCase.java
@@ -0,0 +1,872 @@
+/*
+ * 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.server.reference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Map;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+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.ConnInstanceTO;
+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.ResourceTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.common.rest.api.service.ResourceService;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+@FixMethodOrder(MethodSorters.JVM)
+public class VirAttrITCase extends AbstractITCase {
+
+    @Test
+    public void issueSYNCOPE16() {
+        UserTO userTO = UserITCase.getUniqueSampleTO("issue16@apache.org");
+
+        MembershipTO membershipTO = new MembershipTO();
+        membershipTO.setRoleId(8L);
+        userTO.getMemberships().add(membershipTO);
+
+        // 1. create user
+        UserTO actual = createUser(userTO);
+        assertNotNull(actual);
+
+        // 2. check for virtual attribute value
+        actual = userService.read(actual.getKey());
+        assertNotNull(actual);
+        assertEquals("virtualvalue", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+
+        UserMod userMod = new UserMod();
+        userMod.setKey(actual.getKey());
+        userMod.getVirAttrsToRemove().add("virtualdata");
+        userMod.getVirAttrsToUpdate().add(attrMod("virtualdata", "virtualupdated"));
+
+        // 3. update virtual attribute
+        actual = updateUser(userMod);
+        assertNotNull(actual);
+
+        // 4. check for virtual attribute value
+        actual = userService.read(actual.getKey());
+        assertNotNull(actual);
+        assertEquals("virtualupdated", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+    }
+
+    @Test
+    public void issueSYNCOPE260() {
+        // ----------------------------------
+        // create user and check virtual attribute value propagation
+        // ----------------------------------
+        UserTO userTO = UserITCase.getUniqueSampleTO("260@a.com");
+        userTO.getResources().add(RESOURCE_NAME_WS2);
+
+        userTO = createUser(userTO);
+        assertNotNull(userTO);
+        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
+        assertEquals(RESOURCE_NAME_WS2, userTO.getPropagationStatusTOs().get(0).getResource());
+        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
+
+        ConnObjectTO connObjectTO =
+                resourceService.getConnectorObject(RESOURCE_NAME_WS2, SubjectType.USER, userTO.getKey());
+        assertNotNull(connObjectTO);
+        assertEquals("virtualvalue", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
+        // ----------------------------------
+
+        // ----------------------------------
+        // update user virtual attribute and check virtual attribute value update propagation
+        // ----------------------------------
+        UserMod userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+
+        AttrMod attrMod = new AttrMod();
+        attrMod.setSchema("virtualdata");
+        attrMod.getValuesToBeRemoved().add("virtualvalue");
+        attrMod.getValuesToBeAdded().add("virtualvalue2");
+
+        userMod.getVirAttrsToUpdate().add(attrMod);
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
+        assertEquals("ws-target-resource-2", userTO.getPropagationStatusTOs().get(0).getResource());
+        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
+
+        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_WS2, SubjectType.USER, userTO.getKey());
+        assertNotNull(connObjectTO);
+        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
+        // ----------------------------------
+
+        // ----------------------------------
+        // suspend/reactivate user and check virtual attribute value (unchanged)
+        // ----------------------------------
+        StatusMod statusMod = new StatusMod();
+        statusMod.setType(StatusMod.ModType.SUSPEND);
+        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
+        assertEquals("suspended", userTO.getStatus());
+
+        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_WS2, SubjectType.USER, userTO.getKey());
+        assertNotNull(connObjectTO);
+        assertFalse(connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
+        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
+
+        statusMod = new StatusMod();
+        statusMod.setType(StatusMod.ModType.REACTIVATE);
+        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
+        assertEquals("active", userTO.getStatus());
+
+        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_WS2, SubjectType.USER, userTO.getKey());
+        assertNotNull(connObjectTO);
+        assertFalse(connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
+        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
+        // ----------------------------------
+
+        // ----------------------------------
+        // update user attribute and check virtual attribute value (unchanged)
+        // ----------------------------------
+        userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+
+        attrMod = new AttrMod();
+        attrMod.setSchema("surname");
+        attrMod.getValuesToBeRemoved().add("Surname");
+        attrMod.getValuesToBeAdded().add("Surname2");
+
+        userMod.getPlainAttrsToUpdate().add(attrMod);
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
+        assertEquals(RESOURCE_NAME_WS2, userTO.getPropagationStatusTOs().get(0).getResource());
+        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
+
+        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_WS2, SubjectType.USER, userTO.getKey());
+        assertNotNull(connObjectTO);
+        assertEquals("Surname2", connObjectTO.getPlainAttrMap().get("SURNAME").getValues().get(0));
+
+        // attribute "name" mapped on virtual attribute "virtualdata" shouldn't be changed
+        assertFalse(connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
+        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
+        // ----------------------------------
+
+        // ----------------------------------
+        // remove user virtual attribute and check virtual attribute value (reset)
+        // ----------------------------------
+        userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.getVirAttrsToRemove().add("virtualdata");
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        assertTrue(userTO.getVirAttrs().isEmpty());
+        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
+        assertEquals(RESOURCE_NAME_WS2, userTO.getPropagationStatusTOs().get(0).getResource());
+        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
+
+        connObjectTO = resourceService.getConnectorObject(RESOURCE_NAME_WS2, SubjectType.USER, userTO.getKey());
+        assertNotNull(connObjectTO);
+
+        // attribute "name" mapped on virtual attribute "virtualdata" should be reset
+        assertTrue(connObjectTO.getPlainAttrMap().get("NAME").getValues() == null
+                || connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
+        // ----------------------------------
+    }
+
+    @Test
+    public void virAttrCache() {
+        UserTO userTO = UserITCase.getUniqueSampleTO("virattrcache@apache.org");
+        userTO.getVirAttrs().clear();
+
+        AttrTO virAttrTO = new AttrTO();
+        virAttrTO.setSchema("virtualdata");
+        virAttrTO.getValues().add("virattrcache");
+        userTO.getVirAttrs().add(virAttrTO);
+
+        userTO.getMemberships().clear();
+        userTO.getResources().clear();
+        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
+
+        // 1. create user
+        UserTO actual = createUser(userTO);
+        assertNotNull(actual);
+
+        // 2. check for virtual attribute value
+        actual = userService.read(actual.getKey());
+        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+
+        // 3. update virtual attribute directly
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+
+        String value = jdbcTemplate.queryForObject(
+                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
+        assertEquals("virattrcache", value);
+
+        jdbcTemplate.update("UPDATE testsync set USERNAME='virattrcache2' WHERE ID=?", actual.getKey());
+
+        value = jdbcTemplate.queryForObject(
+                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
+        assertEquals("virattrcache2", value);
+
+        // 4. check for cached attribute value
+        actual = userService.read(actual.getKey());
+        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+
+        UserMod userMod = new UserMod();
+        userMod.setKey(actual.getKey());
+
+        AttrMod virtualdata = new AttrMod();
+        virtualdata.setSchema("virtualdata");
+        virtualdata.getValuesToBeAdded().add("virtualupdated");
+
+        userMod.getVirAttrsToRemove().add("virtualdata");
+        userMod.getVirAttrsToUpdate().add(virtualdata);
+
+        // 5. update virtual attribute
+        actual = updateUser(userMod);
+        assertNotNull(actual);
+
+        // 6. check for virtual attribute value
+        actual = userService.read(actual.getKey());
+        assertNotNull(actual);
+        assertEquals("virtualupdated", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+    }
+
+    @Test
+    public void issueSYNCOPE397() {
+        ResourceTO csv = resourceService.read(RESOURCE_NAME_CSV);
+        final MappingTO origMapping = SerializationUtils.clone(csv.getUmapping());
+        try {
+            // change mapping of resource-csv
+            assertNotNull(origMapping);
+            for (MappingItemTO item : csv.getUmapping().getItems()) {
+                if ("email".equals(item.getIntAttrName())) {
+                    // unset internal attribute mail and set virtual attribute virtualdata as mapped to external email
+                    item.setIntMappingType(IntMappingType.UserVirtualSchema);
+                    item.setIntAttrName("virtualdata");
+                    item.setPurpose(MappingPurpose.BOTH);
+                    item.setExtAttrName("email");
+                }
+            }
+
+            resourceService.update(csv.getKey(), csv);
+            csv = resourceService.read(RESOURCE_NAME_CSV);
+            assertNotNull(csv.getUmapping());
+
+            boolean found = false;
+            for (MappingItemTO item : csv.getUmapping().getItems()) {
+                if ("email".equals(item.getExtAttrName()) && "virtualdata".equals(item.getIntAttrName())) {
+                    found = true;
+                }
+            }
+
+            assertTrue(found);
+
+            // create a new user
+            UserTO userTO = UserITCase.getUniqueSampleTO("syncope397@syncope.apache.org");
+            userTO.getResources().clear();
+            userTO.getMemberships().clear();
+            userTO.getDerAttrs().clear();
+            userTO.getVirAttrs().clear();
+
+            userTO.getDerAttrs().add(attrTO("csvuserid", null));
+            userTO.getDerAttrs().add(attrTO("cn", null));
+            userTO.getVirAttrs().add(attrTO("virtualdata", "test@testone.org"));
+            // assign resource-csv to user
+            userTO.getResources().add(RESOURCE_NAME_CSV);
+            // save user
+            UserTO created = createUser(userTO);
+            // make std controls about user
+            assertNotNull(created);
+            assertTrue(RESOURCE_NAME_CSV.equals(created.getResources().iterator().next()));
+            // update user
+            UserTO toBeUpdated = userService.read(created.getKey());
+            UserMod userMod = new UserMod();
+            userMod.setKey(toBeUpdated.getKey());
+            userMod.setPassword("password2");
+            // assign new resource to user
+            userMod.getResourcesToAdd().add(RESOURCE_NAME_WS2);
+            //modify virtual attribute
+            userMod.getVirAttrsToRemove().add("virtualdata");
+            userMod.getVirAttrsToUpdate().add(attrMod("virtualdata", "test@testoneone.com"));
+
+            // check Syncope change password
+            StatusMod pwdPropRequest = new StatusMod();
+            pwdPropRequest.setOnSyncope(true);
+            pwdPropRequest.getResourceNames().add(RESOURCE_NAME_WS2);
+            userMod.setPwdPropRequest(pwdPropRequest);
+
+            toBeUpdated = updateUser(userMod);
+            assertNotNull(toBeUpdated);
+            assertEquals("test@testoneone.com", toBeUpdated.getVirAttrs().get(0).getValues().get(0));
+            // check if propagates correctly with assertEquals on size of tasks list
+            assertEquals(2, toBeUpdated.getPropagationStatusTOs().size());
+        } finally {
+            // restore mapping of resource-csv
+            csv.setUmapping(origMapping);
+            resourceService.update(csv.getKey(), csv);
+        }
+    }
+
+    @Test
+    public void issueSYNCOPE442() {
+        UserTO userTO = UserITCase.getUniqueSampleTO("syncope442@apache.org");
+        userTO.getVirAttrs().clear();
+
+        AttrTO virAttrTO = new AttrTO();
+        virAttrTO.setSchema("virtualdata");
+        virAttrTO.getValues().add("virattrcache");
+        userTO.getVirAttrs().add(virAttrTO);
+
+        userTO.getMemberships().clear();
+        userTO.getResources().clear();
+        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
+
+        // 1. create user
+        UserTO actual = createUser(userTO);
+        assertNotNull(actual);
+
+        // 2. check for virtual attribute value
+        actual = userService.read(actual.getKey());
+        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+
+        // ----------------------------------------
+        // 3. force cache expiring without any modification
+        // ----------------------------------------
+        String jdbcURL = null;
+        ConnInstanceTO connInstanceBean = connectorService.readByResource(RESOURCE_NAME_DBVIRATTR);
+        for (ConnConfProperty prop : connInstanceBean.getConfiguration()) {
+            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
+                jdbcURL = prop.getValues().iterator().next().toString();
+                prop.getValues().clear();
+                prop.getValues().add("jdbc:h2:tcp://localhost:9092/xxx");
+            }
+        }
+
+        connectorService.update(connInstanceBean.getKey(), connInstanceBean);
+
+        UserMod userMod = new UserMod();
+        userMod.setKey(actual.getKey());
+
+        AttrMod virtualdata = new AttrMod();
+        virtualdata.setSchema("virtualdata");
+        virtualdata.getValuesToBeAdded().add("virtualupdated");
+
+        userMod.getVirAttrsToRemove().add("virtualdata");
+        userMod.getVirAttrsToUpdate().add(virtualdata);
+
+        actual = updateUser(userMod);
+        assertNotNull(actual);
+        // ----------------------------------------
+
+        // ----------------------------------------
+        // 4. update virtual attribute
+        // ----------------------------------------
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+
+        String value = jdbcTemplate.queryForObject(
+                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
+        assertEquals("virattrcache", value);
+
+        jdbcTemplate.update("UPDATE testsync set USERNAME='virattrcache2' WHERE ID=?", actual.getKey());
+
+        value = jdbcTemplate.queryForObject(
+                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
+        assertEquals("virattrcache2", value);
+        // ----------------------------------------
+
+        actual = userService.read(actual.getKey());
+        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+
+        // ----------------------------------------
+        // 5. restore connector
+        // ----------------------------------------
+        for (ConnConfProperty prop : connInstanceBean.getConfiguration()) {
+            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
+                prop.getValues().clear();
+                prop.getValues().add(jdbcURL);
+            }
+        }
+
+        connectorService.update(connInstanceBean.getKey(), connInstanceBean);
+        // ----------------------------------------
+
+        actual = userService.read(actual.getKey());
+        assertEquals("virattrcache2", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
+    }
+
+    @Test
+    public void issueSYNCOPE436() {
+        UserTO userTO = UserITCase.getUniqueSampleTO("syncope436@syncope.apache.org");
+        userTO.getMemberships().clear();
+        userTO.getResources().clear();
+        userTO.getResources().add(RESOURCE_NAME_LDAP);
+        userTO.getVirAttrs().add(attrTO("virtualReadOnly", "readOnly"));
+        userTO = createUser(userTO);
+        //Finding no values because the virtual attribute is readonly 
+        assertTrue(userTO.getVirAttrMap().get("virtualReadOnly").getValues().isEmpty());
+    }
+
+    @Test
+    public void issueSYNCOPE453() {
+        final String resourceName = "issueSYNCOPE453-Res-" + getUUIDString();
+        final String roleName = "issueSYNCOPE453-Role-" + getUUIDString();
+
+        // -------------------------------------------
+        // Create a resource ad-hoc
+        // -------------------------------------------
+        final ResourceTO resourceTO = new ResourceTO();
+
+        resourceTO.setKey(resourceName);
+        resourceTO.setConnectorId(107L);
+
+        MappingTO mapping = new MappingTO();
+
+        MappingItemTO item = new MappingItemTO();
+        item.setIntAttrName("aLong");
+        item.setIntMappingType(IntMappingType.UserPlainSchema);
+        item.setExtAttrName(roleName);
+        item.setPurpose(MappingPurpose.PROPAGATION);
+        item.setAccountid(true);
+        mapping.setAccountIdItem(item);
+
+        item = new MappingItemTO();
+        item.setExtAttrName("USERNAME");
+        item.setIntAttrName("username");
+        item.setIntMappingType(IntMappingType.Username);
+        item.setPurpose(MappingPurpose.PROPAGATION);
+        mapping.getItems().add(item);
+
+        item = new MappingItemTO();
+        item.setExtAttrName("EMAIL");
+        item.setIntAttrName("rvirtualdata");
+        item.setIntMappingType(IntMappingType.RoleVirtualSchema);
+        item.setPurpose(MappingPurpose.PROPAGATION);
+        mapping.getItems().add(item);
+
+        resourceTO.setUmapping(mapping);
+        assertNotNull(getObject(
+                resourceService.create(resourceTO).getLocation(), ResourceService.class, ResourceTO.class));
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Create a role ad-hoc
+        // -------------------------------------------
+        RoleTO roleTO = new RoleTO();
+        roleTO.setName(roleName);
+        roleTO.setParent(8L);
+        roleTO.getRVirAttrTemplates().add("rvirtualdata");
+        roleTO.getVirAttrs().add(attrTO("rvirtualdata", "ml@role.it"));
+        roleTO.getResources().add(RESOURCE_NAME_LDAP);
+        roleTO = createRole(roleTO);
+        assertEquals(1, roleTO.getVirAttrs().size());
+        assertEquals("ml@role.it", roleTO.getVirAttrs().get(0).getValues().get(0));
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Create new user
+        // -------------------------------------------
+        UserTO userTO = UserITCase.getUniqueSampleTO("syncope453@syncope.apache.org");
+        userTO.getPlainAttrs().add(attrTO("aLong", "123"));
+        userTO.getResources().clear();
+        userTO.getResources().add(resourceName);
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getMemberships().clear();
+
+        final MembershipTO membership = new MembershipTO();
+        membership.setRoleId(roleTO.getKey());
+        membership.getVirAttrs().add(attrTO("mvirtualdata", "mvirtualvalue"));
+        userTO.getMemberships().add(membership);
+
+        userTO = createUser(userTO);
+        assertEquals(2, userTO.getPropagationStatusTOs().size());
+        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
+        assertTrue(userTO.getPropagationStatusTOs().get(1).getStatus().isSuccessful());
+
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+
+        final Map<String, Object> actuals = jdbcTemplate.queryForMap(
+                "SELECT id, surname, email FROM testsync WHERE id=?",
+                new Object[] { Integer.parseInt(userTO.getPlainAttrMap().get("aLong").getValues().get(0)) });
+
+        assertEquals(userTO.getPlainAttrMap().get("aLong").getValues().get(0), actuals.get("id").toString());
+        assertEquals("ml@role.it", actuals.get("email"));
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Delete resource and role ad-hoc
+        // -------------------------------------------
+        resourceService.delete(resourceName);
+        roleService.delete(roleTO.getKey());
+        // -------------------------------------------
+    }
+
+    @Test
+    public void issueSYNCOPE459() {
+        UserTO userTO = UserITCase.getUniqueSampleTO("syncope459@apache.org");
+        userTO.getResources().clear();
+        userTO.getMemberships().clear();
+        userTO.getVirAttrs().clear();
+
+        final AttrTO virtualReadOnly = attrTO("virtualReadOnly", "");
+        virtualReadOnly.getValues().clear();
+
+        userTO.getVirAttrs().add(virtualReadOnly);
+
+        userTO = createUser(userTO);
+
+        assertNotNull(userTO.getVirAttrMap().get("virtualReadOnly"));
+
+        UserMod userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+
+        AttrMod virtualdata = new AttrMod();
+        virtualdata.setSchema("virtualdata");
+
+        userMod.getVirAttrsToUpdate().add(virtualdata);
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO.getVirAttrMap().get("virtualdata"));
+    }
+
+    @Test
+    public void issueSYNCOPE458() {
+        // -------------------------------------------
+        // Create a role ad-hoc
+        // -------------------------------------------
+        final String roleName = "issueSYNCOPE458-Role-" + getUUIDString();
+        RoleTO roleTO = new RoleTO();
+        roleTO.setName(roleName);
+        roleTO.setParent(2L);
+        roleTO.setInheritTemplates(true);
+        roleTO = createRole(roleTO);
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Update resource-db-virattr mapping adding new membership virtual schema mapping
+        // -------------------------------------------
+        ResourceTO resourceDBVirAttr = resourceService.read(RESOURCE_NAME_DBVIRATTR);
+        assertNotNull(resourceDBVirAttr);
+
+        final MappingTO resourceUMapping = resourceDBVirAttr.getUmapping();
+
+        MappingItemTO item = new MappingItemTO();
+        item.setIntAttrName("mvirtualdata");
+        item.setIntMappingType(IntMappingType.MembershipVirtualSchema);
+        item.setExtAttrName("EMAIL");
+        item.setPurpose(MappingPurpose.BOTH);
+
+        resourceUMapping.addItem(item);
+
+        resourceDBVirAttr.setUmapping(resourceUMapping);
+
+        resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr);
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Create new user
+        // -------------------------------------------
+        UserTO userTO = UserITCase.getUniqueSampleTO("syncope458@syncope.apache.org");
+        userTO.getResources().clear();
+        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getMemberships().clear();
+
+        // add membership, with virtual attribute populated, to user
+        MembershipTO membership = new MembershipTO();
+        membership.setRoleId(roleTO.getKey());
+        membership.getVirAttrs().add(attrTO("mvirtualdata", "syncope458@syncope.apache.org"));
+        userTO.getMemberships().add(membership);
+
+        //propagate user
+        userTO = createUser(userTO);
+        assertEquals(1, userTO.getPropagationStatusTOs().size());
+        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
+       // -------------------------------------------
+
+        // 1. check if membership has virtual attribute populated
+        assertNotNull(userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata"));
+        assertEquals("syncope458@syncope.apache.org",
+                userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata").getValues().get(0));
+        // -------------------------------------------
+
+        // 2. update membership virtual attribute
+        MembershipMod membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getKey());
+        membershipMod.getVirAttrsToUpdate().add(attrMod("mvirtualdata", "syncope458_NEW@syncope.apache.org"));
+
+        UserMod userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getKey());
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        // 3. check again after update if membership has virtual attribute populated with new value
+        assertNotNull(userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata"));
+        assertEquals("syncope458_NEW@syncope.apache.org", userTO.getMemberships().get(0).getVirAttrMap().get(
+                "mvirtualdata").getValues().get(0));
+
+        // ----------------------------------------
+        // force cache expiring without any modification
+        // ----------------------------------------
+        String jdbcURL = null;
+        ConnInstanceTO connInstanceBean = connectorService.readByResource(RESOURCE_NAME_DBVIRATTR);
+        for (ConnConfProperty prop : connInstanceBean.getConfiguration()) {
+            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
+                jdbcURL = prop.getValues().iterator().next().toString();
+                prop.getValues().clear();
+                prop.getValues().add("jdbc:h2:tcp://localhost:9092/xxx");
+            }
+        }
+
+        connectorService.update(connInstanceBean.getKey(), connInstanceBean);
+
+        membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getKey());
+        membershipMod.getVirAttrsToUpdate().add(attrMod("mvirtualdata", "syncope458_updated@syncope.apache.org"));
+
+        userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getKey());
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        // ----------------------------------
+
+        // change attribute value directly on resource
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+
+        String value = jdbcTemplate.queryForObject(
+                "SELECT EMAIL FROM testsync WHERE ID=?", String.class, userTO.getKey());
+        assertEquals("syncope458_NEW@syncope.apache.org", value);
+
+        jdbcTemplate.update("UPDATE testsync set EMAIL='syncope458_NEW_TWO@syncope.apache.org' WHERE ID=?", userTO.
+                getKey());
+
+        value = jdbcTemplate.queryForObject("SELECT EMAIL FROM testsync WHERE ID=?", String.class, userTO.getKey());
+        assertEquals("syncope458_NEW_TWO@syncope.apache.org", value);
+        // ----------------------------------------
+
+        // ----------------------------------------
+        // restore connector
+        // ----------------------------------------
+        for (ConnConfProperty prop : connInstanceBean.getConfiguration()) {
+            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
+                prop.getValues().clear();
+                prop.getValues().add(jdbcURL);
+            }
+        }
+        connectorService.update(connInstanceBean.getKey(), connInstanceBean);
+        // ----------------------------------------
+
+        userTO = userService.read(userTO.getKey());
+        assertNotNull(userTO);
+        // 4. check virtual attribute synchronization after direct update on resource
+        assertEquals("syncope458_NEW_TWO@syncope.apache.org", userTO.getMemberships().get(0).getVirAttrMap().get(
+                "mvirtualdata").getValues().get(0));
+
+        // 5. remove membership virtual attribute
+        membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getKey());
+        membershipMod.getVirAttrsToRemove().add("mvirtualdata");
+
+        userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getKey());
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        // check again after update if membership hasn't any virtual attribute
+        assertTrue(userTO.getMemberships().get(0).getVirAttrMap().isEmpty());
+
+        // -------------------------------------------
+        // Delete role ad-hoc and restore resource mapping
+        // -------------------------------------------
+        roleService.delete(roleTO.getKey());
+
+        resourceUMapping.removeItem(item);
+        resourceDBVirAttr.setUmapping(resourceUMapping);
+        resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr);
+        // -------------------------------------------
+    }
+
+    @Test
+    public void issueSYNCOPE501() {
+        // PHASE 1: update only user virtual attributes
+
+        // 1. create user and propagate him on resource-db-virattr
+        UserTO userTO = UserITCase.getUniqueSampleTO("syncope501@apache.org");
+        userTO.getResources().clear();
+        userTO.getMemberships().clear();
+        userTO.getVirAttrs().clear();
+
+        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
+
+        // virtualdata is mapped with username
+        final AttrTO virtualData = attrTO("virtualdata", "syncope501@apache.org");
+        userTO.getVirAttrs().add(virtualData);
+
+        userTO = createUser(userTO);
+
+        assertNotNull(userTO.getVirAttrMap().get("virtualdata"));
+        assertEquals("syncope501@apache.org", userTO.getVirAttrMap().get("virtualdata").getValues().get(0));
+
+        // 2. update virtual attribute
+        UserMod userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+
+        final StatusMod statusMod = new StatusMod();
+        statusMod.getResourceNames().addAll(Collections.<String>emptySet());
+        statusMod.setOnSyncope(false);
+
+        userMod.setPwdPropRequest(statusMod);
+        // change virtual attribute value
+        final AttrMod virtualDataMod = new AttrMod();
+        virtualDataMod.setSchema("virtualdata");
+        virtualDataMod.getValuesToBeAdded().add("syncope501_updated@apache.org");
+        virtualDataMod.getValuesToBeRemoved().add("syncope501@apache.org");
+        userMod.getVirAttrsToUpdate().add(virtualDataMod);
+        userMod.getVirAttrsToRemove().add("virtualdata");
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+
+        // 3. check that user virtual attribute has really been updated 
+        assertFalse(userTO.getVirAttrMap().get("virtualdata").getValues().isEmpty());
+        assertEquals("syncope501_updated@apache.org", userTO.getVirAttrMap().get("virtualdata").getValues().get(0));
+
+        // ----------------------------------------------------------
+        // PHASE 2: update only membership virtual attributes
+        // -------------------------------------------
+        // Update resource-db-virattr mapping adding new membership virtual schema mapping
+        // -------------------------------------------
+        ResourceTO resourceDBVirAttr = resourceService.read(RESOURCE_NAME_DBVIRATTR);
+        assertNotNull(resourceDBVirAttr);
+
+        final MappingTO resourceUMapping = resourceDBVirAttr.getUmapping();
+
+        MappingItemTO item = new MappingItemTO();
+        item.setIntAttrName("mvirtualdata");
+        item.setIntMappingType(IntMappingType.MembershipVirtualSchema);
+        item.setExtAttrName("EMAIL");
+        item.setPurpose(MappingPurpose.BOTH);
+
+        resourceUMapping.addItem(item);
+
+        resourceDBVirAttr.setUmapping(resourceUMapping);
+
+        resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr);
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Create a role ad-hoc
+        // -------------------------------------------
+        final String roleName = "issueSYNCOPE501-Role-" + getUUIDString();
+        RoleTO roleTO = new RoleTO();
+        roleTO.setName(roleName);
+        roleTO.setParent(2L);
+        roleTO.setInheritTemplates(true);
+        roleTO = createRole(roleTO);
+        // -------------------------------------------
+
+        // 1. add membership, with virtual attribute populated, to user
+        MembershipMod membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getKey());
+        membershipMod.getVirAttrsToUpdate().add(attrMod("mvirtualdata", "syncope501membership@test.org"));
+
+        userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.setPwdPropRequest(statusMod);
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        assertEquals("syncope501membership@test.org",
+                userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata").getValues().get(0));
+
+        // 2. update only membership virtual attribute and propagate user
+        membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getKey());
+        membershipMod.getVirAttrsToUpdate().add(attrMod("mvirtualdata",
+                "syncope501membership_updated@test.org"));
+        membershipMod.getVirAttrsToRemove().add("syncope501membership@test.org");
+
+        userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getKey());
+        userMod.setPwdPropRequest(statusMod);
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+
+        // 3. check if change has been propagated
+        assertEquals("syncope501membership_updated@test.org", userTO.getMemberships().get(0).getVirAttrMap().
+                get("mvirtualdata").getValues().get(0));
+
+        // 4. delete membership and check on resource attribute deletion
+        userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().get(0).getKey());
+        userMod.setPwdPropRequest(statusMod);
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        assertTrue(userTO.getMemberships().isEmpty());
+
+        // read attribute value directly on resource
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+
+        final String emailValue = jdbcTemplate.queryForObject(
+                "SELECT EMAIL FROM testsync WHERE ID=?", String.class, userTO.getKey());
+        assertTrue(StringUtils.isBlank(emailValue));
+        // ----------------------------------------
+
+        // -------------------------------------------
+        // Delete role ad-hoc and restore resource mapping
+        // -------------------------------------------
+        roleService.delete(roleTO.getKey());
+
+        resourceUMapping.removeItem(item);
+        resourceDBVirAttr.setUmapping(resourceUMapping);
+        resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr);
+        // -------------------------------------------
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirSchemaITCase.java
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirSchemaITCase.java b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirSchemaITCase.java
new file mode 100644
index 0000000..884b5cb
--- /dev/null
+++ b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/VirSchemaITCase.java
@@ -0,0 +1,122 @@
+/*
+ * 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.server.reference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.EntityViolationType;
+import org.apache.syncope.common.lib.types.SchemaType;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.JVM)
+public class VirSchemaITCase extends AbstractITCase {
+
+    @Test
+    public void list() {
+        List<VirSchemaTO> vSchemas = schemaService.list(AttributableType.USER, SchemaType.VIRTUAL);
+        assertFalse(vSchemas.isEmpty());
+        for (VirSchemaTO vSchemaTO : vSchemas) {
+            assertNotNull(vSchemaTO);
+        }
+    }
+
+    @Test
+    public void read() {
+        VirSchemaTO vSchemaTO = schemaService.read(AttributableType.MEMBERSHIP, SchemaType.VIRTUAL,
+                "mvirtualdata");
+        assertNotNull(vSchemaTO);
+    }
+
+    @Test
+    public void create() {
+        VirSchemaTO schema = new VirSchemaTO();
+        schema.setKey("virtual");
+
+        VirSchemaTO actual = createSchema(AttributableType.USER, SchemaType.VIRTUAL, schema);
+        assertNotNull(actual);
+
+        actual = schemaService.read(AttributableType.USER, SchemaType.VIRTUAL, actual.getKey());
+        assertNotNull(actual);
+    }
+
+    @Test
+    public void delete() {
+        VirSchemaTO schema = schemaService.read(AttributableType.ROLE, SchemaType.VIRTUAL, "rvirtualdata");
+        assertNotNull(schema);
+
+        schemaService.delete(AttributableType.ROLE, SchemaType.VIRTUAL, schema.getKey());
+
+        try {
+            schemaService.read(AttributableType.ROLE, SchemaType.VIRTUAL, "rvirtualdata");
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+    }
+
+    @Test
+    public void issueSYNCOPE323() {
+        VirSchemaTO actual = schemaService.read(AttributableType.MEMBERSHIP, SchemaType.VIRTUAL, "mvirtualdata");
+        assertNotNull(actual);
+
+        try {
+            createSchema(AttributableType.MEMBERSHIP, SchemaType.VIRTUAL, actual);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(Response.Status.CONFLICT, e.getType().getResponseStatus());
+            assertEquals(ClientExceptionType.EntityExists, e.getType());
+        }
+
+        actual.setKey(null);
+        try {
+            createSchema(AttributableType.MEMBERSHIP, SchemaType.VIRTUAL, actual);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(Response.Status.BAD_REQUEST, e.getType().getResponseStatus());
+            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
+        }
+    }
+
+    @Test
+    public void issueSYNCOPE418() {
+        VirSchemaTO schema = new VirSchemaTO();
+        schema.setKey("http://schemas.examples.org/security/authorization/organizationUnit");
+
+        try {
+            createSchema(AttributableType.MEMBERSHIP, SchemaType.VIRTUAL, schema);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.InvalidVirSchema, e.getType());
+
+            assertTrue(e.getElements().iterator().next().toString().contains(EntityViolationType.InvalidName.name()));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/WorkflowITCase.java
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/WorkflowITCase.java b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/WorkflowITCase.java
new file mode 100644
index 0000000..161a23c
--- /dev/null
+++ b/syncope620/fit/reference/src/test/java/org/apache/syncope/fit/server/reference/WorkflowITCase.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.fit.server.reference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.ws.rs.core.Response;
+import org.apache.commons.io.IOUtils;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class WorkflowITCase extends AbstractITCase {
+
+    @Test
+    public void isActivitiEnabled() {
+        assertEquals(ActivitiDetector.isActivitiEnabledForUsers(),
+                adminClient.isActivitiEnabledFor(SubjectType.USER));
+        assertEquals(ActivitiDetector.isActivitiEnabledForRoles(),
+                adminClient.isActivitiEnabledFor(SubjectType.ROLE));
+    }
+
+    private void exportDefinition(final SubjectType type) throws IOException {
+        Response response = workflowService.exportDefinition(type);
+        assertTrue(response.getMediaType().toString().
+                startsWith(clientFactory.getContentType().getMediaType().toString()));
+        assertTrue(response.getEntity() instanceof InputStream);
+        String definition = IOUtils.toString((InputStream) response.getEntity());
+        assertNotNull(definition);
+        assertFalse(definition.isEmpty());
+    }
+
+    @Test
+    public void exportUserDefinition() throws IOException {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+        exportDefinition(SubjectType.USER);
+    }
+
+    @Test
+    public void getRoleDefinition() throws IOException {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForRoles());
+        exportDefinition(SubjectType.ROLE);
+    }
+
+    private void importDefinition(final SubjectType type) throws IOException {
+        Response response = workflowService.exportDefinition(type);
+        String definition = IOUtils.toString((InputStream) response.getEntity());
+
+        workflowService.importDefinition(type, definition);
+    }
+
+    @Test
+    public void updateUserDefinition() throws IOException {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForUsers());
+
+        importDefinition(SubjectType.USER);
+    }
+
+    @Test
+    public void updateRoleDefinition() throws IOException {
+        Assume.assumeTrue(ActivitiDetector.isActivitiEnabledForRoles());
+
+        importDefinition(SubjectType.ROLE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/resources/favicon.jpg
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/resources/favicon.jpg b/syncope620/fit/reference/src/test/resources/favicon.jpg
new file mode 100644
index 0000000..cf212be
Binary files /dev/null and b/syncope620/fit/reference/src/test/resources/favicon.jpg differ

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/resources/test.csv
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/resources/test.csv b/syncope620/fit/reference/src/test/resources/test.csv
new file mode 100644
index 0000000..f227366
--- /dev/null
+++ b/syncope620/fit/reference/src/test/resources/test.csv
@@ -0,0 +1,10 @@
+test0,nome0,cognome0,test0@syncope.apache.org,password0,role1,membership1,true,false
+test1,nome1,cognome1,test1@syncope.apache.org,password1,role1,membership1,false,false
+test2,nome2,cognome2,test2@syncope.apache.org,notpermitted1,role1,membership1,true,false
+test3,nome3,cognome3,test3@syncope.apache.org,password3,role1,membership1,true,false
+test4,nome4,cognome4,test4@syncope.apache.org,password4,role1,membership1,true,false
+test5,nome5,cognome5,test5@syncope.apache.org,password5,role1,membership1,true,false
+test6,nome6,cognome6,test6@syncope.apache.org,password6,role1,membership1,true,false
+test7,nome7,cognome7,test7@syncope.apache.org,password7,role1,membership1,true,false
+test8,nome8,cognome8,test8@syncope.apache.org,password8,role1,membership1,true,false
+test9,nome9,cognome9,test9@syncope.apache.org,password999,role1,membership1,true,false

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/fit/reference/src/test/resources/testJDBCContext.xml
----------------------------------------------------------------------
diff --git a/syncope620/fit/reference/src/test/resources/testJDBCContext.xml b/syncope620/fit/reference/src/test/resources/testJDBCContext.xml
new file mode 100644
index 0000000..d89e067
--- /dev/null
+++ b/syncope620/fit/reference/src/test/resources/testJDBCContext.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+  <bean id="testDataSource"
+        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+    <property name="driverClassName" value="${testdb.driver}"/>
+    <property name="url" value="${testdb.url}"/>
+    <property name="username" value="${testdb.username}"/>
+    <property name="password" value="${testdb.password}"/>
+  </bean>
+
+</beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/80589a1b/syncope620/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/pom.xml b/syncope620/pom.xml
index 4b0e328..b784741 100644
--- a/syncope620/pom.xml
+++ b/syncope620/pom.xml
@@ -315,8 +315,8 @@ under the License.
 
     <connid.version>1.4.0.0</connid.version>
     <connid.soap.version>1.3.0</connid.soap.version>
-    <connid.db.table.version>2.2.0</connid.db.table.version>
-    <connid.csvdir.version>0.8</connid.csvdir.version>
+    <connid.db.table.version>2.2.1</connid.db.table.version>
+    <connid.csvdir.version>0.8.1</connid.csvdir.version>
     <connid.ldap.version>1.4.0</connid.ldap.version>
     <connid.ad.version>1.2.1</connid.ad.version>
 
@@ -382,7 +382,7 @@ under the License.
     <cargo.log>${log.directory}/cargo.log</cargo.log>
     <cargo.output>${log.directory}/cargo-output.log</cargo.output>
 
-    <tomcat.version>8.0.15</tomcat.version>
+    <tomcat.version>8.0.17</tomcat.version>
 
     <anonymousUser>anonymous</anonymousUser>
     <!-- static keys, only used for build: generated overlays will override during archetype:generate -->
@@ -881,6 +881,12 @@ under the License.
         </plugin>
         
         <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <version>2.10.1</version>          
+        </plugin>
+        
+        <plugin>
           <groupId>org.apache.openjpa</groupId>
           <artifactId>openjpa-maven-plugin</artifactId>
           <version>${openjpa.version}</version>
@@ -929,9 +935,15 @@ under the License.
         </plugin>
         
         <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-antrun-plugin</artifactId>
+          <version>1.8</version>
+        </plugin>
+        
+        <plugin>
           <groupId>org.codehaus.cargo</groupId>
           <artifactId>cargo-maven2-plugin</artifactId>
-          <version>1.4.11</version>
+          <version>1.4.12</version>
           <configuration>
             <container>
               <containerId>tomcat8x</containerId>
@@ -950,7 +962,7 @@ under the License.
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.18</version>
+          <version>2.18.1</version>
           <configuration>
             <redirectTestOutputToFile>true</redirectTestOutputToFile>
             <encoding>utf-8</encoding>
@@ -958,6 +970,24 @@ under the License.
             <argLine>-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m</argLine>
           </configuration>
         </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-failsafe-plugin</artifactId>
+          <version>2.18.1</version>
+          <configuration>
+            <redirectTestOutputToFile>true</redirectTestOutputToFile>
+            <encoding>utf-8</encoding>
+            <runOrder>alphabetical</runOrder>
+          </configuration>
+          <executions>
+            <execution>
+              <goals>
+                <goal>integration-test</goal>
+                <goal>verify</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
         
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -986,6 +1016,110 @@ under the License.
     </plugins>
   </build>
 
+  <profiles>
+
+    <profile>
+      <id>apache-release</id>
+
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-deploy-plugin</artifactId>
+            <version>2.8.2</version>
+          </plugin>  
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-gpg-plugin</artifactId>
+            <version>1.6</version>
+            <executions>
+              <execution>
+                <id>sign-artifacts</id>
+                <phase>verify</phase>
+                <goals>
+                  <goal>sign</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+
+    <profile>
+      <id>site</id>
+      <build>
+        <defaultGoal>clean site-deploy</defaultGoal>
+
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-site-plugin</artifactId>
+            <version>3.4</version>
+            <configuration>
+              <locales>en</locales>
+              <generateProjectInfo>false</generateProjectInfo>
+              <reportPlugins>
+                <plugin>
+                  <groupId>org.apache.maven.plugins</groupId>
+                  <artifactId>maven-project-info-reports-plugin</artifactId>
+                  <version>2.8</version>
+                  <configuration>
+                    <dependencyDetailsEnabled>false</dependencyDetailsEnabled>
+                    <dependencyLocationsEnabled>false</dependencyLocationsEnabled>
+                  </configuration>
+                  <reports>
+                    <report>index</report>
+                    <report>mailing-list</report>
+                    <report>project-team</report>
+                    <report>issue-tracking</report>
+                    <report>license</report>
+                  </reports>
+                </plugin>
+                <plugin>
+                  <groupId>org.apache.maven.plugins</groupId>
+                  <artifactId>maven-javadoc-plugin</artifactId>
+                  <version>2.10.1</version>
+                  <configuration>
+                    <destDir>apidocs/2.0</destDir>
+                    <detectLinks>true</detectLinks>
+                    <detectJavaApiLink>true</detectJavaApiLink>
+                    <links>
+                      <link>http://docs.oracle.com/javaee/7/api/</link>
+                      <link>http://www.slf4j.org/api/</link>
+                      <link>http://connid.tirasa.net/apidocs/1.4/</link>
+                      <link>http://camel.apache.org/maven/current/camel-core/apidocs/</link>
+                      <link>http://camel.apache.org/maven/current/camel-spring/apidocs/</link>
+                      <link>http://ci.apache.org/projects/wicket/apidocs/7.x/</link>
+                      <link>http://docs.spring.io/spring/docs/4.1.x/javadoc-api/</link>
+                      <link>http://docs.spring.io/spring-security/site/docs/3.2.x/apidocs/</link>
+                      <link>http://activiti.org/javadocs/</link>
+                    </links>
+                  </configuration>
+                  <reports>
+                    <report>aggregate</report>
+                  </reports>
+                </plugin>
+              </reportPlugins>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+
+    <profile>
+      <id>skipTests</id>
+      <properties>
+        <skipTests>true</skipTests>
+      </properties>
+      <build>
+        <defaultGoal>clean install</defaultGoal>
+      </build>
+    </profile>
+
+  </profiles>
+  
   <modules>
     <module>common</module>
     <module>server</module>