Skip to main content
Version: v2.0

Policies

This guide is an introduction to Rhino Policies

What is a Policy?

Rhino Policies are based on the Pundit gem. Policies can:

  • authorize actions ("query methods") such as update? or index?
  • constrain resources returned ("scopes")
  • enforce strong parameters

Policies should be stored under app/policies and tests go under test/policies

What is a Role?

Every auth owner (user) must have at least one role for every base owner they are attached to. In the single user case a user is generally an "admin" which would map to the AdminPolicy.

Every base owner class must provide a roles_for_auth class method that returns a hash of roles and base owners. For instance the default rhino user class implements the following:

def self.roles_for_auth(auth_owner, record = nil)
return {} unless auth_owner

# If user is logged in, but no record, they are still an admin for their data
# Otherwise owner must match to be an admin
# A list of roles as hash keys with an array of base_owners for each
return { 'admin': [auth_owner] } if !record.respond_to?(:base_owner_ids) || record.base_owner_ids.include?(auth_owner&.id)

{}
end

Which allows the user to be admin for their own data but have no role for the data of another user. The default Rhino organization class implements the following:

def self.roles_for_auth(auth_owner, record = nil)
return {} unless auth_owner

users_roles = ::UsersRole.where(user: auth_owner).joins(:organization, :role).includes(:organization, :role)
users_roles = users_roles.where(organization_id: record.base_owner_ids) if record.present? && record.respond_to?(:base_owner_ids)

# A list of roles as hash keys with an array of base_owners for each
users_roles.group_by { |ur| ur.role.name }.transform_values { |ur_array| ur_array.map(&:organization) }
end

Which looks up the user role for each organization from the UsersRole table and returns the hash. A user could be an admin for one organization and a viewer for another. An example return value might look something like:

{
admin: [ Organization.find(1), Organization.find(2) ],
viewer: [ Organization.find(3) ],
custom_role: [ Organization.find(4) ]
}

roles_for_auth can be overridden in the application models (app/models/user.rb or app/models/organization.rb) for customized role behavior.

Configuring policies and roles

By default every resource that is not globally owned, User or Organization, uses CrudPolicy which handles aggregating policies across roles. Globally owned resources use GlobalPolicy by default. User and Organization have their own special policies.

Based on the role of the user and the resource, CrudPolicy will also apply additional policies. For instance if the role is author and the resource is Blog, CrudPolicy will first look for author_blog_policy.rb and then author_policy.rb if that is not found.

Rhino::PolicyHelper.find_policy(:author, Blog)
Rhino::PolicyHelper.find_policy_scope(:author, BlogPost)

A new policy can be generated from the command line:

$ rails g rhino:policy AuthorBlog

By default this will inherit from the Rhino::ViewerPolicy policy. Specify alternate base policies with the --parent option

$ rails g rhino:policy AuthorBlog --parent=Rhino::AdminPolicy

When CrudPolicy is not enough

If there are cases where the default CrudPolicy is not enough, any Pundit policy can be set.

class Blog < ApplicationRecord
rhino_policy :my_custom
end

However in this case the developer must carefully implement their own user and organization level controls.

Strong Parameters

Policies enforce incoming and outgoing parameters on models, just as Pundit allows. The BasePolicy (from which all built in policies inherit) will by default use parameters derived from the properties (rhino_properties_read, rhino_properties_create, rhino_properties_update). You can override these however to get customized behavior by role and resource:

def permitted_attributes_for_show
['only_this_param']
end

Testing

Rhino provides a number of helpers and assertions when inheriting from Rhino::TestCase::Policy. When generating a policy, a sample set of test cases will be provided.

Built In Policies

Rhino provides a number of builtin policies that can be used directly or inherited from to provide a starting point.

Rhino::BasePolicy

Base for others to inherit from that provides basic initialization and defaults to dis-allowing all actions and scoping to no resources.

Rhino::CrudPolicy

The default for most Resources, it inherits from BasePolicy. It obtains the roles for the auth owner (user) and then composes the corresponding policies for the user. If any role of a user allows the action. The scopes for the Auth Owner (user) are unioned together.

Rhino::ViewerPolicy

Allows view actions (index? and show?) actions

Rhino::AdminPolicy

Inherits from ViewerPolicy and allows an authenticated users to view (index? and show?), create (create?), destroy (destroy?) and edit (update?)

Rhino::EditorPolicy

Inherits from ViewerPolicy and allows an authenticated users to view (index? and show?), and edit (update?)

Rhino::GlobalPolicy

Inherits from ViewerPolicy and allows an authenticated users to view all resources (index? and show?).