diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 9e06ba19..53247e6a 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -85,3 +85,26 @@ jobs: - name: Run knip run: pnpm run knip + lint-rego: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: open-policy-agent/setup-regal@v2.0.0 + with: + version: 0.41.1 + + - name: Lint + run: regal lint --format=github ./policy + test-rego: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v6 + + - name: Setup OPA + uses: open-policy-agent/setup-opa@v2 + with: + version: 1.17.1 + + - name: Run OPA Tests + run: opa test ./policy -v diff --git a/.regal/config.yaml b/.regal/config.yaml new file mode 100644 index 00000000..b55ace5a --- /dev/null +++ b/.regal/config.yaml @@ -0,0 +1,17 @@ +rules: + imports: + unresolved-reference: + level: error + except-paths: + - data.keys + - data.possibleLocations + - data.users + - data.keys.kid + - data.users.avi.allowNoBrowser + - data.users.avi.allowNoOrigin + style: + line-length: + level: ignore + idiomatic: + directory-package-mismatch: + level: ignore diff --git a/.vscode/settings.json b/.vscode/settings.json index 9121c9f0..cdff7df8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,7 @@ ], "[github-actions-workflow]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "opa.dependency_paths.opa": "${workspaceFolder}/policy/opa", + "opa.dependency_paths.regal": "${workspaceFolder}/policy/regal" } diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 1945d060..00000000 --- a/example/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# End to end example - -_Before starting make sure you have auth-manager and auth-cron deployed._ - -1. Post all the files in the data folder into the auth-manager api. - -2. Deploy OPA cli as a server. `opa run -s -c opa.yaml` - -3. Deploy nginx with the files in the nginx folder. - -4. Make requests to OPA and check if they are authenticated or not. diff --git a/example/data/policy.rego b/example/data/policy.rego deleted file mode 100644 index 2557aff4..00000000 --- a/example/data/policy.rego +++ /dev/null @@ -1,118 +0,0 @@ -package http.authz - -lower_object_keys(obj) := newObj if { - newObj := {k: v | - some i - v := obj[i] - k := lower(i) - } -} - -constraints := { - "cert": json.marshal({"keys": [data.keys]}), - "alg": "RS256", -} - -headers := lower_object_keys(input.headers) - -tokens := [token | - keys := data.possibleLocations[_] - lowerObj := lower_object_keys(object.get(input, keys[0], "")) - temp := object.get(lowerObj, keys[1], null) - temp != null - token := temp -] - -claims := {"payload": payload, "valid": valid, "kid": kid} if { - token := tokens[_] - [header, payload, _] := io.jwt.decode(token) - kid := object.get(header, "kid", null) # Extract kid from JWT header - [valid, _, _] := io.jwt.decode_verify(token, constraints) -} - -userData := {"domains": ["raster"], "allowNoOrigin": true, "allowNoBrowser": true, "origins": []} if { - claims.payload.sub == "c2b" -} - -userData := data.users[claims.payload.sub] if { - claims.payload.sub != "c2b" -} - -deny contains "no token supplied in any of the possible locations" if { - count(tokens) == 0 -} - -deny contains "token environment mismatch" if { - claims.kid != data.keys.kid -} - -deny contains "token not valid" if { - not claims.valid - count(tokens) > 0 -} - -deny contains "the token is valid, but the user is not found" if { - claims.valid - not userData -} - -deny contains "domain missing" if { - not input.domain -} - -deny contains "domain check failed" if { - every domain in userData.domains { domain != input.domain } -} - -is_origin_invalid(originHeader, allowedOrigin) if { - allowedOrigin != originHeader - not glob.match(allowedOrigin, [], originHeader) -} - - -deny contains "origin check failed" if { - originHeader := object.get(headers, "origin", "A") - every origin in userData.origins { is_origin_invalid(originHeader, origin) } - - not userData.allowNoOrigin - claims.payload.sub != "c2b" - - object.get(headers, "sec-fetch-site", "avi") != "same-origin" -} - -need_user_agent := true if { - not userData.allowNoBrowser -} - -need_user_agent := true if { - claims.payload.sub == "c2b" -} - - -deny contains "user-agent missing" if { - not headers["user-agent"] - need_user_agent -} - -deny contains "user-agent is not from allowed browsers" if { - not userData.allowNoBrowser - not regex.match(".*(Gecko|AppleWebKit|Opera|Trident|Edge|Chrome)\\/\\d.*$", headers["user-agent"]) -} - -deny contains "c2b user only allowed from QGIS or ARCGIS" if { - userAgent := lower(headers["user-agent"]) - - claims.payload.sub == "c2b" - not contains(userAgent, "qgis") - not contains(userAgent, "arcgis") -} - -decision := {"allowed": true, "sub": claims.payload.sub, "kid": claims.kid} if { - count(deny) == 0 - claims.payload.sub != null -} - -decision := {"allowed": false, "reason": reason} if { - count(deny) > 0 - reason := concat(", ", deny) -} diff --git a/example/nginx/Dockerfile b/example/nginx/Dockerfile deleted file mode 100644 index e06f2daf..00000000 --- a/example/nginx/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM nginx -COPY *.js /etc/nginx/ -COPY nginx.conf /etc/nginx/nginx.conf -COPY default.conf /etc/nginx/conf.d/ diff --git a/example/nginx/auth.js b/example/nginx/auth.js deleted file mode 100644 index e6a93351..00000000 --- a/example/nginx/auth.js +++ /dev/null @@ -1,70 +0,0 @@ -import qs from 'querystring'; - -async function opaAuth(r) { - try { - if (r.variables.original_method == 'OPTIONS') { - return r.return(204); - } - - const body = { - input: { - method: r.variables.original_method, - headers: r.headersIn, - // uri: r.variables, - // host: r.variables, - query: qs.parse(r.variables.original_args), - domain: '', - }, - }; - - const response = await r.subrequest('/opa', { - body: JSON.stringify(body), - method: 'POST', - }); - - if (response.status > 500) { - return r.return(response.status); - } - - const opaResult = JSON.parse(response.responseText).result; - if (!opaResult.allowed) { - r.error(opaResult.reason); - const returnCode = opaResult.reason.includes('no token supplied') ? 401 : 403; - - return r.return(returnCode); - } - - r.return(204); - } catch (error) { - r.error(error); - r.return(500); - } -} - -function jwt(data) { - if (data) { - var parts = data - .split('.') - .slice(0, 2) - .map((v) => Buffer.from(v, 'base64url').toString()) - .map(JSON.parse); - return { headers: parts[0], payload: parts[1] }; - } else { - return; - } -} - -function jwt_payload_sub(r) { - try { - let token; - if (r.args['token']) token = jwt(r.args['token']); - else if (r.headersIn['x-api-key']) token = jwt(r.headersIn['x-api-key']); - else return ''; - - return token.payload.sub; - } catch (error) { - return ''; - } -} - -export default { opaAuth, jwt_payload_sub }; diff --git a/example/nginx/default.conf b/example/nginx/default.conf deleted file mode 100644 index 30ca7d70..00000000 --- a/example/nginx/default.conf +++ /dev/null @@ -1,34 +0,0 @@ -server { - listen 8080; - server_name localhost; - - js_set $jwt_payload_sub auth.jwt_payload_sub; - - location / { - set $original_method $request_method; - set $original_args $args; - - auth_request /_validate_jwt; - - root /usr/share/nginx/html; - index index.html index.htm; - } - - location = /_validate_jwt { - internal; - - js_content auth.opaAuth; - } - - location = /opa { - internal; - - proxy_set_header Content-Type application/json; - proxy_pass http://localhost:8181/v1/data/http/authz/decision; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} \ No newline at end of file diff --git a/example/nginx/nginx.conf b/example/nginx/nginx.conf deleted file mode 100644 index 80213fd5..00000000 --- a/example/nginx/nginx.conf +++ /dev/null @@ -1,34 +0,0 @@ -#user nginx; -load_module modules/ngx_http_js_module.so; -worker_processes 4; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - js_import auth from auth.js; - - log_format main '$jwt_payload_sub $remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" $request_time $upstream_response_time $pipe '; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - include /etc/nginx/conf.d/*.conf; -} \ No newline at end of file diff --git a/example/opa.yaml b/example/opa.yaml deleted file mode 100644 index 9ae796ee..00000000 --- a/example/opa.yaml +++ /dev/null @@ -1,8 +0,0 @@ -services: - bundleStore: - url: http://127.0.0.1:5500 - -bundles: - authz: - service: bundleStore - resource: /bundle.tar.gz diff --git a/policy/.gitignore b/policy/.gitignore new file mode 100644 index 00000000..d2370b4b --- /dev/null +++ b/policy/.gitignore @@ -0,0 +1,2 @@ +regal +opa diff --git a/policy/Makefile b/policy/Makefile new file mode 100644 index 00000000..44d77278 --- /dev/null +++ b/policy/Makefile @@ -0,0 +1,19 @@ +.PHONY: lint test check all + +all: lint check test + +lint: + ./regal lint . + +check: + ./opa check . --strict + +test: + ./opa test . -v + +init: + curl -L -o regal https://github.com/open-policy-agent/regal/releases/download/v0.41.1/regal_Linux_x86_64 + chmod 755 ./regal + curl -L -o opa https://openpolicyagent.org/downloads/v1.17.1/opa_linux_amd64_static + chmod 755 ./opa + diff --git a/example/data/data.json b/policy/data.json similarity index 100% rename from example/data/data.json rename to policy/data.json diff --git a/example/data/logs.rego b/policy/logs.rego similarity index 85% rename from example/data/logs.rego rename to policy/logs.rego index 9752d25e..51b9eaea 100644 --- a/example/data/logs.rego +++ b/policy/logs.rego @@ -1,7 +1,8 @@ package system.log # Mask the 'token' field in the query parameters -mask contains "/input/query/token" +mask contains "/input/query/token" + # Mask the 'x-api-key' field in the headers parameters mask contains "/input/headers/x-api-key" diff --git a/policy/policy.rego b/policy/policy.rego new file mode 100644 index 00000000..29723773 --- /dev/null +++ b/policy/policy.rego @@ -0,0 +1,118 @@ +package http.authz + +lower_object_keys(obj) := {k: v | + some i + v := obj[i] + k := lower(i) +} + +constraints := { + "cert": json.marshal({"keys": [data.keys]}), + "alg": "RS256", +} + +headers := lower_object_keys(input.headers) + +tokens := [token | + # keys := data.possibleLocations[_] + some key in data.possibleLocations + lower_obj := lower_object_keys(object.get(input, key[0], "")) + token := object.get(lower_obj, key[1], null) + token != null +] + +claims := {"payload": payload, "valid": valid, "kid": kid} if { + # token := tokens[_] + some token in tokens + [header, payload, _] := io.jwt.decode(token) + kid := object.get(header, "kid", null) # Extract kid from JWT header + [valid, _, _] := io.jwt.decode_verify(token, constraints) +} + +user_data := {"domains": ["raster"], "allowNoOrigin": true, "allowNoBrowser": true, "origins": []} if { + claims.payload.sub == "c2b" +} + +user_data := data.users[claims.payload.sub] if { + claims.payload.sub != "c2b" +} + +is_origin_invalid(origin_header, allowed_origin) if { + allowed_origin != origin_header + not glob.match(allowed_origin, [], origin_header) +} + +need_user_agent if { + not user_data.allowNoBrowser +} + +need_user_agent if { + claims.payload.sub == "c2b" +} + +deny contains "no token supplied in any of the possible locations" if { + count(tokens) == 0 +} + +deny contains "token environment mismatch" if { + claims.kid != data.keys.kid +} + +deny contains "token not valid" if { + not claims.valid + count(tokens) > 0 +} + +deny contains "the token is valid, but the user is not found" if { + claims.valid + not user_data +} + +deny contains "domain missing" if { + not input.domain +} + +deny contains "domain check failed" if { + every domain in user_data.domains { domain != input.domain } +} + +deny contains "origin check failed" if { + origin_header := object.get(headers, "origin", "A") + every origin in user_data.origins { is_origin_invalid(origin_header, origin) } + + not user_data.allowNoOrigin + claims.payload.sub != "c2b" + + object.get(headers, "sec-fetch-site", "avi") != "same-origin" +} + +deny contains "user-agent missing" if { + not headers["user-agent"] + need_user_agent +} + +deny contains "user-agent is not from allowed browsers" if { + not user_data.allowNoBrowser + not regex.match(`.*(Gecko|AppleWebKit|Opera|Trident|Edge|Chrome)\/\d.*$`, headers["user-agent"]) +} + +deny contains "c2b user only allowed from QGIS or ARCGIS" if { + claims.payload.sub == "c2b" + + user_agent := lower(headers["user-agent"]) + + not contains(user_agent, "qgis") + not contains(user_agent, "arcgis") +} + +# METADATA +# entrypoint: true +decision := {"allowed": true, "sub": claims.payload.sub, "kid": claims.kid} if { + count(deny) == 0 + claims.payload.sub != null +} + +decision := {"allowed": false, "reason": reason} if { + count(deny) > 0 + reason := concat(", ", deny) +} diff --git a/example/data/policy_test.rego b/policy/policy_test.rego similarity index 68% rename from example/data/policy_test.rego rename to policy/policy_test.rego index 4c2f8277..98404278 100644 --- a/example/data/policy_test.rego +++ b/policy/policy_test.rego @@ -1,4 +1,6 @@ -package http.authz +package http.authz_test + +import data.http.authz private_key_1 := { "kty": "RSA", @@ -43,35 +45,35 @@ postman_agent := "PostmanRuntime/7.32.2" qgis_agent := "Mozilla/5.0 QGIS/34400/Ubuntu 22.04.5 LTS" users := { - "avi": { + "avi": { "allowNoBrowser": false, "allowNoOrigin": false, "domains": ["avi"], "origins": ["https://avi.com"], - }, - "itzik": { - "allowNoBrowser": false, - "allowNoOrigin": false, - "domains": ["avi"], - "origins": ["*.itzik.maps.com"], - }, - "zvika": { - "allowNoBrowser": false, - "allowNoOrigin": false, - "domains": ["avi"], - "origins": ["https://google.com", "*.zvika.maps.com"], - } + }, + "itzik": { + "allowNoBrowser": false, + "allowNoOrigin": false, + "domains": ["avi"], + "origins": ["*.itzik.maps.com"], + }, + "zvika": { + "allowNoBrowser": false, + "allowNoOrigin": false, + "domains": ["avi"], + "origins": ["https://google.com", "*.zvika.maps.com"], + }, } generate_token(key, payload) := token if { - newPayload := json.patch(payload, [{"op": "add", "path": "iss", "value": "mapcolonies-token-cli"}]) + new_payload := json.patch(payload, [{"op": "add", "path": "iss", "value": "mapcolonies-token-cli"}]) token := io.jwt.encode_sign( { "typ": "JWT", "alg": "RS256", - "kid": key.kid, + "kid": key.kid, }, - newPayload, + new_payload, key, ) } @@ -81,33 +83,33 @@ generate_token(key, payload) := token if { # ========================================================= test_deny_no_token if { - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - res.reason == "no token supplied in any of the possible locations" + res.reason == "no token supplied in any of the possible locations" } test_deny_malformed_token if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": substring(token, 0, 40), "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": substring(token, 0, 40), "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - - res.reason == "token not valid" + + res.reason == "token not valid" } test_deny_token_signed_with_wrong_key if { token := generate_token(private_key_2, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - contains(res.reason, "token environment mismatch") + contains(res.reason, "token environment mismatch") } # ========================================================= @@ -116,18 +118,18 @@ test_deny_token_signed_with_wrong_key if { test_allow_token_supplied_in_header if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 - with data.users as users + with data.users as users res.allowed } test_allow_token_supplied_in_query_param if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}, "query": {"token": token}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}, "query": {"token": token}} with data.keys as public_key_1 - with data.users as users + with data.users as users res.allowed } @@ -138,12 +140,12 @@ test_allow_token_supplied_in_query_param if { test_deny_user_not_found if { token := generate_token(private_key_1, {"sub": "nx_user"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - res.reason == "the token is valid, but the user is not found" + res.reason == "the token is valid, but the user is not found" } # ========================================================= @@ -152,22 +154,22 @@ test_deny_user_not_found if { test_deny_domain_missing_from_request if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - contains(res.reason, "domain missing") + contains(res.reason, "domain missing") } test_deny_domain_not_allowed_for_user if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"itzik", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "itzik", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - contains(res.reason, "domain check failed") + contains(res.reason, "domain check failed") } # ========================================================= @@ -176,7 +178,7 @@ test_deny_domain_not_allowed_for_user if { test_deny_wrong_origin if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi2.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi2.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -185,7 +187,7 @@ test_deny_wrong_origin if { test_deny_missing_origin if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -195,7 +197,7 @@ test_deny_missing_origin if { # sec-fetch-site: same-origin bypasses origin header check (browser same-origin requests don't always send Origin) test_allow_same_origin_fetch_site_bypasses_origin_check if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Sec-Fetch-Site": "same-origin"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Sec-Fetch-Site": "same-origin"}} with data.keys as public_key_1 with data.users as users @@ -204,17 +206,17 @@ test_allow_same_origin_fetch_site_bypasses_origin_check if { test_allow_no_origin_when_user_permits_it if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 - with data.users as users - with data.users.avi.allowNoOrigin as true + with data.users as users + with data.users.avi.allowNoOrigin as true res.allowed } test_allow_wildcard_origin_matching_subdomain if { token := generate_token(private_key_1, {"sub": "itzik"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.itzik.maps.com"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.itzik.maps.com"}} with data.keys as public_key_1 with data.users as users @@ -223,7 +225,7 @@ test_allow_wildcard_origin_matching_subdomain if { test_deny_wildcard_origin_not_matching_subdomain if { token := generate_token(private_key_1, {"sub": "itzik"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.avi.maps.com"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.avi.maps.com"}} with data.keys as public_key_1 with data.users as users @@ -232,7 +234,7 @@ test_deny_wildcard_origin_not_matching_subdomain if { test_allow_specific_origin_for_user_with_multiple_origins if { token := generate_token(private_key_1, {"sub": "zvika"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://google.com"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://google.com"}} with data.keys as public_key_1 with data.users as users @@ -241,7 +243,7 @@ test_allow_specific_origin_for_user_with_multiple_origins if { test_deny_unrecognised_origin_for_user_with_multiple_origins if { token := generate_token(private_key_1, {"sub": "zvika"}) - res := decision with input as {"domain":"avi", "headers": { "User-Agent": chrome_agent, "Origin": "https://ggl.com"}, "query": {"token": token}} + res := authz.decision with input as {"domain": "avi", "headers": {"User-Agent": chrome_agent, "Origin": "https://ggl.com"}, "query": {"token": token}} with data.keys as public_key_1 with data.users as users @@ -250,7 +252,7 @@ test_deny_unrecognised_origin_for_user_with_multiple_origins if { test_deny_origin_matching_wrong_glob_pattern if { token := generate_token(private_key_1, {"sub": "zvika"}) - res := decision with input as {"domain":"avi", "headers": { "User-Agent": chrome_agent, "Origin": "https://avi.bla.zvika.maps.com"}, "query": {"token": token}} + res := authz.decision with input as {"domain": "avi", "headers": {"User-Agent": chrome_agent, "Origin": "https://avi.bla.zvika.maps.com"}, "query": {"token": token}} with data.keys as public_key_1 with data.users as users @@ -263,7 +265,7 @@ test_deny_origin_matching_wrong_glob_pattern if { test_deny_non_browser_user_agent if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": postman_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": postman_agent}} with data.keys as public_key_1 with data.users as users @@ -273,7 +275,7 @@ test_deny_non_browser_user_agent if { test_deny_missing_user_agent if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token}} with data.keys as public_key_1 with data.users as users @@ -283,22 +285,22 @@ test_deny_missing_user_agent if { test_allow_non_browser_user_agent_when_user_permits_it if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": postman_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": postman_agent}} with data.keys as public_key_1 with data.users as users - with data.users.avi.allowNoBrowser as true - with data.users.avi.allowNoOrigin as true + with data.users.avi.allowNoBrowser as true + with data.users.avi.allowNoOrigin as true res.allowed } # ========================================================= -# Decision response shape tests +# authz.decision response shape tests # ========================================================= test_allowed_decision_contains_sub if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -308,7 +310,7 @@ test_allowed_decision_contains_sub if { test_allowed_decision_contains_kid if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -322,7 +324,7 @@ test_allowed_decision_contains_kid if { test_allow_c2b_connection_from_qgis if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} with data.keys as public_key_1 with data.users as users @@ -331,7 +333,7 @@ test_allow_c2b_connection_from_qgis if { test_allow_c2b_connection_from_arcgis if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": "ArcGIS Map Viewer/10.9.1"}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": "ArcGIS Map Viewer/10.9.1"}} with data.keys as public_key_1 with data.users as users @@ -340,7 +342,7 @@ test_allow_c2b_connection_from_arcgis if { test_deny_c2b_connection_from_browser if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -349,7 +351,7 @@ test_deny_c2b_connection_from_browser if { test_deny_c2b_connection_no_user_agent if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token}} with data.keys as public_key_1 with data.users as users @@ -359,7 +361,7 @@ test_deny_c2b_connection_no_user_agent if { test_deny_c2b_connection_wrong_domain if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} with data.keys as public_key_1 with data.users as users @@ -368,8 +370,8 @@ test_deny_c2b_connection_wrong_domain if { } test_deny_c2b_connection_if_token_expired if { - token := generate_token(private_key_1, {"sub": "c2b", "exp": time.now_ns() / 1000000000 - 60}) # 1 minute ago - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} + token := generate_token(private_key_1, {"sub": "c2b", "exp": (time.now_ns() / 1000000000) - 60}) # 1 minute ago + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} with data.keys as public_key_1 with data.users as users diff --git a/example/data/possibleLocations.template b/policy/possibleLocations.template similarity index 100% rename from example/data/possibleLocations.template rename to policy/possibleLocations.template diff --git a/example/data/users.template b/policy/users.template similarity index 100% rename from example/data/users.template rename to policy/users.template