Skip to content

Latest commit

 

History

History
1082 lines (962 loc) · 37.3 KB

modeling-language.mdx

File metadata and controls

1082 lines (962 loc) · 37.3 KB
title sidebar_position slug description
Modeling Language
2
/modeling-language
Learning about the FGA modeling language and using it to build a representation of a system's authorization model

import { AuthzModelSnippetViewer, CheckRequestViewer, DocumentationNotice, ProductConcept, ProductName, ProductNameFormat, RelatedSection, RelationshipTuplesViewer, SyntaxFormat, UpdateProductNameInLinks, WriteRequestViewer, } from '@components/Docs';

Modeling Language

The 's Modeling Language is used to build a representation of a system's . It informs on what the in the system are and how they could relate to one another. It describes the possible on an object of a certain type and lists the conditions under which one is related to that object.

The DSL and the JSON syntax are two presentations of that modeling language. The JSON syntax is accepted by the API and closely matches the API described in the Zanzibar paper. The DSL is syntactic sugar on top of the JSON syntax and compiles down to it before being sent to 's API. The DSL is meant to make modeling easier and more intuitive.

You'll encounter the JSON syntax when calling the API directly or through the SDKs, and the DSL when interacting with through the Playground. Throughout the documentation you can switch between the two presentations.

To understand this guide better, you should be familiar with some and How to get started on modeling.

What Does The Modeling Language Look Like?

Below is a sample authorization model. In the next sections we'll go over the building blocks that make the modeling language.

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type_definitions: [ { type: 'user', }, { type: 'domain', relations: { member: { this: {}, }, }, metadata: { relations: { member: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, { type: 'folder', relations: { can_share: { computedUserset: { object: '', relation: 'writer', }, }, owner: { union: { child: [ { this: {}, }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'owner', }, }, }, ], }, }, parent_folder: { this: {}, }, viewer: { union: { child: [ { this: {}, }, { computedUserset: { object: '', relation: 'writer', }, }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'viewer', }, }, }, ], }, }, writer: { union: { child: [ { this: {}, }, { computedUserset: { object: '', relation: 'owner', }, }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'writer', }, }, }, ], }, }, }, metadata: { relations: { owner: { directly_related_user_types: [{ type: 'user' }, { type: 'domain', relation: 'member' }] }, parent_folder: { directly_related_user_types: [{ type: 'folder' }] }, viewer: { directly_related_user_types: [{ type: 'user' }, { type: 'domain', relation: 'member' }] }, writer: { directly_related_user_types: [{ type: 'user' }, { type: 'domain', relation: 'member' }] }, }, }, }, { type: 'document', relations: { can_share: { computedUserset: { object: '', relation: 'writer', }, }, owner: { union: { child: [ { this: {}, }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'owner', }, }, }, ], }, }, parent_folder: { this: {}, }, viewer: { union: { child: [ { this: {}, }, { computedUserset: { object: '', relation: 'writer', }, }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'viewer', }, }, }, ], }, }, writer: { union: { child: [ { this: {}, }, { computedUserset: { object: '', relation: 'owner', }, }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'writer', }, }, }, ], }, }, }, metadata: { relations: { owner: { directly_related_user_types: [{ type: 'user' }, { type: 'domain', relation: 'member' }] }, parent_folder: { directly_related_user_types: [{ type: 'folder' }] }, viewer: { directly_related_user_types: [{ type: 'user' }, { type: 'domain', relation: 'member' }] }, writer: { directly_related_user_types: [{ type: 'user' }, { type: 'domain', relation: 'member' }] }, }, }, }, ], }} />

:::info

The authorization model describes four of objects: user, domain, folder and document.

The domain has a single called member that only allows .

The folder and document type definitions each have five relations: parent_folder, owner, writer, viewer and can_share.

:::

The Direct Relationship Type Restrictions

[<string, <string>, ...], when used at the beginning of a allows from the objects of these specified types. The strings can be of three formats:

  • <type>: indicating that tuples relating objects of those types as users can be written (e.g. group:marketing can be related if group is in the type restrictions)
  • <type:*>: indicating that a tuple relating all objects of that type can be written (e.g. user:* can be added if user:* is in the type restrictions)
  • <type>#<relation>: indicating that tuples with sets of users that are related to an object of that type by that particular relation (e.g. group:marketing#member can be added if group#member is in the type restrictions)

If the direct relationship type restrictions are missing, then no tuple can be written relating other objects as this particular relation with objects of this type (direct relationships are disallowed).

:::info

[<type1>, <type2>, ...] in the DSL translates to this in the API syntax.

:::

For example, let's take a closer look at the team type.

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type: 'team', relations: { member: { this: {}, }, }, metadata: { relations: { member: { directly_related_user_types: [{ type: 'user' }, { type: 'user:*'}, { type: 'team', relation: 'member' }] }, }, }, }} skipVersion={true} />

This team defines all the that can have with an of type team. In this case the relation is: member.

Due to the direct relationship type restrictions ([user, team#member]) being used, a user in the system can have a with the team type as a member for objects of

  • type user
  • the user (user:*)
  • usersets that have a team type and a member relation (e.g. team:product#member)

In the type definition snippet above, anne is a member of team:product if any one of the following relationship tuple sets exist:

  • <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'member', object: 'team:product', _description: 'Anne is directly related to the product team as a member', }, ]} />

  • <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:*', relation: 'member', object: 'team:product', _description: 'Everyone (*) is directly related to the product team as a member', }, ]} />

  • <RelationshipTuplesViewer relationshipTuples={[ { user: 'team:contoso#member', relation: 'member', object: 'team:product', _description: 'Members of the contoso team are members of the product team', }, { user: 'user:anne', relation: 'member', object: 'team:contoso', _description: 'Anne is a member of the contoso team', }, ]} />

For more examples, take look at Modeling Building Blocks: Direct Relationships.

Referencing Other Relations On The Same Object

You can also reference other relations on the same object. Let us look at a simplified document type definition.

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type: 'document', relations: { editor: { this: {}, }, viewer: { union: { child: [ { this: {} }, { computedUserset: { relation: 'editor', }, }, ], }, }, can_rename: { computedUserset: { relation: 'editor', }, }, }, metadata: { relations: { editor: { directly_related_user_types: [{ type: 'user' }] }, viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }} skipVersion={true} />

The above document defines all the that can have with an of type document. In this case the relations are: editor, viewer and can_rename.

The viewer and can_rename relation definitions are both referencing editor, which is another relation on the same type.

:::info

Notice how can_rename does not reference the direct relationship type restrictions, indicating that a direct relationship is not possible (as in a user cannot be directly assigned this relation, it has to be inherited through an assignment of the editor relation). The viewer relation on the other hand allows both direct and indirect relationships using the Union Operator.

:::

In the type definition snippet above, anne is a viewer of document:new-roadmap if any one of the following relationship tuple sets exists:

  • anne is an editor of document:new-roadmap

    <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'editor', object: 'document:new-roadmap', _description: 'Anne is an editor of the new-roadmap document', }, ]} />

  • anne is a viewer of document:new-roadmap <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'viewer', object: 'document:new-roadmap', _description: 'Anne is a viewer of the new-roadmap document', }, ]} />

anne has a can_rename relationship with document:new-roadmap only if anne has an editor relationship with the document:

  • anne is an editor of document:new-roadmap <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'editor', object: 'document:new-roadmap', _description: 'Anne is an editor of thew new-roadmap document', }, ]} />

For more examples, take a look at Modeling Building Blocks: Concentric Relationships, Modeling: Roles and Permissions and Advanced Modeling: Google Drive.

Referencing Relations On Related Objects

Another form of is made possible by referencing relations on other objects.

The syntax is X from Y. It requires that:

  • the other object is related to the current object as Y
  • the user is related to another object as X

Take a look at the authorization model below.

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type_definitions: [ { type: 'user', }, { type: 'folder', relations: { viewer: { this: {}, }, }, metadata: { relations: { viewer: { directly_related_user_types: [{ type: 'user' }, { type: 'folder', relation: 'viewer' }] }, }, }, }, { type: 'document', relations: { parent_folder: { this: {}, }, viewer: { union: { child: [ { this: {} }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'viewer', }, }, }, ], }, }, }, metadata: { relations: { parent_folder: { directly_related_user_types: [{ type: 'folder' }] }, viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} />

The snippet below taken from the authorization model above is stating that viewers of a document are all users directly assigned the viewer relation and all users who can view the document's parent folder.

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type_definitions: [ { type: 'document', relations: { viewer: { union: { child: [ { this: {} }, { tupleToUserset: { tupleset: { object: '', relation: 'parent_folder', }, computedUserset: { object: '', relation: 'viewer', }, }, }, ], }, }, }, metadata: { relations: { viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} skipVersion={true} />

In the authorization model above, user:anne is a viewer of document:new-roadmap if any one of the following relationship tuple sets exists:

  • Anne is a viewer of the parent folder of the new-roadmap document <RelationshipTuplesViewer relationshipTuples={[ { user: 'folder:planning', relation: 'parent_folder', object: 'document:new-roadmap', _description: 'planning folder is the parent folder of the new-roadmap document', }, { user: 'user:anne', relation: 'viewer', object: 'folder:planning', _description: 'anne is a viewer of the planning folder', }, ]} />
  • Anne is a viewer of the new-roadmap document (direct relationship) <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'viewer', object: 'document:new-roadmap', _description: 'anne is a viewer of the new-roadmap document', }, ]} />

This particular use of referencing relations on related objects is defining a transitive implied relationship. If user A is related to a certain object B as a viewer, and object B is related to object C as parent, then user A is related to object C as viewer.

This can be used to indicate that viewers of a folders are viewers of all documents in that folder.

:::caution Note that does not allow the referenced relation (the word after from, also called the tupleset) to be referencing another relation or allow non-concrete types (type bound public access (<object_type>:*) or usersets (<object_type>#<relation>)) in its type restrictions and will throw a validation error when attempting to call WriteAuthorizationModel. :::

For more examples, take look at Modeling: Parent-Child Objects, Advanced Modeling: Google Drive, Advanced Modeling: GitHub, and Advanced Modeling: Entitlements.

The Union Operator

The union operator (or in the DSL, union in the JSON syntax) is used to indicate that a exists if the is in any of the sets of users (union).

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type_definitions: [ { type: 'document', relations: { viewer: { // a user is related to the object as a viewer if: union: { // they are in any of child: [ { this: {}, // the userset of all users related to the object as "viewer"; indicating that a user can be assigned a direct viewer relation, i.e., not implied through another relation }, { computedUserset: { relation: 'editor', // the userset of all users related to the object as "editor"; indicating that a user who is an editor is also implicitly a viewer }, }, ], }, }, }, metadata: { relations: { viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} skipVersion={true} />

In the snippet above, user:anne is a viewer of document:new-roadmap if any of the following conditions are satisfied:

  • there exists a with anne as editor of document:new-roadmap <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'editor', object: 'document:new-roadmap', }, ]} />
  • anne is a viewer of document:new-roadmap <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'viewer', object: 'document:new-roadmap', }, ]} />

:::info

The above indicates that a user is related as a viewer if they are in any of:

  • the userset of all users related to the object as "viewer"; indicating that a user can be assigned a direct viewer relation
  • the userset of all users related to the object as "editor"; indicating that a user who is an editor is also implicitly a viewer

So if anne is in at least one of those usersets (is either an editor or a viewer), the on {"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"} will return {"allowed": true}.

:::

For more examples, take a look at Modeling Building Blocks: Concentric Relationships, Modeling Roles and Permissions and Advanced Modeling: Modeling for IoT.

The Intersection Operator

The intersection operator (and in the DSL, intersection in the JSON syntax) is used to indicate that a exists if the is in all the sets of users (intersection)

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type_definitions: [ { type: 'document', relations: { viewer: { // a user is related to the object as a viewer if intersection: { // they are in all of child: [ { computedUserset: { // the userset of all users related to the object as "authorized_user" relation: 'authorized_user', }, }, { computedUserset: { // the userset of all users related to the object as "editor" relation: 'editor', }, }, ], }, }, }, metadata: { relations: { viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} skipVersion={true} />

In the snippet above, user:anne is a viewer of document:new-roadmap if all of the following conditions are satisfied:

  • anne is an editor of document:new-roadmap <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'editor', object: 'document:new-roadmap', }, ]} /> AND
  • anne is an authorized_user of document:new-roadmap: <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'authorized_user', object: 'document:new-roadmap', }, ]} />

:::info

The above indicates that a user is related as a viewer if they are in all of:

  • the userset of all users related to the object as "authorized_user"
  • the userset of all users related to the object as "editor"

So anne has to be in the intersection of the usersets (anne has to be both an editor AND an authorized_user), in order for the on {"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"} to return {"allowed": true}.

anne is not a viewer for document:new-roadmap if either of the following is true:

  • anne is not an editor to document:new-roadmap: no relationship tuple of {"user": "user:anne", "relation": "editor", "object": "document:new-roadmap"}
  • anne is not an authorized_user on the document:new-roadmap: no relationship tuple of {"user": "user:anne", "relation": "authorized_user", "object": "document:new-roadmap"}

:::

For more examples, take look at Modeling with Multiple Restrictions.

The Exclusion Operator

The exclusion operator (but not in the DSL, difference in the JSON syntax) is used to indicate that a exists if the is in the base userset, but not in the excluded userset. This is helpful in modeling exclusion or block lists.

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type_definitions: [ { type: 'document', relations: { viewer: { // a user is related to the object as a viewer if they are in difference: { base: { this: {}, // the userset of all users related to the object as "viewer" }, subtract: { computedUserset: { relation: 'blocked', // but not in the userset of all users related to the object as "blocked" }, }, }, }, }, metadata: { relations: { viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} skipVersion={true} />

In the type definition snippet above, user:anne is a viewer of document:new-roadmap if:

  • anne is assigned a direct relationship as viewer to document:new-roadmap

    <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'viewer', object: 'document:new-roadmap', }, ]} /> AND

  • anne is not blocked to document:new-roadmap. That is, the following relation tuple does not exists <RelationshipTuplesViewer relationshipTuples={[ { user: 'user:anne', relation: 'blocked', object: 'document:new-roadmap', }, ]} />

For more information, see Modeling: Blocklists.

:::info

The above indicates that a user is related as a viewer if they are in:

  • the userset of all users related to the object as "viewer"

but not in:

  • the userset of all users related to the object as "blocked"

So anne has to be both a viewer AND NOT blocked, in order for the on {"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"} to return {"allowed": true}.

anne is not a viewer for document:new-roadmap if either of the following is true:

  • anne is not assigned direct relationship as viewer to document:new-roadmap: no relationship tuple of {"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"}
  • anne is blocked on the document:new-roadmap {"user": "user:anne", "relation": "blocked", "object": "document:new-roadmap"}

:::

Equivalent Zanzibar Concepts

The JSON syntax accepted by the API closely mirrors the syntax represented in the Zanzibar paper, with a bit of flattening and converting keys from snake_case to camelCase.

Zanzibar JSON DSL
this this [<type1>,<type2>]
union union or
intersection intersection and
exclusion difference but not
tuple_to_userset tupleToUserset x from y

In the Zanzibar paper, there's this example:

name: "doc"

relation { name: "owner" }

relation {
  name: "editor"
  userset_rewrite {
    union {
      child { _this {} }
      child { computed_userset { relation: "owner" } }
}}}

relation {
 name: "viewer"
 userset_rewrite {
  union {
    child { _this {} }
    child { computed_userset { relation: "editor" } }
    child { tuple_to_userset {
      tupleset { relation: "parent" }
      computed_userset {
        object: $TUPLE_USERSET_OBJECT  # parent folder
        relation: "viewer" }}}
}}}

In the DSL, it would become:

<AuthzModelSnippetViewer onlyShow={SyntaxFormat.Friendly2} configuration={{ schema_version: '1.1', type_definitions: [ { type: 'doc', relations: { owner: { this: {}, }, editor: { union: { child: [ { this: {}, }, { computedUserset: { relation: 'owner', }, }, ], }, }, viewer: { union: { child: [ { this: {}, }, { computedUserset: { relation: 'editor', }, }, { tupleToUserset: { tupleset: { relation: 'parent', }, computedUserset: { relation: 'viewer', }, }, }, ], }, }, }, metadata: { relations: { owner: { directly_related_user_types: [{ type: 'user' }] }, editor: { directly_related_user_types: [{ type: 'user' }] }, viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} />

And in the JSON, it would become:

<AuthzModelSnippetViewer onlyShow={SyntaxFormat.Api} configuration={{ schema_version: '1.1', type_definitions: [ { type: 'doc', relations: { owner: { this: {}, }, editor: { union: { child: [ { this: {}, }, { computedUserset: { relation: 'owner', }, }, ], }, }, viewer: { union: { child: [ { this: {}, }, { computedUserset: { relation: 'editor', }, }, { tupleToUserset: { tupleset: { relation: 'parent', }, computedUserset: { relation: 'viewer', }, }, }, ], }, }, }, metadata: { relations: { owner: { directly_related_user_types: [{ type: 'user' }] }, editor: { directly_related_user_types: [{ type: 'user' }] }, viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} />

We believe this syntax is easier to read/write.

So the following:

<AuthzModelSnippetViewer configuration={{ schema_version: '1.1', type_definitions: [ { type: 'doc', relations: { viewer: { union: { child: [ { // a user can be assigned a direct viewer relation, i.e., not implied through another relation this: {}, }, { // a user that is an editor is also implicitly a viewer computedUserset: { relation: 'editor', }, }, { // a user that is an viewer on any of the object's parents is also implicitly a viewer on the object tupleToUserset: { tupleset: { relation: 'parent', }, computedUserset: { relation: 'viewer', }, }, }, ], }, }, }, metadata: { relations: { viewer: { directly_related_user_types: [{ type: 'user' }] }, }, }, }, ], }} />

Can be read as:

  • The users with a viewer relationship to a certain doc are any of:
    • the set of users who are with this doc as viewer
    • the set of users who are related to this doc as editor
    • the set of users who are related to any object OBJ_1 as viewer, where object OBJ_1 is any object related to this doc as parent (e.g. viewers of this doc's parent folder, where the parent folder is OBJ_1)

Learn more about Zanzibar at the Zanzibar Academy.

Related Sections

<RelatedSection description="Check the following sections for more on how to use the modeling language in modeling authorization." relatedLinks={[ { title: '{ProductName} Concepts', description: 'Learn about the {ProductName} Concepts.', link: './concepts', id: './concepts', }, { title: 'Modeling: Getting Started', description: 'Learn about how to get started with modeling your permission system in {ProductName}.', link: './modeling/getting-started', id: './modeling/getting-started', }, { title: 'Direct Access', description: 'Learn about modeling user access to an object.', link: './modeling/direct-access', id: './modeling/direct-access', }, ]} />