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.