Object Types
Object types are the basic building block of any authorization scheme in Warrant. Represented as JSON, each object type defines a resource type (ex. tenant) in an application along with the relationships that can exist between that resource and others in the application (ex. users). Object types are an incredibly flexible way to define authorization models, even allowing you to express complex hierarchical and inherited relationships.
Object types can be created directly in the Warrant dashboard or via the Object Types API.
In this overview, we'll explain the key attributes of object types by modeling a simple eCommerce application with three object types: user, store, and item.
Type
The first attribute of an object type is its type
. Each object type must have a unique string as its type. In our eCommerce example:
{
"type": "user"
},
{
"type": "store"
},
{
"type": "item"
}
Relations
By defining these three object types, we're building a system that will allow us to create fine-grained authorization rules for stores, items, and users that can help us answer questions like:
Does [user:1] have access to [edit] [item:x]?
is [user:1] an [owner] of [store:3]?
In addition to the object types themselves, we need to define relations (such as 'editor' and 'owner') that can exist on each object type.
In our app, stores can have owners
, editors
, and viewers
. owners
and editors
have more privileged access (ex. being able to modify details about a store) than viewers
(who have read-only access).
Items have the same three relations as stores plus a fourth relation called parent
. A store can be the parent
of an item, meaning that the item belongs to that store. We'll use this relation to implement inherited privileges later.
Lastly, our user object type is relatively simple and has one parent
relation to enable user hierarchies (similar to items).
Let's add these relations to our object types:
{
"type": "user",
"relations": {
"parent": {}
}
},
{
"type": "store",
"relations": {
"owner": {},
"editor": {},
"viewer": {}
}
},
{
"type": "item",
"relations": {
"parent": {},
"owner": {},
"editor": {},
"viewer": {}
}
}
With these object types, we can create authorization rules that specify exactly which users are owners
, editors
, and viewers
of each store and item. We can also assign stores as parents
of items.
Basic Rules
By default, relations like the ones we defined above ("owner": {}
, "editor": {}
, etc.) specify that the given relation must be explicitly granted in a warrant for the user to have that privilege. Explicitly defining every relation as a warrant like this is useful in some scenarios but can be tedious in others. That's why every relation can also specify rules that implicitly grant the relation if fulfilled. Rules can specify other relations that can implicitly grant a relation, and some rules can even be nested. There are 5 types of rules: userset, object userset, anyOf, allOf, and noneOf. Userset and object userset rules are the basic rule types that all other rules are composed of. The anyOf, allOf, and noneOf rule types are a way to compose a set of rules to be fulfilled using set operations.
Relation Inheritance
Hierarchical Relations
In practice, it's common for relations to have a hierarchy and for one relation to be implied if a higher level relaton is assigned. For example, a user with write privileges often implicitly has read privileges too. In our example application, an owner
is also an editor
and a viewer
, and an editor
is also a viewer
. Object types allow you to specify this type of relation hierarchy (i.e. the editor
relation is implicitly granted if the owner
relation is explicitly granted) using the inheritIf
property. Let's add rules to our store
and item
object types specifying that owners are editors
and editors are viewers
:
{
"type": "user",
"relations": {
"parent": {}
}
},
{
"type": "store",
"relations": {
"owner": {},
"editor": {
"inheritIf": "owner"
},
"viewer": {
"inheritIf": "editor"
}
}
},
{
"type": "item",
"relations": {
"parent": {},
"owner": {},
"editor": {
"inheritIf": "owner"
},
"viewer": {
"inheritIf": "editor"
}
}
}
Inheriting Relations from Another Object
In many applications, resources have their own hierarchy (i.e. a document belongs to a folder) and the access rules for these resources must follow that hierarchy. With the ofType
and withRelation
properties, you can specify that a relation be implied when another object has a particular relation on an object (i.e. an editor
of a folder is also an editor
of any document belonging to that folder). In our example, let's say that we now want to define that an editor
of a store is also an editor
of all the items that store is a parent
of. We can specify that a user is an editor
of an item if they are an editor
of a store that is a parent
of that item:
{
"type": "user",
"relations": {
"parent": {}
}
},
{
"type": "store",
"relations": {
"owner": {},
"editor": {
"inheritIf": "owner"
},
"viewer": {
"inheritIf": "editor"
}
}
},
{
"type": "item",
"relations": {
"parent": {},
"owner": {},
"editor": {
"inheritIf": "anyOf",
"rules": [
{
"inheritIf": "owner"
},
{
"inheritIf": "editor",
"ofType": "store",
"withRelation": "parent"
}
]
},
"viewer": {
"inheritIf": "editor"
}
}
}
The inheritIf
, typeOf
, and withRelation
properties make it easy to define complex relationships between objects without having to create a large number of explicit access rules. With them, we'd need to create an access rule for every item ↔ store ↔ user combination in our application which could easily be thousands, if not hundreds of thousands of rules.
Set Rules
While the ability to define hierarchical relations and inherit relations from other objects is incredibly powerful, we're still limited in the types of authorization schemes they can model. That's why the inheritOf
can also specify set rules which allow you to compose a set of rules together to form more complex rules. The three available set rule types are anyOf
, allOf
, and noneOf
.
anyOf
The anyOf rule type allows you to specify a set of rules that are considered fulfilled if at least any one of the rules in the set is satisfied. In other words, the anyOf rule type works like the logical OR operation. The following object type specifies an editor-or-viewer
relation that is implicitly granted if the editor
OR the viewer
relation are present:
{
"type": "item",
"relations": {
"editor": {},
"viewer": {},
"editor-or-viewer": {
"inheritIf": "anyOf",
"rules": [
{
"inheritIf": "editor"
},
{
"inheritIf": "viewer"
}
]
}
}
}
allOf
The allOf rule type allows you to specify a set of rules that are considered fulfilled if all of the rules in the set are satisfied. In other words, the allOf rule type works like the logical AND operation. The following object type specifies an editor-and-viewer
relation that is implicitly granted if the editor
AND the viewer
relation are present:
{
"type": "item",
"relations": {
"editor": {},
"viewer": {},
"editor-and-viewer": {
"inheritIf": "allOf",
"rules": [
{
"inheritIf": "editor"
},
{
"inheritIf": "viewer"
}
]
}
}
}
noneOf
The noneOf rule type allows you to specify a set of rules that are considered fulfilled if none of the rules in the set are satisfied. In other words, the noneOf rule type works like the logical NOR operation. The following object type specifies an not-editor-and-not-viewer
relation that is implicitly granted if the editor
relation is not present AND the viewer
relation is not present:
{
"type": "item",
"relations": {
"editor": {},
"viewer": {},
"not-editor-and-not-viewer": {
"inheritIf": "noneOf",
"rules": [
{
"inheritIf": "editor"
},
{
"inheritIf": "viewer"
}
]
}
}
}
Built-in Object Types
By default, Warrant provides a core set of built-in object types that make it easy to implement well-defined, common authorization schemes like role based access control and multi-tenancy. These built-in types are:
We'll cover each of these built-in object types in detail in the next sections.
Summary
- Object types are composed of
type
andrelations
attributes. - Relations can be granted explicitly or implicitly via a combination of the
inheritIf
,ofType
, andwithRelation
properties. - The
inheritIf
property can beanyOf
,allOf
, ornoneOf
to compose rules together for more complex access control models. - Warrant provides several built-in object types like user, tenant, role, permission, feature, and pricing-tier that make it easy to setup common authorization use-cases.
Next, we'll dive into each of the built-in object types that Warrant provides.