2012-11-04 04:20:45 -05:00
|
|
|
# Pundit
|
|
|
|
|
2012-11-19 07:52:52 -05:00
|
|
|
[![Build Status](https://secure.travis-ci.org/elabs/pundit.png?branch=master)](https://travis-ci.org/elabs/pundit)
|
2013-06-18 04:20:34 -04:00
|
|
|
[![Code Climate](https://codeclimate.com/github/elabs/pundit.png)](https://codeclimate.com/github/elabs/pundit)
|
2014-06-03 15:00:53 -04:00
|
|
|
[![Inline docs](http://inch-ci.org/github/elabs/pundit.png)](http://inch-ci.org/github/elabs/pundit)
|
2015-09-14 07:57:35 -04:00
|
|
|
[![Gem Version](https://badge.fury.io/rb/pundit.svg)](http://badge.fury.io/rb/pundit)
|
2012-11-19 07:52:52 -05:00
|
|
|
|
2012-11-19 07:36:35 -05:00
|
|
|
Pundit provides a set of helpers which guide you in leveraging regular Ruby
|
|
|
|
classes and object oriented design patterns to build a simple, robust and
|
|
|
|
scaleable authorization system.
|
2012-11-04 04:20:45 -05:00
|
|
|
|
2015-03-27 09:31:06 -04:00
|
|
|
Links:
|
|
|
|
|
|
|
|
- [API documentation](http://www.rubydoc.info/gems/pundit)
|
|
|
|
- [Source Code](https://github.com/elabs/pundit)
|
|
|
|
- [Contributing](https://github.com/elabs/pundit/blob/master/CONTRIBUTING.md)
|
|
|
|
- [Code of Conduct](https://github.com/elabs/pundit/blob/master/CODE_OF_CONDUCT.md)
|
|
|
|
|
2015-03-27 05:40:30 -04:00
|
|
|
Sponsored by:
|
|
|
|
|
|
|
|
[<img src="http://d3cv91luii1z1d.cloudfront.net/logo-gh.png" alt="Elabs" height="50px"/>](http://elabs.se)
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
## Installation
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
gem "pundit"
|
|
|
|
```
|
|
|
|
|
2012-11-19 07:13:37 -05:00
|
|
|
Include Pundit in your application controller:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class ApplicationController < ActionController::Base
|
|
|
|
include Pundit
|
|
|
|
protect_from_forgery
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2012-11-19 05:04:18 -05:00
|
|
|
Optionally, you can run the generator, which will set up an application policy
|
2012-11-19 07:37:33 -05:00
|
|
|
with some useful defaults for you:
|
2012-11-19 05:04:18 -05:00
|
|
|
|
|
|
|
``` sh
|
|
|
|
rails g pundit:install
|
|
|
|
```
|
|
|
|
|
2013-08-16 15:27:49 -04:00
|
|
|
After generating your application policy, restart the Rails server so that Rails
|
|
|
|
can pick up any classes in the new `app/policies/` directory.
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
## Policies
|
|
|
|
|
|
|
|
Pundit is focused around the notion of policy classes. We suggest that you put
|
2014-01-30 16:08:01 -05:00
|
|
|
these classes in `app/policies`. This is a simple example that allows updating
|
|
|
|
a post if the user is an admin, or if the post is unpublished:
|
2012-11-04 04:20:45 -05:00
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class PostPolicy
|
|
|
|
attr_reader :user, :post
|
|
|
|
|
|
|
|
def initialize(user, post)
|
|
|
|
@user = user
|
|
|
|
@post = post
|
|
|
|
end
|
|
|
|
|
2014-01-30 16:08:01 -05:00
|
|
|
def update?
|
2012-11-04 04:20:45 -05:00
|
|
|
user.admin? or not post.published?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2014-08-22 05:19:36 -04:00
|
|
|
As you can see, this is just a plain Ruby class. Pundit makes the following
|
2014-06-23 19:40:35 -04:00
|
|
|
assumptions about this class:
|
2012-11-04 04:20:45 -05:00
|
|
|
|
|
|
|
- The class has the same name as some kind of model class, only suffixed
|
|
|
|
with the word "Policy".
|
|
|
|
- The first argument is a user. In your controller, Pundit will call the
|
|
|
|
`current_user` method to retrieve what to send into this argument
|
|
|
|
- The second argument is some kind of model object, whose authorization
|
|
|
|
you want to check. This does not need to be an ActiveRecord or even
|
|
|
|
an ActiveModel object, it can be anything really.
|
2014-01-30 16:08:01 -05:00
|
|
|
- The class implements some kind of query method, in this case `update?`.
|
2012-11-04 04:20:45 -05:00
|
|
|
Usually, this will map to the name of a particular controller action.
|
|
|
|
|
|
|
|
That's it really.
|
|
|
|
|
2014-08-22 05:19:36 -04:00
|
|
|
Usually you'll want to inherit from the application policy created by the
|
2014-06-23 19:40:35 -04:00
|
|
|
generator, or set up your own base class to inherit from:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class PostPolicy < ApplicationPolicy
|
|
|
|
def update?
|
2014-08-22 07:59:56 -04:00
|
|
|
user.admin? or not record.published?
|
2014-06-23 19:40:35 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2014-08-22 07:59:56 -04:00
|
|
|
In the generated `ApplicationPolicy`, the model object is called `record`.
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
Supposing that you have an instance of class `Post`, Pundit now lets you do
|
|
|
|
this in your controller:
|
|
|
|
|
|
|
|
``` ruby
|
2014-01-30 16:08:01 -05:00
|
|
|
def update
|
|
|
|
@post = Post.find(params[:id])
|
2012-11-04 04:20:45 -05:00
|
|
|
authorize @post
|
2014-01-30 16:08:01 -05:00
|
|
|
if @post.update(post_params)
|
2012-11-04 04:20:45 -05:00
|
|
|
redirect_to @post
|
|
|
|
else
|
2014-01-30 16:08:01 -05:00
|
|
|
render :edit
|
2012-11-04 04:20:45 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
The authorize method automatically infers that `Post` will have a matching
|
|
|
|
`PostPolicy` class, and instantiates this class, handing in the current user
|
|
|
|
and the given record. It then infers from the action name, that it should call
|
2014-01-30 16:08:01 -05:00
|
|
|
`update?` on this instance of the policy. In this case, you can imagine that
|
2012-11-04 04:20:45 -05:00
|
|
|
`authorize` would have done something like this:
|
|
|
|
|
|
|
|
``` ruby
|
2014-01-30 16:08:01 -05:00
|
|
|
raise "not authorized" unless PostPolicy.new(current_user, @post).update?
|
2012-11-04 04:20:45 -05:00
|
|
|
```
|
|
|
|
|
2013-02-26 08:21:31 -05:00
|
|
|
You can pass a second argument to `authorize` if the name of the permission you
|
2012-11-19 07:06:48 -05:00
|
|
|
want to check doesn't match the action name. For example:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
def publish
|
|
|
|
@post = Post.find(params[:id])
|
|
|
|
authorize @post, :update?
|
|
|
|
@post.publish!
|
|
|
|
redirect_to @post
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2015-09-18 02:07:54 -04:00
|
|
|
If you don't have an instance for the first argument to `authorize`, then you can pass
|
|
|
|
the class. For example:
|
|
|
|
|
2015-09-18 02:10:04 -04:00
|
|
|
Policy:
|
2015-09-18 02:07:54 -04:00
|
|
|
```ruby
|
|
|
|
class PostPolicy < ApplicationPolicy
|
|
|
|
def admin_list?
|
|
|
|
user.admin?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2015-09-18 02:10:04 -04:00
|
|
|
Controller:
|
2015-09-18 02:07:54 -04:00
|
|
|
```ruby
|
|
|
|
def admin_list
|
|
|
|
authorize Post # we don't have a particular post to authorize
|
|
|
|
# Rest of controller action
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
You can easily get a hold of an instance of the policy through the `policy`
|
|
|
|
method in both the view and controller. This is especially useful for
|
|
|
|
conditionally showing links or buttons in the view:
|
|
|
|
|
|
|
|
``` erb
|
2014-01-30 16:08:01 -05:00
|
|
|
<% if policy(@post).update? %>
|
|
|
|
<%= link_to "Edit post", edit_post_path(@post) %>
|
2012-11-04 04:20:45 -05:00
|
|
|
<% end %>
|
|
|
|
```
|
2014-07-13 06:34:09 -04:00
|
|
|
## Headless policies
|
|
|
|
|
2014-08-22 05:19:36 -04:00
|
|
|
Given there is a policy without a corresponding model / ruby class,
|
2014-07-13 06:34:09 -04:00
|
|
|
you can retrieve it by passing a symbol.
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/policies/dashboard_policy.rb
|
|
|
|
class DashboardPolicy < Struct.new(:user, :dashboard)
|
|
|
|
# ...
|
|
|
|
end
|
2014-10-28 08:52:32 -04:00
|
|
|
```
|
2014-07-13 06:34:09 -04:00
|
|
|
|
2014-10-28 08:52:32 -04:00
|
|
|
```ruby
|
2014-07-13 06:34:09 -04:00
|
|
|
# In controllers
|
|
|
|
authorize :dashboard, :show?
|
2014-10-28 08:52:32 -04:00
|
|
|
```
|
2014-07-13 06:34:09 -04:00
|
|
|
|
2014-10-28 08:52:32 -04:00
|
|
|
```erb
|
2014-07-13 06:34:09 -04:00
|
|
|
# In views
|
|
|
|
<% if policy(:dashboard).show? %>
|
|
|
|
<%= link_to 'Dashboard', dashboard_path %>
|
|
|
|
<% end %>
|
|
|
|
```
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
## Ensuring policies are used
|
|
|
|
|
|
|
|
Pundit adds a method called `verify_authorized` to your controllers. This
|
|
|
|
method will raise an exception if `authorize` has not yet been called. You
|
2014-01-31 21:34:00 -05:00
|
|
|
should run this method in an `after_action` to ensure that you haven't
|
2012-11-04 04:20:45 -05:00
|
|
|
forgotten to authorize the action. For example:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class ApplicationController < ActionController::Base
|
2015-11-29 10:05:17 -05:00
|
|
|
after_action :verify_authorized
|
2012-11-04 04:20:45 -05:00
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2014-01-29 10:57:55 -05:00
|
|
|
Likewise, Pundit also adds `verify_policy_scoped` to your controller. This
|
2015-03-18 10:50:06 -04:00
|
|
|
will raise an exception in the vein of `verify_authorized`. However, it tracks
|
2014-06-25 07:51:49 -04:00
|
|
|
if `policy_scope` is used instead of `authorize`. This is mostly useful for
|
2013-04-18 01:05:24 -04:00
|
|
|
controller actions like `index` which find collections with a scope and don't
|
|
|
|
authorize individual instances.
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class ApplicationController < ActionController::Base
|
2015-11-29 10:05:17 -05:00
|
|
|
after_action :verify_authorized, :except => :index
|
2014-01-31 21:34:00 -05:00
|
|
|
after_action :verify_policy_scoped, :only => :index
|
2013-04-18 01:05:24 -04:00
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2015-02-20 14:38:08 -05:00
|
|
|
If you're using `verify_authorized` in your controllers but need to
|
2015-04-07 21:17:32 -04:00
|
|
|
conditionally bypass verification, you can use `skip_authorization`. For
|
|
|
|
bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful
|
|
|
|
in circumstances where you don't want to disable verification for the
|
2015-02-20 14:38:08 -05:00
|
|
|
entire action, but have some cases where you intend to not authorize.
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
def show
|
|
|
|
record = Record.find_by(attribute: "value")
|
|
|
|
if record.present?
|
|
|
|
authorize record
|
|
|
|
else
|
|
|
|
skip_authorization
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2015-03-18 10:50:06 -04:00
|
|
|
If you need to perform some more sophisticated logic or you want to raise a custom
|
|
|
|
exception you can use the two lower level methods `pundit_policy_authorized?`
|
|
|
|
and `pundit_policy_scoped?` which return `true` or `false` depending on whether
|
|
|
|
`authorize` or `policy_scope` have been called, respectively.
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
## Scopes
|
|
|
|
|
|
|
|
Often, you will want to have some kind of view listing records which a
|
|
|
|
particular user has access to. When using Pundit, you are expected to
|
|
|
|
define a class called a policy scope. It can look something like this:
|
|
|
|
|
|
|
|
``` ruby
|
2014-06-23 19:40:35 -04:00
|
|
|
class PostPolicy < ApplicationPolicy
|
|
|
|
class Scope
|
|
|
|
attr_reader :user, :scope
|
2014-08-22 05:19:36 -04:00
|
|
|
|
2014-06-23 19:40:35 -04:00
|
|
|
def initialize(user, scope)
|
|
|
|
@user = user
|
|
|
|
@scope = scope
|
|
|
|
end
|
2014-08-22 05:19:36 -04:00
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
def resolve
|
|
|
|
if user.admin?
|
2013-10-16 20:11:45 -04:00
|
|
|
scope.all
|
2012-11-04 04:20:45 -05:00
|
|
|
else
|
|
|
|
scope.where(:published => true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-01-30 16:08:01 -05:00
|
|
|
def update?
|
2012-11-04 04:20:45 -05:00
|
|
|
user.admin? or not post.published?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
Pundit makes the following assumptions about this class:
|
|
|
|
|
|
|
|
- The class has the name `Scope` and is nested under the policy class.
|
|
|
|
- The first argument is a user. In your controller, Pundit will call the
|
|
|
|
`current_user` method to retrieve what to send into this argument.
|
|
|
|
- The second argument is a scope of some kind on which to perform some kind of
|
|
|
|
query. It will usually be an ActiveRecord class or a
|
|
|
|
`ActiveRecord::Relation`, but it could be something else entirely.
|
|
|
|
- Instances of this class respond to the method `resolve`, which should return
|
|
|
|
some kind of result which can be iterated over. For ActiveRecord classes,
|
|
|
|
this would usually be an `ActiveRecord::Relation`.
|
|
|
|
|
2014-06-23 19:40:35 -04:00
|
|
|
You'll probably want to inherit from the application policy scope generated by the
|
|
|
|
generator, or create your own base class to inherit from:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class PostPolicy < ApplicationPolicy
|
|
|
|
class Scope < Scope
|
|
|
|
def resolve
|
|
|
|
if user.admin?
|
|
|
|
scope.all
|
|
|
|
else
|
|
|
|
scope.where(:published => true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def update?
|
|
|
|
user.admin? or not post.published?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
You can now use this class from your controller via the `policy_scope` method:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
def index
|
|
|
|
@posts = policy_scope(Post)
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
Just as with your policy, this will automatically infer that you want to use
|
|
|
|
the `PostPolicy::Scope` class, it will instantiate this class and call
|
|
|
|
`resolve` on the instance. In this case it is a shortcut for doing:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
def index
|
|
|
|
@posts = PostPolicy::Scope.new(current_user, Post).resolve
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
You can, and are encouraged to, use this method in views:
|
|
|
|
|
|
|
|
``` erb
|
|
|
|
<% policy_scope(@user.posts).each do |post| %>
|
2014-08-04 22:20:12 -04:00
|
|
|
<p><%= link_to post.title, post_path(post) %></p>
|
2012-11-04 04:20:45 -05:00
|
|
|
<% end %>
|
|
|
|
```
|
|
|
|
|
2013-01-08 03:46:20 -05:00
|
|
|
## Manually specifying policy classes
|
|
|
|
|
|
|
|
Sometimes you might want to explicitly declare which policy to use for a given
|
|
|
|
class, instead of letting Pundit infer it. This can be done like so:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class Post
|
|
|
|
def self.policy_class
|
2013-12-31 13:16:02 -05:00
|
|
|
PostablePolicy
|
2013-01-08 03:46:20 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2012-11-19 05:08:04 -05:00
|
|
|
## Just plain old Ruby
|
2012-11-04 04:20:45 -05:00
|
|
|
|
|
|
|
As you can see, Pundit doesn't do anything you couldn't have easily done
|
|
|
|
yourself. It's a very small library, it just provides a few neat helpers.
|
|
|
|
Together these give you the power of building a well structured, fully working
|
|
|
|
authorization system without using any special DSLs or funky syntax or
|
|
|
|
anything.
|
|
|
|
|
|
|
|
Remember that all of the policy and scope classes are just plain Ruby classes,
|
|
|
|
which means you can use the same mechanisms you always use to DRY things up.
|
|
|
|
Encapsulate a set of permissions into a module and include them in multiple
|
|
|
|
policies. Use `alias_method` to make some permissions behave the same as
|
|
|
|
others. Inherit from a base set of permissions. Use metaprogramming if you
|
2012-11-19 05:08:04 -05:00
|
|
|
really have to.
|
2012-11-04 04:20:45 -05:00
|
|
|
|
2012-11-19 05:05:20 -05:00
|
|
|
## Generator
|
|
|
|
|
|
|
|
Use the supplied generator to generate policies:
|
|
|
|
|
|
|
|
``` sh
|
|
|
|
rails g pundit:policy post
|
|
|
|
```
|
|
|
|
|
2012-11-04 04:20:45 -05:00
|
|
|
## Closed systems
|
|
|
|
|
|
|
|
In many applications, only logged in users are really able to do anything. If
|
|
|
|
you're building such a system, it can be kind of cumbersome to check that the
|
|
|
|
user in a policy isn't `nil` for every single permission.
|
|
|
|
|
|
|
|
We suggest that you define a filter that redirects unauthenticated users to the
|
|
|
|
login page. As a secondary defence, if you've defined an ApplicationPolicy, it
|
|
|
|
might be a good idea to raise an exception if somehow an unauthenticated user
|
|
|
|
got through. This way you can fail more gracefully.
|
|
|
|
|
|
|
|
``` ruby
|
2012-11-19 05:08:04 -05:00
|
|
|
class ApplicationPolicy
|
2012-11-04 04:20:45 -05:00
|
|
|
def initialize(user, record)
|
2012-12-03 08:59:11 -05:00
|
|
|
raise Pundit::NotAuthorizedError, "must be logged in" unless user
|
2012-11-19 05:08:04 -05:00
|
|
|
@user = user
|
|
|
|
@record = record
|
2012-11-04 04:20:45 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
2012-11-19 05:04:18 -05:00
|
|
|
|
2013-08-16 15:27:49 -04:00
|
|
|
## Rescuing a denied Authorization in Rails
|
2013-06-24 12:47:33 -04:00
|
|
|
|
2014-03-05 12:05:00 -05:00
|
|
|
Pundit raises a `Pundit::NotAuthorizedError` you can
|
|
|
|
[rescue_from](http://guides.rubyonrails.org/action_controller_overview.html#rescue-from)
|
|
|
|
in your `ApplicationController`. You can customize the `user_not_authorized`
|
|
|
|
method in every controller.
|
2013-06-24 12:47:33 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ApplicationController < ActionController::Base
|
|
|
|
protect_from_forgery
|
|
|
|
include Pundit
|
|
|
|
|
|
|
|
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def user_not_authorized
|
2014-12-15 05:37:04 -05:00
|
|
|
flash[:alert] = "You are not authorized to perform this action."
|
2014-03-05 12:05:00 -05:00
|
|
|
redirect_to(request.referrer || root_path)
|
2013-06-24 12:47:33 -04:00
|
|
|
end
|
|
|
|
end
|
2013-06-24 16:54:48 -04:00
|
|
|
```
|
2013-06-24 12:47:33 -04:00
|
|
|
|
2014-08-22 07:14:30 -04:00
|
|
|
## Creating custom error messages
|
2014-03-05 12:05:00 -05:00
|
|
|
|
|
|
|
`NotAuthorizedError`s provide information on what query (e.g. `:create?`), what
|
|
|
|
record (e.g. an instance of `Post`), and what policy (e.g. an instance of
|
|
|
|
`PostPolicy`) caused the error to be raised.
|
|
|
|
|
|
|
|
One way to use these `query`, `record`, and `policy` properties is to connect
|
|
|
|
them with `I18n` to generate error messages. Here's how you might go about doing
|
|
|
|
that.
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ApplicationController < ActionController::Base
|
|
|
|
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def user_not_authorized(exception)
|
|
|
|
policy_name = exception.policy.class.to_s.underscore
|
|
|
|
|
2014-11-22 17:07:30 -05:00
|
|
|
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
|
2014-03-05 12:05:00 -05:00
|
|
|
redirect_to(request.referrer || root_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
en:
|
|
|
|
pundit:
|
2014-11-22 17:07:30 -05:00
|
|
|
default: 'You cannot perform this action.'
|
2014-03-05 12:05:00 -05:00
|
|
|
post_policy:
|
|
|
|
update?: 'You cannot edit this post!'
|
|
|
|
create?: 'You cannot create posts!'
|
|
|
|
```
|
|
|
|
|
|
|
|
Of course, this is just an example. Pundit is agnostic as to how you implement
|
|
|
|
your error messaging.
|
|
|
|
|
2012-11-19 05:04:18 -05:00
|
|
|
## Manually retrieving policies and scopes
|
|
|
|
|
|
|
|
Sometimes you want to retrieve a policy for a record outside the controller or
|
|
|
|
view. For example when you delegate permissions from one policy to another.
|
|
|
|
|
|
|
|
You can easily retrieve policies and scopes like this:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
Pundit.policy!(user, post)
|
|
|
|
Pundit.policy(user, post)
|
|
|
|
|
|
|
|
Pundit.policy_scope!(user, Post)
|
|
|
|
Pundit.policy_scope(user, Post)
|
|
|
|
```
|
|
|
|
|
|
|
|
The bang methods will raise an exception if the policy does not exist, whereas
|
|
|
|
those without the bang will return nil.
|
|
|
|
|
2014-01-29 10:57:55 -05:00
|
|
|
## Customize Pundit user
|
2013-07-12 23:42:34 -04:00
|
|
|
|
|
|
|
In some cases your controller might not have access to `current_user`, or your
|
2014-01-29 10:57:55 -05:00
|
|
|
`current_user` is not the method that should be invoked by Pundit. Simply
|
2013-07-12 23:42:34 -04:00
|
|
|
define a method in your controller called `pundit_user`.
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
def pundit_user
|
|
|
|
User.find_by_other_means
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2014-08-22 07:14:30 -04:00
|
|
|
## Additional context
|
|
|
|
|
|
|
|
Pundit strongly encourages you to model your application in such a way that the
|
|
|
|
only context you need for authorization is a user object and a domain model that
|
|
|
|
you want to check authorization for. If you find yourself needing more context than
|
|
|
|
that, consider whether you are authorizing the right domain model, maybe another
|
|
|
|
domain model (or a wrapper around multiple domain models) can provide the context
|
|
|
|
you need.
|
|
|
|
|
|
|
|
Pundit does not allow you to pass additional arguments to policies for precisely
|
|
|
|
this reason.
|
|
|
|
|
|
|
|
However, in very rare cases, you might need to authorize based on more context than just
|
|
|
|
the currently authenticated user. Suppose for example that authorization is dependent
|
|
|
|
on IP address in addition to the authenticated user. In that case, one option is to
|
|
|
|
create a special class which wraps up both user and IP and passes it to the policy.
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
class UserContext
|
|
|
|
attr_reader :user, :ip
|
2014-10-13 18:32:54 -04:00
|
|
|
|
2014-09-01 10:37:43 -04:00
|
|
|
def initialize(user, ip)
|
2014-08-22 07:14:30 -04:00
|
|
|
@user = user
|
|
|
|
@ip = ip
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class ApplicationController
|
|
|
|
include Pundit
|
2014-10-13 18:32:54 -04:00
|
|
|
|
2014-08-22 07:14:30 -04:00
|
|
|
def pundit_user
|
|
|
|
UserContext.new(current_user, request.ip)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-11-09 04:59:28 -05:00
|
|
|
## Strong parameters
|
2013-06-17 19:47:39 -04:00
|
|
|
|
2013-11-09 04:59:28 -05:00
|
|
|
In Rails 4 (or Rails 3.2 with the
|
|
|
|
[strong_parameters](https://github.com/rails/strong_parameters) gem),
|
2015-03-27 05:14:09 -04:00
|
|
|
mass-assignment protection is handled in the controller. With Pundit you can
|
|
|
|
control which attributes a user has access to update via your policies. You can
|
|
|
|
set up a `permitted_attributes` method in your policy like this:
|
2013-06-17 19:47:39 -04:00
|
|
|
|
|
|
|
```ruby
|
2013-11-09 04:59:28 -05:00
|
|
|
# app/policies/post_policy.rb
|
|
|
|
class PostPolicy < ApplicationPolicy
|
2013-06-17 19:47:39 -04:00
|
|
|
def permitted_attributes
|
|
|
|
if user.admin? || user.owner_of?(post)
|
2013-11-09 04:59:28 -05:00
|
|
|
[:title, :body, :tag_list]
|
2013-06-17 19:47:39 -04:00
|
|
|
else
|
2013-11-09 04:59:28 -05:00
|
|
|
[:tag_list]
|
2013-06-17 19:47:39 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-03-27 05:14:09 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
You can now retrieve these attributes from the policy:
|
2013-06-17 19:47:39 -04:00
|
|
|
|
2015-03-27 05:14:09 -04:00
|
|
|
```ruby
|
2013-11-09 04:59:28 -05:00
|
|
|
# app/controllers/posts_controller.rb
|
2013-06-17 19:47:39 -04:00
|
|
|
class PostsController < ApplicationController
|
|
|
|
def update
|
2013-11-09 04:59:28 -05:00
|
|
|
@post = Post.find(params[:id])
|
2015-03-27 05:14:09 -04:00
|
|
|
if @post.update_attributes(post_params)
|
2013-11-09 04:59:28 -05:00
|
|
|
redirect_to @post
|
|
|
|
else
|
|
|
|
render :edit
|
|
|
|
end
|
2013-06-17 19:47:39 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2013-11-09 04:59:28 -05:00
|
|
|
def post_params
|
2015-03-27 05:14:09 -04:00
|
|
|
params.require(:post).permit(policy(@post).permitted_attributes)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
However, this is a bit cumbersome, so Pundit provides a convenient helper method:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/controllers/posts_controller.rb
|
|
|
|
class PostsController < ApplicationController
|
|
|
|
def update
|
|
|
|
@post = Post.find(params[:id])
|
|
|
|
if @post.update_attributes(permitted_attributes(@post))
|
|
|
|
redirect_to @post
|
|
|
|
else
|
|
|
|
render :edit
|
|
|
|
end
|
2013-06-17 19:47:39 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-03-27 09:10:12 -04:00
|
|
|
## RSpec
|
|
|
|
|
2013-10-24 21:30:55 -04:00
|
|
|
### Policy Specs
|
|
|
|
|
2013-03-27 09:10:12 -04:00
|
|
|
Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec.
|
|
|
|
Require `pundit/rspec` in your `spec_helper.rb`:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
require "pundit/rspec"
|
|
|
|
```
|
|
|
|
|
|
|
|
Then put your policy specs in `spec/policies`, and make them look somewhat like this:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
describe PostPolicy do
|
2014-10-13 18:32:54 -04:00
|
|
|
subject { described_class }
|
2013-03-27 09:10:12 -04:00
|
|
|
|
2015-09-18 02:07:54 -04:00
|
|
|
permissions :update?, :edit? do
|
2013-03-27 09:10:12 -04:00
|
|
|
it "denies access if post is published" do
|
2013-12-26 12:25:56 -05:00
|
|
|
expect(subject).not_to permit(User.new(:admin => false), Post.new(:published => true))
|
2013-03-27 09:10:12 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "grants access if post is published and user is an admin" do
|
2013-12-26 12:25:56 -05:00
|
|
|
expect(subject).to permit(User.new(:admin => true), Post.new(:published => true))
|
2013-03-27 09:10:12 -04:00
|
|
|
end
|
|
|
|
|
2013-04-12 15:48:27 -04:00
|
|
|
it "grants access if post is unpublished" do
|
2013-12-26 12:25:56 -05:00
|
|
|
expect(subject).to permit(User.new(:admin => false), Post.new(:published => false))
|
2013-03-27 09:10:12 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-06-18 06:57:46 -04:00
|
|
|
An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
|
2013-08-28 05:03:13 -04:00
|
|
|
[excellent post](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/).
|
2013-06-18 06:57:46 -04:00
|
|
|
|
2013-10-24 21:30:55 -04:00
|
|
|
# External Resources
|
|
|
|
|
2014-02-28 18:21:18 -05:00
|
|
|
- [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
|
2013-10-24 21:30:55 -04:00
|
|
|
- [Migrating to Pundit from CanCan](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
|
|
|
|
- [Testing Pundit Policies with RSpec](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
|
2014-07-13 05:05:48 -04:00
|
|
|
- [Using Pundit outside of a Rails controller](https://github.com/elabs/pundit/pull/136)
|
2013-10-24 21:30:55 -04:00
|
|
|
|
2012-11-19 05:04:18 -05:00
|
|
|
# License
|
|
|
|
|
|
|
|
Licensed under the MIT license, see the separate LICENSE.txt file.
|