Skip to main content
Version: 2.0.0

Modeling Google Drive Permissions with ReBAC

This tutorial explains how to build a simplified Google Drive-like permission system using Permit.io.

Before you begin

  1. Sign up to Permit.io
  2. Get your environment API Key

Setup your client

You can install the latest version of the SDKs to use ReBAC functions, or call the APIs directly using cURL or any other HTTP client.

To install the latest python SDK:

pip install permit

All other instructions from the Python quickstart remain the same.

What you will be building

Google drive is a system you can use to store, share, and collaborate on files and folders.

The G-Drive permission model is documented here. For simplicity we will implement a subset of it with Permit.

Requirements

  1. Object Hierarchy: A google drive account can contain many folders and files. Folders can contain other files and folders.
  2. Direct file access: A user can be a viewer of file, a commenter (meaning they can view and comment on the contents) or an editor (can view, comment and edit).
  3. Folder-level access: A user can be granted access to a folder. A user access level on a certain folder will grant that same access level on all the files within the folder.
  4. Account admin access: A user can be granted admin access to the entire account, which will grant the highest permission level on all folders and files within the account.
  5. General access to "Everyone in account": Files can be shared with all the users who are associated with the account that contains the file (share with all account members).

Test objects

To verify our system work, we will define a few objects (resource instances) and a few users.

The instances we will define:

- a `2023_report` file
- a `finance` folder
- an `acme` account

The hierarchy we will define is:

- the `2023_report` file is located within the `finance` folder
- both `file:2023_report` and `folder:finance` belong to the `acme` account.

The users we will define are

- John who has `viewer` access to file:2023_report // direct file access
- Jane who has `editor` access to folder:finance // folder level access

Modeling the Resource Graph

Define resources and roles

Resources

Resources in Permit are types of objects that you can enforce permissions on. Each resource has a predefined set of actions that a user can perform on it.

Permit ReBAC allows you to model your resources as a graph. Each resource (a type) is a node in the graph, and can connect to other resources via edges called relations.

The resources we will define for this example are:

- Account
- Folder
- File

Resource Roles

Each resource type can define its own roles. A resource role represents an access level (or a set of permissions) that can be granted on instances of a specific resource type.

Resource roles are typically denoted as Folder#editor, which means "a resource role with key editor that is defined on the resource type with key Folder".

The resource roles we will define are:

- Account#admin
- Account#member

- Folder#editor
- Folder#commenter
- Folder#viewer

- File#editor
- File#commenter
- File#viewer

The Account Resource

The Account resource type represents an organization's account in Google Drive. Later in the tutorial, we'll create a specific instance of this resource type for our fictional company, ACME, Inc.

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "account",
"name": "Account",
"actions": {
"invite-member": {},
"list-members": {},
"remove-member": {}
},
"roles": {
"admin": {
"name": "Admin",
"permissions": [
"invite-member",
"list-members",
"remove-member"
]
},
"member": {
"name": "Member",
"permissions": [
"list-members"
]
}
}
}'

We've created the Account resource, with three actions: invite-member, list-members, and remove-member.

We've also created two roles, admin and member. The admin role has permission to perform all actions, while the member action can only perform list-members.

Let's do the same with the Folder and File resource types.

The Folder Resource

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "folder",
"name": "Folder",
"actions": {
"list-files": {},
"create-file": {},
"rename": {}
},
"roles": {
"editor": {
"name": "Editor",
"permissions": [
"list-files",
"create-file",
"rename"
]
},
"commenter": {
"name": "Commenter",
"permissions": [
"list-files"
]
},
"viewer": {
"name": "Viewer",
"permissions": [
"list-files"
]
}
}
}'

Note that for the Folder resource, we've defined two identical roles - commenter and viewer.  Don't worry, we'll address this later on.

The File resource

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "file",
"name": "File",
"actions": {
"read": {},
"comment": {},
"update": {},
"delete": {}
},
"roles": {
"editor": {
"name": "Editor",
"permissions": [
"read",
"comment",
"update",
"delete"
]
},
"commenter": {
"name": "Commenter",
"permissions": [
"read",
"comment"
]
},
"viewer": {
"name": "Viewer",
"permissions": [
"read"
]
}
}
}'

Define relations

A relation is a type of an edge in the graph between two resources.

A relation called parent between a File resource and a Folder allows you to create relationship tuples of this type between a File instance and a Folder instance.

In other words, a relation is a type of a relationship (i.e: relationship tuple).

Example relation:

File -> parent -> Folder

Example of a relationship tuple of this type:

(Folder:soc2, parent, File:access-control-policy)

The relations we will define are:

Folder.account -> Account // relation from folder to its parent account
File.account -> Account // relation from file to its parent account
Folder.parent -> Folder // relation from folder to its parent folder
File.parent -> Folder // relation from file to its parent folder
The direction of relations/relationships

Note that the direction of a relation is typically from the object resource to the subject resource. However for relationships, we read the relationship tuple from the subject to the object, in the reverse direction.

File -> parent -> Folder

We'll define the parent relation, linking a file to the folder that contains it. As discussed before, relations define the kind of links that can exist between resource instances, so we define them on their corresponding resource types.

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/relations \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "parent",
"name": "Parent",
"subject_resource": "folder"
}'

Note that the relation is defined on the file resource /relations endpoint. A relation will always be defined on the object resource. Within the request body, you must specify the subject resource (the resource where the relation points to).

Folder -> parent -> Folder

Obviously, folders can also be in folders, so let's create that as well. Note that this relation is recursive, which means that it points from a resource type to itself, but the relationships that are defined using them will not necessarily be from an resource instance to itself - usually they'll point to another instance of the same type.

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/folder/relations \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "parent",
"name": "Parent",
"subject_resource": "folder"
}'

And let's create the two relations linking files and folders to their account:

Folder -> account -> Account

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/folder/relations \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "account",
"name": "Account",
"subject_resource": "account"
}'

File -> account -> Account

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/relations \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "account",
"name": "Account",
"subject_resource": "account"
}'

The model so far

In this diagram:

  • Resources are denoted by black circles
  • Resource Roles are denoted by blue circles
  • Resource Relations are denoted by black edges (or arrows)

Resource Window

Implementing Google Drive's Permissions

1. Direct file access

Before we can show how to assign direct file access to users, we need to model these files and users.

Creating the user

We create a user john@acme.com who will have a direct viewer access to a file:

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/users \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "john@acme.com"
}'

Creating the file

Now let's create the file instance:

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/resource_instances \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"resource": "file",
"key": "2023_report",
"tenant": "default"
}'
Tenant-boundaries for resource instances

Note that a resource instance must always belong to a tenant so that tenant-boundaries can still be maintained for end-customers. The tenant can be used to signify a completely separated end-customer account on your system.

Assigning the viewer role to the user

In order to assign the viewer access on the file, we must create a role assignment object.

Let's give John read-only access (i.e. the viewer role) to the 2023_report file:

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/role_assignments \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"user": "john@acme.com",
"role": "viewer",
"resource_instance": "file:2023_report"
}'

Bonus: creating resource instances implicitly

We could actually achieve the same result with fewer api calls.

Creating the user will remain the same as before, but we can skip creating the file and instead assign the role based on the instance key. Permit will assume such instance exists on your end and create it implicitly.

Let's give John read-only access (i.e. the viewer role) to the 2023_report file (and create the file implicitly):

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/role_assignments \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"user": "john@acme.com",
"role": "viewer",
"resource_instance": "file:2023_report",
"tenant": "default",
}'

Note that tenant must be specified if the resource instance is to be created implicitly.

2. Folder-level access

Just like we can give permissions on files, we can do the same on folders. Let's grant Jane the editor role on the finance folder.

We'll create a user for jane first:

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/users \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "jane@acme.com"
}'

Then we create the folder instance:

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/resource_instances \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"resource": "folder",
"key": "finance",
"tenant": "default"
}'

And finally we assign the role (editor access to the folder):

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/role_assignments \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"user": "jane@acme.com",
"role": "editor",
"resource_instance": "folder:finance"
}'

3. Folder permissions propagate to child files and folders

Note that while Jane can now list-files, create-file and rename the folder, she can't actually access files within the folder. In fact, there are no files in the folder yet. Let's fix that.

Creating a relationship tuple

First, we'll create a parent relationship from the 2023_report file to the finance folder:

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/relationship_tuples \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"subject": "folder:finance",
"relation": "parent",
"object": "file:2023_report"
}'
Relationship tuples vs. Role assignments

Role assignments and relationship tuples are quite similar:

  • Role assignments create a role relationship between a user and a resource instance.
  • Relationship tuples create a relationship between two resource instances.

The truth is, role assignments are actually a special type of relationship tuples (where the subject is a user and the relation is a role).

Creating a role derivation

Next, we'll change our graph schema slightly and add a role derivation.

A role derivation is a rule that specifies that a role on one resource instance automatically means another role on a related resource instance.

More strictly:

If a user has role `A` on a folder `X`, it implies that the same user
should be automatically granted a role `B` on all files where
the file `parent` is the `X` folder.

In our case, we want anyone who has the editor role on a folder, to automatically get the editor role on any file contained within that folder, that is, any file that has a parent relationship with that folder. Let's define that:

folder#editor -> file#editor (via parent)

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/roles/editor \
-X PATCH \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor"
}
]
}
}'

Note that the url endpoint modifies the granted role (file#editor) and specifies that it's granted to users with the folder#editor role on linked parent folders.

We'll do the same to also propagate the other roles from folder to file.

folder#commenter -> file#commenter (via parent)

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/roles/commenter \
-X PATCH \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "commenter"
}
]
}
}'

folder#viewer -> file#viewer (via parent)

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/roles/viewer \
-X PATCH \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "viewer"
}
]
}
}'

Note that now finally the folder#commenter role is finally different (granting different permissions) from folder#viewer, because they derive different roles on file.

4. Account admin permissions

We wish that if a user is granted admin access on the entire account, it will grant the highest permission level on all folders and files within the account.

We need to first define an account (called acme) that will be related to our files and folders.

account:acme

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/resource_instances \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"resource": "account",
"key": "acme",
"tenant": "default"
}'

relationship (account:acme, 'account', folder:finance)

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/relationship_tuples \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"subject": "account:acme",
"relation": "account",
"object": "folder:finance"
}'

relationship (account:acme, 'account', file:2023_report)

curl https://api.permit.io/v2/facts/$permit_project/$permit_env/relationship_tuples \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"subject": "account:acme",
"relation": "account",
"object": "file:2023_report"
}'

Role derivations

Since role derivations propagate recursively, associating a folder with an account will grant permissions not only on that folder, but also on the files it contains.

Let's grant anyone who has the account#admin role the folder#editor role on any folder in that account:

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/folder/roles/editor \
-X PATCH \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "account",
"on_resource": "account",
"role": "admin"
},
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor"
}
]
}
}'

Let's grant anyone who has the account#admin role the file#editor role on any file in that account (note that since we're PATCHing the editor role, we also specify the existing relation):

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/roles/editor \
-X PATCH \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "account",
"on_resource": "account",
"role": "admin"
},
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor"
}
]
}
}'

5. General access to "Everyone in account"

The last thing we're missing is the ability to give anyone in an account the editor role on a specific file. To do that, we'll add another relation from files to accounts called account_global, and create a role derivation giving any member of the account the editor role on these files.

First, we'll create the new relation:

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/relations \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"key": "account_global",
"name": "Account Global",
"subject_resource": "account"
}'

Then let's add the derived role (note that since we're PATCHing the editor role, we also specify the existing relations):

curl https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/roles/editor \
-X PATCH \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "account_global",
"on_resource": "account",
"role": "member"
}
{
"linked_by_relation": "account",
"on_resource": "account",
"role": "admin",
},
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor"
},
]
}
}'

Checking permissions

Now that we've configured everything and pushed some data, let's do a few permission checks to see what permissions Jane and John have.

First, we'll launch a Policy Decision Point (PDP). The PDP is a small container that fetches policy and data from Permit's cloud service and has all it needs to make decisions locally.

docker run -it \
-e PDP_API_KEY=$permit_sdk_api_key \
-e PDP_CONTROL_PLANE=https://api.permit.io \
-p 7766:7000 \
-p 8081:8081 \
permitio/pdp-v2:latest

That second port (8081) is optional. You can use it if you want to interact directly with the OPA instance running within the PDP.

Let's first see if John can read the 2023_report file:

curl http://localhost:7766/allowed \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"user": {
"key": "john@acme.com"
},
"resource": {
"tenant": "default",
"type": "file",
"key": "2023_report"
},
"action": "read"
}'

We get back a true result, meaning that John can read the file. Can he update it?

curl http://localhost:7766/allowed \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"user": {
"key": "john@acme.com"
},
"resource": {
"tenant": "default",
"type": "file",
"key": "2023_report"
},
"action": "update"
}'

We get back a false result, meaning that John cannot update the file. That's because he only has viewer permissions on the file.

Can Jane update the file?

curl http://localhost:7766/allowed \
-X POST \
-H "Authorization: Bearer $permit_sdk_api_key" \
-H "Content-Type: application/json" \
-d '{
"user": {
"key": "jane@acme.com"
},
"resource": {
"tenant": "default",
"type": "file",
"key": "2023_report"
},
"action": "update"
}'

Yes! Jane can update the file because she has editor permissions on the finance folder, and those permissions propagate through the derived role.