Skip to main content

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 and relations attributes.
  • Relations can be granted explicitly or implicitly via a combination of the inheritIf, ofType, and withRelation properties.
  • The inheritIf property can be anyOf, allOf, or noneOf 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.