This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 4_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/4_0_X by this push:
new bf7138a048 [SYNCOPE-1926] optimize user updates on SCIM PATCH (#1226)
bf7138a048 is described below
commit bf7138a048bc6b45b8b7719b4220ef2d9ad18f25
Author: Samuel Garofalo <[email protected]>
AuthorDate: Tue Nov 4 13:45:35 2025 +0100
[SYNCOPE-1926] optimize user updates on SCIM PATCH (#1226)
---
.../apache/syncope/core/logic/SCIMDataBinder.java | 709 ++++++++++++++++-----
.../syncope/core/logic/SCIMDataBinderTest.java | 276 +++++++-
.../scimv2/cxf/service/SCIMUserServiceImpl.java | 13 +-
3 files changed, 817 insertions(+), 181 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 8f414c475a..22eb5dda26 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
@@ -29,8 +29,10 @@ import java.util.Set;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.mutable.MutableObject;
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;
@@ -39,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;
@@ -54,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;
@@ -62,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;
@@ -74,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;
@@ -553,6 +560,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();
@@ -763,6 +1056,45 @@ 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());
+ }
+ }
+
+ default -> {
+ if ((before.getPlainAttr(schema).isEmpty()
+ ||
!value.equals(before.getPlainAttr(schema).get().getValues().getFirst()))
+ && 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);
@@ -835,206 +1167,253 @@ 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().getFirst() instanceof final SCIMUser after) {
-
- 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) {
+ Mutable<StatusR> statusR = new MutableObject<>();
+ 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);
-
- case "userName" -> {
- if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
- userUR.setUsername(
- new
StringReplacePatchItem.Builder().value(op.getValue().getFirst().toString()).build());
+ userURs.add(userUR);
+ List<String> resources = new ArrayList<>(before.getResources());
+ MutableInt numberUR = new MutableInt(0);
+
+ patch.getOperations().forEach(op -> {
+ if (op.getPath() == null && op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())
+ && op.getValue().getFirst() instanceof final SCIMUser
after) {
+
+ if (after.getActive() != null && before.isSuspended() ==
after.isActive()) {
+ statusR.setValue(new StatusR.Builder(before.getKey(),
+ after.isActive() ? StatusRType.REACTIVATE :
StatusRType.SUSPEND).resources(resources)
+ .build());
}
- }
- case "password" -> {
- if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
- userUR.setPassword(new
PasswordPatch.Builder().value(op.getValue().getFirst().toString()).resources(
- resources).build());
+ if (!after.getGroups().isEmpty()) {
+ String groupKey = after.getGroups().getFirst().getValue();
+ org.apache.syncope.core.persistence.api.entity.group.Group
group =
+ groupDAO.findById(groupKey).orElse(null);
+ if (group != null &&
before.getMembership(groupKey).isEmpty()) {
+ List<? extends ExternalResource> filteredResources =
group.getResources().stream()
+ .filter(resource ->
resource.getProvisions().stream()
+ .anyMatch(provision ->
AnyTypeKind.USER.name().equals(provision.getAnyType())))
+ .toList();
+ filteredResources.forEach(resource ->
resources.add(resource.getKey()));
+ if (!filteredResources.isEmpty()) {
+ UserUR newUserUR = new
UserUR.Builder(before.getKey()).build();
+ userURs.add(newUserUR);
+ numberUR.increment();
+ }
+ }
}
+ populateUserUR(userURs.get(numberUR.get().intValue()), before,
after, resources, op);
+ return;
}
- case "active" -> {
- if (!CollectionUtils.isEmpty(op.getValue())) {
+ SCIMConf conf = confManager.get();
+ if (conf == null) {
+ return;
+ }
- // Workaround for Microsoft Entra being not SCIM compliant
on PATCH requests
- if (op.getValue().getFirst() instanceof String a) {
- op.setValue(List.of(BooleanUtils.toBoolean(a)));
+ switch (op.getPath().getAttribute()) {
+ case "externalId" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getExternalId(),
+ op);
+
+ case "userName" -> {
+ if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
+ userURs.get(numberUR.get().intValue()).setUsername(
+ new
StringReplacePatchItem.Builder().value(op.getValue().getFirst().toString())
+ .build());
}
-
- statusR = new StatusR.Builder(before.getKey(),
- (boolean) op.getValue().getFirst()
- ? StatusRType.REACTIVATE
- :
StatusRType.SUSPEND).resources(resources).build();
}
- }
- 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 (op.getPath().getSub() == null ||
"honorificSuffix".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getHonorificSuffix(), op);
- }
- if (op.getPath().getSub() == null ||
"middleName".equals(op.getPath().getSub())) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getName().getMiddleName(), op);
+ case "password" -> {
+ if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
+ userURs.get(numberUR.get().intValue()).setPassword(
+ new
PasswordPatch.Builder().value(op.getValue().getFirst().toString())
+ .resources(resources).build());
}
}
- }
-
- case "displayName" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getDisplayName(), op);
-
- case "nickName" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getNickName(), op);
- case "profileUrl" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getProfileUrl(), op);
+ case "active" -> {
+ if (!CollectionUtils.isEmpty(op.getValue())) {
- case "title" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTitle(), op);
-
- case "userType" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getUserType(), op);
-
- case "preferredLanguage" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPreferredLanguage(), op);
+ // Workaround for Microsoft Entra being not SCIM
compliant on PATCH requests
+ if (op.getValue().getFirst() instanceof String a) {
+ op.setValue(List.of(BooleanUtils.toBoolean(a)));
+ }
- case "locale" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getLocale(), op);
+ statusR.setValue(new StatusR.Builder(before.getKey(),
(boolean) op.getValue().getFirst()
+ ? StatusRType.REACTIVATE
+ :
StatusRType.SUSPEND).resources(resources).build());
+ }
+ }
- case "timezone" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTimezone(), op);
+ case "name" -> {
+ if (conf.getUserConf().getName() != null) {
+ if (op.getPath().getSub() == null ||
"familyName".equals(op.getPath().getSub())) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
conf.getUserConf().getName().getFamilyName(), op);
+ }
+ if (op.getPath().getSub() == null ||
"formatted".equals(op.getPath().getSub())) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
conf.getUserConf().getName().getFormatted(), op);
+ }
+ if (op.getPath().getSub() == null ||
"givenName".equals(op.getPath().getSub())) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
conf.getUserConf().getName().getGivenName(), op);
+ }
+ if (op.getPath().getSub() == null ||
"honorificPrefix".equals(op.getPath().getSub())) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
conf.getUserConf().getName().getHonorificPrefix(), op);
+ }
+ if (op.getPath().getSub() == null ||
"honorificSuffix".equals(op.getPath().getSub())) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
conf.getUserConf().getName().getHonorificSuffix(), op);
+ }
+ if (op.getPath().getSub() == null ||
"middleName".equals(op.getPath().getSub())) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
conf.getUserConf().getName().getMiddleName(), op);
+ }
+ }
+ }
- case "emails" -> {
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().getFirst() instanceof SCIMUser) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getEmails(),
- ((SCIMUser) op.getValue().getFirst()).getEmails(),
op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getEmails(), op);
+ case "displayName" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getDisplayName(),
+ op);
+
+ case "nickName" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getNickName(), op);
+
+ case "profileUrl" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getProfileUrl(),
+ op);
+
+ case "title" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getTitle(), op);
+
+ case "userType" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getUserType(), op);
+
+ case "preferredLanguage" ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getPreferredLanguage(), op);
+
+ case "locale" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getLocale(), op);
+
+ case "timezone" ->
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getTimezone(), op);
+
+ case "emails" -> {
+ if (!CollectionUtils.isEmpty(op.getValue())
+ && op.getValue().getFirst() instanceof SCIMUser
scimUser) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getEmails(),
+ scimUser.getEmails(), op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getEmails(), op);
+ }
}
- }
- case "phoneNumbers" -> {
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().getFirst() instanceof SCIMUser) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhoneNumbers(),
- ((SCIMUser)
op.getValue().getFirst()).getPhoneNumbers(), op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhoneNumbers(), op);
+ case "phoneNumbers" -> {
+ if (!CollectionUtils.isEmpty(op.getValue())
+ && op.getValue().getFirst() instanceof SCIMUser
scimUser) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getPhoneNumbers(),
+ scimUser.getPhoneNumbers(), op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getPhoneNumbers(),
+ op);
+ }
}
- }
- case "ims" -> {
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().getFirst() instanceof SCIMUser) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getIms(),
- ((SCIMUser) op.getValue().getFirst()).getIms(),
op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getIms(), op);
+ case "ims" -> {
+ if (!CollectionUtils.isEmpty(op.getValue())
+ && op.getValue().getFirst() instanceof SCIMUser
scimUser) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getIms(),
+ scimUser.getIms(), op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getIms(), op);
+ }
}
- }
- case "photos" -> {
- if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().getFirst() instanceof SCIMUser) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhotos(),
- ((SCIMUser) op.getValue().getFirst()).getPhotos(),
op.getOp());
- } else if (op.getPath().getFilter() != null) {
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhotos(), op);
+ case "photos" -> {
+ if (!CollectionUtils.isEmpty(op.getValue())
+ && op.getValue().getFirst() instanceof SCIMUser
scimUser) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getPhotos(),
+ scimUser.getPhotos(), op.getOp());
+ } else if (op.getPath().getFilter() != null) {
+
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ conf.getUserConf().getPhotos(), op);
+ }
}
- }
- case "addresses" -> {
- if (!CollectionUtils.isEmpty(op.getValue())
- && op.getValue().getFirst() instanceof final SCIMUser
after) {
- 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));
+ case "addresses" -> {
+ if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue()
+ .getFirst() instanceof final SCIMUser after) {
+ 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().intValue()).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().intValue()).getPlainAttrs(),
+ addressConf, op));
+ }
}
- }
- case "employeeNumber" ->
- setAttribute(userUR.getPlainAttrs(),
- Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getEmployeeNumber).orElse(null), op);
+ case "employeeNumber" ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getEmployeeNumber)
+ .orElse(null), op);
- case "costCenter" ->
- setAttribute(userUR.getPlainAttrs(),
- Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getCostCenter).orElse(null), op);
+ case "costCenter" ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getCostCenter)
+ .orElse(null), op);
- case "organization" ->
- setAttribute(userUR.getPlainAttrs(),
- Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getOrganization).orElse(null), op);
+ case "organization" ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getOrganization)
+ .orElse(null), op);
- case "division" ->
- setAttribute(userUR.getPlainAttrs(),
- Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getDivision).orElse(null), op);
+ case "division" ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getDivision)
+ .orElse(null), op);
- case "department" ->
- setAttribute(userUR.getPlainAttrs(),
- Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getDepartment).orElse(null), op);
+ case "department" ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getDepartment)
+ .orElse(null), op);
- case "manager" ->
- setAttribute(userUR.getPlainAttrs(),
- Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getManager).map(SCIMManagerConf::getKey).orElse(null),
op);
+ case "manager" ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getManager)
+ .map(SCIMManagerConf::getKey).orElse(null),
op);
- default -> {
- Optional.ofNullable(conf.getExtensionUserConf()).
- flatMap(schema ->
Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute()))).
- ifPresent(schema ->
setAttribute(userUR.getPlainAttrs(), schema, op));
+ default -> {
+ Optional.ofNullable(conf.getExtensionUserConf())
+ .flatMap(schema ->
Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute())))
+ .ifPresent(schema ->
setAttribute(userURs.get(numberUR.get().intValue()).getPlainAttrs(),
+ schema, op));
+ }
}
- }
+ });
- return Pair.of(userUR, statusR);
+ 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..0a88e094b3 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,46 @@
*/
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.Optional;
+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 +67,8 @@ class SCIMDataBinderTest {
private SCIMDataBinder dataBinder;
+ private GroupDAO groupDAO;
+
private static Stream<String> getValue() {
return Stream.of("True", "False");
}
@@ -48,21 +76,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().getFirst().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().getFirst().getPlainAttrs().size());
+
assertTrue(result.getLeft().getFirst().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().getFirst().getPlainAttrs().size());
+
assertTrue(result.getLeft().getFirst().getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("firstname")
+ &&
attrPatch.getAttr().getValues().contains("Gioacchino")));
+
assertTrue(result.getLeft().getFirst().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().getFirst().getPlainAttrs().size());
+ assertEquals(PatchOperation.ADD_REPLACE,
result.getLeft().getFirst().getUsername().getOperation());
+ assertEquals("gverdi",
result.getLeft().getFirst().getUsername().getValue());
+
assertTrue(result.getLeft().getFirst().getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("surname")
+ && attrPatch.getAttr().getValues().contains("Verdi")));
+
assertTrue(result.getLeft().getFirst().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().getFirst().getPlainAttrs().size());
+
assertTrue(result.getLeft().getFirst().getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("surname")
+ &&
attrPatch.getAttr().getValues().contains("Puccini")));
+
assertTrue(result.getLeft().getFirst().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().getFirst().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().getFirst().getUsername().getOperation());
+ assertEquals("bellini",
result.getLeft().getFirst().getUsername().getValue());
+ assertEquals(1, result.getLeft().getFirst().getPlainAttrs().size());
+
assertTrue(result.getLeft().getFirst().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().getFirst().getUsername());
+ assertEquals(1, result.getLeft().getFirst().getPlainAttrs().size());
+
assertTrue(result.getLeft().getFirst().getPlainAttrs().stream().anyMatch(attrPatch
->
+ PatchOperation.ADD_REPLACE.equals(attrPatch.getOperation())
+ && attrPatch.getAttr().getSchema().equals("firstname")
+ &&
attrPatch.getAttr().getValues().contains("Gioacchino")));
+ assertEquals(1, result.getLeft().getFirst().getRoles().size());
+
assertTrue(result.getLeft().getFirst().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.findById("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 Optional.of(syncopeGroup);
+ });
+
when(groupDAO.findById("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 Optional.of(syncopeGroup);
+ });
+
when(groupDAO.findById("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 Optional.of(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 5984aa695e..3b176acad2 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,