Skip to content

osohq/rails_list_filtering_sample_app

Repository files navigation

List Filtering

This Ruby on Rails sample app demonstrates how to render a list of resources the current user has access to in a web app. It uses Oso List Filtering to generate this list without wastefully fetching all authorized resources from the authorization service, and without making multiple requests to the authorization service. To minimize the amount of data that needs to be fetched from the authorization service, some authorization data is stored in this app's own database, and other authorization data is stored in the authorization service. See here for more information on authorizing lists with distributed data.

Authorization Policy

The authorization policy is in oso.polar:

actor User {}

resource Project {
  roles = ["owner"];
}

resource Article {
  permissions = ["view"];
  relations = { project: Project };

  "view" if "owner" on "project";
  "view" if is_public(resource);
}

This policy uses three pieces of authorization data:

  • Roles on projects: has_role(User:<user id>, "owner", Project:<project id>)
  • Relations between articles and projects: has_relation(Article:<article id>, "project", Project:<project id>)
  • Attributes on articles: is_public(Article:<article id>

The service presented in this sample app manages Articles, which users are granted access to if they own the Project the Article belongs to or if the Article's owner has made the Article public.

Data Model

Imagine that Articles are solely the domain of this service, whereas Projects are a concept that may be relevant to multiple services in our system. Authorization data about Projects (the roles that users have on Projects) is centralized in the authorization service so that we may use that information in other services in the future. On the other hand, we can keep authorization data about Articles (which Project the article belongs to and whether the article is public) in this service's database.

The create_articles migration creates an articles table in our PostgreSQL database which stores information about Articles. This includes authorization data as well as data that's not relevant to authorization, such as the article's title and body.

oso.yml specifies how to resolve authorization data from the database:

facts:
  has_relation(Article:_, String:project, Project:_):
    query: SELECT id, project FROM articles

  is_public(Article:_):
    query: SELECT id FROM articles WHERE is_public

sql_types:
  Article: bigint

Enforcement via Filtering

To enforce our authorization policy, we first set up the Oso Cloud client in the oso.rb initializer:

$oso = OsoCloud::Oso.new(
  api_key: ENV['OSO_AUTH'],
  data_bindings: Rails.root.join('config', 'oso.yml')
)

We pass the path to oso.yml into the client so that Oso Cloud knows that some of the authorization data is stored in our own system.

In the Articles controller we use Oso's list_local method to have Oso Cloud partially evaluate a list query against the data that's stored centrally (roles on Projects), and it returns to us a filter that we can use to finish evaluating the authorization logic against the data stored in our database.

We pass the filter returned by list_local into Active Record's where method to filter the results from the database down to just the ones the current user is authorized to view, and combine it with some business logic to sort and limit the results:

class ArticlesController < ApplicationController
  def index
    @articles = Article.where($oso.list_local(current_user, 'view', 'Article', 'id'))
                      .order(:created_at)
                      .limit(10)
  end
end

The final product is that the homepage of our app displays a list of articles the current user is authorized to view.

Running the App

  1. Make sure you have Ruby on Rails and PostgreSQL installed, and that you have your Oso Cloud API Key in the OSO_AUTH environment variable.
  2. Run bin/rails db:setup to set up a PostgreSQL database and seed it with some data.
  3. Run bin/rails oso:seed to seed your Oso Cloud environment with some data.
  4. Run bin/rails server to start the Rails server.
  5. Open http://localhost:3000 in your browser.