Modeling Google Drive permissions with OpenFGA
This tutorial explains how to represent Google Drive permissions model with OpenFGA.
- Indicate relationships between a group of users and an object. See Modeling User Groups for more.
Used here to indicate that all users within a domain can access a document (sharing a document within an organization). - Model concentric relationship to have a certain relation on an object imply another relation on the same object. See Modeling Concepts: Concentric Relationships for more.
Used here is to indicate that writers are also commenters and viewers. - Using the union operator condition to indicate that a user might have a certain relation with an object if they match any of the criteria indicated.
Used here to indicate that a user can be a viewer on a document, or can have the viewer relationship implied through commenter. - Using the type bound public access in a relationship tuple's user field to indicate that everyone has a certain relation with an object. See Modeling Public Access for more.
Used here to share documents publicly. - Model parent-child objects to indicate that a user having a relationship with a certain object implies having a relationship with another object in OpenFGA.
Used here is to indicate that a writer on a folder is a writer on all documents inside that folder.
Before You Start
In order to understand this guide correctly you must be familiar with some OpenFGA concepts and know how to develop the things that we will list below.
OpenFGA Concepts
It would be helpful to have an understanding of some concepts of OpenFGA before you start.
Modeling Concentric Relationships
You need to know how to update the authorization model to allow having nested relations such as all writers are readers. Learn more →
Modeling Object-to-Object Relationships
You need to know how to create relationships between objects and how that might affect a user's relationships to those objects. Learn more →
Used here to indicate that users who have access to view a folder have access to view all documents inside it.
Modeling Public Access
You need to know how to add a relationship tuple to indicate that a resource is publicly available. Learn more →
Concepts & Configuration Language
What You Will Be Modeling
Google Drive is a system to store, share, and collaborate on files and folders. Source
In this tutorial, you will build a subset of the Google Drive permission model (detailed below) in OpenFGA, using some scenarios to validate the model.
Note: For brevity, this tutorial will not model all of Google Drive's permissions. Instead, it will focus on modeling for the scenarios outlined below
Requirements
Google Drive's permission model is represented in their documentation.
In this tutorial, you will be focusing on a subset of these permissions.
Requirements:
- Users can be owners, editors, commenters and viewers of documents
- Documents can be shared with all users in a domain
- Folders can contain documents and users with a certain permission on a folder have that same permission to a document in that folder
- Documents and folders can be shared publicly
Defined Scenarios
There will be the following users:
- Anne, who is in the xyz domain
- Beth, who is in the xyz domain
- Charles, who is in the xyz domain
- Diane, who is NOT in the xyz domain
- Erik, who is NOT in the xyz domain
There will be:
- a 2021-budget document, owned by Anne, shared for commenting with Beth and viewable by all members of the xyz domain.
- a 2021-planning folder, viewable by Diane and contains the 2021-budget document
- a 2021-public-roadmap document, owned by Anne, available for members xyz domain to comment on and is publicly viewable
Modeling Google Drive's Permissions
01. Individual Permissions
To keep thing simple and focus on OpenFGA features rather than Google Drive complexity we will model only four roles (Viewer, Commenter, Writer, Owner).
At the end of this section we want to have the following permissions represented:
To represent permissions in OpenFGA we use relations. For document permissions we need to create the following authorization model:
- DSL
- JSON
model
schema 1.1
type user
type document
relations
define owner: [user]
define writer: [user]
define commenter: [user]
define viewer: [user]
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"owner": {
"this": {}
},
"writer": {
"this": {}
},
"commenter": {
"this": {}
},
"viewer": {
"this": {}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"commenter": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
The OpenFGA service determines if a user has access to an object by checking if the user has a relation to that object. Let us examine one of those relations in detail:
- DSL
- JSON
type document
relations
define viewer: [user]
{
"type": "document",
"relations": {
"viewer": {
"this": {}
}
},
"metadata": {
"relations": {
"viewer": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
The snippet above indicates that objects of type document have users related to them as "viewer" if those users belong to the userset of all users related to the document as "viewer".
This means that a user can be directly related as a viewer to an object of type "document"
If we want to say beth
is a commenter of document:2021-budget we create this relationship tuple:
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
await fgaClient.write({
writes: {
tuple_keys: [
{ user: 'user:beth', relation: 'commenter', object: 'document:2021-budget'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.WriteRequest{
Writes: &fgaSdk.TupleKeys{
TupleKeys: []fgaSdk.TupleKey {
{
User: fgaSdk.PtrString("user:beth"),
Relation: fgaSdk.PtrString("commenter"),
Object: fgaSdk.PtrString("document:2021-budget"),
},
},
},
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw")}
_, response, err := fgaClient.OpenFgaApi.Write(context.Background()).Body(body).Execute()
if err != nil {
// .. Handle error
}
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
await fgaClient.Write(new WriteRequest{
Writes = new TupleKeys(new List<TupleKey>() {
new() { User = "user:beth", Relation = "commenter", Object = "document:2021-budget" }
}),
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.tuple_keys import TupleKeys
# from openfga_sdk.models.write_request import WriteRequest
async def write():
body = WriteRequest(
writes=TupleKeys(
tuple_keys=[
TupleKey(
user="user:beth",
relation="commenter",
object="document:2021-budget",
),
],
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
await fga_client_instance.write(body)
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"user:beth","relation":"commenter","object":"document:2021-budget"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
write([
{
"user":"user:beth",
"relation":"commenter",
"object":"document:2021-budget"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
We can now ask OpenFGA "is beth
a commenter of repository document:2021-budget?"
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:beth',
relation: 'commenter',
object: 'document:2021-budget',
},
});
// allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
TupleKey: fgaSdk.TupleKey{
User: "user:beth",
Relation: "commenter",
Object: "document:2021-budget",
},
}
data, response, err := fgaClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: true }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest
{
TupleKey = new TupleKey() {
User = "user:beth",
Relation = "commenter",
Object = "document:2021-budget"
}, AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"});
// response.Allowed = true
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.check_request import CheckRequest
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
# Run a check
async def check():
body = CheckRequest(
tuple_key=TupleKey(
user="user:beth",
relation="commenter",
object="document:2021-budget",
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
response = await fga_client_instance.check(body)
# response.allowed = true
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:beth","relation":"commenter","object":"document:2021-budget"}}'
# Response: {"allowed":true}
check(
user = "user:beth", // check if the user `user:beth`
relation = "commenter", // has an `commenter` relation
object = "document:2021-budget", // with the object `document:2021-budget`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
We could also say that anne
is an owner of the same document:
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
await fgaClient.write({
writes: {
tuple_keys: [
{ user: 'user:anne', relation: 'owner', object: 'document:2021-budget'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.WriteRequest{
Writes: &fgaSdk.TupleKeys{
TupleKeys: []fgaSdk.TupleKey {
{
User: fgaSdk.PtrString("user:anne"),
Relation: fgaSdk.PtrString("owner"),
Object: fgaSdk.PtrString("document:2021-budget"),
},
},
},
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw")}
_, response, err := fgaClient.OpenFgaApi.Write(context.Background()).Body(body).Execute()
if err != nil {
// .. Handle error
}
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
await fgaClient.Write(new WriteRequest{
Writes = new TupleKeys(new List<TupleKey>() {
new() { User = "user:anne", Relation = "owner", Object = "document:2021-budget" }
}),
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.tuple_keys import TupleKeys
# from openfga_sdk.models.write_request import WriteRequest
async def write():
body = WriteRequest(
writes=TupleKeys(
tuple_keys=[
TupleKey(
user="user:anne",
relation="owner",
object="document:2021-budget",
),
],
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
await fga_client_instance.write(body)
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"user:anne","relation":"owner","object":"document:2021-budget"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
write([
{
"user":"user:anne",
"relation":"owner",
"object":"document:2021-budget"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
And ask some questions to OpenFGA:
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'owner',
object: 'document:2021-budget',
},
});
// allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
TupleKey: fgaSdk.TupleKey{
User: "user:anne",
Relation: "owner",
Object: "document:2021-budget",
},
}
data, response, err := fgaClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: true }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest
{
TupleKey = new TupleKey() {
User = "user:anne",
Relation = "owner",
Object = "document:2021-budget"
}, AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"});
// response.Allowed = true
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.check_request import CheckRequest
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
# Run a check
async def check():
body = CheckRequest(
tuple_key=TupleKey(
user="user:anne",
relation="owner",
object="document:2021-budget",
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
response = await fga_client_instance.check(body)
# response.allowed = true
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"owner","object":"document:2021-budget"}}'
# Response: {"allowed":true}
check(
user = "user:anne", // check if the user `user:anne`
relation = "owner", // has an `owner` relation
object = "document:2021-budget", // with the object `document:2021-budget`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'writer',
object: 'document:2021-budget',
},
});
// allowed = false
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
TupleKey: fgaSdk.TupleKey{
User: "user:anne",
Relation: "writer",
Object: "document:2021-budget",
},
}
data, response, err := fgaClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: false }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest
{
TupleKey = new TupleKey() {
User = "user:anne",
Relation = "writer",
Object = "document:2021-budget"
}, AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"});
// response.Allowed = false
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.check_request import CheckRequest
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
# Run a check
async def check():
body = CheckRequest(
tuple_key=TupleKey(
user="user:anne",
relation="writer",
object="document:2021-budget",
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
response = await fga_client_instance.check(body)
# response.allowed = false
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"writer","object":"document:2021-budget"}}'
# Response: {"allowed":false}
check(
user = "user:anne", // check if the user `user:anne`
relation = "writer", // has an `writer` relation
object = "document:2021-budget", // with the object `document:2021-budget`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: false
The first reply makes sense but the second one does not. Intuitively, if anne
was an owner, she was also be a writer. In fact, Google Drive explains this in their documentation
To make OpenFGA aware of this "concentric" permission model we need to update our definitions:
- DSL
- JSON
model
schema 1.1
type user
type document
relations
define owner: [user]
define writer: [user] or owner
define commenter: [user] or writer
define viewer: [user] or commenter
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"owner": {
"this": {}
},
"writer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "owner"
}
}
]
}
},
"commenter": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "writer"
}
}
]
}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "commenter"
}
}
]
}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"commenter": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
Let's examine one of those relations in detail:
objects of type document have users related to them as "viewer": if they belong to any of (the union of) the following:
- the userset of all users related to the document as "viewer"
- the userset of all users related to the document as "commenter"
With this update our model now supports nested definitions and now:
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'owner',
object: 'document:2021-budget',
},
});
// allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
TupleKey: fgaSdk.TupleKey{
User: "user:anne",
Relation: "owner",
Object: "document:2021-budget",
},
}
data, response, err := fgaClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: true }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest
{
TupleKey = new TupleKey() {
User = "user:anne",
Relation = "owner",
Object = "document:2021-budget"
}, AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"});
// response.Allowed = true
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.check_request import CheckRequest
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
# Run a check
async def check():
body = CheckRequest(
tuple_key=TupleKey(
user="user:anne",
relation="owner",
object="document:2021-budget",
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
response = await fga_client_instance.check(body)
# response.allowed = true
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"owner","object":"document:2021-budget"}}'
# Response: {"allowed":true}
check(
user = "user:anne", // check if the user `user:anne`
relation = "owner", // has an `owner` relation
object = "document:2021-budget", // with the object `document:2021-budget`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'writer',
object: 'document:2021-budget',
},
});
// allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
TupleKey: fgaSdk.TupleKey{
User: "user:anne",
Relation: "writer",
Object: "document:2021-budget",
},
}
data, response, err := fgaClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: true }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest
{
TupleKey = new TupleKey() {
User = "user:anne",
Relation = "writer",
Object = "document:2021-budget"
}, AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"});
// response.Allowed = true
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.check_request import CheckRequest
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
# Run a check
async def check():
body = CheckRequest(
tuple_key=TupleKey(
user="user:anne",
relation="writer",
object="document:2021-budget",
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
response = await fga_client_instance.check(body)
# response.allowed = true
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"writer","object":"document:2021-budget"}}'
# Response: {"allowed":true}
check(
user = "user:anne", // check if the user `user:anne`
relation = "writer", // has an `writer` relation
object = "document:2021-budget", // with the object `document:2021-budget`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
02. Organization Permissions
Google Drive allows you to share a file with everyone in your organization as a viewer, commenter or writer/editor.
At the end of this section we want to end up with the following permissions represented:
To add support for domains and members all we need to do is add this object to the OpenFGA authorization model. In addition, update the model to allow domain member to be assigned to document:
- DSL
- JSON
model
schema 1.1
type user
type document
relations
define owner: [user,domain#member]
define writer: [user,domain#member] or owner
define commenter: [user,domain#member] or writer
define viewer: [user,domain#member] or commenter
type domain
relations
define member: [user]
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"owner": {
"this": {}
},
"writer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "owner"
}
}
]
}
},
"commenter": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "writer"
}
}
]
}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "commenter"
}
}
]
}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"commenter": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
}
}
}
},
{
"type": "domain",
"relations": {
"member": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
Objects of type "domain" have users related to them as "member" if they belong to the userset of all users related to the domain as "member".
In other words, users can be direct members of a domain.
Let's now create a domain, add members to it and make all members viewers of document:2021-budget.
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
await fgaClient.write({
writes: {
tuple_keys: [
// make anne, beth, charles a member of the xyz domain
{ user: 'user:anne', relation: 'member', object: 'domain:xyz'},
{ user: 'user:beth', relation: 'member', object: 'domain:xyz'},
{ user: 'user:charles', relation: 'member', object: 'domain:xyz'},
// make members of xyz domain viewers of document:2021-budget
{ user: 'domain:xyz#member', relation: 'viewer', object: 'document:2021-budget'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.WriteRequest{
Writes: &fgaSdk.TupleKeys{
TupleKeys: []fgaSdk.TupleKey {
{
// make anne, beth, charles a member of the xyz domain
User: fgaSdk.PtrString("user:anne"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("domain:xyz"),
},
{
User: fgaSdk.PtrString("user:beth"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("domain:xyz"),
},
{
User: fgaSdk.PtrString("user:charles"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("domain:xyz"),
},
{
// make members of xyz domain viewers of document:2021-budget
User: fgaSdk.PtrString("domain:xyz#member"),
Relation: fgaSdk.PtrString("viewer"),
Object: fgaSdk.PtrString("document:2021-budget"),
},
},
},
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw")}
_, response, err := fgaClient.OpenFgaApi.Write(context.Background()).Body(body).Execute()
if err != nil {
// .. Handle error
}
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
await fgaClient.Write(new WriteRequest{
Writes = new TupleKeys(new List<TupleKey>() {
// make anne, beth, charles a member of the xyz domain
new() { User = "user:anne", Relation = "member", Object = "domain:xyz" },
new() { User = "user:beth", Relation = "member", Object = "domain:xyz" },
new() { User = "user:charles", Relation = "member", Object = "domain:xyz" },
// make members of xyz domain viewers of document:2021-budget
new() { User = "domain:xyz#member", Relation = "viewer", Object = "document:2021-budget" }
}),
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.tuple_keys import TupleKeys
# from openfga_sdk.models.write_request import WriteRequest
async def write():
body = WriteRequest(
writes=TupleKeys(
tuple_keys=[
TupleKey(
# make anne, beth, charles a member of the xyz domain
user="user:anne",
relation="member",
object="domain:xyz",
),
TupleKey(
user="user:beth",
relation="member",
object="domain:xyz",
),
TupleKey(
user="user:charles",
relation="member",
object="domain:xyz",
),
TupleKey(
# make members of xyz domain viewers of document:2021-budget
user="domain:xyz#member",
relation="viewer",
object="document:2021-budget",
),
],
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
await fga_client_instance.write(body)
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"user:anne","relation":"member","object":"domain:xyz"},{"user":"user:beth","relation":"member","object":"domain:xyz"},{"user":"user:charles","relation":"member","object":"domain:xyz"},{"user":"domain:xyz#member","relation":"viewer","object":"document:2021-budget"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
write([
// make anne, beth, charles a member of the xyz domain
{
"user":"user:anne",
"relation":"member",
"object":"domain:xyz"
},
{
"user":"user:beth",
"relation":"member",
"object":"domain:xyz"
},
{
"user":"user:charles",
"relation":"member",
"object":"domain:xyz"
},
// make members of xyz domain viewers of document:2021-budget
{
"user":"domain:xyz#member",
"relation":"viewer",
"object":"document:2021-budget"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
The last relationship tuple introduces a new OpenFGA concept. A userset. When the value of a user is formatted like this objectType:objectId#relation, OpenFGA will automatically expand the userset into all its individual user identifiers:
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:charles',
relation: 'viewer',
object: 'document:2021-budget',
},
});
// allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
TupleKey: fgaSdk.TupleKey{
User: "user:charles",
Relation: "viewer",
Object: "document:2021-budget",
},
}
data, response, err := fgaClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: true }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest
{
TupleKey = new TupleKey() {
User = "user:charles",
Relation = "viewer",
Object = "document:2021-budget"
}, AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"});
// response.Allowed = true
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.check_request import CheckRequest
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
# Run a check
async def check():
body = CheckRequest(
tuple_key=TupleKey(
user="user:charles",
relation="viewer",
object="document:2021-budget",
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
response = await fga_client_instance.check(body)
# response.allowed = true
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:charles","relation":"viewer","object":"document:2021-budget"}}'
# Response: {"allowed":true}
check(
user = "user:charles", // check if the user `user:charles`
relation = "viewer", // has an `viewer` relation
object = "document:2021-budget", // with the object `document:2021-budget`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
03. Folder Permission Propagation
Permission propagation happens between folders and files: if you are a viewer in a folder, you can view its documents. This applies even when you are not explicitly a viewer in a document.
At the end of this section we want to end up with the following permissions represented. Note that a folder is an object in the document type, as we do not need a separate type:
We need to add the notion that a document can be the parent of another document. We know how to do that:
- DSL
- JSON
model
schema 1.1
type user
type document
relations
define parent: [document]
define owner: [user,domain#member]
define writer: [user,domain#member] or owner
define commenter: [user,domain#member] or writer
define viewer: [user,domain#member] or commenter
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"parent": {
"this": {}
},
"owner": {
"this": {}
},
"writer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "owner"
}
}
]
}
},
"commenter": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "writer"
}
}
]
}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "commenter"
}
}
]
}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "document"
}
]
},
"owner": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"commenter": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
}
}
}
}
]
}
Notice the newly added "parent" relation in the configuration above.
We can indicate this relation by adding the following relationship tuples
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
await fgaClient.write({
writes: {
tuple_keys: [
// Diane is a viewer of document:2021-planning
{ user: 'user:diane', relation: 'viewer', object: 'document:2021-planning'},
// document:2021-planning is a parent of document:2021-budget
{ user: 'document:2021-planning', relation: 'parent', object: 'document:2021-budget'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.WriteRequest{
Writes: &fgaSdk.TupleKeys{
TupleKeys: []fgaSdk.TupleKey {
{
// Diane is a viewer of document:2021-planning
User: fgaSdk.PtrString("user:diane"),
Relation: fgaSdk.PtrString("viewer"),
Object: fgaSdk.PtrString("document:2021-planning"),
},
{
// document:2021-planning is a parent of document:2021-budget
User: fgaSdk.PtrString("document:2021-planning"),
Relation: fgaSdk.PtrString("parent"),
Object: fgaSdk.PtrString("document:2021-budget"),
},
},
},
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw")}
_, response, err := fgaClient.OpenFgaApi.Write(context.Background()).Body(body).Execute()
if err != nil {
// .. Handle error
}
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
await fgaClient.Write(new WriteRequest{
Writes = new TupleKeys(new List<TupleKey>() {
// Diane is a viewer of document:2021-planning
new() { User = "user:diane", Relation = "viewer", Object = "document:2021-planning" },
// document:2021-planning is a parent of document:2021-budget
new() { User = "document:2021-planning", Relation = "parent", Object = "document:2021-budget" }
}),
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.tuple_keys import TupleKeys
# from openfga_sdk.models.write_request import WriteRequest
async def write():
body = WriteRequest(
writes=TupleKeys(
tuple_keys=[
TupleKey(
# Diane is a viewer of document:2021-planning
user="user:diane",
relation="viewer",
object="document:2021-planning",
),
TupleKey(
# document:2021-planning is a parent of document:2021-budget
user="document:2021-planning",
relation="parent",
object="document:2021-budget",
),
],
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
await fga_client_instance.write(body)
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"user:diane","relation":"viewer","object":"document:2021-planning"},{"user":"document:2021-planning","relation":"parent","object":"document:2021-budget"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
write([
// Diane is a viewer of document:2021-planning
{
"user":"user:diane",
"relation":"viewer",
"object":"document:2021-planning"
},
// document:2021-planning is a parent of document:2021-budget
{
"user":"document:2021-planning",
"relation":"parent",
"object":"document:2021-budget"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
What we still lack is the ability to propagate permissions from parent to children. We want to say that a user is a viewer of a document if either:
- [done] they have a viewer relationship (directly or through domain membership)
- [pending] they have a viewer relationship with the parent document
We need a way to consider the parent viewers, not just direct viewers of the document when getting a check for:
- Node.js
- Go
- .NET
- Python
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});
// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:diane',
relation: 'viewer',
object: 'document:2021-budget',
},
});
// allowed = undefined
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
fgaSdk "github.com/openfga/go-sdk"
"os"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
ApiScheme: os.Getenv("FGA_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost: os.Getenv("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
TupleKey: fgaSdk.TupleKey{
User: "user:diane",
Relation: "viewer",
Object: "document:2021-budget",
},
}
data, response, err := fgaClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: undefined }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Api;
using OpenFga.Sdk.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new Configuration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"), // Either "http" or "https", defaults to "https"
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"), // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
};
var fgaClient = new OpenFgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest
{
TupleKey = new TupleKey() {
User = "user:diane",
Relation = "viewer",
Object = "document:2021-budget"
}, AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"});
// response.Allowed = undefined
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import os
import json
import openfga_sdk
from openfga_sdk.api import open_fga_api
configuration = openfga_sdk.Configuration(
api_scheme = os.environ.get('FGA_API_SCHEME'), # Either "http" or "https", defaults to "https"
api_host = os.environ.get('FGA_API_HOST'), # required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
store_id = os.environ.get('FGA_STORE_ID') # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
)
# Create an instance of the API class
fga_client_instance = open_fga_api.OpenFgaApi(openfga_sdk.ApiClient(configuration))
# from openfga_sdk.models.check_request import CheckRequest
# from openfga_sdk.models.tuple_key import TupleKey
# from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
# Run a check
async def check():
body = CheckRequest(
tuple_key=TupleKey(
user="user:diane",
relation="viewer",
object="document:2021-budget",
),
authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw",
)
response = await fga_client_instance.check(body)
# response.allowed = undefined
Get the Bearer Token and set up the FGA_API_URL environment variable
Set FGA_API_URL according to the service you are using
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:diane","relation":"viewer","object":"document:2021-budget"}}'
# Response: {"allowed":undefined}
check(
user = "user:diane", // check if the user `user:diane`
relation = "viewer", // has an `viewer` relation
object = "document:2021-budget", // with the object `document:2021-budget`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: undefined
More details on this technique can be found in the section Modeling Parent-Child Objects.
We express it like this:
- DSL
- JSON
define schema_version:
{
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "commenter"
}
},
{
"tupleToUserset": {
"tupleset": {
"relation": "parent"
},
"computedUserset": {
"relation": "viewer"
}
}
}
]
}
}
}
The users with a viewer relationship to a certain object of type "document" are any of:
- the "viewers": the set of users who are directly related to the document as a "viewer"
- the "commenters": the set of users who are related to the object as "commenter"
- the "viewers of the parents": from the objects who are related to the doc as parent, return the sets of users who are related to those objects as "viewer"
What the added section is doing is:
- read all relationship tuples related to document:2021-budget as parent which returns:
[{ "object": "document:2021-budget", "relation": "parent", "user": "document:2021-planning" }]
- for each relationship tuple read, return all usersets that match the following, returning tuples of shape:
{ "object": "document:2021-planning", "viewer", "user": ??? }
including: { "object": "document:2021-planning", "viewer", "user": "user:diane" }
The updated authorization model looks like this:
- DSL
- JSON
model
schema 1.1
type user
type document
relations
define owner: [user,domain#member] or owner from parent
define writer: [user,domain#member] or owner or writer from parent
define commenter: [user,domain#member] or writer or commenter from parent
define viewer: [user,domain#member] or commenter or viewer from parent
define parent: [document]
type domain
relations
define member: [user]
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"owner": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "owner"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"writer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "owner"
}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "writer"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"commenter": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "writer"
}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "commenter"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "commenter"
}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "viewer"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"parent": {
"this": {}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "document"
}
]
},
"owner": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"commenter": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
}
}
}
},
{
"type": "domain",
"relations": {
"member": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
04. Sharing Files And Folders Publicly
Google Drive has a feature which allows sharing a file or folder publicly, and specifying the permissions a public user might have (writer/commenter/viewer).
Assume that Anne
has created a new document: 2021-public-roadmap
, has shared it with commenter permissions to the xyz.com
, and has shared it as view only with the public at large.
Here's where another OpenFGA feature, type bound public access (as in everyone), would come in handy.
First, we will need to update our model to allow for public access with type user
for viewer relation.
- DSL
- JSON
model
schema 1.1
type user
type document
relations
define owner: [user,domain#member] or owner from parent
define writer: [user,domain#member] or owner or writer from parent
define commenter: [user,domain#member] or writer or commenter from parent
define viewer: [user,user:*,domain#member] or commenter or viewer from parent
define parent: [document]
type domain
relations
define member: [user]
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"owner": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "owner"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"writer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "owner"
}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "writer"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"commenter": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "writer"
}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "commenter"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "commenter"
}
},
{
"tupleToUserset": {
"computedUserset": {
"relation": "viewer"
},
"tupleset": {
"relation": "parent"
}
}
}
]
}
},
"parent": {
"this": {}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "document"
}
]
},
"owner": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"commenter": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "user",
"wildcard": {}
},
{
"type": "domain",
"relation": "member"
}
]
}
}
}
},
{