How do I setup my CanCanCan permissions correctly?
Asked Answered
H

4

6

I am a little confused about how to configure CanCanCan properly.

For starters, do I have to add load_and_authorize_resource to every controller resource I want to restrict access to?

This is what I would like to do:

  • Admin can manage and access all controllers and actions
  • Editor can read all, manage :newsroom, and can manage all Posts
  • Member can read every Post and can create & update Posts (not edit/delete/anything else), cannot access the newsroom. The difference between an update & edit post in our business rules is that an update is creating a new post that is a child post of the current post. So it isn't an edit. Just a new record with an ancestry association.
  • Guest can read every Post, but cannot create Posts nor access the Newsroom.

This is what my ability.rb looks like:

class Ability
  include CanCan::Ability
  def initialize(user)
    user ||= User.new # guest user (not logged in)
    #Admin
   if user.has_role? :admin
        can :manage, :all
        can :manage, :newsroom
   # Editor
    elsif user.has_role? :editor
      can :read, :all
      can :manage, :newsroom
      can :manage, Post
    #Member
    elsif user.has_role? :member
        can :read, :all
        can :create, Post
        can :status, Post
        can :update, Post do |post|
            post.try(:user) == user
        end
    #Guest
    else
        can :read, :all
        can :create, Post
        can :status, Post
    end    
  end
end

In my routes.rb I have this:

  authenticate :user, lambda { |u| u.has_role? :admin or :editor } do
    get 'newsroom', to: 'newsroom#index', as: "newsroom"
    get 'newsroom/published', to: 'newsroom#published'
    get 'newsroom/unpublished', to: 'newsroom#unpublished'    
  end

What is happening though, is when I am logged in with a user that has not been assigned any roles (i.e. what I want to be a "Guest"), they can access the Newsroom.

When I try to edit a post with the role of :member, it gives me a "Not authorized to edit post" error (which is correct).

I just can't quite lockdown the Newsroom and I am not sure why.

Honegger answered 11/12, 2014 at 0:21 Comment(0)
H
2

For what it's worth, I had to setup my NewsroomController like this:

class NewsroomController < ApplicationController
  authorize_resource :class => false

This is what the working version of my ability.rb looks like after I got it to work with the permissions I needed:

#Roles
#Admin
 if user.has_role? :admin
      can :manage, :all
 # Editor
  elsif user.has_role? :editor
    can :manage, :newsroom
    can :manage, Post
  #Member
  elsif user.has_role? :member
      can [:read, :create, :status], Post
      can :update, Post do |post|
        post.try(:user) == user
      end
  #Guest
  else
      can [:read, :status], Post
  end
Honegger answered 17/12, 2014 at 22:41 Comment(0)
N
11

You do not need to use load_and_authorize_resource in every controller. That is a convenience macro that does two things. First it assigns an instance variable with the record(s) assumed for the current controller and action. It then authorizes the resource. For some controller actions the first step might be wrong, so you want to load your resource and then authorize it manually. An example from the Railscasts episode about CanCan is like this:

def edit
  @article = Article.find(params[:id])
  unauthorized! if cannot? :edit, @article
end

You can also do it like in the example on the CanCan Wiki for authorizing controllers:

def show
  @project = Project.find(params[:project])
  authorize! :show, @project
end

Or you can just use authorize_resource and take care of loading it yourself. In the end, you must make sure that CanCan is used for authorization somehow (controller macro or in each action). Regarding your abilities, I think you want something like this:

class Ability
  include CanCan::Ability
  def initialize(user)
    user ||= User.new # guest user (not logged in)
    #Admin
    if user.has_role? :admin
      can :manage, :all
    # Editor
    elsif user.has_role? :editor
      can :read, :all
      can :manage, :newsroom
      can :manage, Post
    #Member
    elsif user.has_role? :member
      can :read, :all
      can :create, Post
      can :status, Post
      can :update, Post do |post|
        post.try(:user) == user
      end
    #Guest
    else
      can :read, :all
      cannot [:index, :published, :unpublished], :newsroom
    end
  end
end

And here's an example like how you might be able to authorize your newsroom:

class ToolsController < ApplicationController
  authorize_resource :class => false
  def show
  # automatically calls authorize!(:show, :tool)
  end
end

A last personal note about CanCan is that I wouldn't suggest it for new projects since it isn't actively maintained anymore and that I found it a bit counterintuitive when defining abilities. That said, CanCan is one of the most well-documented gems that I have worked with, especially the wiki has loads of examples and explanations.

Nonchalance answered 16/12, 2014 at 9:48 Comment(3)
There is now a community version called CanCanCan that is actively maintained - github.com/CanCanCommunity/cancancan. Thanks for this answer though, will try it out and let you know. The only thing that is kinda weird is your last example re: authorizing the newsroom. So I should just use authorize_resource :class => false to authorize my Newsroom class?Honegger
This answer doesn't actually solve my issue. The main issue is that I can still get access to the newsroom with a role that I shouldn't be able to. E.g. I don't want a user with a role of :member to be able to access it, and they still can.Honegger
Sorry for not seeing your comments until now. Thanks for the link to the maintained CanCan project. I see that you finally got it working yourself, correct?Nonchalance
P
3
can :read, :all

means user has permission to read all the resources of your app. It should be

can :read, Post

also add

cannot :manage, :newsroom

where you do not want access to newsroom. The order in which you specify permissions matters. As others have already mentioned, 'load_and_authorize_resource' is optional. Only 'authorize resource' is needed to authorize all actions of a controller. If you skip these then you can 'authorize' individual controller actions.

Avoid using block for ability unless absolutely necessary. For instance if Post has a user_id in it then you could do

can :update, Post, user_id: user.id

Lastly, 'class => false' is used where you do not have a model backing your controller. i.e you do not have a model called 'Newsroom' but you have a controller called 'NewsroomsController'.

Prussia answered 20/12, 2014 at 18:23 Comment(0)
H
2

For what it's worth, I had to setup my NewsroomController like this:

class NewsroomController < ApplicationController
  authorize_resource :class => false

This is what the working version of my ability.rb looks like after I got it to work with the permissions I needed:

#Roles
#Admin
 if user.has_role? :admin
      can :manage, :all
 # Editor
  elsif user.has_role? :editor
    can :manage, :newsroom
    can :manage, Post
  #Member
  elsif user.has_role? :member
      can [:read, :create, :status], Post
      can :update, Post do |post|
        post.try(:user) == user
      end
  #Guest
  else
      can [:read, :status], Post
  end
Honegger answered 17/12, 2014 at 22:41 Comment(0)
H
1

For starters, do I have to add load_and_authorize_resource to every controller resource I want to restrict access to?

Yes.

What is happening though, is when I am logged in with a user that has not been assigned any roles (i.e. what I want to be a "Guest"), they can access the Newsroom.

From the guest role above:

...
#Guest
else
    can :read, :all
    can :create, Post
    can :status, Post
end    

This gives a guest read access to everything and the ability to create posts. If you want your Guests to only be able to read posts it should be:

...
#Guest
else
    can :read, Post
    # can :status, Post # maybe you want this aswell
end   
Handily answered 16/12, 2014 at 8:6 Comment(2)
Also, @Honegger can get rid of the authenticate in the routes, as CanCanCan will be denying access to the newsroom if can :read, :all is removed from the Guest. Good to remove duplication.Irmairme
I want a Guest to be able to read all posts, create posts, and status posts. But I don't want them to be able to access the Newsroom and I don't want them to be able to edit/update/delete any Post.Honegger

© 2022 - 2024 — McMap. All rights reserved.