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.

userset

In practice, it's common for relations to overlap. For example, a user with write privileges often implicitly has read privileges as well. In our app, an owner is also an editor and a viewer, and an editor is also a viewer. A userset rule allows you to specify that a relation (ex. editor) can be inherited if another relation is explicitly granted (ex. owner). Let's define a rule specifying that owners are editors and editors are viewers in our store and item object types:

{
"type": "user",
"relations": {
"parent": {}
}
},
{
"type": "store",
"relations": {
"owner": {},
"editor": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "owner"
}
]
},
"viewer": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "editor"
}
]
}
}
},
{
"type": "item",
"relations": {
"parent": {},
"owner": {},
"editor": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "owner"
}
]
},
"viewer": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "editor"
}
]
}
}
}

objectUserset

An object userset rule allows you to define inherited relations between different object types. 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. Let's specify this inheritance by defining an object userset rule on the editor relation of the item object type:

{
"type": "user",
"relations": {
"parent": {}
}
},
{
"type": "store",
"relations": {
"owner": {},
"editor": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "owner"
}
]
},
"viewer": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "editor"
}
]
}
}
},
{
"type": "item",
"relations": {
"parent": {},
"owner": {},
"editor": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "owner"
},
{
"type": "objectUserset",
"relation": "parent",
"userset": {
"type": "userset",
"relation": "editor"
}
}
]
},
"viewer": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "editor"
}
]
}
}
}

Userset and object userset rules are very powerful, making it easier to define complex relationships without having to create a large number of explicit access rules. For example, if we couldn't define an object userset rule like above, 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 userset and object userset rules by themselves are incredibly powerful, they're limited in the types of authorization schemes they can model. That's why relations can also specify set rules which can compose a set of userset, object userset, and other set rules to form complex, custom 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.

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.

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.

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 rules (the five types of rules are userset, object userset, allOf, anyOf, noneOf)
  • Warrant provides several built-in object types like user, tenant, role, and permission to make it easier to define common authorization use-cases

Next, let's dive into each of the built-in object types that Warrant provides.