This is an automated email from the ASF dual-hosted git repository.
sgarofalo pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/3_0_X by this push:
new fb1819c40c [SYNCOPE-1926] optimize user updates on SCIM PATCH (#1225)
fb1819c40c is described below
commit fb1819c40c06b04ae331407c0142456a03418d4c
Author: Samuel Garofalo <[email protected]>
AuthorDate: Tue Nov 4 08:58:47 2025 +0100
[SYNCOPE-1926] optimize user updates on SCIM PATCH (#1225)
---
.../apache/syncope/core/logic/SCIMDataBinder.java | 777 +++++++++++++++------
.../syncope/core/logic/SCIMDataBinderTest.java | 275 +++++++-
.../scimv2/cxf/service/SCIMUserServiceImpl.java | 13 +-
3 files changed, 848 insertions(+), 217 deletions(-)
diff --git
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
index 1518675eb0..63fe83021d 100644
---
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
+++
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
@@ -26,12 +26,13 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.common.lib.AnyOperations;
import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.EntityTOUtils;
import org.apache.syncope.common.lib.SyncopeConstants;
@@ -40,8 +41,10 @@ import org.apache.syncope.common.lib.request.AnyObjectUR;
import org.apache.syncope.common.lib.request.AttrPatch;
import org.apache.syncope.common.lib.request.GroupCR;
import org.apache.syncope.common.lib.request.GroupUR;
+import org.apache.syncope.common.lib.request.MembershipUR;
import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.request.StatusR;
+import org.apache.syncope.common.lib.request.StringPatchItem;
import org.apache.syncope.common.lib.request.StringReplacePatchItem;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
@@ -55,6 +58,7 @@ import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.StatusRType;
import org.apache.syncope.core.logic.scim.SCIMConfManager;
@@ -63,6 +67,7 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
import org.apache.syncope.core.spring.security.AuthDataAccessor;
@@ -75,6 +80,7 @@ import
org.apache.syncope.ext.scimv2.api.data.SCIMComplexValue;
import org.apache.syncope.ext.scimv2.api.data.SCIMEnterpriseInfo;
import org.apache.syncope.ext.scimv2.api.data.SCIMExtensionInfo;
import org.apache.syncope.ext.scimv2.api.data.SCIMGroup;
+import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOp;
import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation;
import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
import org.apache.syncope.ext.scimv2.api.data.SCIMUserAddress;
@@ -558,6 +564,292 @@ public class SCIMDataBinder {
new
Attr.Builder(conf.getValue()).value(value.getValue()).build())));
}
+ protected <E extends Enum<?>> void setAttribute(
+ final Set<Attr> attrs,
+ final Set<AttrPatch> attrPatches,
+ final List<SCIMComplexConf<E>> confs,
+ final List<SCIMComplexValue> values) {
+
+ values.stream().filter(value -> value.getType() != null).forEach(value
-> confs.stream().
+ filter(object ->
value.getType().equals(object.getType().name())
+ && attrPatches.stream().noneMatch(attrPatch ->
+
attrPatch.getAttr().getSchema().equals(object.getValue()))).findFirst().
+ ifPresent(conf -> attrs.add(
+ new
Attr.Builder(conf.getValue()).value(value.getValue()).build())));
+ }
+
+ public void populateUserUR(
+ final UserUR userUR,
+ final UserTO before,
+ final SCIMUser user,
+ final Collection<String> resources,
+ final SCIMPatchOperation op) {
+ SCIMConf conf = confManager.get();
+
+ if (!SyncopeConstants.ROOT_REALM.equals(before.getRealm())) {
+ userUR.setRealm(new StringReplacePatchItem.Builder()
+
.value(SyncopeConstants.ROOT_REALM).operation(PatchOperation.ADD_REPLACE).build());
+ }
+
+ if (StringUtils.isNotBlank(user.getPassword())) {
+ userUR.setPassword(new PasswordPatch.Builder()
+
.value(user.getPassword()).resources(resources).operation(PatchOperation.ADD_REPLACE).build());
+ }
+
+ if (StringUtils.isNotBlank(user.getUserName()) &&
!user.getUserName().equals(before.getUsername())) {
+ userUR.setUsername(new StringReplacePatchItem.Builder()
+
.value(user.getUserName()).operation(PatchOperation.ADD_REPLACE).build());
+ }
+
+ if (conf.getUserConf() != null) {
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getExternalId(),
+ user.getExternalId(),
+ op);
+
+ if (conf.getUserConf().getName() != null && user.getName() !=
null) {
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getName().getFamilyName(),
+ user.getName().getFamilyName(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getName().getFormatted(),
+ user.getName().getFormatted(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getName().getGivenName(),
+ user.getName().getGivenName(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getName().getHonorificPrefix(),
+ user.getName().getHonorificPrefix(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getName().getHonorificSuffix(),
+ user.getName().getHonorificSuffix(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getName().getMiddleName(),
+ user.getName().getMiddleName(),
+ op);
+ }
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getDisplayName(),
+ user.getDisplayName(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getNickName(),
+ user.getNickName(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getProfileUrl(),
+ user.getProfileUrl(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getTitle(),
+ user.getTitle(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getUserType(),
+ user.getUserType(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getPreferredLanguage(),
+ user.getPreferredLanguage(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getLocale(),
+ user.getLocale(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getTimezone(),
+ user.getTimezone(),
+ op);
+
+ setAttribute(
+ before.getPlainAttrs(), userUR.getPlainAttrs(),
conf.getUserConf().getEmails(), user.getEmails());
+ setAttribute(
+ before.getPlainAttrs(),
+ userUR.getPlainAttrs(),
+ conf.getUserConf().getPhoneNumbers(),
+ user.getPhoneNumbers());
+ setAttribute(before.getPlainAttrs(), userUR.getPlainAttrs(),
conf.getUserConf().getIms(), user.getIms());
+ setAttribute(
+ before.getPlainAttrs(), userUR.getPlainAttrs(),
conf.getUserConf().getPhotos(), user.getPhotos());
+
+ user.getAddresses().stream().filter(address -> address.getType()
!= null).
+ forEach(address ->
conf.getUserConf().getAddresses().stream().
+ filter(object ->
address.getType().equals(object.getType().name())).findFirst().
+ ifPresent(addressConf -> {
+ setAttribute(
+ before,
+ userUR,
+ addressConf.getFormatted(),
+ address.getFormatted(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ addressConf.getStreetAddress(),
+ address.getStreetAddress(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ addressConf.getLocality(),
+ address.getLocality(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ addressConf.getRegion(),
+ address.getRegion(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ addressConf.getPostalCode(),
+ address.getPostalCode(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ addressConf.getCountry(),
+ address.getCountry(),
+ op);
+ }));
+
+ for (int i = 0; i < user.getX509Certificates().size(); i++) {
+ Value certificate = user.getX509Certificates().get(i);
+ if (conf.getUserConf().getX509Certificates().size() > i) {
+ setAttribute(
+ before,
+ userUR,
+ conf.getUserConf().getX509Certificates().get(i),
+ certificate.getValue(),
+ op);
+ }
+ }
+ }
+
+ if (conf.getEnterpriseUserConf() != null && user.getEnterpriseInfo()
!= null) {
+ setAttribute(
+ before,
+ userUR,
+ conf.getEnterpriseUserConf().getEmployeeNumber(),
+ user.getEnterpriseInfo().getEmployeeNumber(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getEnterpriseUserConf().getCostCenter(),
+ user.getEnterpriseInfo().getCostCenter(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getEnterpriseUserConf().getOrganization(),
+ user.getEnterpriseInfo().getOrganization(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getEnterpriseUserConf().getDivision(),
+ user.getEnterpriseInfo().getDivision(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+ conf.getEnterpriseUserConf().getDepartment(),
+ user.getEnterpriseInfo().getDepartment(),
+ op);
+
+ setAttribute(
+ before,
+ userUR,
+
Optional.ofNullable(conf.getEnterpriseUserConf().getManager()).
+ map(SCIMManagerConf::getKey).orElse(null),
+ Optional.ofNullable(user.getEnterpriseInfo().getManager()).
+ map(SCIMUserManager::getValue).orElse(null),
+ op);
+ }
+
+ if (conf.getExtensionUserConf() != null && user.getExtensionInfo() !=
null) {
+ conf.getExtensionUserConf().asMap().forEach((scimAttr,
syncopeAttr) -> setAttribute(
+ before, userUR, syncopeAttr,
user.getExtensionInfo().getAttributes().get(scimAttr), op));
+ }
+
+ user.getGroups().forEach(group -> {
+ if (before.getMembership(group.getValue()).isEmpty()
+ && userUR.getMemberships().stream().noneMatch(membershipUR
->
+ membershipUR.getGroup().equals(group.getValue()))) {
+ userUR.getMemberships().add(new
MembershipUR.Builder(group.getValue())
+ .operation(PatchOperation.ADD_REPLACE).build());
+ }
+ });
+
+ user.getRoles().forEach(role -> {
+ if (!before.getRoles().contains(role.getValue())
+ && userUR.getRoles().stream().noneMatch(roleUR ->
+ roleUR.getValue().equals(role.getValue()))) {
+ userUR.getRoles().add(new StringPatchItem.Builder()
+
.value(role.getValue()).operation(PatchOperation.ADD_REPLACE).build());
+ }
+ });
+ }
+
public UserTO toUserTO(final SCIMUser user, final boolean checkSchemas) {
SCIMConf conf = confManager.get();
@@ -768,6 +1060,44 @@ public class SCIMDataBinder {
return userCR;
}
+ protected void setAttribute(
+ final UserTO before,
+ final UserUR userUR,
+ final String schema,
+ final String value,
+ final SCIMPatchOperation op) {
+ if (schema == null || value == null) {
+ return;
+ }
+ switch (schema) {
+ case "username":
+ if (!value.equals(before.getUsername()) &&
userUR.getUsername() == null) {
+ userUR.setUsername(new StringReplacePatchItem.Builder()
+
.value(value).operation(PatchOperation.ADD_REPLACE).build());
+ }
+ break;
+
+ default:
+ if ((before.getPlainAttr(schema).isEmpty()
+ ||
!value.equals(before.getPlainAttr(schema).get().getValues().get(0)))
+ && userUR.getPlainAttrs().stream().noneMatch(attrPatch
->
+ attrPatch.getAttr().getSchema().equals(schema))
+ && op.getOp() != PatchOp.remove) {
+ userUR.getPlainAttrs().add(new AttrPatch.Builder(new
Attr.Builder(schema).value(value).build())
+ .operation(PatchOperation.ADD_REPLACE)
+ .build());
+ }
+ if (before.getPlainAttr(schema).isPresent()
+ && userUR.getPlainAttrs().stream().noneMatch(attrPatch
->
+ attrPatch.getAttr().getSchema().equals(schema))
+ && op.getOp() == PatchOp.remove) {
+ userUR.getPlainAttrs().add(new AttrPatch.Builder(new
Attr.Builder(schema).build())
+ .operation(PatchOperation.DELETE)
+ .build());
+ }
+ }
+ }
+
protected void setAttribute(final Set<AttrPatch> attrs, final String
schema, final SCIMPatchOperation op) {
Optional.ofNullable(schema).ifPresent(a -> {
Attr.Builder attr = new Attr.Builder(a);
@@ -840,229 +1170,274 @@ public class SCIMDataBinder {
}
}
- public Pair<UserUR, StatusR> toUserUpdate(
+ public Pair<List<UserUR>, StatusR> toUserUpdate(
final UserTO before,
- final Collection<String> resources,
- final SCIMPatchOperation op) {
- StatusR statusR = null;
-
- if (op.getPath() == null && op.getOp() != PatchOp.remove
- && !CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
-
- SCIMUser after = (SCIMUser) op.getValue().get(0);
-
- if (after.getActive() != null && before.isSuspended() ==
after.isActive()) {
- statusR = new StatusR.Builder(
- before.getKey(),
- after.isActive() ? StatusRType.REACTIVATE :
StatusRType.SUSPEND).
- resources(resources).
- build();
- }
-
- UserTO updated = toUserTO(after, false);
- updated.setKey(before.getKey());
- return Pair.of(AnyOperations.diff(updated, before, true), statusR);
- }
-
+ final SCIMPatchOp patch) {
+ AtomicReference<StatusR> statusR = new AtomicReference<>();
+ List<UserUR> userURs = new ArrayList<>();
UserUR userUR = new UserUR.Builder(before.getKey()).build();
-
- SCIMConf conf = confManager.get();
- if (conf == null) {
- return Pair.of(userUR, statusR);
- }
-
- switch (op.getPath().getAttribute()) {
- case "externalId":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getExternalId(), op);
- break;
-
- case "userName":
- if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
- userUR.setUsername(new StringReplacePatchItem.Builder().
- value(op.getValue().get(0).toString()).build());
- }
- break;
-
- case "password":
- if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
- userUR.setPassword(new PasswordPatch.Builder().
-
value(op.getValue().get(0).toString()).resources(resources).build());
- }
- break;
-
- case "active":
- if (!CollectionUtils.isEmpty(op.getValue())) {
-
- // Workaround for Microsoft Entra being not SCIM compliant
on PATCH requests
- if (op.getValue().get(0) instanceof String) {
- String a = (String) op.getValue().get(0);
- op.setValue(List.of(BooleanUtils.toBoolean(a)));
- }
-
- statusR = new StatusR.Builder(
+ userURs.add(userUR);
+ List<String> resources = new ArrayList<>(before.getResources());
+ AtomicInteger numberUR = new AtomicInteger();
+
+ patch.getOperations().forEach(op -> {
+ if (op.getPath() == null && op.getOp() != PatchOp.remove
+ && !CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
+ SCIMUser after = (SCIMUser) op.getValue().get(0);
+ if (after.getActive() != null && before.isSuspended() ==
after.isActive()) {
+ statusR.set(new StatusR.Builder(
before.getKey(),
- (boolean) op.getValue().get(0) ?
StatusRType.REACTIVATE : StatusRType.SUSPEND).
- resources(resources).
- build();
+ after.isActive() ? StatusRType.REACTIVATE :
StatusRType.SUSPEND)
+ .resources(resources)
+ .build());
}
- break;
-
- case "name":
- if (conf.getUserConf().getName() != null) {
- if (op.getPath().getSub() == null ||
"familyName".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getFamilyName(), op);
- }
- if (op.getPath().getSub() == null ||
"formatted".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getFormatted(), op);
- }
- if (op.getPath().getSub() == null ||
"givenName".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getGivenName(), op);
- }
- if (op.getPath().getSub() == null ||
"honorificPrefix".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getHonorificPrefix(), op);
+ if (!after.getGroups().isEmpty()) {
+ String groupKey = after.getGroups().get(0).getValue();
+ org.apache.syncope.core.persistence.api.entity.group.Group
group = groupDAO.find(groupKey);
+ if (group != null &&
before.getMembership(groupKey).isEmpty()) {
+ List<ExternalResource> filteredResources =
group.getResources().stream()
+ .filter(resource ->
resource.getProvisions().stream().anyMatch(provision ->
+
AnyTypeKind.USER.name().equals(provision.getAnyType())))
+ .collect(Collectors.toList());
+ filteredResources.forEach(resource ->
resources.add(resource.getKey()));
+ if (!filteredResources.isEmpty()) {
+ UserUR newUserUR = new
UserUR.Builder(before.getKey()).build();
+ userURs.add(newUserUR);
+ numberUR.getAndIncrement();
+ }
}
- if (op.getPath().getSub() == null ||
"honorificSuffix".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getHonorificSuffix(), op);
+ }
+ populateUserUR(userURs.get(numberUR.get()), before, after,
resources, op);
+ return;
+ }
+ SCIMConf conf = confManager.get();
+ if (conf == null) {
+ return;
+ }
+ switch (op.getPath().getAttribute()) {
+ case "externalId":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getExternalId(), op);
+ break;
+
+ case "userName":
+ if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
+ userURs.get(numberUR.get()).setUsername(new
StringReplacePatchItem.Builder().
+
value(op.getValue().get(0).toString()).build());
}
- if (op.getPath().getSub() == null ||
"middleName".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getMiddleName(), op);
+ break;
+
+ case "password":
+ if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
+ userURs.get(numberUR.get()).setPassword(new
PasswordPatch.Builder().
+ value(op.getValue().get(0).toString()).
+ resources(resources).
+ build());
}
- }
- break;
-
- case "displayName":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getDisplayName(), op);
- break;
-
- case "nickName":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getNickName(), op);
- break;
+ break;
- case "profileUrl":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getProfileUrl(), op);
- break;
+ case "active":
+ if (!CollectionUtils.isEmpty(op.getValue())) {
- case "title":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTitle(), op);
- break;
-
- case "userType":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getUserType(), op);
- break;
+ // Workaround for Microsoft Entra being not SCIM
compliant on PATCH requests
+ if (op.getValue().get(0) instanceof String) {
+ String a = (String) op.getValue().get(0);
+ op.setValue(List.of(BooleanUtils.toBoolean(a)));
+ }
- case "preferredLanguage":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPreferredLanguage(), op);
- break;
+ statusR.set(new StatusR.Builder(
+ before.getKey(),
+ (boolean) op.getValue().get(0) ?
StatusRType.REACTIVATE : StatusRType.SUSPEND).
+ resources(resources).
+ build());
+ }
+ break;
+
+ case "name":
+ if (conf.getUserConf().getName() != null) {
+ if (op.getPath().getSub() == null ||
"familyName".equals(op.getPath().getSub())) {
+ setAttribute(
+
userURs.get(numberUR.get()).getPlainAttrs(),
+
conf.getUserConf().getName().getFamilyName(),
+ op);
+ }
+ if (op.getPath().getSub() == null ||
"formatted".equals(op.getPath().getSub())) {
+ setAttribute(
+
userURs.get(numberUR.get()).getPlainAttrs(),
+
conf.getUserConf().getName().getFormatted(),
+ op);
+ }
+ if (op.getPath().getSub() == null ||
"givenName".equals(op.getPath().getSub())) {
+ setAttribute(
+
userURs.get(numberUR.get()).getPlainAttrs(),
+
conf.getUserConf().getName().getGivenName(),
+ op);
+ }
+ if (op.getPath().getSub() == null ||
"honorificPrefix".equals(op.getPath().getSub())) {
+ setAttribute(
+
userURs.get(numberUR.get()).getPlainAttrs(),
+
conf.getUserConf().getName().getHonorificPrefix(),
+ op);
+ }
+ if (op.getPath().getSub() == null ||
"honorificSuffix".equals(op.getPath().getSub())) {
+ setAttribute(
+
userURs.get(numberUR.get()).getPlainAttrs(),
+
conf.getUserConf().getName().getHonorificSuffix(),
+ op);
+ }
+ if (op.getPath().getSub() == null ||
"middleName".equals(op.getPath().getSub())) {
+ setAttribute(
+
userURs.get(numberUR.get()).getPlainAttrs(),
+
conf.getUserConf().getName().getMiddleName(),
+ op);
+ }
+ }
+ break;
- case "locale":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getLocale(), op);
- break;
+ case "displayName":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getDisplayName(), op);
+ break;
- case "timezone":
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTimezone(), op);
- break;
+ case "nickName":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getNickName(), op);
+ break;
- case "emails":
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getEmails(),
- ((SCIMUser) op.getValue().get(0)).getEmails(),
- op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getEmails(), op);
- }
- break;
+ case "profileUrl":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getProfileUrl(), op);
+ break;
- case "phoneNumbers":
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getPhoneNumbers(),
- ((SCIMUser)
op.getValue().get(0)).getPhoneNumbers(),
- op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhoneNumbers(), op);
- }
- break;
+ case "title":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getTitle(), op);
+ break;
- case "ims":
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getIms(),
- ((SCIMUser) op.getValue().get(0)).getIms(),
- op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getIms(), op);
- }
- break;
+ case "userType":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getUserType(), op);
+ break;
- case "photos":
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
+ case "preferredLanguage":
setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getPhotos(),
- ((SCIMUser) op.getValue().get(0)).getPhotos(),
- op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhotos(), op);
- }
- break;
+ userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getPreferredLanguage(), op);
+ break;
- case "addresses":
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- SCIMUser after = (SCIMUser) op.getValue().get(0);
- after.getAddresses().stream().filter(address ->
address.getType() != null).
- forEach(address ->
conf.getUserConf().getAddresses().stream().
- filter(object ->
address.getType().equals(object.getType().name())).findFirst().
- ifPresent(addressConf ->
setAttribute(userUR.getPlainAttrs(), addressConf, op)));
- } else if (op.getPath().getFilter() != null) {
- conf.getUserConf().getAddresses().stream().
- filter(addressConf ->
BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
- filter2JexlExpression(op.getPath().getFilter()),
- new MapContext(Map.of("type",
addressConf.getType().name()))).toString())).findFirst().
- ifPresent(addressConf ->
setAttribute(userUR.getPlainAttrs(), addressConf, op));
- }
- break;
+ case "locale":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getLocale(), op);
+ break;
- case "employeeNumber":
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getEmployeeNumber).orElse(null), op);
- break;
+ case "timezone":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getTimezone(), op);
+ break;
- case "costCenter":
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getCostCenter).orElse(null), op);
- break;
-
- case "organization":
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getOrganization).orElse(null), op);
- break;
-
- case "division":
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
- map(SCIMEnterpriseUserConf::getDivision).orElse(null),
op);
- break;
-
- case "department":
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getDepartment).orElse(null), op);
- break;
+ case "emails":
+ if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
+ setAttribute(
+ userURs.get(numberUR.get()).getPlainAttrs(),
+ conf.getUserConf().getEmails(),
+ ((SCIMUser) op.getValue().get(0)).getEmails(),
+ op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+
setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getEmails(), op);
+ }
+ break;
- case "manager":
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getManager).map(SCIMManagerConf::getKey).orElse(null),
op);
- break;
+ case "phoneNumbers":
+ if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
+ setAttribute(
+ userURs.get(numberUR.get()).getPlainAttrs(),
+ conf.getUserConf().getPhoneNumbers(),
+ ((SCIMUser)
op.getValue().get(0)).getPhoneNumbers(),
+ op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+ setAttribute(
+ userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getPhoneNumbers(), op);
+ }
+ break;
- default:
- Optional.ofNullable(conf.getExtensionUserConf()).
- flatMap(schema ->
Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute()))).
- ifPresent(schema ->
setAttribute(userUR.getPlainAttrs(), schema, op));
- }
+ case "ims":
+ if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
+ setAttribute(
+ userURs.get(numberUR.get()).getPlainAttrs(),
+ conf.getUserConf().getIms(),
+ ((SCIMUser) op.getValue().get(0)).getIms(),
+ op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+
setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getIms(), op);
+ }
+ break;
- return Pair.of(userUR, statusR);
+ case "photos":
+ if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
+ setAttribute(
+ userURs.get(numberUR.get()).getPlainAttrs(),
+ conf.getUserConf().getPhotos(),
+ ((SCIMUser) op.getValue().get(0)).getPhotos(),
+ op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+
setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
conf.getUserConf().getPhotos(), op);
+ }
+ break;
+
+ case "addresses":
+ if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
+ SCIMUser after = (SCIMUser) op.getValue().get(0);
+ after.getAddresses().stream().filter(address ->
address.getType() != null).
+ forEach(address ->
conf.getUserConf().getAddresses().stream().
+ filter(object ->
address.getType().equals(object.getType().name())).findFirst().
+ ifPresent(addressConf ->
+
setAttribute(userURs.get(numberUR.get()).getPlainAttrs(), addressConf, op)));
+ } else if (op.getPath().getFilter() != null) {
+ conf.getUserConf().getAddresses().stream().
+ filter(addressConf ->
BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
+
filter2JexlExpression(op.getPath().getFilter()),
+ new MapContext(Map.of("type",
addressConf.getType().name()))).toString())).
+ findFirst().
+ ifPresent(addressConf ->
+
setAttribute(userURs.get(numberUR.get()).getPlainAttrs(), addressConf, op));
+ }
+ break;
+
+ case "employeeNumber":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
+ Optional.ofNullable(conf.getEnterpriseUserConf()).
+
map(SCIMEnterpriseUserConf::getEmployeeNumber).orElse(null), op);
+ break;
+
+ case "costCenter":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
+ Optional.ofNullable(conf.getEnterpriseUserConf()).
+
map(SCIMEnterpriseUserConf::getCostCenter).orElse(null), op);
+ break;
+
+ case "organization":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
+ Optional.ofNullable(conf.getEnterpriseUserConf()).
+
map(SCIMEnterpriseUserConf::getOrganization).orElse(null), op);
+ break;
+
+ case "division":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
+ Optional.ofNullable(conf.getEnterpriseUserConf()).
+
map(SCIMEnterpriseUserConf::getDivision).orElse(null), op);
+ break;
+
+ case "department":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
+ Optional.ofNullable(conf.getEnterpriseUserConf()).
+
map(SCIMEnterpriseUserConf::getDepartment).orElse(null), op);
+ break;
+
+ case "manager":
+ setAttribute(userURs.get(numberUR.get()).getPlainAttrs(),
+ Optional.ofNullable(conf.getEnterpriseUserConf()).
+
map(SCIMEnterpriseUserConf::getManager).map(SCIMManagerConf::getKey).orElse(null),
op);
+ break;
+
+ default:
+ Optional.ofNullable(conf.getExtensionUserConf()).
+ flatMap(schema ->
+
Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute()))).
+ ifPresent(schema ->
setAttribute(userURs.get(numberUR.get()).getPlainAttrs(), schema, op));
+ }
+ });
+ return Pair.of(userURs, statusR.get());
}
@Transactional(readOnly = true)
diff --git
a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
index d65e4b9d63..cc0b9014b0 100644
---
a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
+++
b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
@@ -18,20 +18,45 @@
*/
package org.apache.syncope.core.logic;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
import java.util.stream.Stream;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.request.StatusR;
+import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.scim.SCIMConf;
+import org.apache.syncope.common.lib.scim.SCIMUserConf;
+import org.apache.syncope.common.lib.scim.SCIMUserNameConf;
+import org.apache.syncope.common.lib.to.Provision;
import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.lib.types.StatusRType;
import org.apache.syncope.core.logic.scim.SCIMConfManager;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
import org.apache.syncope.core.spring.security.AuthDataAccessor;
+import org.apache.syncope.ext.scimv2.api.data.Group;
+import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOp;
import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation;
import org.apache.syncope.ext.scimv2.api.data.SCIMPatchPath;
+import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
+import org.apache.syncope.ext.scimv2.api.data.SCIMUserName;
+import org.apache.syncope.ext.scimv2.api.data.Value;
+import org.apache.syncope.ext.scimv2.api.type.PatchOp;
+import org.apache.syncope.ext.scimv2.api.type.Resource;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -41,6 +66,8 @@ class SCIMDataBinderTest {
private SCIMDataBinder dataBinder;
+ private GroupDAO groupDAO;
+
private static Stream<String> getValue() {
return Stream.of("True", "False");
}
@@ -48,21 +75,253 @@ class SCIMDataBinderTest {
@BeforeAll
void setup() {
SCIMConfManager scimConfManager = mock(SCIMConfManager.class);
- when(scimConfManager.get()).thenReturn(new SCIMConf());
+ SCIMConf conf = new SCIMConf();
+ conf.setUserConf(new SCIMUserConf());
+ conf.getUserConf().setName(new SCIMUserNameConf());
+ conf.getUserConf().getName().setGivenName("firstname");
+ conf.getUserConf().getName().setFamilyName("surname");
+ when(scimConfManager.get()).thenReturn(conf);
UserLogic userLogic = mock(UserLogic.class);
AuthDataAccessor authDataAccessor = mock(AuthDataAccessor.class);
- GroupDAO groupDAO = mock(GroupDAO.class);
- dataBinder = new SCIMDataBinder(scimConfManager, userLogic,
authDataAccessor, groupDAO);
+ groupDAO = mock(GroupDAO.class);
+ dataBinder = new SCIMDataBinder(scimConfManager, userLogic,
authDataAccessor, groupDAO);
}
@ParameterizedTest
@MethodSource("getValue")
- void toUserUpdate(final String value) {
+ void toUserUpdateActive(final String value) {
+ SCIMPatchOp scimPatchOp = new SCIMPatchOp();
+ scimPatchOp.setOperations(List.of(getOperation("active", null,
PatchOp.add, value)));
+ Pair<List<UserUR>, StatusR> result = dataBinder.toUserUpdate(new
UserTO(), scimPatchOp);
+ assertNotNull(result);
+ assertEquals(1, result.getLeft().size());
+ assertTrue(result.getLeft().get(0).isEmpty());
+ assertNotNull(result.getRight());
+ assertTrue(result.getRight().isOnSyncope());
+ assertEquals(
+ Boolean.parseBoolean(value) ? StatusRType.REACTIVATE :
StatusRType.SUSPEND,
+ result.getRight().getType());
+ }
+
+ @Test
+ void toUserUpdate() {
+ SCIMPatchOp scimPatchOp = new SCIMPatchOp();
+ List<SCIMPatchOperation> operations = new ArrayList<>();
+ operations.add(getOperation("name", "familyName", PatchOp.add,
"Rossini"));
+ scimPatchOp.setOperations(operations);
+
+ Pair<List<UserUR>, StatusR> result = dataBinder.toUserUpdate(new
UserTO(), scimPatchOp);
+ assertNotNull(result);
+ assertNull(result.getRight());
+ assertEquals(1, result.getLeft().size());
+ assertEquals(1, result.getLeft().get(0).getPlainAttrs().size());
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("surname")
+ &&
attrPatch.getAttr().getValues().contains("Rossini")));
+
+ operations.clear();
+ operations.add(getOperation("name", "givenName", PatchOp.add,
"Gioacchino"));
+ operations.add(getOperation("name", "familyName", PatchOp.remove,
null));
+ scimPatchOp.setOperations(operations);
+ result = dataBinder.toUserUpdate(new UserTO(), scimPatchOp);
+ assertNotNull(result);
+ assertNull(result.getRight());
+ assertEquals(1, result.getLeft().size());
+ assertEquals(2, result.getLeft().get(0).getPlainAttrs().size());
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("firstname")
+ &&
attrPatch.getAttr().getValues().contains("Gioacchino")));
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.DELETE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("surname")
+ && attrPatch.getAttr().getValues().isEmpty()));
+
+ operations.clear();
+ operations.add(getOperation("name", "familyName", PatchOp.add,
"Verdi"));
+ operations.add(getOperation("name", "givenName", PatchOp.replace,
"Giuseppe"));
+ operations.add(getOperation("userName", null, PatchOp.add, "gverdi"));
+ scimPatchOp.setOperations(operations);
+ result = dataBinder.toUserUpdate(new UserTO(), scimPatchOp);
+ assertNotNull(result);
+ assertNull(result.getRight());
+ assertEquals(1, result.getLeft().size());
+ assertEquals(2, result.getLeft().get(0).getPlainAttrs().size());
+ assertEquals(PatchOperation.ADD_REPLACE,
result.getLeft().get(0).getUsername().getOperation());
+ assertEquals("gverdi",
result.getLeft().get(0).getUsername().getValue());
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("surname")
+ && attrPatch.getAttr().getValues().contains("Verdi")));
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("firstname")
+ &&
attrPatch.getAttr().getValues().contains("Giuseppe")));
+
+ operations.clear();
+ operations.add(getOperation("name", "familyName", PatchOp.replace,
"Puccini"));
+ operations.add(getOperation("name", "givenName", PatchOp.remove,
null));
+ operations.add(getOperation("active", null, PatchOp.add, "True"));
+ scimPatchOp.setOperations(operations);
+ result = dataBinder.toUserUpdate(new UserTO(), scimPatchOp);
+ assertNotNull(result);
+ assertNotNull(result.getRight());
+ assertTrue(result.getRight().isOnSyncope());
+ assertEquals(StatusRType.REACTIVATE, result.getRight().getType());
+ assertEquals(1, result.getLeft().size());
+ assertEquals(2, result.getLeft().get(0).getPlainAttrs().size());
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("surname")
+ &&
attrPatch.getAttr().getValues().contains("Puccini")));
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.DELETE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("firstname")
+ && attrPatch.getAttr().getValues().isEmpty()));
+
+ UserTO userTO = new UserTO();
+ userTO.setUsername("bellini");
+ userTO.setRealm(SyncopeConstants.ROOT_REALM);
+ userTO.getPlainAttrs().add(new
Attr.Builder("surname").value("Bellini").build());
+ SCIMUser scimUser = new SCIMUser(
+ UUID.randomUUID().toString(), List.of(Resource.User.schema()),
null, "bellini", true);
+ scimUser.setName(new SCIMUserName());
+ scimUser.getName().setFamilyName("Bellini");
+ SCIMPatchOperation operation = new SCIMPatchOperation();
+ operation.setOp(PatchOp.add);
+ operation.setValue(List.of(scimUser));
+ operations.clear();
+ operations.add(operation);
+ scimPatchOp.setOperations(operations);
+ result = dataBinder.toUserUpdate(userTO, scimPatchOp);
+ assertNotNull(result);
+ assertNull(result.getRight());
+ assertEquals(1, result.getLeft().size());
+ assertTrue(result.getLeft().get(0).isEmpty());
+
+ userTO.setUsername("rossini");
+ userTO.getPlainAttrs().clear();
+ userTO.getPlainAttrs().add(new
Attr.Builder("surname").value("Rossini").build());
+ result = dataBinder.toUserUpdate(userTO, scimPatchOp);
+ assertNotNull(result);
+ assertNull(result.getRight());
+ assertEquals(1, result.getLeft().size());
+ assertEquals(PatchOperation.ADD_REPLACE,
result.getLeft().get(0).getUsername().getOperation());
+ assertEquals("bellini",
result.getLeft().get(0).getUsername().getValue());
+ assertEquals(1, result.getLeft().get(0).getPlainAttrs().size());
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("surname")
+ &&
attrPatch.getAttr().getValues().contains("Bellini")));
+
+ userTO.setUsername("bellini");
+ userTO.setSuspended(true);
+ userTO.getPlainAttrs().clear();
+ userTO.getPlainAttrs().add(new
Attr.Builder("surname").value("Bellini").build());
+ scimUser.getName().setGivenName("Gioacchino");
+ scimUser.getRoles().add(new Value("User reviewer"));
+ result = dataBinder.toUserUpdate(userTO, scimPatchOp);
+ assertNotNull(result);
+ assertNotNull(result.getRight());
+ assertTrue(result.getRight().isOnSyncope());
+ assertEquals(StatusRType.REACTIVATE, result.getRight().getType());
+ assertEquals(1, result.getLeft().size());
+ assertNull(result.getLeft().get(0).getUsername());
+ assertEquals(1, result.getLeft().get(0).getPlainAttrs().size());
+
assertTrue(result.getLeft().get(0).getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("firstname")
+ &&
attrPatch.getAttr().getValues().contains("Gioacchino")));
+ assertEquals(1, result.getLeft().get(0).getRoles().size());
+ assertTrue(result.getLeft().get(0).getRoles().stream().anyMatch(role ->
+ PatchOperation.ADD_REPLACE.equals(role.getOperation())
+ && role.getValue().equals("User reviewer")));
+
+ userTO = new UserTO();
+ Group group = new Group("37d15e4c-cdc1-460b-a591-8505c8133806", null,
"root", null);
+ scimUser = new SCIMUser(
+ UUID.randomUUID().toString(), List.of(Resource.User.schema()),
null, "bellini", true);
+ scimUser.getGroups().add(group);
+ group = new Group("29f96485-729e-4d31-88a1-6fc60e4677f3", null,
"citizen", null);
+ scimUser.getGroups().add(group);
+ operation.setOp(PatchOp.add);
+ operation.setValue(List.of(scimUser));
+ operations.clear();
+ operations.add(operation);
+ group = new Group("f779c0d4-633b-4be5-8f57-32eb478a3ca5", null,
"otherchild", null);
+ SCIMUser scimUser2 =
+ new SCIMUser(UUID.randomUUID().toString(),
List.of(Resource.User.schema()), null, "bellini", true);
+ scimUser2.getGroups().add(group);
+ SCIMPatchOperation operation2 = new SCIMPatchOperation();
+ operation2.setOp(PatchOp.add);
+ operation2.setValue(List.of(scimUser2));
+ operations.add(operation2);
+ scimPatchOp.setOperations(operations);
+
when(groupDAO.find("37d15e4c-cdc1-460b-a591-8505c8133806")).thenAnswer(ic -> {
+ org.apache.syncope.core.persistence.api.entity.group.Group
syncopeGroup =
+
mock(org.apache.syncope.core.persistence.api.entity.group.Group.class);
+ ExternalResource resource = mock(ExternalResource.class);
+ Provision provision = mock(Provision.class);
+ when(provision.getAnyType()).thenReturn(AnyTypeKind.USER.name());
+ when(resource.getKey()).thenReturn("resource-ldap");
+ when(resource.getProvisions()).thenAnswer(invocation ->
List.of(provision));
+ when(syncopeGroup.getResources()).thenAnswer(invocation ->
List.of(resource));
+ return syncopeGroup;
+ });
+
when(groupDAO.find("29f96485-729e-4d31-88a1-6fc60e4677f3")).thenAnswer(ic -> {
+ org.apache.syncope.core.persistence.api.entity.group.Group
syncopeGroup =
+
mock(org.apache.syncope.core.persistence.api.entity.group.Group.class);
+ ExternalResource resource = mock(ExternalResource.class);
+ Provision provision = mock(Provision.class);
+ when(provision.getAnyType()).thenReturn(AnyTypeKind.USER.name());
+ when(resource.getKey()).thenReturn("resource-testdb");
+ when(resource.getProvisions()).thenAnswer(invocation ->
List.of(provision));
+ when(syncopeGroup.getResources()).thenAnswer(invocation ->
List.of(resource));
+ return syncopeGroup;
+ });
+
when(groupDAO.find("f779c0d4-633b-4be5-8f57-32eb478a3ca5")).thenAnswer(ic -> {
+ org.apache.syncope.core.persistence.api.entity.group.Group
syncopeGroup =
+
mock(org.apache.syncope.core.persistence.api.entity.group.Group.class);
+ ExternalResource resource = mock(ExternalResource.class);
+ Provision provision = mock(Provision.class);
+ when(provision.getAnyType()).thenReturn(AnyTypeKind.USER.name());
+
when(resource.getKey()).thenReturn("ws-target-resource-list-mappings-1");
+ when(resource.getProvisions()).thenAnswer(invocation ->
List.of(provision));
+
+ ExternalResource resource2 = mock(ExternalResource.class);
+
when(resource2.getKey()).thenReturn("ws-target-resource-list-mappings-2");
+ when(resource2.getProvisions()).thenAnswer(invocation ->
List.of(provision));
+ when(syncopeGroup.getResources()).thenAnswer(invocation ->
List.of(resource, resource2));
+ return syncopeGroup;
+ });
+ result = dataBinder.toUserUpdate(userTO, scimPatchOp);
+ assertNotNull(result);
+ assertNull(result.getRight());
+ assertEquals(3, result.getLeft().size());
+ assertTrue(result.getLeft().get(0).isEmpty());
+ assertEquals(2, result.getLeft().get(1).getMemberships().size());
+
assertTrue(result.getLeft().get(1).getMemberships().stream().anyMatch(membershipUR
->
+ PatchOperation.ADD_REPLACE.equals(membershipUR.getOperation())
+ &&
membershipUR.getGroup().equals("37d15e4c-cdc1-460b-a591-8505c8133806")));
+
assertTrue(result.getLeft().get(1).getMemberships().stream().anyMatch(membershipUR
->
+ PatchOperation.ADD_REPLACE.equals(membershipUR.getOperation())
+ &&
membershipUR.getGroup().equals("29f96485-729e-4d31-88a1-6fc60e4677f3")));
+ assertEquals(1, result.getLeft().get(2).getMemberships().size());
+
assertTrue(result.getLeft().get(2).getMemberships().stream().anyMatch(membershipUR
->
+ PatchOperation.ADD_REPLACE.equals(membershipUR.getOperation())
+ &&
membershipUR.getGroup().equals("f779c0d4-633b-4be5-8f57-32eb478a3ca5")));
+ }
+
+ private SCIMPatchOperation getOperation(
+ final String attribute, final String sub, final PatchOp op, final
String value) {
SCIMPatchOperation operation = new SCIMPatchOperation();
SCIMPatchPath scimPatchPath = new SCIMPatchPath();
- scimPatchPath.setAttribute("active");
+ scimPatchPath.setAttribute(attribute);
+ scimPatchPath.setSub(sub);
+ operation.setOp(op);
operation.setPath(scimPatchPath);
- operation.setValue(List.of(value));
- assertDoesNotThrow(() -> dataBinder.toUserUpdate(new UserTO(),
List.of(), operation));
+ operation.setValue(value == null ? List.of() : List.of(value));
+ return operation;
}
}
diff --git
a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java
b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java
index 52810c2caa..b9319e562f 100644
---
a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java
+++
b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java
@@ -96,14 +96,11 @@ public class SCIMUserServiceImpl extends
AbstractSCIMService<SCIMUser> implement
return builder.build();
}
- patch.getOperations().forEach(op -> {
- Pair<UserUR, StatusR> update = binder.toUserUpdate(
- userLogic.read(id),
- userDAO.findAllResourceKeys(id),
- op);
- userLogic.update(update.getLeft(), false);
- Optional.ofNullable(update.getRight()).ifPresent(statusR ->
userLogic.status(statusR, false));
- });
+ Pair<List<UserUR>, StatusR> update = binder.toUserUpdate(
+ userLogic.read(id),
+ patch);
+ update.getLeft().forEach(userUR -> userLogic.update(userUR, false));
+ Optional.ofNullable(update.getRight()).ifPresent(statusR ->
userLogic.status(statusR, false));
return updateResponse(
id,