Internals
Oso is supported in a number of languages, but the Oso core is written in Rust, with bindings for each specific language.
At the core of Oso is the Polar language. This handles parsing policy files and executing queries in the form of a virtual machine. Oso was designed from the outset to be natively embedded in different languages. It exposes a foreign function interface (FFI) to allow the calling language to drive the execution of its virtual machine.
Oso can read files with the .polar
suffix, which are policy files written in Polar syntax.
These are parsed and loaded into a knowledge base, which can be thought of an
in-memory cache of the rules in the file.
Applications using Oso can tell it relevant information, for example registering classes to be used with policies, which are similarly stored in the knowledge base. The Oso implementation can now be seen as a bridge between the policy code and the application classes.
The Oso library is responsible for converting types between Oso primitive types
(like strings, numbers, and lists), and native application types (e.g. Python’s
str
, int
, and list
classes), as well as keeping track of instances
of application classes.
When executing a query like oso.query("allow", [user, "view", expense])
Oso creates a new virtual machine to execute the query.
The virtual machine executes as a coroutine with the native library, and
therefore your application. To make authorization decisions, your application
asks Oso a question: is this (actor, action, resource) triple allowed? To answer
the question, Oso may in turn ask questions of your application: What’s the
actor’s name? What’s their organization? What’s the resource’s id? etc. The
library provides answers by inspecting application data, and control passes back
and forth until the dialog terminates with a final “yes” or a “no” answer to the
original authorization question. The virtual machine halts, and the library
returns the answer back to your application as the authorization decision.
Data Filtering
Oso supports applying authorization logic at the ORM layer so that you can efficiently authorize entire data sets. For example, suppose you have millions of posts in a social media application created by thousands of users, and regular users are only authorized to view posts from their friends. It would be inefficient to fetch all of the posts and authorize them one by one. It would be much more efficient to distill from the policy a filter that can be applied by the ORM to return only the authorized posts. This idea can be used in any scenario where you need to authorize a subset of a large collection of data.
The Oso policy engine can now produce such filters from your policy.
How it works
Imagine the following authorization rule. A user is allowed to view any public social media posts as well as their own private posts:
allow(user, "view", post) if
post.access_level = "public" or
post.creator = user;
For a particular user, we can ask two fundamental questions in the context of the above rule:
- Is that user allowed to view a specific post, say,
Post{id: 1}
? - Which posts is that user allowed to view?
The answer to the first question is a boolean. The answer to the second is a
set of constraints that must hold in order for any Post
to be authorized.
Oso can produce such constraints through partial evaluation of a policy.
Instead of querying with concrete object (e.g., Post{id: 1}
), you can pass a
Partial
value, which signals to the engine that constraints should be
collected for it. A successful query for a Partial
value returns constraint
expressions:
_this.access_level = "public" or _this.creator.id = 1
Partial evaluation is a generic capability of the Oso engine, but making use of it requires an adapter that translates the emitted constraint expressions into ORM filters. Our first two supported adapters are for the Django and SQLAlchemy ORMs, with more on the way.
These adapters allow Oso to effectively translate policy logic into SQL WHERE
clauses:
WHERE access_level = "public" OR creator.id = 1
In effect, authorization is being enforced by the policy engine and the ORM cooperatively.
Alternative solutions
Partial evaluation is not the only way to efficiently apply authorization to
collections of data.
Manually applying WHERE
clauses to reduce the search space (or using
ActiveRecord-style
scopes)
requires additional application code and still needs to iterate over a
potentially large collection. Authorizing the filter to be applied (or having
Oso output the filter) doesn’t require iterating over individual records, but
it does force you to write policy over filters instead of over application
types, which can lead to more complex policies and is a bit of a leaky
abstraction.
Frameworks
To learn more about this feature and see usage examples, see our ORM specific documentation:
More framework integrations are coming soon — join us on Slack to discuss your use case or open an issue on GitHub.
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.