Permitter¶ ↑
<img src=“https://travis-ci.org/merhard/permitter.png?branch=master” alt=“Build Status” /> <img src=“https://codeclimate.com/github/merhard/permitter.png” />
Here are some instructions for setting up Permitter. Try this out and provide feedback in the issue tracker.
Setup¶ ↑
Permitter expects your controllers to have a current_user
method. Add some authentication for this (such as Devise).
To install Permitter, add it to your Gemfile
and run the bundle
command.
gem "permitter"
Next generate a Permission class, this is where your permissions will be defined.
rails g permitter:permission
Add authorization by calling authorize_user!
in a before_action
in any controller (or the ApplicationController
to authorize the whole app).
class ApplicationController < ActionController::Base before_action :authorize_user! end
This will add an authorization check locking down every action in the controller. If you try visiting a page without granting the user access, a Permitter::Unauthorized
exception will be raised. You can catch this exception and modify its behavior.
class ApplicationController < ActionController::Base before_action :authorize_user! rescue_from Permitter::Unauthorized do |exception| # your code here end end
Defining Abilities¶ ↑
You grant access to controller actions through the Permission
class which was generated above. The current_user
is passed in allowing you to define permissions based on user attributes. For example:
class Permission include Permitter::Permission def initialize(user) if user allow_all else allow_action [:sessions, :registrations], [:new, :create] allow_action :projects, :index end end end
Here, if there is a current_user
(user signed in), he will be able to perform any action on any controller. If current_user
is nil
(user not signed in), the visitor can only access the new and create actions of the SessionsController
and the RegistrationsController
as well as the #index
action of the ProjectsController
.
The first argument to allow_action
is the controller name being permitted. The second argument is the action they can perform in that controller.
As shown above, pass an array to either of these will grant permission on each item in the array. Controller names and actions can be represented as symbols or strings.
You can check permissions in any controller or view using the allowed_action?
method.
<% if allowed_action? :projects, :create %> <%= link_to "New Project", new_project_path %> <% end %>
Here the link will only show up if the user can create projects.
Resource Conditions¶ ↑
If you need to change authorization based on a model’s attributes, you can do so by passing a block as the last argument to allow_action
. For example, if you want to only allow a user to edit projects which he/she owns, first:
class Permission include Permitter::Permission def initialize(user) if user allow_action :projects, [:edit, :update] do |project| project.user_id == user.id end end end end
Then, create a current_resource
method in that controller:
class ProjectsController < ApplicationController private def current_resource @project ||= Project.find(params[:id]) if params[:id] end end
You can still check permissions using the allowed_action?
method. Just pass in the resource.
<% if allowed_action? :projects, :update, @project %> <%= link_to "Edit Project", edit_project_path %> <% end %>
Here, it will only show the edit link if the user_id
of the project matches the current_user.id
.
Resource Attributes¶ ↑
Rails 4 moved mass assignment to the controller level with strong_parameters
. Permitter
fully supports Rails 4 mass assignment. If mass assignment in your app requires no user specific logic, it may not be necessarry to use Permitter for mass assignment sanitation. In this case, just follow normal Rails 4 methods for strong parameter mass assignment.
If your app does require user specific mass assignment logic, Permitter
supplies an allow_param
method to be used along-side allow_action
in your Permission
class.
For example, suppose a user should only be able to set the title (and no other attributes) of projects they own. Just:
class Permission include Permitter::Permission def initialize(user) allow_action :project, [:index, :show] if user allow_action :projects, [:new, :create] allow_action :projects, [:edit, :update] do |project| project.user_id == user.id end allow_param :project, :title allow_all if user.admin? end end end
The allow_param
method takes a resource title (or array of resource titles) and an attribute (or array of attributes) for that resource to be permitted via strong parameters. Permitter
will modify params
for that resource (here, params[:project]
) to only include the whitelisted attributes, removing all others. Permitter
flags the remaining params
of that resource permitted per strong parameters. This allows mass assignment in the controller to follow the old Rails 3.2 syntax while using the more secure methodology of strong paramters.
class ProjectsController < ApplicationController ... def create @project = Project.create(params[:project]) end def update @project = Project.find(params[:id]) @project.update(params[:project]) end ... end
You can check permissions using the allowed_param?
method.
<% if allowed_param? :project, :title %> <div class="field"> <%= f.label :title %><br /> <%= f.text_field :title %> </div> <% end %>
Here, it will only show the title label and text field if the user is allowed to modify the title attribute of Project
.
Permission Scoping¶ ↑
Sometimes you may want to scope the relation used in the #index
action of the controller. Permitter
allows you to do this in your Permission
class without the need to repeat yourself.
For example:
class Permission include Permitter::Permission def initialize(user) allow_action :projects, :index if user allow_action :projects, :show do |project| project.user_id == user.id end end end end class ProjectController < ApplicationController def index @projects = Project.permitted_by(current_permissions) end end
The @projects
variable will now be scoped to projects available to the #show
action.
If the #show
action does not match the scoping needed, any action can be used (even a custom one if none match).
class Permission include Permitter::Permission def initialize(user) allow_action :projects, :index if user allow_action :projects, :show do |project| project.user_id == user.id end allow_action :projects, :custom_action do |project| # your scope here end end end end
Then:
class ProjectController < ApplicationController def index @projects = Project.permitted_by(current_permissions, :custom_action) end end
When writing scopes via an association, a custom action must be used with a block requiring no arguments.
class Permission include Permitter::Permission def initialize(user) allow_action :comments, :index allow_action :comments, :permitted do article.published == true end end end class CommentsController < ApplicationController def index @comments = Comment.joins(:article).permitted_by(@permissions, :permitted) #alternatively: Comment.joins{article}.permitted_by(@permissions, :permitted) end end class Comment < ActiveRecord::Base # t.integer :article_id belongs_to :article end class Article < ActiveRecord::Base # integer :category_id # boolean :published belongs_to :category has_many :comments end class Category < ActiveRecord::Base # boolean :visible has_many :articles end
Nested joins are also supported:
class Permission include Permitter::Permission def initialize(user) allow_action :comments, :index allow_action :comments, :permitted do article.category.visible == true end end end class CommentsController < ApplicationController def index @comments = Comment.joins{article.category}.permitted_by(@permissions, :permitted) end end
Permitter accomplishes this using the squeel gem. See the squeel docs for any query related questions.
Special Thanks¶ ↑
Permitter was inspired by cancan and Railscasts.