This is an automated email from the ASF dual-hosted git repository. ilgrosso pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push: new 5a85f3ec47 [SYNCOPE-1692] Support password change with updateDelta, update ConnId LDAP connector 5a85f3ec47 is described below commit 5a85f3ec47a8d639d0cc4c9f80b72813049a313b Author: Francesco Chicchiriccò <ilgro...@apache.org> AuthorDate: Tue Nov 15 09:12:07 2022 +0100 [SYNCOPE-1692] Support password change with updateDelta, update ConnId LDAP connector --- .../apache/syncope/client/lib/SyncopeClient.java | 11 ++-- .../core/provisioning/api/utils/RealmUtils.java | 8 +-- .../propagation/DefaultPropagationManager.java | 73 +++++++++++++--------- .../java/pushpull/SinglePullJobDelegate.java | 35 +++++------ .../java/pushpull/SinglePushJobDelegate.java | 23 ++++--- .../pushpull/stream/StreamPullJobDelegate.java | 30 +++++---- .../pushpull/stream/StreamPushJobDelegate.java | 22 +++---- .../java/job/JobStatusUpdaterTest.java | 6 +- pom.xml | 4 +- 9 files changed, 109 insertions(+), 103 deletions(-) diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java index 8b18388f46..60546ae1ae 100644 --- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java +++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java @@ -376,13 +376,12 @@ public class SyncopeClient { } try { - return Triple.of(MAPPER.readValue( - response.getHeaderString(RESTHeaders.OWNED_ENTITLEMENTS), - new TypeReference<>() { - }), + return Triple.of( MAPPER.readValue( - response.getHeaderString(RESTHeaders.DELEGATIONS), - new TypeReference<>() { + response.getHeaderString(RESTHeaders.OWNED_ENTITLEMENTS), new TypeReference<>() { + }), + MAPPER.readValue( + response.getHeaderString(RESTHeaders.DELEGATIONS), new TypeReference<>() { }), response.readEntity(UserTO.class)); } catch (IOException e) { diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/utils/RealmUtils.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/utils/RealmUtils.java index 80e4ba6098..cf71c0bb40 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/utils/RealmUtils.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/utils/RealmUtils.java @@ -88,13 +88,7 @@ public final class RealmUtils { } } - private static final Predicate<String> DYN_REALMS_PREDICATE = new Predicate<String>() { - - @Override - public boolean test(final String realm) { - return !realm.startsWith(SyncopeConstants.ROOT_REALM); - } - }; + private static final Predicate<String> DYN_REALMS_PREDICATE = r -> !r.startsWith(SyncopeConstants.ROOT_REALM); public static Set<String> getEffective(final Set<String> allowedRealms, final String requestedRealm) { Pair<Set<String>, Set<String>> normalized = normalize(allowedRealms); 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 7785d0b98d..4b158faf6a 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 @@ -679,6 +679,17 @@ public class DefaultPropagationManager implements PropagationManager { return attrs; } + /** + * Checks whether the given attribute shall be treated as an ordinary attribute or not, for purpose of building + * AttributeDelta instances. + * + * @param attr ConnId attibute + * @return whether the condition is matched or not + */ + protected boolean isOrdinaryForAttrForDelta(final Attribute attr) { + return !attr.is(Name.NAME) && !OperationalAttributes.isOperationalAttribute(attr); + } + @Override public List<PropagationTaskInfo> setAttributeDeltas( final List<PropagationTaskInfo> tasks, @@ -703,18 +714,10 @@ public class DefaultPropagationManager implements PropagationManager { Set<Attribute> attrs = new HashSet<>(beforeAttrs.get(key)); // purge unwanted attributes, even though prepared - attrs.removeIf(attr -> attr instanceof Name - || OperationalAttributes.ENABLE_NAME.equals(attr.getName()) - || MANDATORY_MISSING_ATTR_NAME.equals(attr.getName()) + attrs.removeIf( + attr -> MANDATORY_MISSING_ATTR_NAME.equals(attr.getName()) || MANDATORY_NULL_OR_EMPTY_ATTR_NAME.equals(attr.getName())); - // see org.identityconnectors.framework.impl.api.local.operations.UpdateDeltaImpl - if (attrs.stream().anyMatch(attr -> Name.NAME.equals(attr.getName()) - || OperationalAttributes.getOperationalAttributeNames().contains(attr.getName()))) { - - continue; - } - PropagationData propagationData = task.getPropagationData(); Set<AttributeDelta> attributeDeltas = new HashSet<>(); @@ -725,44 +728,56 @@ public class DefaultPropagationManager implements PropagationManager { Set<Object> valuesToRemove = new HashSet<>(); Optional.ofNullable(AttributeUtil.find(next.getName(), attrs)).ifPresent(prev -> { - if (next.getValue() == null && prev.getValue() != null) { - valuesToRemove.addAll(prev.getValue()); - } else if (next.getValue() != null && prev.getValue() == null) { + // password is unchanged from beforeAttrs but needs to be taken into account anyway + if (next.is(OperationalAttributes.PASSWORD_NAME)) { valuesToAdd.addAll(next.getValue()); - } else if (next.getValue() != null && prev.getValue() != null) { - next.getValue().stream(). - filter(value -> !prev.getValue().contains(value)). - forEach(valuesToAdd::add); - - prev.getValue().stream(). - filter(value -> !next.getValue().contains(value)). - forEach(valuesToRemove::add); + } else { + if (next.getValue() == null && prev.getValue() != null) { + valuesToRemove.addAll(prev.getValue()); + } else if (next.getValue() != null && prev.getValue() == null) { + valuesToAdd.addAll(next.getValue()); + } else if (next.getValue() != null && prev.getValue() != null) { + next.getValue().stream(). + filter(value -> !prev.getValue().contains(value)). + forEach(valuesToAdd::add); + + prev.getValue().stream(). + filter(value -> !next.getValue().contains(value)). + forEach(valuesToRemove::add); + } } }); - if (!valuesToAdd.isEmpty() || !valuesToRemove.isEmpty()) { - attributeDeltas.add(AttributeDeltaBuilder.build(next.getName(), valuesToAdd, valuesToRemove)); + // Following org.identityconnectors.framework.impl.api.local.operations.UpdateDeltaImpl#updateDelta + // we create AttributeDelta instances with (valuesToAdd, valuesToRemove) or (valuesToReplace) + // depending on the attribute name + if (isOrdinaryForAttrForDelta(next)) { + if (!valuesToAdd.isEmpty() || !valuesToRemove.isEmpty()) { + attributeDeltas.add(AttributeDeltaBuilder.build(next.getName(), valuesToAdd, valuesToRemove)); + } + } else { + if (!valuesToAdd.isEmpty()) { + attributeDeltas.add(AttributeDeltaBuilder.build(next.getName(), valuesToAdd)); + } } }); // build delta for new or removed attributes Set<String> nextNames = propagationData.getAttributes().stream(). - filter(attr -> !(attr instanceof Name) && !OperationalAttributes.isOperationalAttribute(attr)). + filter(this::isOrdinaryForAttrForDelta). map(Attribute::getName). collect(Collectors.toSet()); Set<String> prevNames = attrs.stream(). - filter(attr -> !(attr instanceof Name) && !OperationalAttributes.isOperationalAttribute(attr)). + filter(this::isOrdinaryForAttrForDelta). map(Attribute::getName). collect(Collectors.toSet()); nextNames.stream().filter(name -> !prevNames.contains(name)). - forEach(toAdd -> Optional.ofNullable( - AttributeUtil.find(toAdd, propagationData.getAttributes())). + forEach(toAdd -> Optional.ofNullable(AttributeUtil.find(toAdd, propagationData.getAttributes())). ifPresent(attr -> attributeDeltas.add( AttributeDeltaBuilder.build(attr.getName(), attr.getValue(), Set.of())))); prevNames.stream().filter(name -> !nextNames.contains(name)). - forEach(toRemove -> Optional.ofNullable( - AttributeUtil.find(toRemove, attrs)). + forEach(toRemove -> Optional.ofNullable(AttributeUtil.find(toRemove, attrs)). ifPresent(attr -> attributeDeltas.add( AttributeDeltaBuilder.build(attr.getName(), Set.of(), attr.getValue())))); diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java index c11e5421ed..95a726fb9b 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java @@ -74,20 +74,21 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin LOG.debug("Executing pull on {}", resource); + taskType = TaskType.PULL; try { - PullTask pullTask = entityFactory.newEntity(PullTask.class); - pullTask.setResource(resource); - pullTask.setMatchingRule(pullTaskTO.getMatchingRule() == null + task = entityFactory.newEntity(PullTask.class); + task.setResource(resource); + task.setMatchingRule(pullTaskTO.getMatchingRule() == null ? MatchingRule.UPDATE : pullTaskTO.getMatchingRule()); - pullTask.setUnmatchingRule(pullTaskTO.getUnmatchingRule() == null + task.setUnmatchingRule(pullTaskTO.getUnmatchingRule() == null ? UnmatchingRule.PROVISION : pullTaskTO.getUnmatchingRule()); - pullTask.setPullMode(PullMode.FILTERED_RECONCILIATION); - pullTask.setPerformCreate(pullTaskTO.isPerformCreate()); - pullTask.setPerformUpdate(pullTaskTO.isPerformUpdate()); - pullTask.setPerformDelete(pullTaskTO.isPerformDelete()); - pullTask.setSyncStatus(pullTaskTO.isSyncStatus()); - pullTask.setDestinationRealm(realmDAO.findByFullPath(pullTaskTO.getDestinationRealm())); - pullTask.setRemediation(pullTaskTO.isRemediation()); + task.setPullMode(PullMode.FILTERED_RECONCILIATION); + task.setPerformCreate(pullTaskTO.isPerformCreate()); + task.setPerformUpdate(pullTaskTO.isPerformUpdate()); + task.setPerformDelete(pullTaskTO.isPerformDelete()); + task.setSyncStatus(pullTaskTO.isSyncStatus()); + task.setDestinationRealm(realmDAO.findByFullPath(pullTaskTO.getDestinationRealm())); + task.setRemediation(pullTaskTO.isRemediation()); // validate JEXL expressions from templates and proceed if fine TemplateUtils.check(pullTaskTO.getTemplates(), ClientExceptionType.InvalidPullTask); pullTaskTO.getTemplates().forEach((type, template) -> { @@ -95,26 +96,24 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin if (anyType == null) { LOG.debug("Invalid AnyType {} specified, ignoring...", type); } else { - AnyTemplatePullTask anyTemplate = pullTask.getTemplate(anyType.getKey()).orElse(null); + AnyTemplatePullTask anyTemplate = task.getTemplate(anyType.getKey()).orElse(null); if (anyTemplate == null) { anyTemplate = entityFactory.newEntity(AnyTemplatePullTask.class); anyTemplate.setAnyType(anyType); - anyTemplate.setPullTask(pullTask); + anyTemplate.setPullTask(task); - pullTask.add(anyTemplate); + task.add(anyTemplate); } anyTemplate.set(template); } }); - profile = new ProvisioningProfile<>(connector, pullTask); + profile = new ProvisioningProfile<>(connector, task); profile.setDryRun(false); profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH); profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream(). map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList()))); profile.setExecutor(executor); - this.task = profile.getTask(); - this.taskType = TaskType.PULL; for (PullActions action : profile.getActions()) { action.beforeAll(profile); @@ -146,7 +145,7 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin Stream<Item> mapItems = Stream.concat( MappingUtils.getPullItems(provision.getMapping().getItems().stream()), - virSchemaDAO.find(pullTask.getResource().getKey(), anyType.getKey()).stream(). + virSchemaDAO.find(task.getResource().getKey(), anyType.getKey()).stream(). map(VirSchema::asLinkingMappingItem)); connector.filteredReconciliation( diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java index b40edf9861..e25434f88e 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java @@ -56,26 +56,25 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin LOG.debug("Executing push on {}", resource); - PushTask pushTask = entityFactory.newEntity(PushTask.class); - pushTask.setResource(resource); - pushTask.setMatchingRule(pushTaskTO.getMatchingRule() == null + taskType = TaskType.PUSH; + + task = entityFactory.newEntity(PushTask.class); + task.setResource(resource); + task.setMatchingRule(pushTaskTO.getMatchingRule() == null ? MatchingRule.LINK : pushTaskTO.getMatchingRule()); - pushTask.setUnmatchingRule(pushTaskTO.getUnmatchingRule() == null + task.setUnmatchingRule(pushTaskTO.getUnmatchingRule() == null ? UnmatchingRule.ASSIGN : pushTaskTO.getUnmatchingRule()); - pushTask.setPerformCreate(pushTaskTO.isPerformCreate()); - pushTask.setPerformUpdate(pushTaskTO.isPerformUpdate()); - pushTask.setPerformDelete(pushTaskTO.isPerformDelete()); - pushTask.setSyncStatus(pushTaskTO.isSyncStatus()); + task.setPerformCreate(pushTaskTO.isPerformCreate()); + task.setPerformUpdate(pushTaskTO.isPerformUpdate()); + task.setPerformDelete(pushTaskTO.isPerformDelete()); + task.setSyncStatus(pushTaskTO.isSyncStatus()); - profile = new ProvisioningProfile<>(connector, pushTask); + profile = new ProvisioningProfile<>(connector, task); profile.setExecutor(executor); profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream(). map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList()))); profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH); - this.task = profile.getTask(); - this.taskType = TaskType.PUSH; - for (PushActions action : profile.getActions()) { action.beforeAll(profile); } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java index 3b228d23e2..ec9e23d2d6 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java @@ -165,24 +165,25 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr LOG.debug("Executing stream pull"); + taskType = TaskType.PULL; try { ExternalResource resource = externalResource(anyType, keyColumn, columns, conflictResolutionAction, pullCorrelationRule); Provision provision = resource.getProvisions().get(0); - PullTask pullTask = entityFactory.newEntity(PullTask.class); - pullTask.setResource(resource); - pullTask.setMatchingRule(pullTaskTO.getMatchingRule()); - pullTask.setUnmatchingRule(pullTaskTO.getUnmatchingRule()); - pullTask.setPullMode(PullMode.FULL_RECONCILIATION); - pullTask.setPerformCreate(true); - pullTask.setPerformUpdate(true); - pullTask.setPerformDelete(false); - pullTask.setSyncStatus(false); - pullTask.setDestinationRealm(realmDAO.findByFullPath(pullTaskTO.getDestinationRealm())); - pullTask.setRemediation(pullTaskTO.isRemediation()); - - profile = new ProvisioningProfile<>(connector, pullTask); + task = entityFactory.newEntity(PullTask.class); + task.setResource(resource); + task.setMatchingRule(pullTaskTO.getMatchingRule()); + task.setUnmatchingRule(pullTaskTO.getUnmatchingRule()); + task.setPullMode(PullMode.FULL_RECONCILIATION); + task.setPerformCreate(true); + task.setPerformUpdate(true); + task.setPerformDelete(false); + task.setSyncStatus(false); + task.setDestinationRealm(realmDAO.findByFullPath(pullTaskTO.getDestinationRealm())); + task.setRemediation(pullTaskTO.isRemediation()); + + profile = new ProvisioningProfile<>(connector, task); profile.setDryRun(false); profile.setConflictResolutionAction(conflictResolutionAction); profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream(). @@ -219,9 +220,6 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr virSchemaDAO.find(resource.getKey(), anyType.getKey()).stream(). map(VirSchema::asLinkingMappingItem)); - this.task = profile.getTask(); - this.taskType = TaskType.PULL; - connector.fullReconciliation( new ObjectClass(provision.getObjectClass()), handler, diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java index 7628132fe5..0ba6965a59 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java @@ -129,27 +129,25 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr LOG.debug("Executing stream push as {}", executor); + taskType = TaskType.PUSH; try { ExternalResource resource = externalResource(anyType, columns, propagationActions); - PushTask pushTask = entityFactory.newEntity(PushTask.class); - pushTask.setResource(resource); - pushTask.setMatchingRule(pushTaskTO.getMatchingRule()); - pushTask.setUnmatchingRule(pushTaskTO.getUnmatchingRule()); - pushTask.setPerformCreate(true); - pushTask.setPerformUpdate(true); - pushTask.setPerformDelete(true); - pushTask.setSyncStatus(false); + task = entityFactory.newEntity(PushTask.class); + task.setResource(resource); + task.setMatchingRule(pushTaskTO.getMatchingRule()); + task.setUnmatchingRule(pushTaskTO.getUnmatchingRule()); + task.setPerformCreate(true); + task.setPerformUpdate(true); + task.setPerformDelete(true); + task.setSyncStatus(false); - profile = new ProvisioningProfile<>(connector, pushTask); + profile = new ProvisioningProfile<>(connector, task); profile.setExecutor(executor); profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream(). map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList()))); profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH); - this.task = profile.getTask(); - this.taskType = TaskType.PUSH; - for (PushActions action : profile.getActions()) { action.beforeAll(profile); } diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java index 47b4c2ae3b..bddc317e11 100644 --- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java +++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java @@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional; @Transactional("Master") public class JobStatusUpdaterTest extends AbstractTest { + @Autowired private EntityFactory entityFactory; @@ -40,10 +41,13 @@ public class JobStatusUpdaterTest extends AbstractTest { @Test public void verifyUpdate() { + String refDesc = "JobRefDesc-" + SecureRandomUtils.generateRandomNumber(); + JobStatusUpdater jobStatusUpdater = new JobStatusUpdater(jobStatusDAO, entityFactory); - final String refDesc = "JobRefDesc-" + SecureRandomUtils.generateRandomNumber(); + jobStatusUpdater.update(new JobStatusEvent(this, refDesc, "Started")); assertNotNull(jobStatusDAO.find(refDesc)); + jobStatusUpdater.update(new JobStatusEvent(this, refDesc, null)); assertNull(jobStatusDAO.find(refDesc)); } diff --git a/pom.xml b/pom.xml index ee5af62f35..41b2b199e1 100644 --- a/pom.xml +++ b/pom.xml @@ -402,7 +402,7 @@ under the License. <connid.rest.version>1.0.7</connid.rest.version> <connid.database.version>2.2.9</connid.database.version> <connid.csvdir.version>0.8.9</connid.csvdir.version> - <connid.ldap.version>1.5.6</connid.ldap.version> + <connid.ldap.version>1.5.7</connid.ldap.version> <connid.ad.version>1.3.8</connid.ad.version> <connid.googleapps.version>1.4.3</connid.googleapps.version> <connid.azure.version>2.0.0</connid.azure.version> @@ -503,7 +503,7 @@ under the License. <cargo.log>${log.directory}/cargo.log</cargo.log> <cargo.output>${log.directory}/cargo-output.log</cargo.output> - <tomcat.version>9.0.68</tomcat.version> + <tomcat.version>9.0.69</tomcat.version> <wildfly.version>26.1.2.Final</wildfly.version> <payara.version>5.2022.3</payara.version> <javax.faces.version>2.3.14</javax.faces.version>