# Revised Spec
## Request Format
The request follows the same pattern as an AuthZEN evaluation request.
The main difference is that one of
- subject
- action
- resource
may be omitted. The type attribute must always be specified.
This leads to the possible partial evaluation requests:
- subject: who can view document #123?
- action: what action can Alice do on document #123?
- resource: which object can Alice edit?
### Subject Partial Evaluation
#### Definition
The Subject Partial Evaluation request is a 4-tuple constructed of three previously defined entities:
- subject:
REQUIRED. The subject (or principal) of type Subject. NOTE that the Subject type is REQUIRED but the Subject ID can be omitted, and if present, is IGNORED.
- action:
REQUIRED. The action (or verb) of type Action.
- resource:
REQUIRED. The resource of type Resource.
- context:
OPTIONAL. Contextual data about the request.
#### Example
This example is based on 6.1.1 Example in the Access Evaluation API Request section of the AutZEN evaluation API spec
The equivalent Partial Evaluation requests are:
- Who can view account 123?
```
{
"subject": {
"type": "user"
},
"resource": {
"type": "account",
"id" : 123
},
"action": {
"name": "view"
},
"context": {
"time": "1985-10-26T01:22-07:00"
}
}
```
### Resource Partial Evaluation
#### Definition
The Resource Partial Evaluation request is a 4-tuple constructed of three previously defined entities:
- subject:
REQUIRED. the subject of type Subject.
- action:
REQUIRED. The action (or verb) of type Action.
- resource:
REQUIRED.
The resource of type Subject. NOTE that the Resource type is REQUIRED but the Resource ID MAY be omitted.
- context:
OPTIONAL. Contextual data about the request.
#### Example
This example is based on 6.1.1 Example in the Access Evaluation API Request section of the AutZEN evaluation API spec
The equivalent Partial Evaluation requests are:
- Which accounts can Alice view?
```
{
"subject": {
"type": "user",
"id": "Alice"
},
"resource": {
"type": "account"
},
"action": {
"name": "view"
},
"context": {
"time": "1985-10-26T01:22-07:00"
}
}
```
### Action Partial Evaluation
#### Definition
The Resource Partial Evaluation request is a 3-tuple constructed of three previously defined entities and omitting the action object:
- subject:
REQUIRED. the subject of type Subject.
- resource:
REQUIRED. The resource of type Resource.
- context:
OPTIONAL. Contextual data about the request.
#### Example
This example is based on 6.1.1 Example in the Access Evaluation API Request section of the AutZEN evaluation API spec
The equivalent Partial Evaluation requests are:
- Which accounts can Alice view?
```
{
"subject": {
"type": "user",
"id": "Alice"
},
"resource": {
"type": "account",
"id" : 123
},
"context": {
"time": "1985-10-26T01:22-07:00"
}
}
```
## Response Format
The Partial Evaluation response consists of single function with support for function composition. AuthZEN provides a limited specification for a limited set of functions. Policy Enforcement Points MAY return a "400 Bad Request" error in case of ambiguities. Implementations MAY define custom functions with different specifications. Custom functions MUST be defined in a namespace. Supported namespaces MAY be included in the discovery API.
We propose a version of the[ response proposal by Vladi](https://hackmd.io/@oidf-wg-authzen/HkLiZVdb1l) with uniform serialization for both functions and logical composition (and/or) operaters.
The response format only supports a specific set of functions defined below. The Working Group may over time add additional functions assuming there is enough correlation/support between the authorization constructs (policies...) and the target systems. Implementations MAY also define custom operators (functions).
The Partial Evaluation format does not provide translations of the response into specific query languages e.g. SQL. Profiles that map functions from the partial evaluation response into the relevant functions of the target language may be written at a later time.
## Default supported Functions
- `and`: evaluates to true if, and only if, all of the provided functions evaluate to true.
- `or`: evaluates to false if, and only if, all of the provided functions evaluate to false.
- `not`: evaluates to true if, and only if, all of the provided functions evaluate to false.
- `field`: evaluates to the value of the field identified by the given name.
- `value`: evaluates to the provided argument
- `eq`: evaluates to true if, and only if, the first argument is equal to the second argument. This function must have exactly 2 arguments.
- `ne`: evaluates to true if, and only if, the first argument is not equal to the second argument. This function must have exactly 2 arguments.
- `gt`: evaluates to true if, and only if, the first argument is greater than the second argument. This function must have exactly 2 arguments.
- `gte`: evaluates to true if, and only if, the first argument is greater than or equal to the second argument. This function must have exactly 2 arguments.
- `lt`: evaluates to true if, and only if, the first argument is less than the second argument. This function must have exactly 2 arguments.
- `lte`: evaluates to true if, and only if, the first argument is less than or equal to the second argument. This function must have exactly 2 arguments.
- `contains`: evaluates to true if, and only if, the first argument contains the string of the second argument (e.g. `contains['alice', 'li']` returns true.)
- `startswith`: evaluates to true if, and only if, the first argument starts with the string of the second argument (e.g. `startswith['alice', 'al']` returns true.)
- `endswith`: evaluates to true if, and only if, the first argument ends with the string of the second argument (e.g. `endswith['alice', 'ce']` returns true.)
- `in`: evaluates to true if, and only if, the first argument is equal to any of the values in the collection of values in the second argument.
If the second argument does not contain a collection the given second argument is considered as a set with one element.
- `nin`: evaluates to true if, and only if, the first argument is not equal to any of the values in the collection of values in the second argument.
If the second argument does not contain a collection the given second argument is considered as a set with one element.
## Response Structure
Below we propose two serialization formats. In both proposals, we use the following end result as an example
```
lower(location)=='AZ' and balance>(500+limit) and (isAuthorized || userRole=='superadmin')
```
### Object-based proposal
In this proposal, everything is expressed as nested functions. Functions have arguments. Any argument of a function can be a function itself thus allowing for nesting.
- function element that contains
- name (e.g. `eq`)
- an (ordered) array of arguments
- argument:
- function object e.g. `lower`
- constant field e.g. `Arizona`
- attribute object e.g. `user.role`
- constant
- it is directly a value
- attribute
The top-level function MUST return a boolean.
#### Example filter in object based format
``` json
{
"type": "compound",
"name": "and",
"arguments": [
{
"type": "function",
"name": "eq",
"arguments": [
{
"type": "function",
"namespace": "com.example",
"name": "lower",
"arguments": [
{
"type": "attribute",
"name": "location"
}
]
},
{
"type": "constant",
"value": "AZ"
}
]
},
{
"type": "function",
"name": "gt",
"arguments": [
{
"type": "attribute",
"name": "balance"
},
{
"type": "function",
"namespace": "com.example",
"name": "sum",
"arguments": [
{
"type": "attribute",
"name": "limit"
},
{
"type": "constant",
"value": 500
}
]
}
]
},
{
"type": "compound",
"name": "or",
"arguments": [
{
"type": "attribute",
"name": "isAuthorized"
},
{
"type": "function",
"name": "eq",
"arguments": [
{
"type": "attribute",
"name": "userRole"
},
{
"type": "constant",
"value": "superuser"
}
]
}
]
}
]
}
```
### Key-based serialization proposal
A function consists of a JSON object with a single key-value pair. The key is the name of the function. The value is the argument to the function. The function argument can be any JSON element (string, number, boolean, array, object, etc.). e.g.
``` json
{
"i18nLowercase": {
"value": { "const": "Über" },
"collation": "UTF8_GENERAL_CI"
}
}
```
``` json
{ "lower": { "const": "ABC" } }
```
``` json
{ "absolute": { "const": -12 } }
```
``` json
{ "min": [{ "const": 7 }, { "const": 18 }, { "const": 42 }] }
```
Attributes/fields are defined using the function named 'field'.
Constants/values are defined using the function 'const'.
An actual JSON object with only a single key value pair should be escaped by the 'const' function like such:
``` json
{
"eq": [
{
"field": "settings"
},
{
"const": {
"logLevel": "error"
}
}
]
}
```
Example of sample filter:
``` json
{ "and": [
{ "eq": [
{ "com.example.lower": {"field": "location"} },
{ "const": "AZ" } ] },
{ "gt": [
{ "field": "balance" },
{ "com.example.sum": [
{ "field": "limit" },
{ "const": 500 } ] } ] },
{ "or": [
{ "field": "isAuthorized" },
{ "eq": [
{ "field": "userRole" },
{ "const": "superuser" } ] } ] } ] }
```
The below JSON Schema can be used to validate all built-in functions (and allow custom functions without validation):
```
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"oneOf": [
{
"description": "Implementation specific function",
"additionalProperties": false,
"minProperties": 1,
"maxProperties": 1,
"patternProperties": {
"\\.": {}
}
},
{
"required": [
"and"
],
"additionalProperties": false,
"properties": {
"and": {
"type": "array",
"items": {
"$ref": "#"
}
}
}
},
{
"required": [
"or"
],
"additionalProperties": false,
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#"
}
}
}
},
{
"required": [
"not"
],
"additionalProperties": false,
"properties": {
"not": {
"$ref": "#"
}
}
},
{
"required": [
"field"
],
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
}
}
},
{
"required": [
"const"
],
"additionalProperties": false,
"properties": {
"const": {}
}
},
{
"required": [
"eq"
],
"additionalProperties": false,
"properties": {
"eq": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"ne"
],
"additionalProperties": false,
"properties": {
"ne": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"gt"
],
"additionalProperties": false,
"properties": {
"gt": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"gte"
],
"additionalProperties": false,
"properties": {
"gte": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"lt"
],
"additionalProperties": false,
"properties": {
"lt": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"lte"
],
"additionalProperties": false,
"properties": {
"lte": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"contains"
],
"additionalProperties": false,
"properties": {
"contains": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"startswith"
],
"additionalProperties": false,
"properties": {
"startswith": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"endswith"
],
"additionalProperties": false,
"properties": {
"endswith": {
"type": "array",
"items": {
"$ref": "#"
},
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"in"
],
"additionalProperties": false,
"properties": {
"in": {
"type": "array",
"prefixItems": [
{
"$ref": "#"
},
{
"oneOf": [
{
"$ref": "#"
},
{
"type": "array",
"items": {
"$ref": "#"
}
}
]
}
],
"minItems": 2,
"maxItems": 2
}
}
},
{
"required": [
"nin"
],
"additionalProperties": false,
"properties": {
"nin": {
"type": "array",
"prefixItems": [
{
"$ref": "#"
},
{
"oneOf": [
{
"$ref": "#"
},
{
"type": "array",
"items": {
"$ref": "#"
}
}
]
}
],
"minItems": 2,
"maxItems": 2
}
}
}
]
}
```
## Extensibility & Namespaces
Implementations can add their own custom functions. Implementation specific functions MUST use a namespace to avoid collision with the default functions. The part of the function name before the last dot (`.`) is considered the namespace.
Example:
- `acme.equals`
- `com.example.v2.str.lower`
Function names without any dots are thus reserved for use by functions specified in the AuthZEN specification.
Functions should be considered to evaluate lazily. This means that an unsupported function that is not required to be executed, such as a clause in the `or` statement or in future conditional functions like `if`, SHOULD be ignored.
This allows a PDP to return filters for multiple PEP's. For example, in case a PEP only supports the `com.example.v2` namespace the following example should still return true :
``` json
{
"or": [
{ "com.example.v1.filter": "A == 1"},
{ "com.example.v2.filter": "A eq 1"},
{ "sql": "A = 1"}
]
}
```
## Translation into filter expressions
- MongoDB
- GraphQL
- SQL
## Question for the WG
Do we want to further generalize the request to allow questions such as:
- what actions are possible on medical records?
- This would require opening up 2 categories (subject and action)
- This would require partially opening up the resource category (we know the object type `medical record` but not the identifier)
- What can happen?
- This is the most open-ended question where nothing is known
- This type of request is mainly useful for access reviews and policy testing.
### Reference Material
- [Cerbos Partial Evaluation Format](https://docs.cerbos.dev/cerbos/latest/api/#resources-query-plan)
- [Axiomatics Partial Evaluation Format & AST](https://docs.axiomatics.com/contextual-authorization-query/contextual-query-api/contextual-evaluation-endpoints/)
- [PlainID](https://docs.plainid.io/apidocs/policy-resolution)
- [Styra Operation List](https://docs.styra.com/apps/data/reference/ucast-syntax#field-operations)