Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

abstracted the acl hanlers

ungrouped requesters/targets
spec
  • Loading branch information...
commit e8a1797e9df935604d0f9f35a0aa50be3ca3ee01 1 parent fa01ed3
@pschrammel authored
Showing with 563 additions and 364 deletions.
  1. +8 −0 .gitignore
  2. +7 −1 CHANGELOG
  3. +109 −45 README.rdoc
  4. +13 −14 Rakefile
  5. +4 −5 active_acl_plus.gemspec
  6. +1 −1  {generators/active_acl/templates → app}/controllers/privileges_controller.rb
  7. +5 −5 {lib → app/models}/active_acl/acl.rb
  8. +3 −3 {lib → app/models}/active_acl/acl_section.rb
  9. +1 −1  {lib → app/models}/active_acl/controller_action.rb
  10. +1 −1  {lib → app/models}/active_acl/controller_group.rb
  11. 0  {lib → app/models}/active_acl/privilege.rb
  12. 0  {lib → app/models}/active_acl/requester_group_link.rb
  13. 0  {lib → app/models}/active_acl/requester_link.rb
  14. 0  {lib → app/models}/active_acl/target_group_link.rb
  15. 0  {lib → app/models}/active_acl/target_link.rb
  16. 0  {generators/active_acl/templates/views → app/view}/privileges/_privilege_form.rhtml
  17. 0  {generators/active_acl/templates/views → app/view}/privileges/edit.rhtml
  18. 0  {generators/active_acl/templates/views → app/view}/privileges/list.rhtml
  19. +7 −4 db/migrate/001_base_table_setup.rb
  20. +0 −29 generators/active_acl/active_acl_generator.rb
  21. +1 −1  init.rb
  22. +11 −5 lib/active_acl.rb
  23. +17 −22 lib/active_acl/acts_as_access_group.rb
  24. +41 −208 lib/active_acl/acts_as_access_object.rb
  25. +17 −0 lib/active_acl/base.rb
  26. +4 −4 lib/active_acl/cache/memcache_adapter.rb
  27. +5 −5 lib/active_acl/cache/no_cache_adapter.rb
  28. +3 −3 lib/active_acl/db/active_record_adapter.rb
  29. +2 −2 lib/active_acl/db/mysql_adapter.rb
  30. +38 −0 lib/active_acl/grant.rb
  31. +29 −0 lib/active_acl/handler/nested_set.rb
  32. +233 −0 lib/active_acl/handler/object_handler.rb
  33. +1 −3 lib/active_acl/load_controller_actions.rb
  34. +2 −2 lib/active_acl/privilege_const_set.rb
View
8 .gitignore
@@ -0,0 +1,8 @@
+spec
+test
+trash
+pkg
+rdoc
+.project
+.loadpath
+.DS_STORE
View
8 CHANGELOG
@@ -1,6 +1,12 @@
ActiveAcl rails authorization system
-Version wip
+Version 0.4.0 - 2008/03/08
+- complete rewrite of the SQL generation
+- abstraction of the grouping
+- added #grant_privilege
+- dropped test, moving to rspec
+- allows ungrouped requesters/targets
+- propper namespace handling of options (THANKS http://github.com/garytaylor)
Version 0.3.1 - 2008/12/11
- forgot active_acl.rb in gem
View
154 README.rdoc
@@ -2,7 +2,8 @@
The ActiveAclPlus plugin implements a flexible, fast and easy to use generic access control system.
==License
-ActiveAclPlus is released under the LGPL[http://www.opensource.org/licenses/lgpl-license.php] (Gnu Lesser General Public License) - see the included LICENSE file, too.
+ActiveAclPlus is released under the LGPL[http://www.opensource.org/licenses/lgpl-license.php]
+(Gnu Lesser General Public License) - see the included LICENSE file, too.
==Features
* ease of use - uses polymorphic collections for associations.
@@ -10,7 +11,7 @@ ActiveAclPlus is released under the LGPL[http://www.opensource.org/licenses/lgpl
* scalable - there are no real benchmarks yet. But the system design is based on http://phpgacl.sourceforge.net, adding object orientation, polymorphism and two levels of caching. PhpGacl claims "A real-world working version with many added layers of complexity supports over 60,000 Accounts, 200 Groups and 300 ACO's." Tests on my dev notebook show 10 - 30 times performance improvements compared to active_rbac.
* caching - uses instance caching and optionally stores permission results in memcached using timeouts.
* flexible - grants simple (2D, like <code>current_user.has_permission?(User::LOGIN)</code>) and object level (3D, like <code>admin.has_permission?(Forum::ADMIN, :on => team_forum)</code>) permissions. Can assign and request permissions to and from every ActiveRecord model. No "hardcoded" permissions - all permissions can be assigned at runtime.
-* grouping - permissions are inherited at target and requester side through groups. Every model implementing an SQL nested set tree may be used as a group.
+* grouping - permissions can be inherited at target and requester side through groups. Every model implementing an SQL nested set tree may be used as a group.
* ControllerAction model loader: It maps controller actions to the DB so they can be used in permission assignment. The access object is available via calling <code>current_action</code> on the controller.
* exchangeable DB interface: ActiveRecord and direct MySQL adapter available
* supports namespaced models and single table inheritance (STI)
@@ -25,41 +26,68 @@ ActiveAclPlus is released under the LGPL[http://www.opensource.org/licenses/lgpl
ActiveAclPlus uses the has_many_polymorphs[http://www.agilewebdevelopment.com/plugins/restful_authentication] plugin. Make shure you've got a recent version, old versions have bugs affecting active_acl_plus.
!!Be sure you have the paches for 2.1, see: http://rubyforge.org/forum/forum.php?thread_id=26041&forum_id=16450
-./script/plugin install git://github.com/popel/active_acl_plus.git
+ ./script/plugin install git://github.com/popel/active_acl_plus.git
-generators XXX
+or
+
+ sudo gem install activeaclplus
==Short summary
-The ActiveAclPlus system consists of access objects, organized by access groups, that request privileges on each other. Allowing or denying access to a privilege is controlled by ACL (access control list entry) objects. Access objects and access groups can be instances of arbitrary ActiveRecord model classes enhanced by acts_as_access_object and acts_as_access_group. They are associated to ACL entries via polymorphic associations.
+The ActiveAclPlus system consists of access objects, which can be organized by access groups,
+that request privileges on each other. Allowing or denying access to a privilege is controlled by
+ACL (access control list entry) objects. Access objects and access groups can be instances of arbitrary
+ActiveRecord model classes enhanced by acts_as_access_object and acts_as_access_group. They are associated
+to ACL entries via polymorphic associations.
===Access objects
-These are basically requesters and targets in the permission system, as for example a User or a Forum model object. In this case a user could act as a requester ("do I have privilege Y?") or target ("does access object X have privilege Y on me?"). A Forum would most certainly be only used as a target, but all access objects can theoretically be used as both requesters and targets. Access objects use the acts_as_access_object macro inside their definition. This registers the model with the ACL system and enhances it with methods like has_privilege?.
-
-Every model class must specify an association that is used as the "grouping type" to it. So User may declare has_and_belongs_to_many :user_groups and use acts_as_access_object :grouped_by => :user_groups. You can use a has_and_belongs_to_many or a belongs_to association for this. Common mapping attributes (:join_table, :foreign_key etc.) are supported.
+These are basically requesters and targets in the permission system, as for example a User or a Forum model object.
+In this case a user could act as a requester ("do I have privilege Y?") or target ("does access object X have
+privilege Y on me?"). A Forum would most certainly be only used as a target, but all access objects can theoretically
+be used as both requesters and targets. Access objects use the acts_as_access_object macro inside their definition.
+This registers the model with the ACL system and enhances it with methods like has_privilege?.
+Every model class must specify an association that is used as the "grouping type" to it. So User may declare
+has_and_belongs_to_many :user_groups and use acts_as_access_object :grouped_by => :user_groups. You can use
+ a has_and_belongs_to_many or a belongs_to association for this. Common mapping attributes
+ (:join_table, :foreign_key etc.) are supported.
===Access groups
-The access group model needs to implement a tree with nested set semantics having a "left" and "right" column, e.g. by using the built-in acts_as_nested_set or the much more recommended acts_as_betted_nested_set plugin. Groups are declared with acts_as_access_group.
-
-Groups may be used to specify inheritance hierarchies for permissions. So you could have a 'registered users' group as a subgroup of the 'users' group and assign the privilege to log in to this group via an ACL entry. Every user belonging to this group will now be granted the privilege to log in. Then you could add a subgroup to registerd users, 'banned users', and deny the log in privilege for this group. Every user added to this group would now be unable to log in, regardless of beeing in 'registered users' or not, as 'banned users' would override the permission settings of 'registered users'.
+The access group model needs to implement a tree with nested set semantics having a "left" and "right"
+column, e.g. by using the built-in acts_as_nested_set or the much more recommended acts_as_betted_nested_set
+plugin. Groups are declared with acts_as_access_group.
+Groups may be used to specify inheritance hierarchies for permissions. So you could have a 'registered users'
+group as a subgroup of the 'users' group and assign the privilege to log in to this group via an ACL entry.
+Every user belonging to this group will now be granted the privilege to log in. Then you could add a subgroup
+to registerd users, 'banned users', and deny the log in privilege for this group. Every user added to this
+group would now be unable to log in, regardless of beeing in 'registered users' or not, as 'banned users' would
+override the permission settings of 'registered users'.
===Privileges
-A privilege object is an object for the thing we wish to define a permission for. So User::LOGIN could be a privilege object for checking a users permission to log in, while Forum::ADMIN might define administration rights on a forum.
-
-A privilege object itself is little more than it’s name and id and is usually bound to a constant inside the application, as it is not expected to change at runtime. Privileges are usually created by the developer in the source code and not in the admin frontend, as creating new privilege objects that have no meaning (by code that checks for them) would be pointless.
+A privilege object is an object for the thing we wish to define a permission for. So User::LOGIN could be a
+privilege object for checking a users permission to log in, while Forum::ADMIN might define administration rights on a forum.
+A privilege object itself is little more than it’s name and id and is usually bound to a constant inside the
+application, as it is not expected to change at runtime. Privileges are usually created by the developer in
+the source code and not in the admin frontend, as creating new privilege objects that have no meaning (by code
+that checks for them) would be pointless.
===Access Control List (ACL) entries
-ACL entries are the glue between all these objects, defining which requesters and requester groups have access to which privileges, optionally defining target objects and target groups as well. ACL entries are organized by ACL sections, for better overview in the admin screens.
-
+ACL entries are the glue between all these objects, defining which requesters and requester groups have access
+to which privileges, optionally defining target objects and target groups as well. ACL entries are organized by
+ACL sections, for better overview in the admin screens.
==Usage
===In short
-"No access defined" for a privilege evaluates to "deny". This may be overriden by an explicit "allow" or "deny". Privileges are inherited in requestor and target groups, this means you can override them in subgroups again. Privileges directly assigned to an object always supercede those assigned to groups.
+"No access defined" for a privilege evaluates to "deny". This may be overriden by an explicit "allow" or "deny".
+Privileges are inherited in requestor and target groups, this means you can override them in subgroups again.
+Privileges directly assigned to an object always supercede those assigned to groups.
===Simple (2D) permissions
-We want all registered users to be able to log in. We create the User model, the UserGroup model and the User::LOGIN privilege object as described above. Then we create a new ACL entry, set 'allow' to true, add the "registered users" group as requester group, User::LOGIN as privilege and we are done. Every user assigned to "registered users" or a subgroup of it will now be granted access by calling <code>my_user.has_privilege?(User::LOGIN)</code>.
+We want all registered users to be able to log in. We create the User model, the UserGroup model and the
+User::LOGIN privilege object as described above. Then we create a new ACL entry, set 'allow' to true, add
+the "registered users" group as requester group, User::LOGIN as privilege and we are done. Every user assigned
+to "registered users" or a subgroup of it will now be granted access by calling <code>my_user.has_privilege?(User::LOGIN)</code>.
====Simple permissions example
@@ -93,7 +121,9 @@ We want all registered users to be able to log in. We create the User model, the
anonymous.has_privilege?(User::LOGIN) #=> false
===Overriding permissions
-We want to ban specific users from our site. We create another ACL entry, assign the User::LOGIN privilege object, set 'allow' to false and then assign these users as requesters to the ACL entry. The direct permission assignment on the objects overrides the 'allow login' ACL entry from above.
+We want to ban specific users from our site. We create another ACL entry, assign the User::LOGIN privilege
+object, set 'allow' to false and then assign these users as requesters to the ACL entry. The direct
+permission assignment on the objects overrides the 'allow login' ACL entry from above.
====Overriding permissions example
@@ -109,11 +139,17 @@ We want to ban specific users from our site. We create another ACL entry, assign
dr_evil.has_privilege?(User::LOGIN) #=> false
===Object level (3D) permissions
-We want to assign forum permissions. We have several privileges (Forum::ADMIN, Forum::READ, Forum::POST etc.), the afore mentioned User and UserGroup models as well as a Forum and a Category model for grouping the forums.
+We want to assign forum permissions. We have several privileges (Forum::ADMIN, Forum::READ, Forum::POST etc.),
+the afore mentioned User and UserGroup models as well as a Forum and a Category model for grouping the forums.
-If we want to check if a certain user may read in a certain forum, it is not sufficient to check <code>test_user.has_privilege?(Forum::READ)</code> as the target object - in this case a forum - is needed to make a decision. The code to do the check is like <code>test_user.has_privilege?(Forum::READ, :on => teamforum)</code>.
+If we want to check if a certain user may read in a certain forum, it is not sufficient to check
+<code>test_user.has_privilege?(Forum::READ)</code> as the target object - in this case a forum - is needed
+to make a decision. The code to do the check is like <code>test_user.has_privilege?(Forum::READ, :on => teamforum)</code>.
-To make this work you create a new ACL entry, add Forum::POST and Forum::READ as privileges, set 'allow' to true, add the registered users group as a requester group and the public forums category as a target group to the acl. Now every user belonging to the registered users group or a subgroup of it gains post and read privileges on all forums of the public forums category or a subcategory of it.
+To make this work you create a new ACL entry, add Forum::POST and Forum::READ as privileges, set 'allow' to
+true, add the registered users group as a requester group and the public forums category as a target group
+to the acl. Now every user belonging to the registered users group or a subgroup of it gains post and read
+privileges on all forums of the public forums category or a subcategory of it.
====Object level permissions example
@@ -152,43 +188,73 @@ To make this work you create a new ACL entry, add Forum::POST and Forum::READ as
anonymous.has_privilege?(Forum::READ, :on => speakers) #=> false
==CAUTION
-Do not create ACL entries that are on different branches of the inheritance hierarchy and have allow/deny set differently on the same privilege objects. This way it's impossible to tell which permission should take precedence. At present this is by creation date, later entries superceding older ones, but this is most certainly not what you want.
+Do not create ACL entries that are on different branches of the inheritance hierarchy and have
+allow/deny set differently on the same privilege objects. This way it's impossible to tell which
+permission should take precedence. At present this is by creation date, later entries superceding older
+ones, but this is most certainly not what you want.
Error checking for conflicting ACL entries is high up on the ToDo list.
==Controller Actions
-Defining permissions on controller actions (like "may user x execute AdminController.list ?") is quite a common case but we are facing a problem here: Controller actions have no corresponding DB models so permissions on them can't be easily defined.
+Defining permissions on controller actions (like "may user x execute AdminController.list ?") is quite a
+common case but we are facing a problem here: Controller actions have no corresponding DB models so
+permissions on them can't be easily defined.
-ActiveAclPlus solves this by adding a ControllerAction and ControllerGroup model. For every public controller method (=action) there is one ControllerAction object in the DB.
+ActiveAclPlus solves this by adding a ControllerAction and ControllerGroup model. For every public
+controller method (=action) there is one ControllerAction object in the DB.
-On application startup, the plugin loader checks all controller files in app/controllers and loads or creates a ControllerAction object for every action it finds. These objects get cached in a hash in the ActiveAclPlus module. Every controller now has a method <code>current_action</code> that looks up and returns the access object for the current action, so it can be used for access checks like <code>current_user.has_privilege?(ActiveAcl::ControllerAction::EXECUTE, :on => current_action)</code>.
+On application startup, the plugin loader checks all controller files in app/controllers and loads
+or creates a ControllerAction object for every action it finds. These objects get cached in a hash
+in the ActiveAclPlus module. Every controller now has a method <code>current_action</code> that looks
+up and returns the access object for the current action, so it can be used for access checks like
+<code>current_user.has_privilege?(ActiveAcl::ControllerAction::EXECUTE, :on => current_action)</code>.
This works nicely for before_filters with authorization checks.
-A word on the load mechanism: If the action has no corresponding DB entry (it's looked up on method creation by controller and action name) the loader searches for a controller group with the same name as the controller. If it is found, the action is created and assigned to this group. Else the controller group is created as a subgroup to the "unassigned controller actions" group (this name can be changed in the options) and the unassigned action is added to the controller group.
+A word on the load mechanism: If the action has no corresponding DB entry (it's looked up on method creation
+by controller and action name) the loader searches for a controller group with the same name as the
+controller. If it is found, the action is created and assigned to this group. Else the controller group
+is created as a subgroup to the "unassigned controller actions" group (this name can be changed in the
+options) and the unassigned action is added to the controller group.
-So you are free on how to organize your controllers. Maybe create an admin group and a public group and move controllers as a subgroup inside them?
+So you are free on how to organize your controllers. Maybe create an admin group and a public group and
+move controllers as a subgroup inside them?
==Caching
-The plugin provides two levels of caching. The instance cache is a hash inside the access object. The object first tries to serve a permission request from the instance cache. If it is not found and a simple permission is requested, the query fetches all simple permissions of the object and puts them in the instance cache. The reason for this is that there is no noticable speed penalty in fetching all 2D permissions at once compared to fetching only one, so this will save time and DB IO later on. Complex 3D queries are fetched independently and also saved to the instance cache. The instance cache lives inside the access object, so it has it's lifetime, too - which in rails usually is no more than a single request.
-
-The second level cache tries to overcome this limitation by putting the instance cache of an access object in an external cache. It tries to get the instance cache from there if it is not set, and sets it if it was changed. The only real implementation for now is with the memcache daemon. The second level cache uses a timeout (which can be defined in the options) to expire the cached permissions.
-
-Instance and second level cache can be expired explicitly by calling <code>clear_cached_permissions</code> on the access object. Calling <code>reload</code> on the object also purges the caches.
+The plugin provides two levels of caching. The instance cache is a hash inside the access object. The object
+first tries to serve a permission request from the instance cache. If it is not found and a simple permission
+is requested, the query fetches all simple permissions of the object and puts them in the instance cache. The
+reason for this is that there is no noticable speed penalty in fetching all 2D permissions at once compared to
+fetching only one, so this will save time and DB IO later on. Complex 3D queries are fetched independently
+and also saved to the instance cache. The instance cache lives inside the access object, so it has it's
+lifetime, too - which in rails usually is no more than a single request.
+
+The second level cache tries to overcome this limitation by putting the instance cache of an access object
+in an external cache. It tries to get the instance cache from there if it is not set, and sets it if it was
+changed. The only real implementation for now is with the memcache daemon. The second level cache uses a timeout
+(which can be defined in the options) to expire the cached permissions.
+
+Instance and second level cache can be expired explicitly by calling <code>clear_cached_permissions</code>
+on the access object. Calling <code>reload</code> on the object also purges the caches.
See ActiveAcl::Cache::MemcacheAdapter on how to set it up.
==Preloader
-The plugin includes a <code>load_files_from filenames</code> function. It can be used to preload source files (and therefore the classes in it) from an application path and should be used from environment.rb.
+The plugin includes a <code>load_files_from filenames</code> function. It can be used to preload source files
+(and therefore the classes in it) from an application path and should be used from environment.rb.
load_files_from("#{RAILS_ROOT}/app/controllers/**/[^.]*.rb")
load_files_from("#{RAILS_ROOT}/app/models/**/[^.]*.rb")
-will load all models and controllers inside these folders and their subfolders. This way you can be shure they are registered with the ACL system at rails boot time - else they will be registered when they are called for the first time. This means that new controllers will not show up in the admin screens until they were accessed if not using the preloader.
+will load all models and controllers inside these folders and their subfolders. This way you can be shure they
+are registered with the ACL system at rails boot time - else they will be registered when they are called for
+the first time. This means that new controllers will not show up in the admin screens until they were accessed
+if not using the preloader.
==Options
-<code>ActiveAcl::OPTIONS</code> is an array that can be used to override various options for the system by setting the values in environment.rb. <code>ActiveAcl::DEFAULT_OPTIONS</code> is as follows:
+<code>ActiveAcl::OPTIONS</code> is an array that can be used to override various options for the system by setting
+the values in environment.rb. <code>ActiveAcl::DEFAULT_OPTIONS</code> is as follows:
DEFAULT_OPTIONS = {
:acl_sections_table => 'acl_sections',
@@ -202,7 +268,8 @@ will load all models and controllers inside these folders and their subfolders.
:controller_actions_table => 'controller_actions',
:controller_groups_table => 'controller_groups',
- :controllers_group_name => 'unassigned_controller_actions', # the name of the base group that newly created controller groups get assigned to
+ :controllers_group_name => 'unassigned_controller_actions', # the name of the base group
+ # that newly created controller groups get assigned to
:controller_group_name_suffix => '_controller', # name suffix for generated controller groups
:cache_permission_timeout => 10, # timeout in seconds for the second level cache
@@ -216,26 +283,23 @@ See: http://github.com/popel/active_acl_plus_test/tree/master for more info.
==Credits
* Gregor Melhorn implemented this and maintained it up to Version 0.2.1. Thanks for releasing this!
-* Evan for writing that great polymorph plugin and beeing so kind to add namespace and tablename support on my request.
+* Evan for writing that great polymorph plugin and beeing so kind to add namespace and tablename support on Gregor's request.
* ReinH and markmeves for great support and suggestions at the rubyonrails channel on freenode.org.
* http://phpgacl.sourceforge.net as a great source of inspiration
-* Obrie for writing plugin_migrations and loaded_plugins and also very nice support when I got stuck with using them.
+* Obrie for writing plugin_migrations and loaded_plugins and also very nice support when Gregor got stuck with using them.
==ToDo/Ideas
in no particular order, just a reminder...
+* add materialized_tree_support
+* use Moneta as key/value store
* direct PostgreSQL interface
-* example on how to integrate with acts_as_authenticated
+* example on how to integrate with authentication
* example on controller actions
-* make grouping optional
-* add interface generators (started)
* error checking for conflicting ACL entries
-* permissions should have more attributes (like :until,:left_boni,...)
* get all permissions for a requester within a section
* get all permissions of a requester (with one query)
* get all permissions of a requester on a target (with one query)
* get all requester with a given privilege on a target (one query)
* get all targets on which a requester has a certain privilege (one query)
-
-
View
27 Rakefile
@@ -18,13 +18,8 @@ Rake::GemPackageTask.new(PKG_GEM) do |p|
p.need_zip = true
end
-desc 'Default: run unit tests.'
-task :default => :test
-
-#desc "Publish the beta gem"
-#task :pgem => [:package] do
-# Rake::SshFilePublisher.new("pluginaweek@pluginaweek.org", "/home/pluginaweek/gems.pluginaweek.org/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
-#end
+desc 'Default: run specs'
+task :default => :spec
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
@@ -67,11 +62,16 @@ task :coverage_diff do
sh "#{RCOV} --rails -T -Ilib -x db/**/* --text-coverage-diff ../../../coverage/active_acl/coverage.info --output ../../../coverage/active_acl test/all_tests.rb"
end
-desc 'Test the active_acl_plus plugin.'
-Rake::TestTask.new(:test) do |t|
- t.libs << 'lib'
- t.pattern = 'test/unit/**/*_test.rb'
- t.verbose = true
+begin #no spec or spec-rails? ok no tasks
+ require 'spec/rake/spectask'
+
+ desc 'Test the active_acl_plus plugin.'
+ Spec::Rake::SpecTask.new(:spec) do |t|
+ #t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ end
+rescue LoadError => e
+ puts "No spec tasks! - gem install rspec-rails (#{__FILE__})"
end
desc 'Generate documentation for the active_acl_plus plugin.'
@@ -79,6 +79,5 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'ActiveAclPlus'
rdoc.options << '--line-numbers' << '--inline-source'
- rdoc.rdoc_files.include('README')
- rdoc.rdoc_files.include('lib/**/*.rb')
+ rdoc.rdoc_files.include('README.rdoc','lib/**/*.rb','app/**/*.rb')
end
View
9 active_acl_plus.gemspec
@@ -2,7 +2,7 @@
require 'rake'
PKG_NAME='activeaclplus'
-PKG_VERSION= "0.3.1"
+PKG_VERSION= "0.4.0"
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
PKG_GEM=Gem::Specification.new do |s|
@@ -16,14 +16,13 @@ PKG_GEM=Gem::Specification.new do |s|
s.email = ["peter.schrammel@gmx.de"]
s.platform = Gem::Platform::RUBY
s.extra_rdoc_files = []
- s.files = FileList["{lib,tasks,generators,db}/**/*"].to_a + %w(init.rb install.rb LICENSE Rakefile README.rdoc CHANGELOG)
+ s.files = FileList["{lib,tasks,generators,db,app}/**/*"].to_a + %w(init.rb install.rb LICENSE Rakefile README.rdoc CHANGELOG)
s.has_rdoc = true
s.homepage = %q{http://activeaclplus.rubyforge.org/}
- s.rdoc_options = ["--main", "README.rdoc"]
+ s.rdoc_options = [ "--title", "Active Acl Plus", "--main", "README.rdoc"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{activeaclplus}
- s.rubygems_version = %q{0.3.0}
- s.summary = %q{activeaclplus 0.3.0}
+ s.summary = "A new Version (#{PKG_VERSION}) of ActiveAclPlus is available."
s.add_dependency "rails", ">= 2.1.0"
end
View
2  ...cl/templates/controllers/privileges_controller.rb → app/controllers/privileges_controller.rb
@@ -1,4 +1,4 @@
-class <%= privileges_class_name %> < ApplicationController
+class PrivilegesController < ApplicationController
verify :method => :post, :only => [ :create, :update],
:redirect_to => { :action => :list }
View
10 lib/active_acl/acl.rb → app/models/active_acl/acl.rb
@@ -14,8 +14,8 @@ class Acl < ActiveRecord::Base
has_many :requester_group_links, :dependent => :delete_all,:class_name => 'ActiveAcl::RequesterGroupLink'
has_many :target_group_links, :dependent => :delete_all,:class_name => 'ActiveAcl::TargetGroupLink'
- validates_uniqueness_of :note
- validates_presence_of :note
+ validates_uniqueness_of :iname
+ validates_presence_of :iname
def self.reloadable? #:nodoc:
return false
@@ -23,11 +23,11 @@ def self.reloadable? #:nodoc:
# used as instance description in admin screen
def active_acl_description
- if note
+ if iname
if section
- '/' + section.description + '/' + note
+ '/' + section.description + '/' + iname
else
- return note
+ return iname
end
else
return nil
View
6 lib/active_acl/acl_section.rb → app/models/active_acl/acl_section.rb
@@ -6,11 +6,11 @@ class AclSection < ActiveRecord::Base
has_many :members, :class_name => 'ActiveAcl::Acl', :foreign_key => 'section_id'
- validates_presence_of :description
- validates_uniqueness_of :description
+ validates_presence_of :iname
+ validates_uniqueness_of :iname
# Make shure there are no associated acls before destroying a section
- def before_destroy
+ def before_destroy #:nodoc:
if members.empty?
true
else
View
2  lib/active_acl/controller_action.rb → app/models/active_acl/controller_action.rb
@@ -3,7 +3,7 @@
class ActiveAcl::ControllerAction < ActiveRecord::Base
set_table_name ActiveAcl::OPTIONS[:controller_actions_table]
privilege_const_set('EXECUTE')
-
+
belongs_to :controller_group, :class_name => 'ActiveAcl::ControllerGroup'
acts_as_access_object :grouped_by => :"active_acl/controller_group"
validates_presence_of :action, :controller, :controller_group
View
2  lib/active_acl/controller_group.rb → app/models/active_acl/controller_group.rb
@@ -3,7 +3,7 @@ class ActiveAcl::ControllerGroup < ActiveRecord::Base
set_table_name ActiveAcl::OPTIONS[:controller_groups_table]
acts_as_nested_set
has_many :controller_actions,:class_name => 'ActiveAcl::ControllerAction'
- acts_as_access_group
+ acts_as_access_group :type => ActiveAcl::Acts::AccessGroup::NestedSet
validates_presence_of :description
View
0  lib/active_acl/privilege.rb → app/models/active_acl/privilege.rb
File renamed without changes
View
0  lib/active_acl/requester_group_link.rb → app/models/active_acl/requester_group_link.rb
File renamed without changes
View
0  lib/active_acl/requester_link.rb → app/models/active_acl/requester_link.rb
File renamed without changes
View
0  lib/active_acl/target_group_link.rb → app/models/active_acl/target_group_link.rb
File renamed without changes
View
0  lib/active_acl/target_link.rb → app/models/active_acl/target_link.rb
File renamed without changes
View
0  .../templates/views/privileges/_privilege_form.rhtml → app/view/privileges/_privilege_form.rhtml
File renamed without changes
View
0  .../active_acl/templates/views/privileges/edit.rhtml → app/view/privileges/edit.rhtml
File renamed without changes
View
0  .../active_acl/templates/views/privileges/list.rhtml → app/view/privileges/list.rhtml
File renamed without changes
View
11 db/migrate/001_base_table_setup.rb
@@ -2,22 +2,25 @@ class BaseTableSetup < ActiveRecord::Migration
def self.up
create_table ActiveAcl::OPTIONS[:acls_table] do |t|
t.column :section_id, :int
+ t.column :iname, :string,:null => false
t.column :allow, :boolean, :null => false, :default => true
t.column :enabled, :boolean, :null => false, :default => true
- t.column :note, :string, :null => true
+ t.column :description, :text, :null => true
t.column :updated_at, :datetime, :null => false
end
add_index ActiveAcl::OPTIONS[:acls_table], :enabled
add_index ActiveAcl::OPTIONS[:acls_table], :section_id
add_index ActiveAcl::OPTIONS[:acls_table], :updated_at
- add_index ActiveAcl::OPTIONS[:acls_table], :note, :unique
+ add_index ActiveAcl::OPTIONS[:acls_table], :iname, :unique
+
create_table ActiveAcl::OPTIONS[:acl_sections_table] do |t|
- t.column :description, :string, :limit => 230, :null => false
+ t.column :iname, :string, :null => false
+ t.column :description, :text, :null => true
end
- add_index ActiveAcl::OPTIONS[:acl_sections_table], :description, :unique
+ add_index ActiveAcl::OPTIONS[:acl_sections_table], :iname, :unique
create_table ActiveAcl::OPTIONS[:privileges_table] do |t|
t.column :section, :string, :limit => 230, :null => false
View
29 generators/active_acl/active_acl_generator.rb
@@ -1,29 +0,0 @@
-class ActiveAclGenerator < Rails::Generator::Base
- attr_accessor :privileges_class_name, :privileges_file_name, :privileges_view_dir
-
- def initialize(*runtime_args)
- super(*runtime_args)
- @privileges_class_name = (args[0] || 'PrivilegesController')
- @privileges_file_name = @privileges_class_name.underscore
- @privileges_view_dir = File.join('app', 'views', @privileges_file_name.gsub('_controller', ''))
- end
-
- def manifest
- record do |m|
- # Stylesheet, controllers and public directories.
- m.directory File.join('public', 'stylesheets')
- m.directory File.join('app', 'controllers')
- m.directory File.join('app', 'views')
- m.directory privileges_view_dir
-
- m.template 'controllers/privileges_controller.rb', File.join('app', 'controllers', "#{privileges_file_name}.rb")
- m.file 'views/privileges/_privilege_form.rhtml', File.join(privileges_view_dir, '_privilege_form.rhtml')
- m.file 'views/privileges/edit.rhtml', File.join(privileges_view_dir, 'edit.rhtml')
- m.file 'views/privileges/list.rhtml', File.join(privileges_view_dir, 'list.rhtml')
- m.migration_template('../../../db/migrate/001_base_table_setup.rb',
- 'db/migrate',
- :assigns => {:migration_name => "BaseTableSetup"},
- :migration_file_name => "base_table_setup")
- end
- end
-end
View
2  init.rb
@@ -1 +1 @@
-require 'active_acl'
+require 'active_acl'
View
16 lib/active_acl.rb
@@ -1,26 +1,32 @@
module ActiveAcl
+
+
end
# plugin dependency
require 'has_many_polymorphs'
-ActiveAcl::CONTROLLERS = {}
-
require 'active_acl/options'
+require 'active_acl/base'
+
require 'active_acl/privilege_const_set'
+require 'active_acl/grant'
+
+require 'active_acl/handler/object_handler'
+require 'active_acl/handler/nested_set'
require 'active_acl/db/active_record_adapter'
require 'active_acl/cache/no_cache_adapter'
require 'active_acl/load_controller_actions'
require 'active_acl/acts_as_access_object'
require 'active_acl/acts_as_access_group'
-
require 'active_acl/load_files_from'
+
# call class so its loaded and registered as access object
# wrap in rescue block so migrations don't fail
begin
ActiveAcl::ControllerAction
ActiveAcl::ControllerGroup
-rescue
- nil
+rescue StandardError => e
+ puts "Error #{e.message} #{e.backtrace.join("\n")}(need migrations?)"
end
View
39 lib/active_acl/acts_as_access_group.rb
@@ -7,38 +7,33 @@ module AccessGroup #:nodoc:
def self.included(base)
base.extend(ClassMethods)
end
-
+
module ClassMethods
- # Extend self with access group capabilites. See README for details
- # on usage. Accepts the following options as a hash:
- # left_column:: name of the left column for nested set functionality, default :lft
- # right_column:: name of the right column for nested set functionality, default :rgt
- # Don't use 'left' and 'right' as column names - these are reserved words in most DBMS.
+ # Extend self with access group capabilites.
+ # Options can be:
+ # type:: is mandatory and is one of the group handler classes
+ # left_column:: for ActiveAcl::Acts::AccessGroup::NestedSet grouped objects
+ # right_column:: for ActiveAcl::Acts::AccessGroup::NestedSet grouped objects
+
def acts_as_access_group(options = {})
- configuration = {:left_column => :lft, :right_column => :rgt,
- :controller => ActiveAcl::OPTIONS[:default_group_selector_controller],
- :action => ActiveAcl::OPTIONS[:default_group_selector_action]}
- configuration.update(options) if options.is_a?(Hash)
- ActiveAcl::GROUP_CLASSES[self.name] = configuration
+ type=options.delete(:type) || ActiveAcl::Acts::AccessGroup::NestedSet
+ ActiveAcl::GROUP_CLASSES[self.name] = type.new(options)
+
+ include ActiveAcl::Acts::Grant
+ include InstanceMethods
+ extend SingletonMethods
- from_classes = ActiveAcl::GROUP_CLASSES.keys.collect do |x|
- x.split('::').join('/').underscore.pluralize.to_sym
- end
-
ActiveAcl::Acl.instance_eval do
- has_many_polymorphs :requester_groups, {:from => from_classes,
+ has_many_polymorphs :requester_groups, {:from => ActiveAcl.from_classes,
:through => :"active_acl/requester_group_links",
:rename_individual_collections => true}
-
- has_many_polymorphs :target_groups, {:from => from_classes,
+
+ has_many_polymorphs :target_groups, {:from => ActiveAcl.from_classes,
:through => :"active_acl/target_group_links",
:rename_individual_collections => true}
end
- include InstanceMethods
- extend SingletonMethods
-
- end
+ end
end
module SingletonMethods
View
249 lib/active_acl/acts_as_access_object.rb
@@ -1,3 +1,5 @@
+#require 'direct_handler'
+
module ActiveAcl #:nodoc:
module Acts #:nodoc:
module AccessObject #:nodoc:
@@ -6,7 +8,7 @@ def self.included(base)
base.extend(ClassMethods)
end
- module ClassMethods
+ module ClassMethods
# Extend self with access object capabilites. See README for details
# on usage. Accepts the following options as a hash:
@@ -15,133 +17,49 @@ module ClassMethods
# join_table:: name of the join table
# foreign_key:: foreign key of self in the join table
# association_foreign_key:: foreign_key of the group class
- # habtm:: set to <code>true</code> if the grup is joined with a habtm association. If not specified, the plugin tries to guess if the association is has_and_belongs_to_many or belongs_to by creating the singular form of the :grouped_by option and comparing it to itself: If it matches, it assumes a belongs_to association.
+ # habtm:: set to <code>true</code> if the grup is joined with a habtm association.
+ # If not specified, the plugin tries to guess if the association is
+ # has_and_belongs_to_many or belongs_to by creating the singular form of the
+ # :grouped_by option and comparing it to itself: If it matches, it assumes a belongs_to association.
def acts_as_access_object(options = {})
- configuration = {
- :controller => ActiveAcl::OPTIONS[:default_selector_controller],
- :action => ActiveAcl::OPTIONS[:default_selector_action]
- }
- if options[:grouped_by]
- configuration[:group_class_name] = options[:grouped_by].to_s.classify
- configuration[:join_table] = [name.pluralize.underscore, configuration[:group_class_name].pluralize.underscore].sort.join('_')
- configuration[:foreign_key] = "#{name.underscore}_id"
- configuration[:association_foreign_key] = "#{configuration[:group_class_name].underscore}_id"
- configuration[:habtm] = (options[:grouped_by].to_s.demodulize.singularize != options[:grouped_by].to_s.demodulize)
- end
- configuration.update(options) if options.is_a?(Hash)
+ handler=ObjectHandler.new(self,options)
- ActiveAcl::ACCESS_CLASSES[self.name] = configuration
+ ActiveAcl::ACCESS_CLASSES[self.name] = handler
has_many :requester_links, :as => :requester, :dependent => :delete_all, :class_name => 'ActiveAcl::RequesterLink'
has_many :requester_acls, :through => :requester_links, :source => :acl, :class_name => 'ActiveAcl::Acl'
-
+
has_many :target_links, :as => :target, :dependent => :delete_all, :class_name => 'ActiveAcl::TargetLink'
has_many :target_acls, :through => :target_links, :source => :acl, :class_name => 'ActiveAcl::Acl'
include InstanceMethods
extend SingletonMethods
-
- from_classes = ActiveAcl::ACCESS_CLASSES.keys.collect do |x|
- x.split('::').join('/').underscore.pluralize.to_sym
- end
-
+ include ActiveAcl::Acts::Grant
+
ActiveAcl::Acl.instance_eval do
- has_many_polymorphs :requesters, {:from => from_classes,
+ has_many_polymorphs :requesters, {:from => ActiveAcl.from_classes,
:through => :"active_acl/requester_links",
:rename_individual_collections => true}
-
- has_many_polymorphs :targets, {:from => from_classes,
+
+ has_many_polymorphs :targets, {:from => ActiveAcl.from_classes,
:through => :"active_acl/target_links",
- :rename_individual_collections => true}
+ :rename_individual_collections => true}
end
-
+
self.module_eval do
# checks if method is defined to not break tests
unless instance_methods.include? "reload_before_gacl"
alias :reload_before_gacl :reload
# Redefines reload, making shure privilege caches are cleared on reload
- def reload
+ def reload #:nodoc:
clear_cached_permissions
reload_before_gacl
end
end
end
-
- # build ACL query strings once, so we don't need to do this on every request
- requester_groups_table = configuration[:group_class_name].constantize.table_name
- requester_group_type = configuration[:group_class_name].constantize.name
- requester_join_table = configuration[:join_table]
- requester_assoc_fk = configuration[:association_foreign_key]
- requester_fk = configuration[:foreign_key]
- requester_group_left = ActiveAcl::GROUP_CLASSES[configuration[:group_class_name]][:left_column].to_s
- requester_group_right = ActiveAcl::GROUP_CLASSES[configuration[:group_class_name]][:right_column].to_s
- requester_type = self.base_class.name
-
- # last join is necessary to weed out rules associated with targets groups
- query = <<-QUERY
- SELECT acls.id, acls.allow, privileges.id AS privilege_id FROM #{ActiveAcl::OPTIONS[:acls_table]} acls
- LEFT JOIN #{ActiveAcl::OPTIONS[:acls_privileges_table]} acls_privileges ON acls_privileges.acl_id=acls.id
- LEFT JOIN #{ActiveAcl::OPTIONS[:privileges_table]} privileges ON privileges.id = acls_privileges.privilege_id
- LEFT JOIN #{ActiveAcl::OPTIONS[:requester_links_table]} r_links ON r_links.acl_id=acls.id
- LEFT JOIN #{ActiveAcl::OPTIONS[:requester_group_links_table]} r_g_links ON acls.id = r_g_links.acl_id AND r_g_links.requester_group_type = '#{requester_group_type}'
- LEFT JOIN #{requester_groups_table} r_groups ON r_g_links.requester_group_id = r_groups.id
- LEFT JOIN #{ActiveAcl::OPTIONS[:target_group_links_table]} t_g_links ON t_g_links.acl_id=acls.id
- QUERY
-
- acl_query_on_target = '' << query
- acl_query_prefetch = '' << query
-
- # if there are no target groups, don't bother doing the join
- # else append type condition
- acl_query_on_target << " AND t_g_links.target_group_type = '%{target_group_type}' "
- acl_query_on_target << " LEFT JOIN #{ActiveAcl::OPTIONS[:target_links_table]} t_links ON t_links.acl_id=acls.id"
- acl_query_on_target << " LEFT JOIN %{target_groups_table} t_groups ON t_groups.id=t_g_links.target_group_id"
- acl_query_on_target << " WHERE acls.enabled = #{connection.quote(true)} AND (privileges.id = %{privilege_id}) "
- acl_query_prefetch << " WHERE acls.enabled = #{connection.quote(true)} "
-
- query = " AND (((r_links.requester_id=%{requester_id} ) AND (r_links.requester_type='#{requester_type}')) OR (r_g_links.requester_group_id IN "
-
- if configuration[:habtm]
- configuration[:query_group] = <<-QUERY
- (SELECT DISTINCT g2.id FROM #{requester_join_table} ml
- LEFT JOIN #{requester_groups_table} g1 ON ml.#{requester_assoc_fk} = g1.id CROSS JOIN #{requester_groups_table} g2
- WHERE ml.#{requester_fk} = %{requester_id} AND (g2.#{requester_group_left} <= g1.#{requester_group_left} AND g2.#{requester_group_right} >= g1.#{requester_group_right})))
- QUERY
- else
- configuration[:query_group] = <<-QUERY
- (SELECT DISTINCT g2.id FROM #{requester_groups_table} g1 CROSS JOIN #{requester_groups_table} g2
- WHERE g1.id = %{requester_group_id} AND (g2.#{requester_group_left} <= g1.#{requester_group_left} AND g2.#{requester_group_right} >= g1.#{requester_group_right})))
- QUERY
- end
-
- query << configuration[:query_group]
- query << " ) AND ( "
-
- acl_query_on_target << query
- acl_query_prefetch << query
-
- query = "(t_links.target_id=%{target_id} AND t_links.target_type = '%{target_type}' ) OR t_g_links.target_group_id IN %{target_group_query} "
-
- acl_query_on_target << query
- acl_query_prefetch << '(t_g_links.acl_id IS NULL)) '
-
- # The ordering is always very tricky and makes all the difference in the world.
- # Order (CASE WHEN r_links.requester_type = \'Group\' THEN 1 ELSE 0 END) ASC
- # should put ACLs given to specific AROs ahead of any ACLs given to groups.
- # This works well for exceptions to groups.
- order_by_on_target = ['(CASE WHEN r_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC ', "r_groups.#{requester_group_left} - r_groups.#{requester_group_right} ASC",
- '(CASE WHEN t_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC', 't_groups.%{target_group_left} - t_groups.%{target_group_right} ASC', 'acls.updated_at DESC']
- order_by_prefetch = ['privileges.id', '(CASE WHEN r_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC ', "r_groups.#{requester_group_left} - r_groups.#{requester_group_right} ASC", 'acls.updated_at DESC']
-
- acl_query_on_target << 'ORDER BY ' + order_by_on_target.join(',') + ' LIMIT 1'
- acl_query_prefetch << 'ORDER BY ' + order_by_prefetch.join(',')
-
- # save query string to configuration
- configuration[:query_target] = acl_query_on_target.gsub(/\n+/, "\n")
- configuration[:query_simple] = acl_query_prefetch.gsub(/\n+/, "\n")
end
end
@@ -158,121 +76,36 @@ module InstanceMethods
# Option :on defines the target object.
def has_privilege?(privilege, options = {})
target = options[:on] #TODO: add error handling if not a hash
-
- unless (privilege and (privilege.is_a?(Privilege)))
- # no need to check anything if privilege is not a Privilege
- return false
- end
-
- unless (target.nil? or (target.class.respond_to?(:base_class) and ActiveAcl::ACCESS_CLASSES.has_key?(target.class.base_class.name)))
- # no need to check anything if target is no Access Object
- return false
- end
+ # no need to check anything if privilege is not a Privilege
+ raise "first Argument has to be a Privilege" unless privilege.is_a?(Privilege)
+ # no need to check anything if target is no Access Object
+ raise "target hast to be an AccessObject" if target and !(target.class.respond_to?(:base_class) and ActiveAcl::ACCESS_CLASSES.has_key?(target.class.base_class.name))
- query_id = [privilege.id, self.class.base_class.name, id, (target ? target.class.base_class.name : ''), (target ? target.id.to_s : '')].join('-')
- cache_id = 'gacl_instance-' + self.class.base_class.name + '-' + id.to_s
- cache = ActiveAcl::OPTIONS[:cache]
-
- # try to load instance cache from second level cache if not present
- @gacl_instance_cache = cache.get(cache_id) if @gacl_instance_cache.nil?
-
- # try to get from instance cache
- if @gacl_instance_cache
- if not (value = @gacl_instance_cache[query_id]).nil?
- logger.debug 'GACL::INSTANCE_CACHE::' + (value ? 'GRANT ' : 'DENY ') + query_id if logger.debug?
- return value
- elsif target.nil? and @gacl_instance_cache[:prefetch_done]
- # we didn't get a simple query from prefetched cache => cache miss
- logger.debug 'GACL::INSTANCE_CACHE::DENY ' + query_id if logger.debug?
- return false
- end
- end
-
- if value.nil? # still a cache miss?
-
- value = false
-
- r_config = ActiveAcl::ACCESS_CLASSES[self.class.base_class.name]
-
- if target
- qry = r_config[:query_target].clone
-
- t_config = ActiveAcl::ACCESS_CLASSES[target.class.base_class.name]
-
- qry.gsub!('%{target_group_type}', t_config[:group_class_name])
- qry.gsub!('%{target_groups_table}', t_config[:group_class_name].constantize.table_name)
- qry.gsub!('%{target_group_left}', ActiveAcl::GROUP_CLASSES[t_config[:group_class_name]][:left_column].to_s)
- qry.gsub!('%{target_group_right}', ActiveAcl::GROUP_CLASSES[t_config[:group_class_name]][:right_column].to_s)
- qry.gsub!('%{target_type}', target.class.base_class.name)
- qry.gsub!('%{target_id}', target.id.to_s)
-
- group_query = t_config[:query_group].clone
- group_query.gsub!('%{requester_id}', target.id.to_s)
- group_query.gsub!('%{requester_group_id}', target.send(t_config[:association_foreign_key]).to_s) unless t_config[:habtm]
-
- qry.gsub!('%{target_group_query}', group_query)
- else
- qry = r_config[:query_simple].clone
- end
-
- # substitute variables
- qry.gsub!('%{requester_id}', self.id.to_s)
- qry.gsub!('%{privilege_id}', privilege.id.to_s)
- qry.gsub!('%{requester_group_id}', self.send(r_config[:association_foreign_key]).to_s) unless r_config[:habtm]
- results = ActiveAcl::OPTIONS[:db].query(qry)
-
- if target.nil?
- # prefetch privileges
- privilegevalue = nil
- @gacl_instance_cache = {}
-
- results.each do |row|
- if row['privilege_id'] != privilegevalue
- privilegevalue = row['privilege_id']
- c_id = [privilegevalue, self.class.base_class.name, id, '', ''].join('-')
- @gacl_instance_cache[c_id] = ((row['allow'] == '1') or (row['allow'] == 't'))
- end
- end
-
- value = @gacl_instance_cache[query_id]
- @gacl_instance_cache[:prefetch_done] = true
-
- elsif not results.empty?
- # normal gacl query without prefetching
- value = ((results[0]['allow'].to_s == '1') or (results[0]['allow'].to_s == 't'))
- @gacl_instance_cache ||= {} # create if not exists
-
- @gacl_instance_cache[query_id] = value
- end
-
- # nothing found, deny access
- @gacl_instance_cache[query_id] = value = false if value.nil?
-
- # save to second level cache
- cache.set(cache_id, @gacl_instance_cache, ActiveAcl::OPTIONS[:cache_privilege_timeout])
-
- logger.debug 'GACL::INSTANCE_CACHE::' + (value ? 'GRANT ' : 'DENY ') + query_id if logger.debug?
-
- end # cache miss
- return value
+ active_acl_handler.has_privilege?(self,privilege,target)
+ end
+ def active_acl_handler
+ ActiveAcl::ACCESS_CLASSES[self.class.name]
+ end
+ #returns a key value store
+ def active_acl_instance_cache
+ @active_acl_instance_cache ||= active_acl_handler.get_instance_cache(self)
+ end
+ #returns if the 2d acls are already cached
+ def active_acl_cached_2d?
+ !!active_acl_instance_cache[:prefetched_2d]
+ end
+ def active_acl_cached_2d!
+ active_acl_instance_cache[:prefetched_2d]=true
end
+ def active_acl_clear_cache!
+ @active_acl_instance_cache ={} #clear the lokal cache
+ active_acl_handler.delete_cached(self) #clear the 2 level cache
+ end
# override this to customize the description in the interface
def active_acl_description
to_s
end
-
- # link to model selector
- def self.model_selector_link params
- AclsController.url_for(:action => :show_group_members, :clazz => self.class, *params)
- end
-
- # clears the permission caches (instance and memory cache)
- def clear_cached_permissions
- @gacl_instance_cache = nil
- ActiveAcl::OPTIONS[:cache].delete('gacl_instance-' + self.class.name + '-' + id.to_s)
- end
-
end
end
end
View
17 lib/active_acl/base.rb
@@ -0,0 +1,17 @@
+module ActiveAcl
+ CONTROLLERS={}
+ GROUP_CLASSES={}
+ ACCESS_CLASSES={}
+
+ def self.is_access_group?(klass)
+ !!ActiveAcl::GROUP_CLASSES[klass.name]
+ end
+ def self.is_access_object?(klass)
+ !!ActiveAcl::ACCESS_CLASSES[klass.name]
+ end
+ def self.from_classes
+ ActiveAcl::ACCESS_CLASSES.keys.collect do |x|
+ x.split('::').join('/').underscore.pluralize.to_sym
+ end
+ end
+end
View
8 lib/active_acl/cache/memcache_adapter.rb
@@ -8,7 +8,7 @@
# ActiveAcl::Cache::MemcacheAdapter.cache = MemCache.new('localhost:11211', :namespace => 'my_namespace')
#
# Detailed instructions on how to set up the server can be found at http://dev.robotcoop.com/Libraries/memcache-client.
-class ActiveAcl::Cache::MemcacheAdapter
+class ActiveAcl::Cache::MemcacheAdapter #:nodoc:
# returns the memcache server
def self.cache
@@ -23,7 +23,7 @@ def self.cache= cache
# get a value from the cache
def self.get(key)
value = @@cache.get(key)
- RAILS_DEFAULT_LOGGER.debug 'GACL::SECOND_LEVEL_CACHE::' + (value.nil? ? 'MISS ' : 'HIT ')+ key.to_s if RAILS_DEFAULT_LOGGER.debug?
+ Rails.logger.debug 'GACL::SECOND_LEVEL_CACHE::' + (value.nil? ? 'MISS ' : 'HIT ')+ key.to_s
value
end
@@ -31,13 +31,13 @@ def self.get(key)
# Set ttl to 0 for unlimited.
def self.set(key, value, ttl)
@@cache.set(key, value, ttl)
- RAILS_DEFAULT_LOGGER.debug 'GACL::SECOND_LEVEL_CACHE::SET ' + key.to_s + ' TO ' + value.to_s + ' TTL ' + ttl.to_s if RAILS_DEFAULT_LOGGER.debug?
+ Rails.logger.debug 'GACL::SECOND_LEVEL_CACHE::SET ' + key.to_s + ' TO ' + value.inspect.to_s + ' TTL ' + ttl.to_s
end
# purge data from cache.
def self.delete(key)
@@cache.delete(key)
- RAILS_DEFAULT_LOGGER.debug 'GACL::SECOND_LEVEL_CACHE::DELETE ' + key.to_s if RAILS_DEFAULT_LOGGER.debug?
+ Rails.logger.debug 'GACL::SECOND_LEVEL_CACHE::DELETE ' + key.to_s
end
end
View
10 lib/active_acl/cache/no_cache_adapter.rb
@@ -1,22 +1,22 @@
# This module contains different second level cache implementations. The second
# level cache caches the instance cache of an access object between requests.
# Cache adapter can be set with ActiveAcl::OPTIONS[:cache].
-module ActiveAcl::Cache
+module ActiveAcl::Cache #:nodoc:
# The default second level cache dummy implementation, not implementing any
# caching functionality at all.
- class NoCacheAdapter
+ class NoCacheAdapter #:nodoc:
def self.get(key)
- RAILS_DEFAULT_LOGGER.debug 'GACL::SECOND_LEVEL_CACHE::DISABLED::MISS ' + key.to_s if RAILS_DEFAULT_LOGGER.debug?
+ Rails.logger.debug 'ACTIVE_ACL::SECOND_LEVEL_CACHE::DISABLED::MISS ' + key.to_s
nil
end
def self.set(key, value, ttl)
- RAILS_DEFAULT_LOGGER.debug 'GACL::SECOND_LEVEL_CACHE::DISABLED::SET ' + key.to_s + ' TO ' + value.to_s + ' TTL ' + ttl.to_s if RAILS_DEFAULT_LOGGER.debug?
+ Rails.logger.debug 'ACTIVE_ACL::SECOND_LEVEL_CACHE::DISABLED::SET ' + key.to_s + ' TO ' + value.inspect + ' TTL ' + ttl.to_s
end
def self.delete(key)
- RAILS_DEFAULT_LOGGER.debug 'GACL::SECOND_LEVEL_CACHE::DISABLED::DELETE ' + key.to_s if RAILS_DEFAULT_LOGGER.debug?
+ Rails.logger.debug 'ACTIVE_ACL::SECOND_LEVEL_CACHE::DISABLED::DELETE ' + key.to_s
end
end
end
View
6 lib/active_acl/db/active_record_adapter.rb
@@ -1,12 +1,12 @@
# Includes different DBMS adapters, some of them using C extensions to speed up DB access.
# DB adapter can be set with ActiveAcl::OPTIONS[:db].
-module ActiveAcl::DB
+module ActiveAcl::DB #:nodoc:
# Uses ActiveRecord for privilege queries. Should be compatible to all
# db types.
- class ActiveRecordAdapter
+ class ActiveRecordAdapter #:nodoc:
# Execute sql query against the DB, returning an array of results.
- def self.query(sql)
+ def self.query(sql)
ActiveRecord::Base.connection.select_all(sql)
end
end
View
4 lib/active_acl/db/mysql_adapter.rb
@@ -8,11 +8,11 @@ class ActiveRecord::ConnectionAdapters::MysqlAdapter #:nodoc:
# Uses the native MySQL connection to do privilege selects. Should be around 20 % faster than
# ActiveRecord adapter. Sets itself as the DB adapter if the source file is loaded, so requiring it
# is enough to get it activated.
-class ActiveAcl::DB::MySQLAdapter
+class ActiveAcl::DB::MySQLAdapter #:nodoc:
# Execute sql query against the DB, returning an array of results.
def self.query(sql)
- RAILS_DEFAULT_LOGGER.debug 'GACL::DB::EXECUTING QUERY ' + sql if RAILS_DEFAULT_LOGGER.debug?
+ Rails.logger.debug 'GACL::DB::EXECUTING QUERY ' + sql if Rails.logger.debug?
connection = ActiveRecord::Base.connection.connection
connection.query_with_result = true
View
38 lib/active_acl/grant.rb
@@ -0,0 +1,38 @@
+module ActiveAcl
+ module Acts
+ module Grant
+ # grant_permission!(Blog::DELETE,
+ # :on => blog,
+ # :section => 'blogging'
+ # :iname => 'blogging_of_admins'
+ def grant_permission!(privilege,options={})
+ section_name = options[:section] || 'generic'
+ target = options[:on]
+ iname = options[:iname] || "#{privilege.active_acl_description}"
+ acl=nil
+ ActiveAcl::Acl.transaction do
+ section = ActiveAcl::AclSection.find_or_create_by_iname(section_name)
+ section.save! if section.new_record?
+ acl = ActiveAcl::Acl.create :section => section,:iname => iname
+ acl.save!
+
+ acl.privileges << privilege
+ if ActiveAcl.is_access_group?(self.class)
+ acl.requester_groups << self
+ else
+ acl.requesters << self
+ end
+ if target
+ if ActiveAcl.is_access_group?(target.class)
+ acl.target_groups << target
+ else
+ acl.targets << target
+ end
+ end
+ active_acl_clear_cache! if ActiveAcl.is_access_object?(self.class)
+ end
+ acl
+ end
+ end #module
+ end
+end
View
29 lib/active_acl/handler/nested_set.rb
@@ -0,0 +1,29 @@
+module ActiveAcl
+ module Acts #:nodoc:
+ module AccessGroup #:nodoc:
+ class NestedSet #:nodoc:
+ attr_reader :left_column,:right_column
+ def initialize(options)
+
+ @left_column = options[:left_column] || :lft
+ @right_column = options[:right_column] || :rgt
+ # :controller => ActiveAcl::OPTIONS[:default_group_selector_controller],
+ # :action => ActiveAcl::OPTIONS[:default_group_selector_action]}
+
+ end
+ def group_sql(object_handler,target = false)
+ target_requester = (target ? 'target' : 'requester')
+ if object_handler.habtm?
+ "(SELECT DISTINCT g2.id FROM #{object_handler.join_table} ml
+ LEFT JOIN #{object_handler.group_table_name} g1 ON ml.#{object_handler.association_foreign_key} = g1.id CROSS JOIN #{object_handler.group_table_name} g2
+ WHERE ml.#{object_handler.foreign_key} = %{#{target_requester}_id} AND (g2.#{left_column} <= g1.#{left_column} AND g2.#{right_column} >= g1.#{right_column}))"
+ else
+ "(SELECT DISTINCT g2.id FROM #{object_handler.group_table_name} g1 CROSS JOIN #{object_handler.group_table_name} g2
+ WHERE g1.id = %{#{target_requester}_group_id} AND (g2.#{left_column} <= g1.#{left_column} AND g2.#{right_column} >= g1.#{right_column}))"
+ end
+ #"r_groups.#{left_column} - r_groups.#{right_column} ASC"
+ end
+ end #class
+ end
+ end
+end
View
233 lib/active_acl/handler/object_handler.rb
@@ -0,0 +1,233 @@
+module ActiveAcl #:nodoc:
+ module Acts #:nodoc:
+ module AccessObject #:nodoc:
+
+ # handels grouped objects
+ # the group is a nested_set
+ class ObjectHandler #:nodoc:
+ attr_reader :klass,:group_class_name,:join_table,:group_table_name,
+ :foreign_key,:association_foreign_key
+ def initialize(klass,options={})
+ @klass = klass
+ if options[:grouped_by]
+ @group_class_name = options[:grouped_by].to_s.classify
+ @group_table_name=@group_class_name.constantize.table_name
+ @join_table = options[:join_table] || [klass.name.pluralize.underscore.gsub(/\//,'_'), group_class_name.pluralize.underscore.gsub(/\//,'_')].sort.join('_')
+ @foreign_key = options[:foreign_key] || "#{klass.name.demodulize.underscore}_id"
+ @association_foreign_key = options[:association_foreign_key] || "#{group_class_name.demodulize.underscore}_id"
+ @habtm = options[:habtm] || (options[:grouped_by].to_s.demodulize.singularize != options[:grouped_by].to_s.demodulize)
+ end
+
+ #set the SQL fragments
+ requester_query
+ target_query
+ end
+ def habtm?
+ @habtm
+ end
+ def grouped?
+ !!@group_class_name
+ end
+
+ def klass_name
+ klass.base_class.name
+ end
+ def group_handler
+ ActiveAcl::GROUP_CLASSES[@group_class_name]
+ end
+
+ #checks the privilege of a requester on a target (optional)
+ def has_privilege?(requester,privilege,target=nil)
+ value = get_cached(requester,privilege,target)
+
+ return value unless value.nil? #got the cached and return
+ #todo check cash l2
+
+ vars={'requester_id' => requester.id}
+ sql = ''
+ sql << query_r_select
+ if target
+ sql << query_t_select
+ sql << " WHERE "
+ sql << query_r_where_3d
+ sql << query_t_where
+ sql << "\n ORDER BY "
+ sql << order_by_3d
+ sql << " LIMIT 1"
+ vars['privilege_id'] = privilege.id
+ vars['target_id'] = target.id
+ vars['target_type'] = target.class.base_class.name
+ else
+ sql << " WHERE "
+ sql << query_r_where_2d
+ sql << "\n ORDER BY "
+ sql << order_by_2d
+ end
+
+ #replacing the vars in the SQL
+ sql=sql.gsub(/%{[^}]+}/) do |var|
+ vars[var[2..-2]] || var
+ end
+
+ results = ActiveAcl::OPTIONS[:db].query(sql) #get the query from the db
+ value=set_cached(requester,privilege,target,results)
+ return value
+ end
+ #gets the instance cache from the background store or a hash
+ def get_instance_cache(requester)
+ cache.get(requester_cache_id(requester)) || {}
+ end
+ #destroy the 2nd level cache
+ def delete_cached(requester)
+ cache.delete(requester_cache_id(requester))
+ end
+
+
+ #Things go private from here ----------------
+ private
+ def cache
+ ActiveAcl::OPTIONS[:cache]
+ end
+
+ #builds a instance_cache key for a query
+ def query_id(requester,privilege,target)
+ privilege_id = (privilege.kind_of?(ActiveAcl::Privilege) ? privilege.id : privilege)
+ [privilege_id, klass.base_class.name, requester.id, (target ? target.class.base_class.name : ''), (target ? target.id.to_s : '')].join('-')
+ end
+
+ #builds the cache key for a requester for beackground cache
+ def requester_cache_id(requester)
+ 'active_acl_instance-' + klass.base_class.name + '-' + requester.id.to_s
+ end
+
+ # Caching is done on different levels:
+ # Requesting a 2d privilege should fill the instance cache with all 2d privileges
+ # Requesting a 3d should be stored in the instance cache
+ # changing the instance cache stores it to the backstore (if any exists)
+ def get_cached(requester,privilege,target)
+
+ instance_cache=requester.active_acl_instance_cache
+ q_id=query_id(requester,privilege,target)
+ # try to get from instance cache
+ if (value=instance_cache[q_id]).nil? #cache miss?
+ if target.nil? && requester.active_acl_cached_2d?
+ Rails.logger.debug 'ACTIVE_ACL::INSTANCE_CACHE::DENY ' + q_id
+ return false #it should be cached but it's not there: DENY
+ else
+ return nil #we don't cache all 3d acl: DB LOOKUP
+ end
+ else #found in cache: return the results
+ Rails.logger.debug 'ACTIVE_ACL::INSTANCE_CACHE::' + (value ? 'GRANT ' : 'DENY ') + q_id
+ return value
+ end
+ nil
+ end
+
+ def set_cached(requester,privilege,target,results)
+
+ this_query_id=query_id(requester,privilege,target)
+ instance_cache=requester.active_acl_instance_cache
+
+ if target.nil? #no target? then results are all 2d privileges of the requester
+ last_privilege_value = nil
+ results.each do |row|
+ if row['privilege_id'] != last_privilege_value
+ last_privilege_value = row['privilege_id']
+ q_id=query_id(requester,privilege,target)
+ #TODO: put the into the db handler
+ v=((row['allow'] == '1') or (row['allow'] == 't'))
+ instance_cache[q_id] = v
+ end
+ end
+ requester.active_acl_cached_2d! #mark the cache as cached (at least 2d)
+ # the result should be in the cache now or we return false
+ value=instance_cache[this_query_id] || false
+ else #3d request?
+ if results.empty?
+ value=false
+ instance_cache[this_query_id] = value
+ else #3d and a hit
+ value = ((results[0]['allow'].to_s == '1') or (results[0]['allow'].to_s == 't'))
+ instance_cache[this_query_id] = value
+ end
+ end
+ raise "something went realy wrong!" if value.nil?
+
+ #cache the whole instance cache
+ cache.set(requester_cache_id(requester),instance_cache,ActiveAcl::OPTIONS[:cache_privilege_timeout])
+
+ value
+ end
+
+ # build ACL query strings once,
+ # so we don't need to do this on every request
+ # SQL:
+ # we always need acl,and privileges, and requester_links
+ # we need the target_links if its a 3d query
+ # we need the target_groups if the it's a 3d query and the target is grouped
+ # we need the requester_groups if the requester is grouped
+ # the ordering depens on 2d/3d
+ # We'll build the SQL on demand and cache it so it'll
+ # be a function of: requester,target,privilege
+ attr_reader :query_r_select, :query_r_where_2d, :query_r_where_3d, :order_by_3d,:order_by_2d
+ def requester_query
+ @query_r_select = <<-QUERY
+ SELECT acls.id, acls.allow, privileges.id AS privilege_id FROM #{ActiveAcl::OPTIONS[:acls_table]} acls
+ LEFT JOIN #{ActiveAcl::OPTIONS[:acls_privileges_table]} acls_privileges ON acls_privileges.acl_id=acls.id
+ LEFT JOIN #{ActiveAcl::OPTIONS[:privileges_table]} privileges ON privileges.id = acls_privileges.privilege_id
+ LEFT JOIN #{ActiveAcl::OPTIONS[:requester_links_table]} r_links ON r_links.acl_id=acls.id
+ QUERY
+ if grouped?
+ requester_groups_table = group_class_name.constantize.table_name
+ requester_group_type = group_class_name.constantize.name
+
+ @query_r_select << "
+ LEFT JOIN #{ActiveAcl::OPTIONS[:requester_group_links_table]} r_g_links ON acls.id = r_g_links.acl_id AND r_g_links.requester_group_type = '#{requester_group_type}'
+ LEFT JOIN #{requester_groups_table} r_groups ON r_g_links.requester_group_id = r_groups.id
+ "
+ end
+
+ @query_r_where_3d = "acls.enabled = #{klass.connection.quote(true)} AND (privileges.id = %{privilege_id}) "
+ @query_r_where_2d = "acls.enabled = #{klass.connection.quote(true)}"
+ query = " AND ((r_links.requester_id=%{requester_id}
+ AND r_links.requester_type='#{klass.base_class.name}')"
+ if grouped?
+
+ query << " OR (r_g_links.requester_group_id IN #{group_handler.group_sql(self)})) "
+ else
+ query << ")"
+ end
+ @query_r_where_3d << query
+ @query_r_where_2d << query
+
+
+ #@query_r_where_2d << '(t_g_links.acl_id IS NULL)) '
+ @order_by_3d = '(CASE WHEN r_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC,
+ (CASE WHEN t_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC'
+ #TODO ordering of groups
+ @order_by_2d = 'privileges.id, (CASE WHEN r_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC , acls.updated_at DESC'
+ end
+
+ attr_reader :query_t_select,:query_t_where
+ def target_query
+ @query_t_select = " LEFT JOIN #{ActiveAcl::OPTIONS[:target_links_table]} t_links ON t_links.acl_id=acls.id"
+ if grouped?
+ target_groups_table = @group_class_name.constantize.table_name
+ target_group_type = @group_class_name.constantize.name
+
+ @query_t_select << " LEFT JOIN #{ActiveAcl::OPTIONS[:target_group_links_table]} t_g_links ON t_g_links.acl_id=acls.id
+ AND t_g_links.target_group_type = '#{target_group_type}'
+ LEFT JOIN #{target_groups_table} t_groups ON t_groups.id=t_g_links.target_group_id"
+ end
+ @query_t_where = " AND ((t_links.target_id=%{target_id}
+ AND t_links.target_type = '%{target_type}' )"
+ if grouped?
+ @query_t_where << " OR t_g_links.target_group_id IN #{group_handler.group_sql(self,true)})"
+ else
+ @query_t_where << ")"
+ end
+ end
+ end
+ end
+ end
+end
View
4 lib/active_acl/load_controller_actions.rb
@@ -1,5 +1,3 @@
-#require 'active_support'
-#require 'action_view'
class ActionController::Base
# Get the access object for the current action.
@@ -14,7 +12,7 @@ class << self
# Overrides method_added, so the needed ActiveAcl::ControllerAction is loaded/created
# when the action gets added to the controller.
- def self.method_added(action)
+ def self.method_added(action) #:nodoc:
method_added_before_active_acl_controller_action_loading(action)
ActiveAcl::CONTROLLERS[self.name] ||= {}
View
4 lib/active_acl/privilege_const_set.rb
@@ -9,10 +9,10 @@ def privilege_const_set(constant, force_reload = false)
result = []
constant.is_a?(Hash) ? constant_hash = constant : constant_hash = {constant.to_s => nil}
constant_hash.each_pair do |constant_name, description|
- if !const_defined?(constant_name.to_s) or force_reload
+ if !const_defined?(constant_name.to_s) || force_reload
remove_const(constant_name.to_s) if const_defined?(constant_name.to_s)
privilege = ActiveAcl::Privilege.find_by_section_and_value(self.name, constant_name.to_s)
- privilege = ActiveAcl::Privilege.create(:section => self.name, :value => constant_name.to_s, :description => description) unless privilege
+ privilege = ActiveAcl::Privilege.create!(:section => self.name, :value => constant_name.to_s, :description => description) unless privilege
const_set(constant_name.to_s, privilege)
result << privilege
end
Please sign in to comment.
Something went wrong with that request. Please try again.