diff --git a/src/main/java/net/tirasa/connid/bundles/scim/common/AbstractSCIMConnector.java b/src/main/java/net/tirasa/connid/bundles/scim/common/AbstractSCIMConnector.java index f70aaf1..e27e238 100644 --- a/src/main/java/net/tirasa/connid/bundles/scim/common/AbstractSCIMConnector.java +++ b/src/main/java/net/tirasa/connid/bundles/scim/common/AbstractSCIMConnector.java @@ -997,15 +997,6 @@ protected Object buildPatchValue( }).collect(Collectors.toList()); break; - case "roles.default.value": - patchValue = values.stream().filter(Objects::nonNull).map(v -> { - SCIMGenericComplex value = new SCIMGenericComplex<>(); - value.setValue(v.toString()); - value.setType("default"); - return value; - }).collect(Collectors.toList()); - break; - case "entitlements.default.value": patchValue = values.stream().filter(Objects::nonNull).map(v -> { SCIMGenericComplex value = new SCIMGenericComplex<>(); @@ -1031,10 +1022,23 @@ protected Object buildPatchValue( break; default: - // this is mainly useful to manage custom attributes - patchValue = CollectionUtil.isEmpty(values) ? null - : attributeDefinition != null && attributeDefinition.getMultiValued() ? values - : values.get(0).toString(); + // manage and entitlements roles + if (name.startsWith(SCIMAttributeUtils.SCIM_USER_ROLES + ".") + || name.startsWith(SCIMAttributeUtils.SCIM_USER_ENTITLEMENTS + ".")) { + patchValue = values.stream().filter(Objects::nonNull).map(v -> { + SCIMGenericComplex value = new SCIMGenericComplex<>(); + value.setValue(v.toString()); + value.setType(SCIMUtils.getTypeFromAttributeName(name)); + return value; + }).collect(Collectors.toList()); + } else { + // this is mainly useful to manage custom attributes + patchValue = CollectionUtil.isEmpty(values) + ? null + : attributeDefinition != null && attributeDefinition.getMultiValued() + ? values + : values.get(0).toString(); + } break; } diff --git a/src/main/java/net/tirasa/connid/bundles/scim/common/dto/AbstractSCIMUser.java b/src/main/java/net/tirasa/connid/bundles/scim/common/dto/AbstractSCIMUser.java index cdbe7d2..9aa24c8 100644 --- a/src/main/java/net/tirasa/connid/bundles/scim/common/dto/AbstractSCIMUser.java +++ b/src/main/java/net/tirasa/connid/bundles/scim/common/dto/AbstractSCIMUser.java @@ -815,14 +815,6 @@ private void doSetAttribute(final String name, final List values) { handleSCIMUserAddressObject(AddressCanonicalType.other, s -> s.setOperation(String.class.cast(value))); break; - case "roles.default.value": - handleRoles(value); - break; - - case "entitlements.default.value": - handleDefaultEntitlement(value); - break; - case "x509Certificates.default.value": handlex509Certificates(value); break; @@ -832,15 +824,21 @@ private void doSetAttribute(final String name, final List values) { break; default: + // manage all other custom roles abd entitlements + if (name.startsWith(SCIMAttributeUtils.SCIM_USER_ENTITLEMENTS + ".")) { + handleEntitlement(SCIMUtils.getTypeFromAttributeName(name), value); + } else if (name.startsWith(SCIMAttributeUtils.SCIM_USER_ROLES + ".")) { + handleRole(SCIMUtils.getTypeFromAttributeName(name), value); + } break; } } - protected abstract void handleRoles(Object value); + protected abstract void handleRole(String type, Object value); protected abstract void handlex509Certificates(Object value); - protected abstract void handleDefaultEntitlement(Object value); + protected abstract void handleEntitlement(String type, Object value); @JsonIgnore protected void handleSCIMComplexObject(final T type, diff --git a/src/main/java/net/tirasa/connid/bundles/scim/common/service/AbstractSCIMService.java b/src/main/java/net/tirasa/connid/bundles/scim/common/service/AbstractSCIMService.java index f7ed3e4..9b01800 100644 --- a/src/main/java/net/tirasa/connid/bundles/scim/common/service/AbstractSCIMService.java +++ b/src/main/java/net/tirasa/connid/bundles/scim/common/service/AbstractSCIMService.java @@ -364,8 +364,8 @@ protected String checkServiceErrors(final Response response) { if (response.getStatusInfo().getFamily() != Status.Family.SUCCESSFUL) { SCIMUtils.handleGeneralError( - "While executing SCIM request: status is " + response.getStatus() + " and reponse " - + responseAsString); + "While executing SCIM request: status is " + response.getStatus() + " and response " + + responseAsString); } return responseAsString; } diff --git a/src/main/java/net/tirasa/connid/bundles/scim/common/utils/SCIMUtils.java b/src/main/java/net/tirasa/connid/bundles/scim/common/utils/SCIMUtils.java index b14278c..62c11b4 100644 --- a/src/main/java/net/tirasa/connid/bundles/scim/common/utils/SCIMUtils.java +++ b/src/main/java/net/tirasa/connid/bundles/scim/common/utils/SCIMUtils.java @@ -27,6 +27,8 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import net.tirasa.connid.bundles.scim.common.SCIMConnectorConfiguration; import net.tirasa.connid.bundles.scim.common.SCIMProvider; import net.tirasa.connid.bundles.scim.common.dto.BaseResourceReference; @@ -229,6 +231,14 @@ public static String getPath(final String id, final SCIMConnectorConfiguration c return config.getEnableURLPathEncoding() ? URLEncoder.encode(id, StandardCharsets.UTF_8) : id; } + public static String getTypeFromAttributeName(final String attributeName) { + if (StringUtil.isBlank(attributeName)) { + return null; + } + Matcher matcher = Pattern.compile("^[^.]+\\.([^.]+)\\.[^.]+$").matcher(attributeName); + return matcher.find() ? matcher.group(1) : null; + } + private SCIMUtils() { // private constructor for static utility class } diff --git a/src/main/java/net/tirasa/connid/bundles/scim/v11/dto/SCIMv11User.java b/src/main/java/net/tirasa/connid/bundles/scim/v11/dto/SCIMv11User.java index f887ece..bb5e24d 100644 --- a/src/main/java/net/tirasa/connid/bundles/scim/v11/dto/SCIMv11User.java +++ b/src/main/java/net/tirasa/connid/bundles/scim/v11/dto/SCIMv11User.java @@ -43,7 +43,7 @@ public SCIMv11User() { } @Override - protected void handleRoles(final Object value) { + protected void handleRole(final String type, final Object value) { handleSCIMDefaultObject( String.class.cast(value), this.roles, @@ -59,7 +59,7 @@ protected void handlex509Certificates(final Object value) { } @Override - protected void handleDefaultEntitlement(final Object value) { + protected void handleEntitlement(final String type, final Object value) { handleBaseResourceReference( String.class.cast(value), this.entitlements, diff --git a/src/main/java/net/tirasa/connid/bundles/scim/v2/SCIMv2Connector.java b/src/main/java/net/tirasa/connid/bundles/scim/v2/SCIMv2Connector.java index 8dd248c..4626c88 100644 --- a/src/main/java/net/tirasa/connid/bundles/scim/v2/SCIMv2Connector.java +++ b/src/main/java/net/tirasa/connid/bundles/scim/v2/SCIMv2Connector.java @@ -161,6 +161,7 @@ protected SCIMv2Patch buildPatchFromGroup(final SCIMv2Group group) { } @Override + @SuppressWarnings("unchecked") protected SCIMv2Patch buildUserPatch( final Set modifications, final SCIMv2User currentUser, @@ -191,7 +192,24 @@ protected SCIMv2Patch buildUserPatch( && !attrDelta.is(SCIMAttributeUtils.SCIM_USER_GROUPS) // custom attributes are going to be managed further && !isCustomAttribute(attrDelta.getName(), configuration.getUseColonOnExtensionAttributes())) { - patch.addOperations(buildPatchOperations(attrDelta, null)); + + if (CollectionUtil.isEmpty(attrDelta.getValuesToReplace())) { + patch.addOperations(buildPatchOperations(attrDelta, null)); + } else { + // build the patch operation + SCIMv2PatchOperation replacePatchOperation = + buildReplacePatchOperation(attrDelta.getName(), attrDelta.getValuesToReplace(), null); + // check if the attribute has already been added as a patch operation or not, if so aggregate + // values in a single patch operation + patch.getOperations() + .stream() + .filter(op -> op.getValue() instanceof List && replacePatchOperation.getPath() + .equalsIgnoreCase(op.getPath())) + .findFirst() + .ifPresentOrElse(op -> ((List) op.getValue()).addAll( + (List) replacePatchOperation.getValue()), + () -> patch.addOperation(replacePatchOperation)); + } } } // manage addresses patches @@ -340,12 +358,8 @@ protected List buildPatchOperations( operations.add(removePatchOperation); } } else { - SCIMv2PatchOperation replacePatchOperation = new SCIMv2PatchOperation(); - replacePatchOperation.setPath(SCIMAttributeUtils.getBaseAttributeName(currentDelta.getName())); - replacePatchOperation.setOperation(SCIMAttributeUtils.SCIM_REPLACE); - replacePatchOperation.setValue( - buildPatchValue(currentDelta.getName(), currentDelta.getValuesToReplace(), attributeDefinition)); - operations.add(replacePatchOperation); + operations.add(buildReplacePatchOperation(currentDelta.getName(), currentDelta.getValuesToReplace(), + attributeDefinition)); } return operations; @@ -571,7 +585,7 @@ private static void setAddressAttribute( protected void manageEntitlements(final SCIMv2User user, final List values) { List scimEntitlementRefs = values == null ? Collections.emptyList() - : values.stream().map(client::getEntitlement).filter(g -> g != null).collect(Collectors.toList()); + : values.stream().map(client::getEntitlement).filter(Objects::nonNull).collect(Collectors.toList()); scimEntitlementRefs.forEach(e -> user.getEntitlements().add(new SCIMv2Entitlement.Builder().value(e.getId()) .ref(configuration.getBaseAddress() + "Entitlements/" + e.getId()).display(e.getDisplayName()) .primary(true).type(e.getType()).build())); @@ -610,4 +624,16 @@ protected BaseResourceReference buildPatchValue(final SCIMv2User user) { } return builder.build(); } + + protected SCIMv2PatchOperation buildReplacePatchOperation( + final String attrName, + final List valuesToReplace, + final SCIMBaseAttribute attributeDefinition) { + SCIMv2PatchOperation replacePatchOperation = new SCIMv2PatchOperation(); + replacePatchOperation.setPath(SCIMAttributeUtils.getBaseAttributeName(attrName)); + replacePatchOperation.setOperation(SCIMAttributeUtils.SCIM_REPLACE); + replacePatchOperation.setValue( + buildPatchValue(attrName, valuesToReplace, attributeDefinition)); + return replacePatchOperation; + } } diff --git a/src/main/java/net/tirasa/connid/bundles/scim/v2/dto/SCIMv2User.java b/src/main/java/net/tirasa/connid/bundles/scim/v2/dto/SCIMv2User.java index 5d6f843..39e2f91 100644 --- a/src/main/java/net/tirasa/connid/bundles/scim/v2/dto/SCIMv2User.java +++ b/src/main/java/net/tirasa/connid/bundles/scim/v2/dto/SCIMv2User.java @@ -49,11 +49,11 @@ public SCIMv2User() { } @Override - protected void handleRoles(final Object value) { + protected void handleRole(final String type, final Object value) { handleSCIMComplexObject( - SCIMAttributeUtils.SCIM_SCHEMA_TYPE_DEFAULT, + type, this.roles, - s -> s.setValue(String.class.cast(value))); + s -> s.setValue((String) value)); } @Override @@ -65,10 +65,10 @@ protected void handlex509Certificates(final Object value) { } @Override - protected void handleDefaultEntitlement(final Object value) { + protected void handleEntitlement(final String type, final Object value) { handleSCIMv2Entitlement(this.entitlements, s -> { s.setValue(String.class.cast(value)); - s.setType(SCIMAttributeUtils.SCIM_SCHEMA_TYPE_DEFAULT); + s.setType(type); }); } diff --git a/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTests.java b/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTests.java index 70b5967..427d10f 100644 --- a/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTests.java +++ b/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTests.java @@ -223,6 +223,7 @@ private static Uid createUser(final UUID uid, final String... groups) { SCIMv2ConnectorTestsUtils.VALUE_PHONE_NUMBER)); userAttrs.add(AttributeBuilder.build(SCIMv2ConnectorTestsUtils.USER_ATTRIBUTE_PHONE_OTHER_PRIMARY, false)); userAttrs.add(AttributeBuilder.build(SCIMAttributeUtils.USER_ATTRIBUTE_ACTIVE, true)); + userAttrs.add(AttributeBuilder.build(SCIMAttributeUtils.SCIM_USER_ROLES + ".default.value", "mytestrole")); userAttrs.add(password); if (PROPS.containsKey("auth.defaultEntitlement") && StringUtil.isNotBlank( @@ -331,6 +332,7 @@ private static Set updateUserAttributes(final Uid created, final Stri userAttrs.add(AttributeBuilder.build(SCIMv2ConnectorTestsUtils.USER_ATTRIBUTE_ENTITLEMENTS_DEFAULT_VALUE, PROPS.getProperty("auth.defaultEntitlement"))); } + userAttrs.add(AttributeBuilder.build(SCIMAttributeUtils.SCIM_USER_ROLES + ".default.value", "mytestrole")); // custom attributes addCustomAttributes(userAttrs); @@ -481,6 +483,8 @@ private static SCIMv2User readUser(final String id, final SCIMv2Client client) SCIMv2ConnectorTestsUtils.USER_ATTRIBUTE_EMAIL_WORK_VALUE)); assertTrue(SCIMv2ConnectorTestsUtils.hasAttribute(toAttributes, SCIMAttributeUtils.SCIM_USER_SCHEMAS)); assertTrue(SCIMv2ConnectorTestsUtils.hasAttribute(toAttributes, SCIMAttributeUtils.USER_ATTRIBUTE_ACTIVE)); + assertTrue(SCIMv2ConnectorTestsUtils.hasAttribute(toAttributes, + SCIMAttributeUtils.SCIM_USER_ROLES + ".default.value", "mytestrole")); if (PROPS.containsKey("auth.defaultEntitlement") && StringUtil.isNotBlank( PROPS.getProperty("auth.defaultEntitlement"))) { assertTrue(SCIMv2ConnectorTestsUtils.containsAttribute(toAttributes, @@ -1044,8 +1048,8 @@ public void search() { SearchResult result = FACADE.search(ObjectClass.ACCOUNT, null, handler, new OperationOptionsBuilder().setAttributesToGet("name", "emails.work.value", "name.familyName", - "displayName", "active", "entitlements.default.value", "entitlements", - "urn:mem:params:scim:schemas:extension:LuckyNumberExtension:luckyNumber", + "displayName", "active", "entitlements.default.value", "roles.default.value", "roles", + "entitlements", "urn:mem:params:scim:schemas:extension:LuckyNumberExtension:luckyNumber", SCIMv2EnterpriseUser.SCHEMA_URI + ":employeeNumber", SCIMv2EnterpriseUser.SCHEMA_URI + ":manager.value").build()); assertNotNull(result); diff --git a/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTestsUtils.java b/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTestsUtils.java index fdcde1b..af6090f 100644 --- a/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTestsUtils.java +++ b/src/test/java/net/tirasa/connid/bundles/scim/v2/SCIMv2ConnectorTestsUtils.java @@ -15,6 +15,7 @@ */ package net.tirasa.connid.bundles.scim.v2; +import java.util.List; import java.util.Map; import java.util.Set; import net.tirasa.connid.bundles.scim.common.SCIMConnectorConfiguration; @@ -156,10 +157,11 @@ public static boolean isConfigurationValid(final SCIMConnectorConfiguration conn return true; } - public static boolean hasAttribute(final Set attrs, final String name) { + public static boolean hasAttribute(final Set attrs, final String name, final Object... values) { for (Attribute attr : attrs) { if (attr.getName().equals(name)) { - return true; + return values.length == 0 || (attr.getValue() != null && !attr.getValue().isEmpty() && attr.getValue() + .containsAll(List.of(values))); } } return false;