From ff842f5036b3c31b51481a6ad0a07f4612874baa Mon Sep 17 00:00:00 2001 From: Yann D'Isanto Date: Wed, 15 May 2024 18:23:51 +0200 Subject: [PATCH 1/5] feat: configurable external fga server --- .gitignore | 5 +++++ README.md | 16 ++++++++-------- action.yml | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..440c543 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ + + +# IntelliJ IDEA +.idea +*.iml \ No newline at end of file diff --git a/README.md b/README.md index edc2e74..6b43cc2 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ # OpenFGA Github Action - Test -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test?ref=badge_shield) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test?ref=badge_shield) This action can be used to test your authorization model using store test files. ## Parameter -| Parameter | Description | Required | Default | -|----------------------|----------------------------------------------------------------------------------|----------|--------------| -| `test_path` | The path to your store test file or folder relative to the root of your project. | No | `.` | -| `test_files_pattern` | The pattern to match test files. | No | `*.fga.yaml` | +| Parameter | Description | Required | Default | +|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------| +| `test_path` | The path to your store test file or folder relative to the root of your project. | No | `.` | +| `test_files_pattern` | The pattern to match test files. | No | `*.fga.yaml` | +| `fga_server_url` | The OpenFGA server to test the Authorization Model against. If empty (which is the default value), the tests are run using the cli built-in OpenFGA instance. | No | _empty_ | +| `fga_api_token` | The api token to use for testing against an OpenFGA server. Ignored if `fga_server_url` is not provided. | No | _empty_ | > Note: the action will fail if no test is found in the specified test path with the given pattern - ## Examples - ### Running tests of `*.fga.yaml` files present in the repository ```yaml @@ -69,6 +69,6 @@ jobs: test_path: example/model.fga.yaml ``` - ## License + [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test?ref=badge_large) diff --git a/action.yml b/action.yml index 5c01ebe..e0c2826 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,15 @@ inputs: description: 'Pattern to match the test files' required: false default: '*.fga.yaml' + fga_server_url: + description: 'The OpenFGA server to test the Authorization Model against. If not provided, the tests will be run using the cli built-in OpenFGA instance.' + required: false + default: '' + fga_api_token: + description: 'The api token to use for testing against an OpenFGA server. Ignored if fga_server_url is not provided.' + required: false + default: '' + runs: using: composite @@ -24,15 +33,23 @@ runs: cache: enable - name: Run OpenFGA CLI shell: bash + env: + FGA_SERVER_URL: ${{ inputs.fga_server_url }} + FGA_API_TOKEN: ${{ inputs.fga_api_token }} run: | - while IFS= read -r -d '' test_file - do - ((test_files_count+=1)) - echo "Running FGA test file ${test_file}" - fga model test --tests "${test_file}" - done < <(find ${{ inputs.test_path }} -name "${{ inputs.test_files_pattern }}" -print0) - - if [[ ${test_files_count} -eq 0 ]]; then - echo "No FGA test file found for path '${{ inputs.test_path }}' and pattern '${{ inputs.test_files_pattern }}'" - exit 1 - fi \ No newline at end of file + fga_opts="" + if [[ -n "${FGA_SERVER_URL}" ]]; then + fga_opts="--api-url ${FGA_SERVER_URL} ${FGA_API_TOKEN:+--api-token ${FGA_API_TOKEN}}" + fi + + while IFS= read -r -d '' test_file + do + ((test_files_count+=1)) + echo "Running FGA test file ${test_file}" + fga model test ${fga_opts} --tests "${test_file}" + done < <(find ${{ inputs.test_path }} -name "${{ inputs.test_files_pattern }}" -print0) + + if [[ ${test_files_count} -eq 0 ]]; then + echo "No FGA test file found for path '${{ inputs.test_path }}' and pattern '${{ inputs.test_files_pattern }}'" + exit 1 + fi \ No newline at end of file From 10ecf0b0349fb491e462b44a899434d25cb00eb4 Mon Sep 17 00:00:00 2001 From: Yann D'Isanto Date: Thu, 16 May 2024 08:30:45 +0200 Subject: [PATCH 2/5] docs: update README with external server example --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 6b43cc2..0bb5cb9 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,50 @@ jobs: test_path: example/model.fga.yaml ``` +### Running tests against a given version of OpenFGA + +```yaml +name: Test Action + +on: + workflow_dispatch: + +jobs: + test: + name: Run test + runs-on: ubuntu-latest + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: openfga + POSTGRES_PASSWORD: '1234' + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + env: + OPENFGA_DATASTORE_ENGINE: 'postgres' + OPENFGA_DATASTORE_URI: 'postgres://openfga:1234@127.0.0.1:5432/openfga' + OPENFGA_LOG_LEVEL: debug + steps: + - name: Install OpenFGA server v1.5.3 + uses: jaxxstorm/action-install-gh-release@v1.11.0 + with: + repo: openfga/openfga + tag: v1.5.3 + cache: enable + - name: Migrate OpenFGA database + shell: bash + run: openfga migrate + - name: Start OpenFGA server in background + shell: bash + run: openfga run & + - name: Run tests + uses: openfga/action-openfga-test@v0.1 + with: + fga_server_url: 'http://localhost:8080' +``` + ## License [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fopenfga%2Faction-openfga-test?ref=badge_large) From d56a4f70d8574854fdd3bac0526b7c1f18ff27a8 Mon Sep 17 00:00:00 2001 From: Yann D'Isanto Date: Thu, 16 May 2024 09:32:19 +0200 Subject: [PATCH 3/5] ci: test with specified OpenFGA server --- .github/workflows/test.yml | 60 ++++++++++++++++++++++++ example/model_with_conditions.fga | 12 +++++ example/model_with_conditions.fga.yaml | 64 ++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 example/model_with_conditions.fga create mode 100644 example/model_with_conditions.fga.yaml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76ccc21..5b9c31e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,3 +24,63 @@ jobs: uses: ./ with: test_path: ./example/model.fga.yaml + + test_conditions_against_openfga_version: + name: Run test against given OpenFGA version + runs-on: ubuntu-latest + strategy: + matrix: + test: + - openfga_tag: v1.5.3 + conditions_supported: true + - openfga_tag: v1.4.3 + conditions_supported: true + - openfga_tag: v1.3.7 + conditions_supported: false + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: openfga + POSTGRES_PASSWORD: "1234" + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + env: + OPENFGA_DATASTORE_ENGINE: 'postgres' + OPENFGA_DATASTORE_URI: 'postgres://openfga:1234@127.0.0.1:5432/openfga' + OPENFGA_LOG_LEVEL: debug + steps: + - uses: actions/checkout@v4 + - name: Install OpenFGA server ${{ matrix.test.openfga_tag }} + uses: jaxxstorm/action-install-gh-release@v1.11.0 + with: + repo: openfga/openfga + tag: ${{ matrix.test.openfga_tag }} + cache: enable + - name: Migrate OpenFGA Database + shell: bash + run: openfga migrate + - name: Start OpenFGA Server + shell: bash + run: openfga run & + - name: Run OpenFGA CLI Tests + id: 'tests' + uses: ./ + continue-on-error: true + with: + test_path: ./example/model_with_conditions.fga.yaml + fga_server_url: 'http://localhost:8080' + - name: Assert expected results + run: | + if [ "${{ matrix.test.conditions_supported }}" == "true" ] && [ "${{ steps.tests.outcome }}" == "failure" ] + then + echo "${{ matrix.test.openfga_tag }} is expected to support conditions but tests failed" + exit 1 + fi + if [ "${{ matrix.test.conditions_supported }}" == "false" ] && [ "${{ steps.tests.outcome }}" == "success" ] + then + echo "${{ matrix.test.openfga_tag }} is expected to not support conditions but tests passed" + exit 1 + fi + diff --git a/example/model_with_conditions.fga b/example/model_with_conditions.fga new file mode 100644 index 0000000..be929ae --- /dev/null +++ b/example/model_with_conditions.fga @@ -0,0 +1,12 @@ +model + schema 1.1 + +type user + +type document + relations + define viewer: [user, user with non_expired_grant] + +condition non_expired_grant(current_time: timestamp, grant_time: timestamp, grant_duration: duration) { + current_time < grant_time + grant_duration +} \ No newline at end of file diff --git a/example/model_with_conditions.fga.yaml b/example/model_with_conditions.fga.yaml new file mode 100644 index 0000000..89991c5 --- /dev/null +++ b/example/model_with_conditions.fga.yaml @@ -0,0 +1,64 @@ +name: FolderBox with temporal accesses # store name +model_file: ./model_with_conditions.fga + +tuples: + - user: user:bob + relation: viewer + object: document:1 + + - user: user:anne + relation: viewer + object: document:1 + condition: + name: non_expired_grant + context: + grant_time : "2023-01-01T00:00:00Z" + grant_duration : 1h + + - user: user:anne + relation: viewer + object: document:2 + condition: + name: non_expired_grant + context: + grant_time : "2023-01-01T00:00:00Z" + grant_duration : 5s + +tests: + - name: Test temporal access + check: + - user: user:anne + object: document:1 + context: + current_time: "2023-01-01T00:10:00Z" + assertions: + viewer: true + + - user: user:anne + object: document:1 + context: + current_time: "2023-01-01T02:00:00Z" + assertions: + viewer: false + + - user: user:anne + object: document:2 + context: + current_time: "2023-01-01T00:00:09Z" + assertions: + viewer: false + + - user: user:bob + object: document:1 + assertions: + viewer: true + + list_objects: + - user: user:anne + type: document + context: + current_time: "2023-01-01T00:00:01Z" + assertions: + viewer: + - document:1 + - document:2 \ No newline at end of file From 07bf422e1e7cb4e45c1484da5e0c6e37db40bc15 Mon Sep 17 00:00:00 2001 From: Yann D'Isanto Date: Thu, 16 May 2024 14:12:11 +0200 Subject: [PATCH 4/5] fix: missing store --- .github/workflows/test.yml | 19 +++++++++++++++++-- README.md | 28 ++++++++++++++++++++++------ action.yml | 16 +++++++++++----- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b9c31e..23bc879 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,8 +25,8 @@ jobs: with: test_path: ./example/model.fga.yaml - test_conditions_against_openfga_version: - name: Run test against given OpenFGA version + test_conditions_support: + name: Test conditions support runs-on: ubuntu-latest strategy: matrix: @@ -64,6 +64,20 @@ jobs: - name: Start OpenFGA Server shell: bash run: openfga run & + - name: Install OpenFGA cli + uses: jaxxstorm/action-install-gh-release@v1.11.0 + with: + repo: openfga/cli + cache: enable + - name: Install jq + uses: dcarbone/install-jq-action@v2 + - name: Create store with model + id: 'store' + run: | + fga store create --model ./example/model_with_conditions.fga > store_response.json + cat store_response.json + store_id=$(jq -r '.store.id' store_response.json) + echo "store_id=${store_id}" >> $GITHUB_OUTPUT - name: Run OpenFGA CLI Tests id: 'tests' uses: ./ @@ -71,6 +85,7 @@ jobs: with: test_path: ./example/model_with_conditions.fga.yaml fga_server_url: 'http://localhost:8080' + fga_server_store_id: ${{ steps.store.outputs.store_id }} - name: Assert expected results run: | if [ "${{ matrix.test.conditions_supported }}" == "true" ] && [ "${{ steps.tests.outcome }}" == "failure" ] diff --git a/README.md b/README.md index 0bb5cb9..84d0212 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,13 @@ This action can be used to test your authorization model using store test files. ## Parameter -| Parameter | Description | Required | Default | -|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------| -| `test_path` | The path to your store test file or folder relative to the root of your project. | No | `.` | -| `test_files_pattern` | The pattern to match test files. | No | `*.fga.yaml` | -| `fga_server_url` | The OpenFGA server to test the Authorization Model against. If empty (which is the default value), the tests are run using the cli built-in OpenFGA instance. | No | _empty_ | -| `fga_api_token` | The api token to use for testing against an OpenFGA server. Ignored if `fga_server_url` is not provided. | No | _empty_ | +| Parameter | Description | Required | Default | +|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------| +| `test_path` | The path to your store test file or folder relative to the root of your project. | No | `.` | +| `test_files_pattern` | The pattern to match test files. | No | `*.fga.yaml` | +| `fga_server_url` | The OpenFGA server to test the Authorization Model against. If empty (which is the default value), the tests are run using the cli built-in OpenFGA instance. | No | _empty_ | +| `fga_server_store_id` | The OpenFGA server store id. Must be provided if fga_server_url is configured. | No | _empty_ | +| `fga_api_token` | The api token to use for testing against an OpenFGA server. Ignored if `fga_server_url` is not provided. | No | _empty_ | > Note: the action will fail if no test is found in the specified test path with the given pattern @@ -107,10 +108,25 @@ jobs: - name: Start OpenFGA server in background shell: bash run: openfga run & + - name: Install OpenFGA cli + uses: jaxxstorm/action-install-gh-release@v1.11.0 + with: + repo: openfga/cli + cache: enable + - name: Install jq + uses: dcarbone/install-jq-action@v2 + - name: Create store with model + id: 'store' + run: | + fga store create --model ./example/model_with_conditions.fga > store_response.json + cat store_response.json + store_id= $(jq -r '.store.id' store_response.json) + echo "store_id=${store_id}" >> $GITHUB_OUTPUT - name: Run tests uses: openfga/action-openfga-test@v0.1 with: fga_server_url: 'http://localhost:8080' + fga_server_store_id: ${{ steps.store.outputs.store_id }} ``` ## License diff --git a/action.yml b/action.yml index e0c2826..77033ca 100644 --- a/action.yml +++ b/action.yml @@ -17,6 +17,10 @@ inputs: description: 'The OpenFGA server to test the Authorization Model against. If not provided, the tests will be run using the cli built-in OpenFGA instance.' required: false default: '' + fga_server_store_id: + description: 'The OpenFGA server store id. Must be provided if fga_server_url is configured.' + required: false + default: '' fga_api_token: description: 'The api token to use for testing against an OpenFGA server. Ignored if fga_server_url is not provided.' required: false @@ -33,13 +37,15 @@ runs: cache: enable - name: Run OpenFGA CLI shell: bash - env: - FGA_SERVER_URL: ${{ inputs.fga_server_url }} - FGA_API_TOKEN: ${{ inputs.fga_api_token }} run: | fga_opts="" - if [[ -n "${FGA_SERVER_URL}" ]]; then - fga_opts="--api-url ${FGA_SERVER_URL} ${FGA_API_TOKEN:+--api-token ${FGA_API_TOKEN}}" + fga_token="${{ inputs.fga_api_token }}" + if [[ -n "${{ inputs.fga_server_url }}" ]]; then + if [[ -z "${{ inputs.fga_server_store_id }}" ]]; then + echo "missing store id for specified OpenFGA server ${{ inputs.fga_server_url }}." + exit 1 + fi + fga_opts="--api-url ${{ inputs.fga_server_url }} --store-id ${{ inputs.fga_server_store_id }} ${fga_token:+--api-token ${fga_token}}" fi while IFS= read -r -d '' test_file From 74cb4ae465a8ffe00d0e0689a52a77f4f2dbb862 Mon Sep 17 00:00:00 2001 From: Yann D'Isanto Date: Thu, 16 May 2024 14:53:47 +0200 Subject: [PATCH 5/5] fix: remove model for testing against given openfga server --- README.md | 14 +++++++------- action.yml | 32 ++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 84d0212..035b61f 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,13 @@ This action can be used to test your authorization model using store test files. ## Parameter -| Parameter | Description | Required | Default | -|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------| -| `test_path` | The path to your store test file or folder relative to the root of your project. | No | `.` | -| `test_files_pattern` | The pattern to match test files. | No | `*.fga.yaml` | -| `fga_server_url` | The OpenFGA server to test the Authorization Model against. If empty (which is the default value), the tests are run using the cli built-in OpenFGA instance. | No | _empty_ | -| `fga_server_store_id` | The OpenFGA server store id. Must be provided if fga_server_url is configured. | No | _empty_ | -| `fga_api_token` | The api token to use for testing against an OpenFGA server. Ignored if `fga_server_url` is not provided. | No | _empty_ | +| Parameter | Description | Required | Default | +|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------| +| `test_path` | The path to your store test file or folder relative to the root of your project. | No | `.` | +| `test_files_pattern` | The pattern to match test files. | No | `*.fga.yaml` | +| `fga_server_url` | The OpenFGA server to test the Authorization Model against. If empty (which is the default value), the tests are run using the cli built-in OpenFGA instance. If specified, it is mandatory to specify the store id with the `fga_server_store_id` input, also the `model` and `model_file` entries of the tests are ignored | No | _empty_ | +| `fga_server_store_id` | The OpenFGA server store id. Must be provided if fga_server_url is configured. | No | _empty_ | +| `fga_api_token` | The api token to use for testing against an OpenFGA server. Ignored if `fga_server_url` is not provided. | No | _empty_ | > Note: the action will fail if no test is found in the specified test path with the given pattern diff --git a/action.yml b/action.yml index 77033ca..6ab2930 100644 --- a/action.yml +++ b/action.yml @@ -35,24 +35,32 @@ runs: with: repo: openfga/cli cache: enable + - uses: chrisdickinson/setup-yq@v1.0.1 + with: + yq-version: v4.25.3 - name: Run OpenFGA CLI shell: bash run: | - fga_opts="" - fga_token="${{ inputs.fga_api_token }}" - if [[ -n "${{ inputs.fga_server_url }}" ]]; then - if [[ -z "${{ inputs.fga_server_store_id }}" ]]; then - echo "missing store id for specified OpenFGA server ${{ inputs.fga_server_url }}." - exit 1 - fi - fga_opts="--api-url ${{ inputs.fga_server_url }} --store-id ${{ inputs.fga_server_store_id }} ${fga_token:+--api-token ${fga_token}}" - fi - while IFS= read -r -d '' test_file do ((test_files_count+=1)) - echo "Running FGA test file ${test_file}" - fga model test ${fga_opts} --tests "${test_file}" + + if [[ -z "${{ inputs.fga_server_url }}" ]]; then + echo "Running FGA test file ${test_file}" + fga model test --tests "${test_file}" + else + echo "Running FGA test file ${test_file} against OpenFGA server ${{ inputs.fga_server_url }}" + fga_token="${{ inputs.fga_api_token }}" + test_file_without_model=mktemp + yq 'del(.model_file, .model)' ${test_file} > ${test_file_without_model} + fga model test \ + ${fga_server_opts} \ + --api-url "${{ inputs.fga_server_url }}" \ + --store-id "${{ inputs.fga_server_store_id }}" \ + ${fga_token:+--api-token ${fga_token}} \ + --tests "${test_file_without_model}" + fi + done < <(find ${{ inputs.test_path }} -name "${{ inputs.test_files_pattern }}" -print0) if [[ ${test_files_count} -eq 0 ]]; then