Pundit::AuthorizationNotPerformedError in Rails
Pundit adds a method to your controller called verify_authorized
that ensures that the authorize
method is called somewhere in your controller action. You likely setup an after_action
that calls verify_authorized
(https://github.com/elabs/pundit#ensuring-policies-and-scopes-are-used). Make sure you're calling authorize
in each possible execution path through your controller action.
Alternatively, if you do not want to authorize that particular action, you can skip it:
class PagesControler < ApplicationController
include Pundit
after_action :verify_authorized, except: [:home]
...
end
or if you setup the after_action
in an inherited controller:
class ApplicationController < ActionController::Base
include Pundit
after_action :verify_authorized
...
end
class PagesControler < ApplicationController
skip_after_action :verify_authorized, only: [:home]
...
end
Pundit::AuthorizationNotPerformedError in PagesController#home
Solved it.
Since PagesController
is inheriting from ApplicationController
, and the latter contains before_action :authenticate_user!
, I have to write the following in PagesController
:
class PagesController < ApplicationController
include Pundit
skip_after_action :verify_authorized, only: [:home]
skip_before_action :authenticate_user!, only: [:home]
def home
end
end
Pundit::AuthorizationNotPerformedError with Devise controller
You probably need to check this section from Pundit's readme.
It basically says, that when using verify_authorized
is used in after_action
, it will check if authorized
was actually called.
Pundit adds a method called
verify_authorized
to your controllers. This method will raise an exception ifauthorize
has not yet been called. You should run this method in anafter_action
to ensure that you haven't forgotten toauthorize
the action.
The same is true for verify_policy_scoped
, but for policy_scope
:
Likewise, Pundit also adds
verify_policy_scoped
to your controller. This will raise an exception in the vein ofverify_authorized
. However, it tracks ifpolicy_scope
is used instead ofauthorize
. This is mostly useful for controller actions likeindex
which find collections with a scope and don't authorize individual instances.
In your case exception is caused by the fact that you didn't called authorize in Devise::SessionsController#new
action.
I think, the best way to deal with it, is to remove after_action
checks from ApplicationController
and move them to a subclass.
Pundit raising AuthorizationNotPerformedError on a loop where authorize object is called
Thats a very creative solution - however there is a much way simpler and better way to deal with inserting / updating multiple records.
class Hotel
has_many :rooms
accepts_nested_attributes_for :rooms
end
accepts_nested_attributes_for
means that you can create rooms with:
@hotel = Hotel.new(
rooms_attributes: [
{ foo: 'bar' },
{ foo: 'baz' }
]
)
@hotel.save # will insert both the hotel and rooms
You would create a form like so:
<%= form_for(@hotel) |f| %>
<%= f.fields_for :rooms do |rf| %>
<%= f.foo %>
<% end %>
<% end %>
And handle it in your controller like so:
class HotelsController
def update
@hotel.update(hotel_params)
respond_with(@hotel)
end
private
def hotel_params
params.require(:hotel).permit(rooms_attributes: [ :foo ])
end
end
You can nest it deeper with multiple accepts_nested_attributes_for
and fields
for. But in general when you go more than one level down thats a serious code smell. Split it into multiple controllers instead.
Note that you don't really need a create_multiple
or update_multiple
action.
So back to the core of the question, how do I authenticate it? Keep it simple stupid.
def update
authorize @hotel
# ...
end
and handle it in your HotelPolicy
.
def update?
return false unless user
record.account.admin == user || (user.manager && (user.account == record.account))
end
Edit
Based on your description of what you want to do you could simply add a custom getter / setter on your Hotel model.
class Hotel < ActiveRecord::Base
has_many :rooms
# custom getter for binding form inputs
def number_of_rooms
rooms.count
end
# custom setter which actually creates the associated records
# this subtracts the existing number of rooms so that setting
# hotel.number_of_rooms = 50 on an existing record with 20 rooms
# will result in a total of 50 not 70.
def number_of_rooms=(number)
(number.to_i - number_of_rooms).times { rooms.new }
end
end
When you create or update a record like so:
[14] pry(main)> h = Hotel.new(number_of_rooms: 3)
=> #<Hotel:0x007feeb6ee2b20 id: nil, created_at: nil, updated_at: nil>
[15] pry(main)> h.save
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "hotels" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2016-02-08 13:17:06.723803"], ["updated_at", "2016-02-08 13:17:06.723803"]]
SQL (0.2ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.726157"], ["updated_at", "2016-02-08 13:17:06.726157"]]
SQL (0.4ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.728636"], ["updated_at", "2016-02-08 13:17:06.728636"]]
SQL (0.1ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.730291"], ["updated_at", "2016-02-08 13:17:06.730291"]]
(1.6ms) commit transaction
=> true
[16] pry(main)>
It will add build N number of associated rooms. You can then save the record normally.
You would add this to your normal hotel update / create action by simply adding a form input for number_of_rooms
:
<%= form_for(@hotel) |f| %>
<% # ... %>
<%= f.label :number_of_rooms %>
<%= f.number_field :number_of_rooms %>
<% end %>
This input will be in params[:hotel][:number_of_rooms]
. You should add it to your strong parameters whitelist.
Note that however you won't be able to set attributes per room. Follow the recommendation above for authorization.
Rails Pundit Authorization not performed
In your controller you're checking to make sure the policy scope is called with after_action :verify_policy_scoped, only: :show
but you aren't calling anything for the scope in your action.
You can use Scopes to restrict the results based on the logged in users permissions. For instance an admin user on an index screen would likely see all the results, but a non-admin could maybe only see certain records. IMO you shouldn't need scopes on a show so you should be able to remove the verify_policy_scoped
.
Related Topics
Overriding Model in Gem, Adding Callback and Methods
How to Get Gmail Oauth or Xauth Tokens with Omniauth
Ruby: Cannot Install Watir Gem on Windows
Ruby Ternary Operator Structure
Gem Install Rails Doesn't Work Due to Openssl/Etimedout in Windows
Scoping Date Attribute for This Week
In Rspec, Using Let Variable Inside Before :All Block
Ruby - Naming Convention - Letter Case for Acronyms in Class/Module Names
Exclude Option from Collection.Map in Ruby on Rails
Rails Activerecord Create or Find
Rails/Activerecord - Adapternotspecified, Even Though It Is
Exec Onlyif Registry Value Is Not Present
Detect When a Devise Session Expires