Pragma::Policy

Build Status Coverage Status Maintainability

Policies provide fine-grained access control for your API resources.

Installation

Add this line to your application’s Gemfile:

ruby gem 'pragma-policy'

And then execute:

console $ bundle

Or install it yourself as:

console $ gem install pragma-policy

Usage

To create a policy, simply inherit from Pragma::Policy::Base:

ruby module API module V1 module Article class Policy < Pragma::Policy::Base end end end end

By default, the policy does not return any objects when scoping and forbids all operations.

You can start customizing your policy by defining a scope and operation predicates:

```ruby module API module V1 module Article class Policy < Pragma::Policy::Base class Scope < Pragma::Policy::Base::Scope def resolve scope.where(‘published = ? OR author_id = ?’, true, user.id) end end

    def show?
      record.published? || record.author_id == user.id
    end

    def update?
      record.author_id == user.id
    end

    def destroy?
      record.author_id == user.id
    end
  end
end   end end ```

You are ready to use your policy!

Retrieving records

To retrieve all the records accessible by a user, use the .accessible_by class method:

ruby posts = API::V1::Article::Policy::Scope.new(user, Article.all).resolve

Authorizing operations

To authorize an operation, first instantiate the policy, then use the predicate methods:

ruby policy = API::V1::Article::Policy.new(user, post) fail 'You cannot update this post!' unless policy.update?

Since raising when the operation is forbidden is so common, we provide bang methods a shorthand syntax. Pragma::Policy::NotAuthorizedError is raised if the predicate method returns false:

ruby policy = API::V1::Article::Policy.new(user, post) policy.update! # raises if the user cannot update the post

Reusing Pundit policies

If you already use Pundit, there’s no need to copy-paste policies for your API. You can use Pragma::Policy::Pundit to delegate to your existing policies and scopes:

ruby module API module V1 module Article class Policy < Pragma::Pundit::Policy # This is optional: the inferred default would be ArticlePolicy. self.pundit_klass = CustomArticlePolicy end end end end

Note that you can still override specific methods if you want, and we’ll keep delegating the rest to Pundit:

ruby module API module V1 module Article class Policy < Pragma::Pundit::Policy def create? # Your custom create policy here end end end end end

Passing additional context

If you want to pass additional context to the policy, just pass it instead of the user object. Pragma::Policy never uses your context in any way, so you can pass whatever you want:

ruby policy = API::V1::Article::Policy.new(OpenStruct.new(ip: request.remote_ip, user: user), post) policy.update!

In your policy, you can use #context as an alias for #user for convenience:

ruby module API module V1 module Article class Policy < Pragma::Pundit::Policy def update? record.author_id == context.user.id || context.ip == '127.0.0.1' end end end end end

If you are using pragma-rails, you may change the context passed to the policy by defining a #policy_context method on your controller. This way you are not forced to override #current_user or #pragma_user:

```ruby module API module V1 class PostsController < ApplicationController # …

  private

  def policy_context
    OpenStruct.new(ip: request.remote_ip, user: current_user)
  end
end   end end ```

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma-policy.

License

The gem is available as open source under the terms of the MIT License.