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/08 09:27:39 UTC
[syncope] branch 2_1_X updated: [SYNCOPE-957] Pull 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 2b7b0f1 [SYNCOPE-957] Pull implemented
2b7b0f1 is described below
commit 2b7b0f194e2bc968c6273b1e4014c1fc2c2b5b5a
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Tue Oct 8 11:26:19 2019 +0200
[SYNCOPE-957] Pull implemented
---
.../console/implementations/MyLogicActions.groovy | 55 ++-
.../console/implementations/MyPullActions.groovy | 9 +-
.../apache/syncope/common/lib/EntityTOUtils.java | 2 +-
.../syncope/common/lib/to/LinkedAccountTO.java | 27 +-
.../syncope/core/logic/AbstractAnyLogic.java | 49 +-
.../syncope/core/logic/init/EntitlementLoader.java | 8 +-
.../persistence/api/dao/PullCorrelationRule.java | 33 ++
.../core/persistence/api/dao/PullMatch.java | 116 +++++
.../core/provisioning/api/LogicActions.java | 25 +-
.../provisioning/api/PropagationByResource.java | 20 +-
.../provisioning/api/cache/VirAttrCacheValue.java | 69 +--
.../core/provisioning/api/data/UserDataBinder.java | 4 +
.../api/pushpull/ProvisioningReport.java | 30 +-
.../provisioning/api/pushpull/PullActions.java | 8 +-
.../core/provisioning/java/VirAttrHandlerImpl.java | 7 +-
.../provisioning/java/data/UserDataBinderImpl.java | 48 +-
.../AbstractPropagationTaskExecutor.java | 8 +-
.../PriorityPropagationTaskExecutor.java | 6 +-
.../java/propagation/PropagationManagerImpl.java | 33 +-
.../java/pushpull/AbstractPullResultHandler.java | 489 +++++++++----------
.../pushpull/DefaultRealmPullResultHandler.java | 43 +-
.../pushpull/DefaultUserPullResultHandler.java | 534 ++++++++++++++++++++-
.../core/provisioning/java/pushpull/PullUtils.java | 95 ++--
.../core/provisioning/java/pushpull/PushUtils.java | 27 --
.../provisioning/java/utils/ConnObjectUtils.java | 124 ++---
.../camel/CamelUserProvisioningManager.java | 2 +-
.../syncope/core/logic/init/OIDCClientLoader.java | 13 +-
.../syncope/core/logic/oidc/OIDCUserManager.java | 29 +-
.../core/provisioning/api/OIDCProviderActions.java | 18 +-
.../java/DefaultOIDCProviderActions.java | 48 --
.../syncope/core/logic/init/SAML2SPLoader.java | 2 +-
.../syncope/core/logic/saml2/SAML2UserManager.java | 29 +-
.../core/provisioning/api/SAML2IdPActions.java | 17 +-
.../buildtools/cxf/DateParamConverterProvider.java | 61 +++
.../apache/syncope/fit/buildtools/cxf/User.java | 15 +
.../syncope/fit/buildtools/cxf/UserMetadata.java | 41 +-
.../syncope/fit/buildtools/cxf/UserService.java | 6 +
.../fit/buildtools/cxf/UserServiceImpl.java | 86 ++--
fit/build-tools/src/main/resources/cxfContext.xml | 6 +-
.../fit/core/reference/ITImplementationLookup.java | 1 +
.../LinkedAccountSamplePullCorrelationRule.java | 78 +++
...LinkedAccountSamplePullCorrelationRuleConf.java | 25 +-
.../org/apache/syncope/fit/AbstractITCase.java | 3 +
.../org/apache/syncope/fit/core/BatchITCase.java | 3 -
.../apache/syncope/fit/core/DynRealmITCase.java | 3 -
.../syncope/fit/core/LinkedAccountITCase.java | 235 ++++++++-
.../org/apache/syncope/fit/core/OpenAPIITCase.java | 3 +-
.../apache/syncope/fit/core/PullTaskITCase.java | 36 +-
.../test/resources/DoubleValueLogicActions.groovy | 62 ++-
.../src/test/resources/rest/SearchScript.groovy | 5 +-
.../src/test/resources/rest/SyncScript.groovy | 40 +-
51 files changed, 1944 insertions(+), 792 deletions(-)
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy
index df22aa2..abb2e3e 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy
@@ -17,22 +17,69 @@
* under the License.
*/
import groovy.transform.CompileStatic
+import java.util.List
+import java.util.function.Function
import org.apache.syncope.common.lib.patch.AnyPatch
import org.apache.syncope.common.lib.patch.AttrPatch
import org.apache.syncope.common.lib.to.AnyTO
import org.apache.syncope.common.lib.to.AttrTO
+import org.apache.syncope.common.lib.to.PropagationStatus
import org.apache.syncope.core.provisioning.api.LogicActions
@CompileStatic
class MyLogicActions implements LogicActions {
@Override
- <A extends AnyTO> A beforeCreate(final A input) {
- return input;
+ <A extends AnyTO> Function<A, A> beforeCreate() {
+ Function function = {
+ A input ->
+ return input;
+ }
+ return function;
}
@Override
- <M extends AnyPatch> M beforeUpdate(final M input) {
- return input;
+ <A extends AnyTO> Function<A, A> afterCreate(List<PropagationStatus> statuses) {
+ Function function = {
+ A input ->
+ return input;
+ }
+ return function;
+ }
+
+ @Override
+ <P extends AnyPatch> Function<P, P> beforeUpdate() {
+ Function function = {
+ P input ->
+ return input;
+ }
+ return function;
+ }
+
+ @Override
+ <A extends AnyTO> Function<A, A> afterUpdate(List<PropagationStatus> statuses) {
+ Function function = {
+ A input ->
+ return input;
+ }
+ return function;
+ }
+
+ @Override
+ <A extends AnyTO> Function<A, A> beforeDelete() {
+ Function function = {
+ A input ->
+ return input;
+ }
+ return function;
+ }
+
+ @Override
+ <A extends AnyTO> Function<A, A> afterDelete(List<PropagationStatus> statuses) {
+ Function function = {
+ A input ->
+ return input;
+ }
+ return function;
}
}
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy
index 0f995f7..96ba05b 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy
@@ -27,13 +27,18 @@ import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport
import org.apache.syncope.core.provisioning.api.pushpull.PullActions
import org.identityconnectors.framework.common.objects.SyncDelta
import org.quartz.JobExecutionException
+import java.util.function.Function
@CompileStatic
class MyPullActions implements PullActions {
@Override
- SyncDelta preprocess(ProvisioningProfile profile, SyncDelta delta) {
- return delta;
+ Function<SyncDelta, SyncDelta> preprocess(ProvisioningProfile<?, ?> profile) {
+ Function function = {
+ SyncDelta delta ->
+ return delta;
+ }
+ return function;
}
@Override
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 8aeefc0..ceebdc4 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.getconnObjectKeyValue()),
+ 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 967558a..f2f0387 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
@@ -20,7 +20,6 @@ package org.apache.syncope.common.lib.to;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import java.io.Serializable;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@@ -33,7 +32,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
@XmlRootElement(name = "linkedAccount")
@XmlType
-public class LinkedAccountTO implements Serializable {
+public class LinkedAccountTO implements EntityTO {
private static final long serialVersionUID = 7396929732310559535L;
@@ -42,8 +41,13 @@ public class LinkedAccountTO implements Serializable {
private final LinkedAccountTO instance = new LinkedAccountTO();
public Builder(final String resource, final String connObjectKeyValue) {
+ this(null, resource, connObjectKeyValue);
+ }
+
+ public Builder(final String key, final String resource, final String connObjectKeyValue) {
+ instance.setKey(key);
instance.setResource(resource);
- instance.setconnObjectKeyValue(connObjectKeyValue);
+ instance.setConnObjectKeyValue(connObjectKeyValue);
}
public Builder username(final String username) {
@@ -66,6 +70,8 @@ public class LinkedAccountTO implements Serializable {
}
}
+ private String key;
+
private String connObjectKeyValue;
private String resource;
@@ -80,11 +86,20 @@ public class LinkedAccountTO implements Serializable {
private final Set<String> privileges = new HashSet<>();
- public String getconnObjectKeyValue() {
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ public String getConnObjectKeyValue() {
return connObjectKeyValue;
}
- public void setconnObjectKeyValue(final String connObjectKeyValue) {
+ public void setConnObjectKeyValue(final String connObjectKeyValue) {
this.connObjectKeyValue = connObjectKeyValue;
}
@@ -142,6 +157,7 @@ public class LinkedAccountTO implements Serializable {
@Override
public int hashCode() {
return new HashCodeBuilder().
+ append(key).
append(connObjectKeyValue).
append(resource).
append(username).
@@ -164,6 +180,7 @@ public class LinkedAccountTO implements Serializable {
}
final LinkedAccountTO other = (LinkedAccountTO) obj;
return new EqualsBuilder().
+ append(key, other.key).
append(connObjectKeyValue, other.connObjectKeyValue).
append(resource, other.resource).
append(username, other.username).
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
index 450fe72..adfa767 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Function;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.patch.AnyPatch;
@@ -107,9 +108,10 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> ext
templateUtils.apply(any, realm.getTemplate(anyType));
List<LogicActions> actions = getActions(realm);
- for (LogicActions action : actions) {
- any = action.beforeCreate(any);
- }
+ any = (TO) actions.stream().
+ map(action -> action.beforeCreate()).
+ reduce(Function.identity(), Function::andThen).
+ apply(any);
LOG.debug("Input: {}\nOutput: {}\n", input, any);
@@ -124,16 +126,17 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> ext
throw sce;
}
- P mod = input;
+ P patch = input;
List<LogicActions> actions = getActions(realm);
- for (LogicActions action : actions) {
- mod = action.beforeUpdate(mod);
- }
+ patch = (P) actions.stream().
+ map(action -> action.beforeUpdate()).
+ reduce(Function.identity(), Function::andThen).
+ apply(patch);
- LOG.debug("Input: {}\nOutput: {}\n", input, mod);
+ LOG.debug("Input: {}\nOutput: {}\n", input, patch);
- return Pair.of(mod, actions);
+ return Pair.of(patch, actions);
}
protected Pair<TO, List<LogicActions>> beforeDelete(final TO input) {
@@ -147,9 +150,10 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> ext
TO any = input;
List<LogicActions> actions = getActions(realm);
- for (LogicActions action : actions) {
- any = action.beforeDelete(any);
- }
+ any = (TO) actions.stream().
+ map(action -> action.beforeDelete()).
+ reduce(Function.identity(), Function::andThen).
+ apply(any);
LOG.debug("Input: {}\nOutput: {}\n", input, any);
@@ -161,9 +165,10 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> ext
TO any = input;
- for (LogicActions action : actions) {
- any = action.afterCreate(any, statuses);
- }
+ any = (TO) actions.stream().
+ map(action -> action.afterCreate(statuses)).
+ reduce(Function.identity(), Function::andThen).
+ apply(any);
ProvisioningResult<TO> result = new ProvisioningResult<>();
result.setEntity(any);
@@ -192,9 +197,10 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> ext
TO any = input;
- for (LogicActions action : actions) {
- any = action.afterUpdate(any, statuses);
- }
+ any = (TO) actions.stream().
+ map(action -> action.afterUpdate(statuses)).
+ reduce(Function.identity(), Function::andThen).
+ apply(any);
ProvisioningResult<TO> result = new ProvisioningResult<>();
result.setEntity(any);
@@ -208,9 +214,10 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> ext
TO any = input;
- for (LogicActions action : actions) {
- any = action.afterDelete(any, statuses);
- }
+ any = (TO) actions.stream().
+ map(action -> action.afterDelete(statuses)).
+ reduce(Function.identity(), Function::andThen).
+ apply(any);
ProvisioningResult<TO> result = new ProvisioningResult<>();
result.setEntity(any);
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/EntitlementLoader.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/EntitlementLoader.java
index 6ca7df1..5fa17a5 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/EntitlementLoader.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/EntitlementLoader.java
@@ -18,8 +18,6 @@
*/
package org.apache.syncope.core.logic.init;
-import java.util.Map;
-import javax.sql.DataSource;
import org.apache.syncope.common.lib.types.StandardEntitlement;
import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
import org.apache.syncope.core.spring.security.AuthContextUtils;
@@ -46,11 +44,11 @@ public class EntitlementLoader implements SyncopeLoader {
public void load() {
EntitlementsHolder.getInstance().init(StandardEntitlement.values());
- for (Map.Entry<String, DataSource> entry : domainsHolder.getDomains().entrySet()) {
- AuthContextUtils.execWithAuthContext(entry.getKey(), () -> {
+ domainsHolder.getDomains().forEach((domain, datasource) -> {
+ AuthContextUtils.execWithAuthContext(domain, () -> {
entitlementAccessor.addEntitlementsForAnyTypes();
return null;
});
- }
+ });
}
}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PullCorrelationRule.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PullCorrelationRule.java
index 24c13e3..a6fe5e7 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PullCorrelationRule.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PullCorrelationRule.java
@@ -18,8 +18,10 @@
*/
package org.apache.syncope.core.persistence.api.dao;
+import java.util.Optional;
import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.identityconnectors.framework.common.objects.SyncDelta;
@@ -28,6 +30,8 @@ import org.identityconnectors.framework.common.objects.SyncDelta;
*/
public interface PullCorrelationRule {
+ PullMatch NO_MATCH = new PullMatch.Builder().build();
+
default void setConf(PullCorrelationRuleConf conf) {
}
@@ -39,4 +43,33 @@ public interface PullCorrelationRule {
* @return search condition.
*/
SearchCond getSearchCond(SyncDelta syncDelta, Provision provision);
+
+ /**
+ * Create matching information for the given Any, found matching for the given
+ * {@link SyncDelta} and {@link Provision}.
+ * For users, this might end with creating / updating / deleting a
+ * {@link org.apache.syncope.core.persistence.api.entity.user.LinkedAccount}.
+ *
+ * @param any any
+ * @param syncDelta change operation, including external attributes
+ * @param provision resource provision
+ * @return matching information
+ */
+ default PullMatch matching(Any<?> any, SyncDelta syncDelta, Provision provision) {
+ return new PullMatch.Builder().matchingKey(any.getKey()).build();
+ }
+
+ /**
+ * Optionally create matching information in case no matching Any was found for the given
+ * {@link SyncDelta} and {@link Provision}.
+ * For users, this might end with creating a
+ * {@link org.apache.syncope.core.persistence.api.entity.user.LinkedAccount}.
+ *
+ * @param syncDelta change operation, including external attributes
+ * @param provision resource provision
+ * @return matching information
+ */
+ default Optional<PullMatch> unmatching(SyncDelta syncDelta, Provision provision) {
+ return Optional.of(NO_MATCH);
+ }
}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PullMatch.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PullMatch.java
new file mode 100644
index 0000000..f66906f
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PullMatch.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.dao;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+public final class PullMatch implements Serializable {
+
+ private static final long serialVersionUID = 6515473131174179932L;
+
+ public enum MatchTarget {
+ ANY,
+ LINKED_ACCOUNT;
+
+ }
+
+ public static class Builder {
+
+ private final PullMatch instance = new PullMatch();
+
+ public Builder matchingKey(final String matchingKey) {
+ instance.matchingKey = matchingKey;
+ return this;
+ }
+
+ public Builder matchTarget(final MatchTarget matchTarget) {
+ instance.matchTarget = matchTarget;
+ return this;
+ }
+
+ public Builder linkingUserKey(final String linkingUserKey) {
+ instance.linkingUserKey = linkingUserKey;
+ return this;
+ }
+
+ public PullMatch build() {
+ return instance;
+ }
+ }
+
+ private MatchTarget matchTarget = MatchTarget.ANY;
+
+ private String matchingKey;
+
+ private String linkingUserKey;
+
+ private PullMatch() {
+ // private constructor
+ }
+
+ public MatchTarget getMatchTarget() {
+ return matchTarget;
+ }
+
+ public String getMatchingKey() {
+ return matchingKey;
+ }
+
+ public String getLinkingUserKey() {
+ return linkingUserKey;
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().
+ append(matchTarget).
+ append(matchingKey).
+ append(linkingUserKey).
+ build();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PullMatch other = (PullMatch) obj;
+ return new EqualsBuilder().
+ append(matchingKey, other.matchingKey).
+ append(matchTarget, other.matchTarget).
+ append(linkingUserKey, other.linkingUserKey).
+ build();
+ }
+
+ @Override
+ public String toString() {
+ return "PullMatch{"
+ + "matchTarget=" + matchTarget
+ + ", matchingKey=" + matchingKey
+ + ", linkingUserKey=" + linkingUserKey + '}';
+ }
+}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/LogicActions.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/LogicActions.java
index c7f6bc7..1f23a51 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/LogicActions.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/LogicActions.java
@@ -19,6 +19,7 @@
package org.apache.syncope.core.provisioning.api;
import java.util.List;
+import java.util.function.Function;
import org.apache.syncope.common.lib.patch.AnyPatch;
import org.apache.syncope.common.lib.to.AnyTO;
import org.apache.syncope.common.lib.to.PropagationStatus;
@@ -28,27 +29,27 @@ import org.apache.syncope.common.lib.to.PropagationStatus;
*/
public interface LogicActions {
- default <A extends AnyTO> A beforeCreate(A input) {
- return input;
+ default <A extends AnyTO> Function<A, A> beforeCreate() {
+ return Function.identity();
}
- default <A extends AnyTO> A afterCreate(A input, List<PropagationStatus> statuses) {
- return input;
+ default <A extends AnyTO> Function<A, A> afterCreate(List<PropagationStatus> statuses) {
+ return Function.identity();
}
- default <P extends AnyPatch> P beforeUpdate(P input) {
- return input;
+ default <P extends AnyPatch> Function<P, P> beforeUpdate() {
+ return Function.identity();
}
- default <A extends AnyTO> A afterUpdate(A input, List<PropagationStatus> statuses) {
- return input;
+ default <A extends AnyTO> Function<A, A> afterUpdate(List<PropagationStatus> statuses) {
+ return Function.identity();
}
- default <A extends AnyTO> A beforeDelete(A input) {
- return input;
+ default <A extends AnyTO> Function<A, A> beforeDelete() {
+ return Function.identity();
}
- default <A extends AnyTO> A afterDelete(A input, List<PropagationStatus> statuses) {
- return input;
+ default <A extends AnyTO> Function<A, A> afterDelete(List<PropagationStatus> statuses) {
+ return Function.identity();
}
}
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 5f82ef3..43e71aa 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
@@ -193,28 +193,28 @@ public class PropagationByResource<T extends Serializable> implements Serializab
* Removes only the resource names in the underlying resource name sets that are contained in the specified
* collection.
*
- * @param resourceKeys collection containing resource names to be retained in the underlying resource name sets
+ * @param keys collection containing resource names to be retained in the underlying resource name sets
* @return <tt>true</tt> if the underlying resource name sets changed as a result of the call
* @see Collection#removeAll(java.util.Collection)
*/
- public boolean removeAll(final Collection<String> resourceKeys) {
- return toBeCreated.removeAll(resourceKeys)
- | toBeUpdated.removeAll(resourceKeys)
- | toBeDeleted.removeAll(resourceKeys);
+ public boolean removeAll(final Collection<T> keys) {
+ return toBeCreated.removeAll(keys)
+ | toBeUpdated.removeAll(keys)
+ | toBeDeleted.removeAll(keys);
}
/**
* Retains only the resource names in the underlying resource name sets that are contained in the specified
* collection.
*
- * @param resourceKeys collection containing resource names to be retained in the underlying resource name sets
+ * @param keys collection containing resource names to be retained in the underlying resource name sets
* @return <tt>true</tt> if the underlying resource name sets changed as a result of the call
* @see Collection#retainAll(java.util.Collection)
*/
- public boolean retainAll(final Collection<String> resourceKeys) {
- return toBeCreated.retainAll(resourceKeys)
- | toBeUpdated.retainAll(resourceKeys)
- | toBeDeleted.retainAll(resourceKeys);
+ public boolean retainAll(final Collection<T> keys) {
+ return toBeCreated.retainAll(keys)
+ | toBeUpdated.retainAll(keys)
+ | toBeDeleted.retainAll(keys);
}
public boolean contains(final ResourceOperation type, final T key) {
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/cache/VirAttrCacheValue.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/cache/VirAttrCacheValue.java
index 83a729b..f68f9aa 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/cache/VirAttrCacheValue.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/cache/VirAttrCacheValue.java
@@ -22,7 +22,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
-import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
/**
* Cache entry value.
@@ -32,7 +33,7 @@ public class VirAttrCacheValue {
/**
* Virtual attribute values.
*/
- private final List<String> values;
+ private final List<String> values = new ArrayList<>();
/**
* Entry creation date.
@@ -44,59 +45,39 @@ public class VirAttrCacheValue {
*/
private Date lastAccessDate;
- public VirAttrCacheValue() {
- this.creationDate = new Date();
- this.lastAccessDate = new Date();
- this.values = new ArrayList<>();
- }
-
- public void setValues(final Collection<Object> values) {
- this.values.clear();
+ public VirAttrCacheValue(final Collection<Object> values) {
+ creationDate = new Date();
+ lastAccessDate = new Date();
if (values != null) {
- values.forEach(value -> {
- this.values.add(value.toString());
- });
+ values.forEach(value -> this.values.add(value.toString()));
}
}
+ public List<String> getValues() {
+ lastAccessDate = new Date();
+ return values;
+ }
+
public Date getCreationDate() {
- if (creationDate != null) {
- return new Date(creationDate.getTime());
- }
- return null;
+ return new Date(creationDate.getTime());
}
public void forceExpiring() {
creationDate = new Date(0);
}
- public List<String> getValues() {
- return values;
- }
-
public Date getLastAccessDate() {
- if (lastAccessDate != null) {
- return new Date(lastAccessDate.getTime());
- }
- return null;
- }
-
- public void setLastAccessDate(final Date lastAccessDate) {
- if (lastAccessDate != null) {
- this.lastAccessDate = new Date(lastAccessDate.getTime());
- } else {
- this.lastAccessDate = null;
- }
+ return new Date(lastAccessDate.getTime());
}
@Override
public int hashCode() {
- int hash = 5;
- hash = 67 * hash + Objects.hashCode(this.values);
- hash = 67 * hash + Objects.hashCode(this.creationDate);
- hash = 67 * hash + Objects.hashCode(this.lastAccessDate);
- return hash;
+ return new HashCodeBuilder().
+ append(values).
+ append(creationDate).
+ append(lastAccessDate).
+ build();
}
@Override
@@ -111,13 +92,11 @@ public class VirAttrCacheValue {
return false;
}
final VirAttrCacheValue other = (VirAttrCacheValue) obj;
- if (!Objects.equals(this.values, other.values)) {
- return false;
- }
- if (!Objects.equals(this.creationDate, other.creationDate)) {
- return false;
- }
- return Objects.equals(this.lastAccessDate, other.lastAccessDate);
+ return new EqualsBuilder().
+ append(values, other.values).
+ append(creationDate, other.creationDate).
+ append(lastAccessDate, other.lastAccessDate).
+ build();
}
@Override
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java
index 75bf6a4..61bf2ff 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java
@@ -20,7 +20,9 @@ package org.apache.syncope.core.provisioning.api.data;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.LinkedAccountTO;
import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -34,6 +36,8 @@ public interface UserDataBinder {
UserTO getUserTO(User user, boolean details);
+ LinkedAccountTO getLinkedAccountTO(LinkedAccount account);
+
void create(User user, UserTO userTO, boolean storePassword);
/**
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java
index 4f47eda..3c0a6f0 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java
@@ -104,19 +104,6 @@ public class ProvisioningReport {
this.uidValue = uidValue;
}
- @Override
- public String toString() {
- return new ToStringBuilder(this).
- append(message).
- append(status).
- append(anyType).
- append(operation).
- append(key).
- append(name).
- append(uidValue).
- build();
- }
-
/**
* Human readable report string, using the given trace level.
*
@@ -148,9 +135,22 @@ public class ProvisioningReport {
*/
public static String generate(final Collection<ProvisioningReport> results, final TraceLevel level) {
StringBuilder sb = new StringBuilder();
- for (ProvisioningReport result : results) {
+ results.forEach(result -> {
sb.append(result.getReportString(level)).append('\n');
- }
+ });
return sb.toString();
}
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).
+ append(message).
+ append(status).
+ append(anyType).
+ append(operation).
+ append(key).
+ append(name).
+ append(uidValue).
+ build();
+ }
}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java
index d367a7e..2d4d2d5 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.core.provisioning.api.pushpull;
+import java.util.function.Function;
import org.apache.syncope.common.lib.patch.AnyPatch;
import org.apache.syncope.common.lib.to.EntityTO;
import org.identityconnectors.framework.common.objects.SyncDelta;
@@ -34,11 +35,10 @@ public interface PullActions extends ProvisioningActions {
* Pre-process the pull information received by the underlying connector, before any internal activity occurs.
*
* @param profile profile of the pull being executed.
- * @param delta retrieved pull information
- * @return pull information, possibly altered.
+ * @return pull information, possibly altered
*/
- default SyncDelta preprocess(ProvisioningProfile<?, ?> profile, SyncDelta delta) {
- return delta;
+ default Function<SyncDelta, SyncDelta> preprocess(ProvisioningProfile<?, ?> profile) {
+ return Function.identity();
}
/**
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
index 6bc8db3..e3b71d2 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
@@ -126,10 +126,11 @@ public class VirAttrHandlerImpl implements VirAttrHandler {
schemasToRead.forEach(schema -> {
Attribute attr = connectorObject.getAttributeByName(schema.getExtAttrName());
if (attr != null) {
- VirAttrCacheValue virAttrCacheValue = new VirAttrCacheValue();
- virAttrCacheValue.setValues(attr.getValue());
+ VirAttrCacheValue virAttrCacheValue = new VirAttrCacheValue(attr.getValue());
virAttrCache.put(
- any.getType().getKey(), any.getKey(), schema.getKey(),
+ any.getType().getKey(),
+ any.getKey(),
+ schema.getKey(),
virAttrCacheValue);
LOG.debug("Values for {} set in cache: {}", schema, virAttrCacheValue);
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 bc02905..2e22f9d 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
@@ -164,7 +164,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
LOG.debug("Ignoring invalid resource {}", accountTO.getResource());
} else {
Optional<? extends LinkedAccount> found =
- user.getLinkedAccount(resource.getKey(), accountTO.getconnObjectKeyValue());
+ user.getLinkedAccount(resource.getKey(), accountTO.getConnObjectKeyValue());
LinkedAccount account = found.isPresent()
? found.get()
: new Supplier<LinkedAccount>() {
@@ -175,7 +175,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
acct.setOwner(user);
user.add(acct);
- acct.setConnObjectKeyValue(accountTO.getconnObjectKeyValue());
+ acct.setConnObjectKeyValue(accountTO.getConnObjectKeyValue());
acct.setResource(resource);
return acct;
@@ -592,7 +592,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
userPatch.getLinkedAccounts().stream().filter(patch -> patch.getLinkedAccountTO() != null).forEach(patch -> {
user.getLinkedAccount(
patch.getLinkedAccountTO().getResource(),
- patch.getLinkedAccountTO().getconnObjectKeyValue()).ifPresent(account -> {
+ patch.getLinkedAccountTO().getConnObjectKeyValue()).ifPresent(account -> {
if (patch.getOperation() == PatchOperation.DELETE) {
user.getLinkedAccounts().remove(account);
@@ -694,6 +694,28 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
@Transactional(readOnly = true)
@Override
+ public LinkedAccountTO getLinkedAccountTO(final LinkedAccount account) {
+ LinkedAccountTO accountTO = new LinkedAccountTO.Builder(
+ account.getKey(), account.getResource().getKey(), account.getConnObjectKeyValue()).
+ username(account.getUsername()).
+ password(account.getPassword()).
+ suspended(BooleanUtils.isTrue(account.isSuspended())).
+ build();
+
+ account.getPlainAttrs().forEach(plainAttr -> {
+ accountTO.getPlainAttrs().add(new AttrTO.Builder().
+ schema(plainAttr.getSchema().getKey()).
+ values(plainAttr.getValuesAsStrings()).build());
+ });
+
+ accountTO.getPrivileges().addAll(account.getPrivileges().stream().
+ map(Entity::getKey).collect(Collectors.toList()));
+
+ return accountTO;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
public UserTO getUserTO(final User user, final boolean details) {
UserTO userTO = new UserTO();
userTO.setKey(user.getKey());
@@ -764,25 +786,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
// linked accounts
userTO.getLinkedAccounts().addAll(
- user.getLinkedAccounts().stream().map(account -> {
- LinkedAccountTO accountTO = new LinkedAccountTO.Builder(
- account.getResource().getKey(), account.getConnObjectKeyValue()).
- username(account.getUsername()).
- password(user.getPassword()).
- suspended(BooleanUtils.isTrue(account.isSuspended())).
- build();
-
- account.getPlainAttrs().forEach(plainAttr -> {
- accountTO.getPlainAttrs().add(new AttrTO.Builder().
- schema(plainAttr.getSchema().getKey()).
- values(plainAttr.getValuesAsStrings()).build());
- });
-
- accountTO.getPrivileges().addAll(account.getPrivileges().stream().
- map(Entity::getKey).collect(Collectors.toList()));
-
- return accountTO;
- }).collect(Collectors.toList()));
+ user.getLinkedAccounts().stream().map(this::getLinkedAccountTO).collect(Collectors.toList()));
}
return userTO;
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 172f35f..72ef734 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
@@ -662,9 +662,11 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
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);
+ virAttrCache.put(
+ task.getAnyType(),
+ task.getEntityKey(),
+ item.getIntAttrName(),
+ new VirAttrCacheValue(attr.getValue()));
}
}
} catch (TimeoutException toe) {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
index bfdab7b..b21dd21 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
@@ -114,15 +114,19 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
prioritizedTasks.forEach(task -> {
TaskExec execution = null;
ExecStatus execStatus;
+ String errorMessage = null;
try {
execution = newPropagationTaskCallable(task, reporter).call();
execStatus = ExecStatus.valueOf(execution.getStatus());
} catch (Exception e) {
LOG.error("Unexpected exception", e);
execStatus = ExecStatus.FAILURE;
+ errorMessage = e.getMessage();
}
if (execStatus != ExecStatus.SUCCESS) {
- throw new PropagationException(task.getResource(), execution == null ? null : execution.getMessage());
+ throw new PropagationException(
+ task.getResource(),
+ execution == null ? errorMessage : execution.getMessage());
}
});
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 55fb711..4502529 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
@@ -190,7 +190,14 @@ public class PropagationManagerImpl implements PropagationManager {
}
if (noPropResourceKeys != null) {
- propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceKeys);
+ if (propByRes != null) {
+ propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceKeys);
+ }
+
+ if (propByLinkedAccount != null) {
+ propByLinkedAccount.get(ResourceOperation.CREATE).
+ removeIf(account -> noPropResourceKeys.contains(account.getLeft()));
+ }
}
return createTasks(any, password, true, enable, false, propByRes, propByLinkedAccount, vAttrs);
@@ -300,8 +307,19 @@ public class PropagationManagerImpl implements PropagationManager {
final Collection<AttrTO> vAttrs,
final Collection<String> noPropResourceKeys) {
- if (noPropResourceKeys != null && propByRes != null) {
- propByRes.removeAll(noPropResourceKeys);
+ if (noPropResourceKeys != null) {
+ if (propByRes != null) {
+ propByRes.removeAll(noPropResourceKeys);
+ }
+
+ if (propByLinkedAccount != null) {
+ propByLinkedAccount.get(ResourceOperation.CREATE).
+ removeIf(account -> noPropResourceKeys.contains(account.getLeft()));
+ propByLinkedAccount.get(ResourceOperation.UPDATE).
+ removeIf(account -> noPropResourceKeys.contains(account.getLeft()));
+ propByLinkedAccount.get(ResourceOperation.DELETE).
+ removeIf(account -> noPropResourceKeys.contains(account.getLeft()));
+ }
}
return createTasks(
@@ -354,6 +372,15 @@ public class PropagationManagerImpl implements PropagationManager {
if (noPropResourceKeys != null) {
localPropByRes.removeAll(noPropResourceKeys);
+
+ if (propByLinkedAccount != null) {
+ propByLinkedAccount.get(ResourceOperation.CREATE).
+ removeIf(account -> noPropResourceKeys.contains(account.getLeft()));
+ propByLinkedAccount.get(ResourceOperation.UPDATE).
+ removeIf(account -> noPropResourceKeys.contains(account.getLeft()));
+ propByLinkedAccount.get(ResourceOperation.DELETE).
+ removeIf(account -> noPropResourceKeys.contains(account.getLeft()));
+ }
}
return createTasks(any, null, false, false, true, localPropByRes, propByLinkedAccount, null);
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 997e7f4..7af3282 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
@@ -22,13 +22,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
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;
@@ -46,7 +46,6 @@ import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.Remediation;
-import org.apache.syncope.core.persistence.api.entity.VirSchema;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.persistence.api.entity.task.PullTask;
import org.apache.syncope.core.provisioning.api.AuditManager;
@@ -57,6 +56,7 @@ import org.apache.syncope.core.provisioning.api.notification.NotificationManager
import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
+import org.apache.syncope.core.persistence.api.dao.PullMatch;
import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullExecutor;
import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
@@ -178,18 +178,22 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
}
}
- protected List<ProvisioningReport> assign(
- final SyncDelta delta, final Provision provision, final AnyUtils anyUtils)
- throws JobExecutionException {
+ protected List<ProvisioningReport> provision(
+ final UnmatchingRule rule,
+ final SyncDelta delta,
+ final Provision provision,
+ final AnyUtils anyUtils) throws JobExecutionException {
if (!profile.getTask().isPerformCreate()) {
LOG.debug("PullTask not configured for create");
- finalize(UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), Result.SUCCESS, null, null, delta);
+ finalize(UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
return Collections.<ProvisioningReport>emptyList();
}
- AnyTO anyTO = connObjectUtils.getAnyTO(delta.getObject(), profile.getTask(), provision, anyUtils);
- anyTO.getResources().add(profile.getTask().getResource().getKey());
+ AnyTO anyTO = connObjectUtils.getAnyTO(delta.getObject(), profile.getTask(), provision, anyUtils, true);
+ if (rule == UnmatchingRule.ASSIGN) {
+ anyTO.getResources().add(profile.getTask().getResource().getKey());
+ }
ProvisioningReport result = new ProvisioningReport();
result.setOperation(ResourceOperation.CREATE);
@@ -200,46 +204,61 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
if (profile.isDryRun()) {
result.setKey(null);
- finalize(UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), Result.SUCCESS, null, null, delta);
+ finalize(UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
} else {
for (PullActions action : profile.getActions()) {
- action.beforeAssign(profile, delta, anyTO);
+ if (rule == UnmatchingRule.ASSIGN) {
+ action.beforeAssign(profile, delta, anyTO);
+ } else if (rule == UnmatchingRule.PROVISION) {
+ action.beforeProvision(profile, delta, anyTO);
+ }
}
- create(anyTO, delta, UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), provision, result);
- }
-
- return Collections.singletonList(result);
- }
-
- protected List<ProvisioningReport> provision(
- final SyncDelta delta, final Provision provision, final AnyUtils anyUtils)
- throws JobExecutionException {
-
- if (!profile.getTask().isPerformCreate()) {
- LOG.debug("PullTask not configured for create");
- finalize(UnmatchingRule.toEventName(UnmatchingRule.PROVISION), Result.SUCCESS, null, null, delta);
- return Collections.<ProvisioningReport>emptyList();
- }
+ Object output;
+ Result resultStatus;
- AnyTO anyTO = connObjectUtils.getAnyTO(delta.getObject(), profile.getTask(), provision, anyUtils);
+ try {
+ AnyTO created = doCreate(anyTO, delta);
+ output = created;
+ result.setKey(created.getKey());
+ result.setName(getName(created));
+ resultStatus = Result.SUCCESS;
+
+ for (PullActions action : profile.getActions()) {
+ action.after(profile, delta, created, result);
+ }
- ProvisioningReport result = new ProvisioningReport();
- result.setOperation(ResourceOperation.CREATE);
- result.setAnyType(provision.getAnyType().getKey());
- result.setStatus(ProvisioningReport.Status.SUCCESS);
- result.setName(getName(anyTO));
- result.setUidValue(delta.getUid().getUidValue());
+ LOG.debug("{} {} successfully created", created.getType(), created.getKey());
+ } catch (PropagationException e) {
+ // A propagation failure doesn't imply a pull failure.
+ // The propagation exception status will be reported into the propagation task execution.
+ LOG.error("Could not propagate {} {}", anyTO.getType(), delta.getUid().getUidValue(), e);
+ output = e;
+ resultStatus = Result.FAILURE;
+ } catch (Exception e) {
+ throwIgnoreProvisionException(delta, e);
- if (profile.isDryRun()) {
- result.setKey(null);
- finalize(UnmatchingRule.toEventName(UnmatchingRule.PROVISION), Result.SUCCESS, null, null, delta);
- } else {
- for (PullActions action : profile.getActions()) {
- action.beforeProvision(profile, delta, anyTO);
+ result.setStatus(ProvisioningReport.Status.FAILURE);
+ result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+ LOG.error("Could not create {} {} ", anyTO.getType(), delta.getUid().getUidValue(), e);
+ output = e;
+ resultStatus = Result.FAILURE;
+
+ if (profile.getTask().isRemediation()) {
+ Remediation entity = entityFactory.newEntity(Remediation.class);
+ entity.setAnyType(provision.getAnyType());
+ entity.setOperation(ResourceOperation.CREATE);
+ entity.setPayload(anyTO);
+ entity.setError(result.getMessage());
+ entity.setInstant(new Date());
+ entity.setRemoteName(delta.getObject().getName().getNameValue());
+ entity.setPullTask(profile.getTask());
+
+ remediationDAO.save(entity);
+ }
}
- create(anyTO, delta, UnmatchingRule.toEventName(UnmatchingRule.PROVISION), provision, result);
+ finalize(UnmatchingRule.toEventName(rule), resultStatus, null, output, delta);
}
return Collections.singletonList(result);
@@ -263,63 +282,10 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
}
}
- protected void create(
- final AnyTO anyTO,
- final SyncDelta delta,
- final String operation,
- final Provision provision,
- final ProvisioningReport result)
- throws JobExecutionException {
-
- Object output;
- Result resultStatus;
-
- try {
- AnyTO created = doCreate(anyTO, delta);
- output = created;
- result.setKey(created.getKey());
- result.setName(getName(created));
- resultStatus = Result.SUCCESS;
-
- for (PullActions action : profile.getActions()) {
- action.after(profile, delta, created, result);
- }
-
- LOG.debug("{} {} successfully created", created.getType(), created.getKey());
- } catch (PropagationException e) {
- // A propagation failure doesn't imply a pull failure.
- // The propagation exception status will be reported into the propagation task execution.
- LOG.error("Could not propagate {} {}", anyTO.getType(), delta.getUid().getUidValue(), e);
- output = e;
- resultStatus = Result.FAILURE;
- } catch (Exception e) {
- throwIgnoreProvisionException(delta, e);
-
- result.setStatus(ProvisioningReport.Status.FAILURE);
- result.setMessage(ExceptionUtils.getRootCauseMessage(e));
- LOG.error("Could not create {} {} ", anyTO.getType(), delta.getUid().getUidValue(), e);
- output = e;
- resultStatus = Result.FAILURE;
-
- if (profile.getTask().isRemediation()) {
- Remediation entity = entityFactory.newEntity(Remediation.class);
- entity.setAnyType(provision.getAnyType());
- entity.setOperation(ResourceOperation.CREATE);
- entity.setPayload(anyTO);
- entity.setError(result.getMessage());
- entity.setInstant(new Date());
- entity.setRemoteName(delta.getObject().getName().getNameValue());
- entity.setPullTask(profile.getTask());
-
- remediationDAO.save(entity);
- }
- }
-
- finalize(operation, resultStatus, null, output, delta);
- }
-
protected List<ProvisioningReport> update(
- final SyncDelta delta, final List<String> anyKeys, final Provision provision) throws JobExecutionException {
+ final SyncDelta delta,
+ final List<PullMatch> matches,
+ final Provision provision) throws JobExecutionException {
if (!profile.getTask().isPerformUpdate()) {
LOG.debug("PullTask not configured for update");
@@ -327,23 +293,23 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
return Collections.<ProvisioningReport>emptyList();
}
- LOG.debug("About to update {}", anyKeys);
+ LOG.debug("About to update {}", matches);
List<ProvisioningReport> results = new ArrayList<>();
- for (String key : anyKeys) {
- LOG.debug("About to update {}", key);
+ for (PullMatch match : matches) {
+ LOG.debug("About to update {}", match);
ProvisioningReport result = new ProvisioningReport();
result.setOperation(ResourceOperation.UPDATE);
result.setAnyType(provision.getAnyType().getKey());
result.setStatus(ProvisioningReport.Status.SUCCESS);
- result.setKey(key);
+ result.setKey(match.getMatchingKey());
- AnyTO before = getAnyTO(key);
+ AnyTO before = getAnyTO(match.getMatchingKey());
if (before == null) {
result.setStatus(ProvisioningReport.Status.FAILURE);
- result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType().getKey(), key));
+ result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType().getKey(), match));
} else {
result.setName(getName(before));
}
@@ -382,7 +348,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
resultStatus = Result.SUCCESS;
result.setName(getName(updated));
- LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), key);
+ LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), match);
} catch (PropagationException e) {
// A propagation failure doesn't imply a pull failure.
// The propagation exception status will be reported into the propagation task execution.
@@ -423,38 +389,36 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
}
protected List<ProvisioningReport> deprovision(
+ final MatchingRule matchingRule,
final SyncDelta delta,
- final List<String> anyKeys,
- final Provision provision,
- final boolean unlink)
+ final List<PullMatch> matches,
+ final Provision provision)
throws JobExecutionException {
if (!profile.getTask().isPerformUpdate()) {
LOG.debug("PullTask not configured for update");
- finalize(unlink
- ? MatchingRule.toEventName(MatchingRule.UNASSIGN)
- : MatchingRule.toEventName(MatchingRule.DEPROVISION), Result.SUCCESS, null, null, delta);
+ finalize(MatchingRule.toEventName(matchingRule), Result.SUCCESS, null, null, delta);
return Collections.<ProvisioningReport>emptyList();
}
- LOG.debug("About to deprovision {}", anyKeys);
+ LOG.debug("About to deprovision {}", matches);
- final List<ProvisioningReport> results = new ArrayList<>();
+ List<ProvisioningReport> results = new ArrayList<>();
- for (String key : anyKeys) {
- LOG.debug("About to unassign resource {}", key);
+ for (PullMatch match : matches) {
+ LOG.debug("About to unassign resource {}", match);
ProvisioningReport result = new ProvisioningReport();
result.setOperation(ResourceOperation.DELETE);
result.setAnyType(provision.getAnyType().getKey());
result.setStatus(ProvisioningReport.Status.SUCCESS);
- result.setKey(key);
+ result.setKey(match.getMatchingKey());
- AnyTO before = getAnyTO(key);
+ AnyTO before = getAnyTO(match.getMatchingKey());
if (before == null) {
result.setStatus(ProvisioningReport.Status.FAILURE);
- result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType().getKey(), key));
+ result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType().getKey(), match));
}
if (!profile.isDryRun()) {
@@ -468,43 +432,36 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
result.setName(getName(before));
try {
- if (unlink) {
+ if (matchingRule == MatchingRule.UNASSIGN) {
for (PullActions action : profile.getActions()) {
action.beforeUnassign(profile, delta, before);
}
- } else {
+ } else if (matchingRule == MatchingRule.DEPROVISION) {
for (PullActions action : profile.getActions()) {
action.beforeDeprovision(profile, delta, before);
}
}
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())));
- }
+ propByRes.add(ResourceOperation.DELETE, provision.getResource().getKey());
taskExecutor.execute(propagationManager.getDeleteTasks(
provision.getAnyType().getKind(),
- key,
+ match.getMatchingKey(),
propByRes,
- propByLinkedAccount,
+ null,
null),
false);
AnyPatch anyPatch = null;
- if (unlink) {
- anyPatch = getAnyUtils().newAnyPatch(key);
+ if (matchingRule == MatchingRule.UNASSIGN) {
+ anyPatch = getAnyUtils().newAnyPatch(match.getMatchingKey());
anyPatch.getResources().add(new StringPatchItem.Builder().
operation(PatchOperation.DELETE).
value(profile.getTask().getResource().getKey()).build());
}
if (anyPatch == null) {
- output = getAnyTO(key);
+ output = getAnyTO(match.getMatchingKey());
} else {
output = doUpdate(before, anyPatch, delta, result);
}
@@ -515,7 +472,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
resultStatus = Result.SUCCESS;
- LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), key);
+ LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), match);
} catch (PropagationException e) {
// A propagation failure doesn't imply a pull failure.
// The propagation exception status will be reported into the propagation task execution.
@@ -534,9 +491,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
resultStatus = Result.FAILURE;
}
}
- finalize(unlink
- ? MatchingRule.toEventName(MatchingRule.UNASSIGN)
- : MatchingRule.toEventName(MatchingRule.DEPROVISION), resultStatus, before, output, delta);
+ finalize(MatchingRule.toEventName(matchingRule), resultStatus, before, output, delta);
}
results.add(result);
}
@@ -546,7 +501,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
protected List<ProvisioningReport> link(
final SyncDelta delta,
- final List<String> anyKeys,
+ final List<PullMatch> matches,
final Provision provision,
final boolean unlink)
throws JobExecutionException {
@@ -559,24 +514,24 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
return Collections.<ProvisioningReport>emptyList();
}
- LOG.debug("About to update {}", anyKeys);
+ LOG.debug("About to update {}", matches);
final List<ProvisioningReport> results = new ArrayList<>();
- for (String key : anyKeys) {
- LOG.debug("About to unassign resource {}", key);
+ for (PullMatch match : matches) {
+ LOG.debug("About to unassign resource {}", match);
ProvisioningReport result = new ProvisioningReport();
result.setOperation(ResourceOperation.NONE);
result.setAnyType(provision.getAnyType().getKey());
result.setStatus(ProvisioningReport.Status.SUCCESS);
- result.setKey(key);
+ result.setKey(match.getMatchingKey());
- AnyTO before = getAnyTO(key);
+ AnyTO before = getAnyTO(match.getMatchingKey());
if (before == null) {
result.setStatus(ProvisioningReport.Status.FAILURE);
- result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType().getKey(), key));
+ result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType().getKey(), match));
}
if (!profile.isDryRun()) {
@@ -615,7 +570,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
resultStatus = Result.SUCCESS;
- LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), key);
+ LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), match);
} catch (PropagationException e) {
// A propagation failure doesn't imply a pull failure.
// The propagation exception status will be reported into the propagation task execution.
@@ -647,7 +602,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
protected List<ProvisioningReport> delete(
final SyncDelta delta,
- final List<String> anyKeys,
+ final List<PullMatch> matches,
final Provision provision)
throws JobExecutionException {
@@ -657,20 +612,20 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
return Collections.<ProvisioningReport>emptyList();
}
- LOG.debug("About to delete {}", anyKeys);
+ LOG.debug("About to delete {}", matches);
List<ProvisioningReport> results = new ArrayList<>();
- for (String key : anyKeys) {
+ matches.forEach(match -> {
Object output;
Result resultStatus = Result.FAILURE;
ProvisioningReport result = new ProvisioningReport();
try {
- AnyTO before = getAnyTO(key);
+ AnyTO before = getAnyTO(match.getMatchingKey());
- result.setKey(key);
+ result.setKey(match.getMatchingKey());
result.setName(getName(before));
result.setOperation(ResourceOperation.DELETE);
result.setAnyType(provision.getAnyType().getKey());
@@ -682,8 +637,10 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
}
try {
- getProvisioningManager().
- delete(key, Collections.singleton(profile.getTask().getResource().getKey()), true);
+ getProvisioningManager().delete(
+ match.getMatchingKey(),
+ Collections.singleton(profile.getTask().getResource().getKey()),
+ true);
output = null;
resultStatus = Result.SUCCESS;
@@ -695,14 +652,14 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
result.setStatus(ProvisioningReport.Status.FAILURE);
result.setMessage(ExceptionUtils.getRootCauseMessage(e));
- LOG.error("Could not delete {} {}", provision.getAnyType().getKey(), key, e);
+ LOG.error("Could not delete {} {}", provision.getAnyType().getKey(), match, e);
output = e;
if (profile.getTask().isRemediation()) {
Remediation entity = entityFactory.newEntity(Remediation.class);
entity.setAnyType(provision.getAnyType());
entity.setOperation(ResourceOperation.DELETE);
- entity.setPayload(key);
+ entity.setPayload(match.getMatchingKey());
entity.setError(result.getMessage());
entity.setInstant(new Date());
entity.setRemoteName(delta.getObject().getName().getNameValue());
@@ -717,20 +674,20 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
results.add(result);
} catch (NotFoundException e) {
- LOG.error("Could not find {} {}", provision.getAnyType().getKey(), key, e);
+ LOG.error("Could not find {} {}", provision.getAnyType().getKey(), match, e);
} catch (DelegatedAdministrationException e) {
- LOG.error("Not allowed to read {} {}", provision.getAnyType().getKey(), key, e);
+ LOG.error("Not allowed to read {} {}", provision.getAnyType().getKey(), match, e);
} catch (Exception e) {
- LOG.error("Could not delete {} {}", provision.getAnyType().getKey(), key, e);
+ LOG.error("Could not delete {} {}", provision.getAnyType().getKey(), match, e);
}
- }
+ });
return results;
}
protected List<ProvisioningReport> ignore(
final SyncDelta delta,
- final List<String> anyKeys,
+ final List<PullMatch> matches,
final Provision provision,
final boolean matching,
final String... message)
@@ -740,7 +697,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
List<ProvisioningReport> results = new ArrayList<>();
- if (anyKeys == null) {
+ if (matches == null) {
ProvisioningReport report = new ProvisioningReport();
report.setKey(null);
report.setName(delta.getObject().getUid().getUidValue());
@@ -753,9 +710,9 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
results.add(report);
} else {
- for (String anyKey : anyKeys) {
+ matches.forEach(match -> {
ProvisioningReport report = new ProvisioningReport();
- report.setKey(anyKey);
+ report.setKey(match.getMatchingKey());
report.setName(delta.getObject().getUid().getUidValue());
report.setOperation(ResourceOperation.NONE);
report.setAnyType(provision.getAnyType().getKey());
@@ -765,7 +722,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
}
results.add(report);
- }
+ });
}
finalize(matching
@@ -775,6 +732,100 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
return results;
}
+ protected void handleAnys(
+ final SyncDelta delta,
+ final List<PullMatch> matches,
+ final Provision provision,
+ final AnyUtils anyUtils) throws JobExecutionException {
+
+ if (matches.isEmpty()) {
+ LOG.debug("Nothing to do");
+ return;
+ }
+
+ if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
+ if (matches.get(0).getMatchingKey() == null) {
+ switch (profile.getTask().getUnmatchingRule()) {
+ case ASSIGN:
+ case PROVISION:
+ profile.getResults().addAll(
+ provision(profile.getTask().getUnmatchingRule(), delta, provision, anyUtils));
+ break;
+
+ case IGNORE:
+ profile.getResults().addAll(ignore(delta, null, provision, false));
+ break;
+
+ default:
+ // do nothing
+ }
+ } else {
+ // update VirAttrCache
+ virSchemaDAO.findByProvision(provision).forEach(virSchema -> {
+ Attribute attr = delta.getObject().getAttributeByName(virSchema.getExtAttrName());
+ matches.forEach(match -> {
+ if (attr == null) {
+ virAttrCache.expire(
+ provision.getAnyType().getKey(),
+ match.getMatchingKey(),
+ virSchema.getKey());
+ } else {
+ virAttrCache.put(
+ provision.getAnyType().getKey(),
+ match.getMatchingKey(),
+ virSchema.getKey(),
+ new VirAttrCacheValue(attr.getValue()));
+ }
+ });
+ });
+
+ switch (profile.getTask().getMatchingRule()) {
+ case UPDATE:
+ profile.getResults().addAll(update(delta, matches, provision));
+ break;
+
+ case DEPROVISION:
+ case UNASSIGN:
+ profile.getResults().addAll(
+ deprovision(profile.getTask().getMatchingRule(), delta, matches, provision));
+ break;
+
+ case LINK:
+ profile.getResults().addAll(link(delta, matches, provision, false));
+ break;
+
+ case UNLINK:
+ profile.getResults().addAll(link(delta, matches, provision, true));
+ break;
+
+ case IGNORE:
+ profile.getResults().addAll(ignore(delta, matches, provision, true));
+ break;
+
+ default:
+ // do nothing
+ }
+ }
+ } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
+ profile.getResults().addAll(delete(delta, matches, provision));
+ }
+ }
+
+ protected void handleLinkedAccounts(
+ final SyncDelta delta,
+ final List<PullMatch> matches,
+ final Provision provision,
+ final AnyUtils anyUtils) throws JobExecutionException {
+
+ if (matches.isEmpty()) {
+ LOG.debug("Nothing to do");
+ return;
+ }
+
+ // nothing to do in the general case
+ LOG.warn("Unexpected linked accounts found for {}: {}", anyUtils.anyTypeKind(), matches);
+ }
+
/**
* Look into SyncDelta and take necessary profile.getActions() (create / update / delete) on any object(s).
*
@@ -788,115 +839,53 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
LOG.debug("Process {} for {} as {}",
delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass());
- SyncDelta processed = delta;
- for (PullActions action : profile.getActions()) {
- processed = action.preprocess(profile, processed);
- }
+ SyncDelta finalDelta = profile.getActions().stream().
+ map(action -> action.preprocess(profile)).
+ reduce(Function.identity(), Function::andThen).
+ apply(delta);
LOG.debug("Transformed {} for {} as {}",
- processed.getDeltaType(), processed.getUid().getUidValue(), processed.getObject().getObjectClass());
+ finalDelta.getDeltaType(), finalDelta.getUid().getUidValue(), finalDelta.getObject().getObjectClass());
try {
- List<String> keys = pullUtils.match(processed, provision, anyUtils);
+ List<PullMatch> matches = pullUtils.match(finalDelta, provision, anyUtils);
LOG.debug("Match(es) found for {} as {}: {}",
- processed.getUid().getUidValue(), processed.getObject().getObjectClass(), keys);
+ finalDelta.getUid().getUidValue(), finalDelta.getObject().getObjectClass(), matches);
- if (keys.size() > 1) {
+ if (matches.size() > 1) {
switch (profile.getConflictResolutionAction()) {
case IGNORE:
throw new IgnoreProvisionException("More than one match found for "
- + processed.getObject().getUid().getUidValue() + ": " + keys);
+ + finalDelta.getObject().getUid().getUidValue() + ": " + matches);
case FIRSTMATCH:
- keys = keys.subList(0, 1);
+ matches = matches.subList(0, 1);
break;
case LASTMATCH:
- keys = keys.subList(keys.size() - 1, keys.size());
+ matches = matches.subList(matches.size() - 1, matches.size());
break;
default:
- // keep anyKeys unmodified
+ // keep matches unmodified
}
}
- if (SyncDeltaType.CREATE_OR_UPDATE == processed.getDeltaType()) {
- if (keys.isEmpty()) {
- switch (profile.getTask().getUnmatchingRule()) {
- case ASSIGN:
- profile.getResults().addAll(assign(processed, provision, anyUtils));
- break;
-
- case PROVISION:
- profile.getResults().addAll(provision(processed, provision, anyUtils));
- break;
-
- case IGNORE:
- profile.getResults().addAll(ignore(processed, null, provision, false));
- break;
-
- default:
- // do nothing
- }
- } else {
- // update VirAttrCache
- for (VirSchema virSchema : virSchemaDAO.findByProvision(provision)) {
- Attribute attr = processed.getObject().getAttributeByName(virSchema.getExtAttrName());
- for (String anyKey : keys) {
- if (attr == null) {
- virAttrCache.expire(
- provision.getAnyType().getKey(),
- anyKey,
- virSchema.getKey());
- } else {
- VirAttrCacheValue cacheValue = new VirAttrCacheValue();
- cacheValue.setValues(attr.getValue());
- virAttrCache.put(
- provision.getAnyType().getKey(),
- anyKey,
- virSchema.getKey(),
- cacheValue);
- }
- }
- }
-
- switch (profile.getTask().getMatchingRule()) {
- case UPDATE:
- profile.getResults().addAll(update(processed, keys, provision));
- break;
-
- case DEPROVISION:
- profile.getResults().addAll(deprovision(processed, keys, provision, false));
- break;
-
- case UNASSIGN:
- profile.getResults().addAll(deprovision(processed, keys, provision, true));
- break;
-
- case LINK:
- profile.getResults().addAll(link(processed, keys, provision, false));
- break;
-
- case UNLINK:
- profile.getResults().addAll(link(processed, keys, provision, true));
- break;
-
- case IGNORE:
- profile.getResults().addAll(ignore(processed, keys, provision, true));
- break;
-
- default:
- // do nothing
- }
- }
- } else if (SyncDeltaType.DELETE == processed.getDeltaType()) {
- if (keys.isEmpty()) {
- finalize(ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, processed);
- LOG.debug("No match found for deletion");
- } else {
- profile.getResults().addAll(delete(processed, keys, provision));
- }
- }
+ // users, groups and any objects
+ handleAnys(
+ finalDelta,
+ matches.stream().
+ filter(match -> match.getMatchTarget() == PullMatch.MatchTarget.ANY).
+ collect(Collectors.toList()), provision,
+ anyUtils);
+
+ // linked accounts
+ handleLinkedAccounts(
+ finalDelta,
+ matches.stream().
+ filter(match -> match.getMatchTarget() == PullMatch.MatchTarget.LINKED_ACCOUNT).
+ collect(Collectors.toList()), provision,
+ anyUtils);
} catch (IllegalStateException | IllegalArgumentException e) {
LOG.warn(e.getMessage());
}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index 0b884dc..3ea2cac 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.function.Function;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.to.RealmTO;
@@ -670,23 +671,23 @@ public class DefaultRealmPullResultHandler
LOG.debug("Process {} for {} as {}",
delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass());
- SyncDelta processed = delta;
- for (PullActions action : profile.getActions()) {
- processed = action.preprocess(profile, processed);
- }
+ SyncDelta finalDelta = profile.getActions().stream().
+ map(action -> action.preprocess(profile)).
+ reduce(Function.identity(), Function::andThen).
+ apply(delta);
LOG.debug("Transformed {} for {} as {}",
- processed.getDeltaType(), processed.getUid().getUidValue(), processed.getObject().getObjectClass());
+ finalDelta.getDeltaType(), finalDelta.getUid().getUidValue(), finalDelta.getObject().getObjectClass());
- List<String> keys = pullUtils.match(processed, orgUnit);
+ List<String> keys = pullUtils.match(finalDelta, orgUnit);
LOG.debug("Match found for {} as {}: {}",
- processed.getUid().getUidValue(), processed.getObject().getObjectClass(), keys);
+ finalDelta.getUid().getUidValue(), finalDelta.getObject().getObjectClass(), keys);
if (keys.size() > 1) {
switch (profile.getConflictResolutionAction()) {
case IGNORE:
throw new IgnoreProvisionException("More than one match found for "
- + processed.getObject().getUid().getUidValue() + ": " + keys);
+ + finalDelta.getObject().getUid().getUidValue() + ": " + keys);
case FIRSTMATCH:
keys = keys.subList(0, 1);
@@ -702,19 +703,19 @@ public class DefaultRealmPullResultHandler
}
try {
- if (SyncDeltaType.CREATE_OR_UPDATE == processed.getDeltaType()) {
+ if (SyncDeltaType.CREATE_OR_UPDATE == finalDelta.getDeltaType()) {
if (keys.isEmpty()) {
switch (profile.getTask().getUnmatchingRule()) {
case ASSIGN:
- profile.getResults().addAll(assign(processed, orgUnit));
+ profile.getResults().addAll(assign(finalDelta, orgUnit));
break;
case PROVISION:
- profile.getResults().addAll(provision(processed, orgUnit));
+ profile.getResults().addAll(provision(finalDelta, orgUnit));
break;
case IGNORE:
- profile.getResults().add(ignore(processed, false));
+ profile.getResults().add(ignore(finalDelta, false));
break;
default:
@@ -723,39 +724,39 @@ public class DefaultRealmPullResultHandler
} else {
switch (profile.getTask().getMatchingRule()) {
case UPDATE:
- profile.getResults().addAll(update(processed, keys, false));
+ profile.getResults().addAll(update(finalDelta, keys, false));
break;
case DEPROVISION:
- profile.getResults().addAll(deprovision(processed, keys, false));
+ profile.getResults().addAll(deprovision(finalDelta, keys, false));
break;
case UNASSIGN:
- profile.getResults().addAll(deprovision(processed, keys, true));
+ profile.getResults().addAll(deprovision(finalDelta, keys, true));
break;
case LINK:
- profile.getResults().addAll(link(processed, keys, false));
+ profile.getResults().addAll(link(finalDelta, keys, false));
break;
case UNLINK:
- profile.getResults().addAll(link(processed, keys, true));
+ profile.getResults().addAll(link(finalDelta, keys, true));
break;
case IGNORE:
- profile.getResults().add(ignore(processed, true));
+ profile.getResults().add(ignore(finalDelta, true));
break;
default:
// do nothing
}
}
- } else if (SyncDeltaType.DELETE == processed.getDeltaType()) {
+ } else if (SyncDeltaType.DELETE == finalDelta.getDeltaType()) {
if (keys.isEmpty()) {
- finalize(ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, processed);
+ finalize(ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, finalDelta);
LOG.debug("No match found for deletion");
} else {
- profile.getResults().addAll(delete(processed, keys));
+ profile.getResults().addAll(delete(finalDelta, keys));
}
}
} catch (IllegalStateException | IllegalArgumentException e) {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
index 953cb26..63b25c4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
@@ -19,22 +19,46 @@
package org.apache.syncope.core.provisioning.java.pushpull;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.LinkedAccountPatch;
import org.apache.syncope.common.lib.patch.UserPatch;
import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.LinkedAccountTO;
import org.apache.syncope.common.lib.to.PropagationStatus;
import org.apache.syncope.common.lib.to.UserTO;
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;
+import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.core.persistence.api.dao.PullMatch;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+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.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.ProvisioningManager;
import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
import org.identityconnectors.framework.common.objects.SyncDelta;
import org.apache.syncope.core.provisioning.api.pushpull.UserPullResultHandler;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
public class DefaultUserPullResultHandler extends AbstractPullResultHandler implements UserPullResultHandler {
@@ -68,14 +92,19 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl
return new WorkflowResult<>(update.getResult().getLeft(), update.getPropByRes(), update.getPerformedTasks());
}
+ public Boolean enabled(final SyncDelta delta) {
+ return profile.getTask().isSyncStatus() ? AttributeUtil.isEnabled(delta.getObject()) : null;
+ }
+
@Override
protected AnyTO doCreate(final AnyTO anyTO, final SyncDelta delta) {
- UserTO userTO = UserTO.class.cast(anyTO);
-
- Boolean enabled = pullUtils.readEnabled(delta.getObject(), profile.getTask());
- Map.Entry<String, List<PropagationStatus>> created =
- userProvisioningManager.create(userTO, true, true, enabled,
- Collections.singleton(profile.getTask().getResource().getKey()), true);
+ Map.Entry<String, List<PropagationStatus>> created = userProvisioningManager.create(
+ UserTO.class.cast(anyTO),
+ true,
+ true,
+ enabled(delta),
+ Collections.singleton(profile.getTask().getResource().getKey()),
+ true);
return getAnyTO(created.getKey());
}
@@ -87,16 +116,499 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl
final SyncDelta delta,
final ProvisioningReport result) {
- UserPatch userPatch = UserPatch.class.cast(anyPatch);
- Boolean enabled = pullUtils.readEnabled(delta.getObject(), profile.getTask());
-
Pair<UserPatch, List<PropagationStatus>> updated = userProvisioningManager.update(
- userPatch,
+ UserPatch.class.cast(anyPatch),
result,
- enabled,
+ enabled(delta),
Collections.singleton(profile.getTask().getResource().getKey()),
true);
return updated.getLeft();
}
+
+ @Override
+ protected void handleLinkedAccounts(
+ final SyncDelta delta,
+ final List<PullMatch> matches,
+ final Provision provision,
+ final AnyUtils anyUtils) throws JobExecutionException {
+
+ for (PullMatch match : matches) {
+ User user = userDAO.find(match.getLinkingUserKey());
+ if (user == null) {
+ LOG.error("Could not find linking user, cannot process match {}", match);
+ return;
+ }
+
+ Optional<? extends LinkedAccount> found =
+ user.getLinkedAccount(provision.getResource().getKey(), delta.getUid().getUidValue());
+ if (found.isPresent()) {
+ LinkedAccount account = found.get();
+
+ if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
+ switch (profile.getTask().getMatchingRule()) {
+ case UPDATE:
+ update(delta, account, provision).ifPresent(profile.getResults()::add);
+ break;
+
+ case DEPROVISION:
+ case UNASSIGN:
+ deprovision(profile.getTask().getMatchingRule(), delta, account).
+ ifPresent(profile.getResults()::add);
+ break;
+
+ case LINK:
+ case UNLINK:
+ LOG.warn("{} not applicable to linked accounts, ignoring",
+ profile.getTask().getMatchingRule());
+ break;
+
+ case IGNORE:
+ profile.getResults().add(ignore(delta, account, true));
+ break;
+
+ default:
+ // do nothing
+ }
+ } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
+ delete(delta, account).ifPresent(profile.getResults()::add);
+ }
+ } else {
+ if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
+ LinkedAccountTO accountTO = new LinkedAccountTO();
+ accountTO.setConnObjectKeyValue(delta.getUid().getUidValue());
+ accountTO.setResource(provision.getResource().getKey());
+
+ switch (profile.getTask().getUnmatchingRule()) {
+ case ASSIGN:
+ case PROVISION:
+ provision(profile.getTask().getUnmatchingRule(), delta, user, accountTO, provision).
+ ifPresent(profile.getResults()::add);
+ break;
+
+ case IGNORE:
+ profile.getResults().add(ignore(delta, null, false));
+ break;
+
+ default:
+ // do nothing
+ }
+ } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
+ finalize(
+ ResourceOperation.DELETE.name().toLowerCase(),
+ AuditElements.Result.SUCCESS,
+ null,
+ null,
+ delta);
+ LOG.debug("No match found for deletion");
+ }
+ }
+ }
+ }
+
+ protected Optional<ProvisioningReport> deprovision(
+ final MatchingRule matchingRule,
+ final SyncDelta delta,
+ final LinkedAccount account) throws JobExecutionException {
+
+ if (!profile.getTask().isPerformUpdate()) {
+ LOG.debug("PullTask not configured for update");
+ finalize(MatchingRule.toEventName(MatchingRule.UPDATE), Result.SUCCESS, null, null, delta);
+ return Optional.empty();
+ }
+
+ LOG.debug("About to deprovision {}", account);
+
+ ProvisioningReport report = new ProvisioningReport();
+ report.setOperation(ResourceOperation.DELETE);
+ report.setAnyType(PullMatch.MatchTarget.LINKED_ACCOUNT.name());
+ report.setStatus(ProvisioningReport.Status.SUCCESS);
+ report.setKey(account.getKey());
+
+ LinkedAccountTO before = userDataBinder.getLinkedAccountTO(account);
+
+ if (!profile.isDryRun()) {
+ Object output = before;
+ Result resultStatus;
+
+ try {
+ if (matchingRule == MatchingRule.UNASSIGN) {
+ for (PullActions action : profile.getActions()) {
+ action.beforeUnassign(profile, delta, before);
+ }
+ } else if (matchingRule == MatchingRule.DEPROVISION) {
+ for (PullActions action : profile.getActions()) {
+ action.beforeDeprovision(profile, delta, before);
+ }
+ }
+
+ PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
+ propByLinkedAccount.add(
+ ResourceOperation.DELETE,
+ Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()));
+
+ taskExecutor.execute(propagationManager.getDeleteTasks(
+ AnyTypeKind.USER,
+ account.getOwner().getKey(),
+ null,
+ propByLinkedAccount,
+ null),
+ false);
+
+ for (PullActions action : profile.getActions()) {
+ action.after(profile, delta, before, report);
+ }
+
+ resultStatus = Result.SUCCESS;
+
+ LOG.debug("Linked account {} successfully updated", account.getConnObjectKeyValue());
+ } catch (PropagationException e) {
+ // A propagation failure doesn't imply a pull failure.
+ // The propagation exception status will be reported into the propagation task execution.
+ LOG.error("Could not propagate linked acccount {}", account.getConnObjectKeyValue());
+ output = e;
+ resultStatus = Result.FAILURE;
+ } catch (Exception e) {
+ throwIgnoreProvisionException(delta, e);
+
+ report.setStatus(ProvisioningReport.Status.FAILURE);
+ report.setMessage(ExceptionUtils.getRootCauseMessage(e));
+ LOG.error("Could not update linked account {}", account, e);
+ output = e;
+ resultStatus = Result.FAILURE;
+ }
+
+ finalize(MatchingRule.toEventName(matchingRule), resultStatus, before, output, delta);
+ }
+
+ return Optional.of(report);
+ }
+
+ protected Optional<ProvisioningReport> provision(
+ final UnmatchingRule rule,
+ final SyncDelta delta,
+ final User user,
+ final LinkedAccountTO accountTO,
+ final Provision provision)
+ throws JobExecutionException {
+
+ if (!profile.getTask().isPerformCreate()) {
+ LOG.debug("PullTask not configured for create");
+ finalize(UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
+ return Optional.empty();
+ }
+
+ LOG.debug("About to create {}", accountTO);
+
+ ProvisioningReport report = new ProvisioningReport();
+ report.setOperation(ResourceOperation.CREATE);
+ report.setName(accountTO.getConnObjectKeyValue());
+ report.setAnyType(PullMatch.MatchTarget.LINKED_ACCOUNT.name());
+ report.setStatus(ProvisioningReport.Status.SUCCESS);
+
+ if (profile.isDryRun()) {
+ report.setKey(null);
+ finalize(UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
+ } else {
+ UserTO owner = userDataBinder.getUserTO(user, false);
+ UserTO connObject = connObjectUtils.getAnyTO(
+ delta.getObject(), profile.getTask(), provision, getAnyUtils(), false);
+
+ if (connObject.getUsername().equals(owner.getUsername())) {
+ accountTO.setUsername(null);
+ } else if (!connObject.getUsername().equals(accountTO.getUsername())) {
+ accountTO.setUsername(connObject.getUsername());
+ }
+
+ if (connObject.getPassword() != null) {
+ accountTO.setPassword(connObject.getPassword());
+ }
+
+ accountTO.setSuspended(BooleanUtils.isTrue(BooleanUtils.negate(enabled(delta))));
+
+ connObject.getPlainAttrs().forEach(connObjectAttr -> {
+ Optional<AttrTO> ownerAttr = owner.getPlainAttr(connObjectAttr.getSchema());
+ if (ownerAttr.isPresent() && ownerAttr.get().getValues().equals(connObjectAttr.getValues())) {
+ accountTO.getPlainAttrs().removeIf(attr -> connObjectAttr.getSchema().equals(attr.getSchema()));
+ } else {
+ accountTO.getPlainAttrs().add(connObjectAttr);
+ }
+ });
+
+ for (PullActions action : profile.getActions()) {
+ if (rule == UnmatchingRule.ASSIGN) {
+ action.beforeAssign(profile, delta, accountTO);
+ } else if (rule == UnmatchingRule.PROVISION) {
+ action.beforeProvision(profile, delta, accountTO);
+ }
+ }
+
+ UserPatch patch = new UserPatch();
+ patch.setKey(user.getKey());
+ patch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
+ operation(PatchOperation.ADD_REPLACE).linkedAccountTO(accountTO).build());
+
+ Result resultStatus;
+ Object output;
+
+ try {
+ userProvisioningManager.update(
+ patch,
+ report,
+ null,
+ Collections.singleton(profile.getTask().getResource().getKey()),
+ true);
+ resultStatus = Result.SUCCESS;
+
+ LinkedAccountTO created = userDAO.find(patch.getKey()).
+ getLinkedAccount(accountTO.getResource(), accountTO.getConnObjectKeyValue()).
+ map(acct -> userDataBinder.getLinkedAccountTO(acct)).
+ orElse(null);
+ output = created;
+ resultStatus = Result.SUCCESS;
+
+ for (PullActions action : profile.getActions()) {
+ action.after(profile, delta, created, report);
+ }
+
+ LOG.debug("Linked account {} successfully created", accountTO.getConnObjectKeyValue());
+ } catch (PropagationException e) {
+ // A propagation failure doesn't imply a pull failure.
+ // The propagation exception status will be reported into the propagation task execution.
+ LOG.error("Could not propagate linked acccount {}", accountTO.getConnObjectKeyValue());
+ output = e;
+ resultStatus = Result.FAILURE;
+ } catch (Exception e) {
+ throwIgnoreProvisionException(delta, e);
+
+ report.setStatus(ProvisioningReport.Status.FAILURE);
+ report.setMessage(ExceptionUtils.getRootCauseMessage(e));
+ LOG.error("Could not create linked account {} ", accountTO.getConnObjectKeyValue(), e);
+ output = e;
+ resultStatus = Result.FAILURE;
+ }
+
+ finalize(UnmatchingRule.toEventName(rule), resultStatus, null, output, delta);
+ }
+
+ return Optional.of(report);
+ }
+
+ protected Optional<ProvisioningReport> update(
+ final SyncDelta delta,
+ final LinkedAccount account,
+ final Provision provision)
+ throws JobExecutionException {
+
+ if (!profile.getTask().isPerformUpdate()) {
+ LOG.debug("PullTask not configured for update");
+ finalize(MatchingRule.toEventName(MatchingRule.UPDATE), Result.SUCCESS, null, null, delta);
+ return Optional.empty();
+ }
+
+ LOG.debug("About to update {}", account);
+
+ ProvisioningReport report = new ProvisioningReport();
+ report.setOperation(ResourceOperation.UPDATE);
+ report.setKey(account.getKey());
+ report.setName(account.getConnObjectKeyValue());
+ report.setAnyType(PullMatch.MatchTarget.LINKED_ACCOUNT.name());
+ report.setStatus(ProvisioningReport.Status.SUCCESS);
+
+ if (!profile.isDryRun()) {
+ LinkedAccountTO before = userDataBinder.getLinkedAccountTO(account);
+
+ UserTO owner = userDataBinder.getUserTO(account.getOwner(), false);
+ UserTO connObject = connObjectUtils.getAnyTO(
+ delta.getObject(), profile.getTask(), provision, getAnyUtils(), false);
+
+ LinkedAccountTO update = userDataBinder.getLinkedAccountTO(account);
+
+ if (connObject.getUsername().equals(owner.getUsername())) {
+ update.setUsername(null);
+ } else if (!connObject.getUsername().equals(update.getUsername())) {
+ update.setUsername(connObject.getUsername());
+ }
+
+ if (connObject.getPassword() != null) {
+ update.setPassword(connObject.getPassword());
+ }
+
+ update.setSuspended(BooleanUtils.isTrue(BooleanUtils.negate(enabled(delta))));
+
+ Set<String> attrsToRemove = new HashSet<>();
+ connObject.getPlainAttrs().forEach(connObjectAttr -> {
+ Optional<AttrTO> ownerAttr = owner.getPlainAttr(connObjectAttr.getSchema());
+ if (ownerAttr.isPresent() && ownerAttr.get().getValues().equals(connObjectAttr.getValues())) {
+ attrsToRemove.add(connObjectAttr.getSchema());
+ } else {
+ Optional<AttrTO> updateAttr = update.getPlainAttr(connObjectAttr.getSchema());
+ if (!updateAttr.isPresent() || !updateAttr.get().getValues().equals(connObjectAttr.getValues())) {
+ attrsToRemove.add(connObjectAttr.getSchema());
+ update.getPlainAttrs().add(connObjectAttr);
+ }
+ }
+ });
+ update.getPlainAttrs().removeIf(attr -> attrsToRemove.contains(attr.getSchema()));
+
+ UserPatch patch = new UserPatch();
+ patch.setKey(account.getOwner().getKey());
+ patch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
+ operation(PatchOperation.ADD_REPLACE).linkedAccountTO(update).build());
+
+ for (PullActions action : profile.getActions()) {
+ action.beforeUpdate(profile, delta, before, patch);
+ }
+
+ Result resultStatus;
+ Object output;
+
+ try {
+ userProvisioningManager.update(
+ patch,
+ report,
+ null,
+ Collections.singleton(profile.getTask().getResource().getKey()),
+ true);
+ resultStatus = Result.SUCCESS;
+
+ LinkedAccountTO updated = userDAO.find(patch.getKey()).
+ getLinkedAccount(account.getResource().getKey(), account.getConnObjectKeyValue()).
+ map(acct -> userDataBinder.getLinkedAccountTO(acct)).
+ orElse(null);
+ output = updated;
+ resultStatus = Result.SUCCESS;
+
+ for (PullActions action : profile.getActions()) {
+ action.after(profile, delta, updated, report);
+ }
+
+ LOG.debug("Linked account {} successfully updated", account.getConnObjectKeyValue());
+ } catch (PropagationException e) {
+ // A propagation failure doesn't imply a pull failure.
+ // The propagation exception status will be reported into the propagation task execution.
+ LOG.error("Could not propagate linked acccount {}", account.getConnObjectKeyValue());
+ output = e;
+ resultStatus = Result.FAILURE;
+ } catch (Exception e) {
+ throwIgnoreProvisionException(delta, e);
+
+ report.setStatus(ProvisioningReport.Status.FAILURE);
+ report.setMessage(ExceptionUtils.getRootCauseMessage(e));
+ LOG.error("Could not update linked account {}", account, e);
+ output = e;
+ resultStatus = Result.FAILURE;
+ }
+
+ finalize(MatchingRule.toEventName(MatchingRule.UPDATE), resultStatus, before, output, delta);
+ }
+
+ return Optional.of(report);
+ }
+
+ protected Optional<ProvisioningReport> delete(
+ final SyncDelta delta,
+ final LinkedAccount account)
+ throws JobExecutionException {
+
+ if (!profile.getTask().isPerformDelete()) {
+ LOG.debug("PullTask not configured for delete");
+ finalize(ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, delta);
+ return Optional.empty();
+ }
+
+ LOG.debug("About to delete {}", account);
+
+ Object output;
+ Result resultStatus = Result.FAILURE;
+
+ ProvisioningReport report = new ProvisioningReport();
+
+ try {
+ report.setKey(account.getKey());
+ report.setName(account.getConnObjectKeyValue());
+ report.setOperation(ResourceOperation.DELETE);
+ report.setAnyType(PullMatch.MatchTarget.LINKED_ACCOUNT.name());
+ report.setStatus(ProvisioningReport.Status.SUCCESS);
+
+ if (!profile.isDryRun()) {
+ LinkedAccountTO before = userDataBinder.getLinkedAccountTO(account);
+
+ for (PullActions action : profile.getActions()) {
+ action.beforeDelete(profile, delta, before);
+ }
+
+ UserPatch patch = new UserPatch();
+ patch.setKey(account.getOwner().getKey());
+ patch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
+ operation(PatchOperation.DELETE).linkedAccountTO(before).build());
+
+ try {
+ userProvisioningManager.update(
+ patch,
+ report,
+ null,
+ Collections.singleton(profile.getTask().getResource().getKey()),
+ true);
+ resultStatus = Result.SUCCESS;
+
+ output = null;
+
+ for (PullActions action : profile.getActions()) {
+ action.after(profile, delta, before, report);
+ }
+ } catch (Exception e) {
+ throwIgnoreProvisionException(delta, e);
+
+ report.setStatus(ProvisioningReport.Status.FAILURE);
+ report.setMessage(ExceptionUtils.getRootCauseMessage(e));
+ LOG.error("Could not delete linked account {}", account, e);
+ output = e;
+ }
+
+ finalize(ResourceOperation.DELETE.name().toLowerCase(), resultStatus, before, output, delta);
+ }
+ } catch (Exception e) {
+ LOG.error("Could not delete linked account {}", account, e);
+ }
+
+ return Optional.of(report);
+ }
+
+ protected ProvisioningReport ignore(
+ final SyncDelta delta,
+ final LinkedAccount account,
+ final boolean matching,
+ final String... message)
+ throws JobExecutionException {
+
+ LOG.debug("Linked account to ignore {}", delta.getObject().getUid().getUidValue());
+
+ ProvisioningReport report = new ProvisioningReport();
+ if (account == null) {
+ report.setKey(null);
+ report.setName(delta.getObject().getUid().getUidValue());
+ report.setOperation(ResourceOperation.NONE);
+ report.setAnyType(PullMatch.MatchTarget.LINKED_ACCOUNT.name());
+ report.setStatus(ProvisioningReport.Status.SUCCESS);
+ if (message != null && message.length >= 1) {
+ report.setMessage(message[0]);
+ }
+ } else {
+ report.setKey(account.getKey());
+ report.setName(delta.getObject().getUid().getUidValue());
+ report.setOperation(ResourceOperation.NONE);
+ report.setAnyType(PullMatch.MatchTarget.LINKED_ACCOUNT.name());
+ report.setStatus(ProvisioningReport.Status.SUCCESS);
+ if (message != null && message.length >= 1) {
+ report.setMessage(message[0]);
+ }
+ }
+
+ finalize(matching
+ ? MatchingRule.toEventName(MatchingRule.IGNORE)
+ : UnmatchingRule.toEventName(UnmatchingRule.IGNORE), AuditElements.Result.SUCCESS, null, null, delta);
+
+ return report;
+ }
}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
index 2040705..4c4bfbe 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
@@ -53,11 +53,11 @@ import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
-import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.IntAttrName;
import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.persistence.api.dao.PullMatch;
import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
import org.apache.syncope.core.spring.ImplementationManager;
@@ -65,7 +65,6 @@ import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.Name;
-import org.identityconnectors.framework.common.objects.OperationalAttributes;
import org.identityconnectors.framework.common.objects.SearchResult;
import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
import org.identityconnectors.framework.common.objects.SyncDelta;
@@ -162,21 +161,21 @@ public class PullUtils {
ConnectorObject connObj = found.iterator().next();
try {
- List<String> anyKeys = match(
+ List<PullMatch> matches = match(
new SyncDeltaBuilder().
setToken(new SyncToken("")).
setDeltaType(SyncDeltaType.CREATE_OR_UPDATE).
setObject(connObj).
build(),
provision.get(), anyUtils);
- if (anyKeys.isEmpty()) {
+ if (matches.isEmpty()) {
LOG.debug("No matching {} found for {}, aborting", anyUtils.anyTypeKind(), connObj);
} else {
- if (anyKeys.size() > 1) {
- LOG.warn("More than one {} found {} - taking first only", anyUtils.anyTypeKind(), anyKeys);
+ if (matches.size() > 1) {
+ LOG.warn("More than one {} found {} - taking first only", anyUtils.anyTypeKind(), matches);
}
- result = Optional.ofNullable(anyKeys.iterator().next());
+ result = Optional.ofNullable(matches.iterator().next().getMatchingKey());
}
} catch (IllegalArgumentException e) {
LOG.warn(e.getMessage());
@@ -186,9 +185,11 @@ public class PullUtils {
return result;
}
- private List<String> findByConnObjectKey(
+ private List<PullMatch> findByConnObjectKey(
final SyncDelta syncDelta, final Provision provision, final AnyUtils anyUtils) {
+ List<PullMatch> noMatchResult = Collections.singletonList(PullCorrelationRule.NO_MATCH);
+
String connObjectKey = null;
Optional<? extends MappingItem> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
@@ -200,7 +201,7 @@ public class PullUtils {
}
}
if (connObjectKey == null) {
- return Collections.emptyList();
+ return noMatchResult;
}
for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem.get())) {
@@ -213,8 +214,6 @@ public class PullUtils {
}
}
- List<String> result = new ArrayList<>();
-
IntAttrName intAttrName;
try {
intAttrName = intAttrNameParser.parse(
@@ -222,15 +221,17 @@ public class PullUtils {
provision.getAnyType().getKind());
} catch (ParseException e) {
LOG.error("Invalid intAttrName '{}' specified, ignoring", connObjectKeyItem.get().getIntAttrName(), e);
- return result;
+ return noMatchResult;
}
+ List<PullMatch> result = new ArrayList<>();
+
if (intAttrName.getField() != null) {
switch (intAttrName.getField()) {
case "key":
Any<?> any = anyUtils.dao().find(connObjectKey);
if (any != null) {
- result.add(any.getKey());
+ result.add(new PullMatch.Builder().matchingKey(any.getKey()).build());
}
break;
@@ -239,12 +240,13 @@ public class PullUtils {
AnyCond cond = new AnyCond(AttributeCond.Type.IEQ);
cond.setSchema("username");
cond.setExpression(connObjectKey);
- result.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.USER).
- stream().map(Entity::getKey).collect(Collectors.toList()));
+ result.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.USER).stream().
+ map(user -> new PullMatch.Builder().matchingKey(user.getKey()).build()).
+ collect(Collectors.toList()));
} else {
User user = userDAO.findByUsername(connObjectKey);
if (user != null) {
- result.add(user.getKey());
+ result.add(new PullMatch.Builder().matchingKey(user.getKey()).build());
}
}
break;
@@ -254,12 +256,13 @@ public class PullUtils {
AnyCond cond = new AnyCond(AttributeCond.Type.IEQ);
cond.setSchema("name");
cond.setExpression(connObjectKey);
- result.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.GROUP).
- stream().map(Entity::getKey).collect(Collectors.toList()));
+ result.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.GROUP).stream().
+ map(group -> new PullMatch.Builder().matchingKey(group.getKey()).build()).
+ collect(Collectors.toList()));
} else {
Group group = groupDAO.findByName(connObjectKey);
if (group != null) {
- result.add(group.getKey());
+ result.add(new PullMatch.Builder().matchingKey(group.getKey()).build());
}
}
@@ -267,12 +270,13 @@ public class PullUtils {
AnyCond cond = new AnyCond(AttributeCond.Type.IEQ);
cond.setSchema("name");
cond.setExpression(connObjectKey);
- result.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.ANY_OBJECT).
- stream().map(Entity::getKey).collect(Collectors.toList()));
+ result.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.ANY_OBJECT).stream().
+ map(anyObject -> new PullMatch.Builder().matchingKey(anyObject.getKey()).build()).
+ collect(Collectors.toList()));
} else {
AnyObject anyObject = anyObjectDAO.findByName(connObjectKey);
if (anyObject != null) {
- result.add(anyObject.getKey());
+ result.add(new PullMatch.Builder().matchingKey(anyObject.getKey()).build());
}
}
break;
@@ -295,35 +299,46 @@ public class PullUtils {
if (intAttrName.getSchema().isUniqueConstraint()) {
anyUtils.dao().findByPlainAttrUniqueValue((PlainSchema) intAttrName.getSchema(),
(PlainAttrUniqueValue) value, provision.isIgnoreCaseMatch()).
- ifPresent(found -> result.add(found.getKey()));
+ ifPresent(any -> result.add(new PullMatch.Builder().matchingKey(any.getKey()).build()));
} else {
result.addAll(anyUtils.dao().findByPlainAttrValue((PlainSchema) intAttrName.getSchema(),
- value, provision.isIgnoreCaseMatch()).
- stream().map(Entity::getKey).collect(Collectors.toList()));
+ value, provision.isIgnoreCaseMatch()).stream().
+ map(any -> new PullMatch.Builder().matchingKey(any.getKey()).build()).
+ collect(Collectors.toList()));
}
break;
case DERIVED:
- result.addAll(anyUtils.dao().findByDerAttrValue(
- (DerSchema) intAttrName.getSchema(), connObjectKey, provision.isIgnoreCaseMatch()).
- stream().map(Entity::getKey).collect(Collectors.toList()));
+ result.addAll(anyUtils.dao().findByDerAttrValue((DerSchema) intAttrName.getSchema(),
+ connObjectKey, provision.isIgnoreCaseMatch()).stream().
+ map(any -> new PullMatch.Builder().matchingKey(any.getKey()).build()).
+ collect(Collectors.toList()));
break;
default:
}
}
- return result;
+ return result.isEmpty() ? noMatchResult : result;
}
- private List<String> findByCorrelationRule(
+ private List<PullMatch> findByCorrelationRule(
final SyncDelta syncDelta,
final Provision provision,
final PullCorrelationRule rule,
final AnyTypeKind type) {
- return searchDAO.search(rule.getSearchCond(syncDelta, provision), type).stream().
- map(Entity::getKey).collect(Collectors.toList());
+ List<PullMatch> result = new ArrayList<>();
+
+ result.addAll(searchDAO.search(rule.getSearchCond(syncDelta, provision), type).stream().
+ map(any -> rule.matching(any, syncDelta, provision)).
+ collect(Collectors.toList()));
+
+ if (result.isEmpty()) {
+ rule.unmatching(syncDelta, provision).ifPresent(result::add);
+ }
+
+ return result;
}
/**
@@ -334,7 +349,7 @@ public class PullUtils {
* @param anyUtils any utils
* @return list of matching users' / groups' / any objects' keys
*/
- public List<String> match(
+ public List<PullMatch> match(
final SyncDelta syncDelta,
final Provision provision,
final AnyUtils anyUtils) {
@@ -358,7 +373,7 @@ public class PullUtils {
: findByConnObjectKey(syncDelta, provision, anyUtils);
} catch (RuntimeException e) {
LOG.error("Could not match {} with any existing {}", syncDelta, provision.getAnyType(), e);
- return Collections.<String>emptyList();
+ return Collections.emptyList();
}
}
@@ -432,16 +447,4 @@ public class PullUtils {
return result;
}
-
- public Boolean readEnabled(final ConnectorObject connectorObject, final ProvisioningTask task) {
- Boolean enabled = null;
- if (task.isSyncStatus()) {
- Attribute status = AttributeUtil.find(OperationalAttributes.ENABLE_NAME, connectorObject.getAttributes());
- if (status != null && status.getValue() != null && !status.getValue().isEmpty()) {
- enabled = (Boolean) status.getValue().get(0);
- }
- }
-
- return enabled;
- }
}
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 8594401..b260fb5 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,7 +27,6 @@ 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;
@@ -35,7 +34,6 @@ 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;
@@ -139,29 +137,4 @@ 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/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index 4f2d52a..4f1f5e9 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -154,6 +154,8 @@ public class ConnObjectUtils {
* @param pullTask pull task
* @param provision provision information
* @param anyUtils utils
+ * @param generatePasswordIfPossible whether password value shall be generated, in case not found from
+ * connector object and allowed by resource configuration
* @param <T> any object
* @return UserTO for the user to be created
*/
@@ -162,14 +164,15 @@ public class ConnObjectUtils {
final ConnectorObject obj,
final PullTask pullTask,
final Provision provision,
- final AnyUtils anyUtils) {
+ final AnyUtils anyUtils,
+ final boolean generatePasswordIfPossible) {
T anyTO = getAnyTOFromConnObject(obj, pullTask, provision, anyUtils);
// (for users) if password was not set above, generate if resource is configured for that
if (anyTO instanceof UserTO
&& StringUtils.isBlank(((UserTO) anyTO).getPassword())
- && provision.getResource().isRandomPwdIfNotProvided()) {
+ && generatePasswordIfPossible && provision.getResource().isRandomPwdIfNotProvided()) {
UserTO userTO = (UserTO) anyTO;
@@ -187,9 +190,7 @@ public class ConnObjectUtils {
userTO.getResources().stream().
map(resource -> resourceDAO.find(resource)).
filter(resource -> resource != null && resource.getPasswordPolicy() != null).
- forEach(resource -> {
- passwordPolicies.add(resource.getPasswordPolicy());
- });
+ forEach(resource -> passwordPolicies.add(resource.getPasswordPolicy()));
String password;
try {
@@ -241,64 +242,63 @@ public class ConnObjectUtils {
updated.setKey(key);
T anyPatch = null;
- if (null != anyUtils.anyTypeKind()) {
- switch (anyUtils.anyTypeKind()) {
- case USER:
- UserTO originalUser = (UserTO) original;
- UserTO updatedUser = (UserTO) updated;
-
- if (StringUtils.isBlank(updatedUser.getUsername())) {
- updatedUser.setUsername(originalUser.getUsername());
- }
-
- // update password if and only if password is really changed
- User user = userDAO.authFind(key);
- if (StringUtils.isBlank(updatedUser.getPassword())
- || ENCRYPTOR.verify(updatedUser.getPassword(),
- user.getCipherAlgorithm(), user.getPassword())) {
-
- updatedUser.setPassword(null);
- }
-
- updatedUser.setSecurityQuestion(originalUser.getSecurityQuestion());
-
- if (!mappingManager.hasMustChangePassword(provision)) {
- updatedUser.setMustChangePassword(originalUser.isMustChangePassword());
- }
-
- anyPatch = (T) AnyOperations.diff(updatedUser, originalUser, true);
- break;
-
- case GROUP:
- GroupTO originalGroup = (GroupTO) original;
- GroupTO updatedGroup = (GroupTO) updated;
-
- if (StringUtils.isBlank(updatedGroup.getName())) {
- updatedGroup.setName(originalGroup.getName());
- }
- updatedGroup.setUserOwner(originalGroup.getUserOwner());
- updatedGroup.setGroupOwner(originalGroup.getGroupOwner());
- updatedGroup.setUDynMembershipCond(originalGroup.getUDynMembershipCond());
- updatedGroup.getADynMembershipConds().putAll(originalGroup.getADynMembershipConds());
- updatedGroup.getTypeExtensions().addAll(originalGroup.getTypeExtensions());
-
- anyPatch = (T) AnyOperations.diff(updatedGroup, originalGroup, true);
- break;
-
- case ANY_OBJECT:
- AnyObjectTO originalAnyObject = (AnyObjectTO) original;
- AnyObjectTO updatedAnyObject = (AnyObjectTO) updated;
-
- if (StringUtils.isBlank(updatedAnyObject.getName())) {
- updatedAnyObject.setName(originalAnyObject.getName());
- }
-
- anyPatch = (T) AnyOperations.diff(updatedAnyObject, originalAnyObject, true);
- break;
-
- default:
- }
+ switch (anyUtils.anyTypeKind()) {
+ case USER:
+ UserTO originalUser = (UserTO) original;
+ UserTO updatedUser = (UserTO) updated;
+
+ if (StringUtils.isBlank(updatedUser.getUsername())) {
+ updatedUser.setUsername(originalUser.getUsername());
+ }
+
+ // update password if and only if password is really changed
+ User user = userDAO.authFind(key);
+ if (StringUtils.isBlank(updatedUser.getPassword())
+ || ENCRYPTOR.verify(updatedUser.getPassword(),
+ user.getCipherAlgorithm(), user.getPassword())) {
+
+ updatedUser.setPassword(null);
+ }
+
+ updatedUser.setSecurityQuestion(originalUser.getSecurityQuestion());
+
+ if (!mappingManager.hasMustChangePassword(provision)) {
+ updatedUser.setMustChangePassword(originalUser.isMustChangePassword());
+ }
+
+ anyPatch = (T) AnyOperations.diff(updatedUser, originalUser, true);
+ break;
+
+ case GROUP:
+ GroupTO originalGroup = (GroupTO) original;
+ GroupTO updatedGroup = (GroupTO) updated;
+
+ if (StringUtils.isBlank(updatedGroup.getName())) {
+ updatedGroup.setName(originalGroup.getName());
+ }
+ updatedGroup.setUserOwner(originalGroup.getUserOwner());
+ updatedGroup.setGroupOwner(originalGroup.getGroupOwner());
+ updatedGroup.setUDynMembershipCond(originalGroup.getUDynMembershipCond());
+ updatedGroup.getADynMembershipConds().putAll(originalGroup.getADynMembershipConds());
+ updatedGroup.getTypeExtensions().addAll(originalGroup.getTypeExtensions());
+
+ anyPatch = (T) AnyOperations.diff(updatedGroup, originalGroup, true);
+ break;
+
+ case ANY_OBJECT:
+ AnyObjectTO originalAnyObject = (AnyObjectTO) original;
+ AnyObjectTO updatedAnyObject = (AnyObjectTO) updated;
+
+ if (StringUtils.isBlank(updatedAnyObject.getName())) {
+ updatedAnyObject.setName(originalAnyObject.getName());
+ }
+
+ anyPatch = (T) AnyOperations.diff(updatedAnyObject, originalAnyObject, true);
+ break;
+
+ default:
}
+
// SYNCOPE-1343, remove null or empty values from the patch plain attributes
if (anyPatch != null) {
AnyOperations.cleanEmptyAttrs(updated, anyPatch);
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
index c97f93a..50bf5d9 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
@@ -346,7 +346,7 @@ public class CamelUserProvisioningManager extends AbstractCamelProvisioningManag
Exception ex = (Exception) exchange.getProperty(Exchange.EXCEPTION_CAUGHT);
if (ex != null) {
LOG.error("Update of user {} failed, trying to pull its status anyway (if configured)",
- nullPriorityAsync, ex);
+ userPatch.getKey(), ex);
result.setStatus(ProvisioningReport.Status.FAILURE);
result.setMessage("Update failed, trying to pull status anyway (if configured)\n" + ex.getMessage());
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java
index f24eb79..e972305 100644
--- a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java
@@ -41,15 +41,8 @@ public class OIDCClientLoader implements SyncopeLoader {
public void load() {
EntitlementsHolder.getInstance().init(OIDCClientEntitlement.values());
- for (String domain : domainsHolder.getDomains().keySet()) {
- AuthContextUtils.execWithAuthContext(domain, new AuthContextUtils.Executable<Void>() {
-
- @Override
- public Void exec() {
- return null;
- }
- });
- }
+ domainsHolder.getDomains().forEach((domain, datasource) -> {
+ AuthContextUtils.execWithAuthContext(domain, () -> null);
+ });
}
-
}
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
index cf0004d..81b6390 100644
--- a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -244,9 +245,10 @@ public class OIDCUserManager {
}
List<OIDCProviderActions> actions = getActions(op);
- for (OIDCProviderActions action : actions) {
- userTO = action.beforeCreate(userTO, responseTO);
- }
+ userTO = actions.stream().
+ map(action -> action.beforeCreate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userTO);
fill(op, responseTO, userTO);
@@ -260,9 +262,10 @@ public class OIDCUserManager {
Pair<String, List<PropagationStatus>> created = provisioningManager.create(userTO, false, false);
userTO = binder.getUserTO(created.getKey());
- for (OIDCProviderActions action : actions) {
- userTO = action.afterCreate(userTO, responseTO);
- }
+ userTO = actions.stream().
+ map(action -> action.afterCreate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userTO);
return userTO.getUsername();
}
@@ -277,16 +280,18 @@ public class OIDCUserManager {
UserPatch userPatch = AnyOperations.diff(userTO, original, true);
List<OIDCProviderActions> actions = getActions(op);
- for (OIDCProviderActions action : actions) {
- userPatch = action.beforeUpdate(userPatch, responseTO);
- }
+ userPatch = actions.stream().
+ map(action -> action.beforeUpdate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userPatch);
Pair<UserPatch, List<PropagationStatus>> updated = provisioningManager.update(userPatch, false);
userTO = binder.getUserTO(updated.getLeft().getKey());
- for (OIDCProviderActions action : actions) {
- userTO = action.afterUpdate(userTO, responseTO);
- }
+ userTO = actions.stream().
+ map(action -> action.afterUpdate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userTO);
return userTO.getUsername();
}
diff --git a/ext/oidcclient/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/OIDCProviderActions.java b/ext/oidcclient/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/OIDCProviderActions.java
index 40ea6a8..4a641ad 100644
--- a/ext/oidcclient/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/OIDCProviderActions.java
+++ b/ext/oidcclient/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/OIDCProviderActions.java
@@ -18,18 +18,26 @@
*/
package org.apache.syncope.core.provisioning.api;
+import java.util.function.Function;
import org.apache.syncope.common.lib.patch.UserPatch;
import org.apache.syncope.common.lib.to.OIDCLoginResponseTO;
import org.apache.syncope.common.lib.to.UserTO;
public interface OIDCProviderActions {
- UserTO beforeCreate(UserTO input, OIDCLoginResponseTO loginResponse);
+ default Function<UserTO, UserTO> beforeCreate(OIDCLoginResponseTO loginResponse) {
+ return Function.identity();
+ }
- UserTO afterCreate(UserTO input, OIDCLoginResponseTO loginResponse);
+ default Function<UserTO, UserTO> afterCreate(OIDCLoginResponseTO loginResponse) {
+ return Function.identity();
+ }
- UserPatch beforeUpdate(UserPatch input, OIDCLoginResponseTO loginResponse);
-
- UserTO afterUpdate(UserTO input, OIDCLoginResponseTO loginResponse);
+ default Function<UserPatch, UserPatch> beforeUpdate(OIDCLoginResponseTO loginResponse) {
+ return Function.identity();
+ }
+ default Function<UserTO, UserTO> afterUpdate(OIDCLoginResponseTO loginResponse) {
+ return Function.identity();
+ }
}
diff --git a/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultOIDCProviderActions.java b/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultOIDCProviderActions.java
deleted file mode 100644
index e6b060c..0000000
--- a/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultOIDCProviderActions.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.core.provisioning.java;
-
-import org.apache.syncope.common.lib.patch.UserPatch;
-import org.apache.syncope.common.lib.to.OIDCLoginResponseTO;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.core.provisioning.api.OIDCProviderActions;
-
-public class DefaultOIDCProviderActions implements OIDCProviderActions {
-
- @Override
- public UserTO beforeCreate(final UserTO input, final OIDCLoginResponseTO loginResponse) {
- return input;
- }
-
- @Override
- public UserTO afterCreate(final UserTO input, final OIDCLoginResponseTO loginResponse) {
- return input;
- }
-
- @Override
- public UserPatch beforeUpdate(final UserPatch input, final OIDCLoginResponseTO loginResponse) {
- return input;
- }
-
- @Override
- public UserTO afterUpdate(final UserTO input, final OIDCLoginResponseTO loginResponse) {
- return input;
- }
-
-}
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java
index 196d731..8c79b69 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java
@@ -139,7 +139,7 @@ public class SAML2SPLoader implements SyncopeLoader {
inited = false;
}
- domainsHolder.getDomains().keySet().forEach(domain -> {
+ domainsHolder.getDomains().forEach((domain, datasource) -> {
AuthContextUtils.execWithAuthContext(domain, () -> {
idpDAO.findAll().forEach(idp -> {
try {
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
index 5f7d2b6..a555dc9 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -258,9 +259,10 @@ public class SAML2UserManager {
}
List<SAML2IdPActions> actions = getActions(idp);
- for (SAML2IdPActions action : actions) {
- userTO = action.beforeCreate(userTO, responseTO);
- }
+ userTO = actions.stream().
+ map(action -> action.beforeCreate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userTO);
fill(idp.getKey(), responseTO, userTO);
@@ -274,9 +276,10 @@ public class SAML2UserManager {
Pair<String, List<PropagationStatus>> created = provisioningManager.create(userTO, false, false);
userTO = binder.getUserTO(created.getKey());
- for (SAML2IdPActions action : actions) {
- userTO = action.afterCreate(userTO, responseTO);
- }
+ userTO = actions.stream().
+ map(action -> action.afterCreate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userTO);
return userTO.getUsername();
}
@@ -291,16 +294,18 @@ public class SAML2UserManager {
UserPatch userPatch = AnyOperations.diff(userTO, original, true);
List<SAML2IdPActions> actions = getActions(idp);
- for (SAML2IdPActions action : actions) {
- userPatch = action.beforeUpdate(userPatch, responseTO);
- }
+ userPatch = actions.stream().
+ map(action -> action.beforeUpdate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userPatch);
Pair<UserPatch, List<PropagationStatus>> updated = provisioningManager.update(userPatch, false);
userTO = binder.getUserTO(updated.getLeft().getKey());
- for (SAML2IdPActions action : actions) {
- userTO = action.afterUpdate(userTO, responseTO);
- }
+ userTO = actions.stream().
+ map(action -> action.afterUpdate(responseTO)).
+ reduce(Function.identity(), Function::andThen).
+ apply(userTO);
return userTO.getUsername();
}
diff --git a/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java b/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
index d8ec455..ef3bcb4 100644
--- a/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
+++ b/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
@@ -18,25 +18,26 @@
*/
package org.apache.syncope.core.provisioning.api;
+import java.util.function.Function;
import org.apache.syncope.common.lib.patch.UserPatch;
import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
import org.apache.syncope.common.lib.to.UserTO;
public interface SAML2IdPActions {
- default UserTO beforeCreate(UserTO input, SAML2LoginResponseTO loginResponse) {
- return input;
+ default Function<UserTO, UserTO> beforeCreate(SAML2LoginResponseTO loginResponse) {
+ return Function.identity();
}
- default UserTO afterCreate(UserTO input, SAML2LoginResponseTO loginResponse) {
- return input;
+ default Function<UserTO, UserTO> afterCreate(SAML2LoginResponseTO loginResponse) {
+ return Function.identity();
}
- default UserPatch beforeUpdate(UserPatch input, SAML2LoginResponseTO loginResponse) {
- return input;
+ default Function<UserPatch, UserPatch> beforeUpdate(SAML2LoginResponseTO loginResponse) {
+ return Function.identity();
}
- default UserTO afterUpdate(UserTO input, SAML2LoginResponseTO loginResponse) {
- return input;
+ default Function<UserTO, UserTO> afterUpdate(SAML2LoginResponseTO loginResponse) {
+ return Function.identity();
}
}
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/DateParamConverterProvider.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/DateParamConverterProvider.java
new file mode 100644
index 0000000..123bde8
--- /dev/null
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/DateParamConverterProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.fit.buildtools.cxf;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Date;
+import javax.ws.rs.ext.ParamConverter;
+import javax.ws.rs.ext.ParamConverterProvider;
+import org.apache.commons.lang3.StringUtils;
+
+public class DateParamConverterProvider implements ParamConverterProvider {
+
+ private static class DateParamConverter implements ParamConverter<Date> {
+
+ @Override
+ public Date fromString(final String value) {
+ if (StringUtils.isBlank(value)) {
+ return null;
+ }
+ try {
+ return new Date(Long.valueOf(value));
+ } catch (final NumberFormatException e) {
+ throw new IllegalArgumentException("Unparsable date: " + value, e);
+ }
+ }
+
+ @Override
+ public String toString(final Date value) {
+ return value == null ? null : String.valueOf(value.getTime());
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> ParamConverter<T> getConverter(
+ final Class<T> rawType, final Type genericType, final Annotation[] annotations) {
+
+ if (Date.class.equals(rawType)) {
+ return (ParamConverter<T>) new DateParamConverter();
+ }
+
+ return null;
+ }
+}
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/User.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/User.java
index 18b3c7b..add6f94 100644
--- a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/User.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/User.java
@@ -25,6 +25,12 @@ public class User implements Serializable {
private static final long serialVersionUID = -7906946710921162676L;
+ public enum Status {
+ ACTIVE,
+ INACTIVE;
+
+ }
+
private UUID key;
private String username;
@@ -37,6 +43,8 @@ public class User implements Serializable {
private String email;
+ private Status status;
+
public UUID getKey() {
return key;
}
@@ -85,4 +93,11 @@ public class User implements Serializable {
this.email = email;
}
+ public Status getStatus() {
+ return status;
+ }
+
+ public void setStatus(final Status status) {
+ this.status = status;
+ }
}
diff --git a/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserMetadata.java
similarity index 52%
copy from ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
copy to fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserMetadata.java
index d8ec455..d6591b4 100644
--- a/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserMetadata.java
@@ -16,27 +16,42 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.core.provisioning.api;
+package org.apache.syncope.fit.buildtools.cxf;
-import org.apache.syncope.common.lib.patch.UserPatch;
-import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
-import org.apache.syncope.common.lib.to.UserTO;
+import java.io.Serializable;
+import java.util.Date;
-public interface SAML2IdPActions {
+public class UserMetadata implements Serializable {
- default UserTO beforeCreate(UserTO input, SAML2LoginResponseTO loginResponse) {
- return input;
+ private static final long serialVersionUID = -5448360771141372951L;
+
+ private User user;
+
+ private Date lastChangeDate;
+
+ private boolean deleted;
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(final User user) {
+ this.user = user;
+ }
+
+ public Date getLastChangeDate() {
+ return lastChangeDate;
}
- default UserTO afterCreate(UserTO input, SAML2LoginResponseTO loginResponse) {
- return input;
+ public void setLastChangeDate(final Date lastChangeDate) {
+ this.lastChangeDate = lastChangeDate;
}
- default UserPatch beforeUpdate(UserPatch input, SAML2LoginResponseTO loginResponse) {
- return input;
+ public boolean isDeleted() {
+ return deleted;
}
- default UserTO afterUpdate(UserTO input, SAML2LoginResponseTO loginResponse) {
- return input;
+ public void setDeleted(final boolean deleted) {
+ this.deleted = deleted;
}
}
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 700ff9d..509a44b 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
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.fit.buildtools.cxf;
+import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.ws.rs.Consumes;
@@ -40,6 +41,11 @@ public interface UserService {
List<User> list();
@GET
+ @Path("changelog")
+ @Produces({ MediaType.APPLICATION_JSON })
+ List<UserMetadata> changelog(@QueryParam("from") Date from);
+
+ @GET
@Path("{key}")
@Produces({ MediaType.APPLICATION_JSON })
User read(@PathParam("key") UUID key);
diff --git a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
index 6b04515..321767f 100644
--- a/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
+++ b/fit/build-tools/src/main/java/org/apache/syncope/fit/buildtools/cxf/UserServiceImpl.java
@@ -18,11 +18,14 @@
*/
package org.apache.syncope.fit.buildtools.cxf;
-import java.util.ArrayList;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.NotFoundException;
@@ -34,23 +37,35 @@ import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
- private static final Map<UUID, User> USERS = new HashMap<UUID, User>();
+ private static final Map<UUID, UserMetadata> USERS = new HashMap<>();
@Context
private UriInfo uriInfo;
@Override
public List<User> list() {
- return new ArrayList<>(USERS.values());
+ return USERS.values().stream().
+ filter(meta -> !meta.isDeleted()).
+ map(UserMetadata::getUser).
+ collect(Collectors.toList());
+ }
+
+ @Override
+ public List<UserMetadata> changelog(final Date from) {
+ Stream<UserMetadata> users = USERS.values().stream();
+ if (from != null) {
+ users = users.filter(meta -> meta.getLastChangeDate().after(from));
+ }
+ return users.collect(Collectors.toList());
}
@Override
public User read(final UUID key) {
- User user = USERS.get(key);
- if (user == null) {
+ UserMetadata meta = USERS.get(key);
+ if (meta == null || meta.isDeleted()) {
throw new NotFoundException(key.toString());
}
- return user;
+ return meta.getUser();
}
@Override
@@ -58,66 +73,81 @@ public class UserServiceImpl implements UserService {
if (user.getKey() == null) {
user.setKey(UUID.randomUUID());
}
- if (USERS.containsKey(user.getKey())) {
+ if (user.getStatus() == null) {
+ user.setStatus(User.Status.ACTIVE);
+ }
+
+ UserMetadata meta = USERS.get(user.getKey());
+ if (meta != null && !meta.isDeleted()) {
throw new ClientErrorException("User already exists: " + user.getKey(), Response.Status.CONFLICT);
}
- USERS.put(user.getKey(), user);
+
+ meta = new UserMetadata();
+ meta.setLastChangeDate(new Date());
+ meta.setUser(user);
+ USERS.put(user.getKey(), meta);
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getKey().toString()).build()).build();
}
@Override
public void update(final UUID key, final User updatedUser) {
- if (!USERS.containsKey(key)) {
- throw new NotFoundException(updatedUser.getKey().toString());
+ UserMetadata meta = USERS.get(key);
+ if (meta == null || meta.isDeleted()) {
+ throw new NotFoundException(key.toString());
}
- User user = USERS.get(key);
+
if (updatedUser.getUsername() != null) {
- user.setUsername(updatedUser.getUsername());
+ meta.getUser().setUsername(updatedUser.getUsername());
}
if (updatedUser.getPassword() != null) {
- user.setPassword(updatedUser.getPassword());
+ meta.getUser().setPassword(updatedUser.getPassword());
}
if (updatedUser.getFirstName() != null) {
- user.setFirstName(updatedUser.getFirstName());
+ meta.getUser().setFirstName(updatedUser.getFirstName());
}
if (updatedUser.getSurname() != null) {
- user.setSurname(updatedUser.getSurname());
+ meta.getUser().setSurname(updatedUser.getSurname());
}
if (updatedUser.getEmail() != null) {
- user.setEmail(updatedUser.getEmail());
+ meta.getUser().setEmail(updatedUser.getEmail());
+ }
+ if (updatedUser.getStatus() != null) {
+ meta.getUser().setStatus(updatedUser.getStatus());
}
+
+ meta.setLastChangeDate(new Date());
}
@Override
public void delete(final UUID key) {
- if (!USERS.containsKey(key)) {
+ UserMetadata meta = USERS.get(key);
+ if (meta == null || meta.isDeleted()) {
throw new NotFoundException(key.toString());
}
- USERS.remove(key);
+
+ meta.setDeleted(true);
+ meta.setLastChangeDate(new Date());
}
@Override
public User authenticate(final String username, final String password) {
- User user = null;
- for (User entry : USERS.values()) {
- if (username.equals(entry.getUsername())) {
- user = entry;
- }
- }
- if (user == null) {
+ Optional<User> user = USERS.values().stream().
+ filter(meta -> !meta.isDeleted() && username.equals(meta.getUser().getUsername())).
+ findFirst().map(UserMetadata::getUser);
+
+ if (!user.isPresent()) {
throw new NotFoundException(username);
}
- if (!password.equals(user.getPassword())) {
+ if (!password.equals(user.get().getPassword())) {
throw new ForbiddenException();
}
- return user;
+ return user.get();
}
@Override
public void clear() {
USERS.clear();
}
-
}
diff --git a/fit/build-tools/src/main/resources/cxfContext.xml b/fit/build-tools/src/main/resources/cxfContext.xml
index 7384e55..cb0f122 100644
--- a/fit/build-tools/src/main/resources/cxfContext.xml
+++ b/fit/build-tools/src/main/resources/cxfContext.xml
@@ -59,13 +59,17 @@ under the License.
<property name="customizer" ref="openApiCustomizer"/>
</bean>
-
+
<bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider"/>
+
+ <bean id="dateParameterConverterProvider" class="org.apache.syncope.fit.buildtools.cxf.DateParamConverterProvider"/>
+
<jaxrs:server id="restProvisioning" address="/rest"
basePackages="org.apache.syncope.fit.buildtools.cxf"
staticSubresourceResolution="true">
<jaxrs:providers>
<ref bean="jsonProvider"/>
+ <ref bean="dateParameterConverterProvider"/>
</jaxrs:providers>
<jaxrs:features>
<ref bean="openapiFeature"/>
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index a7ee903..9cc0a85 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -132,6 +132,7 @@ public class ITImplementationLookup implements ImplementationLookup {
{
put(DummyPullCorrelationRuleConf.class, DummyPullCorrelationRule.class);
put(DefaultPullCorrelationRuleConf.class, DefaultPullCorrelationRule.class);
+ put(LinkedAccountSamplePullCorrelationRuleConf.class, LinkedAccountSamplePullCorrelationRule.class);
}
};
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRule.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRule.java
new file mode 100644
index 0000000..fed103d
--- /dev/null
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRule.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.fit.core.reference;
+
+import java.util.Optional;
+import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.PullCorrelationRuleConfClass;
+import org.apache.syncope.core.persistence.api.dao.PullMatch;
+import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.springframework.util.CollectionUtils;
+
+@PullCorrelationRuleConfClass(LinkedAccountSamplePullCorrelationRuleConf.class)
+public class LinkedAccountSamplePullCorrelationRule implements PullCorrelationRule {
+
+ public static final String VIVALDI_KEY = "b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee";
+
+ @Override
+ public SearchCond getSearchCond(final SyncDelta syncDelta, final Provision provision) {
+ AttributeCond cond = new AttributeCond();
+
+ Attribute email = syncDelta.getObject().getAttributeByName("email");
+ if (email != null && !CollectionUtils.isEmpty(email.getValue())) {
+ cond.setSchema("email");
+ cond.setType(AttributeCond.Type.EQ);
+ cond.setExpression(email.getValue().get(0).toString());
+ } else {
+ cond.setSchema("");
+ }
+
+ return SearchCond.getLeafCond(cond);
+ }
+
+ @Override
+ public PullMatch matching(final Any<?> any, final SyncDelta syncDelta, final Provision provision) {
+ // if match with internal user vivaldi was found but firstName is different, update linked account
+ // instead of updating user
+ Attribute firstName = syncDelta.getObject().getAttributeByName("firstName");
+ if (VIVALDI_KEY.equals(any.getKey())
+ && firstName != null && !CollectionUtils.isEmpty(firstName.getValue())
+ && !"Antonio".equals(firstName.getValue().get(0).toString())) {
+
+ return new PullMatch.Builder().
+ linkingUserKey(VIVALDI_KEY).
+ matchTarget(PullMatch.MatchTarget.LINKED_ACCOUNT).build();
+ }
+
+ return PullCorrelationRule.super.matching(any, syncDelta, provision);
+ }
+
+ @Override
+ public Optional<PullMatch> unmatching(final SyncDelta syncDelta, final Provision provision) {
+ // if no match with internal user was found, link account to vivaldi instead of creating new user
+ return Optional.of(new PullMatch.Builder().
+ linkingUserKey(VIVALDI_KEY).
+ matchTarget(PullMatch.MatchTarget.LINKED_ACCOUNT).build());
+ }
+}
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRuleConf.java
similarity index 60%
copy from client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy
copy to fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRuleConf.java
index df22aa2..4981c88 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRuleConf.java
@@ -16,23 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-import groovy.transform.CompileStatic
-import org.apache.syncope.common.lib.patch.AnyPatch
-import org.apache.syncope.common.lib.patch.AttrPatch
-import org.apache.syncope.common.lib.to.AnyTO
-import org.apache.syncope.common.lib.to.AttrTO
-import org.apache.syncope.core.provisioning.api.LogicActions
+package org.apache.syncope.fit.core.reference;
-@CompileStatic
-class MyLogicActions implements LogicActions {
-
- @Override
- <A extends AnyTO> A beforeCreate(final A input) {
- return input;
- }
+import org.apache.syncope.common.lib.policy.AbstractCorrelationRuleConf;
+import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
+
+public class LinkedAccountSamplePullCorrelationRuleConf
+ extends AbstractCorrelationRuleConf implements PullCorrelationRuleConf {
+
+ private static final long serialVersionUID = -958386962492907926L;
- @Override
- <M extends AnyPatch> M beforeUpdate(final M input) {
- return input;
- }
}
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 020db67..ca14447 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
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
+import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@@ -122,6 +123,8 @@ public abstract class AbstractITCase {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractITCase.class);
+ protected static final ObjectMapper MAPPER = new ObjectMapper();
+
protected static final String ADMIN_UNAME = "admin";
protected static final String ADMIN_PWD = "password";
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java
index ac8a001..8763f29 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java
@@ -25,7 +25,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -69,8 +68,6 @@ import org.junit.jupiter.api.Test;
public class BatchITCase extends AbstractITCase {
- private static final ObjectMapper MAPPER = new ObjectMapper();
-
private String requestBody(final String boundary) throws JsonProcessingException, JAXBException {
List<BatchRequestItem> reqItems = new ArrayList<>();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java
index c6c4311..2bb5c22 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java
@@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
@@ -63,8 +62,6 @@ import org.junit.jupiter.api.Test;
public class DynRealmITCase extends AbstractITCase {
- private static final ObjectMapper MAPPER = new ObjectMapper();
-
@Test
public void misc() {
DynRealmTO dynRealm = null;
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 c6476e3..b7b3320 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,42 +18,60 @@
*/
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.assertFalse;
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 com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.List;
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.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.common.lib.SyncopeClientException;
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.policy.PullPolicyTO;
import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ExecTO;
+import org.apache.syncope.common.lib.to.ImplementationTO;
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.PullTaskTO;
import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
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.ImplementationEngine;
+import org.apache.syncope.common.lib.types.ImplementationType;
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.ResourceOperation;
import org.apache.syncope.common.lib.types.TaskType;
import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.beans.TaskQuery;
import org.apache.syncope.common.rest.api.service.TaskService;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.apache.syncope.fit.AbstractITCase;
+import org.apache.syncope.fit.core.reference.LinkedAccountSamplePullCorrelationRule;
+import org.apache.syncope.fit.core.reference.LinkedAccountSamplePullCorrelationRuleConf;
import org.junit.jupiter.api.Test;
public class LinkedAccountITCase extends AbstractITCase {
@@ -199,17 +217,18 @@ public class LinkedAccountITCase extends AbstractITCase {
pwdCipherAlgo.getValues().set(0, "AES");
configurationService.set(pwdCipherAlgo);
+ String userKey = null;
+ String connObjectKeyValue = UUID.randomUUID().toString();
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();
+ userKey = user.getKey();
assertNotNull(userKey);
assertNotEquals(userKey, connObjectKeyValue);
@@ -273,6 +292,216 @@ public class LinkedAccountITCase extends AbstractITCase {
// restore initial cipher algorithm
pwdCipherAlgo.getValues().set(0, origpwdCipherAlgo);
configurationService.set(pwdCipherAlgo);
+
+ // delete user and accounts
+ if (userKey != null) {
+ WebClient.create(BUILD_TOOLS_ADDRESS + "/rest/users/" + connObjectKeyValue).delete();
+ WebClient.create(BUILD_TOOLS_ADDRESS + "/rest/users/" + userKey).delete();
+
+ userService.delete(userKey);
+ }
+ }
+ }
+
+ @Test
+ public void pull() {
+ // -----------------------------
+ // Add a custom policy with correlation rule
+ // -----------------------------
+ ResourceTO restResource = resourceService.read(RESOURCE_NAME_REST);
+ if (restResource.getPullPolicy() == null) {
+ ImplementationTO rule = null;
+ try {
+ rule = implementationService.read(
+ ImplementationType.PULL_CORRELATION_RULE, "LinkedAccountSamplePullCorrelationRule");
+ } catch (SyncopeClientException e) {
+ if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
+ rule = new ImplementationTO();
+ rule.setKey("LinkedAccountSamplePullCorrelationRule");
+ rule.setEngine(ImplementationEngine.JAVA);
+ rule.setType(ImplementationType.PULL_CORRELATION_RULE);
+ rule.setBody(POJOHelper.serialize(new LinkedAccountSamplePullCorrelationRuleConf()));
+ Response response = implementationService.create(rule);
+ rule = implementationService.read(
+ rule.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+ assertNotNull(rule.getKey());
+ }
+ }
+ assertNotNull(rule);
+
+ PullPolicyTO policy = new PullPolicyTO();
+ policy.setDescription("Linked Account sample Pull policy");
+ policy.getCorrelationRules().put(AnyTypeKind.USER.name(), rule.getKey());
+ Response response = policyService.create(PolicyType.PULL, policy);
+ policy = policyService.read(PolicyType.PULL, response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+ assertNotNull(policy.getKey());
+
+ restResource.setPullPolicy(policy.getKey());
+ resourceService.update(restResource);
+ }
+
+ // -----------------------------
+ // -----------------------------
+ // Add a pull task
+ // -----------------------------
+ String pullTaskKey;
+
+ PagedResult<PullTaskTO> tasks = taskService.search(
+ new TaskQuery.Builder(TaskType.PULL).resource(RESOURCE_NAME_REST).build());
+ if (tasks.getTotalCount() > 0) {
+ pullTaskKey = tasks.getResult().get(0).getKey();
+ } else {
+ PullTaskTO task = new PullTaskTO();
+ task.setDestinationRealm(SyncopeConstants.ROOT_REALM);
+ task.setName("Linked Account Pull Task");
+ task.setActive(true);
+ task.setResource(RESOURCE_NAME_REST);
+ task.setPullMode(PullMode.INCREMENTAL);
+ task.setPerformCreate(true);
+ task.setPerformUpdate(true);
+ task.setPerformDelete(true);
+ task.setSyncStatus(true);
+
+ Response response = taskService.create(TaskType.PULL, task);
+ task = taskService.read(TaskType.PULL, response.getHeaderString(RESTHeaders.RESOURCE_KEY), false);
+ assertNotNull(task.getKey());
+ pullTaskKey = task.getKey();
+ }
+ assertNotNull(pullTaskKey);
+ // -----------------------------
+
+ // 1. create REST users
+ WebClient webClient = WebClient.create(BUILD_TOOLS_ADDRESS + "/rest/users").
+ accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE);
+
+ ObjectNode user = MAPPER.createObjectNode();
+ user.put("username", "linkedaccount1");
+ user.put("password", "Password123");
+ user.put("firstName", "Pasquale");
+ user.put("surname", "Vivaldi");
+ user.put("email", "vivaldi@syncope.org");
+
+ Response response = webClient.post(user.toString());
+ assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
+ String user1Key = StringUtils.substringAfterLast(response.getHeaderString(HttpHeaders.LOCATION), "/");
+ assertNotNull(user1Key);
+
+ user = MAPPER.createObjectNode();
+ user.put("username", "vivaldi");
+ user.put("password", "Password123");
+ user.put("firstName", "Giovannino");
+ user.put("surname", "Vivaldi");
+ user.put("email", "vivaldi@syncope.org");
+
+ response = webClient.post(user.toString());
+ assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
+ String user2Key = StringUtils.substringAfterLast(response.getHeaderString(HttpHeaders.LOCATION), "/");
+ assertNotNull(user2Key);
+
+ user = MAPPER.createObjectNode();
+ user.put("username", "not.vivaldi");
+ user.put("password", "Password123");
+ user.put("email", "not.vivaldi@syncope.org");
+
+ response = webClient.post(user.toString());
+ assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
+ String user3Key = StringUtils.substringAfterLast(response.getHeaderString(HttpHeaders.LOCATION), "/");
+ assertNotNull(user3Key);
+
+ // 2. execute pull task and verify linked accounts were pulled
+ try {
+ List<LinkedAccountTO> accounts = userService.read("vivaldi").getLinkedAccounts();
+ assertTrue(accounts.isEmpty());
+
+ ExecTO exec = AbstractTaskITCase.execProvisioningTask(taskService, TaskType.PULL, pullTaskKey, 50, false);
+ assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(exec.getStatus()));
+
+ accounts = userService.read("vivaldi").getLinkedAccounts();
+ assertEquals(3, accounts.size());
+
+ Optional<LinkedAccountTO> firstAccount = accounts.stream().
+ filter(account -> user1Key.equals(account.getConnObjectKeyValue())).
+ findFirst();
+ assertTrue(firstAccount.isPresent());
+ assertFalse(firstAccount.get().isSuspended());
+ assertEquals(RESOURCE_NAME_REST, firstAccount.get().getResource());
+ assertEquals("linkedaccount1", firstAccount.get().getUsername());
+ assertEquals("Pasquale", firstAccount.get().getPlainAttr("firstname").get().getValues().get(0));
+
+ Optional<LinkedAccountTO> secondAccount = accounts.stream().
+ filter(account -> user2Key.equals(account.getConnObjectKeyValue())).
+ findFirst();
+ assertTrue(secondAccount.isPresent());
+ assertFalse(secondAccount.get().isSuspended());
+ assertEquals(RESOURCE_NAME_REST, secondAccount.get().getResource());
+ assertNull(secondAccount.get().getUsername());
+ assertEquals("Giovannino", secondAccount.get().getPlainAttr("firstname").get().getValues().get(0));
+
+ Optional<LinkedAccountTO> thirdAccount = accounts.stream().
+ filter(account -> user3Key.equals(account.getConnObjectKeyValue())).
+ filter(account -> "not.vivaldi".equals(account.getUsername())).
+ findFirst();
+ assertTrue(thirdAccount.isPresent());
+ assertFalse(thirdAccount.get().isSuspended());
+ assertEquals(RESOURCE_NAME_REST, thirdAccount.get().getResource());
+ assertEquals("not.vivaldi", thirdAccount.get().getUsername());
+
+ // 3. update / remove REST users
+ response = webClient.path(user1Key).delete();
+ assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
+
+ user = MAPPER.createObjectNode();
+ user.put("username", "linkedaccount2");
+ response = webClient.replacePath(user2Key).put(user.toString());
+ assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
+
+ user = MAPPER.createObjectNode();
+ user.put("status", "INACTIVE");
+ response = webClient.replacePath(user3Key).put(user.toString());
+ assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
+
+ // 4. execute pull task again and verify linked accounts were pulled
+ exec = AbstractTaskITCase.execProvisioningTask(taskService, TaskType.PULL, pullTaskKey, 50, false);
+ assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(exec.getStatus()));
+
+ accounts = userService.read("vivaldi").getLinkedAccounts();
+ assertEquals(2, accounts.size());
+
+ firstAccount = accounts.stream().
+ filter(account -> user1Key.equals(account.getConnObjectKeyValue())).
+ findFirst();
+ assertFalse(firstAccount.isPresent());
+
+ secondAccount = accounts.stream().
+ filter(account -> user2Key.equals(account.getConnObjectKeyValue())).
+ findFirst();
+ assertTrue(secondAccount.isPresent());
+ assertFalse(secondAccount.get().isSuspended());
+ assertEquals(user2Key, secondAccount.get().getConnObjectKeyValue());
+ assertEquals("linkedaccount2", secondAccount.get().getUsername());
+
+ thirdAccount = accounts.stream().
+ filter(account -> "not.vivaldi".equals(account.getUsername())).
+ findFirst();
+ assertTrue(thirdAccount.isPresent());
+ assertTrue(thirdAccount.get().isSuspended());
+ assertEquals(user3Key, thirdAccount.get().getConnObjectKeyValue());
+ } finally {
+ // clean up
+ UserPatch patch = new UserPatch();
+ patch.setKey(LinkedAccountSamplePullCorrelationRule.VIVALDI_KEY);
+ patch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
+ operation(PatchOperation.DELETE).
+ linkedAccountTO(new LinkedAccountTO.Builder(RESOURCE_NAME_REST, user2Key).build()).
+ build());
+ patch.getLinkedAccounts().add(new LinkedAccountPatch.Builder().
+ operation(PatchOperation.DELETE).
+ linkedAccountTO(new LinkedAccountTO.Builder(RESOURCE_NAME_REST, user3Key).build()).
+ build());
+ userService.update(patch);
+
+ webClient.replacePath(user2Key).delete();
+ webClient.replacePath(user3Key).delete();
}
}
}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OpenAPIITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OpenAPIITCase.java
index d2141f7..75ca075 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OpenAPIITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OpenAPIITCase.java
@@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import javax.ws.rs.core.MediaType;
@@ -41,7 +40,7 @@ public class OpenAPIITCase extends AbstractITCase {
Response response = webClient.get();
assumeTrue(response.getStatus() == 200);
- JsonNode tree = new ObjectMapper().readTree((InputStream) response.getEntity());
+ JsonNode tree = MAPPER.readTree((InputStream) response.getEntity());
assertNotNull(tree);
JsonNode info = tree.get("info");
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 98b2841..5b499e0 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
@@ -88,7 +88,6 @@ import org.apache.syncope.common.rest.api.beans.RemediationQuery;
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.core.provisioning.api.pushpull.ProvisioningReport;
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;
@@ -487,26 +486,33 @@ public class PullTaskITCase extends AbstractTaskITCase {
ProvisionTO provision = resource.getProvision("PRINTER").get();
assertNotNull(provision);
+ ImplementationTO transformer = null;
+ try {
+ transformer = implementationService.read(
+ ImplementationType.ITEM_TRANSFORMER, "PrefixItemTransformer");
+ } catch (SyncopeClientException e) {
+ if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
+ transformer = new ImplementationTO();
+ transformer.setKey("PrefixItemTransformer");
+ transformer.setEngine(ImplementationEngine.GROOVY);
+ transformer.setType(ImplementationType.ITEM_TRANSFORMER);
+ transformer.setBody(IOUtils.toString(
+ getClass().getResourceAsStream("/PrefixItemTransformer.groovy"), StandardCharsets.UTF_8));
+ Response response = implementationService.create(transformer);
+ transformer = implementationService.read(
+ transformer.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+ assertNotNull(transformer.getKey());
+ }
+ }
+ assertNotNull(transformer);
+
ItemTO mappingItem = provision.getMapping().getItems().stream().
filter(object -> "location".equals(object.getIntAttrName())).findFirst().get();
assertNotNull(mappingItem);
-
- final String prefix = "PREFIX_";
-
- ImplementationTO transformer = new ImplementationTO();
- transformer.setKey("PrefixItemTransformer");
- transformer.setEngine(ImplementationEngine.GROOVY);
- transformer.setType(ImplementationType.ITEM_TRANSFORMER);
- transformer.setBody(IOUtils.toString(
- getClass().getResourceAsStream("/PrefixItemTransformer.groovy"), StandardCharsets.UTF_8));
- Response response = implementationService.create(transformer);
- transformer = implementationService.read(
- transformer.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
- assertNotNull(transformer);
-
mappingItem.getTransformers().clear();
mappingItem.getTransformers().add(transformer.getKey());
+ final String prefix = "PREFIX_";
try {
resourceService.update(resource);
resourceService.removeSyncToken(resource.getKey(), provision.getAnyType());
diff --git a/fit/core-reference/src/test/resources/DoubleValueLogicActions.groovy b/fit/core-reference/src/test/resources/DoubleValueLogicActions.groovy
index a7bc87d..3557204 100644
--- a/fit/core-reference/src/test/resources/DoubleValueLogicActions.groovy
+++ b/fit/core-reference/src/test/resources/DoubleValueLogicActions.groovy
@@ -1,3 +1,4 @@
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -17,6 +18,7 @@
* under the License.
*/
import groovy.transform.CompileStatic
+import java.util.function.Function
import org.apache.syncope.common.lib.patch.AnyPatch
import org.apache.syncope.common.lib.patch.AttrPatch
import org.apache.syncope.common.lib.to.AnyTO
@@ -32,42 +34,50 @@ class DoubleValueLogicActions implements LogicActions {
private static final String NAME = "makeItDouble";
@Override
- <A extends AnyTO> A beforeCreate(final A input) {
- for (AttrTO attr : input.getPlainAttrs()) {
- if (NAME.equals(attr.getSchema())) {
- List<String> values = new ArrayList<String>(attr.getValues().size());
- for (String value : attr.getValues()) {
- try {
- values.add(String.valueOf(2 * Long.parseLong(value)));
- } catch (NumberFormatException e) {
- // ignore
+ <A extends AnyTO> Function<A, A> beforeCreate() {
+ Function function = {
+ A input ->
+ for (AttrTO attr : input.getPlainAttrs()) {
+ if (NAME.equals(attr.getSchema())) {
+ List<String> values = new ArrayList<String>(attr.getValues().size());
+ for (String value : attr.getValues()) {
+ try {
+ values.add(String.valueOf(2 * Long.parseLong(value)));
+ } catch (NumberFormatException e) {
+ // ignore
+ }
}
+ attr.getValues().clear();
+ attr.getValues().addAll(values);
}
- attr.getValues().clear();
- attr.getValues().addAll(values);
}
- }
- return input;
+ return input;
+ }
+ return function;
}
@Override
- <M extends AnyPatch> M beforeUpdate(final M input) {
- for (AttrPatch patch : input.getPlainAttrs()) {
- if (NAME.equals(patch.getAttrTO().getSchema())) {
- List<String> values = new ArrayList<String>(patch.getAttrTO().getValues().size());
- for (String value : patch.getAttrTO().getValues()) {
- try {
- values.add(String.valueOf(2 * Long.parseLong(value)));
- } catch (NumberFormatException e) {
- // ignore
+ <P extends AnyPatch> Function<P, P> beforeUpdate() {
+ Function function = {
+ P input ->
+ for (AttrPatch patch : input.getPlainAttrs()) {
+ if (NAME.equals(patch.getAttrTO().getSchema())) {
+ List<String> values = new ArrayList<String>(patch.getAttrTO().getValues().size());
+ for (String value : patch.getAttrTO().getValues()) {
+ try {
+ values.add(String.valueOf(2 * Long.parseLong(value)));
+ } catch (NumberFormatException e) {
+ // ignore
+ }
}
+ patch.getAttrTO().getValues().clear();
+ patch.getAttrTO().getValues().addAll(values);
}
- patch.getAttrTO().getValues().clear();
- patch.getAttrTO().getValues().addAll(values);
}
- }
- return input;
+ return input;
+ }
+ return function;
}
}
diff --git a/fit/core-reference/src/test/resources/rest/SearchScript.groovy b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
index a6f5abe..0631654 100644
--- a/fit/core-reference/src/test/resources/rest/SearchScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/SearchScript.groovy
@@ -54,11 +54,10 @@ def buildConnectorObject(node) {
return [
__UID__:node.get("key").textValue(),
__NAME__:node.get("key").textValue(),
+ __ENABLE__:node.get("status").textValue().equals("ACTIVE"),
+ __PASSWORD__:new GuardedString(node.get("password").textValue().toCharArray()),
key:node.get("key").textValue(),
username:node.get("username").textValue(),
- password:node.has("password") && node.get("password").textValue() != null
- ? new GuardedString(node.get("password").textValue().toCharArray())
- : null,
firstName:node.get("firstName").textValue(),
surname:node.get("surname").textValue(),
email:node.get("email").textValue()
diff --git a/fit/core-reference/src/test/resources/rest/SyncScript.groovy b/fit/core-reference/src/test/resources/rest/SyncScript.groovy
index 1676a01..6911614 100644
--- a/fit/core-reference/src/test/resources/rest/SyncScript.groovy
+++ b/fit/core-reference/src/test/resources/rest/SyncScript.groovy
@@ -54,9 +54,10 @@ def buildConnectorObject(node) {
return [
__UID__:node.get("key").textValue(),
__NAME__:node.get("key").textValue(),
+ __ENABLE__:node.get("status").textValue().equals("ACTIVE"),
+ __PASSWORD__:new GuardedString(node.get("password").textValue().toCharArray()),
key:node.get("key").textValue(),
username:node.get("username").textValue(),
- password:new GuardedString(node.get("password").textValue().toCharArray()),
firstName:node.get("firstName").textValue(),
surname:node.get("surname").textValue(),
email:node.get("email").textValue()
@@ -84,17 +85,40 @@ if (action.equalsIgnoreCase("GET_LATEST_SYNC_TOKEN")) {
switch (objectClass) {
case "__ACCOUNT__":
- webClient.path("/users");
+ webClient.path("/users/changelog");
+ if (token != null) {
+ webClient.query("from", token.toString());
+ }
+
+ log.ok("Sending GET to {0}", webClient.getCurrentURI().toASCIIString());
+
Response response = webClient.get();
+
+ log.ok("CHANGELOG response: {0} {1}", response.getStatus(), response.getHeaders());
+
+ if (response.getStatus() != 200) {
+ throw new RuntimeException("Unexpected response from server: "
+ + response.getStatus() + " " + response.getHeaders());
+ }
+
ArrayNode node = mapper.readTree(response.getEntity());
for (i = 0; i < node.size(); i++) {
- result.add([
- operation:"CREATE_OR_UPDATE",
- uid:node.get(i).get("key").textValue(),
- token:new Date().getTime(),
- attributes:buildConnectorObject(node.get(i))
- ]);
+ if (node.get(i).get("deleted").booleanValue()) {
+ result.add([
+ operation:"DELETE",
+ uid:node.get(i).get("user").get("key").textValue(),
+ token:node.get(i).get("lastChangeDate").longValue(),
+ attributes:[]
+ ]);
+ } else {
+ result.add([
+ operation:"CREATE_OR_UPDATE",
+ uid:node.get(i).get("user").get("key").textValue(),
+ token:node.get(i).get("lastChangeDate").longValue(),
+ attributes:buildConnectorObject(node.get(i).get("user"))
+ ]);
+ }
}
break;
}