Compare commits

...

49 Commits

Author SHA1 Message Date
Anand Krishnamoorthi
c56da34843 chore: release (#335)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-22 14:05:26 -07:00
Anand Krishnamoorthi
61f82d1b34 fix: docs failing to build (#334)
Added #![cfg_attr(docsrs, feature(doc_cfg))]

fixes #333
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-10-22 13:37:31 -07:00
dependabot[bot]
00f45c70fe build(deps): update jsonschema requirement from 0.23.0 to 0.24.0 (#332)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.23.0...rust-v0.24.0)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 07:30:12 -07:00
Anand Krishnamoorthi
df73b20192 build(deps): update jsonschema requirement from 0.22.3 to 0.23.0 (#331) 2024-10-15 10:29:41 -07:00
Anand Krishnamoorthi
992b202f60 chore: release (#329)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-09 12:52:25 -07:00
Anand Krishnamoorthi
ce6ecd6fd6 feat: integer conversion functions for Value (#328)
closes #324

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-10-09 12:19:54 -07:00
Anand Krishnamoorthi
37262ccf8f chore: update to OPA v0.69.0 (#327)
Also fix CRLF vs LF related test failures in two doc tests on Windows

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-10-09 10:57:09 -07:00
dependabot[bot]
dcd040cf40 build(deps): update jsonschema requirement from 0.21.0 to 0.22.3 (#326)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.21.0...rust-v0.22.3)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 13:30:05 -07:00
dependabot[bot]
f0a3cf26a0 build(deps): update jsonschema requirement from 0.20.0 to 0.21.0 (#325)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.20.0...rust-v0.21.0)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 08:34:48 -07:00
Anand Krishnamoorthi
13d8289a58 chore: update to jsonschema 0.20.0 (#323) 2024-09-23 13:01:24 -07:00
Anand Krishnamoorthi
d2b27ee512 chore: release (#320)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-18 14:17:17 -07:00
Anand Krishnamoorthi
adb9da0c0c fix: Null terminate C# strings in Rust boundary (#318)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-09-18 13:58:51 -07:00
dependabot[bot]
dc0f48f6bc Update jsonschema requirement from 0.18.0 to 0.19.1 (#317)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.18.0...rust-v0.19.1)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 09:12:00 -07:00
dependabot[bot]
334db3d6ce Update chrono-tz requirement from 0.8.5 to 0.10.0 (#316)
Updates the requirements on [chrono-tz](https://github.com/chronotope/chrono-tz) to permit the latest version.
- [Release notes](https://github.com/chronotope/chrono-tz/releases)
- [Commits](https://github.com/chronotope/chrono-tz/compare/v0.8.5...v0.10.0)

---
updated-dependencies:
- dependency-name: chrono-tz
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 08:42:19 -07:00
Anand Krishnamoorthi
7565ec3ecf feat: or keyword (#315)
Add `or` operator to Rego languages. Available via `rego-extensions`
Cargo feature.

If the evaluated lhs value is not false, null or undefined it is returned.
Otherwise rhs is evaluated and returned.

or operator has least precedence, and is left-associative.

closes #314
2024-09-13 16:39:19 -07:00
Sumedh Alok Sharma
8498274356 Add tests for builtin strings::lower method (#313)
Signed-off-by: Sumedh Alok Sharma <sumsharma@microsoft.com>
2024-09-11 09:15:28 -07:00
Sumedh Alok Sharma
ecd341bbcc Add tests for builtin strings::indexof method (#311)
Signed-off-by: Sumedh Alok Sharma <sumsharma@microsoft.com>
2024-09-11 09:14:21 -07:00
Anand Krishnamoorthi
b6935d1add fix: Update readme with correct path to example policy (#312)
Also add test to lock down example policy path.
Also Fix clippy warning by using unwrap_or_default

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-09-10 11:47:46 -07:00
Anand Krishnamoorthi
502b830c19 chore: release (#310)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-04 10:22:55 -07:00
Anand Krishnamoorthi
8003cfc5b7 fix: Handle parsing corner cases (#309)
A block with a single or expression needs to be treated as a comprehension instead of a
set/array with 1 item. e.g.: {1 | 1 }, [2 | foo]

Allow successfully parsing object comprehensions as rule body
x if { 1:2 | 1 }

fixes #306, fixes #307

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-09-04 10:02:14 -07:00
Anand Krishnamoorthi
a4a80d7fc6 fix: Propagate errors encountered in argument evaluation (#308)
fixes #301

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-31 13:46:55 -07:00
Anand Krishnamoorthi
af5071446b feat: OPA v0.68.0. Engine::set_rego_v1 (#305)
Provide ability in the engine to treat subsequently loaded policies
as rego.v1.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-30 15:11:09 -07:00
Anand Krishnamoorthi
edd0ccca5b fix: Issues #302, #303 (#304)
Handle undefined values correctly in ordered-else. Previously an undefined value
in one of the blocks could cause the entire rule to evaluate to undefined.

Handle undefined values correctly in generic rule refs to prevent them from
propagating to output.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-29 09:47:45 -07:00
dependabot[bot]
fec6f8f8b4 Bump rexml in /bindings/ruby in the bundler group across 1 directory (#300)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.3 to 3.3.6
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.3...v3.3.6)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 14:13:29 -07:00
Anand Krishnamoorthi
dff65f0329 chore: release (#298)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-16 08:45:52 -07:00
dependabot[bot]
6bf40c7394 Update cbindgen requirement from 0.26.0 to 0.27.0 (#296)
Updates the requirements on [cbindgen](https://github.com/mozilla/cbindgen) to permit the latest version.
- [Release notes](https://github.com/mozilla/cbindgen/releases)
- [Changelog](https://github.com/mozilla/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/cbindgen/compare/0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: cbindgen
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 09:09:53 -07:00
Anand Krishnamoorthi
a488a84969 fix: Match OPA behavior for split (#295)
In case of empty delimiter, Rust's split returns leading and trailing
empty strings whereas Golang's doesn't.
Change behavior to match Golang/OPA.

fixes #291

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-08 14:53:01 -07:00
dependabot[bot]
e4a58ad1dc Bump rexml in /bindings/ruby in the bundler group across 1 directory (#294)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.2...v3.3.3)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 13:08:09 -07:00
dependabot[bot]
430a453fde Update csbindgen requirement from =1.9.0 to =1.9.3 (#292)
Updates the requirements on [csbindgen](https://github.com/Cysharp/csbindgen) to permit the latest version.
- [Release notes](https://github.com/Cysharp/csbindgen/releases)
- [Commits](https://github.com/Cysharp/csbindgen/compare/1.9.0...1.9.3)

---
updated-dependencies:
- dependency-name: csbindgen
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 11:39:56 -07:00
Anand Krishnamoorthi
ef549a6528 fix: Merge data to init document (#293)
Init document is the aggregated data documen that the user has
specified using multiple `add_data` calls. Each query evaluation
starts of by initializing the current data to the init document.

Previously `add_data` was incorrectly added to the current document,
causing the added data to be lost if the addition happened after query
evaluation.

With this fix, scenarios where data addition may be interspersed with
query evaluation calls are supported.

Also provide a get_data method to obtain the (init) data document.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-07 11:39:23 -07:00
Anand Krishnamoorthi
52afcbe5c5 chore: release (#289)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-28 13:19:09 +05:30
Anand Krishnamoorthi
f0576cef77 Update readme (#288)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-28 13:04:47 +05:30
Anand Krishnamoorthi
20eece58ed Update binding versions (#287)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-28 12:32:39 +05:30
Anand Krishnamoorthi
6599ce6001 feat: Update to opa v0.67.0 (#286)
Implement strings.count builtin

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-28 11:46:07 +05:30
Anand Krishnamoorthi
7095e269b7 fix: Handle aliases in scheduler (#285)
Earlier scheduler only recognized rules and would raise an
`unsafe var` error on alias.

Register alias var names to fix this.

fixes #284

Also fix clippy warning treated as error

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-27 23:26:53 +05:30
Stuart Neivandt
6e1f8cdb36 build.rs create hooks dir if not exists (#283) 2024-07-21 10:27:51 +05:30
Jie Yang
fb5151e0e4 add extension_list example (#281)
- Created an example of extension policy
- Added C# binding support of .NET framework 4.0 and created a Nuget
  spec for it.
- Added a pytest in python bindings to test the extension policy and the
  python binding
- Restructured the example and Csharp binding directories due to above
  changes.
- Added copyrights.
- Added a Windows workflow for .NET 4.0 build and test.
2024-07-16 11:08:37 +05:30
Anand Krishnamoorthi
37d283cb38 Fix build break (#278)
- Fix warning due to use of deprecated function.
  This was causing a build issue in the hava and csharp bindings
- Lock use of csbindgen@1.9.0
  The newer version 1.9.2  causes a "type of namespace C could not be fond" error
  In the generated code, struct inherits from C instead of uint

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-12 05:28:25 +05:30
dependabot[bot]
25dbd27d82 Update pyo3 requirement from 0.21.0 to 0.22.0 (#275)
Updates the requirements on [pyo3](https://github.com/pyo3/pyo3) to permit the latest version.
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 09:51:50 +05:30
Anand Krishnamoorthi
292948a694 Update to OPA v0.66.0 (#274)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-28 08:57:03 +05:30
Anand Krishnamoorthi
5a0048cd64 chore: release (#271)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-19 16:09:03 -07:00
Anand Krishnamoorthi
4a2a724a80 Fix c,cpp,no-std binding examples (#272)
- Use regorus_ffi in target_link_libraries instead of regorus-ffi.
  Something seems to have changed in corrosion-rs to need this.
- Workaround for cmake issue where FFI header may not be generated in time

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-19 16:01:29 -07:00
Anand Krishnamoorthi
46e28b36f8 feat: get_policies: Way to obtain policy files and content (#267)
closes #254

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-19 00:01:55 -07:00
Anand Krishnamoorthi
ee898e112e Update binding versions for next release (#270)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-18 23:39:12 -07:00
balcanuc
e6f2ec825d rename method from 'Clone' to 'clone' in 'Engine' class to match the java naming convention and definiont in the of java.lang.Object. (#268)
Co-authored-by: Cristi Balcanu <balcanu@amazon.com>
2024-06-18 20:46:21 -07:00
Anand Krishnamoorthi
45627aa64a Suppress clippy unused warning (#269)
The field will be used later.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-18 10:28:02 -07:00
Anand Krishnamoorthi
df98c8d168 Provide ability to get JSON representation of policy AST (#266)
closes #265

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-08 18:58:30 -07:00
Anand Krishnamoorthi
25902bab57 Update OPA tests to v0.65.0 (#264)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-04 20:19:35 -07:00
Anand Krishnamoorthi
e62bfdf161 Allow lexer to be used for other policy languages (#262)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-04 19:59:54 -07:00
77 changed files with 2834 additions and 496 deletions

33
.github/workflows/pr-extensions.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: tests/release
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build only std
run: cargo build -r --example regorus --no-default-features --features "std,rego-extensions"
- name: Doc Tests
run: cargo test -r --doc --features rego-extensions
- name: Run tests
run: cargo test -r --features rego-extensions
- name: Run example
run: cargo run --example regorus --features rego-extensions -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
- name: Run tests (ACI)
run: cargo test -r --test aci --features rego-extensions
- name: Run tests (KATA)
run: cargo test -r --test kata --features rego-extensions
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision,rego-extensions -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -30,6 +30,8 @@ jobs:
run: cargo test -r --doc
- name: Run tests
run: cargo test -r
- name: Run example
run: cargo run --example regorus -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
- name: Run tests (ACI)
run: cargo test -r --test aci
- name: Run tests (KATA)

View File

@@ -18,6 +18,11 @@ jobs:
- name: Setup gcc, g++, cmake, ninja
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
- name: Workaround to ensure that regorus.h is generated
run: |
cargo build -r
working-directory: ./bindings/ffi
- name: Test c binding
run: |
mkdir bindings/c/build

View File

@@ -19,8 +19,8 @@ jobs:
- name: Build
run: dotnet build
working-directory: ./bindings/csharp
working-directory: ./bindings/csharp/net8.0
- name: Run
run: LD_LIBRARY_PATH=. dotnet run
working-directory: ./bindings/csharp
working-directory: ./bindings/csharp/net8.0

28
.github/workflows/test-csharp40.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: bindings/csharp40
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "5.0.x"
- name: Build
run: dotnet build
working-directory: ./bindings/csharp/net40
- name: Run
run: dotnet run
working-directory: ./bindings/csharp/net40

View File

@@ -1,4 +1,4 @@
name: bindings/c-cpp
name: bindings/ffi
on:
push:

View File

@@ -32,6 +32,6 @@ jobs:
- name: Test jar
run: |
javac -cp target/regorus-java-0.1.5.jar Test.java
java -Djava.library.path=../../target/release -cp target/regorus-java-0.1.5.jar:. Test
javac -cp target/regorus-java-0.2.2.jar Test.java
java -Djava.library.path=../../target/release -cp target/regorus-java-0.2.2.jar:. Test
working-directory: ./bindings/java

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
# Generated by Cargo
# will have compiled files and executables
/target/
**/wheels/
**/__pycache__/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

View File

@@ -6,6 +6,93 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.7](https://github.com/microsoft/regorus/compare/regorus-v0.2.6...regorus-v0.2.7) - 2024-10-22
### Fixed
- docs failing to build ([#334](https://github.com/microsoft/regorus/pull/334))
### Other
- *(deps)* update jsonschema requirement from 0.23.0 to 0.24.0 ([#332](https://github.com/microsoft/regorus/pull/332))
- *(deps)* update jsonschema requirement from 0.22.3 to 0.23.0 ([#331](https://github.com/microsoft/regorus/pull/331))
## [0.2.6](https://github.com/microsoft/regorus/compare/regorus-v0.2.5...regorus-v0.2.6) - 2024-10-09
### Added
- integer conversion functions for Value ([#328](https://github.com/microsoft/regorus/pull/328))
### Other
- update to OPA v0.69.0 ([#327](https://github.com/microsoft/regorus/pull/327))
- *(deps)* update jsonschema requirement from 0.21.0 to 0.22.3 ([#326](https://github.com/microsoft/regorus/pull/326))
- *(deps)* update jsonschema requirement from 0.20.0 to 0.21.0 ([#325](https://github.com/microsoft/regorus/pull/325))
- update to jsonschema 0.20.0 ([#323](https://github.com/microsoft/regorus/pull/323))
## [0.2.5](https://github.com/microsoft/regorus/compare/regorus-v0.2.4...regorus-v0.2.5) - 2024-09-18
### Added
- or keyword ([#315](https://github.com/microsoft/regorus/pull/315))
### Fixed
- Null terminate C# strings in Rust boundary ([#318](https://github.com/microsoft/regorus/pull/318))
- Update readme with correct path to example policy ([#312](https://github.com/microsoft/regorus/pull/312))
### Other
- Update jsonschema requirement from 0.18.0 to 0.19.1 ([#317](https://github.com/microsoft/regorus/pull/317))
- Update chrono-tz requirement from 0.8.5 to 0.10.0 ([#316](https://github.com/microsoft/regorus/pull/316))
- Add tests for builtin strings::lower method ([#313](https://github.com/microsoft/regorus/pull/313))
- Add tests for builtin strings::indexof method ([#311](https://github.com/microsoft/regorus/pull/311))
## [0.2.4](https://github.com/microsoft/regorus/compare/regorus-v0.2.3...regorus-v0.2.4) - 2024-09-04
### Added
- OPA v0.68.0. Engine::set_rego_v1 ([#305](https://github.com/microsoft/regorus/pull/305))
### Fixed
- Handle parsing corner cases ([#309](https://github.com/microsoft/regorus/pull/309))
- Propagate errors encountered in argument evaluation ([#308](https://github.com/microsoft/regorus/pull/308))
- Issues [#302](https://github.com/microsoft/regorus/pull/302), [#303](https://github.com/microsoft/regorus/pull/303) ([#304](https://github.com/microsoft/regorus/pull/304))
## [0.2.3](https://github.com/microsoft/regorus/compare/regorus-v0.2.2...regorus-v0.2.3) - 2024-08-16
### Fixed
- Match OPA behavior for split ([#295](https://github.com/microsoft/regorus/pull/295))
- Merge data to init document ([#293](https://github.com/microsoft/regorus/pull/293))
### Other
- Update cbindgen requirement from 0.26.0 to 0.27.0 ([#296](https://github.com/microsoft/regorus/pull/296))
- Bump rexml in /bindings/ruby in the bundler group across 1 directory ([#294](https://github.com/microsoft/regorus/pull/294))
- Update csbindgen requirement from =1.9.0 to =1.9.3 ([#292](https://github.com/microsoft/regorus/pull/292))
## [0.2.2](https://github.com/microsoft/regorus/compare/regorus-v0.2.1...regorus-v0.2.2) - 2024-07-28
### Added
- Update to opa v0.67.0 ([#286](https://github.com/microsoft/regorus/pull/286))
### Fixed
- Handle aliases in scheduler ([#285](https://github.com/microsoft/regorus/pull/285))
### Other
- Update readme ([#288](https://github.com/microsoft/regorus/pull/288))
- Update binding versions ([#287](https://github.com/microsoft/regorus/pull/287))
- build.rs create hooks dir if not exists ([#283](https://github.com/microsoft/regorus/pull/283))
- add extension_list example ([#281](https://github.com/microsoft/regorus/pull/281))
- Fix build break ([#278](https://github.com/microsoft/regorus/pull/278))
- Update pyo3 requirement from 0.21.0 to 0.22.0 ([#275](https://github.com/microsoft/regorus/pull/275))
- Update to OPA v0.66.0 ([#274](https://github.com/microsoft/regorus/pull/274))
## [0.2.1](https://github.com/microsoft/regorus/compare/regorus-v0.2.0...regorus-v0.2.1) - 2024-06-19
### Added
- get_policies: Way to obtain policy files and content ([#267](https://github.com/microsoft/regorus/pull/267))
### Other
- Fix c,cpp,no-std binding examples ([#272](https://github.com/microsoft/regorus/pull/272))
- Update binding versions for next release ([#270](https://github.com/microsoft/regorus/pull/270))
- rename method from 'Clone' to 'clone' in 'Engine' class to match the java naming convention and definiont in the of java.lang.Object. ([#268](https://github.com/microsoft/regorus/pull/268))
- Suppress clippy unused warning ([#269](https://github.com/microsoft/regorus/pull/269))
- Provide ability to get JSON representation of policy AST ([#266](https://github.com/microsoft/regorus/pull/266))
- Update OPA tests to v0.65.0 ([#264](https://github.com/microsoft/regorus/pull/264))
- Allow lexer to be used for other policy languages ([#262](https://github.com/microsoft/regorus/pull/262))
## [0.2.0](https://github.com/microsoft/regorus/compare/regorus-v0.1.5...regorus-v0.2.0) - 2024-05-30
### Other
@@ -340,3 +427,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- LICENSE committed
- CODE_OF_CONDUCT.md committed
- Initial commit

View File

@@ -12,7 +12,7 @@ members = [
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.2.0"
version = "0.2.7"
edition = "2021"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"
@@ -27,6 +27,7 @@ doctest = false
default = ["full-opa", "arc"]
arc = ["scientific/arc"]
ast = []
base64 = ["dep:data-encoding"]
base64url = ["dep:data-encoding"]
coverage = []
@@ -66,7 +67,9 @@ full-opa = [
"time",
"uuid",
"urlquery",
"yaml"
"yaml",
#"rego-extensions"
]
# Features that can be used in no_std environments.
@@ -88,6 +91,9 @@ opa-no-std = [
"lazy_static/spin_no_std"
]
# Rego language extensions
rego-extensions = []
# This feature enables some testing utils for OPA tests.
opa-testutil = []
rand = ["dep:rand"]
@@ -114,9 +120,9 @@ semver = {version = "1.0.20", optional = true, default-features = false }
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
url = { version = "2.5.0", optional = true }
uuid = { version = "1.6.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.18.0", default-features = false, optional = true }
jsonschema = { version = "0.24.0", default-features = false, optional = true }
chrono = { version = "0.4.31", optional = true }
chrono-tz = { version = "0.8.5", optional = true }
chrono-tz = { version = "0.10.0", optional = true }
jsonwebtoken = { version = "9.2.0", optional = true }
itertools = { version = "0.13.0", default-features = false, optional = true }

View File

@@ -10,7 +10,7 @@ Regorus is also
- *cross-platform* - Written in platform-agnostic Rust.
- *no_std compatible* - Regorus can be used in `no_std` environments too. Most of the builtins are supported.
- *current* - We strive to keep Regorus up to date with latest OPA release. Regorus supports `import rego.v1`.
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.69.0](https://github.com/open-policy-agent/opa/releases/tag/v0.69.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
- *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust.
See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
Support for extensibility using other languages coming soon.
@@ -99,7 +99,7 @@ $ cargo build -r --example regorus --no-default-features; strip target/release/e
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
```
Regorus passes the [OPA v0.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
Regorus passes the [OPA v0.69.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
builtins. See [OPA Conformance](#opa-conformance) below.
## Bindings
@@ -184,11 +184,11 @@ This produces the following output
}
```
Next, evaluate a sample [policy](https://github.com/microsoft/regorus/blob/main/examples/example.rego) and [input](https://github.com/microsoft/regorus/blob/main/examples/input.json)
Next, evaluate a sample [policy](https://github.com/microsoft/regorus/blob/main/examples/server/allowed_server.rego) and [input](https://github.com/microsoft/regorus/blob/main/examples/server/input.json)
(borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)):
```bash
$ regorus eval -d examples/example.rego -i examples/input.json data.example
$ regorus eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
```
Finally, evaluate real-world [policies](tests/aci/) used in Azure Container Instances (ACI)
@@ -204,7 +204,7 @@ Regorus allows determining which lines of a policy have been executed using the
We can try it out using the `regorus` example program by passing in the `--coverage` flag.
```shell
$ regorus eval -d examples/example.rego -i examples/input.json data.example --coverage
$ regorus eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example --coverage
```
It produces the following coverage report which shows that all lines are executed except the line that sets `allow` to true.
@@ -276,7 +276,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
```
## OPA Conformance
Regorus has been verified to be compliant with [OPA v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0)
Regorus has been verified to be compliant with [OPA v0.69.0](https://github.com/open-policy-agent/opa/releases/tag/v0.69.0)
using a [test driver](https://github.com/microsoft/regorus/blob/main/tests/opa.rs) that loads and runs the OPA testsuite using Regorus, and verifies that expected outputs are produced.
The test driver can be invoked by running:

View File

@@ -38,4 +38,4 @@ corrosion_import_crate(
add_executable(regorus_test main.c)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus-ffi)
target_link_libraries(regorus_test regorus_ffi)

View File

@@ -31,4 +31,4 @@ corrosion_import_crate(
add_executable(regorus_test main.c)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus-ffi)
target_link_libraries(regorus_test regorus_ffi)

View File

@@ -31,4 +31,4 @@ corrosion_import_crate(
add_executable(regorus_test main.cpp)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus-ffi)
target_link_libraries(regorus_test regorus_ffi)

View File

@@ -1,220 +0,0 @@
using System.Text;
namespace Regorus
{
public class Exception : System.Exception
{
public Exception(string? message) : base(message) {}
}
public class Engine : ICloneable
{
unsafe private RegorusFFI.RegorusEngine* E;
public Engine()
{
unsafe
{
E = RegorusFFI.API.regorus_engine_new();
}
}
public object Clone()
{
var clone = (Engine)this.MemberwiseClone();
unsafe
{
clone.E = RegorusFFI.API.regorus_engine_clone(E);
}
return clone;
}
public string AddPolicy(string path, string rego)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
var regoBytes = Encoding.UTF8.GetBytes(rego);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
fixed(byte* regoPtr = regoBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
}
public string AddPolicyFromFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
}
public void AddDataJson(string data)
{
var dataBytes = Encoding.UTF8.GetBytes(data);
unsafe
{
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
}
}
}
public void AddDataFromJsonFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
}
public void SetInputJson(string input)
{
var inputBytes = Encoding.UTF8.GetBytes(input);
unsafe
{
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
}
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
}
}
}
public string EvalQuery(string query)
{
var queryBytes = Encoding.UTF8.GetBytes(query);
unsafe
{
fixed (byte* queryPtr = queryBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
}
}
}
public string EvalRule(string rule)
{
var ruleBytes = Encoding.UTF8.GetBytes(rule);
unsafe
{
fixed (byte* rulePtr = ruleBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
}
}
}
public void SetEnableCoverage(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
}
}
public void ClearCoverageData()
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
}
}
public string GetCoverageReport()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
}
}
public string GetCoverageReportPretty()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
}
}
public void SetGatherPrints(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
}
}
public string TakePrints()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
}
}
~Engine()
{
unsafe
{
RegorusFFI.API.regorus_engine_drop(E);
}
}
string CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
unsafe {
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
RegorusFFI.API.regorus_result_drop(result);
throw ex;
}
}
var resultString = "";
unsafe
{
if (result.output is not null) {
resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
}
RegorusFFI.API.regorus_result_drop(result);
}
return resultString;
}
}
}

View File

@@ -0,0 +1,65 @@
//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="Microsoft">
// Copyright (c)2012 Microsoft. All rights reserved.
// </copyright>
// <summary>
// Contains code to test the Regorus Policy Engine base class for C#
// and .NET4.0 bindings. It can be built and tested in Windows only.
// </summary>
//-----------------------------------------------------------------------
using System;
using System.Text;
using System.Diagnostics;
using Microsoft.WindowsAzure.Regorus.IaaS;
namespace regoregorus_test
{
class Program
{
static void Main(string[] args)
{
long nanosecPerTick = (1000L * 1000L * 1000L) / Stopwatch.Frequency;
var w = new Stopwatch();
w.Restart();
var engine = new RegorusPolicyEngine();
w.Stop();
var newEngineTicks = w.ElapsedTicks;
w.Restart();
// Load policies and data.
engine.AddPolicyFromFile("../../../examples/extension_list/agent_extension_policy.rego");
engine.AddDataFromJsonFile("../../../examples/extension_list/agent-extension-data-allow-only.json");
w.Stop();
var loadPoliciesTicks = w.ElapsedTicks;
w.Restart();
// Set input and eval query.
engine.SetInputFromJsonFile("../../../examples/extension_list/agent-extension-input.json");
var results = engine.EvalQuery("data.agent_extension_policy.extensions_to_download=x");
Console.WriteLine("Download query test: \n {0}", results);
results = engine.EvalQuery("data.agent_extension_policy.extensions_validated");
Console.WriteLine("Signing validation test: \n {0}", results);
engine.Dispose();
w.Stop();
var evalTicks = w.ElapsedTicks;
Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks * nanosecPerTick) / (1000.0 * 1000.0));
Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks * nanosecPerTick) / (1000.0 * 1000.0));
Console.WriteLine("EvalQuery and print results took {0} msecs", (evalTicks * nanosecPerTick) / (1000.0 * 1000.0));
}
}
}

View File

@@ -0,0 +1,4 @@
The Regorus C# binding library can be built via command "dotnet build". We can use the Regorus C# binding library built from this
directory to create a Nuget. This Nuget will contain the Regorus C# binding library with definitions that
work for .NET framework 4.0 (net40) and above. Note the Nuget can only be created after the binding library has been built.
RegorusCsharp-Lib-x64.nuspec is built for x64 architecture.

View File

@@ -0,0 +1,213 @@
//-----------------------------------------------------------------------
// <copyright file="Regorus.cs" company="Microsoft">
// Copyright (c)2012 Microsoft. All rights reserved.
// </copyright>
// <summary>
// Contains code for the Regorus Policy Engine base class for C# and
// .NET4.0 bindings. Currently this base class is not thread-safe. Make
// sure we use it in a signle-threaded environment or add additional
// protection when using it.
// </summary>
//-----------------------------------------------------------------------
using System;
using System.Text;
using System.IO;
using System.Threading;
namespace Microsoft.WindowsAzure.Regorus.IaaS
{
public class RegorusPolicyEngine : ICloneable, IDisposable
{
unsafe private RegorusFFI.RegorusEngine* E;
public RegorusPolicyEngine()
{
unsafe
{
E = RegorusFFI.API.regorus_engine_new();
}
}
public void Dispose()
{
unsafe
{
if (E != null)
{
RegorusFFI.API.regorus_engine_drop(E);
// to avoid Dispose() being called multiple times by mistake.
E = null;
}
}
}
public object Clone()
{
var clone = (RegorusPolicyEngine)this.MemberwiseClone();
unsafe
{
clone.E = RegorusFFI.API.regorus_engine_clone(E);
}
return clone;
}
byte[] NullTerminatedUTF8Bytes(string s)
{
return Encoding.UTF8.GetBytes(s + char.MinValue);
}
public void AddPolicy(string path, string rego)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
var regoBytes = NullTerminatedUTF8Bytes(rego);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
fixed (byte* regoPtr = regoBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
}
public void AddPolicyFromFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
}
public void AddPolicyFromPath(string path)
{
if (!Directory.Exists(path))
{
return;
}
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
foreach (string file in regoFiles)
{
AddPolicyFromFile(file);
}
}
public void AddDataJson(string data)
{
var dataBytes = NullTerminatedUTF8Bytes(data);
unsafe
{
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
}
}
}
public void AddDataFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
}
public void SetInputJson(string input)
{
var inputBytes = NullTerminatedUTF8Bytes(input);
unsafe
{
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
}
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
}
}
}
public string EvalQuery(string query)
{
var queryBytes = NullTerminatedUTF8Bytes(query);
var resultJson = "";
unsafe
{
fixed (byte* queryPtr = queryBytes)
{
var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr);
if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk)
{
if (result.output != null)
{
resultJson = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.output);
}
RegorusFFI.API.regorus_result_drop(result);
}
else
{
CheckAndDropResult(result);
}
}
}
if (resultJson != null)
{
return resultJson;
}
else
{
return "";
}
}
void CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk)
{
unsafe
{
var message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.error_message);
var ex = new Exception(message);
RegorusFFI.API.regorus_result_drop(result);
throw ex;
}
}
RegorusFFI.API.regorus_result_drop(result);
}
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<package>
<metadata>
<id>RegorusCsharp-Lib-x64</id>
<version>0.2.1</version>
<title>RegorusCsharp-Lib-x64</title>
<authors>yangjie@microsoft.com</authors>
<owners>yangjie@microsoft.com</owners>
<projectUrl>https://www.microsoft.com</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Regorus C# library for x64</description>
<releaseNotes>remove Regorus.cs from Nuget</releaseNotes>
<copyright>Copyright (C) Microsoft Corp</copyright>
<summary></summary>
</metadata>
<files>
<file src="RegorusFFI.g.cs" target="RegorusFFI.g.cs"/>
<file src="regorus_ffi.dll" target="lib\regorusc.dll" />
<file src="README" target="README" />
<file src="..\..\..\LICENSE" target="LICENSE" />
</files>
</package>

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI">
<Target Name="BuildRegorusFFI">
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
<ItemGroup>
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
</ItemGroup>
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
</Target>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net40</TargetFramework>
<RootNamespace>regorus_test</RootNamespace>
<StartupObject>regoregorus_test.Program</StartupObject>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@@ -1,4 +1,14 @@
using System.Diagnostics;
//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="Microsoft">
// Copyright (c)2012 Microsoft. All rights reserved.
// </copyright>
// <summary>
// Contains code to test the Regorus class for C#
// and .NET 8.0 bindings.
// </summary>
//-----------------------------------------------------------------------
using System.Diagnostics;
long nanosecPerTick = (1000L*1000L*1000L) / Stopwatch.Frequency;
var w = new Stopwatch();
@@ -21,10 +31,10 @@ var newEngineTicks = w.ElapsedTicks;
w.Restart();
// Load policies and data.
engine.AddPolicyFromFile("../../tests/aci/framework.rego");
engine.AddPolicyFromFile("../../tests/aci/api.rego");
engine.AddPolicyFromFile("../../tests/aci/policy.rego");
engine.AddDataFromJsonFile("../../tests/aci/data.json");
engine.AddPolicyFromFile("../../../tests/aci/framework.rego");
engine.AddPolicyFromFile("../../../tests/aci/api.rego");
engine.AddPolicyFromFile("../../../tests/aci/policy.rego");
engine.AddDataFromJsonFile("../../../tests/aci/data.json");
w.Stop();
@@ -34,7 +44,7 @@ var loadPoliciesTicks = w.ElapsedTicks;
w.Restart();
// Set input and eval rule.
engine.SetInputFromJsonFile("../../tests/aci/input.json");
engine.SetInputFromJsonFile("../../../tests/aci/input.json");
var value = engine.EvalQuery("data.framework.mount_overlay");
var valueDoc = System.Text.Json.JsonDocument.Parse(value);

View File

@@ -0,0 +1,228 @@
using System.Text;
namespace Regorus
{
public class Exception : System.Exception
{
public Exception(string? message) : base(message) { }
}
public class Engine : ICloneable
{
unsafe private RegorusFFI.RegorusEngine* E;
public Engine()
{
unsafe
{
E = RegorusFFI.API.regorus_engine_new();
}
}
public object Clone()
{
var clone = (Engine)this.MemberwiseClone();
unsafe
{
clone.E = RegorusFFI.API.regorus_engine_clone(E);
}
return clone;
}
byte[] NullTerminatedUTF8Bytes(string s)
{
return Encoding.UTF8.GetBytes(s + char.MinValue);
}
public string AddPolicy(string path, string rego)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
var regoBytes = NullTerminatedUTF8Bytes(rego);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
fixed (byte* regoPtr = regoBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
}
public string AddPolicyFromFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
}
public void AddDataJson(string data)
{
var dataBytes = NullTerminatedUTF8Bytes(data);
unsafe
{
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
}
}
}
public void AddDataFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
}
public void SetInputJson(string input)
{
var inputBytes = NullTerminatedUTF8Bytes(input);
unsafe
{
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
}
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
}
}
}
public string EvalQuery(string query)
{
var queryBytes = NullTerminatedUTF8Bytes(query);
unsafe
{
fixed (byte* queryPtr = queryBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
}
}
}
public string EvalRule(string rule)
{
var ruleBytes = NullTerminatedUTF8Bytes(rule);
unsafe
{
fixed (byte* rulePtr = ruleBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
}
}
}
public void SetEnableCoverage(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
}
}
public void ClearCoverageData()
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
}
}
public string GetCoverageReport()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
}
}
public string GetCoverageReportPretty()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
}
}
public void SetGatherPrints(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
}
}
public string TakePrints()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
}
}
~Engine()
{
unsafe
{
RegorusFFI.API.regorus_engine_drop(E);
}
}
string CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk)
{
unsafe
{
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
RegorusFFI.API.regorus_result_drop(result);
throw ex;
}
}
var resultString = "";
unsafe
{
if (result.output is not null)
{
resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
}
RegorusFFI.API.regorus_result_drop(result);
}
return resultString;
}
}
}

View File

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI">
<Target Name="BuildRegorusFFI">
<Exec Command="cargo build -r --manifest-path ../ffi/Cargo.toml" />
<Copy SourceFiles="../ffi/RegorusFFI.g.cs" DestinationFolder="." />
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
<ItemGroup>
<RegorusDylib Include="..\..\target\release\*regorus_ffi*" />
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
</ItemGroup>
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
</Target>

View File

@@ -1,6 +1,6 @@
[package]
name = "regorus-ffi"
version = "0.1.5"
version = "0.2.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,11 +13,12 @@ regorus = { path = "../..", default-features = false }
serde_json = "1.0.113"
[features]
default = ["std", "coverage", "regorus/arc", "regorus/full-opa"]
default = ["ast", "std", "coverage", "regorus/arc", "regorus/full-opa"]
ast = ["regorus/ast"]
std = ["regorus/std"]
coverage = ["regorus/coverage"]
custom_allocator = []
[build-dependencies]
cbindgen = "0.26.0"
csbindgen = "1.9.0"
cbindgen = "0.27.0"
csbindgen = "=1.9.3"

View File

@@ -189,7 +189,6 @@ pub extern "C" fn regorus_engine_add_data_json(
/// Get list of loaded Rego packages as JSON.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
/// * `data`: JSON encoded value to be used as policy data.
#[no_mangle]
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
to_regorus_string_result(|| -> Result<String> {
@@ -198,6 +197,16 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg
}())
}
/// Get list of policies as JSON.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
#[no_mangle]
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
to_regorus_string_result(|| -> Result<String> {
to_ref(&engine)?.engine.get_policies_as_json()
}())
}
#[cfg(feature = "std")]
#[no_mangle]
pub extern "C" fn regorus_engine_add_data_from_json_file(
@@ -411,6 +420,23 @@ pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> Rego
}
}
/// Get AST of policies.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_ast_as_json
#[no_mangle]
#[cfg(feature = "ast")]
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
let output = || -> Result<String> { to_ref(&engine)?.engine.get_ast_as_json() }();
match output {
Ok(out) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(out),
error_message: std::ptr::null_mut(),
},
Err(e) => to_regorus_result(Err(e)),
}
}
#[cfg(feature = "custom_allocator")]
extern "C" {
fn regorus_aligned_alloc(alignment: usize, size: usize) -> *mut u8;

View File

@@ -82,6 +82,19 @@ func main() {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("%s\n", output)
// Print packages
if output, err = engine1.GetPackages(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("%s\n", output)
// Print policies
if output, err = engine1.GetPolicies(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("%s\n", output)
}

View File

@@ -55,6 +55,25 @@ func (e *Engine) AddPolicyFromFile(path string) (string, error) {
return C.GoString(result.output), nil
}
func (e *Engine) GetPackages() (string, error) {
result := C.regorus_engine_get_packages(e.e)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return C.GoString(result.output), nil
}
func (e *Engine) GetPolicies() (string, error) {
result := C.regorus_engine_get_policies(e.e)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return C.GoString(result.output), nil
}
func (e *Engine) AddDataJson(data string) error {
data_c := C.CString(data)
defer C.free(unsafe.Pointer(data_c))

View File

@@ -1,6 +1,6 @@
[package]
name = "regorus-java"
version = "0.1.5"
version = "0.2.2"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/java"
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -11,7 +11,9 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
crate-type = ["cdylib"]
[features]
default = ["regorus/std", "regorus/full-opa"]
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
coverage = ["regorus/coverage"]
ast = ["regorus/ast"]
[dependencies]
anyhow = "1.0"

View File

@@ -31,6 +31,12 @@ public class Test {
System.out.println(coverageJson);
System.out.println(engine.getCoverageReportPretty());
String packagesJson = engine.getPackages();
System.out.println(packagesJson);
String policiesJson = engine.getPolicies();
System.out.println(policiesJson);
}
}
}

View File

@@ -9,7 +9,7 @@
<groupId>com.microsoft.regorus</groupId>
<artifactId>regorus-java</artifactId>
<version>0.1.5</version>
<version>0.2.2</version>
<name>Regorus Java</name>
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>

View File

@@ -89,6 +89,25 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages(
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPolicies(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let policies = engine.get_policies_as_json()?;
let policies_json = env.new_string(&policies)?;
Ok(policies_json.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData(
env: JNIEnv,
@@ -205,6 +224,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalRule(
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage(
env: JNIEnv,
_class: JClass,
@@ -219,6 +239,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport(
env: JNIEnv,
_class: JClass,
@@ -238,6 +259,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReportPretty(
env: JNIEnv,
_class: JClass,
@@ -257,6 +279,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearCoverageData(
env: JNIEnv,
_class: JClass,
@@ -302,6 +325,26 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeTakePrints(
}
}
#[no_mangle]
#[cfg(feature = "ast")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_getAstAsJson(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let ast = engine.get_ast_as_json()?;
let output = env.new_string(&ast)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
_env: JNIEnv,

View File

@@ -26,6 +26,7 @@ public class Engine implements AutoCloseable, Cloneable {
private static native String nativeAddPolicy(long enginePtr, String path, String rego);
private static native String nativeAddPolicyFromFile(long enginePtr, String path);
private static native String nativeGetPackages(long enginePtr);
private static native String nativeGetPolicies(long enginePtr);
private static native void nativeClearData(long enginePtr);
private static native void nativeAddDataJson(long enginePtr, String data);
private static native void nativeAddDataJsonFromFile(long enginePtr, String path);
@@ -60,7 +61,7 @@ public class Engine implements AutoCloseable, Cloneable {
/**
* Efficiently clones an Engine.
*/
public Engine Clone() {
public Engine clone() {
return new Engine(nativeClone(enginePtr));
}
@@ -96,6 +97,15 @@ public class Engine implements AutoCloseable, Cloneable {
return nativeGetPackages(enginePtr);
}
/**
* Get list of loaded policies.
*
* @return List of Rego policies as a JSON array of sources.
*/
public String getPolicies() {
return nativeGetPolicies(enginePtr);
}
/**
* Clears the data document.
*/

View File

@@ -1,6 +1,6 @@
[package]
name = "regoruspy"
version = "0.1.5"
version = "0.2.2"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/python"
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -12,12 +12,14 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
crate-type = ["cdylib"]
[features]
default = ["regorus/std", "regorus/full-opa"]
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
ast = ["regorus/ast"]
coverage = ["regorus/coverage"]
[dependencies]
anyhow = "1.0"
ordered-float = "4.2.0"
pyo3 = {version = "0.21.0", features = ["anyhow", "extension-module"] }
pyo3 = {version = "0.22.0", features = ["anyhow", "extension-module"] }
regorus = { path = "../..", default-features = false, features = ["arc"] }
serde_json = "1.0.112"

View File

@@ -184,10 +184,18 @@ impl Engine {
/// Get the list of packages defined by loaded policies.
///
pub fn get_packages(&mut self) -> Result<Vec<String>> {
pub fn get_packages(&self) -> Result<Vec<String>> {
self.engine.get_packages()
}
/// Get the list of policies.
///
pub fn get_policies(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(
&self.engine.get_policies_as_json()?,
)?)
}
/// Add policy data.
///
/// * `data`: Rego value. A Rego value is a number, bool, string, None
@@ -312,6 +320,7 @@ impl Engine {
/// Get coverage report as json.
///
#[cfg(feature = "coverage")]
pub fn get_coverage_report_as_json(&self) -> Result<String> {
let report = self.engine.get_coverage_report()?;
serde_json::to_string_pretty(&report).map_err(|e| anyhow!("{e}"))
@@ -319,12 +328,14 @@ impl Engine {
/// Get coverage report as pretty printable string.
///
#[cfg(feature = "coverage")]
pub fn get_coverage_report_pretty(&self) -> Result<String> {
self.engine.get_coverage_report()?.to_string_pretty()
}
/// Clear coverage data.
///
#[cfg(feature = "coverage")]
pub fn clear_coverage_data(&mut self) {
self.engine.clear_coverage_data();
}
@@ -350,6 +361,13 @@ impl Engine {
engine: self.engine.clone(),
}
}
/// Get AST of policies.
///
#[cfg(feature = "ast")]
pub fn get_ast_as_json(&self) -> Result<String> {
self.engine.get_ast_as_json()
}
}
#[pymodule]

View File

@@ -0,0 +1,214 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import json
import pytest
import regorus
TEST_EXT_NAME = "Microsoft.Azure.ActiveDirectory.AADSSHLoginForLinux"
@pytest.fixture(name="engine", scope="function")
def engine_fixture():
"""
Fixture to handle creation and cleanup of a default policy engine.
New engine is created for each test case.
"""
engine = regorus.Engine()
engine.add_policy_from_file('../../examples/extension_list/agent_extension_policy.rego')
yield engine
@pytest.fixture(name="input_data")
def input_data_fixture():
"""
Fixture to handle creation and cleanup of a default input data.
New input data is created for each test case.
"""
input_data = {
"extensions": {
TEST_EXT_NAME: {
"signingInfo": {
"extensionSigned": False
}
}
}
}
input_json = json.dumps(input_data)
yield input_json
@pytest.fixture(name="default_data")
def default_data_fixture():
"""Fixture for default data"""
data_json = {
"azureGuestAgentPolicy": {
"policyVersion": "0.1.0",
"signingRules": {
"extensionSigned": False
},
"allowListOnly": False
}
}
data_json = json.dumps(data_json)
yield data_json
def test_default_data_json(engine, input_data):
"""Test the default data in json format for extension policy."""
data_json = {
"azureGuestAgentPolicy": {
"policyVersion": "0.1.0",
"signingRules": {
"extensionSigned": False
},
"allowListOnly": False
}
}
data_json = json.dumps(data_json)
engine.add_data_json(data_json)
engine.set_input_json(input_data)
# Eval query
results = engine.eval_query('data.agent_extension_policy')
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
def test_default_data_file(engine, input_data):
"""Test the default data in file format for extension policy."""
data_default_path = "../../examples/extension_list/agent-extension-default-data.json"
engine.add_data_from_json_file(data_default_path)
engine.set_input_json(input_data)
# Eval query
results = engine.eval_query('data.agent_extension_policy')
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
def test_allow_all(engine, input_data):
"""Test the policy engine with allow all policy."""
data_json = {
"azureGuestAgentPolicy": {
"policyVersion": "0.1.0",
"signingRules": {
"extensionSigned": False
},
"allowListOnly": False
}
}
data_json = json.dumps(data_json)
engine.add_data_json(data_json)
engine.set_input_json(input_data)
# Eval query
results = engine.eval_query('data.agent_extension_policy')
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
def test_name_only_input(engine, default_data):
"""Test input with only the extension name."""
input_data = {
"extensions": {
TEST_EXT_NAME: {
}
}
}
input_json = json.dumps(input_data)
engine.add_data_json(default_data)
engine.set_input_json(input_json)
# Eval query
results = engine.eval_query('data.agent_extension_policy')
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
@pytest.mark.parametrize("input_signed, extension_signed", [
(True, True),
(True, False),
(False, True),
(False, False)
])
def test_extension_signed_rule(engine, input_signed, extension_signed):
"""
Test extension signing rule. Engine should be able to handle
both signed and unsigned extensions, with extensionSigned rule set
to either true or false.
"""
data_json = {
"azureGuestAgentPolicy": {
"policyVersion": "0.1.0",
"signingRules": {
"extensionSigned": extension_signed
},
"allowListOnly": False
}
}
input_data = {
"extensions": {
TEST_EXT_NAME: {
"signingInfo": {
"extensionSigned": input_signed
}
}
}
}
data_json = json.dumps(data_json)
input_data = json.dumps(input_data)
engine.add_data_json(data_json)
engine.set_input_json(input_data)
# Eval query
results = engine.eval_query('data.agent_extension_policy')
# assert results
if extension_signed:
assert results['result'][0]['expressions'][0]['value']['extensions_validated'][TEST_EXT_NAME]['signingValidated'] == input_signed
else:
assert results['result'][0]['expressions'][0]['value']['extensions_validated'][TEST_EXT_NAME]['signingValidated']
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
@pytest.mark.parametrize("ext_allowed, allow_rule", [
(True, True),
(True, False),
(False, True),
(False, False)
])
def test_allowlist_rule(engine, ext_allowed, allow_rule):
"""
Test allowListOnly rule. Engine should be able to handle
both allowed and disallowed extensions, with allowListOnly rule
set to either true or false.
"""
if ext_allowed:
ext_name = TEST_EXT_NAME
else:
ext_name = "random_disallowed_extension"
input_json = {
"extensions": {
ext_name: {
"signingInfo": {
"extensionSigned": False
}
}
}
}
data_json = {
"azureGuestAgentPolicy": {
"signingRules": {
"extensionSigned": False
},
"allowListOnly": allow_rule
},
"azureGuestExtensionsPolicy": {
"Microsoft.CPlat.Core.RunCommandLinux": {
},
TEST_EXT_NAME: {
}
}
}
input_json = json.dumps(input_json)
data_json = json.dumps(data_json)
engine.add_data_json(data_json)
engine.set_input_json(input_json)
# Eval query
results = engine.eval_query('data.agent_extension_policy')
if allow_rule:
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][ext_name]['downloadAllowed'] == ext_allowed
else:
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][ext_name]['downloadAllowed']

View File

@@ -1,7 +1,7 @@
PATH
remote: .
specs:
regorusrb (0.1.5)
regorusrb (0.2.1)
rb_sys (~> 0.9.97)
GEM
@@ -10,9 +10,9 @@ GEM
ast (2.4.2)
json (2.7.2)
language_server-protocol (3.17.0.3)
minitest (5.23.1)
parallel (1.24.0)
parser (3.3.1.0)
minitest (5.24.1)
parallel (1.25.1)
parser (3.3.4.0)
ast (~> 2.4.1)
racc
racc (1.8.0)
@@ -20,25 +20,25 @@ GEM
rake (13.2.1)
rake-compiler (1.2.7)
rake
rake-compiler-dock (1.5.0)
rb_sys (0.9.97)
rake-compiler-dock (1.5.1)
rb_sys (0.9.99)
regexp_parser (2.9.2)
rexml (3.2.8)
strscan (>= 3.0.9)
rubocop (1.64.0)
rexml (3.3.6)
strscan
rubocop (1.65.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
regexp_parser (>= 2.4, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
rubocop-minitest (0.35.0)
rubocop-minitest (0.35.1)
rubocop (>= 1.61, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rake (0.6.0)
@@ -62,4 +62,4 @@ DEPENDENCIES
rubocop-rake
BUNDLED WITH
2.5.10
2.5.13

View File

@@ -1,6 +1,6 @@
[package]
name = "regorusrb"
version = "0.1.5"
version = "0.2.2"
edition = "2021"
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
publish = false
@@ -10,10 +10,12 @@ crate-type = ["cdylib"]
path = "src/lib.rs"
[features]
default = ["regorus/std", "regorus/full-opa"]
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
ast = ["regorus/ast"]
coverage = ["regorus/coverage"]
[dependencies]
magnus = { version = "0.6.4" }
regorus = { git = "https://github.com/microsoft/regorus", default-features = false, features = ["arc"] }
regorus = { path = "../../../..", default-features = false, features = ["arc"] }
serde_json = "1.0.117"
serde_magnus = "0.8.1"

View File

@@ -92,6 +92,20 @@ impl Engine {
Ok(())
}
fn get_packages(&self) -> Result<Vec<String>, Error> {
self.engine
.borrow()
.get_packages()
.map_err(|e| Error::new(runtime_error(), format!("Failed to get packages: {e}")))
}
fn get_policies(&self) -> Result<String, Error> {
self.engine
.borrow()
.get_policies_as_json()
.map_err(|e| Error::new(runtime_error(), format!("Failed to get policies: {e}")))
}
fn set_input(&self, ruby_hash: magnus::RHash) -> Result<(), Error> {
let input_value: regorus::Value = serde_magnus::deserialize(ruby_hash).map_err(|e| {
Error::new(
@@ -192,11 +206,13 @@ impl Engine {
Ok(self.engine.borrow_mut().eval_deny_query(query, false))
}
#[cfg(feature = "coverage")]
fn set_enable_coverage(&self, enable: bool) -> Result<(), Error> {
self.engine.borrow_mut().set_enable_coverage(enable);
Ok(())
}
#[cfg(feature = "coverage")]
fn get_coverage_report_as_json(&self) -> Result<String, Error> {
let report = self
.engine
@@ -217,6 +233,7 @@ impl Engine {
})
}
#[cfg(feature = "coverage")]
fn get_coverage_report_pretty(&self) -> Result<String, Error> {
let report = self
.engine
@@ -237,6 +254,7 @@ impl Engine {
})
}
#[cfg(feature = "coverage")]
fn clear_coverage_data(&self) -> Result<(), Error> {
self.engine.borrow_mut().clear_coverage_data();
Ok(())
@@ -256,6 +274,14 @@ impl Engine {
)
})
}
#[cfg(feature = "ast")]
fn get_ast_as_json(&self) -> Result<String, Error> {
self.engine
.borrow()
.get_ast_as_json()
.map_err(|e| Error::new(runtime_error(), format!("Failed to get ast: {e}")))
}
}
#[magnus::init]
@@ -277,6 +303,8 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
"add_policy_from_file",
method!(Engine::add_policy_from_file, 1),
)?;
engine_class.define_method("get_packages", method!(Engine::get_packages, 0))?;
engine_class.define_method("get_policies", method!(Engine::get_policies, 0))?;
// data operations
engine_class.define_method("add_data", method!(Engine::add_data, 1))?;
@@ -325,5 +353,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
engine_class.define_method("set_gather_prints", method!(Engine::set_gather_prints, 1))?;
engine_class.define_method("take_prints", method!(Engine::take_prints, 0))?;
// ast
engine_class.define_method("get_ast_as_json", method!(Engine::get_ast_as_json, 0))?;
Ok(())
}

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Regorus
VERSION = "0.1.5"
VERSION = "0.2.1"
end

View File

@@ -1,6 +1,6 @@
[package]
name = "regorusjs"
version = "0.1.5"
version = "0.2.2"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/wasm"
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -11,7 +11,9 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
crate-type = ["cdylib"]
[features]
default = ["regorus/std", "regorus/full-opa"]
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
ast = ["regorus/ast"]
coverage = ["regorus/coverage"]
[dependencies]
regorus = { path = "../..", default-features = false, features = ["arc"] }

View File

@@ -72,6 +72,13 @@ impl Engine {
self.engine.get_packages().map_err(error_to_jsvalue)
}
/// Get the list of policies.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
pub fn getPolicies(&self) -> Result<String, JsValue> {
self.engine.get_policies_as_json().map_err(error_to_jsvalue)
}
/// Clear policy data.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
@@ -131,6 +138,7 @@ impl Engine {
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
/// * `b`: Whether to enable gathering coverage or not.
#[cfg(feature = "coverage")]
pub fn setEnableCoverage(&mut self, enable: bool) {
self.engine.set_enable_coverage(enable)
}
@@ -138,6 +146,7 @@ impl Engine {
/// Get the coverage report as json.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
#[cfg(feature = "coverage")]
pub fn getCoverageReport(&self) -> Result<String, JsValue> {
let report = self
.engine
@@ -149,6 +158,7 @@ impl Engine {
/// Clear gathered coverage data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
#[cfg(feature = "coverage")]
pub fn clearCoverageData(&mut self) {
self.engine.clear_coverage_data()
}
@@ -156,6 +166,7 @@ impl Engine {
/// Get ANSI color coded coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
#[cfg(feature = "coverage")]
pub fn getCoverageReportPretty(&self) -> Result<String, JsValue> {
let report = self
.engine
@@ -163,14 +174,24 @@ impl Engine {
.map_err(error_to_jsvalue)?;
report.to_string_pretty().map_err(error_to_jsvalue)
}
/// Get AST of policies.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_ast_as_json
#[cfg(feature = "ast")]
pub fn getAstAsJson(&self) -> Result<String, JsValue> {
self.engine.get_ast_as_json().map_err(error_to_jsvalue)
}
}
#[cfg(test)]
mod tests {
use crate::error_to_jsvalue;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::wasm_bindgen_test;
#[wasm_bindgen_test]
#[allow(dead_code)]
pub fn basic() -> Result<(), JsValue> {
let mut engine = crate::Engine::new();
engine.setEnableCoverage(true);
@@ -204,7 +225,7 @@ mod tests {
assert_eq!(pkg, "data.test");
let results = engine.evalQuery("data".to_string())?;
let r = regorus::Value::from_json_str(&results).map_err(crate::error_to_jsvalue)?;
let r = regorus::Value::from_json_str(&results).map_err(error_to_jsvalue)?;
let v = &r["result"][0]["expressions"][0]["value"];
@@ -216,7 +237,7 @@ mod tests {
// Use eval_rule to perform same query.
let v = engine.evalRule("data.test.message".to_owned())?;
let v = regorus::Value::from_json_str(&v).map_err(crate::error_to_jsvalue)?;
let v = regorus::Value::from_json_str(&v).map_err(error_to_jsvalue)?;
// Ensure that input and policy were evaluated.
assert_eq!(v, regorus::Value::from("Hello"));
@@ -234,7 +255,7 @@ mod tests {
// Test code coverage.
let report = engine1.getCoverageReport()?;
let r = regorus::Value::from_json_str(&report).map_err(crate::error_to_jsvalue)?;
let r = regorus::Value::from_json_str(&report).map_err(error_to_jsvalue)?;
assert_eq!(
r["files"][0]["covered"]
@@ -246,6 +267,13 @@ mod tests {
println!("{}", engine1.getCoverageReportPretty()?);
engine1.clearCoverageData();
let policies = engine1.getPolicies()?;
let v = regorus::Value::from_json_str(&policies).map_err(error_to_jsvalue)?;
assert_eq!(
v[0]["path"].as_string().map_err(error_to_jsvalue)?.as_ref(),
"hello.rego"
);
Ok(())
}
}

View File

@@ -8,6 +8,9 @@ fn main() -> Result<()> {
// Copy hooks to appropriate location so that git will run them.
// In git worktrees, .git is a symlink and the following commands fail.
if Path::new(".git").is_dir() {
if !Path::new("./.git/hooks").exists() {
std::fs::create_dir_all("./.git/hooks")?;
}
std::fs::copy("./scripts/pre-commit", "./.git/hooks/pre-commit")?;
std::fs::copy("./scripts/pre-push", "./.git/hooks/pre-push")?;
}

View File

@@ -61,9 +61,9 @@ in-expr ::= in-expr 'in' bool-expr
bool-expr ::= bool-expr bool-op or-expr
| or-expr
bool-op ::= '<' | '<=' | '==' | '>=' | '>' | '!='
or-expr ::= or-expr '|' and-expr
| and-expr
and-expr ::= and-expr '&' arith-expr
set-union-expr ::= set-union-expr '|' set-intersection-expr
| set-intersection-expr
set-intersection-expr ::= set-intersection-expr '&' arith-expr
| arith-expr
arith-expr ::= arith-expr ('+' | '-') mul-div-expr
| mul-div-expr

View File

@@ -0,0 +1,19 @@
{
"azureGuestAgentPolicy": {
"signingRules": {
"extensionSigned": true
},
"allowListOnly": true
},
"azureGuestExtensionsPolicy": {
"test3": {
"runtimeRules": {}
},
"test2": {
"signingRules": {
"extensionSigned": false
},
"runtimeRules": {}
}
}
}

View File

@@ -0,0 +1,9 @@
{
"azureGuestAgentPolicy": {
"policyVersion": "0.1.0",
"signingRules": {
"extensionSigned": false
},
"allowListOnly": false
}
}

View File

@@ -0,0 +1,28 @@
{
"extensions": {
"Microsoft.Azure.ActiveDirectory.AADSSHLoginForLinux": {
"signingInfo": {
"extensionSigned": false
}
},
"test2": {
"signingInfo": {
"extensionSigned": true
}
},
"test3": {
"signingInfo": {
"extensionSigned": false
}
},
"test1": {
"signingInfo": {
"extensionSigned": false
}
},
"test4": {}
}
}

View File

@@ -0,0 +1,125 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
package agent_extension_policy
import rego.v1
policy_version := "0.1.0"
default default_global_rules := {
"allowListOnly": false,
"signingRules": {
"extensionSigned": false,
"signingDetails": {},
},
"updateAllowed": true,
"uninstallAllowed": true,
}
default global_rules := {
"allowListOnly": false,
"signingRules": {
"extensionSigned": false,
"signingDetails": {},
},
"updateAllowed": true,
"uninstallAllowed": true,
}
global_rules := object.union(default_global_rules, data.azureGuestAgentPolicy) if {
data.azureGuestAgentPolicy
}
default any_extension_allowed := true
any_extension_allowed := false if {
global_rules.allowListOnly
}
default default_signing_info := {"signingInfo": {}}
# Download rule 1: if the extension is in the list and download rule satisfied: download allowed
extensions_to_download[name] := extension if {
some name, input_extension in input.extensions
data.azureGuestExtensionsPolicy[name]
download_rule_validated(input_extension, data.azureGuestExtensionsPolicy[name])
extension := object.union(input_extension, {"downloadAllowed": true})
}
# Download rule 2: if the extension is in the list and download rule not satisfied: download denied
extensions_to_download[name] := extension if {
some name, input_extension in input.extensions
data.azureGuestExtensionsPolicy[name]
not download_rule_validated(input_extension, data.azureGuestExtensionsPolicy[name])
extension := object.union(input_extension, {"downloadAllowed": false})
}
# Download rule 3: if the extension is not in the list: depending on allowListOnly on or off
extensions_to_download[name] := extension if {
some name, input_extension in input.extensions
not data.azureGuestExtensionsPolicy[name]
extension := object.union(input_extension, {"downloadAllowed": any_extension_allowed})
}
# Validate rule 1: if individual signing rule exists, signing rule validated according to the rules
extensions_validated[name] := extension if {
some name, input_extension in input.extensions
data.azureGuestExtensionsPolicy[name]
extension_global_rules := object.union(global_rules, data.azureGuestExtensionsPolicy[name])
extension_signing_info := object.union(extension_global_rules, default_signing_info)
output := object.union(input_extension, extension_signing_info)
signing_validated(output.signingInfo, output.signingRules)
extension := object.union(output, {"signingValidated": true})
}
# Validate rule 2: if indivual signing rule exists, signing rule not validated according to the rules
extensions_validated[name] := extension if {
some name, input_extension in input.extensions
data.azureGuestExtensionsPolicy[name]
extension_global_rules := object.union(global_rules, data.azureGuestExtensionsPolicy[name])
extension_signing_info := object.union(extension_global_rules, default_signing_info)
output := object.union(input_extension, extension_signing_info)
not signing_validated(output.signingInfo, output.signingRules)
extension := object.union(output, {"signingValidated": false})
}
# Validate rule 3: if individual signing rule doesn't exist, signing rule validated according to global signing rule
extensions_validated[name] := extension if {
some name, input_extension in input.extensions
not data.azureGuestExtensionsPolicy[name]
extension_global_rules := object.union(input_extension, global_rules)
output := object.union(extension_global_rules, default_signing_info)
signing_validated(output.signingInfo, output.signingRules)
extension := object.union(output, {"signingValidated": true})
}
# Validate rule 4: if individual signing rule doesn't exist, signing rule not validated according to the global rules
extensions_validated[name] := extension if {
some name, input_extension in input.extensions
not data.azureGuestExtensionsPolicy[name]
extension_global_rules := object.union(input_extension, global_rules)
output := object.union(extension_global_rules, default_signing_info)
not signing_validated(output.signingInfo, output.signingRules)
extension := object.union(output, {"signingValidated": false})
}
# Currently if download rules doesn't exist, allow the extension because its name is in the list.
# In the future additional rules can be checked with downloadRules present.
download_rule_validated(_, rules) if {
not rules.downloadRules
}
# Signing is validated if input comes with extension signed, or the input of signing information is matching the
# rules in data.
signing_validated(signingInfo, signingRules) if {
signingInfo
signingRules
signingInfo.extensionSigned
} else if {
signingInfo
signingRules
signingInfo.extensionSigned == signingRules.extensionSigned
}

View File

@@ -33,6 +33,7 @@ fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<St
engine.add_policy(path.clone(), read_file(&path)?)
}
#[allow(clippy::too_many_arguments)]
fn rego_eval(
bundles: &[String],
files: &[String],
@@ -41,6 +42,7 @@ fn rego_eval(
enable_tracing: bool,
non_strict: bool,
#[cfg(feature = "coverage")] coverage: bool,
v1: bool,
) -> Result<()> {
// Create engine.
let mut engine = regorus::Engine::new();
@@ -50,6 +52,8 @@ fn rego_eval(
#[cfg(feature = "coverage")]
engine.set_enable_coverage(coverage);
engine.set_rego_v1(v1);
// Load files from given bundles.
for dir in bundles.iter() {
let entries =
@@ -170,8 +174,40 @@ fn rego_parse(file: String) -> Result<()> {
Ok(())
}
#[allow(unused_variables)]
fn rego_ast(file: String) -> Result<()> {
#[cfg(feature = "ast")]
{
// Create engine.
let mut engine = regorus::Engine::new();
// Create source.
#[cfg(feature = "std")]
engine.add_policy_from_file(file)?;
#[cfg(not(feature = "std"))]
engine.add_policy(file.clone(), read_file(&file)?)?;
let ast = engine.get_ast_as_json()?;
println!("{ast}");
Ok(())
}
#[cfg(not(feature = "ast"))]
{
bail!("`ast` feature must be enabled");
}
}
#[derive(clap::Subcommand)]
enum RegorusCommand {
/// Parse a Rego policy and dump AST.
Ast {
/// Rego policy file.
file: String,
},
/// Evaluate a Rego Query.
Eval {
/// Directories containing Rego files.
@@ -201,6 +237,10 @@ enum RegorusCommand {
#[cfg(feature = "coverage")]
#[arg(long, short)]
coverage: bool,
/// Turn on rego.v1
#[arg(long)]
v1: bool,
},
/// Tokenize a Rego policy.
@@ -242,6 +282,7 @@ fn main() -> Result<()> {
non_strict,
#[cfg(feature = "coverage")]
coverage,
v1,
} => rego_eval(
&bundles,
&data,
@@ -251,8 +292,10 @@ fn main() -> Result<()> {
non_strict,
#[cfg(feature = "coverage")]
coverage,
v1,
),
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
RegorusCommand::Parse { file } => rego_parse(file),
RegorusCommand::Ast { file } => rego_ast(file),
}
}

View File

@@ -30,6 +30,11 @@ if [ -f Cargo.toml ]; then
cargo test -r --test aci
cargo test -r --test kata
# Ensure that all tests pass with extensions
cargo test -r --features rego-extensions
cargo test -r --test aci rego-extensions
cargo test -r --test kata rego-extensions
# Ensure that OPA conformance tests don't regress.
cargo test -r --features opa-testutil,serde_json/arbitrary_precision --test opa -- $(tr '\n' ' ' < tests/opa.passing)
cargo test -r --features opa-testutil,serde_json/arbitrary_precision,rego-extensions --test opa -- $(tr '\n' ' ' < tests/opa.passing)
fi

View File

@@ -8,12 +8,14 @@ use crate::*;
use core::{cmp, fmt, ops::Deref};
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum BinOp {
And,
Or,
Intersection,
Union,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum ArithOp {
Add,
Sub,
@@ -23,6 +25,7 @@ pub enum ArithOp {
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum BoolOp {
Lt,
Le,
@@ -33,12 +36,15 @@ pub enum BoolOp {
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum AssignOp {
Eq,
ColEq,
}
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct NodeRef<T> {
#[cfg_attr(feature = "ast", serde(flatten))]
r: Rc<T>,
}
@@ -97,6 +103,7 @@ impl<T> NodeRef<T> {
pub type Ref<T> = NodeRef<T>;
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum Expr {
// Simple items that only have a span as content.
String((Span, Value)),
@@ -202,6 +209,13 @@ pub enum Expr {
value: Ref<Expr>,
collection: Ref<Expr>,
},
#[cfg(feature = "rego-extensions")]
OrExpr {
span: Span,
lhs: Ref<Expr>,
rhs: Ref<Expr>,
},
}
impl Expr {
@@ -225,11 +239,14 @@ impl Expr {
| ArithExpr { span, .. }
| AssignExpr { span, .. }
| Membership { span, .. } => span,
#[cfg(feature = "rego-extensions")]
OrExpr { span, .. } => span,
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum Literal {
SomeVars {
span: Span,
@@ -259,6 +276,7 @@ pub enum Literal {
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct WithModifier {
pub span: Span,
pub refr: Ref<Expr>,
@@ -266,19 +284,23 @@ pub struct WithModifier {
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct LiteralStmt {
pub span: Span,
pub literal: Literal,
#[cfg_attr(feature = "ast", serde(skip_serializing_if = "Vec::is_empty"))]
pub with_mods: Vec<WithModifier>,
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct Query {
pub span: Span,
pub stmts: Vec<LiteralStmt>,
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct RuleAssign {
pub span: Span,
pub op: AssignOp,
@@ -286,6 +308,7 @@ pub struct RuleAssign {
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct RuleBody {
pub span: Span,
pub assign: Option<RuleAssign>,
@@ -293,6 +316,7 @@ pub struct RuleBody {
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum RuleHead {
Compr {
span: Span,
@@ -313,6 +337,7 @@ pub enum RuleHead {
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum Rule {
Spec {
span: Span,
@@ -337,22 +362,27 @@ impl Rule {
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct Package {
pub span: Span,
pub refr: Ref<Expr>,
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct Import {
pub span: Span,
pub refr: Ref<Expr>,
#[cfg_attr(feature = "ast", serde(skip_serializing_if = "Option::is_none"))]
pub r#as: Option<Span>,
}
#[derive(Debug)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct Module {
pub package: Package,
pub imports: Vec<Import>,
#[cfg_attr(feature = "ast", serde(rename(serialize = "rules")))]
pub policy: Vec<Ref<Rule>>,
pub rego_v1: bool,
}

View File

@@ -391,14 +391,14 @@ fn object_union_n(
}
#[cfg(feature = "jsonschema")]
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::JSONSchema> {
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::Validator> {
let schema_str = match arg {
Value::String(schema_str) => schema_str.as_ref().to_string(),
_ => arg.to_json_str()?,
};
if let Ok(schema) = serde_json::from_str(&schema_str) {
match jsonschema::JSONSchema::compile(&schema) {
match jsonschema::validator_for(&schema) {
Ok(schema) => return Ok(schema),
Err(e) => bail!(e.to_string()),
}

View File

@@ -28,6 +28,7 @@ pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn
m.insert("startswith", (startswith, 2));
m.insert("strings.any_prefix_match", (any_prefix_match, 2));
m.insert("strings.any_suffix_match", (any_suffix_match, 2));
m.insert("strings.count", (strings_count, 2));
m.insert("strings.replace_n", (replace_n, 2));
m.insert("strings.reverse", (reverse, 1));
m.insert("substring", (substring, 3));
@@ -145,11 +146,18 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
let s = ensure_string(name, &params[0], &args[0])?;
let delimiter = ensure_string(name, &params[1], &args[1])?;
Ok(Value::from_array(
// Handle https://github.com/microsoft/regorus/issues/291
let parts: Vec<Value> = if delimiter.as_ref() == "" {
// If delimiter is "", str::split returns a leading and trailing "" whereas Golang's split doesn't.
// Therefore avoid str::split and instead return each char as a Value::String.
s.chars().map(|c| Value::from(c.to_string())).collect()
} else {
s.split(delimiter.as_ref())
.map(|s| Value::String(s.into()))
.collect(),
))
.collect()
};
Ok(Value::from(parts))
}
fn to_string(v: &Value, unescape: bool) -> String {
@@ -512,6 +520,27 @@ fn any_suffix_match(
))
}
fn strings_count(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "strings.count";
ensure_args_count(span, name, params, args, 2)?;
let search = ensure_string(name, &params[0], &args[0])?;
let substring = ensure_string(name, &params[0], &args[1])?;
Ok(Value::from(
search
.as_bytes()
.windows(substring.len())
.filter(|&w| w == substring.as_bytes())
.count(),
))
}
fn startswith(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "startswith";
ensure_args_count(span, name, params, args, 2)?;

View File

@@ -121,6 +121,7 @@ fn timestamp(uuid: &Uuid) -> Option<Timestamp> {
// https://github.com/uuid-rs/uuid/blob/94ecea893fadac93248f1bd6f47673c09cec5912/src/lib.rs#L900-L904
if uuid.get_version_num() == 2 {
let (ticks, counter) = decode_rfc4122_timestamp(uuid);
#[allow(deprecated)]
return Some(Timestamp::from_rfc4122(ticks, counter));
}

View File

@@ -20,6 +20,7 @@ pub struct Engine {
modules: Vec<Ref<Module>>,
interpreter: Interpreter,
prepared: bool,
rego_v1: bool,
}
/// Create a default engine.
@@ -36,9 +37,35 @@ impl Engine {
modules: vec![],
interpreter: Interpreter::new(),
prepared: false,
rego_v1: false,
}
}
/// Turn rego.v1 on/off for subsequently added policies.
///
/// Explicit import rego.v1 is not needed if set.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.set_rego_v1(true);
/// engine.add_policy(
/// "test.rego".to_string(),
/// r#"
/// package test
/// allow if true # if keyword is automatically imported
/// "#.to_string())?;
///
/// # Ok(())
/// # }
/// ```
///
pub fn set_rego_v1(&mut self, rego_v1: bool) {
self.rego_v1 = rego_v1;
}
/// Add a policy.
///
/// The policy file will be parsed and converted to AST representation.
@@ -67,7 +94,7 @@ impl Engine {
///
pub fn add_policy(&mut self, path: String, rego: String) -> Result<String> {
let source = Source::from_contents(path, rego)?;
let mut parser = Parser::new(&source)?;
let mut parser = self.make_parser(&source)?;
let module = Ref::new(parser.parse()?);
self.modules.push(module.clone());
// if policies change, interpreter needs to be prepared again
@@ -98,7 +125,7 @@ impl Engine {
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn add_policy_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<String> {
let source = Source::from_file(path)?;
let mut parser = Parser::new(&source)?;
let mut parser = self.make_parser(&source)?;
let module = Ref::new(parser.parse()?);
self.modules.push(module.clone());
// if policies change, interpreter needs to be prepared again
@@ -129,6 +156,66 @@ impl Engine {
.collect()
}
/// Get the list of policy files.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// # let mut engine = Engine::new();
///
/// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?;
/// assert_eq!(pkg, "data.test");
///
/// let policies = engine.get_policies()?;
///
/// assert_eq!(policies[0].get_path(), "hello.rego");
/// assert_eq!(policies[0].get_contents(), "package test");
/// # Ok(())
/// # }
/// ```
pub fn get_policies(&self) -> Result<Vec<Source>> {
Ok(self
.modules
.iter()
.map(|m| m.package.refr.span().source.clone())
.collect())
}
/// Get the list of policy files as a JSON object.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// # let mut engine = Engine::new();
///
/// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?;
/// assert_eq!(pkg, "data.test");
///
/// let policies = engine.get_policies_as_json()?;
///
/// let v = Value::from_json_str(&policies)?;
/// assert_eq!(v[0]["path"].as_string()?.as_ref(), "hello.rego");
/// assert_eq!(v[0]["contents"].as_string()?.as_ref(), "package test");
/// # Ok(())
/// # }
/// ```
pub fn get_policies_as_json(&self) -> Result<String> {
#[derive(Serialize)]
struct Source<'a> {
path: &'a String,
contents: &'a String,
}
let mut sources = vec![];
for m in self.modules.iter() {
let source = &m.package.refr.span().source;
sources.push(Source {
path: source.get_path(),
contents: source.get_contents(),
});
}
serde_json::to_string_pretty(&sources).map_err(anyhow::Error::msg)
}
/// Set the input document.
///
/// * `input`: Input documented. Typically this [Value] is constructed from JSON or YAML.
@@ -179,7 +266,7 @@ impl Engine {
/// # }
/// ```
pub fn clear_data(&mut self) {
self.interpreter.set_data(Value::new_object());
self.interpreter.set_init_data(Value::new_object());
self.prepared = false;
}
@@ -216,7 +303,40 @@ impl Engine {
bail!("data must be object");
}
self.prepared = false;
self.interpreter.get_data_mut().merge(data)
self.interpreter.get_init_data_mut().merge(data)
}
/// Get the data document.
///
/// The returned value is the data document that has been constructed using
/// one or more calls to [`Engine::add_data`]. The values of policy rules are
/// not included in the returned document.
///
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// // If not set, data document is empty.
/// assert_eq!(engine.get_data(), Value::new_object());
///
/// // Merge { "x" : 1, "y" : {} }
/// assert!(engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?).is_ok());
///
/// // Merge { "z" : 2 }
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?).is_ok());
///
/// let data = engine.get_data();
/// assert_eq!(data["x"], Value::from(1));
/// assert_eq!(data["y"], Value::new_object());
/// assert_eq!(data["z"], Value::from(2));
///
/// # Ok(())
/// # }
/// ```
pub fn get_data(&self) -> Value {
self.interpreter.get_init_data().clone()
}
pub fn add_data_json(&mut self, data_json: &str) -> Result<()> {
@@ -335,7 +455,7 @@ impl Engine {
// Parse the query.
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
let mut parser = Parser::new(&query_source)?;
let mut parser = self.make_parser(&query_source)?;
let query_node = parser.parse_user_query()?;
if query_node.span.text() == "data" {
self.eval_modules(enable_tracing)?;
@@ -452,7 +572,7 @@ impl Engine {
// Parse the query.
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
let mut parser = Parser::new(&query_source)?;
let mut parser = self.make_parser(&query_source)?;
let query_node = parser.parse_user_query()?;
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
self.interpreter.eval_user_query(
@@ -477,11 +597,7 @@ impl Engine {
self.interpreter.set_modules(&self.modules);
self.interpreter.clear_builtins_cache();
// when the interpreter is prepared the initial data is saved
// the data will be reset to init_data each time clean_internal_evaluation_state is called
let init_data = self.interpreter.get_data_mut().clone();
self.interpreter.set_init_data(init_data);
// clean_internal_evaluation_state will set data to an efficient clont of use supplied init_data
// Initialize the with-document with initial data values.
// with-modifiers will be applied to this document.
self.interpreter.init_with_document()?;
@@ -743,4 +859,50 @@ impl Engine {
pub fn take_prints(&mut self) -> Result<Vec<String>> {
self.interpreter.take_prints()
}
/// Get the policies and corresponding AST.
///
///
/// ```rust
/// # use regorus::*;
/// # use anyhow::{bail, Result};
/// # fn main() -> Result<()> {
/// # let mut engine = Engine::new();
/// engine.add_policy("test.rego".to_string(), "package test\n x := 1".to_string())?;
///
/// let ast = engine.get_ast_as_json()?;
/// let value = Value::from_json_str(&ast)?;
///
/// assert_eq!(value[0]["ast"]["package"]["refr"]["Var"][1].as_string()?.as_ref(), "test");
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "ast")]
#[cfg_attr(docsrs, doc(cfg(feature = "ast")))]
pub fn get_ast_as_json(&self) -> Result<String> {
#[derive(Serialize)]
struct Policy<'a> {
source: &'a Source,
version: u32,
ast: &'a Module,
}
let mut ast = vec![];
for m in &self.modules {
ast.push(Policy {
source: &m.package.span.source,
version: 1,
ast: m,
});
}
serde_json::to_string_pretty(&ast).map_err(anyhow::Error::msg)
}
fn make_parser<'a>(&self, source: &'a Source) -> Result<Parser<'a>> {
let mut parser = Parser::new(source)?;
if self.rego_v1 {
parser.enable_rego_v1()?;
}
Ok(parser)
}
}

View File

@@ -216,18 +216,22 @@ impl Interpreter {
self.modules = modules.to_vec();
}
pub fn set_init_data(&mut self, init_data: Value) {
self.init_data = init_data;
}
pub fn set_data(&mut self, data: Value) {
self.data = data;
}
pub fn get_data_mut(&mut self) -> &mut Value {
&mut self.data
}
pub fn set_init_data(&mut self, data: Value) {
self.init_data = data;
}
pub fn get_init_data(&self) -> &Value {
&self.init_data
}
pub fn get_init_data_mut(&mut self) -> &mut Value {
&mut self.init_data
}
pub fn set_traces(&mut self, enable_tracing: bool) {
self.traces = match enable_tracing {
true => Some(vec![]),
@@ -480,6 +484,12 @@ impl Interpreter {
self.hoist_loops_impl(rhs, loops);
}
#[cfg(feature = "rego-extensions")]
OrExpr { lhs, rhs, .. } => {
self.hoist_loops_impl(lhs, loops);
self.hoist_loops_impl(rhs, loops);
}
Membership {
key,
value,
@@ -550,8 +560,8 @@ impl Interpreter {
}
match op {
BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
BinOp::Union => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
BinOp::Intersection => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
}
}
@@ -1554,6 +1564,10 @@ impl Interpreter {
let mut obj = &mut self.data;
let len = path.len();
for (idx, p) in path.into_iter().enumerate() {
// Stop at the first undefined component in the path
if p == Value::Undefined {
break;
}
if idx == len - 1 {
// last key.
if is_set {
@@ -1688,6 +1702,7 @@ impl Interpreter {
}
if output == Value::Undefined || !comps_defined {
ctx.rule_value = Value::Undefined;
return Ok(false);
}
@@ -1867,14 +1882,14 @@ impl Interpreter {
self.hoist_loops_impl(oe, &mut loops);
}
self.eval_output_expr_in_loop(&loops[..])?;
let r = self.eval_output_expr_in_loop(&loops[..])?;
let ctx = self.get_current_context()?;
if let Some(_oe) = &ctx.output_expr {
// Ensure that at least one output was generated.
Ok(ctx.value != Value::Undefined)
Ok(ctx.rule_value != Value::Undefined)
} else {
Ok(true)
Ok(r)
}
}
@@ -2274,15 +2289,8 @@ impl Interpreter {
};
let mut param_values = Vec::with_capacity(params.len());
let mut error = None;
for p in params {
match self.eval_expr(p) {
Ok(v) => param_values.push(v),
Err(e) => {
error = Some(Err(e));
break;
}
}
param_values.push(self.eval_expr(p)?);
}
let orig_fcn_path = fcn_path;
@@ -2299,9 +2307,6 @@ impl Interpreter {
if param_values.iter().any(|v| v == &Value::Undefined) {
return Ok(Value::Undefined);
}
if let Some(err) = error {
err?;
};
return Ok(v.clone());
}
_ => orig_fcn_path.clone(),
@@ -2832,6 +2837,15 @@ impl Interpreter {
..
} => self.eval_membership(key, value, collection),
#[cfg(feature = "rego-extensions")]
Expr::OrExpr { lhs, rhs, .. } => {
let lhs = self.eval_expr(lhs)?;
match lhs {
Value::Bool(false) | Value::Null | Value::Undefined => self.eval_expr(rhs),
_ => Ok(lhs),
}
}
// Creation expression
Expr::Array { items, .. } => self.eval_array(items),
Expr::Object { fields, .. } => self.eval_object(fields),
@@ -2944,7 +2958,6 @@ impl Interpreter {
});
}
result = self.eval_query(&body.query);
if matches!(&result, Ok(true) | Err(_)) {
break;
}
@@ -3128,6 +3141,8 @@ impl Interpreter {
ArithExpr { span, .. } => ("arithexpr", span),
AssignExpr { span, .. } => ("assignexpr", span),
Membership { span, .. } => ("membership", span),
#[cfg(feature = "rego-extensions")]
OrExpr { span, .. } => ("orexpr", span),
};
Err(span.error(format!("invalid `{kind}` in default value").as_str()))

View File

@@ -12,17 +12,34 @@ use crate::Value;
use anyhow::{anyhow, bail, Result};
#[derive(Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
struct SourceInternal {
pub file: String,
pub contents: String,
#[cfg_attr(feature = "ast", serde(skip_serializing))]
pub lines: Vec<(u32, u32)>,
}
/// A policy file.
#[derive(Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct Source {
#[cfg_attr(feature = "ast", serde(flatten))]
src: Rc<SourceInternal>,
}
impl Source {
/// The path associated with the policy file.
pub fn get_path(&self) -> &String {
&self.src.file
}
/// The contents of the policy file.
pub fn get_contents(&self) -> &String {
&self.src.contents
}
}
impl cmp::Ord for Source {
fn cmp(&self, other: &Source) -> cmp::Ordering {
Rc::as_ptr(&self.src).cmp(&Rc::as_ptr(&other.src))
@@ -212,7 +229,9 @@ impl Source {
}
#[derive(Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub struct Span {
#[cfg_attr(feature = "ast", serde(skip_serializing))]
pub source: Source,
pub line: u32,
pub col: u32,
@@ -274,6 +293,10 @@ pub struct Lexer<'source> {
iter: Peekable<CharIndices<'source>>,
line: u32,
col: u32,
unknown_char_is_symbol: bool,
allow_slash_star_escape: bool,
comment_starts_with_double_slash: bool,
double_colon_token: bool,
}
impl<'source> Lexer<'source> {
@@ -283,9 +306,29 @@ impl<'source> Lexer<'source> {
iter: source.contents().char_indices().peekable(),
line: 1,
col: 1,
unknown_char_is_symbol: false,
allow_slash_star_escape: false,
comment_starts_with_double_slash: false,
double_colon_token: false,
}
}
pub fn set_unknown_char_is_symbol(&mut self, b: bool) {
self.unknown_char_is_symbol = b;
}
pub fn set_allow_slash_star_escape(&mut self, b: bool) {
self.allow_slash_star_escape = b;
}
pub fn set_comment_starts_with_double_slash(&mut self, b: bool) {
self.comment_starts_with_double_slash = b;
}
pub fn set_double_colon_token(&mut self, b: bool) {
self.double_colon_token = b;
}
fn peek(&mut self) -> (usize, char) {
match self.iter.peek() {
Some((index, chr)) => (*index, *chr),
@@ -465,6 +508,7 @@ impl<'source> Lexer<'source> {
match ch {
// json escape sequence
'"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' => (),
'*' if self.allow_slash_star_escape => (),
'u' => {
for _i in 0..4 {
let (offset, ch) = self.peek();
@@ -528,12 +572,24 @@ impl<'source> Lexer<'source> {
))
}
#[inline]
fn skip_past_newline(&mut self) -> Result<()> {
self.iter.next();
loop {
match self.peek().1 {
'\n' | '\x00' => break,
_ => self.iter.next(),
};
}
Ok(())
}
fn skip_ws(&mut self) -> Result<()> {
// Only the 4 json whitespace characters are recognized.
// https://www.crockford.com/mckeeman.html.
// Additionally, comments are also skipped.
// A tab is considered 4 space characters.
'outer: loop {
loop {
match self.peek().1 {
' ' => self.col += 1,
'\t' => self.col += 4,
@@ -550,14 +606,13 @@ impl<'source> Lexer<'source> {
self.col = 1;
self.line += 1;
}
'#' => {
self.iter.next();
loop {
match self.peek().1 {
'\n' | '\x00' => continue 'outer,
_ => self.iter.next(),
};
}
'#' if !self.comment_starts_with_double_slash => {
self.skip_past_newline()?;
continue;
}
'/' if self.comment_starts_with_double_slash && self.peekahead(1).1 == '/' => {
self.skip_past_newline()?;
continue;
}
_ => break,
}
@@ -601,7 +656,7 @@ impl<'source> Lexer<'source> {
self.col += 1;
self.iter.next();
let mut end = start as u32 + 1;
if self.peek().1 == '=' {
if self.peek().1 == '=' || (self.peek().1 == ':' && self.double_colon_token) {
self.col += 1;
self.iter.next();
end += 1;
@@ -675,6 +730,17 @@ impl<'source> Lexer<'source> {
}
Ok(ident)
}
_ if self.unknown_char_is_symbol => {
self.col += 1;
self.iter.next();
Ok(Token(TokenKind::Symbol, Span {
source: self.source.clone(),
line: self.line,
col,
start: start as u32,
end: start as u32 + 1,
}))
}
_ => Err(self.source.error(self.line, self.col, "invalid character"))
}
}

View File

@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(unknown_lints)]
#![allow(clippy::doc_lazy_continuation)]
// Use README.md as crate documentation.
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
// We'll default to building for no_std - use core, alloc instead of std.
@@ -28,6 +31,7 @@ mod utils;
mod value;
pub use engine::Engine;
pub use lexer::Source;
pub use value::Value;
#[cfg(feature = "arc")]

View File

@@ -40,6 +40,18 @@ impl<'source> Parser<'source> {
})
}
pub fn enable_rego_v1(&mut self) -> Result<()> {
self.turn_on_rego_v1(self.tok.1.clone())
}
fn turn_on_rego_v1(&mut self, span: Span) -> Result<()> {
self.rego_v1 = true;
for kw in FUTURE_KEYWORDS {
self.set_future_keyword(kw, &span)?;
}
Ok(())
}
pub fn token_text(&self) -> &str {
match self.tok.0 {
TokenKind::Symbol | TokenKind::Number | TokenKind::Ident | TokenKind::Eof => {
@@ -487,7 +499,7 @@ impl<'source> Parser<'source> {
fn parse_parens_expr(&mut self) -> Result<Expr> {
self.next_token()?;
let expr = self.parse_membership_expr()?;
let expr = self.parse_expr()?;
self.expect(")", "while parsing parenthesized expression")?;
//TODO: if needed introduce a parens-expr node or adjust expr's span.
Ok(expr)
@@ -688,7 +700,7 @@ impl<'source> Parser<'source> {
}
}
fn parse_and_expr(&mut self) -> Result<Expr> {
fn parse_set_intersection_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_arith_expr()?;
@@ -700,7 +712,7 @@ impl<'source> Parser<'source> {
span.end = self.end;
expr = Expr::BinExpr {
span,
op: BinOp::And,
op: BinOp::Intersection,
lhs: Ref::new(expr),
rhs: Ref::new(right),
};
@@ -708,19 +720,19 @@ impl<'source> Parser<'source> {
Ok(expr)
}
fn parse_or_expr(&mut self) -> Result<Expr> {
fn parse_set_union_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_and_expr()?;
let mut expr = self.parse_set_intersection_expr()?;
while self.token_text() == "|" {
let mut span = self.tok.1.clone();
span.start = start;
self.next_token()?;
let right = self.parse_and_expr()?;
let right = self.parse_set_intersection_expr()?;
span.end = self.end;
expr = Expr::BinExpr {
span,
op: BinOp::Or,
op: BinOp::Union,
lhs: Ref::new(expr),
rhs: Ref::new(right),
};
@@ -730,7 +742,7 @@ impl<'source> Parser<'source> {
fn parse_bool_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_or_expr()?;
let mut expr = self.parse_set_union_expr()?;
loop {
let mut span = self.tok.1.clone();
span.start = start;
@@ -744,7 +756,7 @@ impl<'source> Parser<'source> {
_ => break,
};
self.next_token()?;
let right = self.parse_or_expr()?;
let right = self.parse_set_union_expr()?;
span.end = self.end;
expr = Expr::BoolExpr {
span,
@@ -799,6 +811,32 @@ impl<'source> Parser<'source> {
Ok(expr)
}
pub fn parse_expr(&mut self) -> Result<Expr> {
#[cfg(feature = "rego-extensions")]
return self.parse_or_expr();
#[cfg(not(feature = "rego-extensions"))]
return self.parse_membership_expr();
}
#[cfg(feature = "rego-extensions")]
pub fn parse_or_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_membership_expr()?;
while self.token_text() == "or" {
let mut span = self.tok.1.clone();
span.start = start;
self.next_token()?;
let rhs = self.parse_membership_expr()?;
expr = Expr::OrExpr {
span,
lhs: Ref::new(expr),
rhs: Ref::new(rhs),
};
}
Ok(expr)
}
pub fn parse_membership_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_bool_expr()?;
@@ -839,12 +877,12 @@ impl<'source> Parser<'source> {
":=" => AssignOp::ColEq,
_ => {
*self = state;
return self.parse_membership_expr();
return self.parse_expr();
}
};
self.next_token()?;
let right = self.parse_membership_expr()?;
let right = self.parse_expr()?;
span.end = self.end;
Ok(Expr::AssignExpr {
span,
@@ -1026,6 +1064,29 @@ impl<'source> Parser<'source> {
let mut literals = vec![];
let stmt = match self.parse_literal_stmt() {
Ok(_) if self.token_text() == ":" => {
// This is likely an object comprehension.
// Restore the state and return.
*self = state;
bail!("try parsing as comprehension");
}
Ok(stmt) if self.token_text() == end_delim => {
// Treat { 1 | 1 } as a comprehension instead of a
// set of 1 element.
if let Literal::Expr { expr: e, .. } = &stmt.literal {
if matches!(
e.as_ref(),
Expr::BinExpr {
op: BinOp::Union,
..
}
) {
*self = state;
bail!("try parse as comprehension");
}
}
stmt
}
Ok(stmt) => stmt,
Err(e) if is_definite_query => return Err(e),
Err(e) if matches!(self.token_text(), "=" | ":=") => return Err(e),
@@ -1087,7 +1148,7 @@ impl<'source> Parser<'source> {
_ => return Ok(None),
};
let expr = Ref::new(self.parse_membership_expr()?);
let expr = Ref::new(self.parse_expr()?);
span.end = self.end;
Ok(Some(RuleAssign {
span,
@@ -1235,7 +1296,7 @@ impl<'source> Parser<'source> {
}
"[" => {
self.next_token()?;
let index = self.parse_membership_expr()?;
let index = self.parse_expr()?;
span.end = self.end;
self.expect("]", "while parsing bracketed reference")?;
term = Expr::RefBrack {
@@ -1283,7 +1344,7 @@ impl<'source> Parser<'source> {
}
"contains" => {
self.next_token()?;
let key = Ref::new(self.parse_membership_expr()?);
let key = Ref::new(self.parse_expr()?);
span.end = self.end;
Ok(RuleHead::Set {
span,
@@ -1648,10 +1709,7 @@ impl<'source> Parser<'source> {
let is_future_kw =
if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" {
self.rego_v1 = true;
for kw in FUTURE_KEYWORDS {
self.set_future_keyword(kw, &span)?;
}
self.turn_on_rego_v1(span.clone())?;
true
} else {
self.handle_import_future_keywords(&comps)?

View File

@@ -258,6 +258,12 @@ pub fn traverse(expr: &Ref<Expr>, f: &mut dyn FnMut(&Ref<Expr>) -> Result<bool>)
traverse(rhs, f)?;
}
#[cfg(feature = "rego-extensions")]
OrExpr { lhs, rhs, .. } => {
traverse(lhs, f)?;
traverse(rhs, f)?;
}
Membership {
key,
value,
@@ -386,7 +392,9 @@ pub struct Analyzer {
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Schedule {
#[allow(unused)]
pub scopes: BTreeMap<Ref<Query>, Scope>,
pub order: BTreeMap<Ref<Query>, Vec<u16>>,
}
@@ -410,7 +418,7 @@ impl Analyzer {
}
pub fn analyze(mut self, modules: &[Ref<Module>]) -> Result<Schedule> {
self.add_rules(modules)?;
self.add_rules_and_aliases(modules)?;
self.functions = gather_functions(modules)?;
for m in modules {
@@ -428,7 +436,7 @@ impl Analyzer {
modules: &[Ref<Module>],
query: &Ref<Query>,
) -> Result<Schedule> {
self.add_rules(modules)?;
self.add_rules_and_aliases(modules)?;
self.analyze_query(None, None, query, Scope::default())?;
Ok(Schedule {
@@ -437,7 +445,7 @@ impl Analyzer {
})
}
fn add_rules(&mut self, modules: &[Ref<Module>]) -> Result<()> {
fn add_rules_and_aliases(&mut self, modules: &[Ref<Module>]) -> Result<()> {
for m in modules {
let path = get_path_string(&m.package.refr, Some("data"))?;
let scope: &mut Scope = self.packages.entry(path).or_default();
@@ -454,6 +462,12 @@ impl Analyzer {
};
scope.unscoped.insert(var);
}
for import in &m.imports {
if let Some(var) = &import.r#as {
scope.unscoped.insert(var.source_str());
}
}
}
Ok(())

View File

@@ -385,6 +385,11 @@ fn yaml_test_impl(file: &str) -> Result<()> {
}
fn yaml_test(file: &str) -> Result<()> {
#[cfg(not(feature = "rego-extensions"))]
if file.contains("rego-extensions") {
return Ok(());
}
match yaml_test_impl(file) {
Ok(_) => Ok(()),
Err(e) => {
@@ -422,3 +427,32 @@ fn one_yaml() -> Result<()> {
fn run(path: &str) {
yaml_test(path).unwrap()
}
#[test]
fn test_get_data() -> Result<()> {
let mut engine = Engine::new();
// Merge { "x" : 1, "y" : {} }
engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?)?;
// Merge { "z" : 2 }
engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?)?;
// Add a policy
engine.add_policy("policy.rego".to_string(), "package a".to_string())?;
// Evaluate virtual data document. The virtual document includes all rules as well.
let v_data = engine.eval_query("data".to_string(), false)?.result[0].expressions[0]
.value
.clone();
// There must be an empty package.
assert_eq!(v_data["a"], Value::new_object());
// Get the data document.
let data = engine.get_data();
// There must NOT be any value of `a`.
assert_eq!(data["a"], Value::Undefined);
Ok(())
}

View File

@@ -78,10 +78,7 @@ pub fn get_extra_arg(
module: Option<&str>,
functions: &FunctionTable,
) -> Option<Ref<Expr>> {
match get_extra_arg_impl(expr, module, functions) {
Ok(a) => a,
_ => None,
}
get_extra_arg_impl(expr, module, functions).unwrap_or_default()
}
pub fn gather_functions(modules: &[Ref<Module>]) -> Result<FunctionTable> {

View File

@@ -322,7 +322,8 @@ impl Value {
/// // Convert the value back to json.
/// let json_str = value.to_json_str()?;
///
/// assert_eq!(json_str.trim(), std::fs::read_to_string("tests/aci/input.json")?.trim());
/// assert_eq!(json_str.trim(),
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
/// # Ok(())
/// # }
/// ```
@@ -345,7 +346,8 @@ impl Value {
/// // Convert the value back to json.
/// let json_str = value.to_json_str()?;
///
/// assert_eq!(json_str.trim(), std::fs::read_to_string("tests/aci/input.json")?.trim());
/// assert_eq!(json_str.trim(),
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
/// # Ok(())
/// # }
/// ```
@@ -902,6 +904,180 @@ impl Value {
}
}
/// Cast value to [`& u32`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a u32.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(10);
/// assert_eq!(v.as_u32()?, 10u32);
///
/// let v = Value::from(-10);
/// assert!(v.as_u32().is_err());
/// # Ok(())
/// # }
pub fn as_u32(&self) -> Result<u32> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_u64() {
if let Ok(v) = u32::try_from(n) {
return Ok(v);
}
}
bail!("not a u32");
}
_ => Err(anyhow!("not a u32")),
}
}
/// Cast value to [`& i32`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i32.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(-10);
/// assert_eq!(v.as_i32()?, -10i32);
///
/// let v = Value::from(2_147_483_648i64);
/// assert!(v.as_i32().is_err());
/// # Ok(())
/// # }
pub fn as_i32(&self) -> Result<i32> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_i64() {
if let Ok(v) = i32::try_from(n) {
return Ok(v);
}
}
bail!("not an i32");
}
_ => Err(anyhow!("not an i32")),
}
}
/// Cast value to [`& u16`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a u16.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(10);
/// assert_eq!(v.as_u16()?, 10u16);
///
/// let v = Value::from(-10);
/// assert!(v.as_u16().is_err());
/// # Ok(())
/// # }
pub fn as_u16(&self) -> Result<u16> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_u64() {
if let Ok(v) = u16::try_from(n) {
return Ok(v);
}
}
bail!("not a u16");
}
_ => Err(anyhow!("not a u16")),
}
}
/// Cast value to [`& i16`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i16.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(-10);
/// assert_eq!(v.as_i16()?, -10i16);
///
/// let v = Value::from(32768i64);
/// assert!(v.as_i16().is_err());
/// # Ok(())
/// # }
pub fn as_i16(&self) -> Result<i16> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_i64() {
if let Ok(v) = i16::try_from(n) {
return Ok(v);
}
}
bail!("not an i16");
}
_ => Err(anyhow!("not an i16")),
}
}
/// Cast value to [`& u8`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a u8.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(10);
/// assert_eq!(v.as_u8()?, 10u8);
///
/// let v = Value::from(-10);
/// assert!(v.as_u8().is_err());
/// # Ok(())
/// # }
pub fn as_u8(&self) -> Result<u8> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_u64() {
if let Ok(v) = u8::try_from(n) {
return Ok(v);
}
}
bail!("not a u8");
}
_ => Err(anyhow!("not a u8")),
}
}
/// Cast value to [`& i8`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i8.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(-10);
/// assert_eq!(v.as_i8()?, -10i8);
///
/// let v = Value::from(128);
/// assert!(v.as_i8().is_err());
/// # Ok(())
/// # }
pub fn as_i8(&self) -> Result<i8> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_i64() {
if let Ok(v) = i8::try_from(n) {
return Ok(v);
}
}
bail!("not an i8");
}
_ => Err(anyhow!("not an i8")),
}
}
/// Cast value to [`& f64`] if [`Value::Number`].
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i64.

View File

@@ -0,0 +1,151 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: base
data: {}
modules:
- |
package test
v1 = indexof("Hello world", "llo") # valid substring
v2 = indexof("Hello world", "hel") # case sensitive
v3 = indexof("Hello world", "l") # single character
v4 = indexof("", ",") # empty string
v5 = indexof("", "") # empty substring and string
query: data.test
want_result:
v1: 2
v2: -1
v3: 2
v4: -1
v5: -1
- note: unicode-char
data: {}
modules:
- |
package test
v1 = indexof("μx", "x")
query: data.test
want_result:
v1: 1
- note: unicode-chars-not-found
data: {}
modules:
- |
package test
v1 = indexof("μ", "μμ")
query: data.test
want_result:
v1: -1
- note: unicode-string
data: {}
modules:
- |
package test
v1 = indexof("skön var våren", "vår")
query: data.test
want_result:
v1: 9
- note: undefined-string
data: {}
modules:
- |
package test
x { false }
y = indexof(x, "")
query: data.test
want_result: {}
- note: undefined-substring
data: {}
modules:
- |
package test
x { false }
y = indexof(",", x)
query: data.test
want_result: {}
- note: invalid-null-string
data: {}
modules: ["package test\nx=indexof(null, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-bool-string
data: {}
modules: ["package test\nx=indexof(true, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-number-string
data: {}
modules: ["package test\nx=indexof(1, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-array-string
data: {}
modules: ["package test\nx=indexof([], ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-set-string
data: {}
modules: ["package test\nx=indexof(set(), ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-object-string
data: {}
modules: ["package test\nx=indexof({}, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-null-substring
data: {}
modules: ["package test\nx=indexof(``, null)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-bool-substring
data: {}
modules: ["package test\nx=indexof(``, true)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-number-substring
data: {}
modules: ["package test\nx=indexof(``, 1)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-array-substring
data: {}
modules: ["package test\nx=indexof(``, [])"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-set-substring
data: {}
modules: ["package test\nx=indexof(``, set())"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-object-substring
data: {}
modules: ["package test\nx=indexof(``, {})"]
query: data.test
error: "`indexof` expects string argument."

View File

@@ -0,0 +1,79 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: base
data: {}
modules:
- |
package test
v1 = lower("") # empty string
v2 = lower("a")
v3 = lower("A")
v4 = lower("AbCd")
v5 = lower("aBcD109")
v6 = lower("aabAB09_")
v7 = lower("longStrinGwitHmixofsmaLLandcAps")
query: data.test
want_result:
v1: ""
v2: "a"
v3: "a"
v4: "abcd"
v5: "abcd109"
v6: "aabab09_"
v7: "longstringwithmixofsmallandcaps"
- note: unicode string
data: {}
modules:
- |
package test
v1 = lower("Σ")
v2 = lower("ὈΔΥΣΣΕΎΣ")
v3 = lower("LONG\u2C6FSTRING\u2C6FWITH\u2C6FNONASCII\u2C6FCHARS")
query: data.test
want_result:
v1: "σ"
v2: "ὀδυσσεύς"
v3: "long\u0250string\u0250with\u0250nonascii\u0250chars"
- note: invalid-null-string
data: {}
modules: ["package test\nx=lower(null)"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-bool-string
data: {}
modules: ["package test\nx=lower(true)"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-number-string
data: {}
modules: ["package test\nx=lower(1)"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-array-string
data: {}
modules: ["package test\nx=lower([])"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-set-string
data: {}
modules: ["package test\nx=lower(set())"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-object-string
data: {}
modules: ["package test\nx=lower({})"]
query: data.test
error: "`lower` expects string argument."

View File

@@ -0,0 +1,17 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: empty separator
data: {}
modules: []
query: "x := split(\"test\", \"\")"
want_result:
x: ["t", "e", "s", "t"]
- note: empty separator, empty string
data: {}
modules: []
query: "x := split(\"\", \"\")"
want_result:
x: []

View File

@@ -59,3 +59,20 @@ cases:
a1 = inc(5)
query: data.test
want_result: {}
- note: call parameter raises error
data: {}
modules:
- |
package test
import rego.v1
bar := 1 if {
1 + "hello"
}
foo := 1 if {
count(bar)
}
query: data.test
error: expects numeric argument.

View File

@@ -0,0 +1,45 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: treat { 1 | 1 } as comprehension
data: {}
modules:
- |
package test
import rego.v1
x if { 1 | 1 }
y := { 1 | 1 }
z := { (1) | 1}
# Parsed as a set
a := { ({1} | {2}) }
b := v if { { 1 | 1 } = v }
query: data.test
want_result:
x: true
y:
set!: [1]
z:
set!: [1]
a:
set!:
- set!: [1, 2]
b:
set!: [1]
- note: rule body is object comprehension
data: {}
modules:
- |
package test
import future.keywords
x if { 1:2 | some p in [1,2] }
y := 2 if { 1:2 | some p in [1,2] }
query: data.test
want_result:
x: true
y: 2

View File

@@ -34,6 +34,7 @@ cases:
- |
package b
import rego.v1
# Both the following imports are overridden by rules
#import data.a.b as a
#import data.a.b
@@ -43,6 +44,10 @@ cases:
a = 10
c = C + b
r if {
some v in [C]
}
query: data
want_result:
a:
@@ -50,6 +55,7 @@ cases:
b:
a: 10
c: 22
r: true
- note: import overridden by rule
modules:

View File

@@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: basic
modules:
- |
package test
import rego.v1
x := data.foo or 2 # undefined lhs
y := false or 3 # false rhs
z := null or 4
a := data.foo or false or null or 5
b := startswith("a", "b") or startswith("a", "a")
c := 5 in [1,2] or 6 in [6]
d := x if {
x := false or [1, 2][_]
x > 1
}
e if 1 > 2 or false
query: data.test
want_result:
x: 2
y: 3
z: 4
a: 5
b: true
c: true
d: 2
- note: Azure Policy
modules:
- |
package policy
effect := parameters.effect if {
resource.type == "Microsoft.Storage/storageaccounts"
resource.properties.networkAcls.defaultAction == "Deny"
or count(resource.properties.networkAcls.ipRules) >= 1
}
resource := input.resource
parameters := input.parameters
input:
resource:
type: "Microsoft.Storage/storageaccounts"
properties:
networksAcls:
ipRules: ["rule1", "rule2"]
parameters:
effect: "Deny"
query: data.policy.effect
want_result: "Deny"

View File

@@ -2,17 +2,34 @@
# Licensed under the MIT License.
cases:
- note: else without body
# - note: else without body
# data: {}
# modules:
# - |
# package test
# x = 4 {
# false
# } else = 5
# y = 6
# query: data.test
# want_result:
# x: 5
# y: 6
- note: undefined values being assigned
data: {}
modules:
- |
package test
x = 4 {
false
} else = 5
y = 6
import rego.v1
x := data.y if {
true
} else := 2 if {
true
}
query: data.test
want_result:
x: 5
y: 6
x: 2

View File

@@ -0,0 +1,26 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: undefined components
data: {}
modules:
- |
package test
import rego.v1
principal := input.principal
action := input.action
p[principal][action] := 1 if {
some a in []
}
q[principal][action] contains 1 if {
some a in []
}
query: data.test
want_result:
p: {}
q: {}

View File

@@ -172,6 +172,64 @@ struct Cli {
generate: bool,
}
fn stateful_policy_test() -> Result<()> {
// Create an engine for evaluating Rego policies.
let mut engine = regorus::Engine::new();
let policy = String::from(
r#"
package example
import rego.v1
default allow := false
allow if {
print("data.allowed_actions = ", data.allowed_actions)
input.action in data.allowed_actions["user1"]
print("This rule should be allowed")
}
"#,
);
// Add policy to the engine.
engine.add_policy(String::from("policy.rego"), policy)?;
// Evaluate first input. Expect to evaluate to false, since state is not set
engine.set_input(regorus::Value::from_json_str(
r#"{
"action": "write"
}"#,
)?);
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
println!("Received result: {:?}", r);
assert_eq!(r, false);
// Add data to engine. Set state
engine.add_data(regorus::Value::from_json_str(
r#"{
"allowed_actions": {
"user1" : ["read", "write"]
}}"#,
)?)?;
// Evaluate second input. Expect to evaluate to true, since state has been set now
engine.set_input(regorus::Value::from_json_str(
r#"{
"action": "write"
}"#,
)?);
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
println!("Received result: {:?}", r);
assert_eq!(
r, true,
"expect result to be true since rule evaluates to true after state has been updated, per rego logs"
);
Ok(())
}
fn main() -> Result<()> {
let cli = Cli::parse();
run_kata_tests(
@@ -179,5 +237,6 @@ fn main() -> Result<()> {
&cli.name,
cli.coverage,
cli.generate,
)
)?;
stateful_policy_test()
}

View File

@@ -1,115 +1,230 @@
aggregates
all
any
arithmetic
array
assignments
base64builtins
base64urlbuiltins
baseandvirtualdocs
bitsand
bitsnegate
bitsor
bitsshiftleft
bitsshiftright
bitsxor
casts
comparisonexpr
completedoc
compositebasedereference
compositereferences
comprehensions
containskeyword
cryptohmacequal
cryptohmacmd5
cryptohmacsha1
cryptohmacsha256
cryptohmacsha512
cryptomd5
cryptosha1
cryptosha256
dataderef
defaultkeyword
disjunction
elsekeyword
embeddedvirtualdoc
eqexpr
evaltermexpr
every
example
fix1863
functionerrors
functions
globmatch
globquotemeta
helloworld
hexbuiltins
indexing
indirectreferences
inputvalues
intersection
jsonbuiltins
jsonfilter
jsonfilteridempotent
jsonremove
jsonremoveidempotent
jsonschema
jwtbuiltins
negation
nestedreferences
numbersrange
numbersrangestep
objectfilter
objectfilteridempotent
objectfilternonstringkey
objectget
objectkeys
objectremove
objectremoveidempotent
objectremovenonstringkey
objectunion
objectunionn
partialdocconstants
partialiter
partialobjectdoc
partialsetdoc
planner-ir
rand
reachable
refheads
regexfind
regexfindallstringsubmatch
regexisvalid
regexmatch
regexmatchtemplate
regexreplace
regexsplit
replacen
semvercompare
semverisvalid
sets
sprintf
strings
subset
toarray
topdowndynamicdispatch
toset
time
trim
trimleft
trimprefix
trimright
trimspace
trimsuffix
type
typebuiltin
typenamebuiltin
undos
union
units
urlbuiltins
uuid
varreferences
virtualdocs
walkbuiltin
withkeyword
v0/aggregates
v0/all
v0/any
v0/arithmetic
v0/array
v0/assignments
v0/base64builtins
v0/base64urlbuiltins
v0/baseandvirtualdocs
v0/bitsand
v0/bitsnegate
v0/bitsor
v0/bitsshiftleft
v0/bitsshiftright
v0/bitsxor
v0/casts
v0/comparisonexpr
v0/completedoc
v0/compositebasedereference
v0/compositereferences
v0/comprehensions
v0/containskeyword
v0/cryptohmacequal
v0/cryptohmacmd5
v0/cryptohmacsha1
v0/cryptohmacsha256
v0/cryptohmacsha512
v0/cryptomd5
v0/cryptosha1
v0/cryptosha256
v0/dataderef
v0/defaultkeyword
v0/disjunction
v0/elsekeyword
v0/embeddedvirtualdoc
v0/eqexpr
v0/evaltermexpr
v0/every
v0/example
v0/fix1863
v0/functionerrors
v0/functions
v0/globmatch
v0/globquotemeta
v0/helloworld
v0/hexbuiltins
v0/indexing
v0/indirectreferences
v0/inputvalues
v0/intersection
v0/jsonbuiltins
v0/jsonfilter
v0/jsonfilteridempotent
v0/jsonremove
v0/jsonremoveidempotent
v0/jsonschema
v0/jwtbuiltins
v0/negation
v0/nestedreferences
v0/numbersrange
v0/numbersrangestep
v0/objectfilter
v0/objectfilteridempotent
v0/objectfilternonstringkey
v0/objectget
v0/objectkeys
v0/objectremove
v0/objectremoveidempotent
v0/objectremovenonstringkey
v0/objectunion
v0/objectunionn
v0/partialdocconstants
v0/partialiter
v0/partialobjectdoc
v0/partialsetdoc
v0/planner-ir
v0/rand
v0/reachable
v0/refheads
v0/regexfind
v0/regexfindallstringsubmatch
v0/regexisvalid
v0/regexmatch
v0/regexmatchtemplate
v0/regexreplace
v0/regexsplit
v0/replacen
v0/semvercompare
v0/semverisvalid
v0/sets
v0/sprintf
v0/strings
v0/subset
v0/toarray
v0/topdowndynamicdispatch
v0/toset
v0/time
v0/trim
v0/trimleft
v0/trimprefix
v0/trimright
v0/trimspace
v0/trimsuffix
v0/type
v0/typebuiltin
v0/typenamebuiltin
v0/undos
v0/union
v0/units
v0/urlbuiltins
v0/uuid
v0/varreferences
v0/virtualdocs
v0/walkbuiltin
v0/withkeyword
v1/aggregates
v1/all
v1/any
v1/arithmetic
v1/array
v1/assignments
v1/base64builtins
v1/base64urlbuiltins
v1/baseandvirtualdocs
v1/bitsand
v1/bitsnegate
v1/bitsor
v1/bitsshiftleft
v1/bitsshiftright
v1/bitsxor
v1/casts
v1/comparisonexpr
v1/completedoc
v1/compositebasedereference
v1/compositereferences
v1/comprehensions
v1/containskeyword
v1/cryptohmacequal
v1/cryptohmacmd5
v1/cryptohmacsha1
v1/cryptohmacsha256
v1/cryptohmacsha512
v1/cryptomd5
v1/cryptosha1
v1/cryptosha256
v1/dataderef
v1/defaultkeyword
v1/disjunction
v1/elsekeyword
v1/embeddedvirtualdoc
v1/eqexpr
v1/evaltermexpr
v1/every
v1/example
v1/fix1863
v1/functionerrors
v1/functions
v1/globmatch
v1/globquotemeta
v1/helloworld
v1/hexbuiltins
v1/indexing
v1/indirectreferences
v1/inputvalues
v1/intersection
v1/jsonbuiltins
v1/jsonfilter
v1/jsonfilteridempotent
v1/jsonremove
v1/jsonremoveidempotent
v1/jsonschema
v1/jwtbuiltins
v1/negation
v1/nestedreferences
v1/numbersrange
v1/numbersrangestep
v1/objectfilter
v1/objectfilteridempotent
v1/objectfilternonstringkey
v1/objectget
v1/objectkeys
v1/objectremove
v1/objectremoveidempotent
v1/objectremovenonstringkey
v1/objectunion
v1/objectunionn
v1/partialdocconstants
v1/partialiter
v1/partialobjectdoc
v1/partialsetdoc
v1/planner-ir
v1/rand
v1/reachable
v1/refheads
v1/regexfind
v1/regexfindallstringsubmatch
v1/regexisvalid
v1/regexmatch
v1/regexmatchtemplate
v1/regexreplace
v1/regexsplit
v1/replacen
v1/semvercompare
v1/semverisvalid
v1/sets
v1/sprintf
v1/strings
v1/subset
v1/toarray
v1/topdowndynamicdispatch
v1/toset
v1/time
v1/trim
v1/trimleft
v1/trimprefix
v1/trimright
v1/trimspace
v1/trimsuffix
v1/type
v1/typebuiltin
v1/typenamebuiltin
v1/undos
v1/union
v1/units
v1/urlbuiltins
v1/uuid
v1/varreferences
v1/virtualdocs
v1/walkbuiltin
v1/withkeyword

View File

@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
const OPA_BRANCH: &str = "v0.64.0";
const OPA_BRANCH: &str = "v0.69.0";
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
@@ -51,12 +51,14 @@ struct YamlTest {
cases: Vec<TestCase>,
}
fn eval_test_case(case: &TestCase) -> Result<Value> {
fn eval_test_case(case: &TestCase, is_rego_v1_test: bool) -> Result<Value> {
let mut engine = Engine::new();
#[cfg(feature = "coverage")]
engine.set_enable_coverage(true);
engine.set_rego_v1(is_rego_v1_test);
if let Some(data) = &case.data {
engine.add_data(data.clone())?;
}
@@ -172,6 +174,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
continue;
}
let is_rego_v1_test = path_dir_str.starts_with("v1/") || path_dir.starts_with("v1\\");
let entry = status.entry(path_dir_str).or_insert((0, 0, 0));
let yaml_str = std::fs::read_to_string(&path_str)?;
@@ -216,7 +219,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
print!("{:4}: {:90}", entry.2, case.note);
entry.2 += 1;
match (eval_test_case(&case), &case.want_result) {
match (eval_test_case(&case, is_rego_v1_test), &case.want_result) {
(Ok(actual), Some(expected))
if is_json_schema_test && json_schema_tests_check(&actual, &expected) =>
{
@@ -325,9 +328,9 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
if npass == 0 && nfail == 0 {
bail!("no matching tests found.");
} else if nfail == 0 {
println!("\x1b[32m {:42}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
println!("\x1b[32m {:40}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
} else {
println!("\x1b[31m {:42}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
println!("\x1b[31m {:40}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
}
if !missing_functions.is_empty() {

View File

@@ -198,6 +198,13 @@ fn match_expr_impl(e: &Expr, v: &Value) -> Result<()> {
match_expr(value, &v["inexpr"]["value"])?;
match_expr(collection, &v["inexpr"]["collection"])
}
#[cfg(feature = "rego-extensions")]
Expr::OrExpr { span, lhs, rhs } => {
match_span_opt(span, &v["orexpr"]["span"])?;
match_expr(lhs, &v["orexpr"]["lhs"])?;
match_expr(rhs, &v["orexpr"]["rhs"])
}
}
}
@@ -324,8 +331,8 @@ fn match_expr_opt(s: &Span, e: &Option<Ref<Expr>>, v: &Value) -> Result<()> {
fn match_bin_op(s: &Span, op: &BinOp, v: &Value) -> Result<()> {
match (op, v) {
(BinOp::And, Value::String(s)) if s.as_ref() == "&" => Ok(()),
(BinOp::Or, Value::String(s)) if s.as_ref() == "|" => Ok(()),
(BinOp::Intersection, Value::String(s)) if s.as_ref() == "&" => Ok(()),
(BinOp::Union, Value::String(s)) if s.as_ref() == "|" => Ok(()),
_ => bail!(
"{}",
s.source.message(