-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2399 from jayasi/integration_pundit
Pundit Integration
- Loading branch information
Showing
11 changed files
with
253 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require 'rails_admin/extensions/pundit/authorization_adapter' | ||
|
||
RailsAdmin.add_extension(:pundit, RailsAdmin::Extensions::Pundit, authorization: true) |
60 changes: 60 additions & 0 deletions
60
lib/rails_admin/extensions/pundit/authorization_adapter.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
module RailsAdmin | ||
module Extensions | ||
module Pundit | ||
# This adapter is for the Pundit[https://github.com/elabs/pundit] authorization library. | ||
# You can create another adapter for different authorization behavior, just be certain it | ||
# responds to each of the public methods here. | ||
class AuthorizationAdapter | ||
# See the +authorize_with+ config method for where the initialization happens. | ||
def initialize(controller) | ||
@controller = controller | ||
@controller.class.send(:alias_method, :pundit_user, :_current_user) | ||
end | ||
|
||
# This method is called in every controller action and should raise an exception | ||
# when the authorization fails. The first argument is the name of the controller | ||
# action as a symbol (:create, :bulk_delete, etc.). The second argument is the | ||
# AbstractModel instance that applies. The third argument is the actual model | ||
# instance if it is available. | ||
def authorize(action, abstract_model = nil, model_object = nil) | ||
record = model_object || abstract_model && abstract_model.model | ||
fail ::Pundit::NotAuthorizedError.new("not allowed to #{action} this #{record}") unless policy(record).send(action) if action | ||
end | ||
|
||
# This method is called primarily from the view to determine whether the given user | ||
# has access to perform the action on a given model. It should return true when authorized. | ||
# This takes the same arguments as +authorize+. The difference is that this will | ||
# return a boolean whereas +authorize+ will raise an exception when not authorized. | ||
def authorized?(action, abstract_model = nil, model_object = nil) | ||
record = model_object || abstract_model && abstract_model.model | ||
policy(record).send(action) if action | ||
end | ||
|
||
# This is called when needing to scope a database query. It is called within the list | ||
# and bulk_delete/destroy actions and should return a scope which limits the records | ||
# to those which the user can perform the given action on. | ||
def query(_action, abstract_model) | ||
@controller.policy_scope(abstract_model.model.all) | ||
rescue ::Pundit::NotDefinedError | ||
abstract_model.model.all | ||
end | ||
|
||
# This is called in the new/create actions to determine the initial attributes for new | ||
# records. It should return a hash of attributes which match what the user | ||
# is authorized to create. | ||
def attributes_for(action, abstract_model) | ||
record = abstract_model && abstract_model.model | ||
policy(record).try(:attributes_for, action) || {} | ||
end | ||
|
||
private | ||
|
||
def policy(record) | ||
@controller.policy(record) | ||
rescue ::Pundit::NotDefinedError | ||
::ApplicationPolicy.new(@controller.send(:_current_user), record) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
require 'spec_helper' | ||
include Pundit | ||
|
||
class ApplicationPolicy | ||
attr_reader :user, :record | ||
|
||
def initialize(user, record) | ||
@user = user | ||
@record = record | ||
end | ||
|
||
def show | ||
user.roles.include? :admin | ||
end | ||
|
||
def destroy | ||
false | ||
end | ||
|
||
def history | ||
user.roles.include? :admin | ||
end | ||
|
||
def show_in_app | ||
user.roles.include? :admin | ||
end | ||
|
||
def dashboard | ||
user.roles.include? :admin | ||
end | ||
|
||
def index | ||
false | ||
end | ||
|
||
def new | ||
user.roles.include? :admin | ||
end | ||
|
||
def edit | ||
user.roles.include? :admin | ||
end | ||
|
||
def export | ||
user.roles.include? :admin | ||
end | ||
end | ||
|
||
class PlayerPolicy < ApplicationPolicy | ||
def new | ||
(user.roles.include?(:create_player) || user.roles.include?(:admin) || user.roles.include?(:manage_player)) | ||
end | ||
|
||
def edit | ||
(user.roles.include? :manage_player) | ||
end | ||
|
||
def destroy | ||
(user.roles.include? :manage_player) | ||
end | ||
|
||
def index | ||
user.roles.include? :admin | ||
end | ||
end | ||
|
||
describe 'RailsAdmin Pundit Authorization', type: :request do | ||
subject { page } | ||
|
||
before do | ||
RailsAdmin.config do |c| | ||
c.authorize_with(:pundit) | ||
c.authenticate_with { warden.authenticate! scope: :user } | ||
c.current_user_method(&:current_user) | ||
end | ||
@player_model = RailsAdmin::AbstractModel.new(Player) | ||
@user = FactoryGirl.create :user | ||
login_as @user | ||
end | ||
|
||
describe 'with no roles' do | ||
before do | ||
@user.update_attributes(roles: []) | ||
end | ||
|
||
it 'GET /admin should raise Pundit::NotAuthorizedError' do | ||
expect { visit dashboard_path }.to raise_error(Pundit::NotAuthorizedError) | ||
end | ||
|
||
it 'GET /admin/player should raise Pundit::NotAuthorizedError' do | ||
expect { visit index_path(model_name: 'player') }.to raise_error(Pundit::NotAuthorizedError) | ||
end | ||
end | ||
|
||
describe 'with read player role' do | ||
before do | ||
@user.update_attributes(roles: [:admin, :read_player]) | ||
end | ||
|
||
it 'GET /admin should show Player but not League' do | ||
visit dashboard_path | ||
is_expected.to have_content('Player') | ||
is_expected.not_to have_content('League') | ||
is_expected.not_to have_content('Add new') | ||
end | ||
|
||
it 'GET /admin/team should raise Pundit::NotAuthorizedError' do | ||
expect { visit index_path(model_name: 'team') }.to raise_error(Pundit::NotAuthorizedError) | ||
end | ||
|
||
it 'GET /admin/player/1/edit should raise access denied' do | ||
@player = FactoryGirl.create :player | ||
expect { visit edit_path(model_name: 'player', id: @player.id) }.to raise_error(Pundit::NotAuthorizedError) | ||
end | ||
end | ||
|
||
describe 'with admin role' do | ||
before do | ||
@user.update_attributes(roles: [:admin, :manage_player]) | ||
end | ||
|
||
it 'GET /admin should show Player but not League' do | ||
visit dashboard_path | ||
is_expected.to have_content('Player') | ||
end | ||
|
||
it 'GET /admin/player/new should render and create record upon submission' do | ||
visit new_path(model_name: 'player') | ||
|
||
is_expected.to have_content('Save and edit') | ||
is_expected.not_to have_content('Delete') | ||
|
||
is_expected.to have_content('Save and add another') | ||
fill_in 'player[name]', with: 'Jackie Robinson' | ||
fill_in 'player[number]', with: '42' | ||
fill_in 'player[position]', with: 'Second baseman' | ||
click_button 'Save' | ||
is_expected.not_to have_content('Edit') | ||
|
||
@player = RailsAdmin::AbstractModel.new('Player').first | ||
expect(@player.name).to eq('Jackie Robinson') | ||
expect(@player.number).to eq(42) | ||
expect(@player.position).to eq('Second baseman') | ||
end | ||
end | ||
|
||
describe 'with all roles' do | ||
it 'shows links to all actions' do | ||
@user.update_attributes(roles: [:admin, :manage_player]) | ||
@player = FactoryGirl.create :player | ||
|
||
visit index_path(model_name: 'player') | ||
is_expected.to have_css('.show_member_link') | ||
is_expected.to have_css('.edit_member_link') | ||
is_expected.to have_css('.delete_member_link') | ||
is_expected.to have_css('.history_show_member_link') | ||
is_expected.to have_css('.show_in_app_member_link') | ||
|
||
visit show_path(model_name: 'player', id: @player.id) | ||
is_expected.to have_content('Show') | ||
is_expected.to have_content('Edit') | ||
is_expected.to have_content('Delete') | ||
is_expected.to have_content('History') | ||
is_expected.to have_content('Show in app') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
RSpec::Matchers.define :permit do |action| | ||
match do |policy| | ||
policy.public_send("#{action}?") | ||
end | ||
|
||
failure_message do |policy| | ||
"#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}." | ||
end | ||
|
||
failure_message_when_negated do |policy| | ||
"#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}." | ||
end | ||
end |