Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PATCH add/remove/replace extensions using path to extension but not to exact attribute #694

Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,23 @@ private <T extends ScimResource> void apply(T source, Map<String, Object> source
// if the attribute has a URN, assume it's an extension that URN does not match the baseUrn
if (attributeReference.hasUrn() && !attributeReference.getUrn().equals(source.getBaseUrn())) {
Schema schema = this.schemaRegistry.getSchema(attributeReference.getUrn());
Attribute attribute = schema.getAttribute(attributeReference.getAttributeName());
checkMutability(schema.getAttributeFromPath(attributeReference.getFullAttributeName()));

patchOperationHandler.applyExtensionValue(source, sourceAsMap, schema, attribute, valuePathExpression, attributeReference.getUrn(), patchOperation.getValue());
if (schema != null) {
Attribute attribute = schema.getAttribute(attributeReference.getAttributeName());
checkMutability(schema.getAttributeFromPath(attributeReference.getFullAttributeName()));

patchOperationHandler.applyExtensionValue(source, sourceAsMap, schema, attribute, valuePathExpression, attributeReference.getUrn(), patchOperation.getValue());
} else {
// If schema is null, it's either the root of an extension, or an invalid patch path
// It's not possible from the antlr parser to tell the diff between 'this:is:extension:urn' and 'this:is:extension:urn:attribute'
jasonfagerberg-toast marked this conversation as resolved.
Show resolved Hide resolved
// Check if the fully qualified attribute is a valid schema
schema = this.schemaRegistry.getSchema(attributeReference.getFullyQualifiedAttributeName());
if (schema == null) {
throw new IllegalArgumentException("Invalid attribute path found in patch request: " + attributeReference);
}

patchOperationHandler.applyRootExtensionValue(source, sourceAsMap, schema, patchOperation.getValue());
}
} else {
Schema schema = this.schemaRegistry.getSchema(source.getBaseUrn());
Attribute attribute = schema.getAttribute(attributeReference.getAttributeName());
Expand Down Expand Up @@ -229,6 +242,28 @@ default <T extends ScimResource> void applyExtensionValue(final T source, Map<St
}
}

default <T extends ScimResource> void applyRootExtensionValue(final T source, Map<String, Object> sourceAsMap, Schema schema, Object value) {

// root extension object found, per RFC, the value of the patch must be a Map
if (!(value instanceof Map)) {
throw new IllegalArgumentException("Invalid patch value for root of extension, expected map");
}

// loop through each attribute and update them
Map<String, ?> mapValue = (Map<String, ?>) value;
for (Map.Entry<String, ?> entry : mapValue.entrySet()) {
String attributeName = entry.getKey();
Attribute attribute = schema.getAttribute(attributeName);
checkMutability(attribute);

// recreate the valuePathExpression using each child attribute
ValuePathExpression extensionValuePathExpression = new ValuePathExpression(new AttributeReference(schema.getUrn(), attributeName));

// now update the sourceAsMap
applyExtensionValue(source, sourceAsMap, schema, attribute, extensionValuePathExpression, schema.getUrn(), entry.getValue());
}
}

void applySingleValue(Map<String, Object> sourceAsMap, Attribute attribute, AttributeReference attributeReference, Object value);
<T extends ScimResource> void applyMultiValue(final T source, Map<String, Object> sourceAsMap, Schema schema, Attribute attribute, AttributeReference attributeReference, Object value);
<T extends ScimResource> void applyMultiValue(final T source, Map<String, Object> sourceAsMap, Schema schema, Attribute attribute, ValuePathExpression valuePathExpression, Object value);
Expand Down Expand Up @@ -329,6 +364,21 @@ public <T extends ScimResource> void applyMultiValue(T source, Map<String, Objec

private static class ReplaceOperationHandler implements PatchOperationHandler {

@Override
public <T extends ScimResource> void applyExtensionValue(T source, Map<String, Object> sourceAsMap, Schema schema, Attribute attribute, ValuePathExpression valuePathExpression, String urn, Object value) {

// add the extension URN
Collection<String> schemas = (Collection<String>) sourceAsMap.get("schemas");
schemas.add(urn);

// if the extension object does not yet exist, create it
if (!sourceAsMap.containsKey(urn)) {
sourceAsMap.put(urn, new HashMap<>());
}

PatchOperationHandler.super.applyExtensionValue(source, sourceAsMap, schema, attribute, valuePathExpression, urn, value);
}

@Override
public void applySingleValue(Map<String, Object> sourceAsMap, Attribute attribute, AttributeReference attributeReference, Object value) {
if (attributeReference.hasSubAttribute()) {
Expand Down Expand Up @@ -399,6 +449,22 @@ public <T extends ScimResource> void applyValue(final T source, Map<String, Obje
}
}

@Override
public <T extends ScimResource> void applyRootExtensionValue(T source, Map<String, Object> sourceAsMap, Schema schema, Object value) {

// remove the whole extension if value is null
if (value == null) {
// remove the schema definition
Collection<String> schemas = (Collection<String>) sourceAsMap.get("schemas");
schemas.remove(schema.getUrn());

// remove the root extension object
sourceAsMap.remove(schema.getUrn());
} else {
throw new IllegalArgumentException("Unsupported remove operation, expected patch value to be null when removing full extension object.");
}
}

@Override
public void applySingleValue(Map<String, Object> sourceAsMap, Attribute attribute, AttributeReference attributeReference, Object value) {
if (attributeReference.hasSubAttribute()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.apache.directory.scim.core.repository;

import java.util.HashMap;
import lombok.SneakyThrows;
import org.apache.directory.scim.core.schema.SchemaRegistry;
import org.apache.directory.scim.spec.extension.EnterpriseExtension;
Expand Down Expand Up @@ -56,6 +57,43 @@ public PatchHandlerTest() {
this.patchHandler = new DefaultPatchHandler(schemaRegistry);
}

@Test
public void applyReplaceEntireEnterpriseExtension() {
String enterpriseExtensionUrn = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
Map<String, String> enterpriseExtensionValue = new HashMap<>();
enterpriseExtensionValue.put("costCenter", "New Cost Center");
enterpriseExtensionValue.put("department", "New Department");
jasonfagerberg-toast marked this conversation as resolved.
Show resolved Hide resolved
PatchOperation op = patchOperation(REPLACE, enterpriseExtensionUrn, enterpriseExtensionValue);
ScimUser updatedUser = patchHandler.apply(user(), List.of(op));
EnterpriseExtension actual = (EnterpriseExtension) updatedUser.getExtension("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User");
jasonfagerberg-toast marked this conversation as resolved.
Show resolved Hide resolved
assertThat(actual).isNotNull();
assertThat(actual.getCostCenter()).isEqualTo("New Cost Center");
assertThat(actual.getDepartment()).isEqualTo("New Department");
}

@Test
public void applyReplaceEntireEnterpriseExtensionWithNullPath() {
String enterpriseExtensionUrn = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
Map<String, String> enterpriseExtensionValue = Map.of(
"costCenter", "New Cost Center",
"department", "New Department"
);
PatchOperation op = patchOperation(REPLACE, null, Map.of(enterpriseExtensionUrn, enterpriseExtensionValue));
ScimUser updatedUser = patchHandler.apply(user(), List.of(op));
EnterpriseExtension actual = (EnterpriseExtension) updatedUser.getExtension("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User");
assertThat(actual).isNotNull();
assertThat(actual.getCostCenter()).isEqualTo("New Cost Center");
assertThat(actual.getDepartment()).isEqualTo("New Department");
}

@Test
public void applyRemoveEntireEnterpriseExtension() {
String enterpriseExtensionUrn = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
PatchOperation op = patchOperation(REMOVE, enterpriseExtensionUrn, null);
ScimUser updatedUser = patchHandler.apply(user(), List.of(op));
assertThat(updatedUser.getExtension("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User")).isNull();
}

@Test
public void applyReplaceUserName() {
String newUserName = "[email protected]";
Expand Down