Skip to content

StairwayB/pundit_roles

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PunditRoles

Gem Version Build Status Coverage Status Maintainability

PunditRoles is a helper gem which works on top of Pundit (if you are not familiar with Pundit, it is recommended you read it's documentation before continuing). It allows you to extend Pundit's authorization system to include attributes and associations, and provides a couple of helpers for convenience.

If you are already using Pundit, this should not conflict with any of Pundit's existing functionality. You may use Pundit's features as well as the features from this gem interchangeably. There are some caveats however, see the Porting over from Pundit.

Please note that this gem is not affiliated with Pundit or it's creators.

  • The Readme contains only a cursory overview of the gem. For an in-depth tutorial, consult the wiki

Installation

Add this line to your application's Gemfile:

gem 'pundit_roles'

Add PunditRoles to your ApplicationController(Pundit is included in PunditRoles, so no need to add both)

class ApplicationController < ActionController::Base
  include PunditRoles
end

And inherit your ApplicationPolicy from Policy::Base

class ApplicationPolicy < Policy::Base
end

Roles

PunditRoles operates around the notion of roles. Each role needs to be defined at the Policy level and provided with a conditional method that determines whether the @user(the current_user in the context of a Policy) falls into this role. Additionally, each role can have a set of options defined for it(like attributes, associations and scope). A basic example for a UserPolicy would be:

class UserPolicy < ApplicationPolicy
  role :regular_user,
       attributes: {
         show: %i(username name avatar is_confirmed created_at)
       },
       scope: lambda{resource.regular_user_scope}

  role :correct_user,
       attributes: {
         show: %i(email phone_number confirmed_at updated_at),
         update: %i(username email password password_confirmation current_password name avatar)
       }
  
  # in the query methods, you define the roles which are allowed for the particular action
  def show?
    %i(regular_user correct_user)
  end

  # or with the allow helper method:
  def update?
    allow :correct_user, :some_other_role
  end
end

In your Controller, you simply call the authorize! method for the action you want authorized:

class UserController < ApplicationController
  def show
    @user = User.find(params[:id])
    authorize! @user
    render jsonapi: user, fields: permitted_show_attributes
  end
end

An in-depth description of the features can be found on the wiki:

  1. The basics
  2. Defining roles
  3. Declaring attributes and associations

The :guest role

PunditRoles comes with a default :guest role, which simply checks if the user is nil. If you wish to permit guest users for a particular action, simply define the options for it and allow it in your query method.

class UserPolicy < ApplicationPolicy

  role :guest,
       attributes: {
         show: %i(username first_name last_name avatar),
         create: %i(username email password password_confirmation first_name last_name avatar)
       },
       associations: {}
  
  def show?
    allow :guest, :some, :other, :roles
  end
  
  def create?
    allow :guest, :admin_user
  end
  
end

Important

  • The :guest role is exclusionary by default, meaning it cannot be merged with other roles. It is also the first role that is evaluated, and if the user is a :guest, it will return the guest attributes if :guest is allowed, or raise Pundit::NotAuthorizedError if not.
  • Do not use a custom role for nil users, use :guest. If you do, it will most likely lead to unwanted errors.

Authorizing Associations

Detailed description in the Authorizing associations wiki.

  • Controller
class UsersController < ApplicationController
    def show
      user = User.where(id: 1).includes([:followers, {posts: [:comments]}]).first
      authorize!(user, associations: [:followers, {posts: [:comments]}])
      # then you just render the results, using the helper methods 
      render jsonapi: user, include: permitted_show_associations, fields: permitted_show_attributes
    end
end
  • Policies
class UserPolicy < ApplicationPolicy
  role :regular_user,
       attributes: {...},
       associations: {show: [:posts]},
       associated_as: {posts: [:regular_user]}
       
  role :correct_user,
         attributes: {...},
         associations: {show: [:posts]},
         associated_as: {posts: [:regular_user, :correct_user]}
                                                               
  def show? 
    allow :regular_user, :correct_user
  end
end

class PostPolicy < ApplicationPolicy
  role :regular_user,
       attributes: {...},
       associations: {show: [:comments]},
       associated_as: {posts: [:regular_user]}
       
  role :correct_user,
         attributes: {...},
         associations: {show: [:comments]},
         associated_as: {posts: [:regular_user]}
end

class CommentPolicy < ApplicationPolicy
  role :regular_user, 
       attributes: {...}
       
  role :correct_user,
       attributes: {...}
end

Important

  • Only the primary model is authorized, meaning that PunditRoles will not run the query methods(i.e. allow :correct_user, ...) or the conditional methods of the roles in associated policies! This means that you must specify which roles correspond to which roles in associated policies(check the wiki for a more detailed description).

Scopes

Detailed description in the Defining scopes for roles wiki.

  • Policy:
role :guest,
    attributes: {
      show: %i(name avatar),
    },
    associations: {},
    scope: lambda{resource.where(visible_publicly: true)}

role :regular_user,
   attributes: {
     show: %i(username name avatar)
   },
   associations: {
     show: %i(posts followers following)
   },
   scope: lambda{resource.where.not(id: user.id)}
   
def index?
  allow :guest, :regular_user
end
  • Controller
def index
  @users = policy_scope!(User.all)
end

Strong parameters

Detailed description in the Strong parameters wiki.

  • Controller
def create
  authorize! User # you will need to authorize the model first, in order to get the permitted attributes
  @user = User.new(create_params)
  if @user.save!
    render jsonapi: @user, fields: {users: permitted_show_attributes}
  end
end

private
  
def create_params
  params.require(:users).permit(permitted_create_attributes)
end

Porting over from Pundit

If you're already using Pundit, this gem should not conflict with any existing functionality. However, there are a couple of things to watch out for:

  • PunditRoles uses @resouce instead of @record in the Policy. This change was made, to reflect the fact that the Policy can have scopes as well as records passed to it.
  • PunditRoles uses the bang methods authorize! and policy_scope!, instead of authorize and policy_scope.
  • PunditRoles does not use the Scope class of Pundit, but it is included in Policy::Base so you may use that as well, if you so choose.

Planned updates

Generators, some config options, and possibly rspec helpers will be coming in the near future.

Contributing

Bug reports are welcome on GitHub at StairwayB.

License

The gem is available as open source under the terms of the MIT License.

About

Role based authorization based on Pundit, allowing attribute and association level permissions

Resources

License

Stars

Watchers

Forks

Packages

No packages published