Hello all. I''m trying to implement my own role-based access scheme, and I''ve hit a bit of a snag. It may be that my idea is just plain wrong, and if so, I''d appreciate gentle instruction as to how to make it more sound. My intent is to do something like class MyController < ApplicationController permit_access(:admin) permit_access(:normal_user, :only => [:this, :that]) before_filter :require_access def this end def that end def the_other end private def require_access redirect_to error_path unless verify_access(@current_user) end end The point of this endeavor is to more easily allow multiple roles to the same action. To that end, I created a module called RoleAccess that defines a permit_access method and a verify_access method. The idea is that I''d have a hash of controllers, actions, and roles that would be populated by permit_access and inspected by verify_access. The problem is that permit_access is a class method and verify_access is an instance method, so my module looks like module RoleAccess module ClassMethods def permit_access end end module InstanceMethods def verify_access end end end and I can''t figure out how to have a hash shared between ClassMethods::permit_access and InstanceMethods::verify_access. First, can this be done? If so, will someone please point me to some sample code that illustrates how to do it. I''ve been looking through various pieces of Rails source and scouring the internet, which has brought me to the point where I am, but I can''t quite get over the hump. Peace, Phillip -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
On Sep 5, 2:40 am, Phillip Koebbe <rails-mailing-l...-ARtvInVfO7ksV2N9l4h3zg@public.gmane.org> wrote:> and I can''t figure out how to have a hash shared between > ClassMethods::permit_access and InstanceMethods::verify_access. First, > can this be done? If so, will someone please point me to some sample > code that illustrates how to do it. I''ve been looking through various > pieces of Rails source and scouring the internet, which has brought me > to the point where I am, but I can''t quite get over the hump. >Use your favourite class variable pattern and then do self.class.permitted_actions or whatever you call it. Fred --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
> Use your favourite class variable pattern and then do > self.class.permitted_actions or whatever you call it. > > FredHi Fred. Funny enough, when I initially wrote "Hello all", I mused to myself that I should probably just write "Hello Fred". Heh heh. Thanks for the instruction. You are a quite a help to a lot of people. I was watching the Ruby Hero awards the other day, and was thinking that you deserve one of those as well. Cheers to you! Now, if I could pick your (or anyone else''s for that matter) brain a bit more, I''m having another problem with my access scheme. The original post was just generalities, so I''ll use specific code this time. In application.rb, I have class ApplicationController < ActionController::Base extend RoleAccess init_role_access protected def require_access redirect_to person_path unless verify_access(@current_person) end end and in an actual controller, this class SportsController < ApplicationController permit_access(:system_administrator) permit_access(:oranization_administrator, [:index, :show, :update]) permit_access(:sport_administrator, [:show, :update]) before_filter :require_logged_in before_filter :require_access ... end and finally, lib/role_access.rb looks like module RoleAccess def init_role_access include InstanceMethods extend ClassMethods @@access_map ||= {} end def self.access_map @@access_map end module ClassMethods def permit_access(*args) access_map = RoleAccess::access_map #debugger controller = self.controller_name.to_sym #******************************************** # Problem with next line, see comment below all_actions = self.action_methods.collect { |action| action.to_sym } #******************************************** options = args.last.is_a?(Hash) ? args.pop.symbolize_keys : {} by = args.first.is_a?(Array) ? args.first : [args.first] return if by.size.zero? if options.has_key?(:only) only = options[:only] || [] to = all_actions.select { |a| only.include?(a) } elsif options.has_key?(:except) except = options[:except] || [] to = all_actions.delete_if { |a| except.include?(a) } end to ||= all_actions return if to.size.zero? access_map[controller] = {} unless access_map.has_key?(controller) to.each do |action| access_map[controller][action] = [] unless access_map[controller].has_key?(action) by.each do |role| access_map[controller][action] << role unless access_map[controller][action].include?(role) end end end # permit_access end # module ClassMethods module InstanceMethods protected def verify_access(current_person) #debugger access_map = RoleAccess::access_map controller = controller_name.to_sym action = action_name.to_sym # if the controller is not explicitly listed, allow access return true unless access_map.has_key?(controller) # if the action is not explicitly listed, allow access return true unless access_map[controller].has_key?(action) # if we get here, the controller and action are both in access_map...check to see # if the user can access the action roles = access_map[:controller][:action] roles.each do |role| role_method = "#{role}?" return true if current_person.respond_to?(role_method) && current_person.send(role_method) end return false end # verify_access(current_person) end # module InstanceMethods end My intent, if it''s not clear, is to grant access to all the actions to system administrators, and access to selected actions to organization and sport administrators. It appears that sharing @@access_map is now working, but the problem I am now having relates to the line indicated above: all_actions = self.action_methods.collect { |action| action.to_sym } If I comment out all of the lines related to loading RoleAccess (as if it never existed) and go into script/console, SportController.action_methods returns>> SportsController.action_methods=> #<Set: {"edit", "delete", "show", "new", "update", "destroy", "index", "create"}> When trying to use RoleAccess and debugging the aforementioned line, I see that self is SportsController, yet action_methods returns an empty set. /Users/phillip/dev/projects/sports/sports_dev/lib/role_access.rb:17 controller = self.controller_name.to_sym (rdb:27) p self SportsController (rdb:27) p self.respond_to?(:action_methods) true (rdb:27) p self.action_methods #<Set: {}> (rdb:27) p SportsController.action_methods #<Set: {}> (rdb:27) So the long and short of it is when I don''t pass any actions in the call permit_access(:system_administrator) I want to grab all of the actions dynamically, but the fact that self.action_methods is empty results in the system administrator being granted access to nothing. Ideas? Peace, Phillip -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
On 5 Sep 2008, at 15:28, Phillip Koebbe wrote:> >> Use your favourite class variable pattern and then do >> self.class.permitted_actions or whatever you call it. >> >> Fred > > Hi Fred. > > Funny enough, when I initially wrote "Hello all", I mused to myself > that > I should probably just write "Hello Fred". Heh heh. > > Thanks for the instruction. You are a quite a help to a lot of > people. I > was watching the Ruby Hero awards the other day, and was thinking that > you deserve one of those as well. Cheers to you! > > Now, if I could pick your (or anyone else''s for that matter) brain a > bit > more, I''m having another problem with my access scheme. The original > post was just generalities, so I''ll use specific code this time. > > In application.rb, I have > > class ApplicationController < ActionController::Base > extend RoleAccess > init_role_accessAt this point, no methods have yet been defined so it''s normal that action_methods is empty. You also want to be careful with @@ - since you first define @@access_map on ApplicationController, all controllers will share it (I wrote up some stuff on that not too long ago at http://www.spacevatican.org/2008/8/19/fun-with-class-variables) one minor thing you could do would be to use the self.included hook on modules. This is called when the module is included so you could do just include RoleAccess and do your init_role_access stuff from that hook Fred> > > protected > > def require_access > redirect_to person_path unless verify_access(@current_person) > end > end > > and in an actual controller, this > > class SportsController < ApplicationController > permit_access(:system_administrator) > permit_access(:oranization_administrator, [:index, :show, :update]) > permit_access(:sport_administrator, [:show, :update]) > > before_filter :require_logged_in > before_filter :require_access > > ... > end > > and finally, lib/role_access.rb looks like > > module RoleAccess > def init_role_access > include InstanceMethods > extend ClassMethods > @@access_map ||= {} > end > > def self.access_map > @@access_map > end > > module ClassMethods > def permit_access(*args) > access_map = RoleAccess::access_map > > #debugger > controller = self.controller_name.to_sym > > #******************************************** > # Problem with next line, see comment below > all_actions = self.action_methods.collect { |action| > action.to_sym > } > #******************************************** > > options = args.last.is_a?(Hash) ? args.pop.symbolize_keys : {} > by = args.first.is_a?(Array) ? args.first : [args.first] > > return if by.size.zero? > > if options.has_key?(:only) > only = options[:only] || [] > to = all_actions.select { |a| only.include?(a) } > elsif options.has_key?(:except) > except = options[:except] || [] > to = all_actions.delete_if { |a| except.include?(a) } > end > > to ||= all_actions > > return if to.size.zero? > > access_map[controller] = {} unless access_map.has_key? > (controller) > > to.each do |action| > access_map[controller][action] = [] unless > access_map[controller].has_key?(action) > > by.each do |role| > access_map[controller][action] << role unless > access_map[controller][action].include?(role) > end > end > end # permit_access > end # module ClassMethods > > module InstanceMethods > protected > > def verify_access(current_person) > #debugger > access_map = RoleAccess::access_map > > controller = controller_name.to_sym > action = action_name.to_sym > > # if the controller is not explicitly listed, allow access > return true unless access_map.has_key?(controller) > > # if the action is not explicitly listed, allow access > return true unless access_map[controller].has_key?(action) > > # if we get here, the controller and action are both in > access_map...check to see > # if the user can access the action > roles = access_map[:controller][:action] > roles.each do |role| > role_method = "#{role}?" > return true if current_person.respond_to?(role_method) && > current_person.send(role_method) > end > > return false > end # verify_access(current_person) > end # module InstanceMethods > end > > My intent, if it''s not clear, is to grant access to all the actions to > system administrators, and access to selected actions to organization > and sport administrators. It appears that sharing @@access_map is now > working, but the problem I am now having relates to the line indicated > above: > > all_actions = self.action_methods.collect { |action| action.to_sym } > > If I comment out all of the lines related to loading RoleAccess (as if > it never existed) and go into script/console, > SportController.action_methods returns > >>> SportsController.action_methods > => #<Set: {"edit", "delete", "show", "new", "update", "destroy", > "index", "create"}> > > When trying to use RoleAccess and debugging the aforementioned line, I > see that self is SportsController, yet action_methods returns an empty > set. > > /Users/phillip/dev/projects/sports/sports_dev/lib/role_access.rb:17 > controller = self.controller_name.to_sym > (rdb:27) p self > SportsController > (rdb:27) p self.respond_to?(:action_methods) > true > (rdb:27) p self.action_methods > #<Set: {}> > (rdb:27) p SportsController.action_methods > #<Set: {}> > (rdb:27) > > So the long and short of it is when I don''t pass any actions in the > call > > permit_access(:system_administrator) > > I want to grab all of the actions dynamically, but the fact that > self.action_methods is empty results in the system administrator being > granted access to nothing. > > Ideas? > > Peace, > Phillip > -- > Posted via http://www.ruby-forum.com/. > > >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Frederick Cheung wrote:> At this point, no methods have yet been defined so it''s normal that > action_methods is empty. >Well, hm. So they aren''t. Going to have to think about that one. I guess I need to look at the filter code some more to see how Rails gets around that.> You also want to be careful with @@ - since you first define > @@access_map on ApplicationController, all controllers will share it > (I wrote up some stuff on that not too long ago at > http://www.spacevatican.org/2008/8/19/fun-with-class-variables) >I thought of that, which is why I start by adding a key for the controller. This sort of thing is pretty new to me, so I might be missing something fundamental. If I just make it @access_map, will each controller have its own? I''ll read your article and see if I understand better afterward.> one minor thing you could do would be to use the self.included hook on > modules. This is called when the module is included so you could do just > > include RoleAccess and do your init_role_access stuff from that hook >I tried do this different ways, but didn''t have any success. Part of my problem might be that I''m a bit confused by extend versus include. Based on what I''ve been reading, I thought that include was used to add methods to classes whereas extend added methods to instances (objects). The current form of extend RoleAccess is actually the reverse of what I thought I needed to have, so my grip on that one is apparently pretty weak. I''ll experiment with them and see if can''t get it cleaner.> FredThanks, Fred. Your insight and patience are very much appreciated. Phillip -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
On 5 Sep 2008, at 16:46, Phillip Koebbe wrote:> > Frederick Cheung wrote: > >> At this point, no methods have yet been defined so it''s normal that >> action_methods is empty. >> > > Well, hm. So they aren''t. Going to have to think about that one. I > guess > I need to look at the filter code some more to see how Rails gets > around > that.Personally I''d do it when the filter runs, not ahead of time.> > >> You also want to be careful with @@ - since you first define >> @@access_map on ApplicationController, all controllers will share it >> (I wrote up some stuff on that not too long ago at >> http://www.spacevatican.org/2008/8/19/fun-with-class-variables) >> > > I thought of that, which is why I start by adding a key for the > controller. This sort of thing is pretty new to me, so I might be > missing something fundamental. If I just make it @access_map, will > each > controller have its own? I''ll read your article and see if I > understand > better afterward. > >> one minor thing you could do would be to use the self.included hook >> on >> modules. This is called when the module is included so you could do >> just >> >> include RoleAccess and do your init_role_access stuff from that hook >> > > I tried do this different ways, but didn''t have any success. Part of > my > problem might be that I''m a bit confused by extend versus include. > Based > on what I''ve been reading, I thought that include was used to add > methods to classes whereas extend added methods to instances > (objects). > The current form of > > extend RoleAccess >Have a look at http://blog.jayfields.com/2006/05/ruby-extend-and-include.html or http://www.dcmanges.com/blog/27 Fred> is actually the reverse of what I thought I needed to have, so my grip > on that one is apparently pretty weak. I''ll experiment with them and > see > if can''t get it cleaner. > >> Fred > > Thanks, Fred. Your insight and patience are very much appreciated. > > Phillip > -- > Posted via http://www.ruby-forum.com/. > > >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Frederick Cheung wrote:> Personally I''d do it when the filter runs, not ahead of time.Thanks for saying that. It didn''t register at first what you were talking about, but it finally sank in and I followed that route. Wow, it really simplified things. I decided to take ApplicationController out of the picture completely and include RoleAccess in the specific controllers in which I need greater flexibility. Coupling that with your suggestion made it all cleaner. Now I can just include RoleAccess in SportsController and not worry about whether it''s a class method or an instance method. Since I am calling permit_access and verify_access from within require_access, they are both instance methods. And that also solves the problem of action_methods not being populated. The only difference is now I have to call self.class.action_methods but that''s okay. The only downside, and it''s purely aesthetic, is not having the statements about access control at the top of the controller. When I come back to this later, or if someone else ever has to maintain this code (which might happen), it won''t be as obvious what''s going on. I suppose I could resort to that legacy construct we used to hear so much about. Oh what was it? A ''comment'', I think.> Have a look at > http://blog.jayfields.com/2006/05/ruby-extend-and-include.html > or http://www.dcmanges.com/blog/27 >Thanks again. Those resources helped. I''m going to have to put those in Evernote so I don''t forget about them. So my module is much cleaner now, but I''m still confused by the access to the instance variable. I now have module RoleAccess @access_map ||= {} def self.access_map @access_map end # access_map private def permit_access(*args) access_map = RoleAccess::access_map ... end def verify_access(current_person = nil) access_map = RoleAccess::access_map ... end end I tried various ways to access @access_map without having to use that ugly RoleAccess::access_map, but nothing worked. I tried to apply what you wrote in the blog post you mentioned earlier, but I believe there are some differences between classes and modules in this regard. This approach does work, but I''d like it to be a bit cleaner. Again, thanks for the help and patience. I really enjoy Ruby/Rails, but it''s taking me a bit of effort to wrap my head around a lot of it.> FredPhillip -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---