What you will learn
- How to model an entitlement use case in OpenFGA
- How to start with a given set of requirements and scenarios and iterate on the OpenFGA model until those requirements are met
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. It would be helpful to have an understanding of some concepts of OpenFGA before you start.OpenFGA concepts
OpenFGA concepts
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 members of an org are subscriber members of the plan the org is subscriber to, and subscriber members of a plan get access to all the plan’s features.Direct relationships
You need to know how to disallow granting direct relation to an object and requiring the user to have a relation with another object that would imply a relation with the first one. Learn more →Used here to indicate that “access” to a feature cannot be directly granted to a user, but is implied through the users organization subscribing to a plan that offers that feature.Concepts & configuration language
What you will be modeling
In many product offerings, the features are behind multiple tiers. In this tutorial, you will build an authorization model for a subset of GitHub’s entitlements (detailed below) using OpenFGA. You will use some scenarios to validate the model.Note: For brevity, this tutorial will not model all of GitHub entitlements. Instead, it will focus on modeling for the scenarios outlined below
Requirements
You will model an entitlement system similar to GitHub’s, focusing on a few scenarios. GitHub has 3 plans: “Free”, “Team” and “Enterprise”, with each of them offering several features. The higher-priced plans include all the features of the lower priced plans. You will be focusing on a subset of the features offered. A summary of GitHub’s entitlement system:- Free
- Issues
- Team
- Everything from the free plan
- Draft Pull Requests
- Enterprise
- Everything from the team plan
- SAML Single Sign-On
Defined scenarios
Use the following scenarios to be able to validate whether the model of the requirements is correct.-
Take these three organizations
- Alpha Beta Gamma (
alpha), a subscriber on the free plan - Bayer Water Supplies (
bayer), a subscriber on the team plan - Cups and Dishes (
cups), a subscriber on the enterprise plan
- Alpha Beta Gamma (
-
Take these three users
- Anne, member of Alpha Beta Gamma
- Beth, member of Bayer Water Supplies
- Charles, member of Cups and Dishes
- Anne has access to Issues (expecting
yes) - Anne has access to Draft Pull Requests (expecting
no) - Anne has access to Single Sign-on (expecting
no) - Beth has access to Issues (expecting
yes) - Beth has access to Draft Pull Requests (expecting
yes) - Beth has access to Single Sign-on (expecting
no) - Charles has access to Issues (expecting
yes) - Charles has access to Draft Pull Requests (expecting
yes) - Charles has access to Single Sign-on (expecting
yes)
Modeling entitlements for GitHub
01. Building The Initial Authorization Model And Relationship Tuples
In this tutorial you are going to take a different approach to previous tutorials. You will start with a simple authorization model, add relationship tuples to represent some sample scenarios, and iterate until those scenarios return the results you expect. In the scenarios outlined above, you haveorganizations, plans and features.
Similar to the example above, start with a basic listing of the types and their relations:
- A
featurehas aplanassociated to it, we’ll call the relation between themassociated_plan - A
planhas an organization as asubscriberto it - An
organizationhas users asmembers
02. Populating the relationship tuples
Now you can add the relationship tuples to represent these relationships mentioned in the requirements and scenarios sections: The relations between the features and plans are as follows: The relations between the plans and the organizations are as follows: The relations between the organizations and the users are as follows: So far you have given OpenFGA a representation of the current state of your system’s relationships. You will keep iterating and updating the authorization model until the results of the queries match what you expect.Verification
Now that you have some data, you can start using it to ask is${USER} related to ${OBJECT} as ${RELATION}?
First, you will check if anne is a member of organization:alpha. This is one of the relationship tuples you previously added, you will make sure OpenFGA can detect a relation in this case.
Querying for relationship tuples that you fed into OpenFGA earlier should work, try a few before proceeding to make sure everything is working well.
03. Updating the authorization model
You are working towards OpenFGA returning the correct answer when you query whetheranne has access to feature:issues. It won’t work yet, but you will keep updating your configuration to reach that goal.
To start, try to run that query on is anne related to feature:issues as access?
The OpenFGA service is returning that the query tuple is invalid. That is because you are asking for relation as access, but that relation is not in the configuration of the feature type!
Add it now. Like so:
In this tutorial, you will find the phrases direct relationship and implied relationship.A direct relationship R between user X and object Y means the relationship tuple (user=X, relation=R, object=Y) exists, and the OpenFGA authorization model for that relation allows this direct relationship (by use of direct relationship type restrictions).An implied relationship R exists between user X and object Y if user X is related to an object Z that is in direct or implied relationship with object Y, and the OpenFGA authorization model allows it.
Adding modeling pattern of parent-child objects
Now we can ask the following query:is anne related to feature:issues as access? again.
So far so good. OpenFGA understood your query, but said that no relation exists. That is because according to the configuration provided so far, there is no access relation between anne and feature:issues.
We can also try to query is organization:alpha related to feature:issues as access? and we see that there is no relationship.
If you have already completed some of the other tutorials you might have encountered the modeling pattern of parent-child objects which is modeled as such:
With this, when asked to check a user’s
viewer relationship with the object, OpenFGA will:- Read all relationship tuples of users related to this particular object as relation
parent - For each relationship tuple, return all usersets that have
all_objects_viewerrelation to the objects in those relationship tuples - If the user is in any of those usersets, return yes, as the user is a
vieweron this object. In other words, users related asall_objects_viewerto any of this object’sparentsare related asviewerto this object.
Users related to
feature as access are the union of (any of):- the set of users with a direct
accessrelation - the set of users related to the
associated_planassubscriber(the feature’s associated plans’ subscribers)
access relation to a feature y, there needs to be either:
- a direct relationship via a relationship tuple:
e.g.
{ "user": "user:x", "relation": "access", "object": "feature:y" } - a subscriber relationship with another object related to x associated_plan:
e.g.
{ "user": "user:x", "relation": "subscriber", "object": "plan:z" } { "user": "plan:z", "relation": "associated_plan", "object": "feature:y" }
Adding Subscriber Relationship With Another Object Related To x associated_plan
One way forward would be to add a directaccess relation between a user and a feature e.g. { "user": "anne", "relation": "access", "object": "feature:y" } whenever the organization anne is subscribed to a plan, or the organization anne is in subscribes to a new plan.
But there are several downsides to this:
- Your application layer now needs to worry about computing this relationship. Instead of letting OpenFGA figure this all out, the app layer needs to do the checks whenever a user is being added or removed
- If an organization changes its subscription, your application layer has to loop through all the users and update their
accessrelationships to features accordingly
access relation completely, but for now you will make sure the changes to the store you have made so far are working.
Replace all the existing code you had previously with the updated authorization model from the below snippet.
Now we can ask following query: is organization:alpha related to feature:issues as access? again.
You will notice that OpenFGA now did find a relation, as organization:alpha is a subscriber to plan:free which has an associated_plan relation to feature:issues. From that and the authorization model you updated above, OpenFGA deduced that organization:alpha has an implied access relation to feature:issues.
That is good, but you want to be able to ask is anne related to feature:issues as access?, not is organization:alpha related to feature:issues as access?. As in, you want the subscriber members to have access to the feature, not the subscriber itself.
In order to do that, you will add a relation on the plan, that indicates that all members of an organization subscribed to it, have a subscriber_member relation to the plan. And you can modify the change you did above to give implied access to the subscriber_member instead of the subscriber. Like so:
Notice that
subscriber has been updated to subscriber_member in the access relation of the feature type.Under the plan type, in order for someone to have a subscriber_member relation to the plan, they have to be related as a member to the object related as a subscriber to the plan (as in they have to be a member of on of the plan’s subscribers).is anne related to feature:issues as access?
Disallow direct relationship
So far, with just a OpenFGA authorization model, and the initial relationship tuples indicating the relations you know, you configured OpenFGA to give you the correct response. Earlier on, the idea of not allowing a directaccess relation between a user and a feature was discussed, e.g. adding a relationship tuple like { "user": "user:anne", "relation": "access", "object": "feature:y" }. You will remove it now.
To disallow a direct relationship, you need to remove the direct relationship type restriction. The following snippet:
becomes
With this change, even if your app layer added the following relationship tuple:
{ "user": "user:anne", "relation": "access", "object": "feature:issues" }
is anne related to feature:issues as access? would return no relation. The only way for a relation to exist is if the following three relationship tuples do:
{ "user": "user:anne", "relation": "member", "object": "organization:z" }{ "user": "organization:z", "relation": "subscriber", "object": "plan:y" }{ "user": "plan:y", "relation": "associated_plan", "object": "feature:issues" }
Verification
Ensure that your authorization model matches the one below You will now verify that the configuration is correct by running checks for all the scenarios mentioned at the beginning of the tutorial:- Anne has access to Issues (expecting
yes) - Anne has access to Draft Pull Requests (expecting
no) - Anne has access to Single Sign-on (expecting
no) - Beth has access to Issues (expecting
yes) - Beth has access to Draft Pull Requests (expecting
yes) - Beth has access to Single Sign-on (expecting
no) - Charles has access to Issues (expecting
yes) - Charles has access to Draft Pull Requests (expecting
yes) - Charles has access to Single Sign-on (expecting
yes)
Summary
In this tutorial, you learned:- to model entitlements for a system in OpenFGA
- how to start with a set of requirements and scenarios and iterate on the OpenFGA authorization model until the checks match the expected scenarios
- how to model parent-child relationships to indicate that a user having a relationship with a certain object implies having a relationship with another object in OpenFGA
- how to use the union operator condition to indicate multiple possible paths for a relationship between two objects to be computed
- using direct relationship type restrictions in a OpenFGA authorization model, and how to block direct relationships by removing it