diff --git a/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java b/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java index 7e44e4d8..44c9ab40 100644 --- a/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java +++ b/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java @@ -23,6 +23,7 @@ public class UUIDBuiltins { private static final long UUID_EPOCH_OFFSET_100NS = 122192928000000000L; private static final String RFC4122 = "uuid.rfc4122"; + private static final String URN_PREFIX = "urn:uuid:"; private static final SecureRandom RANDOM = new SecureRandom(); private static final Pattern CANONICAL_UUID_PATTERN = Pattern.compile( @@ -137,16 +138,16 @@ private String normalize(String input) { value = input; break; case 38: - if (!input.startsWith("{") || !input.endsWith("}")) { - return null; - } + // google/uuid only examines the middle 36 bytes of the "Microsoft style" + // form, so the surrounding bytes are not required to be braces. value = input.substring(1, input.length() - 1); break; case 45: - if (!input.startsWith("urn:uuid:")) { + // google/uuid compares the prefix with strings.EqualFold. + if (!input.regionMatches(true, 0, URN_PREFIX, 0, URN_PREFIX.length())) { return null; } - value = input.substring("urn:uuid:".length()); + value = input.substring(URN_PREFIX.length()); break; default: return null; diff --git a/opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java b/opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java index 9106e974..e45acb56 100644 --- a/opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java +++ b/opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java @@ -84,6 +84,44 @@ void parseReturnsUndefinedForInvalidUuid() { assertNull(builtins.parse(null, new RegoValue[] {new RegoString("123")})); } + /** + * google/uuid compares the urn prefix with {@code strings.EqualFold}, so any casing parses. Not + * covered by the upstream uuid fixtures; verified with {@code opa eval} against OPA 1.19.0. + */ + @Test + void parsesUrnPrefixCaseInsensitively() { + for (String prefix : new String[] {"urn:uuid:", "URN:UUID:", "Urn:Uuid:"}) { + RegoObject result = + (RegoObject) + builtins.parse( + null, + new RegoValue[] {new RegoString(prefix + "000003e8-48b9-21ee-b200-325096b39f47")}); + + assertEquals(RegoInt32.of(2), result.getProperty("version"), prefix); + assertEquals(new RegoString("RFC4122"), result.getProperty("variant"), prefix); + assertEquals(new RegoBigInt(1000L), result.getProperty("id"), prefix); + } + } + + /** + * google/uuid only examines the middle 36 bytes of the 38-byte "Microsoft style" form, so the + * surrounding bytes are not required to be braces — {@code opa eval} accepts all of these. + */ + @Test + void parsesThirtyEightByteFormWithoutRequiringBraces() { + for (String input : + new String[] { + "{000003e8-48b9-21ee-b200-325096b39f47}", + "(000003e8-48b9-21ee-b200-325096b39f47)", + "X000003e8-48b9-21ee-b200-325096b39f47Y", + "{000003e8-48b9-21ee-b200-325096b39f47X" + }) { + RegoObject result = (RegoObject) builtins.parse(null, new RegoValue[] {new RegoString(input)}); + + assertEquals(RegoInt32.of(2), result.getProperty("version"), input); + } + } + @Test void parseRejectsLenientJavaUuidFieldWidths() { assertNull(builtins.parse(null, new RegoValue[] {new RegoString("1-1-1-1-1")})); diff --git a/opa-evaluator/src/test/resources/compliance/known-missing-builtins.txt b/opa-evaluator/src/test/resources/compliance/known-missing-builtins.txt index 7979170a..7362d8ca 100644 --- a/opa-evaluator/src/test/resources/compliance/known-missing-builtins.txt +++ b/opa-evaluator/src/test/resources/compliance/known-missing-builtins.txt @@ -67,8 +67,5 @@ urlquery.decode_object urlquery.encode urlquery.encode_object -uuid.parse -uuid.rfc4122 - # Delete this line along with the walk implementation in #141. walk