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 2019/10/02 10:45:25 UTC

[syncope] branch 2_1_X updated: [SYNCOPE-957] Push implemented

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/2_1_X by this push:
     new bb01007  [SYNCOPE-957] Push implemented
bb01007 is described below

commit bb0100731b8655ceb8e0cab3f9855b6c1a6f2754
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed Oct 2 12:36:07 2019 +0200

    [SYNCOPE-957] Push implemented
---
 .../apache/syncope/common/lib/EntityTOUtils.java   |   2 +-
 .../syncope/common/lib/to/LinkedAccountTO.java     |  23 ++-
 .../syncope/core/persistence/api/dao/UserDAO.java  |   3 +-
 .../persistence/api/entity/user/LinkedAccount.java |   4 +-
 .../core/persistence/api/entity/user/User.java     |   4 +-
 .../persistence/jpa/entity/user/JPAJSONUser.java   |  11 +-
 .../core/persistence/jpa/dao/JPAUserDAO.java       |  25 ++--
 .../jpa/entity/user/JPALinkedAccount.java          |  12 +-
 .../core/persistence/jpa/entity/user/JPAUser.java  |  12 +-
 .../core/persistence/jpa/outer/UserTest.java       |   9 +-
 .../provisioning/api/PropagationByResource.java    |   2 +-
 .../api/propagation/PropagationManager.java        |   4 +
 .../java/DefaultAnyObjectProvisioningManager.java  |   4 +
 .../java/DefaultGroupProvisioningManager.java      |  36 ++---
 .../java/DefaultUserProvisioningManager.java       |  11 +-
 .../core/provisioning/java/MappingManagerImpl.java |   2 +-
 .../provisioning/java/data/UserDataBinderImpl.java |  50 ++++---
 .../AbstractPropagationTaskExecutor.java           |  61 ++++----
 .../java/propagation/DeletingLinkedAccount.java    |  16 +--
 .../java/propagation/PropagationManagerImpl.java   |  10 +-
 .../java/pushpull/AbstractPullResultHandler.java   |  15 ++
 .../java/pushpull/AbstractPushResultHandler.java   |  19 +--
 .../pushpull/DefaultUserPushResultHandler.java     |  93 +++++++++++-
 .../core/provisioning/java/pushpull/PushUtils.java |  27 ++++
 .../workflow/java/DefaultUserWorkflowAdapter.java  |   2 +-
 .../camel/component/PropagateEndpoint.java         |   2 +-
 .../camel/producer/DeleteProducer.java             |   6 +-
 .../camel/producer/DeprovisionProducer.java        |  12 ++
 .../camel/producer/ProvisionProducer.java          |  14 +-
 .../camel/producer/StatusProducer.java             |   9 ++
 .../camel/producer/UpdateProducer.java             |   1 +
 .../flowable/impl/FlowableUserWorkflowAdapter.java |   4 +-
 .../syncope/fit/buildtools/cxf/UserService.java    |   1 +
 fit/build-tools/src/main/resources/log4j2.xml      |   4 +-
 .../org/apache/syncope/fit/AbstractITCase.java     |  25 ++++
 .../syncope/fit/core/LinkedAccountITCase.java      | 156 ++++++++++++++++++---
 .../apache/syncope/fit/core/PushTaskITCase.java    |   4 +-
 .../org/apache/syncope/fit/core/UserITCase.java    |  14 +-
 .../src/test/resources/rest/CreateScript.groovy    |  10 +-
 .../src/test/resources/rest/DeleteScript.groovy    |   8 +-
 .../src/test/resources/rest/SearchScript.groovy    |  19 ++-
 .../src/test/resources/rest/UpdateScript.groovy    |   8 +-
 42 files changed, 572 insertions(+), 182 deletions(-)

diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java b/common/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java
index ecac7d9..8aeefc0 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java
@@ -52,7 +52,7 @@ public final class EntityTOUtils {
             final Collection<LinkedAccountTO> accounts) {
 
         return Collections.unmodifiableMap(accounts.stream().collect(Collectors.toMap(
-                account -> Pair.of(account.getResource(), account.getConnObjectName()),
+                account -> Pair.of(account.getResource(), account.getconnObjectKeyValue()),
                 Function.identity(),
                 (exist, repl) -> repl)));
     }
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java
index ef5210b..967558a 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/LinkedAccountTO.java
@@ -41,14 +41,9 @@ public class LinkedAccountTO implements Serializable {
 
         private final LinkedAccountTO instance = new LinkedAccountTO();
 
-        public Builder connObjectName(final String connObjectName) {
-            instance.setConnObjectName(connObjectName);
-            return this;
-        }
-
-        public Builder resource(final String resource) {
+        public Builder(final String resource, final String connObjectKeyValue) {
             instance.setResource(resource);
-            return this;
+            instance.setconnObjectKeyValue(connObjectKeyValue);
         }
 
         public Builder username(final String username) {
@@ -71,7 +66,7 @@ public class LinkedAccountTO implements Serializable {
         }
     }
 
-    private String connObjectName;
+    private String connObjectKeyValue;
 
     private String resource;
 
@@ -85,12 +80,12 @@ public class LinkedAccountTO implements Serializable {
 
     private final Set<String> privileges = new HashSet<>();
 
-    public String getConnObjectName() {
-        return connObjectName;
+    public String getconnObjectKeyValue() {
+        return connObjectKeyValue;
     }
 
-    public void setConnObjectName(final String connObjectName) {
-        this.connObjectName = connObjectName;
+    public void setconnObjectKeyValue(final String connObjectKeyValue) {
+        this.connObjectKeyValue = connObjectKeyValue;
     }
 
     public String getResource() {
@@ -147,7 +142,7 @@ public class LinkedAccountTO implements Serializable {
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
-                append(connObjectName).
+                append(connObjectKeyValue).
                 append(resource).
                 append(username).
                 append(suspended).
@@ -169,7 +164,7 @@ public class LinkedAccountTO implements Serializable {
         }
         final LinkedAccountTO other = (LinkedAccountTO) obj;
         return new EqualsBuilder().
-                append(connObjectName, other.connObjectName).
+                append(connObjectKeyValue, other.connObjectKeyValue).
                 append(resource, other.resource).
                 append(username, other.username).
                 append(suspended, other.suspended).
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
index 5ecd95b..008791e 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
@@ -21,7 +21,6 @@ package org.apache.syncope.core.persistence.api.dao;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
@@ -61,7 +60,7 @@ public interface UserDAO extends AnyDAO<User> {
 
     Collection<ExternalResource> findAllResources(User user);
 
-    Optional<LinkedAccount> findLinkedAccountByConnObjectName(String connObjectName);
+    boolean linkedAccountExists(String userKey, String connObjectKeyValue);
 
     List<LinkedAccount> findLinkedAccounts(String userKey);
 
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/LinkedAccount.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/LinkedAccount.java
index 455bf79..0485f05 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/LinkedAccount.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/LinkedAccount.java
@@ -25,9 +25,9 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 
 public interface LinkedAccount extends Account, Attributable<LAPlainAttr> {
 
-    String getConnObjectName();
+    String getConnObjectKeyValue();
 
-    void setConnObjectName(String connObjectName);
+    void setConnObjectKeyValue(String connObjectKeyValue);
 
     User getOwner();
 
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java
index 9137294..458d415 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java
@@ -75,7 +75,9 @@ public interface User extends Account, GroupableRelatable<User, UMembership, UPl
 
     boolean add(LinkedAccount account);
 
-    Optional<? extends LinkedAccount> getLinkedAccount(String resource, String connObjectName);
+    Optional<? extends LinkedAccount> getLinkedAccount(String resource, String connObjectKeyValue);
+
+    List<? extends LinkedAccount> getLinkedAccounts(String resource);
 
     List<? extends LinkedAccount> getLinkedAccounts();
 }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUser.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUser.java
index ab346dc..87e2ca2 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUser.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUser.java
@@ -133,14 +133,21 @@ public class JPAJSONUser extends JPAUser implements JSONAttributable<User>, User
     }
 
     @Override
-    public Optional<? extends LinkedAccount> getLinkedAccount(final String resource, final String connObjectName) {
+    public Optional<? extends LinkedAccount> getLinkedAccount(final String resource, final String connObjectKeyValue) {
         return linkedAccounts.stream().
                 filter(account -> account.getResource().getKey().equals(resource)
-                && account.getConnObjectName().equals(connObjectName)).
+                && account.getConnObjectKeyValue().equals(connObjectKeyValue)).
                 findFirst();
     }
 
     @Override
+    public List<? extends LinkedAccount> getLinkedAccounts(final String resource) {
+        return linkedAccounts.stream().
+                filter(account -> account.getResource().getKey().equals(resource)).
+                collect(Collectors.toList());
+    }
+
+    @Override
     public List<? extends LinkedAccount> getLinkedAccounts() {
         return linkedAccounts;
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index f4f1d05..07ee141 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -25,7 +25,6 @@ import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -545,6 +544,17 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
 
     @Transactional(readOnly = true)
     @Override
+    public boolean linkedAccountExists(final String userKey, final String connObjectKeyValue) {
+        Query query = entityManager().createNativeQuery(
+                "SELECT id FROM " + JPALinkedAccount.TABLE + " WHERE owner_id=? AND connObjectKeyValue=?");
+        query.setParameter(1, userKey);
+        query.setParameter(2, connObjectKeyValue);
+
+        return !query.getResultList().isEmpty();
+    }
+
+    @Transactional(readOnly = true)
+    @Override
     public List<LinkedAccount> findLinkedAccounts(final String userKey) {
         TypedQuery<LinkedAccount> query = entityManager().createQuery(
                 "SELECT e FROM " + JPALinkedAccount.class.getSimpleName() + " e "
@@ -563,19 +573,6 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
     }
 
     @Override
-    public Optional<LinkedAccount> findLinkedAccountByConnObjectName(final String connObjectName) {
-        TypedQuery<LinkedAccount> query = entityManager().createQuery(
-                "SELECT e FROM " + JPALinkedAccount.class.getSimpleName() + " e "
-                + "WHERE e.connObjectName=:connObjectName", LinkedAccount.class);
-        query.setParameter("connObjectName", connObjectName);
-
-        List<LinkedAccount> result = query.getResultList();
-        return result.isEmpty()
-                ? Optional.empty()
-                : Optional.of(result.get(0));
-    }
-
-    @Override
     public List<LinkedAccount> findLinkedAccountsByResource(final ExternalResource resource) {
         TypedQuery<LinkedAccount> query = entityManager().createQuery(
                 "SELECT e FROM " + JPALinkedAccount.class.getSimpleName() + " e "
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java
index c24a85c..dbd47e8 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java
@@ -51,7 +51,7 @@ import org.apache.syncope.core.spring.security.Encryptor;
 
 @Entity
 @Table(name = JPALinkedAccount.TABLE, uniqueConstraints =
-        @UniqueConstraint(columnNames = { "connObjectName", "resource_id" }))
+        @UniqueConstraint(columnNames = { "connObjectKeyValue", "resource_id" }))
 public class JPALinkedAccount extends AbstractGeneratedKeyEntity implements LinkedAccount {
 
     private static final long serialVersionUID = -5141654998687601522L;
@@ -61,7 +61,7 @@ public class JPALinkedAccount extends AbstractGeneratedKeyEntity implements Link
     private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
 
     @NotNull
-    private String connObjectName;
+    private String connObjectKeyValue;
 
     @ManyToOne(fetch = FetchType.EAGER)
     private JPAUser owner;
@@ -94,13 +94,13 @@ public class JPALinkedAccount extends AbstractGeneratedKeyEntity implements Link
     private Set<JPAPrivilege> privileges = new HashSet<>();
 
     @Override
-    public String getConnObjectName() {
-        return connObjectName;
+    public String getConnObjectKeyValue() {
+        return connObjectKeyValue;
     }
 
     @Override
-    public void setConnObjectName(final String connObjectName) {
-        this.connObjectName = connObjectName;
+    public void setConnObjectKeyValue(final String connObjectKeyValue) {
+        this.connObjectKeyValue = connObjectKeyValue;
     }
 
     @Override
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
index 651d6f6..abcccac 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
@@ -23,6 +23,7 @@ import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import javax.persistence.Cacheable;
 import javax.persistence.CascadeType;
 import javax.persistence.CollectionTable;
@@ -483,14 +484,21 @@ public class JPAUser
     }
 
     @Override
-    public Optional<? extends LinkedAccount> getLinkedAccount(final String resource, final String connObjectName) {
+    public Optional<? extends LinkedAccount> getLinkedAccount(final String resource, final String connObjectKeyValue) {
         return linkedAccounts.stream().
                 filter(account -> account.getResource().getKey().equals(resource)
-                && account.getConnObjectName().equals(connObjectName)).
+                && account.getConnObjectKeyValue().equals(connObjectKeyValue)).
                 findFirst();
     }
 
     @Override
+    public List<? extends LinkedAccount> getLinkedAccounts(final String resource) {
+        return linkedAccounts.stream().
+                filter(account -> account.getResource().getKey().equals(resource)).
+                collect(Collectors.toList());
+    }
+
+    @Override
     public List<? extends LinkedAccount> getLinkedAccounts() {
         return linkedAccounts;
     }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
index ccf52b4..e7196f6 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
@@ -28,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.fail;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.UUID;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
@@ -232,7 +231,7 @@ public class UserTest extends AbstractTest {
         assertTrue(user.getPlainAttrs("obscure").stream().anyMatch(plainAttr -> newM.equals(plainAttr.getMembership())));
     }
 
-    private LinkedAccount newLinkedAccount(final String connObjectName) {
+    private LinkedAccount newLinkedAccount(final String connObjectKeyValue) {
         User user = userDAO.findByUsername("vivaldi");
         user.getLinkedAccounts().stream().filter(Objects::nonNull).forEach(account -> account.setOwner(null));
         user.getLinkedAccounts().clear();
@@ -241,7 +240,7 @@ public class UserTest extends AbstractTest {
         account.setOwner(user);
         user.add(account);
 
-        account.setConnObjectName(connObjectName);
+        account.setConnObjectKeyValue(connObjectKeyValue);
         account.setResource(resourceDAO.find("resource-ldap"));
         account.add(applicationDAO.findPrivilege("getMighty"));
 
@@ -272,9 +271,7 @@ public class UserTest extends AbstractTest {
         assertTrue(account.getPlainAttr("obscure").isPresent());
         assertEquals(account.getOwner(), account.getPlainAttr("obscure").get().getOwner());
 
-        Optional<LinkedAccount> found = userDAO.findLinkedAccountByConnObjectName(account.getConnObjectName());
-        assertTrue(found.isPresent());
-        assertEquals(account, found.get());
+        assertTrue(userDAO.linkedAccountExists(account.getOwner().getKey(), account.getConnObjectKeyValue()));
 
         List<LinkedAccount> accounts = userDAO.findLinkedAccountsByResource(resourceDAO.find("resource-ldap"));
         assertEquals(1, accounts.size());
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/PropagationByResource.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/PropagationByResource.java
index bc1f796..5f82ef3 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/PropagationByResource.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/PropagationByResource.java
@@ -31,7 +31,7 @@ import org.apache.syncope.common.lib.types.ResourceOperation;
 /**
  * Encapsulates operations to be performed on various resources.
  *
- * @param <T> key for propagation: could be simple resource or pair (resource, connObjectName) for linked accounts
+ * @param <T> key for propagation: could be simple resource or pair (resource, connObjectKeyValue) for linked accounts
  */
 public class PropagationByResource<T extends Serializable> implements Serializable {
 
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java
index a083b66..0378950 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java
@@ -78,6 +78,7 @@ public interface PropagationManager {
      * @param changePwd whether password should be included for propagation attributes or not
      * @param enable whether any object should be enabled or not, may be null to leave unchanged
      * @param propByRes operation to be performed per resource
+     * @param propByLinkedAccount operation to be performed for linked accounts
      * @param vAttrs virtual attributes to be set
      * @param noPropResourceKeys external resource keys not to be considered for propagation
      * @return list of propagation tasks
@@ -88,6 +89,7 @@ public interface PropagationManager {
             boolean changePwd,
             Boolean enable,
             PropagationByResource<String> propByRes,
+            PropagationByResource<Pair<String, String>> propByLinkedAccount,
             Collection<AttrTO> vAttrs,
             Collection<String> noPropResourceKeys);
 
@@ -119,6 +121,7 @@ public interface PropagationManager {
      * @param kind any object type kind
      * @param key any object key
      * @param propByRes operation to be performed per resource
+     * @param propByLinkedAccount operation to be performed for linked accounts
      * @param noPropResourceKeys external resource keys not to be considered for propagation
      * @return list of propagation tasks
      */
@@ -126,6 +129,7 @@ public interface PropagationManager {
             AnyTypeKind kind,
             String key,
             PropagationByResource<String> propByRes,
+            PropagationByResource<Pair<String, String>> propByLinkedAccount,
             Collection<String> noPropResourceKeys);
 
     /**
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java
index 2866183..171fc88 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java
@@ -107,6 +107,7 @@ public class DefaultAnyObjectProvisioningManager implements AnyObjectProvisionin
                 false,
                 null,
                 updated.getPropByRes(),
+                null,
                 anyObjectPatch.getVirAttrs(),
                 excludedResources);
         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync);
@@ -136,6 +137,7 @@ public class DefaultAnyObjectProvisioningManager implements AnyObjectProvisionin
                 AnyTypeKind.ANY_OBJECT,
                 key,
                 propByRes,
+                null,
                 excludedResources);
         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync);
 
@@ -172,6 +174,7 @@ public class DefaultAnyObjectProvisioningManager implements AnyObjectProvisionin
                 null,
                 propByRes,
                 null,
+                null,
                 null);
         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync);
 
@@ -189,6 +192,7 @@ public class DefaultAnyObjectProvisioningManager implements AnyObjectProvisionin
                 AnyTypeKind.ANY_OBJECT,
                 key,
                 propByRes,
+                null,
                 anyObjectDAO.findAllResourceKeys(key).stream().
                         filter(resource -> !resources.contains(resource)).
                         collect(Collectors.toList()));
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java
index 87c8789..bf93ee1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java
@@ -129,6 +129,7 @@ public class DefaultGroupProvisioningManager implements GroupProvisioningManager
                 false,
                 null,
                 updated.getPropByRes(),
+                null,
                 groupPatch.getVirAttrs(),
                 excludedResources);
         PropagationReporter propagationReporter = taskExecutor.execute(tasks, nullPriorityAsync);
@@ -150,28 +151,29 @@ public class DefaultGroupProvisioningManager implements GroupProvisioningManager
 
         // Generate propagation tasks for deleting users and any objects from group resources, 
         // if they are on those resources only because of the reason being deleted (see SYNCOPE-357)
-        groupDataBinder.findUsersWithTransitiveResources(key).entrySet().
-                forEach(entry -> {
-                    taskInfos.addAll(propagationManager.getDeleteTasks(
-                            AnyTypeKind.USER,
-                            entry.getKey(),
-                            entry.getValue(),
-                            excludedResources));
-                });
-        groupDataBinder.findAnyObjectsWithTransitiveResources(key).entrySet().
-                forEach(entry -> {
-                    taskInfos.addAll(propagationManager.getDeleteTasks(
-                            AnyTypeKind.ANY_OBJECT,
-                            entry.getKey(),
-                            entry.getValue(),
-                            excludedResources));
-                });
+        groupDataBinder.findUsersWithTransitiveResources(key).forEach((anyKey, propByRes) -> {
+            taskInfos.addAll(propagationManager.getDeleteTasks(
+                    AnyTypeKind.USER,
+                    anyKey,
+                    propByRes,
+                    null,
+                    excludedResources));
+        });
+        groupDataBinder.findAnyObjectsWithTransitiveResources(key).forEach((anyKey, propByRes) -> {
+            taskInfos.addAll(propagationManager.getDeleteTasks(
+                    AnyTypeKind.ANY_OBJECT,
+                    anyKey,
+                    propByRes,
+                    null,
+                    excludedResources));
+        });
 
         // Generate propagation tasks for deleting this group from resources
         taskInfos.addAll(propagationManager.getDeleteTasks(
                 AnyTypeKind.GROUP,
                 key,
                 null,
+                null,
                 null));
 
         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync);
@@ -200,6 +202,7 @@ public class DefaultGroupProvisioningManager implements GroupProvisioningManager
                 null,
                 propByRes,
                 null,
+                null,
                 null);
         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync);
 
@@ -217,6 +220,7 @@ public class DefaultGroupProvisioningManager implements GroupProvisioningManager
                 AnyTypeKind.GROUP,
                 key,
                 propByRes,
+                null,
                 groupDAO.findAllResourceKeys(key).stream().
                         filter(resource -> !resources.contains(resource)).
                         collect(Collectors.toList()));
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
index c964321..46e4b9b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
@@ -198,7 +198,7 @@ public class DefaultUserProvisioningManager implements UserProvisioningManager {
         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
         userDAO.findLinkedAccounts(key).forEach(account -> propByLinkedAccount.add(
                 ResourceOperation.DELETE,
-                Pair.of(account.getResource().getKey(), account.getConnObjectName())));
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
 
         // Note here that we can only notify about "delete", not any other
         // task defined in workflow process definition: this because this
@@ -280,6 +280,7 @@ public class DefaultUserProvisioningManager implements UserProvisioningManager {
                 statusPatch.getType() != StatusPatchType.SUSPEND,
                 propByRes,
                 null,
+                null,
                 null);
         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync);
 
@@ -345,10 +346,18 @@ public class DefaultUserProvisioningManager implements UserProvisioningManager {
         PropagationByResource<String> propByRes = new PropagationByResource<>();
         propByRes.set(ResourceOperation.DELETE, resources);
 
+        PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+        userDAO.findLinkedAccounts(key).stream().
+                filter(account -> resources.contains(account.getResource().getKey())).
+                forEach(account -> propByLinkedAccount.add(
+                ResourceOperation.DELETE,
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+
         List<PropagationTaskInfo> taskInfos = propagationManager.getDeleteTasks(
                 AnyTypeKind.USER,
                 key,
                 propByRes,
+                propByLinkedAccount,
                 userDAO.findAllResourceKeys(key).stream().
                         filter(resource -> !resources.contains(resource)).
                         collect(Collectors.toList()));
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
index ae90118..29d5461 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
@@ -292,7 +292,7 @@ public class MappingManagerImpl implements MappingManager {
             }
         }
 
-        String connObjectKey = account.getConnObjectName();
+        String connObjectKey = account.getConnObjectKeyValue();
         MappingUtils.getConnObjectKeyItem(provision).ifPresent(connObjectKeyItem -> {
             Attribute connObjectKeyExtAttr = AttributeUtil.find(connObjectKeyItem.getExtAttrName(), attributes);
             if (connObjectKeyExtAttr != null) {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 6342a80..bc02905 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -25,7 +25,9 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
 import org.apache.commons.lang3.BooleanUtils;
@@ -161,12 +163,25 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
         if (resource == null) {
             LOG.debug("Ignoring invalid resource {}", accountTO.getResource());
         } else {
-            LinkedAccount account = entityFactory.newEntity(LinkedAccount.class);
-            account.setOwner(user);
-            user.add(account);
+            Optional<? extends LinkedAccount> found =
+                    user.getLinkedAccount(resource.getKey(), accountTO.getconnObjectKeyValue());
+            LinkedAccount account = found.isPresent()
+                    ? found.get()
+                    : new Supplier<LinkedAccount>() {
+
+                        @Override
+                        public LinkedAccount get() {
+                            LinkedAccount acct = entityFactory.newEntity(LinkedAccount.class);
+                            acct.setOwner(user);
+                            user.add(acct);
+
+                            acct.setConnObjectKeyValue(accountTO.getconnObjectKeyValue());
+                            acct.setResource(resource);
+
+                            return acct;
+                        }
+                    }.get();
 
-            account.setConnObjectName(accountTO.getConnObjectName());
-            account.setResource(resource);
             account.setUsername(accountTO.getUsername());
             if (StringUtils.isNotBlank(accountTO.getPassword())) {
                 account.setPassword(accountTO.getPassword(), CipherAlgorithm.AES);
@@ -577,10 +592,17 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
         userPatch.getLinkedAccounts().stream().filter(patch -> patch.getLinkedAccountTO() != null).forEach(patch -> {
             user.getLinkedAccount(
                     patch.getLinkedAccountTO().getResource(),
-                    patch.getLinkedAccountTO().getConnObjectName()).ifPresent(account -> {
+                    patch.getLinkedAccountTO().getconnObjectKeyValue()).ifPresent(account -> {
+
+                if (patch.getOperation() == PatchOperation.DELETE) {
+                    user.getLinkedAccounts().remove(account);
+                    account.setOwner(null);
+
+                    propByLinkedAccount.add(
+                            ResourceOperation.DELETE,
+                            Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()));
+                }
 
-                user.getLinkedAccounts().remove(account);
-                account.setOwner(null);
                 account.getPlainAttrs().stream().collect(Collectors.toSet()).forEach(attr -> {
                     account.remove(attr);
                     attr.setOwner(null);
@@ -589,11 +611,6 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
                     plainAttrDAO.delete(attr);
                 });
 
-                if (patch.getOperation() == PatchOperation.DELETE) {
-                    propByLinkedAccount.add(
-                            ResourceOperation.DELETE,
-                            Pair.of(account.getResource().getKey(), account.getConnObjectName()));
-                }
             });
             if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
                 linkedAccount(
@@ -606,7 +623,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
         user.getLinkedAccounts().forEach(account -> {
             propByLinkedAccount.add(
                     ResourceOperation.CREATE,
-                    Pair.of(account.getResource().getKey(), account.getConnObjectName()));
+                    Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()));
         });
 
         // finalize resource management
@@ -748,9 +765,8 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
             // linked accounts
             userTO.getLinkedAccounts().addAll(
                     user.getLinkedAccounts().stream().map(account -> {
-                        LinkedAccountTO accountTO = new LinkedAccountTO.Builder().
-                                resource(account.getResource().getKey()).
-                                connObjectName(account.getConnObjectName()).
+                        LinkedAccountTO accountTO = new LinkedAccountTO.Builder(
+                                account.getResource().getKey(), account.getConnObjectKeyValue()).
                                 username(account.getUsername()).
                                 password(user.getPassword()).
                                 suspended(BooleanUtils.isTrue(account.isSuspended())).
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index bb5f17c..172f35f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.collections.IteratorChain;
 import org.apache.syncope.common.lib.to.ExecTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.ExecStatus;
@@ -632,37 +633,45 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 ? task.getConnObjectKey()
                 : task.getOldConnObjectKey();
 
-        Set<MappingItem> linkingMappingItems = virSchemaDAO.findByProvision(provision).stream().
-                map(schema -> schema.asLinkingMappingItem()).collect(Collectors.toSet());
+        boolean isLinkedAccount = task.getAnyTypeKind() == AnyTypeKind.USER
+                && userDAO.linkedAccountExists(task.getEntityKey(), connObjectKey);
+
+        Set<MappingItem> linkingMappingItems = isLinkedAccount
+                ? Collections.emptySet()
+                : virSchemaDAO.findByProvision(provision).stream().
+                        map(schema -> schema.asLinkingMappingItem()).collect(Collectors.toSet());
 
         ConnectorObject obj = null;
+
         Optional<? extends MappingItem> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
-        if (connObjectKeyItem.isPresent()) {
-            try {
-                obj = connector.getObject(
-                        new ObjectClass(task.getObjectClassName()),
-                        AttributeBuilder.build(connObjectKeyItem.get().getExtAttrName(), connObjectKey),
-                        provision.isIgnoreCaseMatch(),
-                        MappingUtils.buildOperationOptions(new IteratorChain<>(
-                                MappingUtils.getPropagationItems(provision.getMapping().getItems()).iterator(),
-                                linkingMappingItems.iterator())));
-
-                for (MappingItem item : linkingMappingItems) {
-                    Attribute attr = obj.getAttributeByName(item.getExtAttrName());
-                    if (attr == null) {
-                        virAttrCache.expire(task.getAnyType(), task.getEntityKey(), item.getIntAttrName());
-                    } else {
-                        VirAttrCacheValue cacheValue = new VirAttrCacheValue();
-                        cacheValue.setValues(attr.getValue());
-                        virAttrCache.put(task.getAnyType(), task.getEntityKey(), item.getIntAttrName(), cacheValue);
-                    }
+        String connObjectKeyName = connObjectKeyItem.isPresent()
+                ? connObjectKeyItem.get().getExtAttrName()
+                : Name.NAME;
+
+        try {
+            obj = connector.getObject(
+                    new ObjectClass(task.getObjectClassName()),
+                    AttributeBuilder.build(connObjectKeyName, connObjectKey),
+                    provision.isIgnoreCaseMatch(),
+                    MappingUtils.buildOperationOptions(new IteratorChain<>(
+                            MappingUtils.getPropagationItems(provision.getMapping().getItems()).iterator(),
+                            linkingMappingItems.iterator())));
+
+            for (MappingItem item : linkingMappingItems) {
+                Attribute attr = obj.getAttributeByName(item.getExtAttrName());
+                if (attr == null) {
+                    virAttrCache.expire(task.getAnyType(), task.getEntityKey(), item.getIntAttrName());
+                } else {
+                    VirAttrCacheValue cacheValue = new VirAttrCacheValue();
+                    cacheValue.setValues(attr.getValue());
+                    virAttrCache.put(task.getAnyType(), task.getEntityKey(), item.getIntAttrName(), cacheValue);
                 }
-            } catch (TimeoutException toe) {
-                LOG.debug("Request timeout", toe);
-                throw toe;
-            } catch (RuntimeException ignore) {
-                LOG.debug("While resolving {}", connObjectKey, ignore);
             }
+        } catch (TimeoutException toe) {
+            LOG.debug("Request timeout", toe);
+            throw toe;
+        } catch (RuntimeException ignore) {
+            LOG.debug("While resolving {}", connObjectKey, ignore);
         }
 
         return obj;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DeletingLinkedAccount.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DeletingLinkedAccount.java
index 331aa84..c96eebc 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DeletingLinkedAccount.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DeletingLinkedAccount.java
@@ -39,12 +39,12 @@ public class DeletingLinkedAccount implements LinkedAccount {
 
     private final ExternalResource resource;
 
-    private final String connObjectName;
+    private final String connObjectKeyValue;
 
-    public DeletingLinkedAccount(final User user, final ExternalResource resource, final String connObjectName) {
+    public DeletingLinkedAccount(final User user, final ExternalResource resource, final String connObjectKeyValue) {
         this.user = user;
         this.resource = resource;
-        this.connObjectName = connObjectName;
+        this.connObjectKeyValue = connObjectKeyValue;
     }
 
     @Override
@@ -53,12 +53,12 @@ public class DeletingLinkedAccount implements LinkedAccount {
     }
 
     @Override
-    public String getConnObjectName() {
-        return connObjectName;
+    public String getConnObjectKeyValue() {
+        return connObjectKeyValue;
     }
 
     @Override
-    public void setConnObjectName(final String connObjectName) {
+    public void setConnObjectKeyValue(final String connObjectKeyValue) {
         // unsupported
     }
 
@@ -162,7 +162,7 @@ public class DeletingLinkedAccount implements LinkedAccount {
         return new HashCodeBuilder().
                 append(user.getKey()).
                 append(resource).
-                append(connObjectName).
+                append(connObjectKeyValue).
                 build();
     }
 
@@ -181,7 +181,7 @@ public class DeletingLinkedAccount implements LinkedAccount {
         return new EqualsBuilder().
                 append(user.getKey(), other.user.getKey()).
                 append(resource, other.resource).
-                append(connObjectName, other.connObjectName).
+                append(connObjectKeyValue, other.connObjectKeyValue).
                 build();
     }
 }
diff --git 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
index f519640..55fb711 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
@@ -203,6 +203,7 @@ public class PropagationManagerImpl implements PropagationManager {
             final boolean changePwd,
             final Boolean enable,
             final PropagationByResource<String> propByRes,
+            final PropagationByResource<Pair<String, String>> propByLinkedAccount,
             final Collection<AttrTO> vAttrs,
             final Collection<String> noPropResourceKeys) {
 
@@ -212,7 +213,7 @@ public class PropagationManagerImpl implements PropagationManager {
                 changePwd,
                 enable,
                 propByRes,
-                null,
+                propByLinkedAccount,
                 vAttrs,
                 noPropResourceKeys);
     }
@@ -319,9 +320,10 @@ public class PropagationManagerImpl implements PropagationManager {
             final AnyTypeKind kind,
             final String key,
             final PropagationByResource<String> propByRes,
+            final PropagationByResource<Pair<String, String>> propByLinkedAccount,
             final Collection<String> noPropResourceKeys) {
 
-        return getDeleteTasks(dao(kind).authFind(key), propByRes, null, noPropResourceKeys);
+        return getDeleteTasks(dao(kind).authFind(key), propByRes, propByLinkedAccount, noPropResourceKeys);
     }
 
     @Override
@@ -544,12 +546,12 @@ public class PropagationManagerImpl implements PropagationManager {
                             provision,
                             deleteOnResource,
                             mappingItems,
-                            Pair.of(account.getConnObjectName(),
+                            Pair.of(account.getConnObjectKeyValue(),
                                     mappingManager.prepareAttrs(user, account, password, changePwd, provision)));
                     tasks.add(accountTask);
 
                     LOG.debug("PropagationTask created for Linked Account {}: {}",
-                            account.getConnObjectName(), accountTask);
+                            account.getConnObjectKeyValue(), accountTask);
                 }
             });
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
index 356d6fc..997e7f4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
@@ -23,10 +23,12 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.AnyOperations;
 import org.apache.syncope.common.lib.patch.AnyPatch;
 import org.apache.syncope.common.lib.patch.StringPatchItem;
 import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.MatchingRule;
@@ -37,6 +39,7 @@ import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
@@ -82,6 +85,9 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
     protected ConnObjectUtils connObjectUtils;
 
     @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
     protected RemediationDAO remediationDAO;
 
     @Autowired
@@ -474,10 +480,19 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
 
                         PropagationByResource<String> propByRes = new PropagationByResource<>();
                         propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
+
+                        PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+                        if (getAnyUtils().anyTypeKind() == AnyTypeKind.USER) {
+                            userDAO.findLinkedAccounts(key).forEach(account -> propByLinkedAccount.add(
+                                    ResourceOperation.DELETE,
+                                    Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+                        }
+
                         taskExecutor.execute(propagationManager.getDeleteTasks(
                                 provision.getAnyType().getKind(),
                                 key,
                                 propByRes,
+                                propByLinkedAccount,
                                 null),
                                 false);
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
index d6829c8..e835c0e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.syncope.common.lib.patch.AnyPatch;
 import org.apache.syncope.common.lib.patch.StringPatchItem;
@@ -118,6 +119,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                 enable,
                 propByRes,
                 null,
+                null,
                 noPropResources);
         if (!taskInfos.isEmpty()) {
             taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
@@ -141,6 +143,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                 any.getType().getKind(),
                 any.getKey(),
                 propByRes,
+                null,
                 noPropResources);
         if (!taskInfos.isEmpty()) {
             taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
@@ -281,15 +284,6 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
         ConnectorObject beforeObj = connObjs.isEmpty() ? null : connObjs.get(0);
 
-        Object output = null;
-        Result resultStatus = null;
-
-        Boolean enable = any instanceof User && profile.getTask().isSyncStatus()
-                ? ((User) any).isSuspended()
-                ? Boolean.FALSE
-                : Boolean.TRUE
-                : null;
-
         if (profile.isDryRun()) {
             if (beforeObj == null) {
                 result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
@@ -313,6 +307,13 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                     any.getType().getKind().name().toLowerCase(),
                     profile.getTask().getResource().getKey(),
                     operation);
+
+            Object output = null;
+            Result resultStatus = null;
+
+            Boolean enable = any instanceof User && profile.getTask().isSyncStatus()
+                    ? BooleanUtils.negate(((User) any).isSuspended())
+                    : null;
             try {
                 if (beforeObj == null) {
                     result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java
index d1181d0..f7a4547 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java
@@ -20,6 +20,8 @@ package org.apache.syncope.core.provisioning.java.pushpull;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.patch.AnyPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
@@ -29,11 +31,15 @@ import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.Entity;
 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.PropagationReporter;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
+import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
 
 public class DefaultUserPushResultHandler extends AbstractPushResultHandler implements UserPushResultHandler {
 
@@ -43,6 +49,16 @@ public class DefaultUserPushResultHandler extends AbstractPushResultHandler impl
     }
 
     @Override
+    protected String getName(final Any<?> any) {
+        return User.class.cast(any).getUsername();
+    }
+
+    @Override
+    protected AnyTO getAnyTO(final String key) {
+        return userDataBinder.getUserTO(key);
+    }
+
+    @Override
     protected void provision(final Any<?> any, final Boolean enabled, final ProvisioningReport result) {
         AnyTO before = getAnyTO(any.getKey());
 
@@ -52,12 +68,18 @@ public class DefaultUserPushResultHandler extends AbstractPushResultHandler impl
         PropagationByResource<String> propByRes = new PropagationByResource<>();
         propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
 
+        PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+        ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()).
+                forEach(account -> propByLinkedAccount.add(
+                ResourceOperation.CREATE,
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+
         PropagationReporter reporter = taskExecutor.execute(propagationManager.getUserCreateTasks(
                 before.getKey(),
                 null,
                 enabled,
                 propByRes,
-                new PropagationByResource<>(),
+                propByLinkedAccount,
                 before.getVirAttrs(),
                 noPropResources),
                 false);
@@ -65,13 +87,74 @@ public class DefaultUserPushResultHandler extends AbstractPushResultHandler impl
     }
 
     @Override
-    protected String getName(final Any<?> any) {
-        return User.class.cast(any).getUsername();
+    protected void update(
+            final Any<?> any,
+            final Boolean enable,
+            final ConnectorObject beforeObj,
+            final ProvisioningReport result) {
+
+        List<String> ownedResources = getAnyUtils().getAllResources(any).stream().
+                map(Entity::getKey).collect(Collectors.toList());
+
+        List<String> noPropResources = new ArrayList<>(ownedResources);
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        PropagationByResource<String> propByRes = new PropagationByResource<>();
+        propByRes.add(ResourceOperation.UPDATE, profile.getTask().getResource().getKey());
+        propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue());
+
+        PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+        ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()).
+                forEach(account -> propByLinkedAccount.add(
+                ResourceOperation.UPDATE,
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+
+        List<PropagationTaskInfo> taskInfos = propagationManager.getUpdateTasks(
+                any.getType().getKind(),
+                any.getKey(),
+                true,
+                enable,
+                propByRes,
+                propByLinkedAccount,
+                null,
+                noPropResources);
+        if (!taskInfos.isEmpty()) {
+            taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
+            PropagationReporter reporter = new DefaultPropagationReporter();
+            taskExecutor.execute(taskInfos.get(0), reporter);
+            reportPropagation(result, reporter);
+        }
     }
 
     @Override
-    protected AnyTO getAnyTO(final String key) {
-        return userDataBinder.getUserTO(key);
+    protected void deprovision(final Any<?> any, final ConnectorObject beforeObj, final ProvisioningReport result) {
+        AnyTO before = getAnyTO(any.getKey());
+
+        List<String> noPropResources = new ArrayList<>(before.getResources());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        PropagationByResource<String> propByRes = new PropagationByResource<>();
+        propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
+        propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue());
+
+        PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+        ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()).
+                forEach(account -> propByLinkedAccount.add(
+                ResourceOperation.DELETE,
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+
+        List<PropagationTaskInfo> taskInfos = propagationManager.getDeleteTasks(
+                any.getType().getKind(),
+                any.getKey(),
+                propByRes,
+                propByLinkedAccount,
+                noPropResources);
+        if (!taskInfos.isEmpty()) {
+            taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
+            PropagationReporter reporter = new DefaultPropagationReporter();
+            taskExecutor.execute(taskInfos.get(0), reporter);
+            reportPropagation(result, reporter);
+        }
     }
 
     @Override
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushUtils.java
index b260fb5..8594401 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushUtils.java
@@ -27,6 +27,7 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.MappingManager;
 import org.apache.syncope.core.provisioning.api.TimeoutException;
@@ -34,6 +35,7 @@ import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.apache.syncope.core.spring.ImplementationManager;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.SearchResult;
 import org.identityconnectors.framework.spi.SearchResultsHandler;
 import org.slf4j.Logger;
@@ -137,4 +139,29 @@ public class PushUtils {
 
         return obj == null ? Collections.emptyList() : Collections.singletonList(obj);
     }
+
+    public ConnectorObject match(
+            final Connector connector,
+            final LinkedAccount account,
+            final Provision provision) {
+
+        Optional<? extends MappingItem> connObjectKey = MappingUtils.getConnObjectKeyItem(provision);
+        String connObjectKeyName = connObjectKey.isPresent()
+                ? connObjectKey.get().getExtAttrName()
+                : Name.NAME;
+
+        ConnectorObject obj = null;
+        try {
+            obj = connector.getObject(
+                    provision.getObjectClass(),
+                    AttributeBuilder.build(connObjectKeyName, account.getConnObjectKeyValue()),
+                    provision.isIgnoreCaseMatch(),
+                    MappingUtils.buildOperationOptions(provision.getMapping().getItems().iterator()));
+        } catch (TimeoutException toe) {
+            LOG.debug("Request timeout", toe);
+            throw toe;
+        }
+
+        return obj;
+    }
 }
diff --git a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
index f03042b..06769c2 100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
@@ -75,7 +75,7 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
         user.getLinkedAccounts().forEach(account -> propByLinkedAccount.add(
                 ResourceOperation.CREATE,
-                Pair.of(account.getResource().getKey(), account.getConnObjectName())));
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
 
         return new UserWorkflowResult<>(
                 Pair.of(user.getKey(), propagateEnable),
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/component/PropagateEndpoint.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/component/PropagateEndpoint.java
index 5936430..729cc04 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/component/PropagateEndpoint.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/component/PropagateEndpoint.java
@@ -91,7 +91,7 @@ public class PropagateEndpoint extends DefaultEndpoint {
                     producer = new DeleteProducer(this, anyTypeKind, userDAO, groupDataBinder);
                     break;
                 case provision:
-                    producer = new ProvisionProducer(this, anyTypeKind);
+                    producer = new ProvisionProducer(this, anyTypeKind, userDAO);
                     break;
                 case deprovision:
                     producer = new DeprovisionProducer(this, anyTypeKind, userDAO, groupDAO, anyObjectDAO);
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeleteProducer.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeleteProducer.java
index fce47dc..e765f71 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeleteProducer.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeleteProducer.java
@@ -67,7 +67,7 @@ public class DeleteProducer extends AbstractProducer {
                     PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
                     userDAO.findLinkedAccounts(key).forEach(account -> propByLinkedAccount.add(
                             ResourceOperation.DELETE,
-                            Pair.of(account.getResource().getKey(), account.getConnObjectName())));
+                            Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
 
                     // Note here that we can only notify about "delete", not any other
                     // task defined in workflow process definition: this because this
@@ -93,6 +93,7 @@ public class DeleteProducer extends AbstractProducer {
                                     AnyTypeKind.USER,
                                     anyKey,
                                     anyPropByRes,
+                                    null,
                                     excludedResources)));
                     groupDataBinder.findAnyObjectsWithTransitiveResources(key).
                             forEach((anyKey, anyPropByRes) -> {
@@ -100,6 +101,7 @@ public class DeleteProducer extends AbstractProducer {
                                         AnyTypeKind.ANY_OBJECT,
                                         anyKey,
                                         anyPropByRes,
+                                        null,
                                         excludedResources));
                             });
                     // Generate propagation tasks for deleting this group from resources
@@ -107,6 +109,7 @@ public class DeleteProducer extends AbstractProducer {
                             AnyTypeKind.GROUP,
                             key,
                             null,
+                            null,
                             null));
                     reporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
                     exchange.setProperty("statuses", reporter.getStatuses());
@@ -117,6 +120,7 @@ public class DeleteProducer extends AbstractProducer {
                             AnyTypeKind.ANY_OBJECT,
                             key,
                             null,
+                            null,
                             excludedResources);
                     reporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
                     exchange.setProperty("statuses", reporter.getStatuses());
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeprovisionProducer.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeprovisionProducer.java
index 4292737..cc91946 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeprovisionProducer.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/DeprovisionProducer.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -66,10 +67,19 @@ public class DeprovisionProducer extends AbstractProducer {
             switch (getAnyTypeKind()) {
                 case USER:
                     propByRes.set(ResourceOperation.DELETE, resources);
+
+                    PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+                    userDAO.findLinkedAccounts(key).stream().
+                            filter(account -> resources.contains(account.getResource().getKey())).
+                            forEach(account -> propByLinkedAccount.add(
+                            ResourceOperation.DELETE,
+                            Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+
                     taskInfos = getPropagationManager().getDeleteTasks(
                             AnyTypeKind.USER,
                             key,
                             propByRes,
+                            propByLinkedAccount,
                             userDAO.findAllResourceKeys(key).stream().
                                     filter(resource -> !resources.contains(resource)).collect(Collectors.toList()));
                     propagationReporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
@@ -82,6 +92,7 @@ public class DeprovisionProducer extends AbstractProducer {
                             AnyTypeKind.GROUP,
                             key,
                             propByRes,
+                            null,
                             groupDAO.findAllResourceKeys(key).stream().
                                     filter(resource -> !resources.contains(resource)).collect(Collectors.toList()));
                     propagationReporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
@@ -94,6 +105,7 @@ public class DeprovisionProducer extends AbstractProducer {
                             AnyTypeKind.ANY_OBJECT,
                             key,
                             propByRes,
+                            null,
                             anyObjectDAO.findAllResourceKeys(key).stream().
                                     filter(resource -> !resources.contains(resource)).collect(Collectors.toList()));
                     propagationReporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/ProvisionProducer.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/ProvisionProducer.java
index 62316f0..4bdfbf8 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/ProvisionProducer.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/ProvisionProducer.java
@@ -29,6 +29,7 @@ import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
@@ -36,8 +37,11 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 
 public class ProvisionProducer extends AbstractProducer {
 
-    public ProvisionProducer(final Endpoint endpoint, final AnyTypeKind anyType) {
+    private final UserDAO userDAO;
+
+    public ProvisionProducer(final Endpoint endpoint, final AnyTypeKind anyType, final UserDAO userDAO) {
         super(endpoint, anyType);
+        this.userDAO = userDAO;
     }
 
     @SuppressWarnings("unchecked")
@@ -77,6 +81,13 @@ public class ProvisionProducer extends AbstractProducer {
             PropagationByResource<String> propByRes = new PropagationByResource<>();
             propByRes.addAll(ResourceOperation.UPDATE, resources);
 
+            PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+            userDAO.findLinkedAccounts(key).stream().
+                    filter(account -> resources.contains(account.getResource().getKey())).
+                    forEach(account -> propByLinkedAccount.add(
+                    ResourceOperation.UPDATE,
+                    Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+
             AnyTypeKind anyTypeKind = AnyTypeKind.GROUP;
             if (getAnyTypeKind() != null) {
                 anyTypeKind = getAnyTypeKind();
@@ -88,6 +99,7 @@ public class ProvisionProducer extends AbstractProducer {
                     false,
                     null,
                     propByRes,
+                    propByLinkedAccount,
                     null,
                     null);
             PropagationReporter reporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/StatusProducer.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/StatusProducer.java
index 6be38e2..cc20d93 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/StatusProducer.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/StatusProducer.java
@@ -91,12 +91,21 @@ public class StatusProducer extends AbstractProducer {
 
             PropagationByResource<String> propByRes = new PropagationByResource<>();
             propByRes.addAll(ResourceOperation.UPDATE, statusPatch.getResources());
+
+            PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+            userDAO.findLinkedAccounts(statusPatch.getKey()).stream().
+                    filter(account -> statusPatch.getResources().contains(account.getResource().getKey())).
+                    forEach(account -> propByLinkedAccount.add(
+                    ResourceOperation.UPDATE,
+                    Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
+
             List<PropagationTaskInfo> taskInfos = getPropagationManager().getUpdateTasks(
                     AnyTypeKind.USER,
                     statusPatch.getKey(),
                     false,
                     statusPatch.getType() != StatusPatchType.SUSPEND,
                     propByRes,
+                    propByLinkedAccount,
                     null,
                     null);
             PropagationReporter reporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/UpdateProducer.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/UpdateProducer.java
index c658b07..0fd2b9f 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/UpdateProducer.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/producer/UpdateProducer.java
@@ -70,6 +70,7 @@ public class UpdateProducer extends AbstractProducer {
                         false,
                         null,
                         updated.getPropByRes(),
+                        null,
                         ((AnyPatch) actual).getVirAttrs(),
                         excludedResources);
                 PropagationReporter reporter = getPropagationTaskExecutor().execute(taskInfos, nullPriorityAsync);
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
index b124e20..ec1bb4d 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
@@ -147,7 +147,7 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
         user.getLinkedAccounts().forEach(account -> propByLinkedAccount.add(
                 ResourceOperation.CREATE,
-                Pair.of(account.getResource().getKey(), account.getConnObjectName())));
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
 
         FlowableRuntimeUtils.saveForFormSubmit(
                 engine,
@@ -399,7 +399,7 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
         user.getLinkedAccounts().forEach(account -> propByLinkedAccount.add(
                 ResourceOperation.DELETE,
-                Pair.of(account.getResource().getKey(), account.getConnObjectName())));
+                Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
 
         if (engine.getRuntimeService().createProcessInstanceQuery().
                 processInstanceId(procInstID).active().list().isEmpty()) {
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
index f02cae4..700ff9d 100644
--- a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserService.java
@@ -36,6 +36,7 @@ import javax.ws.rs.core.Response;
 public interface UserService {
 
     @GET
+    @Produces({ MediaType.APPLICATION_JSON })
     List<User> list();
 
     @GET
diff --git a/fit/build-tools/src/main/resources/log4j2.xml b/fit/build-tools/src/main/resources/log4j2.xml
index bab8707..7551796 100644
--- a/fit/build-tools/src/main/resources/log4j2.xml
+++ b/fit/build-tools/src/main/resources/log4j2.xml
@@ -33,8 +33,8 @@ under the License.
       <appender-ref ref="main"/>
     </asyncLogger>
 
-    <root level="ERROR">
+    <root level="WARN">
       <appenderRef ref="main"/>
     </root>
   </loggers>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 6c80c44..020db67 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -178,6 +178,8 @@ public abstract class AbstractITCase {
 
     protected static final String RESOURCE_NAME_DBSCRIPTED = "resource-db-scripted";
 
+    protected static final String RESOURCE_NAME_REST = "rest-target-resource";
+
     protected static final String RESOURCE_LDAP_ADMIN_DN = "uid=admin,ou=system";
 
     protected static final String RESOURCE_LDAP_ADMIN_PWD = "secret";
@@ -618,6 +620,29 @@ public abstract class AbstractITCase {
         }
     }
 
+    protected void removeLdapRemoteObject(
+            final String bindDn,
+            final String bindPwd,
+            final String objectDn) {
+
+        InitialDirContext ctx = null;
+        try {
+            ctx = getLdapResourceDirContext(bindDn, bindPwd);
+
+            ctx.destroySubcontext(objectDn);
+        } catch (Exception e) {
+            LOG.error("While removing {}", objectDn, e);
+        } finally {
+            if (ctx != null) {
+                try {
+                    ctx.close();
+                } catch (NamingException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
     protected <T> T queryForObject(
             final JdbcTemplate jdbcTemplate,
             final int maxWaitSeconds,
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java
index 57d86bc..c6476e3 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java
@@ -18,45 +18,55 @@
  */
 package org.apache.syncope.fit.core;
 
+import static org.apache.syncope.fit.AbstractITCase.getObject;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.Optional;
+import java.util.UUID;
 import javax.naming.NamingException;
 import javax.naming.directory.Attributes;
 import javax.naming.ldap.LdapContext;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.patch.LinkedAccountPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.LinkedAccountTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.TaskTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ExecStatus;
+import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.common.rest.api.beans.TaskQuery;
+import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.Test;
 
 public class LinkedAccountITCase extends AbstractITCase {
 
     @Test
-    public void createWithLinkedAccountThenRemove() throws NamingException {
+    public void createWithLinkedAccountThenUpdateThenRemove() throws NamingException {
         // 1. create user with linked account
         UserTO user = UserITCase.getSampleTO(
                 "linkedAccount" + RandomStringUtils.randomNumeric(5) + "@syncope.apache.org");
-        String connObjectName = "uid=" + user.getUsername() + ",ou=People,o=isp";
+        String connObjectKeyValue = "uid=" + user.getUsername() + ",ou=People,o=isp";
         String privilege = applicationService.read("mightyApp").getPrivileges().get(0).getKey();
 
-        LinkedAccountTO account = new LinkedAccountTO.Builder().
-                connObjectName(connObjectName).
-                resource(RESOURCE_NAME_LDAP).
-                build();
+        LinkedAccountTO account = new LinkedAccountTO.Builder(RESOURCE_NAME_LDAP, connObjectKeyValue).build();
         account.getPlainAttrs().add(attrTO("surname", "LINKED_SURNAME"));
         account.getPrivileges().add(privilege);
         user.getLinkedAccounts().add(account);
@@ -70,12 +80,12 @@ public class LinkedAccountITCase extends AbstractITCase {
                 new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_LDAP).
                         anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
         assertEquals(1, tasks.getTotalCount());
-        assertEquals(connObjectName, tasks.getResult().get(0).getConnObjectKey());
+        assertEquals(connObjectKeyValue, tasks.getResult().get(0).getConnObjectKey());
         assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
         assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
 
         LdapContext ldapObj = (LdapContext) getLdapRemoteObject(
-                RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectName);
+                RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue);
         assertNotNull(ldapObj);
 
         Attributes ldapAttrs = ldapObj.getAttributes("");
@@ -84,9 +94,29 @@ public class LinkedAccountITCase extends AbstractITCase {
                 ldapAttrs.get("mail").getAll().next().toString());
         assertEquals("LINKED_SURNAME", ldapAttrs.get("sn").getAll().next().toString());
 
-        // 3. remove linked account from user
+        // 3. update linked account
         UserPatch userPatch = new UserPatch();
         userPatch.setKey(user.getKey());
+
+        account.getPlainAttrs().clear();
+        account.getPlainAttrs().add(attrTO("email", "UPDATED_EMAIL@syncope.apache.org"));
+        account.getPlainAttrs().add(attrTO("surname", "UPDATED_SURNAME"));
+        userPatch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().linkedAccountTO(account).build());
+
+        user = updateUser(userPatch).getEntity();
+        assertEquals(1, user.getLinkedAccounts().size());
+
+        // 4 verify that account was updated on resource
+        ldapObj = (LdapContext) getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue);
+        assertNotNull(ldapObj);
+
+        ldapAttrs = ldapObj.getAttributes("");
+        assertEquals("UPDATED_EMAIL@syncope.apache.org", ldapAttrs.get("mail").getAll().next().toString());
+        assertEquals("UPDATED_SURNAME", ldapAttrs.get("sn").getAll().next().toString());
+
+        // 5. remove linked account from user
+        userPatch = new UserPatch();
+        userPatch.setKey(user.getKey());
         userPatch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
                 operation(PatchOperation.DELETE).
                 linkedAccountTO(user.getLinkedAccounts().get(0)).build());
@@ -94,19 +124,19 @@ public class LinkedAccountITCase extends AbstractITCase {
         user = updateUser(userPatch).getEntity();
         assertTrue(user.getLinkedAccounts().isEmpty());
 
-        // 4. verify that propagation task was generated and that account is not any more on resource
+        // 6. verify that propagation task was generated and that account is not any more on resource
         tasks = taskService.search(
                 new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_LDAP).
                         anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
-        assertEquals(2, tasks.getTotalCount());
+        assertEquals(3, tasks.getTotalCount());
 
         Optional<PropagationTaskTO> deletTask =
                 tasks.getResult().stream().filter(task -> task.getOperation() == ResourceOperation.DELETE).findFirst();
         assertTrue(deletTask.isPresent());
-        assertEquals(connObjectName, deletTask.get().getConnObjectKey());
+        assertEquals(connObjectKeyValue, deletTask.get().getConnObjectKey());
         assertEquals(ExecStatus.SUCCESS.name(), deletTask.get().getLatestExecStatus());
 
-        assertNull(getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectName));
+        assertNull(getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue));
     }
 
     @Test
@@ -114,7 +144,7 @@ public class LinkedAccountITCase extends AbstractITCase {
         // 1. create user without linked account
         UserTO user = UserITCase.getSampleTO(
                 "linkedAccount" + RandomStringUtils.randomNumeric(5) + "@syncope.apache.org");
-        String connObjectName = "uid=" + user.getUsername() + ",ou=People,o=isp";
+        String connObjectKeyValue = "uid=" + user.getUsername() + ",ou=People,o=isp";
 
         user = createUser(user).getEntity();
         assertNotNull(user.getKey());
@@ -125,16 +155,13 @@ public class LinkedAccountITCase extends AbstractITCase {
                         anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
         assertEquals(0, tasks.getTotalCount());
 
-        assertNull(getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectName));
+        assertNull(getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue));
 
         // 2. add linked account to user
         UserPatch userPatch = new UserPatch();
         userPatch.setKey(user.getKey());
 
-        LinkedAccountTO account = new LinkedAccountTO.Builder().
-                connObjectName(connObjectName).
-                resource(RESOURCE_NAME_LDAP).
-                build();
+        LinkedAccountTO account = new LinkedAccountTO.Builder(RESOURCE_NAME_LDAP, connObjectKeyValue).build();
         account.getPlainAttrs().add(attrTO("surname", "LINKED_SURNAME"));
         account.setPassword("Password123");
         userPatch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().linkedAccountTO(account).build());
@@ -147,12 +174,12 @@ public class LinkedAccountITCase extends AbstractITCase {
                 new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_LDAP).
                         anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
         assertEquals(1, tasks.getTotalCount());
-        assertEquals(connObjectName, tasks.getResult().get(0).getConnObjectKey());
+        assertEquals(connObjectKeyValue, tasks.getResult().get(0).getConnObjectKey());
         assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
         assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
 
         LdapContext ldapObj = (LdapContext) getLdapRemoteObject(
-                RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectName);
+                RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue);
         assertNotNull(ldapObj);
 
         Attributes ldapAttrs = ldapObj.getAttributes("");
@@ -161,4 +188,91 @@ public class LinkedAccountITCase extends AbstractITCase {
                 ldapAttrs.get("mail").getAll().next().toString());
         assertEquals("LINKED_SURNAME", ldapAttrs.get("sn").getAll().next().toString());
     }
+
+    @Test
+    public void push() {
+        // 0a. read configured cipher algorithm in order to be able to restore it at the end of test
+        AttrTO pwdCipherAlgo = configurationService.get("password.cipher.algorithm");
+        String origpwdCipherAlgo = pwdCipherAlgo.getValues().get(0);
+
+        // 0b. set AES password cipher algorithm
+        pwdCipherAlgo.getValues().set(0, "AES");
+        configurationService.set(pwdCipherAlgo);
+
+        try {
+            // 1. create user with linked account
+            UserTO user = UserITCase.getSampleTO(
+                    "linkedAccount" + RandomStringUtils.randomNumeric(5) + "@syncope.apache.org");
+            String connObjectKeyValue = UUID.randomUUID().toString();
+
+            LinkedAccountTO account = new LinkedAccountTO.Builder(RESOURCE_NAME_REST, connObjectKeyValue).build();
+            user.getLinkedAccounts().add(account);
+
+            user = createUser(user).getEntity();
+            String userKey = user.getKey();
+            assertNotNull(userKey);
+            assertNotEquals(userKey, connObjectKeyValue);
+
+            // 2. verify that account is found on resource
+            PagedResult<PropagationTaskTO> tasks = taskService.search(
+                    new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_REST).
+                            anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
+            assertEquals(1, tasks.getTotalCount());
+            assertEquals(connObjectKeyValue, tasks.getResult().get(0).getConnObjectKey());
+            assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
+            assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
+
+            WebClient webClient = WebClient.create(BUILD_TOOLS_ADDRESS + "/rest/users/" + connObjectKeyValue).
+                    accept(MediaType.APPLICATION_JSON_TYPE);
+            Response response = webClient.get();
+            assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+
+            // 3. remove account from resource
+            response = webClient.delete();
+            assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
+
+            response = webClient.get();
+            assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
+
+            // 4. create PushTask for the user above
+            PushTaskTO sendUser = new PushTaskTO();
+            sendUser.setName("Send User " + user.getUsername());
+            sendUser.setResource(RESOURCE_NAME_REST);
+            sendUser.setUnmatchingRule(UnmatchingRule.PROVISION);
+            sendUser.setMatchingRule(MatchingRule.UPDATE);
+            sendUser.setSourceRealm(SyncopeConstants.ROOT_REALM);
+            sendUser.getFilters().put(AnyTypeKind.USER.name(), "username==" + user.getUsername());
+            sendUser.setPerformCreate(true);
+            sendUser.setPerformUpdate(true);
+
+            response = taskService.create(TaskType.PUSH, sendUser);
+            sendUser = getObject(response.getLocation(), TaskService.class, PushTaskTO.class);
+            assertNotNull(sendUser);
+
+            // 5. execute PushTask
+            AbstractTaskITCase.execProvisioningTask(taskService, TaskType.PUSH, sendUser.getKey(), 50, false);
+
+            TaskTO task = taskService.read(TaskType.PUSH, sendUser.getKey(), true);
+            assertEquals(1, task.getExecutions().size());
+            assertEquals(ExecStatus.SUCCESS.name(), task.getExecutions().get(0).getStatus());
+
+            tasks = taskService.search(
+                    new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_REST).
+                            anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
+            assertEquals(3, tasks.getTotalCount());
+
+            // 6. verify that both user and account are now found on resource
+            response = webClient.get();
+            assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+
+            webClient = WebClient.create(BUILD_TOOLS_ADDRESS + "/rest/users/" + userKey).
+                    accept(MediaType.APPLICATION_JSON_TYPE);
+            response = webClient.get();
+            assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        } finally {
+            // restore initial cipher algorithm
+            pwdCipherAlgo.getValues().set(0, origpwdCipherAlgo);
+            configurationService.set(pwdCipherAlgo);
+        }
+    }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
index da4570b..b9b61f2 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
@@ -108,8 +108,8 @@ public class PushTaskITCase extends AbstractTaskITCase {
                 SyncopeClient.getGroupSearchConditionBuilder().isNotNull("cool").query());
         task.setMatchingRule(MatchingRule.LINK);
 
-        final Response response = taskService.create(TaskType.PUSH, task);
-        final PushTaskTO actual = getObject(response.getLocation(), TaskService.class, PushTaskTO.class);
+        Response response = taskService.create(TaskType.PUSH, task);
+        PushTaskTO actual = getObject(response.getLocation(), TaskService.class, PushTaskTO.class);
         assertNotNull(actual);
 
         task = taskService.read(TaskType.PUSH, actual.getKey(), true);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
index 5956fcd..29ab274 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
@@ -1322,7 +1322,7 @@ public class UserITCase extends AbstractITCase {
     public void restResource() {
         UserTO userTO = getUniqueSampleTO("rest@syncope.apache.org");
         userTO.getResources().clear();
-        userTO.getResources().add("rest-target-resource");
+        userTO.getResources().add(RESOURCE_NAME_REST);
 
         // 1. create
         ProvisioningResult<UserTO> result = userService.create(userTO, true).readEntity(
@@ -1330,13 +1330,13 @@ public class UserITCase extends AbstractITCase {
         });
         assertEquals(1, result.getPropagationStatuses().size());
         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
-        assertEquals("rest-target-resource", result.getPropagationStatuses().get(0).getResource());
+        assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
         assertEquals("surname", userTO.getPlainAttr("surname").get().getValues().get(0));
 
         // verify user exists on the backend REST service
         WebClient webClient = WebClient.create(BUILD_TOOLS_ADDRESS + "/rest/users/" + result.getEntity().getKey());
         Response response = webClient.get();
-        assertEquals(200, response.getStatus());
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
         assertNotNull(response.getEntity());
 
         // 2. update
@@ -1349,12 +1349,12 @@ public class UserITCase extends AbstractITCase {
         });
         assertEquals(1, result.getPropagationStatuses().size());
         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
-        assertEquals("rest-target-resource", result.getPropagationStatuses().get(0).getResource());
+        assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
         assertEquals("surname2", result.getEntity().getPlainAttr("surname").get().getValues().get(0));
 
         // verify user still exists on the backend REST service
         response = webClient.get();
-        assertEquals(200, response.getStatus());
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
         assertNotNull(response.getEntity());
 
         // 3. delete
@@ -1363,10 +1363,10 @@ public class UserITCase extends AbstractITCase {
         });
         assertEquals(1, result.getPropagationStatuses().size());
         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
-        assertEquals("rest-target-resource", result.getPropagationStatuses().get(0).getResource());
+        assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
 
         // verify user was removed by the backend REST service
-        assertEquals(404, webClient.get().getStatus());
+        assertEquals(Response.Status.NOT_FOUND.getStatusCode(), webClient.get().getStatus());
     }
 
     @Test
diff --git a/fit/core-reference/src/test/resources/rest/CreateScript.groovy b/fit/core-reference/src/test/resources/rest/CreateScript.groovy
index f97961e..98bb496 100644
--- a/fit/core-reference/src/test/resources/rest/CreateScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/CreateScript.groovy
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.databind.node.ObjectNode
 import org.apache.cxf.jaxrs.client.WebClient
 import org.identityconnectors.framework.common.objects.Uid
+import javax.ws.rs.core.Response
 
 // Parameters:
 // The connector sends us the following:
@@ -51,10 +52,15 @@ case "__ACCOUNT__":
   node.set("email", node.textNode(attributes.get("email").get(0)));
   
   String payload = mapper.writeValueAsString(node);
-  
+
   webClient.path("/users");
-  webClient.post(payload);
+
+  log.ok("Sending POST to {0} with payload {1}", webClient.getCurrentURI().toASCIIString(), payload);
+
+  Response response = webClient.post(payload);
   
+  log.ok("Create response: {0} {1}", response.getStatus(), response.getHeaders());
+
   key = node.get("key").textValue();
   break
 
diff --git a/fit/core-reference/src/test/resources/rest/DeleteScript.groovy b/fit/core-reference/src/test/resources/rest/DeleteScript.groovy
index 7cf805a..f78355e 100644
--- a/fit/core-reference/src/test/resources/rest/DeleteScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/DeleteScript.groovy
@@ -17,6 +17,7 @@
  * under the License.
  */
 import org.apache.cxf.jaxrs.client.WebClient
+import javax.ws.rs.core.Response
 
 // Parameters:
 // The connector sends the following:
@@ -36,7 +37,12 @@ assert uid != null
 switch ( objectClass ) {
 case "__ACCOUNT__":
   webClient.path("/users/" + uid);
-  webClient.delete();
+
+  log.ok("Sending DELETE to {0}", webClient.getCurrentURI().toASCIIString());
+
+  Response response =  webClient.delete();
+
+  log.ok("Delete response: {0} {1}", response.getStatus(), response.getHeaders());
   break
 
 default:
diff --git a/fit/core-reference/src/test/resources/rest/SearchScript.groovy b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
index ba7a2e8..a6f5abe 100644
--- a/fit/core-reference/src/test/resources/rest/SearchScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
@@ -22,7 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode
 import javax.ws.rs.core.Response
 import org.apache.cxf.jaxrs.client.WebClient
 import org.identityconnectors.common.security.GuardedString
-import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.OperationOptions
 
 // Parameters:
 // The connector sends the following:
@@ -92,9 +92,18 @@ def result = []
 
 switch (objectClass) {
 case "__ACCOUNT__":
-  if (query == null || (!query.get("left").equals("__UID__") && !query.get("conditionType").equals("EQUALS"))) {
+  if (query == null 
+    || (!query.get("left").equals("__UID__") && !query.get("left").equals("key")
+      && !query.get("conditionType").equals("EQUALS"))) {
+
     webClient.path("/users");
+
+    log.ok("Sending GET to {0}", webClient.getCurrentURI().toASCIIString());
+
     Response response = webClient.get();    
+
+    log.ok("LIST response: {0} {1}", response.getStatus(), response.getHeaders());
+
     ArrayNode nodes = mapper.readTree(response.getEntity());
     
     // beware: this is not enforcing any server-side pagination feature
@@ -103,7 +112,13 @@ case "__ACCOUNT__":
     }
   } else {
     webClient.path("/users/" + query.get("right"));
+
+    log.ok("Sending GET to {0}", webClient.getCurrentURI().toASCIIString());
+
     Response response = webClient.get();
+
+    log.ok("READ response: {0} {1}", response.getStatus(), response.getHeaders());
+
     if (response.getStatus() == 200) {
       ObjectNode node = mapper.readTree(response.getEntity());
       result.add(buildConnectorObject(node));
diff --git a/fit/core-reference/src/test/resources/rest/UpdateScript.groovy b/fit/core-reference/src/test/resources/rest/UpdateScript.groovy
index c0d258a..95cae71 100644
--- a/fit/core-reference/src/test/resources/rest/UpdateScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/UpdateScript.groovy
@@ -19,6 +19,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.databind.node.ObjectNode
 import org.apache.cxf.jaxrs.client.WebClient
+import javax.ws.rs.core.Response
 
 // Parameters:
 // The connector sends us the following:
@@ -81,8 +82,13 @@ case "UPDATE":
 
     // this if update works with PUT
     webClient.path("users").path(uid);
-    webClient.put(payload);
     
+    log.ok("Sending PUT to {0} with payload {1}", webClient.getCurrentURI().toASCIIString(), payload);
+    
+    Response response = webClient.put(payload);
+    
+    log.ok("Update response: {0} {1}", response.getStatus(), response.getHeaders());
+  
     // this instead if update works with PATCH
     //webClient.path("users").path(uid);
     //WebClient.getConfig(webClient).getRequestContext().put("use.async.http.conduit", true);