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", git: "git://github.com/merhard/permitter.git"

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.