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
10 changes: 4 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ jobs:
build_and_test_cedar_java_ffi:
name: Rust project - latest
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- name: Checkout Cedar Examples
uses: actions/checkout@v3
Expand All @@ -24,7 +20,7 @@ jobs:
ref: release/2.3.x
path: ./cedar
- name: rustup
run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
run: rustup update stable && rustup default stable
- name: cargo fmt
working-directory: ./CedarJavaFFI
run: cargo fmt --all --check
Expand All @@ -34,7 +30,9 @@ jobs:
run: bash config.sh run_int_tests
- name: cargo rustc
working-directory: ./CedarJavaFFI
run: RUSTFLAGS="-D warnings -F unsafe-code" cargo build --verbose
run: |
sed -i '1i #![allow(text_direction_codepoint_in_literal)]' ../cedar/cedar-policy-validator/src/lib.rs
cargo build --verbose
- name: cargo test
working-directory: ./CedarJavaFFI
run: cargo test --verbose
Expand Down
14 changes: 12 additions & 2 deletions CedarJava/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ plugins {
id 'signing'
}

sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17

/*
Applies community Gradle plugins, usually added as build-tools in Config.
*/
Expand All @@ -38,11 +41,18 @@ apply plugin: 'com.github.spotbugs'

apply plugin: 'org.owasp.dependencycheck'

check.dependsOn dependencyCheckAnalyze
if (System.getenv('NVD_API_KEY')) {
check.dependsOn dependencyCheckAnalyze
}
dependencyCheck {
format='HTML'
failBuildOnCVSS=7
suppressionFile='suppressions.xml'
if (System.getenv('NVD_API_KEY')) {
nvd {
apiKey = System.getenv('NVD_API_KEY')
}
}
}

/*
Expand Down Expand Up @@ -116,7 +126,7 @@ publishing {
from components.java
groupId = 'com.cedarpolicy'
artifactId = 'cedar-java'
version = '2.3.3'
version = '2.3.6'
pom {
name = 'CedarJava'
description = 'Java bindings for Cedar policy language.'
Expand Down
6 changes: 2 additions & 4 deletions CedarJava/config.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@ if [ "$(uname)" == "Darwin" ]; then
else
ffi_lib_str=" environment 'CEDAR_JAVA_FFI_LIB', '"$parent_dir"/CedarJavaFFI/target/debug/libcedar_java_ffi.so'"
fi
sed "101s;.*;$ffi_lib_str;" "build.gradle" > new_build.gradle
mv new_build.gradle build.gradle
sed -i.bak "s|.*environment 'CEDAR_JAVA_FFI_LIB'.*|$ffi_lib_str|" "build.gradle" && rm -f build.gradle.bak

# In CI, we need to pull the latest cedar-policy to match the latest cedar-integration-tests
# We require that integration tests be run
# Outside of CI, we can skip the integration tests (run script with no args)
# If you call this script with `run_int_tests`, we assume you have `cedar` checkout out in the `cedar-java` dir
if [ "$#" -ne 0 ] && [ "$1" == "run_int_tests" ]; then
integration_tests_str=" environment 'CEDAR_INTEGRATION_TESTS_ROOT', '"$parent_dir"/cedar/cedar-integration-tests'"
sed "100s;.*;$integration_tests_str;" "build.gradle" > new_build.gradle
mv new_build.gradle build.gradle
sed -i.bak "s|.*environment.*CEDAR_INTEGRATION_TESTS_ROOT.*|$integration_tests_str|" "build.gradle" && rm -f build.gradle.bak

export MUST_RUN_CEDAR_INTEGRATION_TESTS=1

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"));
}

}
Loading
Loading