Aidbox
Search…
Access Policies
​Aidbox provides a flexible model to customise request authorization rules. User is allowed to declare a set of checks for all incoming requests. If the incoming request satisfies those checks, it's considered authorised and being processed further. Otherwise the request is denied and the client gets 403 Unauthorized. Such checks are declared with AccessPolicy resource.
AccessPolicy resource has the following structure:
resourceType: AccessPolicy
description: policy description text
​
# type of evaluation engine
engine: allow | sql | json-schema | complex
​
# JSON Schema for `json-schema` engine
schema: {}
​
# SQL string for `sql` engine
sql:
query: "SELECT true FROM ..."
​
# References to either Client, User or Operation resources
link:
- { resourceType: Operation, id: op-id }
- { resourceType: User, id: user-1 }
- { resourceType: Client, id: client-1 }
It supports five evaluation modes (engines): SQL, JSON Schema, Allow, Matcho and Complex Engine. Evaluation engine specifies how checks are expressed: with a SQL statement, with a JSON Schema, or as a list of allowed endpoints.

Request Object Structure

Aidbox evaluates AccessPolicy against a request object, a data structure representing incoming HTTP request. It has the following structure:
# Request body (for PUT or POST)
body: null
​
# Parsed query-string params merged with URL params extracted by routing engine
params: {__debug: policy, type: Patient}
​
# Request method (get/post/put/delete)
request-method: get
​
# Request URI (no query string)
uri: /fhir/Patient
​
# Object containing current User resource (if any)
user:
data:
patient_id: 42
id: b66bc7c5-1a56-422f-8cf8-e64469135ce2
meta:
lastUpdated: '2018-10-31T13:43:18.566Z'
tag:
- code: updated
system: https://aidbox.io
versionId: '6'
phone: 123-123-123
resourceType: User
​
# Parsed JWT claims (if any)
jwt: {sub: xxxxxxx, jti: xxxxxxx, iss: aidbox,
iat: 15409xxxxx, exp: 15409xxxxx}
​
# Query string
query-string: __debug=policy
​
# Client's IP address
remote-addr: 10.128.0.6
​
# Client resource (if any)
client:
id: b4930671-410c-462b-8b12-23cdef91af0c
meta:
lastUpdated: '2018-10-31T13:38:08.982Z'
tag:
- code: created
system: https://aidbox.io
versionId: '4'
resourceType: Client
​
# Request scheme (http or https)
scheme: http
​
# Request headers
headers: {x-original-uri: '/fhir/Patient?__debug=policy', origin: 'https://ui.aidbox.app',
x-forwarded-host: xxxx.aidbox.app, host: xxxx.aidbox.app, user-agent: 'Mozilla/5.0
(Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100
Safari/537.36', content-type: 'text/yaml, application/json', x-forwarded-port: '443',
referer: 'https://ui.aidbox.app/', connection: close, accept: text/yaml, accept-language: 'en-US,en;q=0.9,ru;q=0.8',
authorization: Bearer xxxxxx,
x-forwarded-for: 10.128.0.6, accept-encoding: 'gzip, deflate, br', x-forwarded-proto: https,
x-scheme: https, x-real-ip: 10.128.0.6}

Request Authorization Logic

AccessPolicy instance can be linked to User, Client or Operation resources with AccessPolicy.link resource. If AccessPolicy has no links, it's considered as global policy. To authorize a request, Aidbox uses AccessPolicies linked to the current request's User, Client and Operation plus all global policies.
It iterates through them, evaluating each AccessPolicy against current request object. If some policy validates the request (evaluation result is true), the request considered authorized and Aidbox stops further policies evaluation. If all policies denied the request (all of them evaluated to false), then Aidbox denies such request and responds with 403 Unauthorized.
Performance consideration: Link you policy to User, Client or Operation to reduce number of checks per request!
If no AccessPolicy instances exist for a box, all requests will be denied.

Debugging

JSON Schema Engine

JSON Schema engine allows to put JSON Schema under the AccessPolicy.schema element and this schema will be used to validate the request object. Currently supported JSON Schema version is draft-07.
Fields with empty values, such as [], {}, "", null, are removed before passing request into access policy processing. Make sure to add require check of the fields that are validated by a json schema

Example

The following policy requires request.params.resource/type to be present in a request and have the value "Organization":
resourceType: AccessPolicy
engine: json-schema
schema:
properties:
params:
required: [resource/type]
properties:
resource/type:
const:
Organization

SQL Engine

SQL Engine executes SQL statement and uses its result as an evaluation result. The SQL statement should return a single row with just one column, i.e.:
SELECT true FROM patient WHERE id = {{jwt.patient_id}} LIMIT 1;
SQL statement can include interpolations in double curly braces, like in the example above. String inside curly braces will be used as a path to get value from the request object.

Example

Assuming that User has a reference to Practitioner resource through User.data.practitioner_id element, the following policy allows requests only to /fhir/Patient/<patient_id> URLs, and only for those patients who have the Patient.generalPractitioner element referencing same practitioner as the current User. In other words, User as a Practitioner is only allowed to see patients who are referencing him with the Patient.generalPractitioner.
sql:
query: |
SELECT
{{user}} IS NOT NULL
AND {{user.data.practitioner_id}} IS NOT NULL
AND {{uri}} LIKE '/fhir/Patient/%'
AND resource->'generalPractitioner' @>
jsonb_build_array(jsonb_build_object('resourceType',
'Practitioner', 'id', {{user.data.practitioner_id}}::text))
FROM patient WHERE id = {{params.resource/id}};
engine: sql
id: practitioner-only-allowed-to-see-his-patients
resourceType: AccessPolicy

Interpolation Rules

In your SQL query, you can parameterize with attributes from the request object using {{path}} syntax. For example, to get a role from user {data: {role: 'admin'}} you can write {{user.data.role}}. Parameter expressions are escaped by default to protect from SQL injection. If you want to make dynamic queries (parameterize table name, for example), you have to use {{!path}} syntax. For example, the expression SELECT true from {{!params.resource/type}} limit 1 with params = {resource/type: "Patient"} will be transformed into SELECT true from "patient". Such identifier names are double quoted and lower-cased by default.

Allow Engine

Allow Engine constantly evaluates to true regardless the content of the request object.

Complex Engine

Complex engine provides ability to include several checks into a single policy and apply "AND" / "OR" operator on results. It's allowed to use any policy engine to define a check, you can even use "complex" engine to get sub-expression:
and:
- { engine: "sql", sql: { query: "select true" } } # Check 1
- engine: complex
or:
- { engine: "sql", sql: { query: "select false" } } # Check 2
- { engine: "sql", sql: { query: "select false" } } # Check 3
​
engine: complex
id: complex-example-1
resourceType: AccessPolicy
Policy in the example above represents the following logical expression: check 1 AND (check 2 OR check 3). It's forbidden to have both and and or keys on the same level.

Example

Let's split SQL Policy example into two separate checks and combine them with the AND operator:
and:
# Check: request.user && request.user.data.practitioner_id are required
- engine: json-schema
schema:
type: object
required: ["user"]
properties:
user:
type: object
required: ["data"]
properties:
data:
type: object
required: ["practitioner_id"]
​
# Check: Current practitioner is patient's generalPractitioner
- engine: sql
sql:
query: |
SELECT
{{uri}} LIKE '/fhir/Patient/%'
AND resource->'generalPractitioner' @>
jsonb_build_array(jsonb_build_object('resourceType',
'Practitioner', 'id', {{user.data.practitioner_id}}::text))
FROM patient WHERE id = {{params.resource/id}};
engine: complex
id: complex-example-2
resourceType: AccessPolicy

Matcho Engine

This custom DSL engine has limited expressiveness, but is very compact and declarative. The general idea is pattern matching with few extensions.
resourceType: AccessPolicy
engine: matcho
matcho:
user:
# user.role should be equal to admin
role: admin
# user.data.practitioner_id should be present
data: {practitioner_id: present?}
# uri match regexp /Encounter/.*
uri: '#/Encounter.*'
# request method should be one of get or post
request-method: {$enum: ['get', 'post']}
params:
# parameter practitioner should be equal to user.data.practitioner_id
practitioner: .user.data.practitioner_id
Match DSL definition:
  • If pattern (match) is object, search for inclusion of this object into subject. For example: {x: 1} matches {x: 1, y: 2 ....}. This algorithm is recursive β€” {a: {b: 5}} matches {a: {b: 5, ...}...}
  • Objects with special keys:
    • $enum β€” test subject is equal to one of items in the enumeration. {request-method: {$enum: ['get','post']}} matches {request-method: 'post'}
    • $contains β€” **** if a subject is a collection, then search at least one match. {type: {$contains: {system: 'loinc'}} matches {type: [{system: 'snomed'}, {system: 'loinc'}]}
    • $one-of β€” try to match one of patterns. {a: {$one-of: [{b: present?}, {c: present?}]} matches {a: {c: 5}}
    • $reference - parse Reference or string into aidbox format. Examples:
      • Parse Reference elements
        • parser: {reference: "Patient/pid"} => {id: "pid", resourceType: "Patient"}
        • {resource: {patient: {$reference: {id: '.user.data.patient_id'}}}
      • Parse reference string
        • "Patient/pid" => {id: "pid", resourceType: "Patient"}
        • {params: {subject: {$reference: {id: '.user.data.patient_id'}}}
    • $not - negate nested pattern. Example: $not: {status: private} matches {status: public} and doesn't match {status: private}. Be careful using $not as it is possible to create too permissive policy. See more info below
    • $every - for collection subject check that each item satisfies the specified pattern. Example: $every: {foo: bar} matches [{foo: bar}, {foo: bar, baz: quux}] and doesn't match [{foo: bar}, {foo: baz}]
  • For array, match the first item in the pattern with the first item in the subject. [1,2] matches [1,2,3...]
  • Primitive values (strings, numbers and booleans) are compared by value
  • If a string starts with '#' , it will be transformed into regex and matched as regex. {a: '#\\d+'} matches {a: '2345'}
  • If a string starts with '.' , it's interpreted as a pointer to another path in the subject to compare. For example: {params: {user_id: '.user.id'}} matches {user: {id: 1}, params: {user_id: 1}}, i.e. user.id == param.user_id
  • There are several special string literals postfixed with the ?
    • present? β€” matches the subject if it is not null, i.e. {a: 'present?'} matches {a: 5} or {a: {b: 6}}
    • nil? β€” matches if nil/null β€” {a: nil?} matches {b: 6}
    • not-blank? β€” matches not blank string.
Be careful using $not as it is possible to create too permissive policy, i.e. statement that something is not allowed means that everything else is allowed. Example:
resourceType: AccessPolicy
engine: matcho
matcho:
request-method: delete
uri: '#^/Patient.*#x27;
user: {$not: {data: {role: guest}}}
While original intent was to forbid guest users to delete Patient resources this AccessPolicy allows to do DELETE /Patient/<id> for unauthorized users.
In this case it is better to explicitly list possible roles with $enum
Need more rules? Contact us on the telegram chat!
Here are some examples:
# only users with role admin
​
user: {role: {$contains: 'admin'}}
# only authorized users get patients or encountres
​
user: present?
request-method: get
params:
'resource/type': {$enum: ['Patient', 'Encounter'}
# search encounter with required parameter practitioner,
# wich should be equal to user.data.pract_id
​
user: {data: {pract_id: present?}}
uri: '/Encounter'
params:
practitioner: '.user.data.pract_id'

Allow-RPC engine

Allows access to every RPC endpoint listed in the rpc field of the AccessPolicy resource with engine allow-rpc.
Example:
description: allow to list notebooks
rpc:
aidbox.notebooks.list-notebooks: true
type: rpc
resourceType: AccessPolicy
id: allow-list-notebooks
engine: allow-rpc

Matcho-RPC engine

Allows access to the method if the request passes matcho check (see Matcho engine section).
Example:
description: allow everyone to read "hello" notebook
rpc:
aidbox.notebooks/get-notebook-by-id:
params:
notebook:
id: hello
type: rpc
resourceType: AccessPolicy
id: allow-hello-notebook
engine: matcho-rpc
Copy link
Edit on GitHub
On this page
Request Object Structure
Request Authorization Logic
Debugging
JSON Schema Engine
SQL Engine
Allow Engine
Complex Engine
Matcho Engine
Allow-RPC engine
Matcho-RPC engine