Reputation: 12663
What is the correct way to authorize and check abilities for a namespaced, model-less controller using CanCanCan?
After much googling and reading the wiki, I currently have
#controllers/namespaces/unattacheds_controller.rb
def Namespaces::UnattachedsController
authorize_resource class: false
def create
# does some stuff
end
end
#models/ability.rb
def admin
can [:create], :namespaces_unattacheds
end
#view/
<%= if can? :create, :namespaces_unattacheds %>
# show a create form to authorized users
<% end %>
This is not correctly authorizing the controller. Admins can see the conditional create form, but are not authorized to post to the create action.
post :create, valid_params
Failure/Error: { it { expect( flash ).to have_content "Successfully created" }
expected to find text "Successfully created"
got: "You are not authorized to access this page."
In one example, the wiki suggests creating a separate Ability class for a namespaced controller. https://github.com/CanCanCommunity/cancancan/wiki/Admin-Namespace
Is there a simpler way to achieve this? This app uses many namespaced controllers, I don't really want to create an ability class for each one.
Is there correct syntax to refer to the namespaced controller in the Ability class?
can [:create], Namespaces::Unattacheds
can [:create], :namespaces_unattacheds
can [:create], namespaces/unattacheds
????
Upvotes: 2
Views: 2810
Reputation: 12663
Adding an answer because this question seems to be getting a reasonable number of visitors. Hope it can help someone.
I received some good suggestions here, that helped me think through my problem. Ultimately I realised that I was fighting against CanCanCan's conventions with what I was trying to achieve.
I swapped to Pundit, which allowed me to authorise an object in the controller while referring to a specific policy class. Something like
# Admin::UserController
def update
authorize @user, policy_class: Admin::UserPolicy
end
# UserController
def update
authorize @user, policy_class: UserPolicy
end
Combined with strong params defined on the controller, I was able to ensure that only specific attributes could be updated.
This won't work for everyone. For example, this approach would not prevent admins from updating all a user's attributes if they bypass the controller (e.g., via the console). But it worked for my situation.
Upvotes: 0
Reputation: 993
Maybe not the prettiest solution but I managed to achive this by adding
skip_authorization_check
before_action { raise CanCan::AccessDenied unless current_user.can?(params[:action].to_sym, ::namespaces_unattacheds) }
If you do it like this, you can pass whatever you want from this controller to the ability class.
You need to add the can? method first to be able to use this https://github.com/CanCanCommunity/cancancan/wiki/Ability-for-Other-Users
Upvotes: 0
Reputation: 3306
It sounds like you are setting permissions on the Namespaces::Unattacheds
model, which means your controller doesn't need to do:
authorize_resource class: false
Your controller does have a model. Maybe it also inherits from ApplicationController? (That would be a logical thing to do.)
If you are trying to avoid affecting certain controller methods, use only
/except
clauses, as described here:
https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-controller-actions#choosing-actions
I don't think the namespace depth is an issue IF it matches between your model and your controller. You just need load_and_authorize_resource
and the proper form in ability.rb
:
can [:create], Namespaces::Unattacheds
Upvotes: 0