Filter collections of data

Many applications perform authorization over large collections of data that cannot be loaded into memory. Often index pages showing users a number of resources, like the repositories they can access, will need to use data filtering. The data filtering API provides support for these use cases seamlessly without requiring you to alter your policy.

Get all authorized resources

In Enforce authorization we discussed resource-level authorization. The authorize method tells you whether a specific resource is authorized. But to fetch all authorized resources, we need to use authorized_resources instead:

data_filtering.rb
get "/repos" do
  repositories = oso.authorized_resources(
    get_current_user(),
    "read",
    Repository)

  serialize(repositories)
end

To use this API, you must pass some additional information to register_class so that Oso knows how to retrieve your application’s objects.

Implementing data filtering query functions

To use data filtering, you tell Oso how to make queries to your data store for the resources used in your policy. Oso uses Query objects to query your data store. A Query represents a set of filters to apply to a collection of data.

You can use any type as a Query. Many ORMs have these built in, but you may have your own representation if your resources are retrieved from an external service, or with a lower-level database API.

You implement three functions to tell Oso how to work with your Query:

  • build_query(filters) -> Query: Creates a query from a list of authorization filters produced by evaluating the Oso policy.
  • exec_query(query) -> List[Object]: Executes the query, returning the list of objects retrieved by the query.
  • combine_query(q1, q2) -> Query: Combines two queries q1 and q2 together such that the new query returns the UNION of q1 and q2 (all results from each).
data_filtering.rb
  # This is an example implementation for the Sequel ORM, but you can
  # use any ORM with this API.
  def self.get_repositories(filters)
    query = Repository
    filters.each do |filter|
      value = filter.value

      if filter.field.nil?
        value = value.name
        field = :name
      else
        field = filter.field.to_sym
      end

      if filter.kind == "Eq"
        query = query.where(field => value)
      else
        raise "unimplemented constraint kind #{filter.kind}"
      end
    end

    query
  end

  def self.combine_query(q1, q2)
    q1.union(q2)
  end

  def self.exec_query(q)
    q.all
  end

  OSO.register_class(User)
  OSO.register_class(
    Repository,
    name: "Repository",
    fields: {
      is_public: PolarBoolean
    },
    build_query: method(:get_repositories),
    combine_query: method(:combine_query),
    exec_query: method(:exec_query)
  )

  OSO.load_files(["main.polar"])

When you call authorized_resources, Oso will create a query using the build_query function with filters obtained by running the policy. For example, in Write Polar Rules we wrote the rule:

has_permission(_user: User, "read", repository: Repository) if
	repository.is_public = true;

This rule would produce the filters: [Filter(kind=Eq, field="is_public", value=true)]. Oso then uses SQLAlchemy in our example to create a query and retrieve repositories that have the is_public field as true from the database by calling the exec_query function. This pushes down filters to the database, allowing you to retrieve only authorized objects. Notably, the same rule can be executed using authorize and authorized_resources.

Adding filters on top of authorization

Often, you may want to add to the query after it is authorized. Let’s say we want to order Repositories by name.

To do this, we can use the authorized_query API:

get "/repos" do
  query = oso.authorized_query(
    get_current_user(),
    "read",
    Repository)

  # Use the ORM's Query API to alter the query before it is
  # executed by the database with .all.
  repositories = query.order_by(:name).all

  serialize(repositories)
end

authorized_query returns the query object used by our ORM with authorization filters applied so that we can add additional filters, pagination, or ordering to it.

What’s next

In this brief example we covered what the data filtering API does. For a more detailed how to of using data filtering and implementing query builder functions, see the Data Filtering guide.

This is the end of Add to your app! For more detail on using Oso, see the guides section.

Set up a 1x1 with an Oso Engineer

Our team is happy to help you get started with Oso. If you'd like to learn more about using Oso in your app or have any questions about this guide, schedule a 1x1 with an Oso engineer.


Was this page useful?