Ruby Client API
For changes to the Ruby Client API, see Changelog.
First, install the oso-cloud
gem from RubyGems:
gem install oso-cloud -v 1.10.0
Or add the following to your Gemfile
:
gem 'oso-cloud', '~> 1.10', '>= 1.10.0'
Before going through this guide, make sure you follow the Oso Cloud Quickstart to get your Oso API Key properly set in your environment.
Instantiating an Oso Cloud Client
The Oso Cloud client provides an OsoCloud::Oso
class that takes your Oso Cloud URL and API key:
require 'oso-cloud'oso = OsoCloud::Oso.new(url: "https://cloud.osohq.com", api_key: YOUR_API_KEY)# Later:oso.tell("has_role", user, role, resource)# Wherever authorization needs to be performed:if oso.authorize(user, action, resource) # Action is allowed
You should instantiate one client and share it across your application. Under the hood, it reuses connections to avoid paying the cost of negotiating a new connection on every request.
Specifying an Oso Fallback host
If you have deployed Oso Fallback nodes to your infrastructure, you may specify the host when instantiating the Oso Cloud client.
# Assumes Oso Fallback is hosted at http://localhost:8080oso = OsoCloud::Oso.new(url: "https://cloud.osohq.com", api_key: YOUR_API_KEY, fallback_url: "http://localhost:8080")
Passing application entities into the client
Under the hood, Oso Cloud represents an entity in your application as a combination of a type and an ID, which together uniquely identify the entity.
The Ruby client represents these entities using the OsoCloud::Value
class, a struct with both type
and id
properties.
For example:
alice = OsoCloud::Value.new(type: "User", id: "alice")anvilsRepository = OsoCloud::Value.new(type: "Repository", id: "anvils")
You will pass objects like these into nearly every function call you make to the Ruby client.
Centralized Authorization Data API
Oso Cloud clients provide an API to manage authorization data stored directly in Oso Cloud.
Add fact: oso.tell(name, *args)
Adds a fact named name
with the provided arguments. Example:
require 'oso-cloud'
api_key = ENV.fetch('OSO_CLOUD_API_KEY', nil)
oso = OsoCloud::Oso.new(url: "https://cloud.osohq.com", api_key: api_key)
oso.tell(
"has_role",
OsoCloud::Value.new(type: "User", id: "bob"),
"owner",
OsoCloud::Value.new(type: "Organization", id: "acme")
)
For Oso Cloud developer accounts, bulk_tell
, bulk_delete
, and bulk
calls
are limited to 20 facts. If you attempt to send more than 20 facts, these
functions will throw an error.
The bulk commands have a 10MB body limit, an exception will be thrown for exceeding this limit.
Add many facts: oso.bulk_tell([*[name, *args]])
Adds many facts at once. Example:
oso.bulk_tell([ [ "has_role", OsoCloud::Value.new(type: "User", id: "bob"), "owner", OsoCloud::Value.new(type: "Organization", id: "acme") ], [ "has_role", OsoCloud::Value.new(type: "User", id: "bob"), "maintainer", OsoCloud::Value.new(type: "Repository", id: "anvil") ]])
Delete fact: oso.delete(name, *args)
Deletes a fact. Does not throw an error if the fact is not found. Example:
oso.delete( "has_role", OsoCloud::Value.new(type: "User", id: "bob"), "maintainer", OsoCloud::Value.new(type: "Repository", id: "anvils"))
Delete many facts: oso.bulk_delete([*[name, *args]])
Deletes many facts at once. Does not throw an error when some of the facts are not found. Example:
oso.bulk_delete([ [ "has_role", OsoCloud::Value.new(type: "User", id: "bob"), "owner", OsoCloud::Value.new(type: "Organization", id: "acme") ], [ "has_role", OsoCloud::Value.new(type: "User", id: "bob"), "maintainer", OsoCloud::Value.new(type: "Repository", id: "anvils") ],])
Transactionally delete and add facts: oso.bulk(delete: [*facts], tell: [*facts])
Deletes and adds many facts in one atomic transaction.
The deletions are performed before the additions.
nil
can be used as a wildcard in facts in delete
.
Does not throw an error when the facts to delete are not found
or when the facts to add already exist.
Example:
oso.bulk(delete: [[ "has_role", OsoCloud::Value.new(type: "User", id: "bob"), nil, OsoCloud::Value.new(type: "Repo", id: "anvils") ]], insert: [[ "has_role", OsoCloud::Value.new(type: "User", id: "bob"), "member", OsoCloud::Value.new(type: "Repo", id: "anvils") ]])
Note the wildcard in the delete
section; using this, all has_role
facts
linking User:bob
and Repo:anvils
will be deleted.
List facts: oso.get(name, *args)
For Oso Cloud developer accounts, Get
calls are limited to 1000 results. If
you have more than 1000 facts, the function will throw an error.
Lists facts that are stored in Oso Cloud. Can be used to check the existence of a particular fact, or used to fetch all facts that have a particular argument:
# Get one fact:oso.get( "has_role", OsoCloud::Value.new(type: "User", id: "bob") "admin", OsoCloud::Value.new(type: "Repository", id: "anvils"))# => [[# "has_role",# OsoCloud::Value.new(type: "User", id: "bob"),# "admin",# OsoCloud::Value.new(type: "Repository", id: "anvils")# ]]# List all roles on the `anvils` repooso.get("has_role", nil, nil, OsoCloud::Value.new(type: "Repository", id: "anvils"))# => [# [# "has_role",# OsoCloud::Value.new(type: "User", id: "bob"),# "admin",# OsoCloud::Value.new(type: "Repository", id: "anvils")# ],# ... other has_role facts#]
Note that nil
behaves like a wildcard: passing nil, nil, anvils
means
"find all facts where anvils
is the third argument, regardless of other
arguments".
Check API
For Oso Cloud developer accounts, the number of context facts per request is limited to 20; and the number of records returned is limited to 1000.
Context facts
The Check API lets you provide context facts with each request. When Oso Cloud performs a check, it considers the request's context facts in addition to any other centralized authorization data. Context facts are only used in the API call in which they're provided-- they do not persist across requests.
For more details, see Context Facts.
Check a permission: oso.authorize(actor, action, resource)
Determines whether or not an action is allowed, based on a combination of authorization data and policy logic. Example:
alice = OsoCloud::Value.new(type: "User", id: "alice")anvils_repository = OsoCloud::Value.new(type: "Repository", id: "anvils")raise "Action is not allowed" unless oso.authorize(alice, "read", anvils_repository)
You may provide an array of context facts as an optional fourth argument to this method. Example:
issue_on_anvils_repository = OsoCloud::Value.new(type: "Repository", id: "anvils-1")oso.authorize(alice, "read", anvils_repository, [ ["has_relation", issue_on_anvils_repository, "parent", anvils_repository] # a context fact])
Check authorized resources: oso.authorize_resources(actor, action, resources)
Returns a subset of resources
on which an actor can perform a particular action.
Ordering and duplicates, if any exist, are preserved.
For Oso Cloud developer accounts, the number of input resources is limited to 1000.
Example:
alice = OsoCloud::Value.new(type: "User", id: "alice")anvils_repository = OsoCloud::Value.new(type: "Repository", id: "anvils")acme_repository = OsoCloud::Value.new(type: "Repository", id: "acme")resources = oso.authorize_resources(alice, "read", [anvils_repository, acme_repository])# => [acme_repository]
You may provide an array of context facts as an optional fourth argument to this method. Example:
issue_on_acme_repository = OsoCloud::Value.new(type: "Repository", id: "acme-1")issue_on_anvils_repository = OsoCloud::Value.new(type: "Repository", id: "anvils-2")oso.authorize_resources( alice, "read", [issue_on_anvils_repository, issue_on_acme_repository], [ # context facts ["has_relation", issue_on_anvils_repository, "parent", anvils_repository], ["has_relation", issue_on_acme_repository, "parent", acme_repository] ])# => [issue_on_acme_repository]
List authorized resources: oso.list(actor, action, resource_type)
Fetches a list of resource ids on which an actor can perform a particular action. Example:
alice = OsoCloud::Value.new(type: "User", id: "alice")oso.list(alice, "read", "Repository")# => ["acme"]
You may provide an array of context facts as an optional fourth argument to this method. Example:
anvils_repository = OsoCloud::Value.new(type: "Repository", id: "anvils")acme_repository = OsoCloud::Value.new(type: "Repository", id: "acme")issue_on_acme_repository = OsoCloud::Value.new(type: "Repository", id: "acme-1")issue_on_anvils_repository = OsoCloud::Value.new(type: "Repository", id: "anvils-2")oso.list( alice, "read", "Issue", [ # context facts ["has_relation", issue_on_anvils_repository, "parent", anvils_repository], ["has_relation", issue_on_acme_repository, "parent", acme_repository] ])# => ["acme-1"]
List authorized actions: oso.actions(actor, resource)
Fetches a list of actions which an actor can perform on a particular resource. Example:
alice = OsoCloud::Value.new(type: "User", id: "alice")acme_repository = OsoCloud::Value.new(type: "Repository", id: "acme")oso.actions(alice, acme_repository)# => ["read"]
You may provide an array of context facts as an optional third argument to this method. Example:
issue_on_acme_repository = OsoCloud::Value.new(type: "Repository", id: "acme-1")oso.actions( alice, issue_on_acme_repository, [ ["has_relation", issue_on_acme_repository, "parent", acme_repository] # a context fact ])# => ["read"]
Query for anything: oso.query(rule)
Query Oso Cloud for any predicate and any combination of concrete and wildcard arguments.
Unlike oso.get
, which only lists facts you've added, you can use oso.query
to list derived
information about any rule in your policy.
Example:
# Query for all the repos `User:bob` can `read`oso.query("allow", OsoCloud::Value.new(type: "User", id: "bob"), "read", OsoCloud::Value.new(type: "Repository"))# => [# [ "allow", #<struct OsoCloud::Value type="User", id="bob">, "read", #<struct OsoCloud::Value type="Repo", id="acme"> ],# [ "allow", #<struct OsoCloud::Value type="User", id="bob">, "read", #<struct OsoCloud::Value type="Repo", id="anvils"> ]# ]# Query for all the objects `User:admin` can `read`oso.query("allow", OsoCloud::Value.new(type: "User", id: "admin"), "read", nil)# => [# # `User:admin` can `read` anything# [ "allow", #<struct OsoCloud::Value type="User", id="admin">, "read", nil ]# ]# Query for all the repos `User:bob` can `write` derived from context factsoso.query("allow", OsoCloud::Value.new(type: "User", id: "bob"), "write", OsoCloud::Value.new(type: "Repository"), context_facts: [ [ "has_permission", OsoCloud::Value.new(type: "User", id: "bob"), "write", OsoCloud::Value.new(type: "Repository", id: "anvils")] ])# => [# [ "allow", #<struct OsoCloud::Value type="User", id="bob">, "write", #<struct OsoCloud::Value type="Repo", id="anvils"> ],# ]
Note that nil
behaves like a wildcard. Passing "allow", nil, nil, anvils
means "find anyone who can do anything to anvils
". nil
also behaves like a
wildcard in return values from oso.query
. Additionally, if you want to query
over all instances of a particular type, pass a dictionary with a "type"
key
but no "id"
key. For example, "allow", bob, "read", OsoCloud::Value.new(type: "Repository")
will query for all the objects of
type "Repository"
that bob
can read
.
Learn more about how to query Oso Cloud.
Local Check API
The local check API lets you perform authorization using data that's distributed across Oso Cloud and your own database.
After creating your Local Authorization configuration, provide the path to the YAML file that specifies how to resolve facts in your database.
oso = OsoCloud::Oso.new(..., data_bindings: "path/to/local_authorization_config.yaml")
For more information, see Local Authorization.
List authorized resources with local data: oso.list_local(actor, action, resource_type, column)
Fetches a filter that can be applied to a database query to return just the resources on which an actor can perform an action. Example with Ruby on Rails:
alice = OsoCloud::Value.new(type: "User", id: "alice")authorized_issues = Issue.where(oso.list_local(alice, "read", "Issue", "id"))
You may use the Active Record query interface (opens in a new tab) to combine this authorization filter with other things such as ordering and pagination.
You may provide an array of context facts as an optional fifth argument to this method. Example:
bob = OsoCloud::Value.new(type: "User", id: "bob")authorized_issues = Issue.where(oso.list_local(bob, "read", "Issue", "id", [ ["has_role", bob, "admin", anvils_repository] # a context fact]))
Check a permission with local data: oso.authorize_local(actor, action, resource)
Fetches a query that can be run against your database to determine whether an actor can perform an action on a resource. Example with Ruby on Rails:
alice = OsoCloud::Value.new(type: "User", id: "alice")swage_issue = OsoCloud::Value.new(type: "Issue", id: "swage")query = oso.authorize_local(alice, "read", swage_issue)allowed = Issue.connection.select_value(query)return head :forbidden unless allowed
You may provide an array of context facts as an optional fourth argument to this method. Example:
bob = OsoCloud::Value.new(type: "User", id: "bob")query = oso.authorize_local(bob, "read", swage_issue, [ ["has_role", bob, "admin", anvils_repository] # a context fact])
List authorized actions with local data: oso.actions_local(actor, resource)
Fetches a query that can be run against your database to fetch the actions an actor can perform on a resource. Example with Ruby on Rails:
alice = OsoCloud::Value.new(type: "User", id: "alice")swage_issue = OsoCloud::Value.new(type: "Issue", id: "swage")query = oso.actions_local(alice, swage_issue)actions = Issue.connection.select_all(query).pluck("actions")
You may provide an array of context facts as an optional third argument to this method. Example:
bob = OsoCloud::Value.new(type: "User", id: "bob")query = oso.actions_local(bob, swage_issue, [ ["has_role", bob, "admin", anvils_repository] # a context fact])
Policy API
Update the active policy: oso.policy(policy)
Updates the policy in Oso Cloud. The string passed into this method should be written in Polar. Example:
oso.policy("actor User {}")
This command will run any tests defined in your policy. If one or more of these tests fail, your policy will not be updated.
Get policy metadata: oso.get_policy_metadata
Returns metadata about the currently active policy. Example:
metadata = oso.get_policy_metadataputs metadata.resources.keys# returns:# Organization# Repository# User# globalputs metadata.resources[:Organization].roles# returns:# admin# member
See the Policy Metadata guide for more information on use cases.
Oso Migrate
Oso Migrate is a suite of developer tools built into the Oso Dev Server to help you migrate your legacy authorization system over to Oso as quickly as possible. The tooling helps you migrate incrementally, safely and quickly by observing and understanding authorization behavior as it happens in your application, whilst offering the ability to compare results against your legacy authorization system.
First, download the version of our client library that includes Oso Migrate (any version after 1.10.0):
# install latest versiongem install oso-cloud
Follow the initialization instructions in the Oso Migrate documentation to get started with the tooling suite.
Parity Handle
With Oso Migrate, you can compare the results of your legacy authorization system with Oso side by side.
In the Logs tab, the Actual
column represents Oso's decision, based on the policy and facts.
You can supply the expected result from your legacy authorization system using the ParityHandle
.
The ParityHandle
is passed as an optional keyword argument into oso.authorize()
or oso.authorize_local()
:
parity_handle = OsoCloud::ParityHandle.newallowed = oso.authorize(user, action, resource, parity_handle: parity_handle)
Here’s an example of using ParityHandle
with a legacy authorization system:
require 'oso-cloud'# Checks authorization in both Oso and the legacy authorization system.# Compares results using ParityHandle.def authorize_with_parity_check(oso, user_id, action, resource) parity_handle = OsoCloud::ParityHandle.new user = OsoCloud::Value.new(type: "User", id: user_id) # Run Oso and legacy authorization in parallel oso_thread = Thread.new do begin oso.authorize(user, action, resource, parity_handle: parity_handle) rescue => e puts "Oso authorization failed: #{e.message}" false end end legacy_result = begin legacy_authorize(user_id, action, resource) rescue => e puts "Legacy authorization failed: #{e.message}" false end oso_thread.join # Record the expected result. This will show up in the `Expected` column. begin parity_handle.expect(legacy_result) rescue => e puts "Failed to record expected result: #{e.message}" end # Enforce the legacy result until you're confident things are consistent. legacy_resultend
The ParityHandle
also works with Local Authorization:
require 'oso-cloud'# Checks authorization in both Oso and the legacy authorization system.# Compares results using ParityHandle.def authorize_local_with_parity_check(oso, user_id, action, resource) parity_handle = OsoCloud::ParityHandle.new user = OsoCloud::Value.new(type: "User", id: user_id) legacy_result = begin legacy_authorize(user_id, action, resource) rescue => e puts "Legacy authorization failed: #{e.message}" false end # You can call expect before or after authorize and authorize_local begin parity_handle.expect(legacy_result) rescue => e puts "Failed to record expected result: #{e.message}" end # Optional context facts context_facts = [ ["has_relation", resource, "parent", OsoCloud::Value.new(type: "Repository", id: "anvil")] ] # Get the SQL query from Oso begin sql_query = oso.authorize_local(user, action, resource, context_facts, parity_handle: parity_handle) # Execute the SQL query in your database to get the Oso result. # This result will show up in the "Actual" column in the Logs tab. result = ActiveRecord::Base.connection.select_one(sql_query) oso_result = result["allowed"] # Enforce the legacy result until you're confident things are consistent legacy_result rescue => e puts "Oso local authorization failed: #{e.message}" legacy_result endend
Other Features
Oso Migrate has many other features, including Request Replay, Policy Debugger, and a Test Runner. Read through the documentation to learn more!
We are actively iterating on developer experience and would appreciate all feedback on the Oso Migrate and the broader development experience with Oso Cloud. Please do not hesitate to reach out on Slack (opens in a new tab).
Talk to an Oso engineer
If you'd like to learn more about using Oso Cloud in your app or have any questions about this guide, schedule a 1x1 with an Oso engineer. We're happy to help.