Skip to content

Commit f4fbab6

Browse files
author
luca
committed
Fix PATCH with nested fields
This fixes an issue where PATCH requests with add operation and no path field containing nested fields (with '.') where not correctly applied.
1 parent 371487f commit f4fbab6

1 file changed

Lines changed: 223 additions & 6 deletions

File tree

scim2-sdk-common/src/main/java/com/unboundid/scim2/common/messages/PatchOperation.java

Lines changed: 223 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.fasterxml.jackson.databind.node.BooleanNode;
3030
import com.fasterxml.jackson.databind.node.DoubleNode;
3131
import com.fasterxml.jackson.databind.node.IntNode;
32+
import com.fasterxml.jackson.databind.node.JsonNodeType;
3233
import com.fasterxml.jackson.databind.node.LongNode;
3334
import com.fasterxml.jackson.databind.node.ObjectNode;
3435
import com.fasterxml.jackson.databind.node.TextNode;
@@ -47,9 +48,11 @@
4748

4849
import java.net.URI;
4950
import java.util.ArrayList;
51+
import java.util.Collections;
5052
import java.util.Date;
5153
import java.util.Iterator;
5254
import java.util.List;
55+
import java.util.stream.Collectors;
5356

5457
import static com.unboundid.scim2.common.utils.StaticUtils.toList;
5558

@@ -220,7 +223,7 @@ public void apply(@NotNull final ObjectNode node) throws ScimException
220223
}
221224
else
222225
{
223-
JsonUtils.addValue(path, node, value);
226+
applyAdd(node);
224227
}
225228

226229
addMissingSchemaUrns(node);
@@ -414,6 +417,90 @@ private void applyAddWithValueFilter(
414417
existingResource.replace(attributeName, attribute);
415418
}
416419

420+
private void applyAdd(final ObjectNode node) throws ScimException
421+
{
422+
Path path = null;
423+
if (getPath() == null)
424+
{
425+
if (value.getNodeType() == JsonNodeType.OBJECT)
426+
{
427+
boolean containsDot = false;
428+
429+
final List<String> keys = new ArrayList<>();
430+
final Iterator<String> iterator = value.fieldNames();
431+
iterator.forEachRemaining(e -> keys.add(e));
432+
433+
for (final String key : keys)
434+
{
435+
if (key.contains("."))
436+
{
437+
containsDot = true;
438+
break;
439+
}
440+
}
441+
442+
if (containsDot)
443+
{
444+
final ObjectNode removedValues = (ObjectNode) value.deepCopy();
445+
final List<Path> dotPaths = keys.stream().filter(key -> key.contains("."))
446+
.map(key -> {
447+
try
448+
{
449+
return Path.fromString(key);
450+
}
451+
catch (final BadRequestException e)
452+
{
453+
return Path.root();
454+
}
455+
}).collect(Collectors.toList());
456+
457+
for (final Path dotPath : dotPaths)
458+
{
459+
JsonNode dotPathJsonNode = value.get(dotPath.toString());
460+
if (SchemaUtils.isUrn(dotPath.toString()))
461+
{
462+
if (dotPathJsonNode.getNodeType() != JsonNodeType.OBJECT &&
463+
dotPathJsonNode.getNodeType() != JsonNodeType.ARRAY)
464+
{
465+
JsonUtils.addValue(dotPath, node, dotPathJsonNode);
466+
467+
removedValues.remove(dotPath.toString());
468+
}
469+
}
470+
else
471+
{
472+
473+
JsonUtils.addValue(dotPath, node, dotPathJsonNode);
474+
475+
removedValues.remove(dotPath.toString());
476+
}
477+
}
478+
479+
path = Path.root();
480+
JsonUtils.addValue(path, node, removedValues);
481+
}
482+
else
483+
{
484+
path = Path.root();
485+
486+
JsonUtils.addValue(path, node, value);
487+
}
488+
}
489+
else
490+
{
491+
path = Path.root();
492+
493+
JsonUtils.addValue(path, node, value);
494+
}
495+
}
496+
else
497+
{
498+
path = getPath();
499+
500+
JsonUtils.addValue(path, node, value);
501+
}
502+
}
503+
417504
/**
418505
* Indicates whether the provided object is equal to this add operation.
419506
*
@@ -627,11 +714,130 @@ public <T> List<T> getValues(@NotNull final Class<T> cls)
627714
@Override
628715
public void apply(@NotNull final ObjectNode node) throws ScimException
629716
{
630-
Path path = (getPath() == null) ? Path.root() : getPath();
631-
JsonUtils.replaceValue(path, node, value);
717+
applyReplace(node);
632718
addMissingSchemaUrns(node);
633719
}
634720

721+
private void applyReplace(final ObjectNode node) throws ScimException
722+
{
723+
Path path = null;
724+
List<Path> existingPaths = Collections.emptyList();
725+
List<Path> nonExistingPaths = Collections.emptyList();
726+
if (getPath() == null)
727+
{
728+
if (value.getNodeType() == JsonNodeType.OBJECT)
729+
{
730+
boolean containsDot = false;
731+
732+
final List<String> keys = new ArrayList<>();
733+
final Iterator<String> iterator = value.fieldNames();
734+
iterator.forEachRemaining(e -> keys.add(e));
735+
736+
for (final String key : keys)
737+
{
738+
if (key.contains("."))
739+
{
740+
containsDot = true;
741+
break;
742+
}
743+
}
744+
745+
if (containsDot)
746+
{
747+
existingPaths = keys.stream().filter(key ->
748+
{
749+
try
750+
{
751+
return JsonUtils.pathExists(Path.fromString(key), node);
752+
}
753+
catch (final BadRequestException e)
754+
{
755+
return false;
756+
}
757+
catch (final ScimException e)
758+
{
759+
return false;
760+
}
761+
}).map(key ->
762+
{
763+
try
764+
{
765+
return Path.fromString(key);
766+
}
767+
catch (final BadRequestException e)
768+
{
769+
return Path.root();
770+
}
771+
}).collect(Collectors.toList());
772+
773+
nonExistingPaths = keys.stream().filter(key ->
774+
{
775+
try
776+
{
777+
return !JsonUtils.pathExists(Path.fromString(key), node);
778+
}
779+
catch (final BadRequestException e)
780+
{
781+
return false;
782+
}
783+
catch (final ScimException e)
784+
{
785+
return false;
786+
}
787+
}).map(key ->
788+
{
789+
try
790+
{
791+
return Path.fromString(key);
792+
}
793+
catch (final BadRequestException e)
794+
{
795+
return Path.root();
796+
}
797+
}).collect(Collectors.toList());
798+
799+
if (existingPaths.size() <= 0)
800+
{
801+
path = Path.root();
802+
803+
JsonUtils.replaceValue(path, node, value);
804+
}
805+
else
806+
{
807+
for (final Path existingPath : existingPaths)
808+
{
809+
JsonUtils.replaceValue(existingPath, node, value.get(existingPath.toString()));
810+
}
811+
812+
for (final Path nonExistingPath : nonExistingPaths)
813+
{
814+
JsonUtils.replaceValue(nonExistingPath, node,
815+
value.get(nonExistingPath.toString()));
816+
}
817+
}
818+
}
819+
else
820+
{
821+
path = Path.root();
822+
823+
JsonUtils.replaceValue(path, node, value);
824+
}
825+
}
826+
else
827+
{
828+
path = Path.root();
829+
830+
JsonUtils.replaceValue(path, node, value);
831+
}
832+
}
833+
else
834+
{
835+
path = getPath();
836+
837+
JsonUtils.replaceValue(path, node, value);
838+
}
839+
}
840+
635841
/**
636842
* Indicates whether the provided object is equal to this replace operation.
637843
*
@@ -855,15 +1061,26 @@ else if(getPath().getSchemaUrn() != null)
8551061
private void addSchemaUrnIfMissing(@NotNull final ArrayNode schemas,
8561062
@NotNull final String schemaUrn)
8571063
{
858-
for(JsonNode node : schemas)
1064+
final String enterpriseUserUri = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
1065+
for (final JsonNode node : schemas)
8591066
{
860-
if(node.isTextual() && node.textValue().equalsIgnoreCase(schemaUrn))
1067+
if (node.isTextual()
1068+
&& (node.textValue().equalsIgnoreCase(schemaUrn)
1069+
|| node.textValue().toUpperCase().contains(enterpriseUserUri.toUpperCase())))
8611070
{
8621071
return;
8631072
}
8641073
}
8651074

866-
schemas.add(schemaUrn);
1075+
if (schemaUrn.toUpperCase().contains(enterpriseUserUri.toUpperCase())
1076+
&& schemaUrn.length() > enterpriseUserUri.length())
1077+
{
1078+
schemas.add(enterpriseUserUri);
1079+
}
1080+
else
1081+
{
1082+
schemas.add(schemaUrn);
1083+
}
8671084
}
8681085

8691086
/**

0 commit comments

Comments
 (0)