Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

How can I use CanCan on a controller without a companion Model? #22

Closed
rnhurt opened this Issue · 19 comments

8 participants

@rnhurt

I have a calendar controller which builds and displays a calendar but there is no Calendar model. When I use "load_and_authorize_resource" on the controller it fails with a "uninitialized constant" error. I realize that I probably shouldn't use "load_and_authorize_resource" in this case but I don't really know how to get CanCan to secure the controller otherwise.

Am I missing something simple?
See: http://railscasts.com/episodes/192-authorization-with-cancan questions 42 & 44 for more detail and another viewpoint.

@molte

Could you do something like this in a before_filter or in each action?

unauthorized! if cannot? :read, :calendar

Of course you should replace :read with the proper action.

@rnhurt

One way around this might be to use the +class+ option on load_and_authorize_resource to point it to another, possibly unrelated, Model. The downside is that you cannot use the surrogate Model for any authentication purposes in the Ability model but in my case (with a Calendar) that's not a big deal.

I ended up using the Ability model itself as the surrogate class and then included a "can :read, Ability" in the user rules. It seems to work OK for what I need. Am I missing something that would open a security hole? Or maybe there is a better way to do what I want?

@rnhurt

Well, my :class => Ability hack sorta works. It breaks down when I need to apply permissions beyond just :read. For example. when I'm in my Admin::ImportsController (allows the Admin to import data into the database) it allows everyone in. :(

moite: I don't think I can use the before_filter because :calendar is still undefined and doesn't really have any permissions on it. :/

Darn, this is turning out to be harder than I thought. Maybe I'm coming at the problem wrong...

UPDATE: It turns out that I can use the :class => Admin for my Imports controller (and other administrative controllers) and it works just fine. Hmmm...

@molte

I am not sure I completely understand your problem...

You have a CalendarController, but no Calendar model. You want to restrict access to the controller. Is that right?

CanCan supports using a symbol instead of a model class when defining permissions. So you can do like this:
class Ability
...
can :read, :calendar
end

class CalendarController < ApplicationController
  before_filter(:only => [:index, :show]) { unauthorized! if cannot? :read, :calendar }
  ...
end

And then don't use the load_and_authorize_resource method.

@rnhurt

Oh, so you can use a symbol instead of a class?! I think that might work after all.

Thanx!
Richard

@ryanb
Owner

As molte mentioned, use a symbol if you want to define permission on something which isn't an object or class.

@elisehuard

wouldn't be handy in this case to have a symbol you could use - this would ensure authorization at controller level without depending on having an identically named model.
for instance
authorize_resource :resource => :controller
in a controller called CalendarController would check for :calendar and not do any activerecord actions. I'm looking into a patch for this, if it's doable without breakage.

@Skulli

I tried the above before_filter in my controller but i only get
undefined method `cannot?'...

can, cannot seems to be only useable inside the actions, not outside.
Anyone know how to get that working?

@elisehuard

Skulli: you could try my (very recent) fork :)

@sg552

I also met the same problem with "rnhurt", that I want to authorize some controller which doesn't have any corresponding model. e.g.:
regular: products controller CRUD product model.
my case: tools controller only contains some helper methods, but there's no "Tool" model.

I have googled around and checked most of the document, but there's no solution for this. ( elisehuard supplied a solution, however now it's not available in current version 1.4.0)

Finally I come to this workaround: just simply define a blank model for this controller,

class Tool
end

and define the ability:
can :manage, Tool # or can not ....

done!

@ryanb
Owner

@sg552 You should still be able to pass a symbol instead of a class and define permissions on that in 1.4.0, is it not working for you?

I'm re-opening this ticket and marking it for documentation. It will be nice to have a Controller without Model wiki page.

@sg552

@ryanb,
thanks for your reply, the solution you mentioned doesn't work for me.

Preparation:

  1. there is an "ToolsController" which doesn't have any corresponding Models, just contains some helper method,e.g. generate SQL ...
  2. there are 2 roles: admin , maintainer

My expectation:

  1. admin is able to "manage" all
  2. maintainer is only able to manage ToolsController

Implementation No.1 that doesn't work:

class ToolsController < ApplicationController
  # this will throw an exception:  
  authorize_resource :resource => :controller 
end

class Ability
  include CanCan::Ability
  def initialize(user)
    if user.blank?
      # anonymous could do nothing but login
    elsif user.admin?
      can :manage, :all
    elsif user.maintainer?
      can :manage, :tools
    end
  end
end

and I got an exception that is the BEST I had even seen!

CanCan::ImplementationRemoved, 
The :resource option has been renamed back to :class, use false if no class.

so I changed the corresponding code to:

class ToolsController < ApplicationController
  authorize_resource :class => :controller
end

or:

class ToolsController < ApplicationController
  authorize_resource :class => false
end

and both of them throws CanCan::AccessDenied exception.

Implementation No.2 that works, I thought about this solution in my dinner time, even before went to bed :-)

class ToolsController < ApplicationController
  authorize_resource 
end

#used for cancan, a blank model.
class Tool
end

class Ability
  include CanCan::Ability
  def initialize(user)
    if user.blank?
      # anonymous could do nothing but login
    elsif user.admin?
      can :manage, :all
    elsif user.maintainer?
      can :manage, Tool
    end
  end
end

My environment:

windows XP( Linux at home), rails 2.3.5, ruby 1.8.7, cancan 1.4.0

@ryanb
Owner

@sg552, hmm, passing false to :class should be the right approach. Like this:

class ToolsController < ApplicationController
  authorize_resource :class => false
end

# in Ability
can :manage, :tool

Did that not work?

@sg552

Yes, it works! thanks!

I think it would be better for newbies to understand if this example appears in RDoc or give them a WIKI page. :-)

@ryanb
Owner

Yeah, I've marked this to be documented better.

@ryanb
Owner

This is now documented better on the Non Restful Controllers wiki page.

@ricardopacheco

@ryanb

and in case of a controller with namespace, I can write this way?

class Admin::ToolsController < ApplicationController
authorize_resource :class => false
end

in Ability

can :manage, [:admin, :tool]

@choyno

@ ryanb thank you for this

class ToolsController < ApplicationController
authorize_resource :class => false
end

in Ability

can :manage, :tool

@ricardopacheco

@choyno
authorize_resource :class => :tool

it's works too!

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.