Skip to content

Commit

Permalink
Add unknown value for cedar partial evaluation (#259)
Browse files Browse the repository at this point in the history
Signed-off-by: Mohamed Amine Ouali <[email protected]>
  • Loading branch information
amzn-mdamine authored Nov 21, 2024
1 parent 1c09e46 commit 22f569b
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.cedarpolicy.value.PrimBool;
import com.cedarpolicy.value.PrimLong;
import com.cedarpolicy.value.PrimString;
import com.cedarpolicy.value.Unknown;
import com.cedarpolicy.value.Value;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
Expand Down Expand Up @@ -120,11 +121,12 @@ public Value deserialize(JsonParser parser, DeserializationContext context) thro
throw new InvalidValueDeserializationException(parser,
"Not textual node: " + arg.toString(), node.asToken(), Map.class);
}

if (fn.textValue().equals("ip")) {
return new IpAddress(arg.textValue());
} else if (fn.textValue().equals("decimal")) {
return new Decimal(arg.textValue());
} else if (fn.textValue().equals("unknown")) {
return new Unknown(arg.textValue());
} else {
throw new InvalidValueDeserializationException(parser,
"Invalid function type: " + fn.toString(), node.asToken(), Map.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.cedarpolicy.value.PrimBool;
import com.cedarpolicy.value.PrimLong;
import com.cedarpolicy.value.PrimString;
import com.cedarpolicy.value.Unknown;
import com.cedarpolicy.value.Value;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
Expand Down Expand Up @@ -91,6 +92,16 @@ public void serialize(
jsonGenerator.writeString(value.toString());
jsonGenerator.writeEndObject();
jsonGenerator.writeEndObject();
} else if (value instanceof Unknown) {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName(EXTENSION_ESCAPE_SEQ);
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("fn");
jsonGenerator.writeString("unknown");
jsonGenerator.writeFieldName("arg");
jsonGenerator.writeString(value.toString());
jsonGenerator.writeEndObject();
jsonGenerator.writeEndObject();
} else {
// It is recommended that you extend the Value classes in
// main.java.com.cedarpolicy.model.value or that you convert your class to a CedarMap
Expand Down
85 changes: 85 additions & 0 deletions CedarJava/src/main/java/com/cedarpolicy/value/Unknown.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright Cedar Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.cedarpolicy.value;

import java.util.Objects;

import com.cedarpolicy.Experimental;
import com.cedarpolicy.ExperimentalFeature;


/**
* Represents a Cedar unknown extension value.
* This class can only be used with partial evaluation.
*/
@Experimental(ExperimentalFeature.PARTIAL_EVALUATION)
public class Unknown extends Value {


/**
* arg as a string.
*/
private final String arg;

/**
* Construct Unknown.
*
* @param arg for the unknown extension
*/
public Unknown(String arg) throws NullPointerException, IllegalArgumentException {
this.arg = arg;
}

/**
* Convert Decimal to Cedar expr that can be used in a Cedar policy.
*/
@Override
public String toCedarExpr() {
return "Unknown(\"" + arg + "\")";
}

/**
* Equals.
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Unknown unknown = (Unknown) o;
return arg.equals(unknown.arg);
}

/**
* Hash.
*/
@Override
public int hashCode() {
return Objects.hash(arg);
}

/**
* As a string.
*/
@Override
public String toString() {
return arg;
}
}
25 changes: 25 additions & 0 deletions CedarJava/src/test/java/com/cedarpolicy/AuthTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@
import com.cedarpolicy.model.policy.PolicySet;
import com.cedarpolicy.value.EntityTypeName;
import com.cedarpolicy.value.EntityUID;
import com.cedarpolicy.value.Unknown;
import com.cedarpolicy.value.Value;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

import static org.junit.jupiter.api.Assertions.*;

import java.util.Map;
import java.util.Set;

public class AuthTests {
Expand Down Expand Up @@ -100,6 +104,27 @@ public void residual() {
});
}

@Test
public void residualWithUnknownValue() {
var auth = new BasicAuthorizationEngine();
var alice = new EntityUID(EntityTypeName.parse("User").get(), "alice");
var view = new EntityUID(EntityTypeName.parse("Action").get(), "view");
Map<String, Value> context = Map.of("authenticated", new Unknown("AuthenticatedIsUnknown"));
var q = PartialAuthorizationRequest.builder().principal(alice).action(view).resource(alice).context(context).build();
var policies = new HashSet<Policy>();
policies.add(new Policy("permit(principal == User::\"alice\",action,resource) when{context.authenticated};", "p0"));
var policySet = new PolicySet(policies);
assumePartialEvaluation(() -> {
try {
final PartialAuthorizationResponse response = auth.isAuthorizedPartial(q, policySet, new HashSet<>());
assertNull(response.success.orElseThrow().getDecision());
assertEquals("p0", response.success.orElseThrow().getResiduals().entrySet().iterator().next().getKey());
} catch (Exception e) {
fail("error: " + e.toString());
}
});
}

private void assumePartialEvaluation(Executable executable) {
try {
executable.execute();
Expand Down
22 changes: 22 additions & 0 deletions CedarJava/src/test/java/com/cedarpolicy/JSONTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.cedarpolicy.value.PrimBool;
import com.cedarpolicy.value.PrimLong;
import com.cedarpolicy.value.PrimString;
import com.cedarpolicy.value.Unknown;
import com.cedarpolicy.value.Value;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
Expand Down Expand Up @@ -242,6 +243,27 @@ public void testList() {
assertJSONEqual(listJson, l);
}

@Test
public void testUnknown() {
Unknown unknown = new Unknown("test");
ObjectNode n = JsonNodeFactory.instance.objectNode();
ObjectNode inner = JsonNodeFactory.instance.objectNode();
inner.put("fn", "unknown");
inner.put("arg", "test");
n.replace("__extn", inner);
assertJSONEqual(n, unknown);
}

/** Tests deserialization of unknown value */
@Test
public void testDeserializationUnknown() throws JsonProcessingException {
String json = "{\"__extn\":{\"fn\":\"unknown\",\"arg\":\"test\"}}";
Value value = CedarJson.objectMapper().readValue(json, Value.class);
assertInstanceOf(Unknown.class, value);
Unknown unknown = (Unknown) value;
assertEquals("test", unknown.toString());
}

/** Tests deserialization of value that causes stack overflow */
@Test
public void testDeserializationStackOverflow() {
Expand Down

0 comments on commit 22f569b

Please sign in to comment.