From 772b6e8b67c9cac90a597b31343cf8c1cc800c44 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 8 Aug 2024 11:02:21 -0700 Subject: [PATCH] Provide an overload to hide all fields on a message with ProtoTypeMask PiperOrigin-RevId: 660903826 --- .../test/java/dev/cel/bundle/CelImplTest.java | 47 +++++++++++++++++++ .../java/dev/cel/checker/ProtoTypeMask.java | 22 +++++++++ .../checker/ProtoTypeMaskTypeProvider.java | 9 +++- .../dev/cel/checker/ProtoTypeMaskTest.java | 8 ++++ .../ProtoTypeMaskTypeProviderTest.java | 40 +++++++++++++++- 5 files changed, 122 insertions(+), 4 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index ef751df18..ecbac380e 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -579,6 +579,53 @@ public void program_withProtoVars() throws Exception { .isEqualTo(true); } + @Test + public void program_withAllFieldsHidden_emptyMessageConstructionSuccess() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addMessageTypes(AttributeContext.getDescriptor()) + .setContainer("google.rpc.context.AttributeContext") + .addProtoTypeMasks( + ProtoTypeMask.ofAllFieldsHidden("google.rpc.context.AttributeContext")) + .build(); + + assertThat(cel.createProgram(cel.compile("AttributeContext{}").getAst()).eval()) + .isEqualTo(AttributeContext.getDefaultInstance()); + } + + @Test + public void compile_withAllFieldsHidden_selectHiddenField_throws() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addMessageTypes(AttributeContext.getDescriptor()) + .setContainer("google.rpc.context.AttributeContext") + .addProtoTypeMasks( + ProtoTypeMask.ofAllFieldsHidden("google.rpc.context.AttributeContext")) + .build(); + + CelValidationException e = + assertThrows( + CelValidationException.class, + () -> cel.compile("AttributeContext{ request: AttributeContext.Request{} }").getAst()); + assertThat(e).hasMessageThat().contains("undefined field 'request'"); + } + + @Test + public void compile_withAllFieldsHidden_selectHiddenFieldOnVar_throws() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addMessageTypes(AttributeContext.getDescriptor()) + .setContainer("google.rpc.context.AttributeContext") + .addProtoTypeMasks( + ProtoTypeMask.ofAllFieldsHidden("google.rpc.context.AttributeContext")) + .addVar("attr_ctx", StructTypeReference.create("google.rpc.context.AttributeContext")) + .build(); + + CelValidationException e = + assertThrows(CelValidationException.class, () -> cel.compile("attr_ctx.source").getAst()); + assertThat(e).hasMessageThat().contains("undefined field 'source'"); + } + @Test public void program_withNestedRestrictedProtoVars() throws Exception { Cel cel = diff --git a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java index 1a129a91f..c019604d5 100644 --- a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java +++ b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java @@ -38,6 +38,13 @@ public abstract class ProtoTypeMask { /** WILDCARD_FIELD indicates that all fields within the proto type are visible. */ static final String WILDCARD_FIELD = "*"; + /** HIDDEN_FIELD indicates that all fields within the proto type are not visible. */ + static final String HIDDEN_FIELD = "!"; + + private static final FieldMask HIDDEN_FIELD_MASK = + FieldMask.newBuilder().addPaths(HIDDEN_FIELD).build(); + + private static final FieldPath HIDDEN_FIELD_PATH = FieldPath.of(HIDDEN_FIELD); private static final FieldMask WILDCARD_FIELD_MASK = FieldMask.newBuilder().addPaths(WILDCARD_FIELD).build(); private static final FieldPath WILDCARD_FIELD_PATH = FieldPath.of(WILDCARD_FIELD); @@ -52,6 +59,10 @@ boolean areAllFieldPathsExposed() { return getFieldPathsExposed().stream().allMatch(fp -> fp.equals(WILDCARD_FIELD_PATH)); } + boolean areAllFieldPathsHidden() { + return getFieldPathsExposed().stream().allMatch(fp -> fp.equals(HIDDEN_FIELD_PATH)); + } + public ProtoTypeMask withFieldsAsVariableDeclarations() { return new AutoValue_ProtoTypeMask(getTypeName(), getFieldPathsExposed(), true); } @@ -98,6 +109,17 @@ public static ProtoTypeMask ofAllFields(String fullyQualifiedTypeName) { return of(fullyQualifiedTypeName, WILDCARD_FIELD_MASK); } + /** + * Construct a new {@code ProtoTypeMask} which hides all fields in the given {@code typeName} for + * use within CEL expressions. + * + *

The {@code typeName} should be a fully-qualified path, e.g., {@code + * "google.rpc.context.AttributeContext"}. + */ + public static ProtoTypeMask ofAllFieldsHidden(String fullyQualifiedTypeName) { + return of(fullyQualifiedTypeName, HIDDEN_FIELD_MASK); + } + /** * FieldPath is the equivalent of a field selection represented within a {@link FieldMask#path}. */ diff --git a/checker/src/main/java/dev/cel/checker/ProtoTypeMaskTypeProvider.java b/checker/src/main/java/dev/cel/checker/ProtoTypeMaskTypeProvider.java index 61511c38d..025dfaf1a 100644 --- a/checker/src/main/java/dev/cel/checker/ProtoTypeMaskTypeProvider.java +++ b/checker/src/main/java/dev/cel/checker/ProtoTypeMaskTypeProvider.java @@ -92,11 +92,16 @@ private static ImmutableMap computeVisibleFieldsMap( CelTypeProvider delegateProvider, ImmutableSet protoTypeMasks) { Map> fieldMap = new HashMap<>(); for (ProtoTypeMask typeMask : protoTypeMasks) { - Optional rootType = delegateProvider.findType(typeMask.getTypeName()); - checkArgument(rootType.isPresent(), "message not registered: %s", typeMask.getTypeName()); + String typeName = typeMask.getTypeName(); + Optional rootType = delegateProvider.findType(typeName); + checkArgument(rootType.isPresent(), "message not registered: %s", typeName); if (typeMask.areAllFieldPathsExposed()) { continue; } + if (typeMask.areAllFieldPathsHidden()) { + fieldMap.put(typeName, ImmutableSet.of()); + continue; + } // Unroll the type(messageType) to just messageType. CelType type = rootType.get(); checkArgument(type instanceof ProtoMessageType, "type is not a protobuf: %s", type.name()); diff --git a/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTest.java b/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTest.java index f937dbdc2..89241652c 100644 --- a/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTest.java +++ b/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTest.java @@ -85,6 +85,14 @@ public void ofTypeWithFieldMask_invalidMask() { () -> ProtoTypeMask.of("test", FieldMask.newBuilder().addPaths("").build())); } + @Test + public void ofAllFieldsHidden() { + ProtoTypeMask typeExpr = ProtoTypeMask.ofAllFieldsHidden("google.rpc.context.AttributeContext"); + assertThat(typeExpr.areAllFieldPathsExposed()).isFalse(); + assertThat(typeExpr.getFieldPathsExposed()) + .containsExactly(FieldPath.of(ProtoTypeMask.HIDDEN_FIELD)); + } + @Test public void withFieldsAsVariableDeclarations() { assertThat(ProtoTypeMask.ofAllFields("google.type.Expr").fieldsAreVariableDeclarations()) diff --git a/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTypeProviderTest.java b/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTypeProviderTest.java index bf41c1e20..b4b52bd26 100644 --- a/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTypeProviderTest.java +++ b/checker/src/test/java/dev/cel/checker/ProtoTypeMaskTypeProviderTest.java @@ -28,6 +28,7 @@ import dev.cel.common.types.ProtoMessageType; import dev.cel.common.types.ProtoMessageTypeProvider; import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructType.Field; import java.util.Arrays; import java.util.Optional; import org.junit.Test; @@ -72,7 +73,7 @@ public void lookupFieldNames_noProtoDecls() { ProtoTypeMaskTypeProvider protoTypeMaskProvider = new ProtoTypeMaskTypeProvider(celTypeProvider, ImmutableSet.of()); ProtoMessageType protoType = assertTypeFound(protoTypeMaskProvider, ATTRIBUTE_CONTEXT_TYPE); - assertThat(protoType.fields().stream().map(f -> f.name()).collect(toImmutableList())) + assertThat(protoType.fields().stream().map(Field::name).collect(toImmutableList())) .containsExactly( "resource", "request", @@ -87,6 +88,41 @@ public void lookupFieldNames_noProtoDecls() { assertThat(protoType).isSameInstanceAs(origProtoType); } + @Test + public void lookupFieldNames_allFieldsHidden() { + CelTypeProvider celTypeProvider = + new ProtoMessageTypeProvider(ImmutableSet.of(AttributeContext.getDescriptor())); + ProtoTypeMaskTypeProvider protoTypeMaskProvider = + new ProtoTypeMaskTypeProvider( + celTypeProvider, + ImmutableSet.of(ProtoTypeMask.ofAllFieldsHidden(ATTRIBUTE_CONTEXT_TYPE))); + + ProtoMessageType protoType = assertTypeFound(protoTypeMaskProvider, ATTRIBUTE_CONTEXT_TYPE); + assertThat(protoType.fieldNames()).isEmpty(); + ProtoMessageType origProtoType = assertTypeFound(celTypeProvider, ATTRIBUTE_CONTEXT_TYPE); + assertThat(protoType).isNotSameInstanceAs(origProtoType); + } + + @Test + public void protoTypeMaskProvider_hiddenFieldSentinelCharOnSubPath_throws() { + CelTypeProvider celTypeProvider = + new ProtoMessageTypeProvider(ImmutableSet.of(AttributeContext.getDescriptor())); + + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> + new ProtoTypeMaskTypeProvider( + celTypeProvider, + ImmutableSet.of( + ProtoTypeMask.of( + "google.rpc.context.AttributeContext", + FieldMask.newBuilder().addPaths("resource.!").build())))); + assertThat(e) + .hasMessageThat() + .contains("message google.rpc.context.AttributeContext.Resource does not declare field: !"); + } + @Test public void lookupFieldNames_fullProtoDecl() { CelTypeProvider celTypeProvider = @@ -263,7 +299,7 @@ private ProtoMessageType assertTypeFound(CelTypeProvider celTypeProvider, String private void assertTypeHasFields(ProtoMessageType protoType, ImmutableSet fields) { ImmutableSet typeFieldNames = - protoType.fields().stream().map(f -> f.name()).collect(toImmutableSet()); + protoType.fields().stream().map(Field::name).collect(toImmutableSet()); assertThat(typeFieldNames).containsExactlyElementsIn(fields); }