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

mdisabatino pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/3_0_X by this push:
     new a2c77d3f66 [SYNCOPE-1864] Unwanted password propagation after update 
on pull
a2c77d3f66 is described below

commit a2c77d3f665a6e13d5af669988a24481d8503610
Author: Marco Di Sabatino Di Diodoro <[email protected]>
AuthorDate: Thu Feb 6 12:11:51 2025 +0100

    [SYNCOPE-1864] Unwanted password propagation after update on pull
---
 .../api/propagation/PropagationManager.java        |  13 +--
 .../java/DefaultAnyObjectProvisioningManager.java  |   6 +-
 .../java/DefaultGroupProvisioningManager.java      |   6 +-
 .../java/DefaultUserProvisioningManager.java       |  13 ++-
 .../java/data/AbstractAnyDataBinder.java           |   4 +-
 .../java/data/AnyObjectDataBinderImpl.java         |   4 +-
 .../java/data/GroupDataBinderImpl.java             |   5 +-
 .../provisioning/java/data/UserDataBinderImpl.java |  12 +--
 .../propagation/DefaultPropagationManager.java     |  50 ++++++----
 .../java/pushpull/AbstractPushResultHandler.java   |   5 +-
 .../pushpull/DefaultUserPushResultHandler.java     |   2 +-
 .../apache/syncope/fit/core/PullTaskITCase.java    | 102 +++++++++++++++++++++
 .../test/resources/AddResourcePullActions.groovy   |  69 ++++++++++++++
 13 files changed, 239 insertions(+), 52 deletions(-)

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 c0e2b99860..f9ac05cca2 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
@@ -100,7 +100,7 @@ public interface PropagationManager {
      * @param anyUR update request
      * @param kind any type kind
      * @param key any key
-     * @param changePwd whether password should be included for propagation 
attributes or not
+     * @param changePwdRes the resources in which the password must be 
included in the propagation attributes
      * @param enable whether any 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
@@ -112,7 +112,7 @@ public interface PropagationManager {
             AnyUR anyUR,
             AnyTypeKind kind,
             String key,
-            boolean changePwd,
+            List<String> changePwdRes,
             Boolean enable,
             PropagationByResource<String> propByRes,
             PropagationByResource<Pair<String, String>> propByLinkedAccount,
@@ -123,13 +123,13 @@ public interface PropagationManager {
      * Create the update tasks for the user on each resource associated, 
unless in {@code excludedResources}.
      *
      * @param wfResult user to be propagated (and info associated), as per 
result from workflow
-     * @param changePwd whether password should be included for propagation 
attributes or not
+     * @param changePwdRes the resources in which the password must be 
included in the propagation attributes
      * @param excludedResources external resources not to be considered for 
propagation
      * @return list of propagation tasks
      */
     List<PropagationTaskInfo> getUserUpdateTasks(
             UserWorkflowResult<Pair<UserUR, Boolean>> wfResult,
-            boolean changePwd,
+            List<String> changePwdRes,
             Collection<String> excludedResources);
 
     /**
@@ -186,7 +186,8 @@ public interface PropagationManager {
      * @param kind any type kind
      * @param key any key
      * @param password to be set (for users)
-     * @param changePwd whether password should be included for propagation 
attributes or not (for users)
+     * @param changePwdRes the resources in which the password must be 
included in the propagation attributes (for 
+     * users)
      * @param enable whether any should be enabled or not, may be null to 
leave unchanged
      * @param excludedResources external resource keys not to be considered 
for propagation
      * @return map with prepared attributes per External Resource
@@ -195,7 +196,7 @@ public interface PropagationManager {
             AnyTypeKind kind,
             String key,
             String password,
-            boolean changePwd,
+            List<String> changePwdRes,
             Boolean enable,
             Collection<String> excludedResources);
 
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 2d6f8e011a..839ae78950 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
@@ -115,7 +115,7 @@ public class DefaultAnyObjectProvisioningManager implements 
AnyObjectProvisionin
                 AnyTypeKind.ANY_OBJECT,
                 anyObjectUR.getKey(),
                 null,
-                false,
+                List.of(),
                 null,
                 excludedResources);
 
@@ -126,7 +126,7 @@ public class DefaultAnyObjectProvisioningManager implements 
AnyObjectProvisionin
                         updated.getResult(),
                         AnyTypeKind.ANY_OBJECT,
                         updated.getResult().getKey(),
-                        false,
+                        List.of(),
                         null,
                         updated.getPropByRes(),
                         null,
@@ -199,7 +199,7 @@ public class DefaultAnyObjectProvisioningManager implements 
AnyObjectProvisionin
                 null,
                 AnyTypeKind.ANY_OBJECT,
                 key,
-                false,
+                List.of(),
                 null,
                 propByRes,
                 null,
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 7f45eba1be..65e3c06b95 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
@@ -135,7 +135,7 @@ public class DefaultGroupProvisioningManager implements 
GroupProvisioningManager
                 AnyTypeKind.GROUP,
                 groupUR.getKey(),
                 null,
-                false,
+                List.of(),
                 null,
                 excludedResources);
 
@@ -146,7 +146,7 @@ public class DefaultGroupProvisioningManager implements 
GroupProvisioningManager
                         updated.getResult(),
                         AnyTypeKind.GROUP,
                         updated.getResult().getKey(),
-                        false,
+                        List.of(),
                         null,
                         updated.getPropByRes(),
                         null,
@@ -234,7 +234,7 @@ public class DefaultGroupProvisioningManager implements 
GroupProvisioningManager
                 null,
                 AnyTypeKind.GROUP,
                 key,
-                false,
+                List.of(),
                 null,
                 propByRes,
                 null,
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 ab3f613a32..d1985f6f28 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
@@ -123,7 +123,7 @@ public class DefaultUserProvisioningManager implements 
UserProvisioningManager {
                 AnyTypeKind.USER,
                 userUR.getKey(),
                 
Optional.ofNullable(userUR.getPassword()).map(PasswordPatch::getValue).orElse(null),
-                userUR.getPassword() != null,
+                userUR.getPassword() != null ? 
userUR.getPassword().getResources() : List.of(),
                 null,
                 Set.of());
 
@@ -163,7 +163,7 @@ public class DefaultUserProvisioningManager implements 
UserProvisioningManager {
                 AnyTypeKind.USER,
                 userUR.getKey(),
                 
Optional.ofNullable(userUR.getPassword()).map(PasswordPatch::getValue).orElse(null),
-                userUR.getPassword() != null,
+                userUR.getPassword() != null ? 
userUR.getPassword().getResources() : List.of(),
                 enabled,
                 excludedResources);
 
@@ -187,7 +187,9 @@ public class DefaultUserProvisioningManager implements 
UserProvisioningManager {
         List<PropagationTaskInfo> taskInfos = 
propagationManager.setAttributeDeltas(
                 propagationManager.getUserUpdateTasks(
                         updated,
-                        updated.getResult().getLeft().getPassword() != null,
+                        updated.getResult().getLeft().getPassword() != null
+                                ? 
updated.getResult().getLeft().getPassword().getResources()
+                                : List.of(),
                         excludedResources),
                 beforeAttrs);
         PropagationReporter propagationReporter = 
taskExecutor.execute(taskInfos, nullPriorityAsync, updater);
@@ -290,7 +292,7 @@ public class DefaultUserProvisioningManager implements 
UserProvisioningManager {
                 null,
                 AnyTypeKind.USER,
                 statusR.getKey(),
-                false,
+                List.of(),
                 statusR.getType() != StatusRType.SUSPEND,
                 propByRes,
                 null,
@@ -348,7 +350,8 @@ public class DefaultUserProvisioningManager implements 
UserProvisioningManager {
         UserWorkflowResult<Pair<UserUR, Boolean>> wfResult = new 
UserWorkflowResult<>(
                 Pair.of(userUR, (Boolean) null), propByRes, null, "update");
 
-        List<PropagationTaskInfo> taskInfos = 
propagationManager.getUserUpdateTasks(wfResult, changePwd, null);
+        List<PropagationTaskInfo> taskInfos = 
propagationManager.getUserUpdateTasks(wfResult,
+                userUR.getPassword().getResources(), null);
         PropagationReporter propagationReporter = 
taskExecutor.execute(taskInfos, nullPriorityAsync, executor);
 
         return propagationReporter.getStatuses();
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index fe072db0ff..fb2b6e2d8d 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -196,7 +196,7 @@ abstract class AbstractAnyDataBinder {
             final Any<?> any,
             final Collection<String> resources,
             final String password,
-            final boolean changePwd) {
+            final Set<String> changePwdRes) {
 
         Map<String, ConnObject> onResources = new HashMap<>();
 
@@ -205,7 +205,7 @@ abstract class AbstractAnyDataBinder {
                     ifPresent(provision -> 
MappingUtils.getConnObjectKeyItem(provision).ifPresent(keyItem -> {
 
                 Pair<String, Set<Attribute>> prepared = 
mappingManager.prepareAttrsFromAny(
-                        any, password, changePwd, true, resource, provision);
+                        any, password, 
changePwdRes.contains(resource.getKey()), true, resource, provision);
 
                 ConnObject connObjectTO;
                 if (StringUtils.isBlank(prepared.getLeft())) {
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index 87cb87b384..36a47406f7 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -313,7 +313,7 @@ public class AnyObjectDataBinderImpl extends 
AbstractAnyDataBinder implements An
 
         // Save projection on Resources (before update)
         Map<String, ConnObject> beforeOnResources =
-                onResources(anyObject, 
anyObjectDAO.findAllResourceKeys(anyObject.getKey()), null, false);
+                onResources(anyObject, 
anyObjectDAO.findAllResourceKeys(anyObject.getKey()), null, Set.of());
 
         SyncopeClientCompositeException scce = 
SyncopeClientException.buildComposite();
 
@@ -478,7 +478,7 @@ public class AnyObjectDataBinderImpl extends 
AbstractAnyDataBinder implements An
         // Build final information for next stage (propagation)
         propByRes.merge(propByRes(
                 beforeOnResources,
-                onResources(saved, 
anyObjectDAO.findAllResourceKeys(anyObject.getKey()), null, false)));
+                onResources(saved, 
anyObjectDAO.findAllResourceKeys(anyObject.getKey()), null, Set.of())));
         return propByRes;
     }
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index 7429c05b38..f80ca653f7 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
@@ -251,7 +252,7 @@ public class GroupDataBinderImpl extends 
AbstractAnyDataBinder implements GroupD
 
         // Save projection on Resources (before update)
         Map<String, ConnObject> beforeOnResources =
-                onResources(group, 
groupDAO.findAllResourceKeys(group.getKey()), null, false);
+                onResources(group, 
groupDAO.findAllResourceKeys(group.getKey()), null, Set.of());
 
         SyncopeClientCompositeException scce = 
SyncopeClientException.buildComposite();
 
@@ -382,7 +383,7 @@ public class GroupDataBinderImpl extends 
AbstractAnyDataBinder implements GroupD
 
         // Build final information for next stage (propagation)
         PropagationByResource<String> propByRes = propByRes(
-                beforeOnResources, onResources(group, 
groupDAO.findAllResourceKeys(group.getKey()), null, false));
+                beforeOnResources, onResources(group, 
groupDAO.findAllResourceKeys(group.getKey()), null, Set.of()));
         propByRes.merge(ownerPropByRes);
         return propByRes;
     }
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 60027eb448..2d97ce08c9 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
@@ -461,12 +461,12 @@ public class UserDataBinderImpl extends 
AbstractAnyDataBinder implements UserDat
 
         // password
         String password = null;
-        boolean changePwd = false;
+        Set<String> changePwdRes = new HashSet<>();
         if (userUR.getPassword() != null) {
             if (userUR.getPassword().getOperation() == PatchOperation.DELETE) {
                 user.setEncodedPassword(null, null);
 
-                changePwd = true;
+                changePwdRes.addAll(userUR.getPassword().getResources());
             } else if 
(StringUtils.isNotBlank(userUR.getPassword().getValue())) {
                 if (userUR.getPassword().isOnSyncope()) {
                     setPassword(user, userUR.getPassword().getValue(), scce);
@@ -474,17 +474,17 @@ public class UserDataBinderImpl extends 
AbstractAnyDataBinder implements UserDat
                 }
 
                 password = userUR.getPassword().getValue();
-                changePwd = true;
+                changePwdRes.addAll(userUR.getPassword().getResources());
             }
 
-            if (changePwd) {
+            if (!changePwdRes.isEmpty()) {
                 propByRes.addAll(ResourceOperation.UPDATE, 
userUR.getPassword().getResources());
             }
         }
 
         // Save projection on Resources (before update)
         Map<String, ConnObject> beforeOnResources =
-                onResources(user, userDAO.findAllResourceKeys(user.getKey()), 
password, changePwd);
+                onResources(user, userDAO.findAllResourceKeys(user.getKey()), 
password, changePwdRes);
 
         // realm
         setRealm(user, userUR);
@@ -729,7 +729,7 @@ public class UserDataBinderImpl extends 
AbstractAnyDataBinder implements UserDat
 
         // Build final information for next stage (propagation)
         Map<String, ConnObject> afterOnResources =
-                onResources(user, userDAO.findAllResourceKeys(user.getKey()), 
password, changePwd);
+                onResources(user, userDAO.findAllResourceKeys(user.getKey()), 
password, changePwdRes);
         propByRes.merge(propByRes(beforeOnResources, afterOnResources));
 
         if (userUR.getMustChangePassword() != null) {
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
index 9a7dfc46ae..3d141e3914 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
@@ -168,6 +168,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
             return List.of();
         }
 
+        List<String> changePwdRes = new ArrayList<>();
         if (excludedResources != null) {
             if (propByRes != null) {
                 
propByRes.get(ResourceOperation.CREATE).removeAll(excludedResources);
@@ -179,7 +180,13 @@ public class DefaultPropagationManager implements 
PropagationManager {
             }
         }
 
-        return createTasks(any, password, true, enable, propByRes, 
propByLinkedAccount, vAttrs);
+        if (propByRes != null) {
+            propByRes.asMap().forEach((resource, resourceOperation) -> 
changePwdRes.add(resource));
+        }
+        if (propByLinkedAccount != null) {
+            propByLinkedAccount.asMap().forEach((resource, resourceOperation) 
-> changePwdRes.add(resource.getKey()));
+        }
+        return createTasks(any, password, changePwdRes, enable, propByRes, 
propByLinkedAccount, vAttrs);
     }
 
     @Override
@@ -187,7 +194,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
             final AnyUR anyUR,
             final AnyTypeKind kind,
             final String key,
-            final boolean changePwd,
+            final List<String> changePwdRes,
             final Boolean enable,
             final PropagationByResource<String> propByRes,
             final PropagationByResource<Pair<String, String>> 
propByLinkedAccount,
@@ -198,7 +205,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
                 anyUR,
                 anyUtilsFactory.getInstance(kind).dao().authFind(key),
                 null,
-                changePwd,
+                changePwdRes,
                 enable,
                 propByRes,
                 propByLinkedAccount,
@@ -209,7 +216,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
     @Override
     public List<PropagationTaskInfo> getUserUpdateTasks(
             final UserWorkflowResult<Pair<UserUR, Boolean>> wfResult,
-            final boolean changePwd,
+            final List<String> changePwdRes,
             final Collection<String> excludedResources) {
 
         return getUpdateTasks(
@@ -217,7 +224,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
                 
anyUtilsFactory.getInstance(AnyTypeKind.USER).dao().authFind(wfResult.getResult().getLeft().getKey()),
                 
Optional.ofNullable(wfResult.getResult().getLeft().getPassword()).
                         map(PasswordPatch::getValue).orElse(null),
-                changePwd,
+                changePwdRes,
                 wfResult.getResult().getRight(),
                 wfResult.getPropByRes(),
                 wfResult.getPropByLinkedAccount(),
@@ -233,7 +240,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
         List<PropagationTaskInfo> tasks;
         if (userUR.getPassword() == null) {
             // a. no specific password propagation request: generate 
propagation tasks for any resource associated
-            tasks = getUserUpdateTasks(wfResult, false, null);
+            tasks = getUserUpdateTasks(wfResult, List.of(), null);
         } else {
             tasks = new ArrayList<>();
 
@@ -263,7 +270,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
                         
map(AbstractPatchItem::getValue).collect(Collectors.toList()));
                 toBeExcluded.removeAll(pwdResourceNames);
 
-                tasks.addAll(getUserUpdateTasks(pwdWFResult, true, 
toBeExcluded));
+                tasks.addAll(getUserUpdateTasks(pwdWFResult, new 
ArrayList<>(pwdResourceNames), toBeExcluded));
             }
 
             UserWorkflowResult<Pair<UserUR, Boolean>> noPwdWFResult = new 
UserWorkflowResult<>(
@@ -276,7 +283,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
             noPwdWFResult.getPropByRes().removeAll(pwdResourceNames);
             noPwdWFResult.getPropByRes().purge();
             if (!noPwdWFResult.getPropByRes().isEmpty()) {
-                tasks.addAll(getUserUpdateTasks(noPwdWFResult, false, 
pwdResourceNames));
+                tasks.addAll(getUserUpdateTasks(noPwdWFResult, List.of(), 
pwdResourceNames));
             }
 
             tasks = tasks.stream().distinct().collect(Collectors.toList());
@@ -290,7 +297,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
             final AnyUR anyUR,
             final Any<?> any,
             final String password,
-            final boolean changePwd,
+            final List<String> changePwdRes,
             final Boolean enable,
             final PropagationByResource<String> propByRes,
             final PropagationByResource<Pair<String, String>> 
propByLinkedAccount,
@@ -315,7 +322,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
         List<PropagationTaskInfo> tasks = createTasks(
                 any,
                 password,
-                changePwd,
+                changePwdRes,
                 enable,
                 
Optional.ofNullable(propByRes).orElseGet(PropagationByResource::new),
                 propByLinkedAccount,
@@ -366,7 +373,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
             }
         }
 
-        return createTasks(any, null, false, false, localPropByRes, 
propByLinkedAccount, null);
+        return createTasks(any, null, List.of(), false, localPropByRes, 
propByLinkedAccount, null);
     }
 
     @Override
@@ -419,7 +426,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
      *
      * @param any to be provisioned
      * @param password clear text password to be provisioned
-     * @param changePwd whether password should be included for propagation 
attributes or not
+     * @param changePwdRes the resources in which the password must be 
included in the propagation attributes
      * @param enable whether user must be enabled or not
      * @param propByRes operation to be performed per resource
      * @param propByLinkedAccount operation to be performed on linked accounts
@@ -429,7 +436,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
     protected List<PropagationTaskInfo> createTasks(
             final Any<?> any,
             final String password,
-            final boolean changePwd,
+            final List<String> changePwdRes,
             final Boolean enable,
             final PropagationByResource<String> propByRes,
             final PropagationByResource<Pair<String, String>> 
propByLinkedAccount,
@@ -496,7 +503,8 @@ public class DefaultPropagationManager implements 
PropagationManager {
                         any.getType(), resource);
             } else {
                 Pair<String, Set<Attribute>> preparedAttrs =
-                        mappingManager.prepareAttrsFromAny(any, password, 
changePwd, enable, resource, provision);
+                        mappingManager.prepareAttrsFromAny(any, password, 
changePwdRes.contains(resourceKey),
+                                enable, resource, provision);
                 if (vAttrMap.containsKey(resourceKey)) {
                     preparedAttrs.getRight().addAll(vAttrMap.get(resourceKey));
                 }
@@ -553,7 +561,9 @@ public class DefaultPropagationManager implements 
PropagationManager {
                             mappingItems,
                             Pair.of(account.getConnObjectKeyValue(),
                                     
mappingManager.prepareAttrsFromLinkedAccount(
-                                            user, account, password, true, 
provision)));
+                                            user, account, password, 
+                                            
changePwdRes.contains(account.getResource().getKey()),
+                                            provision)));
                     tasks.add(accountTask);
 
                     LOG.debug("PropagationTask created for Linked Account {}: 
{}",
@@ -617,13 +627,14 @@ public class DefaultPropagationManager implements 
PropagationManager {
         return tasks;
     }
 
-    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
+    @Transactional(readOnly = true,
+                   propagation = Propagation.REQUIRES_NEW)
     @Override
     public Map<Pair<String, String>, Set<Attribute>> prepareAttrs(
             final AnyTypeKind kind,
             final String key,
             final String password,
-            final boolean changePwd,
+            final List<String> changePwdRes,
             final Boolean enable,
             final Collection<String> excludedResources) {
 
@@ -640,7 +651,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
                     Pair<String, Set<Attribute>> preparedAttrs = 
mappingManager.prepareAttrsFromAny(
                             any,
                             password,
-                            changePwd,
+                            changePwdRes.contains(resource.getKey()),
                             enable,
                             resource,
                             
resource.getProvisionByAnyType(any.getType().getKey()).get());
@@ -672,7 +683,8 @@ public class DefaultPropagationManager implements 
PropagationManager {
         return attrs;
     }
 
-    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
+    @Transactional(readOnly = true,
+                   propagation = Propagation.REQUIRES_NEW)
     @Override
     public Map<Pair<String, String>, Set<Attribute>> prepareAttrs(final Realm 
realm) {
         Map<Pair<String, String>, Set<Attribute>> attrs = new HashMap<>();
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 dda4dfad11..7b64944f7b 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
@@ -95,8 +95,7 @@ public abstract class AbstractPushResultHandler extends 
AbstractSyncopeResultHan
             final Boolean enable,
             final ConnectorObject beforeObj,
             final ProvisioningReport result) {
-
-        boolean changepwd = any instanceof User;
+        
         List<String> ownedResources = 
getAnyUtils().getAllResources(any).stream().
                 map(ExternalResource::getKey).collect(Collectors.toList());
 
@@ -111,7 +110,7 @@ public abstract class AbstractPushResultHandler extends 
AbstractSyncopeResultHan
                 null,
                 any.getType().getKind(),
                 any.getKey(),
-                changepwd,
+                any instanceof User ? 
List.of(profile.getTask().getResource().getKey()) : List.of(),
                 enable,
                 propByRes,
                 null,
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 12e4dde3f4..6fceb9d1b3 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
@@ -128,7 +128,7 @@ public class DefaultUserPushResultHandler extends 
AbstractPushResultHandler impl
                 null,
                 any.getType().getKind(),
                 any.getKey(),
-                true,
+                List.of(profile.getTask().getResource().getKey()),
                 enable,
                 propByRes,
                 propByLinkedAccount,
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index 10ae357ead..40bde3b524 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -66,6 +66,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.request.AnyCR;
 import org.apache.syncope.common.lib.request.AnyObjectCR;
+import org.apache.syncope.common.lib.request.AttrPatch;
 import org.apache.syncope.common.lib.request.GroupCR;
 import org.apache.syncope.common.lib.request.PasswordPatch;
 import org.apache.syncope.common.lib.request.ResourceDR;
@@ -80,6 +81,7 @@ import org.apache.syncope.common.lib.to.ImplementationTO;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.PullTaskTO;
@@ -97,6 +99,7 @@ import 
org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.lib.types.PullMode;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
@@ -112,10 +115,13 @@ import org.apache.syncope.common.rest.api.beans.TaskQuery;
 import org.apache.syncope.common.rest.api.service.ConnectorService;
 import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.common.rest.api.service.UserService;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationData;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import 
org.apache.syncope.core.provisioning.java.pushpull.DBPasswordPullActions;
 import 
org.apache.syncope.core.provisioning.java.pushpull.LDAPPasswordPullActions;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.fit.core.reference.TestPullActions;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -1592,6 +1598,102 @@ public class PullTaskITCase extends AbstractTaskITCase {
         }
     }
 
+    @Test
+    public void issueSYNCOPE1864() throws Exception {
+        // First of all, clear any potential conflict with existing user / 
group
+        ldapCleanup();
+
+        UserTO user = null;
+        PullTaskTO pullTask = null;
+        ConnInstanceTO resourceConnector = null;
+        ConnConfProperty property = null;
+        try {
+            // 1. create user in LDAP
+            String oldCleanPassword = "security123";
+            UserCR userCR = 
UserITCase.getUniqueSample("[email protected]");
+            userCR.setPassword(oldCleanPassword);
+            userCR.getResources().add(RESOURCE_NAME_LDAP);
+            userCR.getResources().add(RESOURCE_NAME_DBPULL);
+            user = createUser(userCR).getEntity();
+            assertNotNull(user);
+            assertFalse(user.getResources().isEmpty());
+
+            // 2. Pull the user from the resource
+            ImplementationTO pullActions;
+            try {
+                pullActions = IMPLEMENTATION_SERVICE.read(
+                        IdMImplementationType.PULL_ACTIONS, 
"AddResourcePullActions");
+            } catch (SyncopeClientException e) {
+                pullActions = new ImplementationTO();
+                pullActions.setKey("AddResourcePullActions");
+                pullActions.setEngine(ImplementationEngine.GROOVY);
+                pullActions.setType(IdMImplementationType.PULL_ACTIONS);
+                pullActions.setBody(org.apache.commons.io.IOUtils.toString(
+                        
getClass().getResourceAsStream("/AddResourcePullActions.groovy"), 
StandardCharsets.UTF_8));
+                Response response = IMPLEMENTATION_SERVICE.create(pullActions);
+                pullActions = IMPLEMENTATION_SERVICE.read(
+                        pullActions.getType(), 
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+            }
+            assertNotNull(pullActions);
+
+            pullTask = new PullTaskTO();
+            pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
+            pullTask.setName(getUUIDString());
+            pullTask.setActive(true);
+            pullTask.setPerformCreate(true);
+            pullTask.setPerformUpdate(true);
+            pullTask.setPullMode(PullMode.FULL_RECONCILIATION);
+            pullTask.setResource(RESOURCE_NAME_LDAP);
+            pullTask.getActions().add(pullActions.getKey());
+            Response taskResponse = TASK_SERVICE.create(TaskType.PULL, 
pullTask);
+
+            pullTask = getObject(taskResponse.getLocation(), 
TaskService.class, PullTaskTO.class);
+            assertNotNull(pullTask);
+
+            ExecTO execution = execProvisioningTask(
+                    TASK_SERVICE, TaskType.PULL, pullTask.getKey(), 
MAX_WAIT_SECONDS, false);
+            assertEquals(ExecStatus.SUCCESS, 
ExecStatus.valueOf(execution.getStatus()));
+
+            // 3. Test if password is not present in the propagation task for 
DB
+            PagedResult<PropagationTaskTO> propagationTasks = 
TASK_SERVICE.search(
+                    new TaskQuery.Builder(TaskType.PROPAGATION).
+                            resource(RESOURCE_NAME_TESTDB2).
+                            
anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
+            
assertTrue(propagationTasks.getResult().stream().anyMatch(propagationTask -> {
+                Set<org.identityconnectors.framework.common.objects.Attribute> 
attributes =
+                        POJOHelper.deserialize(
+                                propagationTask.getPropagationData(), 
PropagationData.class).getAttributes();
+                return 
ResourceOperation.UPDATE.equals(propagationTask.getOperation())
+                        && AttributeUtil.getPasswordValue(attributes) != null;
+            }));
+
+            propagationTasks = TASK_SERVICE.search(
+                    new TaskQuery.Builder(TaskType.PROPAGATION).
+                            resource(RESOURCE_NAME_DBPULL).
+                            
anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
+            
assertTrue(propagationTasks.getResult().stream().anyMatch(propagationTask -> {
+                Set<org.identityconnectors.framework.common.objects.Attribute> 
attributes =
+                        POJOHelper.deserialize(
+                                propagationTask.getPropagationData(), 
PropagationData.class).getAttributes();
+                return 
ResourceOperation.UPDATE.equals(propagationTask.getOperation())
+                        && AttributeUtil.getPasswordValue(attributes) == null;
+            }));
+            
+            UserUR userUR = new UserUR();
+            userUR.setKey(user.getKey());
+            Attr attr = new Attr();
+            attr.setSchema("surname");
+            attr.getValues().add("surname2");
+            AttrPatch attrPatch = new AttrPatch();
+            attrPatch.setAttr(attr);
+            attrPatch.setOperation(PatchOperation.ADD_REPLACE);
+            userUR.getPlainAttrs().add(attrPatch);
+        } finally {
+            // remove test entity
+            deleteUser(user.getKey());
+        }
+    }
+
     private static void cleanUpRemediations() {
         REMEDIATION_SERVICE.list(new 
RemediationQuery.Builder().page(1).size(100).build()).getResult().forEach(
                 r -> REMEDIATION_SERVICE.delete(r.getKey()));
diff --git 
a/fit/core-reference/src/test/resources/AddResourcePullActions.groovy 
b/fit/core-reference/src/test/resources/AddResourcePullActions.groovy
new file mode 100644
index 0000000000..483a5e8c80
--- /dev/null
+++ b/fit/core-reference/src/test/resources/AddResourcePullActions.groovy
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import groovy.transform.CompileStatic
+
+import org.apache.syncope.core.provisioning.api.pushpull.PullActions
+import org.apache.syncope.common.lib.request.AnyUR;
+import org.apache.syncope.common.lib.to.EntityTO;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.request.PasswordPatch;
+import org.apache.syncope.common.lib.request.StringPatchItem;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
+import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.request.AttrPatch;
+
+/**
+ * Class for integration tests: add new resource and put a password only for 
it.
+ */
+@CompileStatic
+class AddResourcePullActions implements PullActions {
+
+  void beforeUpdate(
+          final ProvisioningProfile<?, ?> profile,
+          final SyncDelta delta,
+          final EntityTO entity,
+          final AnyUR anyUR) throws JobExecutionException {
+
+    if (anyUR instanceof UserUR) {
+      UserUR userUR = (UserUR) anyUR;
+      Attr attr = new Attr();
+      attr.setSchema("surname");
+      attr.getValues().add("surname2");
+      AttrPatch attrPatch = new AttrPatch();
+      attrPatch.setAttr(attr);
+      attrPatch.setOperation(PatchOperation.ADD_REPLACE);
+      userUR.getPlainAttrs().add(attrPatch);
+
+      PasswordPatch patch = new PasswordPatch();
+      patch.setOnSyncope(false);
+      patch.setValue("Password123");
+      patch.setOperation(PatchOperation.ADD_REPLACE);
+      patch.getResources().add("resource-testdb2");
+      userUR.setPassword(patch);
+
+      StringPatchItem resPatchItem = new StringPatchItem();
+      resPatchItem.setValue("resource-testdb2");
+      resPatchItem.setOperation(PatchOperation.ADD_REPLACE);
+      userUR.getResources().add(resPatchItem);
+    }
+  }
+}
\ No newline at end of file


Reply via email to