Andy Harvey
Andy Harvey

Reputation: 12663

How to authorize namespace, model-less controllers using CanCanCan?

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

Answers (3)

Andy Harvey
Andy Harvey

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

Markus Andreas
Markus Andreas

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

Joe Atzberger
Joe Atzberger

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

Related Questions