Pragma::Policy
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.