Pragma::Policy
Policies provide fine-grained access control for your API resources.
Installation
Add this line to your application's Gemfile:
gem 'pragma-policy'
And then execute:
$ bundle
Or install it yourself as:
$ gem install pragma-policy
Usage
To create a policy, simply inherit from Pragma::Policy::Base
:
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:
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:
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:
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
:
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:
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:
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:
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:
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
:
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.