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
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.
- Python
- Node.js
- Java
- cURL
To install the latest python SDK:
pip install permit
All other instructions from the Python quickstart remain the same.
To install the latest Node.js SDK:
npm install permitio
All other instructions from the Node.js quickstart remain the same.
To install the latest Java SDK you'll need version 2.0.0
.
For Maven projects, use:
<dependency>
<groupId>io.permit</groupId>
<artifactId>permit-sdk-java</artifactId>
<version>2.0.0</version>
</dependency>
For Gradle projects, configure permit-sdk-java
as a dependency in your build.gradle
file:
dependencies {
// ...
implementation 'io.permit:permit-sdk-java:2.0.0'
}
All other instructions from the Java quickstart remain the same.
export permit_project="<your-project-key>" # for example: the `default` project
export permit_env="<your-environment-key>" # for example: the `dev` environment
export permit_sdk_api_key="<your-api-key>" # for example: `permit_key_...`
The API key you should use for your API calls must be an environment-level API key.
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
- Object Hierarchy: A google drive account can contain many folders and files. Folders can contain other files and folders.
- 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).
- 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.
- 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.
- 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
- Python
- Node.js
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"
]
}
}
}'
from permit import Permit
permit = Permit(token="<YOUR_API_KEY>", ...)
await permit.api.create_resource(
{
"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"
]
}
}
}
)
const { Permit } = require("permitio");
const permit = new Permit({token: "<YOUR_API_KEY>", ...});
await permit.api.createResource(
{
"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
- Python
- Node.js
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"
]
}
}
}'
from permit import Permit
permit = Permit(token="<YOUR_API_KEY>", ...)
await permit.api.create_resource(
{
"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",
],
},
},
}
)
const { Permit } = require("permitio");
const permit = new Permit({token: "<YOUR_API_KEY>", ...});
await permit.api.createResource(
{
"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
- Python
- Node.js
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"
]
}
}
}'
from permit import Permit
permit = Permit(token="<YOUR_API_KEY>", ...)
await permit.api.create_resource(
{
"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",
],
},
},
}
)
const { Permit } = require("permitio");
const permit = new Permit({token: "<YOUR_API_KEY>", ...});
await permit.api.createResource(
{
"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
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
- Python
- Node.js
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"
}'
await permit.api.resource_relations.create(
"file",
{
"key": "parent",
"name": "Parent",
"subject_resource": "folder",
}
)
await permit.api.resourceRelations.create("file", {
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
- Python
- Node.js
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"
}'
await permit.api.resource_relations.create(
"folder",
{
"key": "parent",
"name": "Parent",
"subject_resource": "folder",
}
)
await permit.api.resourceRelations.create("folder", {
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
- Python
- Node.js
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"
}'
await permit.api.resource_relations.create(
"folder",
{
"key": "account",
"name": "Account",
"subject_resource": "account",
}
)
await permit.api.resourceRelations.create("folder", {
key: "account",
name: "Account",
subject_resource: "account",
});
File -> account -> Account
- cURL
- Python
- Node.js
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"
}'
await permit.api.resource_relations.create(
"file",
{
"key": "account",
"name": "Account",
"subject_resource": "account",
}
)
await permit.api.resourceRelations.create("file", {
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)
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
- Python
- Node.js
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"
}'
await permit.api.sync_user(
{
"key": "john@acme.com",
}
)
await permit.api.syncUser({
key: "john@acme.com",
});
Creating the file
Now let's create the file instance:
- cURL
- Python
- Node.js
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"
}'
await permit.api.resource_instances.create(
{
"resource": "file",
"key": "2023_report",
"tenant": "default"
}
)
await permit.api.resourceInstances.create({
resource: "file",
key: "2023_report",
tenant: "default",
});
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
- Python
- Node.js
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"
}'
await permit.api.role_assignments.assign(
{
"user": "john@acme.com",
"role": "viewer",
"resource_instance": "file:2023_report",
}
)
await permit.api.roleAssignments.assign({
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
- Python
- Node.js
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",
}'
await permit.api.role_assignments.assign(
{
"user": "john@acme.com",
"role": "viewer",
"resource_instance": "file:2023_report",
"tenant": "default",
}
)
await permit.api.roleAssignments.assign({
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
- Python
- Node.js
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"
}'
await permit.api.sync_user(
{
"key": "jane@acme.com",
}
)
await permit.api.syncUser({
key: "jane@acme.com",
});
Then we create the folder instance:
- cURL
- Python
- Node.js
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"
}'
await permit.api.resource_instances.create(
{
"resource": "folder",
"key": "finance",
"tenant": "default"
}
)
await permit.api.resourceInstances.create({
resource: "folder",
key: "finance",
tenant: "default",
});
And finally we assign the role (editor
access to the folder):
- cURL
- Python
- Node.js
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"
}'
await permit.api.role_assignments.assign(
{
"user": "jane@acme.com",
"role": "editor",
"resource_instance": "folder:finance",
}
)
await permit.api.roleAssignments.assign({
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
- Python
- Node.js
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"
}'
await permit.api.relationship_tuples.create(
{
"subject": "folder:finance",
"relation": "parent",
"object": "file:2023_report",
}
)
await permit.api.relationshipTuples.create({
subject: "folder:finance",
relation: "parent",
object: "file:2023_report",
});
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
- Python
- Node.js
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"
}
]
}
}'
await permit.api.resource_roles.update(
"file",
"editor",
{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor",
},
],
},
}
)
await permit.api.resourceRoles.update("file", "editor", {
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
- Python
- Node.js
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"
}
]
}
}'
await permit.api.resource_roles.update(
"file",
"commenter",
{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "commenter",
}
]
}
}
)
await permit.api.resourceRoles.update("file", "commenter", {
granted_to: {
users_with_role: [
{
linked_by_relation: "parent",
on_resource: "folder",
role: "commenter",
},
],
},
});
folder#viewer -> file#viewer (via parent
)
- cURL
- Python
- Node.js
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"
}
]
}
}'
await permit.api.resource_roles.update(
"file",
"viewer",
{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "viewer",
}
]
}
}
)
await permit.api.resourceRoles.update("file", "viewer", {
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
- Python
- Node.js
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"
}'
await permit.api.resource_instances.create(
{
"resource": "account",
"key": "acme",
"tenant": "default",
}
)
await permit.api.resourceInstances.create({
resource: "account",
key: "acme",
tenant: "default",
});
relationship (account:acme, 'account', folder:finance)
- cURL
- Python
- Node.js
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"
}'
await permit.api.relationship_tuples.create(
{
"subject": "account:acme",
"relation": "account",
"object": "folder:finance",
}
)
await permit.api.relationshipTuples.create(
{
"subject": "account:acme",
"relation": "account",
"object": "folder:finance",
}
)
relationship (account:acme, 'account', file:2023_report)
- cURL
- Python
- Node.js
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"
}'
await permit.api.relationship_tuples.create(
{
"subject": "account:acme",
"relation": "account",
"object": "file:2023_report",
}
)
await permit.api.relationshipTuples.create(
{
"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
- Python
- Node.js
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"
}
]
}
}'
await permit.api.resource_roles.update(
"folder",
"editor",
{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "account",
"on_resource": "account",
"role": "admin",
},
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor",
},
]
}
}
)
await permit.api.resourceRoles.update(
"folder",
"editor",
{
"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
- Python
- Node.js
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"
}
]
}
}'
await permit.api.resource_roles.update(
"file",
"editor",
{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "account",
"on_resource": "account",
"role": "admin",
},
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor",
},
]
}
}
)
await permit.api.resourceRoles.update("file", "editor", {
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
- Python
- Node.js
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"
}'
await permit.api.resource_relations.create(
"file",
{
"key": "account_global",
"name": "Account Global",
"subject_resource": "account",
}
)
await permit.api.resourceRelations.create("file", {
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
- Python
- Node.js
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"
},
]
}
}'
await permit.api.resource_roles.update(
"file",
"editor",
{
"granted_to": {
"users_with_role": [
{
"linked_by_relation": "parent",
"on_resource": "folder",
"role": "editor",
},
{
"linked_by_relation": "account_global",
"on_resource": "account",
"role": "member",
},
]
}
}
)
await permit.api.resourceRoles.update("file", "editor", {
granted_to: {
users_with_role: [
{
linked_by_relation: "parent",
on_resource: "folder",
role: "editor",
},
{
linked_by_relation: "account_global",
on_resource: "account",
role: "member",
},
],
},
});
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.
- US West
- US East
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
docker run -it \
-e PDP_API_KEY=$permit_sdk_api_key \
-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
- Python
- Node.js
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"
}'
await permit.check(
# user
"john@acme.com",
# action
"read",
# resource
{
"type": "file",
"key": "2023_report",
},
)
await permit.check(
// user
"john@acme.com",
// action
"read",
// resource
{
type: "file",
key: "2023_report",
}
);
We get back a true
result, meaning that John can read the file. Can he update it?
- cURL
- Python
- Node.js
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"
}'
await permit.check(
# user
"john@acme.com",
# action
"update",
# resource
{
"type": "file",
"key": "2023_report",
},
)
await permit.check(
// user
"john@acme.com",
// action
"update",
// resource
{
type: "file",
key: "2023_report",
}
);
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
- Python
- Node.js
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"
}'
await permit.check(
# user
"jane@acme.com",
# action
"read",
# resource
{
"type": "file",
"key": "2023_report",
},
)
await permit.check(
// user
"jane@acme.com",
// action
"read",
// resource
{
type: "file",
key: "2023_report",
}
);
Yes! Jane can update the file because she has editor permissions on the finance folder, and those permissions propagate through the derived role.