Learn by doing

Here are a collection of practice-oriented guides for using Oso.

Each guide is focused around implementing specific functionality in your application.

Build Role-Based Access Control (RBAC)

Build role-based access control (RBAC) with Oso's built-in authorization modeling features.

Filter Data
Filter Data This guide shows you how to use Data Filtering in the Oso Library. Data filtering lets you select certain data from your data store, based on the logic in your policy. In the Oso Library, data filtering works by telling Oso how to turn Polar constraints into queries against your data store, such as SQL queries or ORM query objects. If you’re using Oso Cloud as an authorization service, data filtering is built in. Read about how to list authorized resources using the Oso Cloud API. Why do you need Data Filtering? When you call authorize(actor, action, resource) , Oso evaluates the allow rule(s) you have defined in your policy to determine if actor is allowed to perform action on resource. For example, if jane wants to "edit" a document, Oso may check that jane = document.owner. But what if you need the set of all documents that Jane is allowed to edit? For example, you may want to render them as a list in your application. One way to answer this question is to take every document in the system and call is_allowed on it. This isn’t efficient and many times is just impossible. There could be thousands of documents in a database but only three that have the owner "steve". Instead of fetching every document and passing it into Oso, it’s better to ask the database for only the documents that have the owner "steve". Oso provides a “data filtering” API to do this. You can use data filtering to enforce authorization on queries made to your data store. Oso will take the logic in the policy and turn it into a query for the authorized data. Examples could include an ORM filter object, an HTTP request or an elastic-search query. The query object and the way the logic maps to a query are both user defined. Data filtering is initiated through two methods on Oso. authorizedResources returns a list of all the resources a user is allowed to do an action on. The results of a built and executed query. authorizedQuery returns the query object itself. This lets you add additional filters or sorts or any other data to it before executing it. The mapping from Polar to a query is defined by an Adapter. If an adapter exists for your ORM or database you can use it, otherwise you may have to implement your own. Implementing an Adapter Adapters An adapter is an interface that defines two methods. Once you’ve defined an adapter, you can configure your Oso instance to use it with the setDataFilteringAdapter method. Build a Query buildQuery takes some type information and a Filter object and returns a Query. A Filter is a representation of a query. It is very similar to a SQL query. It has four fields: model Is the name of the type we are filtering. relations Are named relations to other types, typically turned into joins. conditions Are the individual pieces of logic that must be true with respect to objects matching the filter. These typically get turned into where clauses. types Is a map from type names to user type information, including registered relations. We use this to generate the join SQL. Relations A relation has three properties: fromTypeName, FilterRelation, and toTypeName. The adapter uses these properties to look up the tables and fields to join together for the query. Conditions A condition has three properties lhs, cmp, and rhs. The left and right fields will be either Immediate objects with a value field that can be inserted directly into a query, or Projection objects with string properties typeName and optionally fieldName. A missing fieldName property indicates the adapter should substitute an appropriate unique identifier, usually a primary key. Execute a Query executeQuery takes a query and returns a list of the results. Fields The other thing you have to provide to use data filtering is type information for registered classes. This lets Oso know what the types of an object’s fields are. Oso needs this information to handle specializers and other things in the policy when we don’t have a concrete resource. The fields are a object from field name to type. Relations Often you need data that is not contained on the object to make authorization decisions. This comes up when the role required to do something is implied by a role on it’s parent object. For instance, you want to check the organization for a repository but that data isn’t embedded on the repository object. You can add a Relation type to the type definition that states how the other resource is related to this one. Then you can access this field in the policy like any other field and it will fetch the data when it needs it (via the query functions). Relations are a special type that tells Oso how one Class is related to another. They specify what the related type is and how it’s related. kind is either “one” or “many”. “one” means there is one related object and “many” means there is a list of related objects. other_type is the class of the related objects. my_field Is the field on this object that matches other_field. other_field Is the field on the other object that matches this_field. The my_field / other_field relationship is similar to a foreign key. It lets Oso know what fields to match up with building a query for the other type. Example data_filtering_example_b.ts import { Relation, Oso, ForbiddenError, NotFoundError } from "oso"; import { createConnection, In, Not, Entity, PrimaryGeneratedColumn, Column, PrimaryColumn, JoinColumn, ManyToOne } from "typeorm"; import { readFileSync } from "fs"; import * as assert from 'assert'; import { typeOrmAdapter } from 'oso/dist/src/typeOrmAdapter'; @Entity() class Repository { @PrimaryColumn() id: string; @Column() org_id: string; } @Entity() class User { @PrimaryColumn() id: string; } @Entity() class RepoRole { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() repo_id: string; @Column() user_id: string; } @Entity() class Organization { @PrimaryColumn() id: string; } @Entity() class OrgRole { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() user_id: string; @Column() org_id: string; } data_filtering_example_b.ts async function test() { const connection = await createConnection({ type: 'sqlite', database: ':memory:', entities: [User, Repository, RepoRole, Organization, OrgRole], synchronize: true, logging: false, }); const oso = new Oso(); oso.setDataFilteringAdapter(typeOrmAdapter(connection)); oso.registerClass(Repository, { fields: { id: String, organization: new Relation("one", "Organization", "org_id", "id"), } }); oso.registerClass(Organization, { fields: { id: String, repos: new Relation("many", "Repository", "id", "org_id"), } }); oso.registerClass(User, { fields: { id: String, repo_roles: new Relation("many", "RepoRole", "id", "user_id"), org_roles: new Relation("many", "OrgRole", "id", "user_id") } }); oso.registerClass(RepoRole, { fields: { id: Number, user: new Relation("one", "User", "user_id", "id"), repo: new Relation("one", "Repository", "repo_id", "id") } }); oso.registerClass(OrgRole, { fields: { id: Number, user: new Relation("one", "User", "user_id", "id"), organization: new Relation("one", "Organization", "org_id", "id") } }); policy_b.polar actor User {} resource Organization { permissions = ["add_member", "read", "delete"]; roles = ["member", "owner"]; "add_member" if "owner"; "delete" if "owner"; "member" if "owner"; } # Anyone can read. allow(_, "read", _org: Organization); resource Repository { permissions = ["read", "push", "delete"]; roles = ["contributor", "maintainer", "admin"]; relations = { parent: Organization }; "read" if "contributor"; "push" if "maintainer"; "delete" if "admin"; "maintainer" if "admin"; "contributor" if "maintainer"; "contributor" if "member" on "parent"; "admin" if "owner" on "parent"; } has_relation(organization: Organization, "parent", repository: Repository) if repository.organization = organization; has_role(user: User, role_name: String, repository: Repository) if role in user.repo_roles and role.name = role_name and role.repo_id = repository.id; has_role(user: User, role_name: String, organization: Organization) if role in user.org_roles and role.name = role_name and role.org_id = organization.id; allow(actor, action, resource) if has_permission(actor, action, resource); data_filtering_example_b.ts oso.loadFiles(["policy_b.polar"]); const orgs = connection.getRepository(Organization), users = connection.getRepository(User), repos = connection.getRepository(Repository), roles = connection.getRepository(OrgRole); await orgs.save({ id: 'osohq' }); await orgs.save({ id: 'apple' }); await repos.save({ id: 'ios', org_id: 'apple' }); await repos.save({ id: 'oso', org_id: 'osohq' }); await repos.save({ id: 'demo', org_id: 'osohq' }); await users.save({ id: 'leina' }); await users.save({ id: 'steve' }); await roles.save({ user_id: 'leina', org_id: 'osohq', name: 'owner' }); // for sorting results const compare = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0; repos.findByIds(['oso', 'demo']).then(repos => users.findOne({ id: 'leina' }).then(leina => oso.authorizedResources(leina, 'read', Repository).then(result => assert.deepEqual(result.sort(compare), repos.sort(compare))))); } test() Evaluation When Oso is evaluating data filtering methods it uses the adapter to build queries and execute them. Relation fields also work when you are not using data filtering methods are also use the adapter to query for the related resources when you access them. Limitations Some Polar operators including cut and arithmetic operators aren’t supported in data filtering queries. You can’t call any methods on the resource argument or pass the resource as an argument to other methods. Many cases where you would want to do this are better handled by Relation fields. The new data filtering backend doesn’t support queries where a given resource type occurs more than once, so direct or indirect relations from a type to itself are currently unsupported. This limitation will be removed in an upcoming release.
Enforce an Oso Policy
Enforce an Oso Policy To use an Oso policy in your app, you’ll need to “enforce” it. A policy is useless without an app that consults the policy on user actions. For most apps, policies can be enforced on multiple “levels”: Resource-level: is the user allowed to perform this action on a particular resource? Field-level: which fields on this object can the user read? Which ones can they update? Request-level: should this user even be able to hit this endpoint, regardless of the resources it involves? Oso provides an API to enforce authorization at all levels, each of which are described in this guide. An Oso instance provides the following methods to enforce to make it easy to enforce your policy in a number of situations: authorize(actor, action, resource) : Ensure that an actor can perform an action on a certain resource. Read about resource-level enforcement. authorizeRequest(actor, request) : Ensure that an actor is allowed to access a certain endpoint. Read about request-level enforcement. authorizeField(actor, action, resource, field) : Ensure that a actor can perform a particular action on one field of a given resource. Read about field-level enforcement. authorizedActions(actor, resource) : List the actions that actor is allowed to take on resource. authorizedFields(actor, action, resource) : List the fields that actor is allowed to perform action upon. We recommend starting out by reading about resource-level enforcement.
Write Rules

Learn about writing Oso policies - the source of truth for authorization logic.

Authorize Across Services

If you want to start diving into how to handle authorization in two or thousands of services learn more about Oso Cloud. Our latest creation makes authorization across services as easy as oso.authorize(user, action, resource).

More

Read more guides here

Connect with us on Slack

If you have any questions, or just want to talk something through, jump into Slack. An Oso engineer or one of the thousands of developers in the growing community will be happy to help.