Skip to content

Commit 4eecd02

Browse files
committed
Merge pull request #2399 from jayasi/integration_pundit
Pundit Integration
2 parents 35c3d64 + bf63d68 commit 4eecd02

11 files changed

Lines changed: 253 additions & 1 deletion

File tree

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ group :test do
5252
gem 'rubocop', '~> 0.31.0'
5353
gem 'simplecov', '>= 0.9', require: false
5454
gem 'timecop', '>= 0.5'
55-
55+
gem 'pundit'
5656
platforms :ruby_21, :ruby_22 do
5757
gem 'refile', '~> 0.5', require: 'refile/rails'
5858
gem 'refile-mini_magick', '>= 0.1.0'

gemfiles/rails_4.0.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ group :test do
5757
gem "rubocop", "~> 0.31.0"
5858
gem "simplecov", ">= 0.9", :require => false
5959
gem "timecop", ">= 0.5"
60+
gem "pundit"
6061

6162
platforms :ruby_21, :ruby_22 do
6263
gem "refile", "~> 0.5", :require => "refile/rails"

gemfiles/rails_4.1.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ group :test do
5555
gem "rubocop", "~> 0.31.0"
5656
gem "simplecov", ">= 0.9", :require => false
5757
gem "timecop", ">= 0.5"
58+
gem "pundit"
5859

5960
platforms :ruby_21, :ruby_22 do
6061
gem "refile", "~> 0.5", :require => "refile/rails"

gemfiles/rails_4.2.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ group :test do
5656
gem "rubocop", "~> 0.31.0"
5757
gem "simplecov", ">= 0.9", :require => false
5858
gem "timecop", ">= 0.5"
59+
gem "pundit"
5960

6061
platforms :ruby_21, :ruby_22 do
6162
gem "refile", "~> 0.5", :require => "refile/rails"

lib/generators/rails_admin/templates/initializer.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ RailsAdmin.config do |config|
1111
## == Cancan ==
1212
# config.authorize_with :cancan
1313

14+
## == Pundit ==
15+
# config.authorize_with :pundit
16+
1417
## == PaperTrail ==
1518
# config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0
1619

lib/rails_admin.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'rails_admin/extension'
55
require 'rails_admin/extensions/cancan'
66
require 'rails_admin/extensions/cancancan'
7+
require 'rails_admin/extensions/pundit'
78
require 'rails_admin/extensions/paper_trail'
89
require 'rails_admin/extensions/history'
910
require 'rails_admin/support/csv_converter'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
require 'rails_admin/extensions/pundit/authorization_adapter'
2+
3+
RailsAdmin.add_extension(:pundit, RailsAdmin::Extensions::Pundit, authorization: true)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
module RailsAdmin
2+
module Extensions
3+
module Pundit
4+
# This adapter is for the Pundit[https://github.com/elabs/pundit] authorization library.
5+
# You can create another adapter for different authorization behavior, just be certain it
6+
# responds to each of the public methods here.
7+
class AuthorizationAdapter
8+
# See the +authorize_with+ config method for where the initialization happens.
9+
def initialize(controller)
10+
@controller = controller
11+
@controller.class.send(:alias_method, :pundit_user, :_current_user)
12+
end
13+
14+
# This method is called in every controller action and should raise an exception
15+
# when the authorization fails. The first argument is the name of the controller
16+
# action as a symbol (:create, :bulk_delete, etc.). The second argument is the
17+
# AbstractModel instance that applies. The third argument is the actual model
18+
# instance if it is available.
19+
def authorize(action, abstract_model = nil, model_object = nil)
20+
record = model_object || abstract_model && abstract_model.model
21+
fail ::Pundit::NotAuthorizedError.new("not allowed to #{action} this #{record}") unless policy(record).send(action) if action
22+
end
23+
24+
# This method is called primarily from the view to determine whether the given user
25+
# has access to perform the action on a given model. It should return true when authorized.
26+
# This takes the same arguments as +authorize+. The difference is that this will
27+
# return a boolean whereas +authorize+ will raise an exception when not authorized.
28+
def authorized?(action, abstract_model = nil, model_object = nil)
29+
record = model_object || abstract_model && abstract_model.model
30+
policy(record).send(action) if action
31+
end
32+
33+
# This is called when needing to scope a database query. It is called within the list
34+
# and bulk_delete/destroy actions and should return a scope which limits the records
35+
# to those which the user can perform the given action on.
36+
def query(_action, abstract_model)
37+
@controller.policy_scope(abstract_model.model.all)
38+
rescue ::Pundit::NotDefinedError
39+
abstract_model.model.all
40+
end
41+
42+
# This is called in the new/create actions to determine the initial attributes for new
43+
# records. It should return a hash of attributes which match what the user
44+
# is authorized to create.
45+
def attributes_for(action, abstract_model)
46+
record = abstract_model && abstract_model.model
47+
policy(record).try(:attributes_for, action) || {}
48+
end
49+
50+
private
51+
52+
def policy(record)
53+
@controller.policy(record)
54+
rescue ::Pundit::NotDefinedError
55+
::ApplicationPolicy.new(@controller.send(:_current_user), record)
56+
end
57+
end
58+
end
59+
end
60+
end
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
require 'spec_helper'
2+
include Pundit
3+
4+
class ApplicationPolicy
5+
attr_reader :user, :record
6+
7+
def initialize(user, record)
8+
@user = user
9+
@record = record
10+
end
11+
12+
def show
13+
user.roles.include? :admin
14+
end
15+
16+
def destroy
17+
false
18+
end
19+
20+
def history
21+
user.roles.include? :admin
22+
end
23+
24+
def show_in_app
25+
user.roles.include? :admin
26+
end
27+
28+
def dashboard
29+
user.roles.include? :admin
30+
end
31+
32+
def index
33+
false
34+
end
35+
36+
def new
37+
user.roles.include? :admin
38+
end
39+
40+
def edit
41+
user.roles.include? :admin
42+
end
43+
44+
def export
45+
user.roles.include? :admin
46+
end
47+
end
48+
49+
class PlayerPolicy < ApplicationPolicy
50+
def new
51+
(user.roles.include?(:create_player) || user.roles.include?(:admin) || user.roles.include?(:manage_player))
52+
end
53+
54+
def edit
55+
(user.roles.include? :manage_player)
56+
end
57+
58+
def destroy
59+
(user.roles.include? :manage_player)
60+
end
61+
62+
def index
63+
user.roles.include? :admin
64+
end
65+
end
66+
67+
describe 'RailsAdmin Pundit Authorization', type: :request do
68+
subject { page }
69+
70+
before do
71+
RailsAdmin.config do |c|
72+
c.authorize_with(:pundit)
73+
c.authenticate_with { warden.authenticate! scope: :user }
74+
c.current_user_method(&:current_user)
75+
end
76+
@player_model = RailsAdmin::AbstractModel.new(Player)
77+
@user = FactoryGirl.create :user
78+
login_as @user
79+
end
80+
81+
describe 'with no roles' do
82+
before do
83+
@user.update_attributes(roles: [])
84+
end
85+
86+
it 'GET /admin should raise Pundit::NotAuthorizedError' do
87+
expect { visit dashboard_path }.to raise_error(Pundit::NotAuthorizedError)
88+
end
89+
90+
it 'GET /admin/player should raise Pundit::NotAuthorizedError' do
91+
expect { visit index_path(model_name: 'player') }.to raise_error(Pundit::NotAuthorizedError)
92+
end
93+
end
94+
95+
describe 'with read player role' do
96+
before do
97+
@user.update_attributes(roles: [:admin, :read_player])
98+
end
99+
100+
it 'GET /admin should show Player but not League' do
101+
visit dashboard_path
102+
is_expected.to have_content('Player')
103+
is_expected.not_to have_content('League')
104+
is_expected.not_to have_content('Add new')
105+
end
106+
107+
it 'GET /admin/team should raise Pundit::NotAuthorizedError' do
108+
expect { visit index_path(model_name: 'team') }.to raise_error(Pundit::NotAuthorizedError)
109+
end
110+
111+
it 'GET /admin/player/1/edit should raise access denied' do
112+
@player = FactoryGirl.create :player
113+
expect { visit edit_path(model_name: 'player', id: @player.id) }.to raise_error(Pundit::NotAuthorizedError)
114+
end
115+
end
116+
117+
describe 'with admin role' do
118+
before do
119+
@user.update_attributes(roles: [:admin, :manage_player])
120+
end
121+
122+
it 'GET /admin should show Player but not League' do
123+
visit dashboard_path
124+
is_expected.to have_content('Player')
125+
end
126+
127+
it 'GET /admin/player/new should render and create record upon submission' do
128+
visit new_path(model_name: 'player')
129+
130+
is_expected.to have_content('Save and edit')
131+
is_expected.not_to have_content('Delete')
132+
133+
is_expected.to have_content('Save and add another')
134+
fill_in 'player[name]', with: 'Jackie Robinson'
135+
fill_in 'player[number]', with: '42'
136+
fill_in 'player[position]', with: 'Second baseman'
137+
click_button 'Save'
138+
is_expected.not_to have_content('Edit')
139+
140+
@player = RailsAdmin::AbstractModel.new('Player').first
141+
expect(@player.name).to eq('Jackie Robinson')
142+
expect(@player.number).to eq(42)
143+
expect(@player.position).to eq('Second baseman')
144+
end
145+
end
146+
147+
describe 'with all roles' do
148+
it 'shows links to all actions' do
149+
@user.update_attributes(roles: [:admin, :manage_player])
150+
@player = FactoryGirl.create :player
151+
152+
visit index_path(model_name: 'player')
153+
is_expected.to have_css('.show_member_link')
154+
is_expected.to have_css('.edit_member_link')
155+
is_expected.to have_css('.delete_member_link')
156+
is_expected.to have_css('.history_show_member_link')
157+
is_expected.to have_css('.show_in_app_member_link')
158+
159+
visit show_path(model_name: 'player', id: @player.id)
160+
is_expected.to have_content('Show')
161+
is_expected.to have_content('Edit')
162+
is_expected.to have_content('Delete')
163+
is_expected.to have_content('History')
164+
is_expected.to have_content('Show in app')
165+
end
166+
end
167+
end

spec/spec_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
require 'simplecov'
88
require 'coveralls'
9+
require 'pundit/rspec'
910

1011
SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
1112

@@ -21,6 +22,7 @@
2122
require 'factory_girl'
2223
require 'factories'
2324
require 'database_cleaner'
25+
require 'support/pundit_matcher.rb'
2426
require "orm/#{CI_ORM}"
2527

2628
Dir[File.expand_path('../shared_examples/**/*.rb', __FILE__)].each { |f| require f }

0 commit comments

Comments
 (0)