Policies provide fine-grained access control for your API resources.
Add this line to your application's Gemfile:
gem 'pragma-policy'And then execute:
$ bundleOr install it yourself as:
$ gem install pragma-policyTo create a policy, simply inherit from Pragma::Policy::Base:
module API
module V1
module Article
class Policy < Pragma::Policy::Base
end
end
end
endBy 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
endYou are ready to use your policy!
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).resolveTo 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 postIf 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
endNote 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
endIf 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
endIf 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
endBug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma-policy.
The gem is available as open source under the terms of the MIT License.