Skip to content

Commit 4341e1e

Browse files
jon-whitmiparnisarirhamzeh
authored
docs(abac): add initial ABAC Condition documentation (#583)
Co-authored-by: Maria Ines Parnisari <[email protected]> Co-authored-by: Raghd Hamzeh <[email protected]>
1 parent 35a3935 commit 4341e1e

File tree

9 files changed

+460
-96
lines changed

9 files changed

+460
-96
lines changed

docs/content/concepts.mdx

+46-1
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,27 @@ This will affect only relations that are [directly related](#what-are-direct-and
350350

351351
</details>
352352

353+
<details>
354+
<summary>
355+
## What is a Condition?
356+
357+
A **Condition** is a function composed of one or more parameters and an expression which must evaluate to a boolean outcome. Expressions are defined using [Google's Common Expression Language (CEL)](https://github.com/google/cel-spec).
358+
</summary>
359+
360+
For example, in the following snippet `less_than_hundred` defines a Condition which evaluates to a boolean outcome. The provided parameter `x`, defined as an integer type, is used in the boolean expression `x < 100`. The condition will return a truthy outcome if the expression returns a truthy outcome and false otherwise.
361+
```
362+
condition less_than_hundred(x: int) {
363+
x < 100
364+
}
365+
```
366+
</details>
367+
353368
<details>
354369
<summary>
355370

356371
## What Is A Relationship Tuple?
357372

358-
A **relationship tuple** is a tuple consisting of a user, relation and object stored in <ProductName format={ProductNameFormat.ShortForm}/>.
373+
A **relationship tuple** is composed of a base tuple/triplet consisting of a user, relation and object and an optional condition which applies to it (see [Conditional Relationship Tuples](#what-is-a-conditional-relationship-tuple) for more info). Relationship tuples are written and stored in <ProductName format={ProductNameFormat.ShortForm}/>.
359374

360375
</summary>
361376

@@ -364,6 +379,7 @@ A **relationship tuple** consists of a:
364379
- **[user](#what-is-a-user)**, e.g. `user:anne`, `user:3f7768e0-4fa7-4e93-8417-4da68ce1846c`, `workspace:auth0` or `folder:planning#editor`
365380
- **[relation](#what-is-a-relation)**, e.g. `editor`, `member` or `parent_workspace`
366381
- **[object](#what-is-an-object)**, e.g `repo:auth0/express_jwt`, `domain:auth0.com` or `channel:marketing`
382+
- **[condition](#what-is-a-condition)** (optional), e.g. `{"condition": "in_allowed_ip_range", "context": {...}}`
367383

368384
An [authorization model](#what-is-an-authorization-model), together with _relationship tuples_, allow the determination of whether a [relationship](#what-is-a-relationship) exists between a [user](#what-is-a-user) and an [object](#what-is-an-object).
369385

@@ -383,6 +399,35 @@ For more information, please see [Direct Access](./modeling/direct-access.mdx).
383399

384400
</details>
385401

402+
<details>
403+
<summary>
404+
## What Is A Conditional Relationship Tuple?
405+
406+
A **conditional relationship tuple** is a [relationship tuple](#what-is-a-relationship-tuple) which represents a [relationship](#what-is-a-relationship) conditioned upon the evaluation of some [Condition](#what-is-a-condition).
407+
</summary>
408+
409+
If a relationship tuple is conditioned, then the condition which the tuple is conditioned upon must evaluate to a truthy outcome for the relationship tuple to be considered a permissible relationship.
410+
411+
The following relationship tuple is a conditional relationship tuple because it is conditioned on `less_than_hundred`. If the expression for `less_than_hundred` is defined as `x < 100`, then the relationship is considered permissible because the expression evaluates to a truthy outcome since `20 < 100`.
412+
413+
<RelationshipTuplesViewer
414+
relationshipTuples={[
415+
{
416+
user: 'user:anne',
417+
relation: 'editor',
418+
object: 'document:new-roadmap',
419+
condition: {
420+
"name": "less_than_hundred",
421+
"context": {
422+
"x": 20
423+
}
424+
}
425+
},
426+
]}
427+
/>
428+
429+
</details>
430+
386431
<details>
387432
<summary>
388433

docs/content/modeling/conditions.mdx

+244
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
sidebar_position: 8
3+
slug: /modeling/conditions
4+
description: Modeling relationships with Conditions
5+
---
6+
7+
import {
8+
AuthzModelSnippetViewer,
9+
WriteRequestViewer,
10+
CheckRequestViewer,
11+
ListObjectsRequestViewer,
12+
languageLabelMap,
13+
SdkSetupPrerequisite,
14+
SupportedLanguage,
15+
WriteAuthzModelViewer,
16+
} from '@components/Docs';
17+
18+
import Tabs from '@theme/Tabs';
19+
import TabItem from '@theme/TabItem';
20+
21+
# Conditions
22+
23+
## Overview
24+
Conditions allow you to model more complex authorization modeling scenarios involving attributes and can be used to represent some Attribute-based Access Control (ABAC) policies. Take a look at the [Conditions](../concepts.mdx#what-is-a-condition) and [Conditional Relationship Tuples](../concepts.mdx#what-is-a-conditional-relationship-tuple) concepts for a quick overview.
25+
26+
There are various use cases where Conditions can be helpful. These include, but are not limited to:
27+
28+
* [**Temporal Access Policies**](https://github.com/openfga/sample-stores/tree/main/stores/temporal-access) - manage user access for a window of time.
29+
* [**IP Allowlists or Geo-fencing Policies**](https://github.com/openfga/sample-stores/tree/main/stores/ip-based-access) - limit or grant access based on an IP Address range or corporate network policy.
30+
* [**Usage-based/Feature-based Policies (Entitlements)**](https://github.com/openfga/sample-stores/tree/main/stores/advanced-entitlements) - enforce quoata or usage of some resource or feature.
31+
* [**Resource-attribute Policies**](https://github.com/openfga/sample-stores/tree/main/stores/groups-resource-attributes) - define policies to access resources based on attributes/fields of the resource(s).
32+
33+
For more information and background context on why we added this feature, please see our blog post on [Conditional Relationship Tuples for OpenFGA](https://openfga.dev/blog/conditional-tuples-announcement).
34+
35+
## Defining Conditions in Models
36+
For this example we'll use the following authorization model to demonstrate a temporal based access policy. Namely, a user can view a document if and only if they have been granted the viewer relationship AND their non-expired grant policy is met.
37+
38+
```dsl.openfga
39+
model
40+
schema 1.1
41+
42+
type user
43+
44+
type document
45+
relations
46+
define viewer: [user with non_expired_grant]
47+
48+
condition non_expired_grant(current_time: timestamp, grant_time: timestamp, grant_duration: duration) {
49+
current_time < grant_time + grant_duration
50+
}
51+
```
52+
53+
:::note
54+
The type restriction for `document#viewer` requires that any user of type `user` that is written in the relationship tuple must be accompanied by the `non_expired_grant` condition. This is denoted by the `user with non_expired_grant` specification.
55+
:::
56+
57+
Write the model to the FGA store:
58+
<WriteAuthzModelViewer
59+
authorizationModel={{
60+
"schema_version":"1.1",
61+
"type_definitions": [
62+
{
63+
"type":"user"
64+
},
65+
{
66+
"type":"document",
67+
"relations": {
68+
"viewer": {
69+
"this": {}
70+
}
71+
},
72+
"metadata": {
73+
"relations": {
74+
"viewer": {
75+
"directly_related_user_types": [
76+
{
77+
"type":"user",
78+
"condition":"non_expired_grant"
79+
}
80+
]
81+
}
82+
}
83+
}
84+
}
85+
],
86+
"conditions": {
87+
"non_expired_grant": {
88+
"name":"non_expired_grant",
89+
"expression":"current_time < grant_time + grant_duration",
90+
"parameters": {
91+
"current_time": {
92+
"type_name":"TYPE_NAME_TIMESTAMP"
93+
},
94+
"grant_duration": {
95+
"type_name":"TYPE_NAME_DURATION"
96+
},
97+
"grant_time": {
98+
"type_name":"TYPE_NAME_TIMESTAMP"
99+
}
100+
}
101+
}
102+
}
103+
}}
104+
skipSetup={true}
105+
allowedLanguages={[
106+
SupportedLanguage.JS_SDK,
107+
SupportedLanguage.GO_SDK,
108+
SupportedLanguage.CLI,
109+
SupportedLanguage.CURL,
110+
]}
111+
/>
112+
113+
## Writing Conditional Relationship Tuples
114+
Using the model above, when we [Write](https://openfga.dev/api/service#/Relationship%20Tuples/Write) relationship tuples to the OpenFGA store, then any `document#viewer` relationship with `user` objects must be accompanied by the condition `non_expired_grant` because the type restriction requires it.
115+
116+
For example, we can give `user:anne` viewer access to `document:1` for 10 minutes by writing the following relationship tuple:
117+
118+
<WriteRequestViewer
119+
relationshipTuples={[
120+
{
121+
user: 'user:anne',
122+
relation: 'viewer',
123+
object: 'document:1',
124+
condition: {
125+
name: 'non_expired_grant',
126+
context: {
127+
grant_time: '2023-01-01T00:00:00Z',
128+
grant_duration: '10m',
129+
}
130+
}
131+
},
132+
]}
133+
skipSetup={true}
134+
allowedLanguages={[
135+
SupportedLanguage.JS_SDK,
136+
SupportedLanguage.GO_SDK,
137+
SupportedLanguage.CURL,
138+
]}
139+
/>
140+
141+
## Queries with Condition Context
142+
Now that we have written a [Conditional Relationship Tuple](../concepts.mdx#what-is-a-conditional-relationship-tuple), we can query OpenFGA using the [Check API](https://openfga.dev/api/service#/Relationship%20Queries/Check) to see if `user:anne` has viewer access to `document:1` under certain conditions/context. That is, `user:anne` should only have access if the current timestamp is less than the grant timestamp (e.g. the time which the tuple was written) plus the duration of the grant (10 minutes). If the current timestamp is less than, then you'll get a permissive decision. For example,
143+
144+
<CheckRequestViewer
145+
user={'user:anne'}
146+
relation={'viewer'}
147+
object={'document:1'}
148+
context={{current_time: "2023-01-01T00:09:50Z"}}
149+
allowed={true}
150+
skipSetup={true}
151+
allowedLanguages={[
152+
SupportedLanguage.JS_SDK,
153+
SupportedLanguage.GO_SDK,
154+
SupportedLanguage.CURL,
155+
]}
156+
/>
157+
158+
but if the current time is outside the grant window then you get a deny decision. For example,
159+
160+
<CheckRequestViewer
161+
user={'user:anne'}
162+
relation={'viewer'}
163+
object={'document:1'}
164+
context={{current_time: "2023-01-01T00:10:01Z"}}
165+
allowed={false}
166+
skipSetup={true}
167+
allowedLanguages={[
168+
SupportedLanguage.JS_SDK,
169+
SupportedLanguage.GO_SDK,
170+
SupportedLanguage.CURL,
171+
]}
172+
/>
173+
174+
Similarly, we can use the [ListObjects API](https://openfga.dev/api/service#/Relationship%20Queries/ListObjects) to return all of the documents that `user:anne` has viewer access to given the current time. For example,
175+
176+
<ListObjectsRequestViewer
177+
objectType="document"
178+
relation="viewer"
179+
user="user:anne"
180+
context={{current_time: "2023-01-01T00:09:50Z"}}
181+
expectedResults={['document:1']}
182+
skipSetup={true}
183+
allowedLanguages={[
184+
SupportedLanguage.JS_SDK,
185+
SupportedLanguage.GO_SDK,
186+
SupportedLanguage.CURL,
187+
]}
188+
/>
189+
190+
but if the current time is outside the grant window then we don't get the object in the response. For example,
191+
192+
<ListObjectsRequestViewer
193+
objectType="document"
194+
relation="viewer"
195+
user="user:anne"
196+
context={{current_time: "2023-01-01T00:10:01Z"}}
197+
expectedResults={[]}
198+
skipSetup={true}
199+
allowedLanguages={[
200+
SupportedLanguage.JS_SDK,
201+
SupportedLanguage.GO_SDK,
202+
SupportedLanguage.CURL,
203+
]}
204+
/>
205+
206+
:::note
207+
When evaluating a condition at request time, the context written/persisted in the relationship tuple and the context provided at request time are merged together into a single evaluation context.
208+
209+
If you provide a context value in the request context that is also written/persisted in the relationship tuple, then the context values written in the relationship tuple take precedence. That is, the merge strategy is such that persisted context has higher precedence than request context.
210+
:::
211+
212+
## Examples
213+
For more examples take a look at our [Sample Stores](https://github.com/openfga/sample-stores) repository. There are various examples with ABAC models in that repository.
214+
215+
216+
## Supported Parameter Types
217+
The following table enumerates the list of supported parameter types. The more formal list is defined in https://github.com/openfga/openfga/tree/main/internal/condition/types.
218+
219+
Note that some of the types support generics, these types are indicated with `<T>`.
220+
221+
| Friendly Type Name | Type Name (Protobuf Enum) | Description | Examples |
222+
|--------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
223+
| int | TYPE_NAME_INT | A 64-bit signed integer value. | -1<br />"-1" |
224+
| uint | TYPE_NAME_UINT | A 64-bit unsigned integer value. | 1<br />"1" |
225+
| double | TYPE_NAME_DOUBLE | A double-width floating point value, represented equivalently as a Go `float64` value.<br /><br />If the value is provided as a string we parse it with `strconv.ParseFloat(s, 64)`. See [`strconv.ParseFloat`](https://pkg.go.dev/strconv#ParseFloat) for more info. | 3.14159<br />-0.75<br />"1"<br />"-2.5" |
226+
| bool | TYPE_NAME_BOOL | A boolean value. | true<br />false<br /><br />"true"<br />"false" |
227+
| bytes | TYPE_NAME_BYTES | An array of byte values specified as a byte string. | "bytestring" |
228+
| string | TYPE_NAME_STRING | A string value. | "hello, world" |
229+
| duration | TYPE_NAME_DURATION | A value representing a duration of time specified using Go duration string format.<br /><br />See [time.Duration#ParseDuration](https://pkg.go.dev/time#ParseDuration) | "120s"<br />"2m" |
230+
| timestamp | TYPE_NAME_TIMESTAMP | A timestamp value that follows the RFC3339 specification. | "2023-01-01T00:00:00Z" |
231+
| any | TYPE_NAME_ANY | A variant type which permits any value to be provided. | \{"x": 1\}<br />"hello"<br />["a", "b"] |
232+
| list\<T\> | TYPE_NAME_LIST | A list of values of generic type T. | list\<string\> - ["a", "b", "c"]<br />list\<int\> - [-1, 1]<br />list\<duration\> - ["60s", "1m"] |
233+
| map\<T\> | TYPE_NAME_MAP | A map whose keys are strings and whose values are values of generic type T.<br /><br />Any map value must have string keys, only the value types can vary. | map\<int\> - \{"x": -1, "y": 1\}<br />map\<string\> - \{"key": "value"\} |
234+
| ipaddress | TYPE_NAME_IPADDRESS | A custom value type specified as a string representation of an IP Address. | "192.168.0.1" |
235+
236+
237+
## Limitations
238+
* The size of the condition `context` parameter that can be written alongside a relationship tuple is limited to 32KB in size.
239+
240+
* The size of the condition `context` parameter for query requests (e.g. Check, ListObjects, etc..) is not explicitly limited, but the OpenFGA server has an overall request size limit of 512KB at this time.
241+
242+
* We're still working on the changes to support Conditions in the official FGA CLI and various OpenFGA SDKs including: .NET and Python. At this moment you cannot Write conditional relationship tuples with these tools and/or query (e.g. Check, ListObjects, etc..) OpenFGA with condition context.
243+
244+
* We enforce a maximum Google CEL expression evaluation cost of 100 (by default) to protect the server from malicious conditions. The evaluation cost of a CEL expression is a function of the size the input that is being compared and the composition of the expression. For more general information please see the official [Language Definition for Google CEL](https://github.com/google/cel-spec/blob/master/doc/langdef.md). If you hit these limits with practical use-cases, please reach out to the maintainer team and we can discuss.

docs/sidebars.js

+5
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ const sidebars = {
160160
label: 'Custom Roles',
161161
id: 'content/modeling/custom-roles',
162162
},
163+
{
164+
type: 'doc',
165+
label: 'Conditions',
166+
id: 'content/modeling/conditions',
167+
},
163168
{
164169
type: 'doc',
165170
label: 'Contextual and Time-Based Authorization',

docusaurus.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const config = {
5757
// location of the swagger file
5858
apiDocsBasePath: process.env.API_DOCS_PATH
5959
? process.env.API_DOCS_PATH
60-
: 'https://raw.githubusercontent.com/openfga/api/0f1d73e766d26ac5c004383d741ee0f815c9b1e6/docs/openapiv2/apidocs.swagger.json',
60+
: 'https://raw.githubusercontent.com/openfga/api/main/docs/openapiv2/apidocs.swagger.json',
6161

6262
// Customization for product information
6363
/* eslint-disable max-len */

0 commit comments

Comments
 (0)