Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run_cedar_java_reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
ref: ${{ inputs.cedar_policy_ref }}
path: ./cedar
- name: Prepare Rust Build
run: rustup install 1.80.0 && rustup default 1.80.0 && rustup component add rustfmt
run: rustup install stable && rustup default stable && rustup component add rustfmt
- name: Configure CedarJavaFFI for CI build
run: bash configure_ci_build.sh
- name: Check FFI Formatting
Expand Down
2 changes: 1 addition & 1 deletion CedarJava/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ publishing {
from components.java
groupId = 'com.cedarpolicy'
artifactId = 'cedar-java'
version = '3.4.0'
version = '3.4.1'

artifacts {
jar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ public void serialize(
} else if (value instanceof CedarMap) {
jsonGenerator.writeStartObject();
for (Map.Entry<String, Value> entry : ((CedarMap) value).entrySet()) {
jsonGenerator.writeObjectField(entry.getKey(), entry.getValue());
String key = entry.getKey();
if (ENTITY_ESCAPE_SEQ.equals(key) || EXTENSION_ESCAPE_SEQ.equals(key)) {
throw new InvalidValueSerializationException(
"CedarMap key \"" + key + "\" is reserved by the Cedar JSON protocol"
+ " and cannot be used as a record key.");
}
jsonGenerator.writeObjectField(key, entry.getValue());
}
jsonGenerator.writeEndObject();
} else if (value instanceof IpAddress) {
Expand Down
25 changes: 24 additions & 1 deletion CedarJava/src/main/java/com/cedarpolicy/value/CedarMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@

/** Represents a Cedar Map value. Maps support mapping strings to arbitrary values. */
public final class CedarMap extends Value implements Map<String, Value> {
/** Reserved keys in the Cedar JSON protocol that cannot be used as record keys. */
private static final String ENTITY_ESCAPE_SEQ = "__entity";
private static final String EXTENSION_ESCAPE_SEQ = "__extn";

/** Internal map data. */
private final java.util.Map<String, Value> map;

Expand All @@ -34,6 +38,13 @@ public final class CedarMap extends Value implements Map<String, Value> {
* @param source map to copy from
*/
public CedarMap(java.util.Map<String, Value> source) {
for (String key : source.keySet()) {
if (ENTITY_ESCAPE_SEQ.equals(key) || EXTENSION_ESCAPE_SEQ.equals(key)) {
throw new IllegalArgumentException(
"Key \"" + key + "\" is reserved by the Cedar JSON protocol"
+ " and cannot be used as a record key.");
}
}
this.map = new HashMap<>(source);
}

Expand Down Expand Up @@ -66,7 +77,7 @@ public int hashCode() {
public String toCedarExpr() {
return "{"
+ map.entrySet().stream()
.map(e -> '\"' + e.getKey() + "\": " + e.getValue().toCedarExpr())
.map(e -> '\"' + CedarStrings.escape(e.getKey()) + "\": " + e.getValue().toCedarExpr())
.collect(Collectors.joining(", "))
+ "}";
}
Expand Down Expand Up @@ -118,12 +129,24 @@ public Value put(String k, Value v) throws NullPointerException {
if (v == null) {
throw new NullPointerException("Attempt to put null value in CedarMap");
}
if (ENTITY_ESCAPE_SEQ.equals(k) || EXTENSION_ESCAPE_SEQ.equals(k)) {
throw new IllegalArgumentException(
"Key \"" + k + "\" is reserved by the Cedar JSON protocol"
+ " and cannot be used as a record key.");
}

return map.put(k, v);
}

@Override
public void putAll(Map<? extends String, ? extends Value> m) {
for (String key : m.keySet()) {
if (ENTITY_ESCAPE_SEQ.equals(key) || EXTENSION_ESCAPE_SEQ.equals(key)) {
throw new IllegalArgumentException(
"Key \"" + key + "\" is reserved by the Cedar JSON protocol"
+ " and cannot be used as a record key.");
}
}
map.putAll(m);
}

Expand Down
63 changes: 63 additions & 0 deletions CedarJava/src/main/java/com/cedarpolicy/value/CedarStrings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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;

/**
* Utility methods for working with Cedar string literals.
*/
public final class CedarStrings {

private CedarStrings() { }

/**
* Escape a string for safe inclusion in Cedar source code as a string literal.
* Handles backslashes, double quotes, and control characters recognized by Cedar.
*
* @param s the raw string value
* @return the escaped string (without surrounding quotes)
*/
public static String escape(String s) {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\\':
sb.append("\\\\");
break;
case '"':
sb.append("\\\"");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
case '\0':
sb.append("\\0");
break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ public String toString() {
/** To Cedar expr that can be used in a Cedar policy. */
@Override
public String toCedarExpr() {
return "\"" + value + "\"";
return "\"" + CedarStrings.escape(value) + "\"";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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;

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

import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;

import com.cedarpolicy.value.CedarMap;
import com.cedarpolicy.value.PrimString;
import com.cedarpolicy.value.Value;

public class CedarExprEscapingTests {

@Test
public void testPrimStringEscapesDoubleQuotes() {
PrimString s = new PrimString("x\" && false && \"x");
assertEquals("\"x\\\" && false && \\\"x\"", s.toCedarExpr());
}

@Test
public void testPrimStringEscapesBackslash() {
PrimString s = new PrimString("path\\to\\file");
assertEquals("\"path\\\\to\\\\file\"", s.toCedarExpr());
}

@Test
public void testPrimStringEscapesControlCharacters() {
PrimString s = new PrimString("line1\nline2\ttab\r\0");
assertEquals("\"line1\\nline2\\ttab\\r\\0\"", s.toCedarExpr());
}

@Test
public void testPrimStringPlainStringUnchanged() {
PrimString s = new PrimString("hello world");
assertEquals("\"hello world\"", s.toCedarExpr());
}

@Test
public void testPrimStringEmptyString() {
PrimString s = new PrimString("");
assertEquals("\"\"", s.toCedarExpr());
}

@Test
public void testCedarMapEscapesKeys() {
Map<String, Value> source = new HashMap<>();
source.put("key\"injection", new PrimString("value"));
CedarMap map = new CedarMap(source);
String expr = map.toCedarExpr();
assertTrue(expr.contains("key\\\"injection"));
assertFalse(expr.contains("key\"injection"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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;

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

import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;

import com.cedarpolicy.value.CedarMap;
import com.cedarpolicy.value.PrimString;
import com.cedarpolicy.value.Value;

public class CedarMapReservedKeyTests {

@Test
public void testPutRejectsEntityKey() {
CedarMap map = new CedarMap();
assertThrows(IllegalArgumentException.class, () -> {
map.put("__entity", new PrimString("spoofed"));
});
}

@Test
public void testPutRejectsExtnKey() {
CedarMap map = new CedarMap();
assertThrows(IllegalArgumentException.class, () -> {
map.put("__extn", new PrimString("spoofed"));
});
}

@Test
public void testConstructorRejectsEntityKey() {
Map<String, Value> source = new HashMap<>();
source.put("__entity", new PrimString("spoofed"));
assertThrows(IllegalArgumentException.class, () -> {
new CedarMap(source);
});
}

@Test
public void testConstructorRejectsExtnKey() {
Map<String, Value> source = new HashMap<>();
source.put("__extn", new PrimString("spoofed"));
assertThrows(IllegalArgumentException.class, () -> {
new CedarMap(source);
});
}

@Test
public void testPutAllRejectsEntityKey() {
CedarMap map = new CedarMap();
Map<String, Value> source = new HashMap<>();
source.put("safe", new PrimString("ok"));
source.put("__entity", new PrimString("spoofed"));
assertThrows(IllegalArgumentException.class, () -> {
map.putAll(source);
});
}

@Test
public void testPutAllRejectsExtnKey() {
CedarMap map = new CedarMap();
Map<String, Value> source = new HashMap<>();
source.put("__extn", new PrimString("spoofed"));
assertThrows(IllegalArgumentException.class, () -> {
map.putAll(source);
});
}

@Test
public void testNormalKeysStillWork() {
CedarMap map = new CedarMap();
assertDoesNotThrow(() -> {
map.put("entity", new PrimString("ok"));
map.put("__other", new PrimString("ok"));
map.put("_entity", new PrimString("ok"));
});
}
}
Loading