Short summary

The ActiveAcl 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.

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.

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’.

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 its 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.

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.

ActiveAcl 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 ActiveAcl module. Every controller now has a method current_action that looks up and returns the access object for the current action, so it can be used for access checks like current_user.has_privilege?(ActiveAcl::ControllerAction::EXECUTE, :on => current_action).

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.

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?