Skip to main content

Introduction

An authorization policy (or simply, a policy) is expressed in a declarative language called rego.

A policy can include one or more rego files, and one or more JSON data files.

A rego file has a package directive which sets the namespace for the rego file, and defines one or more decisions.

package sample.GET.api.orders

allowed {
true
}
note

In the rego file above, the package name follows the Topaz convention:

policy-root.HTTP method.HTTP route

Decision

A decision is an output from the evaluation of a policy. The policy above exports a decision called allowed, and sets its value to true.

The policy below exports the same decision (allowed), has a default value of false for this decision, and a rule that sets allowed to true only if the logged-in users's department property is equal to "Sales":

package sample.GET.api.orders

default allowed = false

allowed {
input.user.properties.department == "Sales"
}

This is known as attribute-based access control, or ABAC, because the department property is an attribute of the user.

User context

The Topaz authorization APIs take an identity context, which allows the caller to pass in the identity of the user as a JWT or a Subject. Topaz will automatically resolve the user and make it available as input.user, so that the policy can access any of the user's properties.

In the policy above, we were able to use the department property to help compute the allowed decision. This is an example of User context that is used in a policy. If you import your identities from your identity provider, any properties they contain about users will be available to your policy.

In addition, you can add properties on your objects in an extensible JSON property bag, and use these properties in authorization decisions.

Resource context

Policies can also use resource context in their evaluation process. If we added the allowed clause below to our policy, and the caller included a resource context that contains { "user_id": "<the-user-id>" }, the policy would evaluate allowed to true if the user id and the resource user_id match.

allowed {
input.user.id == input.resource.user_id
}

Objects

The directory can store any object, and if you pass in an id in your resource context, you may want to load the object (with its properties) based on this id.

To do this you can use the ds.object built-in. Objects are resolved based on their object_type and object_id value.

You don't need to do this for the user, since Topaz automatically does it for you. But if you wanted to load a department resource based on an id passed in the resource context, you would do it as follows:

user = ds.object({ "object_type": "department", "object_id": input.resource.id })

Check built-in

The ds.check built-in allows you to query the Topaz directory for a relation or permission between an object an a subject.

allowed {
ds.check({
"object_type": "department",
"object_id": input.resource.id,
"relation": "owner",
"subject_type": "user",
"subject_id": input.user.id
})
}

The example above will check whether the user represented in the identity context is a department owner of the department object with the id passed in as input.resource.id.

We can also use the ds.check built-in to check if the object has a permission with the subject.

allowed {
ds.check({
"object_type": "department",
"object_id": input.resource.id,
"relation": "can-read",
"subject_type": "user",
"subject_id": input.user.id
})
}

Example policy

The policy below is a sample policy that will take advantage of the relation and permission built-ins to check if a user has permission to delete a department. It will also combine an ABAC rule to check if the user is a member of the "IT" department. The resulting policy will be true if either of these conditions is met.

package sample.DELETE.api.departments

default allowed = false

## allow if the department property of the user is "IT"
allowed {
input.user.properties.department == "IT"
}

## allow if the user has the "can-delete" permission on the department identified by "id"
allowed {
ds.check({
"object_type": "department",
"object_id": input.resource.id,
"relation": "can-delete",
"subject_type": "user",
"subject_id": input.user.id
})
}

To combine the rules so that BOTH must be true for the allowed decision to evaluate to true, you can put them in the same allowed block: these conditions are AND-ed together.

## allow if BOTH conditions are true
allowed {
input.user.properties.department == "IT"
ds.check({
"object_type": "department",
"object_id": input.resource.id,
"relation": "can-delete",
"subject_type": "user",
"subject_id": input.user.id
})
}