Context Aware Authorization Using Cancan

Context aware authorization using CanCan

Ok, I solved the problem. My use case is briefly mentioned in the beginning of the CanCan README and I missed it. You can define new Ability classes in app/models/ that take in a different parameter other than current_user. To do so, you put the following in your controller:

def current_ability 
if params[:controller] == 'leagues'
@current_ability = LeagueAbility.new(current_user_league_relation)
elsif params[:controller] == 'league_relations'
@current_ability = LeagueRelationAbility.new(current_user_league_relation)
else
@current_ability = Ability.new(current_user)
end
end

Now you can create league_ability.rb in app/models/.

class LeagueAbility
include CanCan::Ability

def initialize(league_relation)
league_relation ||= LeagueRelation.new

if league_relation.owner?
can :manage, League, :id => league_relation.league_id
elsif league_relation.moderator?
can :manage, League, :id => league_relation.league_id
cannot [:delete, :destroy], League
else
can :read, League
can :create, League
end
end
end

One thing to note is that this relies on your application controller calling a method in a child class. Hope that helps!

CanCan: load_and_authorize_resource in namespace other than that of MainApp

It seems to be a bug in CanCan::ControllerResource#namespace:

def namespace
@params[:controller].split("::")[0..-2]
end

As you see, it tries to split controller path by :: but it comes in the form of my_engine/my_controller.

So the fix is dumb simple:

def namespace
@params[:controller].split("/")[0..-2]
end

Wonder how they could miss such a stupid bug for so long. Shall send them a pull request.

P.S. Have just signed up to answer 8)

Is it possible to raise an exception other than Access Denied with cancan?

I did some digging and it doesn't look like CanCan supports the kind of granularity for exceptions you require.

So... why not use current_user.status in your controller as well?

def index
authorize! :show, Organization
rescue CanCan::AccessDenied => e
if current_user.status
render_403
else
render_404
end
end

(Or, if duplicate code/logic is a concern, put them both in a method you call both in your ability class and the controller.)

Then simplify the snippet from your ability class down to the following:

can :show, Organization

BTW, CanCan hasn't been maintained in years. You may want to switch to the more actively maintained CanCanCan (but I think you'll require the same solution above).

Also, if you end up showing different pages depending on the user's status (whatever that status may communicate), you may be inadvertently leaking information that users and/or non-users aren't supposed to see, similar to what's explained here.

Cancan authorization in Non RESTful controller

I tried to debug following the link Debugging Abilities and figured out what was wrong.

The abilities were correct, I was doing a small mistake by specifying them in the wrong order.

For others coming on to this page, if you face any problems with Cancan abilities not working, try to debug using the above link. You will eventually figure out whats not working and why.

Defining abilities in more complex environment with role and group models

I had the same problem just the other day. I figured out the solution after reading the CanCan readme, which you should do if you haven't yet.

You can view my solution here: Context aware authorization using CanCan

To give you an answer more specific to your use case, do the follow:

In your application controller you need to define some logic which will pick your abilities.

class ApplicationController < ActionController::Base
check_authorization

def current_ability
if <no group selected logic> # Maybe: params[:controller] == 'groups'
@current_ability = NoGroupSelectedAbility.new(current_user)
else
@current_ability = GroupSelectedAbility.new(current_user)
end
end

# Application Controller Logic Below
end

You'll then need to create a new ability (or abilities) in your app/models/ folder. You can also do cool stuff like this:

if request.path_parameters[:controller] == groups
@current_ability = GroupsAbility.new(current_group_relation)
end

Where current_group_relation is defined in app/controllers/groups_controller.rb. This will give you specific abilities for specific controllers. Remember that a parent classes can call methods in child classes in Ruby. You can define a method in your controller, and call it from ApplicationController, as long as you are certain what controller is currently being used to handle the request.

Hope that helps.

EDIT: I wanted to show you what a custom ability looks like.

# File path: app/models/group_ability.rb
class GroupsAbility
include CanCan::Ability

# This can take in whatever you want, you decide what to argument to
# use in your Application Controller
def initialize(group_relation)
group_relation ||= GroupRelation.new

if group_relation.id.nil?
# User does not have a relation to a group
can :read, all
elsif group_relation.moderator?
# Allow a mod to manage all group relations associated with the mod's group.
can :manage, :all, :id => group_relation.group.id
end
end
end

How to modify cancan load and authorize resource to load resource using different id

The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separate before_filter.

class EventsController < ApplicationController
before_filter :find_event
load_and_authorize_resource

private

def find_event
@event = Event.find(params[:event_id])
end
end

Read more in the docs: https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-controller-actions

Should I skip authorization, with CanCan, of an action that instantiates a resource?

I found my problem. CanCan was not the issue at all! The following line in my CardSet controller was throwing the exception and redirecting my guest (not logged in) users to the log in page:

before_filter :authenticate_user!, :except => [:show, :index]

I changed it to read:

before_filter :authenticate_user!, :except => [:show, :index, :random]

And now the code works as intended: Guest users can view the new random sets created, but cannot 'Save' them unless they first log in.

So, my real problem was with Devise (or, actually, my Devise configuration) and not with CanCan at all.



Related Topics



Leave a reply



Submit