From 444439953f31b92e07c9fe7b7cef93fa2fe404ce Mon Sep 17 00:00:00 2001 From: Sharagoz Date: Fri, 20 Aug 2010 00:18:45 +0200 Subject: [PATCH 01/30] Extended the authorization rule test helpers to take the same options as the authorization engine --- lib/declarative_authorization/maintenance.rb | 24 ++++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/declarative_authorization/maintenance.rb b/lib/declarative_authorization/maintenance.rb index 792fce1e..0ce2d6e5 100644 --- a/lib/declarative_authorization/maintenance.rb +++ b/lib/declarative_authorization/maintenance.rb @@ -148,24 +148,38 @@ def assert_raise_with_user (user, *args, &block) end end - # Test helper to test authorization rules. E.g. + # Test helper to test authorization rules. # with_user a_normal_user do # should_not_be_allowed_to :update, :conferences # should_not_be_allowed_to :read, an_unpublished_conference # should_be_allowed_to :read, a_published_conference # end - def should_be_allowed_to (privilege, object_or_context) + # + # If the objects class name does not match the controller name, you can set the object and context manually + # should_be_allowed_to :create, :object => car, :context => :vehicles + # + # If you use specify the object and context manually, you can also specify the user manually, skipping the with_user block: + # should_be_allowed_to :create, :object => car, :context => :vehicles, :user => a_normal_user + def should_be_allowed_to (privilege, *args) options = {} - options[object_or_context.is_a?(Symbol) ? :context : :object] = object_or_context + if(args.first.class == Hash) + options = args.extract_options! + else + options[args[0].is_a?(Symbol) ? :context : :object] = args[0] + end assert_nothing_raised do Authorization::Engine.instance.permit!(privilege, options) end end # See should_be_allowed_to - def should_not_be_allowed_to (privilege, object_or_context) + def should_not_be_allowed_to (privilege, *args) options = {} - options[object_or_context.is_a?(Symbol) ? :context : :object] = object_or_context + if(args.first.class == Hash) + options = args.extract_options! + else + options[args[0].is_a?(Symbol) ? :context : :object] = args[0] + end assert !Authorization::Engine.instance.permit?(privilege, options) end From cac40a0842554ab697e87da9156c93cdbaa0bf9d Mon Sep 17 00:00:00 2001 From: Brad Langhorst Date: Mon, 30 Aug 2010 10:30:33 -0400 Subject: [PATCH 02/30] includeds prototype on the fly instead of assuming that it's available (makes the authorization_usages bits work even if the rest of the site is using jquery) --- app/views/authorization_rules/_show_graph.erb | 7 +++++++ app/views/authorization_rules/graph.html.erb | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/app/views/authorization_rules/_show_graph.erb b/app/views/authorization_rules/_show_graph.erb index 2f82512f..713edfd9 100644 --- a/app/views/authorization_rules/_show_graph.erb +++ b/app/views/authorization_rules/_show_graph.erb @@ -1,4 +1,11 @@ <% javascript_tag do %> + if (typeof Prototype != 'object') { + //load up prototype... it's needed here + var s = document.createElement('script'); + s.setAttribute('src','http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js'); + document.getElementsByTagName('body')[0].appendChild(s); + } + function show_graph (privilege, context, user_ids) { var params = { privilege_hierarchy: 1, diff --git a/app/views/authorization_rules/graph.html.erb b/app/views/authorization_rules/graph.html.erb index 7fdfa047..11ff1935 100644 --- a/app/views/authorization_rules/graph.html.erb +++ b/app/views/authorization_rules/graph.html.erb @@ -3,6 +3,13 @@

<%= navigation %>

<% javascript_tag do %> + if (typeof Prototype != 'object') { + //load up prototype... it's needed here + var s = document.createElement('script'); + s.setAttribute('src','http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js'); + document.getElementsByTagName('body')[0].appendChild(s); + } + function update_graph (form) { base_url = "<%= url_for :format => 'svg' %>"; $('graph').data = base_url + '?' + form.serialize(); From 1b710bfb9735597865ae88b9119d9158a2736c43 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Mon, 6 Sep 2010 13:09:53 +0200 Subject: [PATCH 03/30] Rails.root was no Pathname object prior to 2.3 (fixes #67) --- lib/declarative_authorization/authorization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index db4cb37d..8aeaa57b 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -20,7 +20,7 @@ class AuthorizationUsageError < AuthorizationError ; end # The exception is raised to ensure that the entire rule is invalidated. class NilAttributeValueError < AuthorizationError; end - AUTH_DSL_FILES = [(Rails.root || Pathname.new('')).join("config", "authorization_rules.rb").to_s] unless defined? AUTH_DSL_FILES + AUTH_DSL_FILES = [Pathname.new(Rails.root || '').join("config", "authorization_rules.rb").to_s] unless defined? AUTH_DSL_FILES # Controller-independent method for retrieving the current user. # Needed for model security where the current controller is not available. From 2d641b2fd1d7a8a4334418b28af3720ca6efd717 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Sun, 12 Sep 2010 08:07:19 +0200 Subject: [PATCH 04/30] Removed Rails gem dependency because of problems with decl_auth causing the latest Rails version to be loaded and bumped the minor gem version --- CHANGELOG | 1 + declarative_authorization.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4affa4e4..f1310973 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +** RELEASE 0.5.1 (Sep 12, 2010) ** ** RELEASE 0.5 (July 21, 2010) ** diff --git a/declarative_authorization.gemspec b/declarative_authorization.gemspec index eb229a4f..f329191e 100644 --- a/declarative_authorization.gemspec +++ b/declarative_authorization.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "declarative_authorization" - s.version = "0.5" + s.version = "0.5.1" s.required_ruby_version = ">= 1.8.6" s.authors = ["Steffen Bartsch"] @@ -13,5 +13,5 @@ Gem::Specification.new do |s| s.extra_rdoc_files = ['README.rdoc', 'CHANGELOG'] s.homepage = %q{http://github.com/stffn/declarative_authorization} - s.add_dependency('rails', '>= 2.1.0') + #s.add_dependency('rails', '>= 2.1.0') end From cefb6368046f8c1b1d99e8388eb44cc88771f931 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Sun, 12 Sep 2010 08:56:59 +0200 Subject: [PATCH 05/30] Prevent Rails 3 deprecation warnings on authorization backend routes [dgm] --- config/routes.rb | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 4693af4c..f5c49d31 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,10 +1,20 @@ -# Rails 3 depreciates ActionController::Routing::Routes -routes = (Rails.respond_to?(:application) ? Rails.application.routes : ActionController::Routing::Routes) - -routes.draw do |map| - if Authorization::activate_authorization_rules_browser? - map.resources :authorization_rules, :only => [:index], - :collection => {:graph => :get, :change => :get, :suggest_change => :get} - map.resources :authorization_usages, :only => :index +if Authorization::activate_authorization_rules_browser? + if Rails.respond_to?(:application) + Rails.application.routes.draw do + resources :authorization_rules, :only => [:index] do + collection do + get :graph + get :change + get :suggest_change + end + end + resources :authorization_usages, :only => :index + end + else + ActionController::Routing::Routes.draw do |map| + map.resources :authorization_rules, :only => [:index], + :collection => {:graph => :get, :change => :get, :suggest_change => :get} + map.resources :authorization_usages, :only => :index + end end -end \ No newline at end of file +end From c1a2f93220714c396987dab4a594a9d8a8484dea Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Wed, 29 Sep 2010 14:12:28 +0200 Subject: [PATCH 06/30] Fix for defining decl_auth before_filter multiple times (Fixes #72) --- lib/declarative_authorization/in_controller.rb | 2 ++ test/controller_test.rb | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index 3ef29259..0031f0b9 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -274,6 +274,8 @@ def filter_access_to (*args, &filter_block) context = options[:context] actions = args.flatten + # prevent setting filter_access_filter multiple times + skip_before_filter :filter_access_filter before_filter :filter_access_filter filter_access_permissions.each do |perm| diff --git a/test/controller_test.rb b/test/controller_test.rb index 95be2fab..0c001214 100644 --- a/test/controller_test.rb +++ b/test/controller_test.rb @@ -198,6 +198,7 @@ def test_filter_access_all ################## class LoadMockObjectsController < MocksController + before_filter { @@load_method_call_count = 0 } filter_access_to :show, :attribute_check => true, :model => LoadMockObject filter_access_to :edit, :attribute_check => true filter_access_to :update, :delete, :attribute_check => true, @@ -207,9 +208,18 @@ class LoadMockObjectsController < MocksController end filter_access_to :view, :attribute_check => true, :load_method => :load_method def load_method + self.class.load_method_called MockDataObject.new(:test => 2) end define_action_methods :show, :edit, :update, :delete, :create, :view + + def self.load_method_called + @@load_method_call_count ||= 0 + @@load_method_call_count += 1 + end + def self.load_method_call_count + @@load_method_call_count || 0 + end end class LoadObjectControllerTest < ActionController::TestCase tests LoadMockObjectsController @@ -287,7 +297,12 @@ def test_filter_access_with_object_load_custom request!(MockUser.new(:test_role), "view", reader) assert @controller.authorized? + assert_equal 1, @controller.class.load_method_call_count + request!(MockUser.new(:test_role_2), "view", reader) + assert !@controller.authorized? + assert_equal 1, @controller.class.load_method_call_count + request!(MockUser.new(:test_role), "update", reader) assert @controller.authorized? end From 528cb9d4ec7211445a0be660be39afe6296a2a6c Mon Sep 17 00:00:00 2001 From: dbloete Date: Sun, 10 Oct 2010 20:18:40 +0200 Subject: [PATCH 07/30] Added instructions for testing with RSpec --- README.rdoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rdoc b/README.rdoc index c8ad3659..a80167b0 100644 --- a/README.rdoc +++ b/README.rdoc @@ -332,6 +332,12 @@ In your test_helper.rb, to enable the helpers add ... end +For using the test helpers with RSpec, just add the following lines to your +spec_helper.rb (somewhere after require 'spec/rails'): + + require 'declarative_authorization/maintenance' + include Authorization::TestHelper + Now, in unit tests, you may deactivate authorization if needed e.g. for test setup and assume certain identities for tests: From 46dcb21450dd27efee87b55fce84e449cd961570 Mon Sep 17 00:00:00 2001 From: dbloete Date: Mon, 11 Oct 2010 10:38:35 +0200 Subject: [PATCH 08/30] Added example for RSpec --- README.rdoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.rdoc b/README.rdoc index a80167b0..759082b7 100644 --- a/README.rdoc +++ b/README.rdoc @@ -353,6 +353,19 @@ setup and assume certain identities for tests: end end end + +Or, with RSpec, it would work like this: + + describe Employee do + it "should read" do + without_access_control do + Employee.create(...) + end + with_user(admin) do + Employee.find(:first) + end + end + end In functional tests, get, posts, etc. may be tested in the name of certain users: From f680dc30f8f706ee02aabc3e56c647a8e13b9ddc Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Thu, 28 Oct 2010 15:44:29 +0200 Subject: [PATCH 09/30] Fixed queries for combinations of :join_by :and and if_permitted_to with OR'ed rules --- .../authorization.rb | 19 ++++++-- test/model_test.rb | 46 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index 8aeaa57b..89511fa3 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -373,18 +373,27 @@ def obligations (attr_validator) exceptions << e nil end - end.flatten.compact + end if exceptions.length > 0 and (@join_operator == :and or exceptions.length == @attributes.length) raise NotAuthorized, "Missing authorization in collecting obligations: #{exceptions.map(&:to_s) * ", "}" end if @join_operator == :and and !obligations.empty? - merged_obligation = obligations.first - obligations[1..-1].each do |obligation| - merged_obligation = merged_obligation.deep_merge(obligation) + # cross product of OR'ed obligations in arrays + arrayed_obligations = obligations.map {|obligation| obligation.is_a?(Hash) ? [obligation] : obligation} + merged_obligations = arrayed_obligations.first + arrayed_obligations[1..-1].each do |inner_obligations| + previous_merged_obligations = merged_obligations + merged_obligations = inner_obligations.collect do |inner_obligation| + previous_merged_obligations.collect do |merged_obligation| + merged_obligation.deep_merge(inner_obligation) + end + end.flatten end - obligations = [merged_obligation] + obligations = merged_obligations + else + obligations = obligations.flatten.compact end obligations.empty? ? [{}] : obligations end diff --git a/test/model_test.rb b/test/model_test.rb index 12e219bd..0218207e 100644 --- a/test/model_test.rb +++ b/test/model_test.rb @@ -1099,6 +1099,52 @@ def test_with_if_permitted_to TestAttr.delete_all end + def test_with_anded_if_permitted_to + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :base_role do + has_permission_on :test_attrs, :to => :read, :join_by => :and do + if_permitted_to :read, :test_model + if_attribute :attr => 1 + end + end + role :first_role do + includes :base_role + has_permission_on :test_models, :to => :read do + if_attribute :content => "first test" + end + end + role :second_role do + includes :base_role + has_permission_on :test_models, :to => :read do + if_attribute :country_id => 2 + end + end + end + } + Authorization::Engine.instance(reader) + + test_model_1 = TestModel.create!(:content => "first test") + test_model_1.test_attrs.create!(:attr => 1) + test_model_for_second_role = TestModel.create!(:country_id => 2) + test_model_for_second_role.test_attrs.create!(:attr => 1) + test_model_for_second_role.test_attrs.create!(:attr => 2) + + user = MockUser.new(:first_role) + assert Authorization::Engine.instance.permit?(:read, :object => test_model_1.test_attrs.first, :user => user) + assert_equal 1, TestAttr.with_permissions_to(:read, :user => user).length + + user_with_both_roles = MockUser.new(:first_role, :second_role) + assert Authorization::Engine.instance.permit?(:read, :object => test_model_1.test_attrs.first, :user => user_with_both_roles) + assert Authorization::Engine.instance.permit?(:read, :object => test_model_for_second_role.test_attrs.first, :user => user_with_both_roles) + #p Authorization::Engine.instance.obligations(:read, :user => user_with_both_roles, :context => :test_attrs) + assert_equal 2, TestAttr.with_permissions_to(:read, :user => user_with_both_roles).length + + TestModel.delete_all + TestAttr.delete_all + end + def test_with_if_permitted_to_with_no_child_permissions reader = Authorization::Reader::DSLReader.new reader.parse %{ From 150e4f10b36946f7d2bdbbc609c7b0ef2e20d112 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Tue, 16 Nov 2010 08:58:41 +0100 Subject: [PATCH 10/30] Fixed exception from frozen role_symbols Closes #79 --- .../authorization.rb | 2 +- test/authorization_test.rb | 20 +++++++++++++++++++ test/test_helper.rb | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index 89511fa3..0f05eb71 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -296,7 +296,7 @@ def user_roles_privleges_from_options(privilege, options) def flatten_roles (roles) # TODO caching? - flattened_roles = roles.clone.to_a + flattened_roles = roles.dup.to_a flattened_roles.each do |role| flattened_roles.concat(@role_hierarchy[role]).uniq! if @role_hierarchy[role] end diff --git a/test/authorization_test.rb b/test/authorization_test.rb index 62ee63e2..7f0c398e 100644 --- a/test/authorization_test.rb +++ b/test/authorization_test.rb @@ -69,6 +69,26 @@ def test_permit_multiple_contexts assert engine.permit?(:test, :context => :permissions_4, :user => MockUser.new(:test_role)) assert engine.permit?(:test, :context => :permissions_5, :user => MockUser.new(:test_role)) end + + def test_permit_with_frozen_roles + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :other_role do + includes :test_role + end + role :test_role do + has_permission_on :permissions, :to => :test + end + end + } + engine = Authorization::Engine.new(reader) + roles = [:other_role].freeze + assert_nothing_raised do + assert engine.permit?(:test, :context => :permissions, + :user => MockUser.new(:role_symbols => roles)) + end + end def test_obligations_without_conditions reader = Authorization::Reader::DSLReader.new diff --git a/test/test_helper.rb b/test/test_helper.rb index 8321ae51..28071f28 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -70,7 +70,7 @@ def self.find(*args) class MockUser < MockDataObject def initialize (*roles) options = roles.last.is_a?(::Hash) ? roles.pop : {} - super(options.merge(:role_symbols => roles, :login => hash)) + super({:role_symbols => roles, :login => hash}.merge(options)) end def initialize_copy (other) From e127c0555eb311b9746804c7b39de2f544c76126 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Fri, 31 Dec 2010 10:01:29 +0100 Subject: [PATCH 11/30] Updated contributors --- README.rdoc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.rdoc b/README.rdoc index 759082b7..046d35d7 100644 --- a/README.rdoc +++ b/README.rdoc @@ -510,10 +510,11 @@ sbartsch at tzi.org = Contributors -Thanks to John Joseph Bachir, Eike Carls, Kai Chen, Erik Dahlstrand, Jeroen van Dijk, -Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen, Tim Harper, hollownest, -Daniel Kristensen, Brian Langenfeld, Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, -Thomas Maurer, TJ Singleton, Mike Vincent +Thanks to John Joseph Bachir, Eike Carls, Dennis Blöte, Kai Chen, Erik Dahlstrand, +Jeroen van Dijk, Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen, +Tim Harper, hollownest, Daniel Kristensen, Brad Langhorst, Brian Langenfeld, +Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Sharagoz, +TJ Singleton, Mike Vincent = Licence From b2964ca63588e04aed3218920fc7cc360f508c8b Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Fri, 31 Dec 2010 10:14:40 +0100 Subject: [PATCH 12/30] Updated Changelog and bumped patch level to 0.5.2 --- CHANGELOG | 4 ++++ declarative_authorization.gemspec | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f1310973..4bf7bbbf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +** RELEASE 0.5.2 (Dec 31, 2010) ** + +* Bugfixes and documentation updates + ** RELEASE 0.5.1 (Sep 12, 2010) ** ** RELEASE 0.5 (July 21, 2010) ** diff --git a/declarative_authorization.gemspec b/declarative_authorization.gemspec index f329191e..1bbb4e12 100644 --- a/declarative_authorization.gemspec +++ b/declarative_authorization.gemspec @@ -2,11 +2,11 @@ Gem::Specification.new do |s| s.name = "declarative_authorization" - s.version = "0.5.1" + s.version = "0.5.2" s.required_ruby_version = ">= 1.8.6" s.authors = ["Steffen Bartsch"] - s.summary = "declarative_authorization is a Rails plugin for authorization based on readable authorization rules." + s.summary = "declarative_authorization is a Rails plugin for maintainable authorization based on readable authorization rules." s.email = "sbartsch@tzi.org" s.files = %w{CHANGELOG MIT-LICENSE README.rdoc Rakefile authorization_rules.dist.rb garlic_example.rb init.rb} + Dir["app/**/*.rb"] + Dir["app/**/*.erb"] + Dir["config/*"] + Dir["lib/*.rb"] + Dir["lib/**/*.rb"] + Dir["lib/tasks/*"] + Dir["test/*"] s.has_rdoc = true From 1e393fecef239e3acf4f34575460db0b0252cc38 Mon Sep 17 00:00:00 2001 From: dbloete Date: Mon, 24 Jan 2011 16:46:20 +0100 Subject: [PATCH 13/30] Allow changing the default role --- README.rdoc | 5 +++++ .../authorization.rb | 19 ++++++++++++++----- test/authorization_test.rb | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/README.rdoc b/README.rdoc index 046d35d7..cc28bf35 100644 --- a/README.rdoc +++ b/README.rdoc @@ -256,6 +256,11 @@ public pages, :+guest+ can be used to allow access for users that are not logged in. All other roles are application defined and need to be associated with users by the application. +If you need to change the default role, you can do so by adding an initializer +that contains the following statement: + + Authorization.default_role = :anonymous + Privileges, such as :create, may be put into hierarchies to simplify maintenance. So the example above has the same meaning as diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index 0f05eb71..8c2cacc9 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -25,7 +25,7 @@ class NilAttributeValueError < AuthorizationError; end # Controller-independent method for retrieving the current user. # Needed for model security where the current controller is not available. def self.current_user - Thread.current["current_user"] || GuestUser.new + Thread.current["current_user"] || AnonymousUser.new end # Controller-independent method for setting the current user. @@ -52,6 +52,15 @@ def self.dot_path= (path) @@dot_path = path end + @@default_role = :guest + def self.default_role + @@default_role + end + + def self.default_role= (role) + @@default_role = role.to_sym + end + # Authorization::Engine implements the reference monitor. It may be used # for querying the permission and retrieving obligations under which # a certain privilege is granted for the current user. @@ -241,7 +250,7 @@ def roles_for (user) "doesn't return an Array of Symbols (#{roles.inspect})" \ if !roles.is_a?(Array) or (!roles.empty? and !roles[0].is_a?(Symbol)) - (roles.empty? ? [:guest] : roles) + (roles.empty? ? [Authorization.default_role] : roles) end # Returns the role symbols and inherritted role symbols for the given user @@ -685,10 +694,10 @@ def self.reflection_for_path (parent_model, path) end end - # Represents a pseudo-user to facilitate guest users in applications - class GuestUser + # Represents a pseudo-user to facilitate anonymous users in applications + class AnonymousUser attr_reader :role_symbols - def initialize (roles = [:guest]) + def initialize (roles = [Authorization.default_role]) @role_symbols = roles end end diff --git a/test/authorization_test.rb b/test/authorization_test.rb index 7f0c398e..439e5d19 100644 --- a/test/authorization_test.rb +++ b/test/authorization_test.rb @@ -316,6 +316,25 @@ def test_guest_user assert !engine.permit?(:test, :context => :permissions_2) end + def test_default_role + previous_default_role = Authorization.default_role + Authorization.default_role = :anonymous + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :anonymous do + has_permission_on :permissions, :to => :test + end + end + } + engine = Authorization::Engine.new(reader) + assert engine.permit?(:test, :context => :permissions) + assert !engine.permit?(:test, :context => :permissions, + :user => MockUser.new(:guest)) + # reset the default role, so that it does not mess up other tests + Authorization.default_role = previous_default_role + end + def test_invalid_user_model reader = Authorization::Reader::DSLReader.new reader.parse %{ From bacc1f18039f3936c9a836a047aaf01e1c63c333 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Wed, 23 Feb 2011 08:37:04 +0000 Subject: [PATCH 14/30] Prevent unnecessary queries from proxy scope --- lib/declarative_authorization/in_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index 0031f0b9..a4ee6511 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -52,7 +52,7 @@ def permitted_to! (privilege, object_or_sym = nil, options = {}, &block) context = object = nil if object_or_sym.nil? context = self.class.decl_auth_context - elsif object_or_sym.is_a?(Symbol) + elsif !object_or_sym.respond_to?(:proxy_reflection) and object_or_sym.is_a?(Symbol) context = object_or_sym else object = object_or_sym From 274170c3a1abc081af9ab315cce64c1014548774 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Mon, 28 Mar 2011 20:37:27 +0100 Subject: [PATCH 15/30] Fixes failed_auto_loading_is_not_found, closes #94 --- lib/declarative_authorization/in_controller.rb | 2 +- test/controller_test.rb | 2 +- test/test_helper.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index a4ee6511..d8c43969 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -609,7 +609,7 @@ def load_object(contr) unless object begin object = load_object_model.find(contr.params[:id]) - rescue RuntimeError => e + rescue => e contr.logger.debug("filter_access_to tried to find " + "#{load_object_model} from params[:id] " + "(#{contr.params[:id].inspect}), because attribute_check is enabled " + diff --git a/test/controller_test.rb b/test/controller_test.rb index 0c001214..98001381 100644 --- a/test/controller_test.rb +++ b/test/controller_test.rb @@ -262,7 +262,7 @@ def test_filter_access_object_load_without_param end } - assert_raise RuntimeError, "No id param supplied" do + assert_raise StandardError, "No id param supplied" do request!(MockUser.new(:test_role), "show", reader) end diff --git a/test/test_helper.rb b/test/test_helper.rb index 28071f28..d4737b58 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -62,7 +62,7 @@ def self.name end def self.find(*args) - raise "Couldn't find #{self.name} with id #{args[0].inspect}" unless args[0] + raise StandardError, "Couldn't find #{self.name} with id #{args[0].inspect}" unless args[0] new :id => args[0] end end From 2a465d3216019d8c022a4ff250fd6497f6c57557 Mon Sep 17 00:00:00 2001 From: "t.pickett66" Date: Mon, 7 Mar 2011 14:42:46 -0600 Subject: [PATCH 16/30] add and test has_any_role? Allows passing an array of roles and returning true/executing a block if ANY of the supplied roles are present rather than ALL --- lib/declarative_authorization/helper.rb | 4 ++ .../in_controller.rb | 11 ++++++ test/helper_test.rb | 37 ++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/declarative_authorization/helper.rb b/lib/declarative_authorization/helper.rb index 4cf8fe03..cf06ada2 100644 --- a/lib/declarative_authorization/helper.rb +++ b/lib/declarative_authorization/helper.rb @@ -56,5 +56,9 @@ def has_role? (*roles, &block) def has_role_with_hierarchy?(*roles, &block) controller.has_role_with_hierarchy?(*roles, &block) end + + def has_any_role?(*roles,&block) + controller.has_any_role?(*roles,&block) + end end end \ No newline at end of file diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index d8c43969..67c686fa 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -86,6 +86,17 @@ def has_role? (*roles, &block) result end + # Intended to be used where you want to allow users with any single listed role to view + # the content in question + def has_any_role?(*roles,&block) + user_roles = authorization_engine.roles_for(current_user) + result = roles.any? do |role| + user_roles.include?(role) + end + yield if result and block_given? + result + end + # As has_role? except checks all roles included in the role hierarchy def has_role_with_hierarchy?(*roles, &block) user_roles = authorization_engine.roles_with_hierarchy_for(current_user) diff --git a/test/helper_test.rb b/test/helper_test.rb index e4ba202a..5ced67e7 100644 --- a/test/helper_test.rb +++ b/test/helper_test.rb @@ -113,7 +113,42 @@ def test_has_role end assert !block_evaled end - + + def test_has_any_role + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :mocks, :to => :show + end + end + } + user = MockUser.new(:test_role) + request!(user, :action, reader) + + assert has_any_role?(:test_role) + assert !has_any_role?(:test_role2) + assert has_any_role?(:test_role, :test_role2) + + block_evaled = false + has_any_role?(:test_role) do + block_evaled = true + end + assert block_evaled + + block_evaled = false + has_any_role?(:test_role2) do + block_evaled = true + end + assert !block_evaled + + block_evaled = false + has_any_role?(:test_role,:test_role2) do + block_evaled = true + end + assert block_evaled + end + def test_has_role_with_guest_user reader = Authorization::Reader::DSLReader.new reader.parse %{ From dbbe7832f1dddd688da689b7a778620c3dbd1eca Mon Sep 17 00:00:00 2001 From: "t.pickett66" Date: Mon, 7 Mar 2011 14:43:25 -0600 Subject: [PATCH 17/30] add and test has_any_role_with_hierarchy? Allows passing an array of roles and returning true/executing a block if ANY of the supplied roles are present rather than ALL --- lib/declarative_authorization/helper.rb | 4 ++ .../in_controller.rb | 9 ++++ test/helper_test.rb | 44 ++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/declarative_authorization/helper.rb b/lib/declarative_authorization/helper.rb index cf06ada2..00bb9472 100644 --- a/lib/declarative_authorization/helper.rb +++ b/lib/declarative_authorization/helper.rb @@ -60,5 +60,9 @@ def has_role_with_hierarchy?(*roles, &block) def has_any_role?(*roles,&block) controller.has_any_role?(*roles,&block) end + + def has_any_role_with_hierarchy?(*roles, &block) + controller.has_any_role_with_hierarchy?(*roles, &block) + end end end \ No newline at end of file diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index 67c686fa..beba8830 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -107,6 +107,15 @@ def has_role_with_hierarchy?(*roles, &block) result end + # As has_any_role? except checks all roles included in the role hierarchy + def has_any_role_with_hierarchy?(*roles, &block) + user_roles = authorization_engine.roles_with_hierarchy_for(current_user) + result = roles.any? do |role| + user_roles.include?(role) + end + yield if result and block_given? + result + end protected def filter_access_filter # :nodoc: diff --git a/test/helper_test.rb b/test/helper_test.rb index 5ced67e7..e01905e1 100644 --- a/test/helper_test.rb +++ b/test/helper_test.rb @@ -200,8 +200,48 @@ def test_has_role_with_hierarchy block_evaled = true end assert !block_evaled - end - + def test_has_any_role_with_hierarchy + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :mocks, :to => :show + end + role :other_role do + has_permission_on :another_mocks, :to => :show + end + + role :root do + includes :test_role + end + end + } + + user = MockUser.new(:root) + request!(user, :action, reader) + + assert has_any_role_with_hierarchy?(:test_role) + assert !has_any_role_with_hierarchy?(:other_role) + assert has_any_role_with_hierarchy?(:test_role,:other_role) + + block_evaled = false + has_any_role_with_hierarchy?(:test_role) do + block_evaled = true + end + assert block_evaled + + block_evaled = false + has_any_role_with_hierarchy?(:test_role2) do + block_evaled = true + end + assert !block_evaled + + block_evaled = false + has_any_role_with_hierarchy?(:test_role,:test_role2) do + block_evaled = true + end + assert block_evaled + end end \ No newline at end of file From 012bdc0ff4002f2033ae155fdc73e1d7eaac1d3d Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Mon, 28 Mar 2011 20:52:24 +0100 Subject: [PATCH 18/30] Fixes documentation, closes #87 --- lib/declarative_authorization/reader.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/declarative_authorization/reader.rb b/lib/declarative_authorization/reader.rb index 11d0e27f..3b2bd0f6 100644 --- a/lib/declarative_authorization/reader.rb +++ b/lib/declarative_authorization/reader.rb @@ -381,12 +381,12 @@ def if_attribute (attr_conditions_hash) # # role :branch_manager # has_permission_on :branches, :to => :manage do - # if_attribute :employees => includes { user } + # if_attribute :employees => contains { user } # end # has_permission_on :employees, :to => :read do # if_permitted_to :read, :branch # # instead of - # # if_attribute :branch => { :employees => includes { user } } + # # if_attribute :branch => { :employees => contains { user } } # end # end # @@ -404,7 +404,7 @@ def if_attribute (attr_conditions_hash) # To check permissions based on the current object, the attribute has to # be left out: # has_permission_on :branches, :to => :manage do - # if_attribute :employees => includes { user } + # if_attribute :employees => contains { user } # end # has_permission_on :branches, :to => :paint_green do # if_permitted_to :update From 7b55386f3f10c7ed7d32e9aabb3da7c70115f63d Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Mon, 28 Mar 2011 21:21:38 +0100 Subject: [PATCH 19/30] Test case for #93 --- test/model_test.rb | 28 ++++++++++++++++++++++++++++ test/schema.sql | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/test/model_test.rb b/test/model_test.rb index 0218207e..c069db29 100644 --- a/test/model_test.rb +++ b/test/model_test.rb @@ -71,6 +71,7 @@ class TestAttr < ActiveRecord::Base belongs_to :branch belongs_to :company has_many :test_attr_throughs + has_many :test_model_security_model_with_finds attr_reader :role_symbols def initialize (*args) @role_symbols = [] @@ -89,6 +90,7 @@ class TestModelSecurityModel < ActiveRecord::Base class TestModelSecurityModelWithFind < ActiveRecord::Base set_table_name "test_model_security_models" has_many :test_attrs + belongs_to :test_attr using_access_control :include_read => true, :context => :test_model_security_models end @@ -1591,6 +1593,32 @@ def test_model_security_with_and_without_find_restrictions end end + def test_model_security_with_read_restrictions_and_exists + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :test_model_security_models do + to :read, :create, :update, :delete + if_attribute :test_attr => is { user.test_attr } + end + end + end + } + Authorization::Engine.instance(reader) + + test_attr = TestAttr.create + Authorization.current_user = MockUser.new(:test_role, :test_attr => test_attr) + object_with_find = TestModelSecurityModelWithFind.create :test_attr => test_attr + assert_nothing_raised do + object_with_find.class.find(object_with_find.id) + end + assert_equal 1, test_attr.test_model_security_model_with_finds.length + + # Raises error since AR does not populate the object + #assert test_attr.test_model_security_model_with_finds.exists?(object_with_find) + end + def test_model_security_delete_unallowed reader = Authorization::Reader::DSLReader.new reader.parse %{ diff --git a/test/schema.sql b/test/schema.sql index 774138f8..ca368ad6 100644 --- a/test/schema.sql +++ b/test/schema.sql @@ -28,7 +28,8 @@ CREATE TABLE 'test_attr_throughs' ( CREATE TABLE 'test_model_security_models' ( 'id' INTEGER PRIMARY KEY NOT NULL, 'attr' integer default 1, - 'attr_2' integer default 1 + 'attr_2' integer default 1, + 'test_attr_id' integer ); CREATE TABLE 'n_way_join_items' ( From ac6e6379b23aeab8a15e801b9f1cd34505a81dab Mon Sep 17 00:00:00 2001 From: Jeremy Kleindl Date: Thu, 7 Apr 2011 15:01:08 -0500 Subject: [PATCH 20/30] Documentation cleanup and minor additions for clarification on using :additional_members --- README.rdoc | 8 ++--- .../authorization.rb | 4 +-- .../development_support/analyzer.rb | 2 +- .../development_support/change_analyzer.rb | 2 +- .../in_controller.rb | 34 +++++++++++++------ lib/declarative_authorization/reader.rb | 6 ++-- 6 files changed, 35 insertions(+), 21 deletions(-) diff --git a/README.rdoc b/README.rdoc index cc28bf35..5a622933 100644 --- a/README.rdoc +++ b/README.rdoc @@ -192,13 +192,13 @@ See also Authorization::AuthorizationHelper. == Models -There are two destinct features for model security built into this plugin: +There are two distinct features for model security built into this plugin: authorizing CRUD operations on objects as well as query rewriting to limit results according to certain privileges. See also Authorization::AuthorizationInModel. -=== Model security for CRUD opterations +=== Model security for CRUD operations To activate model security, all it takes is an explicit enabling for each model that model security should be enforced on, i.e. @@ -215,7 +215,7 @@ happened if an operation is denied, the filters throw Authorization::NotAuthorized exceptions. As access control on read are costly, with possibly lots of objects being -loaded at a time in one query, checks on read need to be actived explicitly by +loaded at a time in one query, checks on read need to be activated explicitly by adding the :include_read option. === Query rewriting through named scopes @@ -522,7 +522,7 @@ Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Sharagoz TJ Singleton, Mike Vincent -= Licence += License Copyright (c) 2008 Steffen Bartsch, TZI, Universität Bremen, Germany released under the MIT license diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index 8c2cacc9..a2126557 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -9,7 +9,7 @@ class AuthorizationError < StandardError ; end # NotAuthorized is raised if the current user is not allowed to perform # the given operation possibly on a specific object. class NotAuthorized < AuthorizationError ; end - # AttributeAuthorizationError is more specific than NotAuthorized, signalling + # AttributeAuthorizationError is more specific than NotAuthorized, signaling # that the access was denied on the grounds of attribute conditions. class AttributeAuthorizationError < NotAuthorized ; end # AuthorizationUsageError is used whenever a situation is encountered @@ -122,7 +122,7 @@ def initialize_copy (from) # :nodoc: # The context part of the privilege. # Defaults either to the tableized +class_name+ of the given :+object+, if given. # That is, :+users+ for :+object+ of type User. - # Raises AuthorizationUsageError if context is missing and not to be infered. + # Raises AuthorizationUsageError if context is missing and not to be inferred. # [:+object+] An context object to test attribute checks against. # [:+skip_attribute_test+] # Skips those attribute checks in the diff --git a/lib/declarative_authorization/development_support/analyzer.rb b/lib/declarative_authorization/development_support/analyzer.rb index 5cd4bd77..f152ae4d 100644 --- a/lib/declarative_authorization/development_support/analyzer.rb +++ b/lib/declarative_authorization/development_support/analyzer.rb @@ -18,7 +18,7 @@ module DevelopmentSupport # * merging roles # * role hierarchy # - # Mergeable Rules: respect if_permitted_to hash + # Merge-able Rules: respect if_permitted_to hash # class Analyzer < AbstractAnalyzer def analyze (rules) diff --git a/lib/declarative_authorization/development_support/change_analyzer.rb b/lib/declarative_authorization/development_support/change_analyzer.rb index 5fe3636e..7647fd6d 100644 --- a/lib/declarative_authorization/development_support/change_analyzer.rb +++ b/lib/declarative_authorization/development_support/change_analyzer.rb @@ -32,7 +32,7 @@ def find_approaches_for (change_action, type, options, &tests) # * strategy for removing: [remove privilege, add privilege to different role] @seen_states = Set.new - # * heurisic: change of failed tests; small number of policy items + # * heuristic: change of failed tests; small number of policy items strategy = case [change_action, type] when [:remove, :permission] [:remove_role_from_user, :remove_privilege, :add_privilege, diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index beba8830..0bc3bc12 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -12,13 +12,13 @@ def self.included(base) # :nodoc: DEFAULT_DENY = false - # If attribute_check is set for filter_access_to, decl_auth will try to + # If attribute_check is set for filter_access_to, decl_auth_context will try to # load the appropriate object from the current controller's model with # the id from params[:id]. If that fails, a 404 Not Found is often the # right way to handle the error. If you have additional measures in place # that restricts the find scope, handling this error as a permission denied # might be a better way. Set failed_auto_loading_is_not_found to false - # for the latter behaviour. + # for the latter behavior. @@failed_auto_loading_is_not_found = true def self.failed_auto_loading_is_not_found? @@failed_auto_loading_is_not_found @@ -214,7 +214,7 @@ module ClassMethods # end # end # - # By default, required privileges are infered from the action name and + # By default, required privileges are inferred from the action name and # the controller name. Thus, in UserController :+edit+ requires # :+edit+ +users+. To specify required privilege, use the option :+require+ # filter_access_to :new, :create, :require => :create, :context => :users @@ -276,7 +276,7 @@ module ClassMethods # to load the object. Both should return the loaded object. # If a Proc object is given, e.g. by way of # +lambda+, it is called in the instance of the controller. - # Example demonstrating the default behaviour: + # Example demonstrating the default behavior: # filter_access_to :show, :attribute_check => true, # :load_method => lambda { User.find(params[:id]) } # @@ -357,7 +357,7 @@ def all_filter_access_permissions # :nodoc: # # In many cases, the default seven CRUD actions are not sufficient. As in # the resource definition for routing you may thus give additional member, - # new and collection methods. The options allow you to specify the + # new and collection methods. The +options+ allow you to specify the # required privileges for each action by providing a hash or an array of # pairs. By default, for each action the action name is taken as privilege # (action search in the example below requires the privilege :index @@ -368,7 +368,21 @@ def all_filter_access_permissions # :nodoc: # :additional_member => {:mark_as_key_company => :update} # end # The +additional_+* options add to the respective CRUD actions, - # the other options replace the respective CRUD actions. + # the other options (:+member+, :+collection+, :+new+) replace their + # respective CRUD actions. + # filter_resource_access :member => { :extra => :new } + # Would declare :extra as the only member action in the controller and + # require that permission on action :new be granted for the current user. + # filter_resource_access :additional_member => { :extra => :new } + # Would add a permission requirement of :+extra+ that uses the :+new+ and + # :additional_new behavior when checking against the authorization rules, in + # the case of the above example using the +new_branch_from_params+ before_filter. + # + # If :+collection+ is an array of method names filter_resource_access will + # associate a permission with the method that is the same as the method + # name and no attribute checks will be performed unless + # :attribute_check => true + # is added in the options. # # You can override the default object loading by implementing any of the # following instance methods on the controller. Examples are given for the @@ -410,7 +424,7 @@ def all_filter_access_permissions # :nodoc: # +nested_in+, attribute check is deactivated for these actions. By # default, collection is set to :+index+. # [:+additional_collection+] - # Allows to add additional collaction actions to the default resource +collection+ + # Allows to add additional collection actions to the default resource +collection+ # actions. # [:+new+] # +new+ methods are actions such as +new+ and +create+, which don't @@ -442,7 +456,7 @@ def all_filter_access_permissions # :nodoc: # +additional_member+ or rather the default member actions (:+show+, :+edit+, # :+update+, :+destroy+). # [:+no_attribute_check+] - # Allows to set actions for which no attribute check should be perfomed. + # Allows to set actions for which no attribute check should be performed. # See filter_access_to on details. By default, with no +nested_in+, # +no_attribute_check+ is set to all collections. If +nested_in+ is given # +no_attribute_check+ is empty by default. @@ -463,8 +477,8 @@ def filter_resource_access(options = {}) :nested_in => nil, }.merge(options) - new_actions = actions_from_option(options[:new]).merge( - actions_from_option(options[:additional_new])) + new_actions = actions_from_option( options[:new] ).merge( + actions_from_option(options[:additional_new]) ) members = actions_from_option(options[:member]).merge( actions_from_option(options[:additional_member])) collections = actions_from_option(options[:collection]).merge( diff --git a/lib/declarative_authorization/reader.rb b/lib/declarative_authorization/reader.rb index 3b2bd0f6..8f9ecd63 100644 --- a/lib/declarative_authorization/reader.rb +++ b/lib/declarative_authorization/reader.rb @@ -6,7 +6,7 @@ module Authorization # Parses an authorization configuration file in the authorization DSL and # constructs a data model of its contents. # - # For examples and the modelled data model, see the + # For examples and the modeled data model, see the # README[link:files/README_rdoc.html]. # # Also, see role definition methods @@ -409,14 +409,14 @@ def if_attribute (attr_conditions_hash) # has_permission_on :branches, :to => :paint_green do # if_permitted_to :update # end - # Normally, one would merge those rules into one. Deviding makes sense + # Normally, one would merge those rules into one. Dividing makes sense # if additional if_attribute are used in the second rule or those rules # are applied to different roles. # # Options: # [:+context+] # When using with_permissions_to, the target context of the if_permitted_to - # statement is infered from the last reflections target class. Still, + # statement is inferred from the last reflections target class. Still, # you may override this algorithm by setting the context explicitly. # if_permitted_to :read, :home_branch, :context => :branches # if_permitted_to :read, :branch => :main_company, :context => :companies From 890b7e2d5a8fe911a1d11877568fead07bcb3984 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Thu, 21 Apr 2011 22:30:47 +0100 Subject: [PATCH 21/30] Further additional_member clarification --- lib/declarative_authorization/in_controller.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index 0bc3bc12..f2aea1f8 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -370,13 +370,11 @@ def all_filter_access_permissions # :nodoc: # The +additional_+* options add to the respective CRUD actions, # the other options (:+member+, :+collection+, :+new+) replace their # respective CRUD actions. - # filter_resource_access :member => { :extra => :new } - # Would declare :extra as the only member action in the controller and - # require that permission on action :new be granted for the current user. - # filter_resource_access :additional_member => { :extra => :new } - # Would add a permission requirement of :+extra+ that uses the :+new+ and - # :additional_new behavior when checking against the authorization rules, in - # the case of the above example using the +new_branch_from_params+ before_filter. + # filter_resource_access :member => { :toggle_open => :update } + # Would declare :toggle_open as the only member action in the controller and + # require that permission :update is granted for the current user. + # filter_resource_access :additional_member => { :toggle_open => :update } + # Would add a member action :+toggle_open+ to the default members, such as :+show+. # # If :+collection+ is an array of method names filter_resource_access will # associate a permission with the method that is the same as the method From e891e447884a0a4fa2a81b4b451151a4296d5bbe Mon Sep 17 00:00:00 2001 From: sunkencity Date: Thu, 14 Apr 2011 09:22:33 +0200 Subject: [PATCH 22/30] RAILS_DEFAULT_LOGGER creates a warning on rails 3 --- lib/declarative_authorization/authorization.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index a2126557..71290cf8 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -241,8 +241,8 @@ def roles_for (user) raise AuthorizationUsageError, "User object doesn't respond to roles (#{user.inspect})" \ if !user.respond_to?(:role_symbols) and !user.respond_to?(:roles) - RAILS_DEFAULT_LOGGER.info("The use of user.roles is deprecated. Please add a method " + - "role_symbols to your User model.") if defined?(RAILS_DEFAULT_LOGGER) and !user.respond_to?(:role_symbols) + Rails.logger.info("The use of user.roles is deprecated. Please add a method " + + "role_symbols to your User model.") if defined?(Rails) and Rails.respond_to?(:logger) and !user.respond_to?(:role_symbols) roles = user.respond_to?(:role_symbols) ? user.role_symbols : user.roles From 27373691be91176dee5e182e1e5f4a4a508a1f2a Mon Sep 17 00:00:00 2001 From: Benjamin ter Kuile Date: Thu, 21 Apr 2011 17:57:21 +0200 Subject: [PATCH 23/30] Add namespace support for authorization_usage --- app/views/authorization_usages/index.html.erb | 2 +- lib/declarative_authorization/maintenance.rb | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/views/authorization_usages/index.html.erb b/app/views/authorization_usages/index.html.erb index 0433aae5..808f79b5 100644 --- a/app/views/authorization_usages/index.html.erb +++ b/app/views/authorization_usages/index.html.erb @@ -17,7 +17,7 @@ <% @auth_usages_by_controller.keys.sort {|c1, c2| c1.name <=> c2.name}.each do |controller| %> <% default_context = controller.controller_name.to_sym rescue nil %> - <%= h controller.controller_name %> + <%= h controller.name.underscore.sub(/_controller\Z/, '') %> <% @auth_usages_by_controller[controller].keys.sort {|c1, c2| c1.to_s <=> c2.to_s}.each do |action| %> <% auth_info = @auth_usages_by_controller[controller][action] %> diff --git a/lib/declarative_authorization/maintenance.rb b/lib/declarative_authorization/maintenance.rb index 0ce2d6e5..60b4c382 100644 --- a/lib/declarative_authorization/maintenance.rb +++ b/lib/declarative_authorization/maintenance.rb @@ -55,17 +55,14 @@ module Usage def self.usages_by_controller # load each application controller begin - Dir.foreach(File.join(::Rails.root, %w{app controllers})) do |entry| - if entry =~ /^\w+_controller\.rb$/ - require File.join(::Rails.root, %w{app controllers}, entry) - end + Dir.glob(File.join(::Rails.root, 'app', 'controllers', '**', '*_controller\.rb')) do |entry| + require entry end rescue Errno::ENOENT end controllers = [] ObjectSpace.each_object(Class) do |obj| - controllers << obj if obj.ancestors.include?(ActionController::Base) and - !%w{ActionController::Base ApplicationController}.include?(obj.name) + controllers << obj if obj.ancestors.include?(ActionController::Base) and obj != ActionController::Base and obj.name.demodulize != 'ApplicationController' end controllers.inject({}) do |memo, controller| From 6bea96a98efdea1c6fdf2f081af8563a32d2228b Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Wed, 25 May 2011 15:57:16 +0200 Subject: [PATCH 24/30] Rails 3.1 compatibility (fixes #102) --- lib/declarative_authorization/in_model.rb | 71 ++++++++++++++++------- test/test_helper.rb | 6 +- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/lib/declarative_authorization/in_model.rb b/lib/declarative_authorization/in_model.rb index 3f210bd1..862bea2f 100644 --- a/lib/declarative_authorization/in_model.rb +++ b/lib/declarative_authorization/in_model.rb @@ -34,29 +34,31 @@ def permitted_to! (privilege, options = {} ) def self.included(base) # :nodoc: #base.extend(ClassMethods) base.module_eval do - scopes[:with_permissions_to] = lambda do |parent_scope, *args| - options = args.last.is_a?(Hash) ? args.pop : {} - privilege = (args[0] || :read).to_sym - privileges = [privilege] - context = - if options[:context] - options[:context] - elsif parent_scope.respond_to?(:proxy_reflection) - parent_scope.proxy_reflection.klass.name.tableize.to_sym - elsif parent_scope.respond_to?(:decl_auth_context) - parent_scope.decl_auth_context - else - parent_scope.name.tableize.to_sym - end - - user = options[:user] || Authorization.current_user + if Rails.version < "3.1" + scopes[:with_permissions_to] = lambda do |parent_scope, *args| + options = args.last.is_a?(Hash) ? args.pop : {} + privilege = (args[0] || :read).to_sym + privileges = [privilege] + context = + if options[:context] + options[:context] + elsif parent_scope.respond_to?(:proxy_reflection) + parent_scope.proxy_reflection.klass.name.tableize.to_sym + elsif parent_scope.respond_to?(:decl_auth_context) + parent_scope.decl_auth_context + else + parent_scope.name.tableize.to_sym + end - engine = options[:engine] || Authorization::Engine.instance - engine.permit!(privileges, :user => user, :skip_attribute_test => true, - :context => context) + user = options[:user] || Authorization.current_user + + engine = options[:engine] || Authorization::Engine.instance + engine.permit!(privileges, :user => user, :skip_attribute_test => true, + :context => context) - obligation_scope_for( privileges, :user => user, - :context => context, :engine => engine, :model => parent_scope) + obligation_scope_for( privileges, :user => user, + :context => context, :engine => engine, :model => parent_scope) + end end # Builds and returns a scope with joins and conditions satisfying all obligations. @@ -96,7 +98,32 @@ def self.obligation_scope_for( privileges, options = {} ) # current user. # def self.with_permissions_to (*args) - scopes[:with_permissions_to].call(self, *args) + if Rails.version < "3.1" + scopes[:with_permissions_to].call(self, *args) + else + options = args.last.is_a?(Hash) ? args.pop : {} + privilege = (args[0] || :read).to_sym + privileges = [privilege] + + parent_scope = scoped + context = + if options[:context] + options[:context] + elsif parent_scope.klass.respond_to?(:decl_auth_context) + parent_scope.klass.decl_auth_context + else + parent_scope.klass.name.tableize.to_sym + end + + user = options[:user] || Authorization.current_user + + engine = options[:engine] || Authorization::Engine.instance + engine.permit!(privileges, :user => user, :skip_attribute_test => true, + :context => context) + + obligation_scope_for( privileges, :user => user, + :context => context, :engine => engine, :model => parent_scope.klass) + end end # Activates model security for the current model. Then, CRUD operations diff --git a/test/test_helper.rb b/test/test_helper.rb index d4737b58..f9078ef4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -118,7 +118,8 @@ def warn?; end map.connect ':controller/:action/:id' end else - Rails::Application.routes.draw do + #Rails::Application.routes.draw do + Rails.application.routes.draw do match '/name/spaced_things(/:action)' => 'name/spaced_things' match '/deep/name_spaced/things(/:action)' => 'deep/name_spaced/things' match '/:controller(/:action(/:id))' @@ -146,7 +147,8 @@ def request! (user, action, reader, params = {}) unless Rails.version < "3" def setup - @routes = Rails::Application.routes + #@routes = Rails::Application.routes + @routes = Rails.application.routes end end end From 37cf39fed88a9223b5ff04034383b6a50bbae21e Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Wed, 25 May 2011 16:08:04 +0200 Subject: [PATCH 25/30] Updated change log, contributors, bumped minor for 0.5.3 release --- CHANGELOG | 10 ++++++++++ README.rdoc | 6 +++--- declarative_authorization.gemspec | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4bf7bbbf..7ea14bb4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +** RELEASE 0.5.3 (May 25, 2011) + +* Bugfixes and documentation cleanup + +* Rails 3.1.rc1 compatibility [sb] + +* Added has_any_role?, has_any_role_with_hierarchy? [t.pickett66] + +* Allow changing the default role [dbloete] + ** RELEASE 0.5.2 (Dec 31, 2010) ** * Bugfixes and documentation updates diff --git a/README.rdoc b/README.rdoc index 5a622933..688d93a2 100644 --- a/README.rdoc +++ b/README.rdoc @@ -517,9 +517,9 @@ sbartsch at tzi.org Thanks to John Joseph Bachir, Eike Carls, Dennis Blöte, Kai Chen, Erik Dahlstrand, Jeroen van Dijk, Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen, -Tim Harper, hollownest, Daniel Kristensen, Brad Langhorst, Brian Langenfeld, -Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Sharagoz, -TJ Singleton, Mike Vincent +Tim Harper, hollownest, Daniel Kristensen, Jeremy Kleindl, Brad Langhorst, Brian Langenfeld, +Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Tyler Pickett, Sharagoz, +TJ Singleton, Mike Vincent, Joel Westerberg = License diff --git a/declarative_authorization.gemspec b/declarative_authorization.gemspec index 1bbb4e12..3218c1ba 100644 --- a/declarative_authorization.gemspec +++ b/declarative_authorization.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "declarative_authorization" - s.version = "0.5.2" + s.version = "0.5.3" s.required_ruby_version = ">= 1.8.6" s.authors = ["Steffen Bartsch"] From 5303bf7762b2a189ded0a557a410286d53dc4e4c Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 31 May 2011 22:45:17 -0700 Subject: [PATCH 26/30] use a hash for lookups through matching_auth_rules --- .../authorization.rb | 53 +++++++++++++++++-- test/authorization_test.rb | 8 +-- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index 71290cf8..0d3e5da0 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -79,7 +79,7 @@ def initialize (reader = nil) @privileges = reader.privileges_reader.privileges # {priv => [[priv, ctx],...]} @privilege_hierarchy = reader.privileges_reader.privilege_hierarchy - @auth_rules = reader.auth_rules_reader.auth_rules + @auth_rules = AuthorizationRuleSet.new reader.auth_rules_reader.auth_rules @roles = reader.auth_rules_reader.roles @omnipotent_roles = reader.auth_rules_reader.omnipotent_roles @role_hierarchy = reader.auth_rules_reader.role_hierarchy @@ -107,9 +107,8 @@ def initialize (reader = nil) def initialize_copy (from) # :nodoc: [ :privileges, :privilege_hierarchy, :roles, :role_hierarchy, :role_titles, - :role_descriptions, :rev_priv_hierarchy, :rev_role_hierarchy + :role_descriptions, :rev_priv_hierarchy, :rev_role_hierarchy, :auth_rules ].each {|attr| instance_variable_set(:"@#{attr}", from.send(attr).clone) } - @auth_rules = from.auth_rules.collect {|rule| rule.clone} end # Returns true if privilege is met by the current user. Raises @@ -323,10 +322,56 @@ def flatten_privileges (privileges, context = nil) end def matching_auth_rules (roles, privileges, context) - @auth_rules.select {|rule| rule.matches? roles, privileges, context} + @auth_rules.matching(roles, privileges, context) end end + + class AuthorizationRuleSet + include Enumerable + + def initialize rules + @rules = rules + reset! + end + def initialize_copy source + initialize @rules.collect {|rule| rule.clone} + end + def matching(roles, privileges, context) + roles = [roles] unless roles.is_a?(Array) + rules = cached_auth_rules[context] || [] + rules.select do |rule| + rule.matches? roles, privileges, context + end + end + def delete rule + @rules.delete rule + reset! + end + def << rule + @rules << rule + reset! + end + def each &block + @rules.each &block + end + + private + def reset! + @cached_auth_rules =nil + end + def cached_auth_rules + return @cached_auth_rules if @cached_auth_rules + @cached_auth_rules = {} + @rules.each do |rule| + rule.contexts.each do |context| + @cached_auth_rules[context] ||= [] + @cached_auth_rules[context] << rule + end + end + @cached_auth_rules + end + end class AuthorizationRule attr_reader :attributes, :contexts, :role, :privileges, :join_operator, :source_file, :source_line diff --git a/test/authorization_test.rb b/test/authorization_test.rb index 439e5d19..0e81f5f8 100644 --- a/test/authorization_test.rb +++ b/test/authorization_test.rb @@ -1095,10 +1095,10 @@ def test_clone engine = Authorization::Engine.new(reader) cloned_engine = engine.clone - assert_not_equal engine.auth_rules[0].contexts.object_id, - cloned_engine.auth_rules[0].contexts.object_id - assert_not_equal engine.auth_rules[0].attributes[0].send(:instance_variable_get, :@conditions_hash)[:attr].object_id, - cloned_engine.auth_rules[0].attributes[0].send(:instance_variable_get, :@conditions_hash)[:attr].object_id + assert_not_equal engine.auth_rules.first.contexts.object_id, + cloned_engine.auth_rules.first.contexts.object_id + assert_not_equal engine.auth_rules.first.attributes.first.send(:instance_variable_get, :@conditions_hash)[:attr].object_id, + cloned_engine.auth_rules.first.attributes.first.send(:instance_variable_get, :@conditions_hash)[:attr].object_id end end From ea7150280bb31a102f8a8ad2ba53a4698867a153 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 31 May 2011 23:11:17 -0700 Subject: [PATCH 27/30] add a :bang option to Authorization::Engine.permit! With this option permit! will return false instead of raising exceptions. --- .../authorization.rb | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index 0d3e5da0..313a25ac 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -129,13 +129,17 @@ def initialize_copy (from) # :nodoc: # [:+user+] # The user to check the authorization for. # Defaults to Authorization#current_user. + # [:+bang+] + # Should NotAuthorized exceptions be raised + # Defaults to true. # def permit! (privilege, options = {}) return true if Authorization.ignore_access_control options = { :object => nil, :skip_attribute_test => false, - :context => nil + :context => nil, + :bang => true }.merge(options) # Make sure we're handling all privileges as symbols. @@ -168,17 +172,23 @@ def permit! (privilege, options = {}) # at least one of the given privileges attr_validator = AttributeValidator.new(self, user, options[:object], privilege, options[:context]) rules = matching_auth_rules(roles, privileges, options[:context]) - if rules.empty? - raise NotAuthorized, "No matching rules found for #{privilege} for #{user.inspect} " + - "(roles #{roles.inspect}, privileges #{privileges.inspect}, " + - "context #{options[:context].inspect})." - end # Test each rule in turn to see whether any one of them is satisfied. - unless rules.any? {|rule| rule.validate?(attr_validator, options[:skip_attribute_test])} - raise AttributeAuthorizationError, "#{privilege} not allowed for #{user.inspect} on #{(options[:object] || options[:context]).inspect}." + rules.each do |rule| + return true if rule.validate?(attr_validator, options[:skip_attribute_test]) + end + + if options[:bang] + if rules.empty? + raise NotAuthorized, "No matching rules found for #{privilege} for #{user.inspect} " + + "(roles #{roles.inspect}, privileges #{privileges.inspect}, " + + "context #{options[:context].inspect})." + else + raise AttributeAuthorizationError, "#{privilege} not allowed for #{user.inspect} on #{(options[:object] || options[:context]).inspect}." + end + else + false end - true end # Calls permit! but rescues the AuthorizationException and returns false From 792b1558dd79c5305dadade72e9eb458073de190 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 1 Jun 2011 00:14:30 -0700 Subject: [PATCH 28/30] avoid use of exceptions wherever possible with help of new :bang option --- .../authorization.rb | 18 +++---- .../in_controller.rb | 51 ++++++++++--------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index 313a25ac..f55a0ff3 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -191,15 +191,15 @@ def permit! (privilege, options = {}) end end - # Calls permit! but rescues the AuthorizationException and returns false - # instead. If no exception is raised, permit? returns true and yields - # to the optional block. - def permit? (privilege, options = {}, &block) # :yields: - permit!(privilege, options) - yield if block_given? - true - rescue NotAuthorized - false + # Calls permit! but doesn't raise authorization errors. If no exception is + # raised, permit? returns true and yields to the optional block. + def permit? (privilege, options = {}) # :yields: + if permit!(privilege, options.merge(:bang=> false)) + yield if block_given? + true + else + false + end end # Returns the obligations to be met by the current user for the given diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index f2aea1f8..a477df2b 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -42,35 +42,19 @@ def authorization_engine # If no object or context is specified, the controller_name is used as # context. # - def permitted_to? (privilege, object_or_sym = nil, options = {}, &block) - permitted_to!(privilege, object_or_sym, options.merge(:non_bang => true), &block) + def permitted_to? (privilege, object_or_sym = nil, options = {}) + if authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, false)) + yield if block_given? + true + else + false + end end # Works similar to the permitted_to? method, but # throws the authorization exceptions, just like Engine#permit! - def permitted_to! (privilege, object_or_sym = nil, options = {}, &block) - context = object = nil - if object_or_sym.nil? - context = self.class.decl_auth_context - elsif !object_or_sym.respond_to?(:proxy_reflection) and object_or_sym.is_a?(Symbol) - context = object_or_sym - else - object = object_or_sym - end - - non_bang = options.delete(:non_bang) - args = [ - privilege, - {:user => current_user, - :object => object, - :context => context, - :skip_attribute_test => object.nil?}.merge(options) - ] - if non_bang - authorization_engine.permit?(*args, &block) - else - authorization_engine.permit!(*args, &block) - end + def permitted_to! (privilege, object_or_sym = nil, options = {}) + authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, true)) end # While permitted_to? is used for authorization, in some cases @@ -182,6 +166,23 @@ def new_controller_object_for_collection (context_without_namespace, parent_cont instance_variable_set(instance_var, model_or_proxy.new) end + def options_for_permit (object_or_sym = nil, options = {}, bang = true) + context = object = nil + if object_or_sym.nil? + context = self.class.decl_auth_context + elsif !object_or_sym.respond_to?(:proxy_reflection) and object_or_sym.is_a?(Symbol) + context = object_or_sym + else + object = object_or_sym + end + + {:user => current_user, + :object => object, + :context => context, + :skip_attribute_test => object.nil?, + :bang => bang}.merge(options) + end + module ClassMethods # # Defines a filter to be applied according to the authorization of the From 79a4fd058d1daee552a7770a1341e7535b289e98 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Wed, 24 Aug 2011 15:03:18 +0200 Subject: [PATCH 29/30] Improve README to clarify, fixes #111 --- README.rdoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.rdoc b/README.rdoc index 688d93a2..997a1043 100644 --- a/README.rdoc +++ b/README.rdoc @@ -390,10 +390,8 @@ One of three options to install the plugin: gem tumble And call from your application's root directory rake gems:install -* Alternatively, to install from github, execute in your application's root directory +* Alternativelyi, in Rails 2, to install from github, execute in your application's root directory cd vendor/plugins && git clone git://github.com/stffn/declarative_authorization.git -* Or, download one of the released versions from Github at - http://github.com/stffn/declarative_authorization/downloads Then, * provide the requirements as noted below, From bba5e16b858f3b6a9d4abb0621bea70da0cfdf31 Mon Sep 17 00:00:00 2001 From: Steffen Bartsch Date: Fri, 26 Aug 2011 16:36:26 +0200 Subject: [PATCH 30/30] Don't modify enumerable iterating over (closes #108) --- .../authorization.rb | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/declarative_authorization/authorization.rb b/lib/declarative_authorization/authorization.rb index f55a0ff3..895aca87 100644 --- a/lib/declarative_authorization/authorization.rb +++ b/lib/declarative_authorization/authorization.rb @@ -312,23 +312,25 @@ def user_roles_privleges_from_options(privilege, options) [user, roles, privileges] end - def flatten_roles (roles) + def flatten_roles (roles, flattened_roles = Set.new) # TODO caching? - flattened_roles = roles.dup.to_a - flattened_roles.each do |role| - flattened_roles.concat(@role_hierarchy[role]).uniq! if @role_hierarchy[role] + roles.reject {|role| flattened_roles.include?(role)}.each do |role| + flattened_roles << role + flatten_roles(@role_hierarchy[role], flattened_roles) if @role_hierarchy[role] end + flattened_roles.to_a end # Returns the privilege hierarchy flattened for given privileges in context. - def flatten_privileges (privileges, context = nil) + def flatten_privileges (privileges, context = nil, flattened_privileges = Set.new) # TODO caching? raise AuthorizationUsageError, "No context given or inferable from object" unless context - flattened_privileges = privileges.clone - flattened_privileges.each do |priv| - flattened_privileges.concat(@rev_priv_hierarchy[[priv, nil]]).uniq! if @rev_priv_hierarchy[[priv, nil]] - flattened_privileges.concat(@rev_priv_hierarchy[[priv, context]]).uniq! if @rev_priv_hierarchy[[priv, context]] + privileges.reject {|priv| flattened_privileges.include?(priv)}.each do |priv| + flattened_privileges << priv + flatten_privileges(@rev_priv_hierarchy[[priv, nil]], context, flattened_privileges) if @rev_priv_hierarchy[[priv, nil]] + flatten_privileges(@rev_priv_hierarchy[[priv, context]], context, flattened_privileges) if @rev_priv_hierarchy[[priv, context]] end + flattened_privileges.to_a end def matching_auth_rules (roles, privileges, context)