Authorizing STI subclasses via parent controller #368

Open
gamov opened this Issue May 13, 2011 · 8 comments

Comments

Projects
None yet
5 participants

gamov commented May 13, 2011

I use STI with a single controller but I'm having difficulty authorizing any actions with an instance (index works fine).
STI single controller: http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html

My setup:
Company is the parent class and table in DB. There is not records of this class, only subclasses:
Company > Customer
Company > Supplier

class Customer
  belongs_to :business_site
end
class User
  belongs_to :business_site
end

I'm trying to authorize and restrict (to the user.business_site) instances of Customer or Supplier via the CompaniesController without success.
My Abilities:

can :access, :companies, :business_site_id => user.business_site_id
can :access, :customers, :business_site_id => user.business_site_id

The CompaniesController > index works perfectly, it's when I want to access CompaniesController > show, edit, etc that I get:
CanCan::InsufficientAuthorizationCheck in CompaniesController#show
I tried to access the same instance via a CustomerController > show and it works.
I tried to add this to my CompaniesController:
load_and_authorize_resource :customers
But I still get the insufficentAuthCheck.
I'm using CanCan 2.0

Owner

ryanb commented May 13, 2011

Thanks for reporting this. Unfortunately CanCan 2.0 does not yet work well for edge cases in the controller like this. It is one reason it is not released yet. I hope to work on this soon.

gamov commented May 13, 2011

I knew the risk :) Happy to help by reporting issues.

gamov commented May 18, 2011

I have a few questions regarding this issue (basically, I'm looking for a workaround for the time being):

  • I tried authorizing manually the instances for show, edit, etc but it doesn't seem to work:
# WITHOUT the load_and_authorize method in the controller
@company = Company.find(params[:id])
#Rails smartly convert @company to the correct Customer class if @company is a Customer
authorize! :show, @company #this still doesn't work but it should, shouldn't it?
  • Do you think downgrading to 1.6 will help?
Owner

ryanb commented May 18, 2011

Are you still getting the InsufficientAuthorizationCheck exception? Try marking it as fully authorized with this command.

current_ability.fully_authorized! :show, :companies

Place that at the end of the show action. I'll be working on improving this behavior.

gamov commented May 18, 2011

Yes, it was still InsufficientAuthorizationCheck.
Cool, your workaround worked. thanks! I'll check CanCan code to see why.

fignuts commented Apr 18, 2012

I am having similar issues with cancan 2.0.

I have an Issue model with many subclasses (STI) for specific issue types on which my users may vote_yes or vote_no through IssuesController.

The behavior that I am seeing with cancan 2.0 when trying to load_and_authorize_resource for my vote_yes action on an issue of type Issue::MySubIssue is the following:

  • With can :vote_yes, :issues this in my ability file my request reaches the controller action, but authorization on the instance variable raises CanCan::Unauthorized.
  • Adding can :vote_yes 'issue/my_sub_issues' in my ability file also results in CanCan::Unauthorized.
  • Changing this line to can :vote_yes 'issue/my_sub_issues'.to_sym passes the authorization check on the instance variable, but results in CanCan::InsufficientAuthorizationCheck being raised.
  • Putting current_ability.fully_authorized! :vote_yes, :issues in the controller action in addition to the above seems to result in correct behavior.

I believe this strange behavior is coming from rule.rb in the matches_subject? function

def matches_subject?(subject)
  subject = subject_name(subject) if subject_object? subject
  @expanded_subjects.include?(:all) || @expanded_subjects.include?(subject.to_sym) # || matches_subject_class?(subject)
end

where subject is set to subject.class.to_s.underscore.pluralize.to_sym (which evaluates to symbol 'issue/my_sub_issues' in this case) by the subject_name function which causes the match to fail for STI subjects unless the ability is specified as can :do_something, 'my_base_class/my_sub_class_plural'.to_sym

It seems that the missing functionality is two parts:

  1. match strings to class subjects (don't require to_sym on a string in ability file for it to match a subject)
  2. match STI classes to their parent class' rules

It looks like the second piece of functionality was part of cancan at one point based on the commented || matches_subject_class?(subject) in matches_subject? which is defined as:

# TODO deperecate this
def matches_subject_class?(subject)
  @expanded_subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
end

But I am not sure how to properly reintegrate this functionality or what I might break if I were to do so (my ruby-fu is weak). Any ideas about how to best handle STI with cancan would be appreciated. For now I am just going to use the workaround that I found.

I had issue with CanCan STI access. I googled this post and it helped me SOLVE IT, so for sake those who have similar issue like me:

I got model

Option  < ActiveRecord::Base  #app/model/option.rb

...and got several STIs on it

Option::Alert < Option  do
   # this is fixing the STI Rails routing problem http://stackoverflow.com/questions/4507149/best-practices-to-handle-routes-for-sti-subclasses-in-rails
   def self.model_name
       Option.model_name
   end
end

Option::Foo < Option  do
   def self.model_name
       Option.model_name
   end
end

Option::Bar < Option  do
   def self.model_name
       Option.model_name
   end
end

or you can do above dynamically https://gist.github.com/3414278

in app/model/ability.rb

def initialize
   can :access, :options
   can :access, 'option/foos'
   can :access, 'option/bars'
   can :access, 'option/alerts'  #yes, must be plural 
end

a/c/options_controller.rb

class OptionsController <  InheritedResources::Base  
  load_and_authorize_resource 
end

don't mind that I'm using Inherited Resource on this, it suppose to work the same without it

...and works

xhoy commented Apr 10, 2014

Dear submitter, Since cancan/raynB hasn't been active for more than 6 months and no body else then ryam himself has commit permissions the cancan project is on a stand still.
Since cancan has several issues including missing support for rails 4 cancan is moving forward to cancancan. More details on: #994

If your feel that your pull request or bug is still applicable (and hasn't been merged in to cancan) it would be really appreciated if you would resubmit it to cancancan (https://github.com/cancancommunity/cancancan)

We hope to see you on the other side!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment