Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Zend_Acl Component Proposal

Proposed Component Name Zend_Acl
Developer Notes
Proposers Simon Mundy
Revision 0.2 - Finalised proposal (wiki revision: 11)

Table of Contents

1. Overview

Zend_Acl provides lightweight and flexible access control list (ACL) functionality. In general, an application may utilize such functionality to control access to certain protected objects by other requesting objects.

In this proposal:

  • an Access Control Object (ACO) is an object to which access is controlled.
  • an Access Request Object (ARO) is an object that may request access to an ACO.

For example, if a person requests access to a car, then the person would be the requesting ARO, and the car would be the ACO, since its access is under control.

Through the specification and use of an access control list (ACL), an application may control how requesting objects (AROs) are granted access to protected objects (ACOs).

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

  • No database backend required - complete PHP implementation
  • Permits explicit allow-all, allow-specific, neutral, deny-specific, deny-all for all groups
  • Authorization queries must be computed with efficiency and speed
  • Performance penalties, where reasonably unavoidable, should be isolated to ACL data structure modifications
  • Instances must be serializable (e.g., for caching ACL data structures)

4. Dependencies on Other Framework Components

  • Zend_Exception

5. Theory of Operation

The goal of Zend_Acl was to create a simple, flexible means of creating ACLs, free from the dependencies of Databases, template engines or complex administration tools. The starting point was phpGACL, as its 'allow,deny' and inheritance scheme is straightforward and allows huge flexibility for implementation.

Zend_Acl builds on the tree-based model of phpGACL, providing a means to add multiple ACOs that may represent 'areas' or 'domains' of control within the ACL. Objects under access control may be organized from general (at the tree root) to specific (toward tree leaves). This approach allows for creating very fine-grained access controls, where desirable, with minimal effort.

Each ACO may also be assigned its own set of 'actions' or 'permissions' (e.g., add, edit, delete) that can be used to determine the level of access each requesting ARO would have. Actions may be assigned to ACOs simply by calling allow() or deny() on the ACO in question.

AROs may be organized in different ways, according to the particular needs of an application. To accommodate these various organizations, the ARO registry of Zend_Acl is represented by a directed acyclic graph (DAG) data structure. This approach supports having multiple roles or groups, and AROs may also inherit permissions from multiple AROs.

To illustrate by example, an application may need access roles for administrators, members, and the general public. Each of these roles would have a corresponding ARO in the access control list. The general public would have the least access privileges, members would have additional permissions (e.g., to edit their profiles), and administrators would have exclusive permissions to perform administrative operations (e.g., blocking abuse). Suppose we also have an ARO for each login user identity. Each of these "user" AROs may inherit permissions that have been assigned to one or more of the "role" AROs previously mentioned (e.g., administrators). In this way we can add users to, and remove users from, such roles without having to otherwise modify the access controls.

6. Milestones / Tasks

zone: Missing {zone-data:milestones}

7. Class Index

  • Zend_Acl
  • Zend_Acl_Aro
  • Zend_Acl_Aro_Registry
  • Zend_Acl_Permission

8. Use Cases

At its most simplistic level, Zend_Acl can provide the bare necessities of an authorisation scheme. To illustrate, we'll assume the following 'real-world' scenario:-

ACL - Football match

ACOs - The ground
  • Stadium
  • Public seating
  • Reserved seating
  • Corporate seating
  • Coaches box
  • Dressing rooms
  • Pitch
AROs - The groups
  • Public (or '_default' group) - unreserved seats or reserved seats
  • Guests - members of the public who are invited to a corporate box
  • Coaching staff
  • Players
  • Club officials
Basic setup

We create an ACL instance and add our AROs, adding inheritence where necessary.

Now we start adding the expected rules typical to a game of football

The above example demonstrates how a moderately complex ACL can be created with a minimum of effort. However, if you wanted to refine these generic rules so that the AROs can perform only certain actions, then you can start getting selective

Now it's time to use your ACL during the execution of your script. A simple 'valid' method provides a boolean answer to each of your queries

Zend_Acl will assume that non-specific AROs or contexts will apply to any member, so you can provide null values to these arguments as you add permissions. However, if you add specific 'allow' or 'deny' actions to an ACO, you are effectively providing only limited access to that ACO. You can, however, add multiple permissions to the same ACO to different AROs to ensure your permissions are as fine-grained as you require. Zend_Acl will also check for conflicts when adding permissions to remove the likelihood of 'deadlocks'.

9. Class Skeletons



Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jul 31, 2006

    <p>For setAllow/setDeny, I think an array would be more appropriate for the first parameter rather than a delimited string. Perhaps the type could be mixed where a string would indicate a single permission and an array would indicate multiple permissions. Beyond that, I think the API is structured well and very much hope to see it adopted into the core.</p>

    1. Jul 31, 2006

      <p>Indeed, the _getContext method will accept either a string (which is transformed into an array) or an existing array.</p>

      <p>While I look at this again with fresh eyes, though, I realise that a 'setAllow' or 'setDeny' doesn't allow you to incrementally add values - I will update the proposal shortly to allow addAllow and addDeny. It will also automatically resolve conflicts whilst doing so (e.g. if 'edit' existed in 'deny' and then I added 'edit' to 'allow' it will remove the former to prevent a neutral result).</p>

  2. Aug 02, 2006

    <p>OK, I've updated the class to allow incremental permissions via addAllow, addDeny, removeAllow and removeDeny. You can also pass an array or a string to any of the values (context, container and/or group) to allow more flexible and concise population of permissions.</p>

    <p>Here's an example of a live site I have running:-</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $acl = new Zend_Acl();
    $acl->addGroup('member', 'staff');
    $acl->addGroup('administrator', 'franchisee');
    $acl->addAllow('all', null, 'staff')
    ->addAllow('edit', 'profile', 'staff')
    ->addAllow('all', array('reports', 'staff', 'admin'), 'administrator')
    ->addAllow('finance', 'staff', array('administrator', 'member'))
    ->addDeny('edit,delete,finance', null, 'staff')
    ->addDeny('all', 'admin', 'staff')
    ->removeAllow('finance', 'admin', 'staff')
    ->removeAllow('finance', 'staff', 'member')
    ->addDeny('all', 'reports', array('staff', 'member'));

    <p>(You would obviously optimise this in your own app, but the redundant 'addDeny' methods are added for the purpose of demonstration).</p>

    <p>With these 9 lines I have a pretty robust approach for permission checking across the whole site. In my app, I have a sitemap class that uses the above rules to automatically show/hide sections for staff/members/admins. This also works in conjunction with a Zend_Controller_Action_Plugin that performs automatic ACL checks and auto re-routes to a nopermissionAction if a user has insufficient privileges for that controller.</p>

  3. Aug 08, 2006

    <p>1) Role/Group/User should act same as permission object. Any permission object can inherit permissions from another permission object. Doesn't care if the permission object is authentificationable or not. It seems that's the way you do it.</p>

    <p>2) At initialization time, permissions should be made static as much as possible. I mean they should inherit their permissions and be converted into simple set of area-action. And these static permission should be cached. Whather permission object or its ancestor changes the cache is deleted. It can be done via cache's tags or via dependencies and chain reaction. Doing so make frontend checks/validations much faster - just in_array. On the other hand changes to permissions (which aren't so often in comparsion to checks) are little more complex due to requirement of deleting caches.</p>

    <p>3) There should be much more transparent solution for checks.</p>
    <li>Posibility to set default permission object.</li>
    <li>Posibility to auto-magically check area and action using debug_backtrace().</li>

    <p>My dreaming use case is like this:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    //initialize Acl
    $acl = new Zend_Acl();

    //set default permission object

    //simple usage
    class Area {
    function Action()

    Unknown macro: { //magically check using debug_backtrace() (for default object) if(!$acl->valid()) return false; //simple check for Action in Area which is same as above one in this context if(!$acl->valid(null, 'Area', 'Action')) return false; //check permission for Action and Action2 in Area if(!$acl->valid(null, 'Area', Array('Action', 'Action2'))) return false; //magically check permission for another permission object for this context if(!$acl->valid($ObjectNameOrId2)) return false; //check for multiple areas and actions if(!$acl->multivalid( null, Array( //array of checks Array('Area', Array('Action', 'Action2')), Array('Area2', 'Action') ) )) return false; //any action.. }


    <p>4) Posibility to check against set of permission objects and so set, add and remove default set of permission objects.</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    //magically check permission for another permission object for this context
    Array($ObjectNameOrId, $ObjectNameOrId2)
    ) return false;

    1. Aug 08, 2006

      <p>Hi Marek</p>

      <p>You're right on point 1 - authentication is something best tested for outside of this component.</p>

      <p>Point 2 touches on caches and inheritance but they're not really issues with the current implementation. The permissions checking works its way from the outside->in rather than vice-versa. It means that you get a definite answer much quicker if explicit permissions are set to either allow or deny. </p>

      <p>For example, say I have a container of $acl->house->upstairs->bedroom->cupboard set to 'deny' for the group 'children' and $acl->house set to 'allow' for 'parents'. When $acl->house->upstairs->bedroom->cupboard->valid('rummage', 'children') is performed, the validation starts from the outermost path (i.e. cupboard). Since an explicit deny is found, the validation returns false immediately which is nice and quick. If $acl->house->upstairs->bedroom->cupboard->valid('rummage', 'parents') is called, it tries 'cupboard' first and finds no explicit or implied permissions for 'parents', so it checks the next valid permission back up the tree. Now, because no explicit permissions have been set for 'upstairs' or 'bedroom' either, the next explicit permissions object to check is back at 'house', where it finds 'allow' for 'parents' and that returns a true. So as you can see, the performance is quite good as there are no calls to redundant/non-existent objects along the path and any dynamic changes to permissions on the object are quite straightforward to achieve.</p>

      <p>Point 3 I may need some further explanation. How/why will debug_backtrace be required? And why do we need to check for specific objects within the ACL - is there any benefit over simply identifying it by a string? I can see where you're heading with 'multivalid' (the existing valid method would still work with this kind of call) but did you imagine that those checks would be AND instead of OR - i.e. Return true for $acl->house->valid() if context is in 'rummage,store,add' and group is 'parent' OR context is in 'hide' and group is in 'children'? I could add this as a convenience but it still may be simpler just to add this to your own logic rather than beefing up the component's methods.</p>

      <p>And Point 4 - what are we checking for when comparing permissions objects? Can you provide an example of the kinds of properties to check against or the contents of $objectNameOrId to illustrate the point?</p>

      <p>Many thanks - look forward to your feedback</p>

  4. Aug 08, 2006

    <p>If there are any plans for DB permissions, how it will be done?</p>

    <p>My idea is to have column permissionId at each table i wanna be able to check. Then at the inicialization time, permission object will get 3 sets of permissionId's for updates, deletes and selects. Then when it queries against DB then additional where is added. It should be done via extended Zend_Db e.g. Zend_Db_Perms.</p>

    <p>The permissionId supplies set of accessibilities like which permission objects have which rights (update, delete, select).</p>

    <p>This is most optimal for performance and there isn't any deal. But it shows problem of permissions from another perspective. Because It's not the permission object who tell which records are for him even It's not DB record who tells which users can hadle him. There is 1:1 between records and permissionId due performance decision. And relationship M:N between permissionId and permission object with set of actions (delete, update, select) which is negotiated during initialization into 3 permissions sets described above. Simply accesses for record are grouped into permissionId.</p>

    <p>Solution should be like this: These three sets can be unified under areas (in acl naming conventions paths) db-delete, db-update and db-select with actions (in acl naming conventions contexts) as sets of permissionsId (which reflect to the column in DB). </p>

    <p>But the main deal is while editing db record. You need to convert (by event) given permissions into new/existing permissionId and add this permissionId into permission objects.</p>

    <p>Maybe It should be done via completely another rountine or extended one. But if not, think about it here. There is too much common for them - e.g. inheritance and workarounds for managing them.</p>

    1. Aug 08, 2006

      <p>I didn't see the class itself needing to be completely driven by a DB driver - all it really needs to do is load data from a source (such as a Zend_Config container or via a Zend_Db object) and parse that to make the root Zend_Acl object.</p>

      <p>But then again if there's any specific benefits that can be gained by moving the permissions checking into SQL then I'd be happy to look at that. Performance at the moment is quite good and the ACL can be serialised/cached, so I don't see any advantages yet that can't be achieved by loading permissions once from the database.</p>

      1. Oct 05, 2006

        <p>I've received feedback regarding storing and loading the ACL to/from files or databases. I also see a need to mix and match ACO and ARO trees both, without having to merge everything into a single "blob" before storing or loading. If everyone agrees that there is a need and value in the ability to save and load individual ACO/ARO trees, then do we need detailed use cases, or would it be easy to support storing, loading, and using multiple ACO/ARO trees? For example, the ability to <code>getSerializedACO()</code>, <code>getSerializedARO()</code>, <code>setACO($serializedARO)</code> and <code>setACO($serializedACO)</code> appear to solve the requests I've heard for persistence, excluding the mixing and matching idea (seems less important to me).</p>

  5. Aug 14, 2006

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Comments</ac:parameter><ac:rich-text-body>

    <p>According to the proposal overview, Zend_Acl would enable assigning permissions to both 'containers' and 'context' to one or more groups. We should be well served by further explanation of the terminology and an analysis of the problem.</p>

    <p>Now let's revise the proposal - this version is noted as being a "rough draft" - with respect to the issues outlined below and the attached code, in order to arrive at a version which is expected to be approved for the framework incubator. We are closing in on a good authorization component design, and with the continued help of the community, we should succeed in providing a solution that fits well for most use cases.</p>

    <p>These comments are intended to be neither normative nor authoritative. It is encouraged, rather, that we continue to question and debate and revise various issues toward reasonable conclusions.</p>


    <li>In alignment with the proposal and Simon's comments, we support that no backend requirement be imposed. Instead of theorizing on data storage implementations, let's focus on getting a best-of-breed PHP API for managing and checking against authorization rules (aka permissions or access controls). Where the authorization data are stored for a particular application is expected to vary widely: database, XML, LDAP, Active Directory, etc.</li>
    <li>Standardize the proposal terminology for describing the problem model and the proposed solution. For the purposes of this review, we leverage existing terms as seen in the <a href="">phpGACL documentation</a>. Feel free to divert from the terminology used here, however; just strive for clarity and consistency.
    <li>The main problem that this component should solve is that of <strong>authorization</strong>. Authorization may be described as the process of answering a simple question, to which a boolean answer is expected: in general OOP terms, whether a requesting object is allowed access to another object. Of course, the component must also enable simple but robust management of the authorization rules to support various applications. The final answer to an authorization question must be boolean (e.g., ALLOW or DENY); there must be no "neutral" response.</li>
    <li>We can refer to an object requesting access as an <em>Access Request Object</em>, or ARO. For example, Han Solo may need access to the cockpit controls. Here, Han Solo is requesting access, so he would be represented by an ARO.</li>
    <li>The object to which access is being requested can be referred to as an <em>Access Control Object</em>, or ACO. In the previous example, the cockpit controls would be an ACO, an object to which access controls are applied and to which access may be requested.</li>
    <li>In order to simplify management of access rules, AROs may be organized in <em>groups</em>. An example of the use of groups would be for assigning access rules on a group, such as "Administrators" or "Guests", so that AROs in the group inherit the access permissions of the group. This prevents the requirement of redundant access rules assigned directly to each ARO in a group for such cases. An ARO must not be restricted to a single group, however, and thus, the component will need to address an ambiguity problem with a sane policy. That is, access rules may conflict across different groups to which the ARO is a member, and Zend_Acl must have a sane policy for preventing and/or resolving such ambiguities.</li>
    <li>A tree structure seems to fit the problem model well - access rules are ordered in the tree from general (at the root) to specific (at a leaf), enabling simple expression and calculation of access rules. We may organize groups and AROs into what we may call an <em>ARO tree</em>.</li>
    <li>We may benefit from having the problem model illustrated and analyzed for possible shortcomings of proposed solutions.</li>
    <li>Arrays are preferable to delimited strings for expressing multiple items.</li>
    <li>Operations outside the context of checking against a particular access rule, such as <code>AND</code>, <code>NOT</code>, and <code>OR</code>, should be performed with PHP and not reimplemented within the component. For example, the pseudo-expression
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $acl->valid("Han has access to the cockpit") && $acl->valid("Han has access to the flight controls")
    should be used by developers if necessary, and we should not provide a reimplementation of PHP operators. In many cases the necessity of performing such operations can be avoided by having a well-constructed access ruleset. The previous pseudo-expression can be reduced to one method call if we have the cockpit be a parent of the flight controls in an ACO hierarchy, so that in effect we have:
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $acl->valid("Han has access to the cockpit flight controls")
    <li><code>Zend_Acl->setAllow($values, $path = Zend_Acl::PATH_DEFAULT, $id = Zend_Acl::GRP_DEFAULT)</code> and friends (i.e., <code>setDeny()</code>, <code>addAllow()</code>, <code>addDeny()</code>, <code>removeAllow()</code>, and <code>removeDeny()</code>)
    <li><code>'all'</code> is used as a reserved word, and it is preferred that establishing reserved words is avoided where reasonably possible.</li>
    <li><code>$path</code> - denotes an ACO hierarchy to which access rules are applied.
    <li>Are there good reasons why there seem to be two "root" path values (i.e., <code>null</code> and <code>'_default'</code>)? Please explain.</li>
    <li>Paths should not be delimited by underscores in strings. Perhaps a better expression to use would be the object notation, such as <code>$acl->door->hatch</code>. This would only apply to ACO identifiers; extra information about ACOs (such as full descriptions and locations) would be stored elsewhere, perhaps within extending classes.</li>
    <li>The current implementation appears to include support for ACO trees and directed acyclic graphs (DAGs) of AROs. Adapted from the proposal test code, one might write something like:
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    // Create ACL object
    require_once 'Zend/Acl.php';
    $acl = new Zend_Acl();

    // Set up ARO DAG
    $aro = $acl->initARO();
    $aro->add('chewie', $aro->crew);
    $aro->add('jedi', $aro->passenger);
    $aro->add('luke', $aro->passenger->jedi);
    $aro->add('engineer', array($aro->crew, $aro->passenger));

    // The engineer node inherits from both crew and passenger, so the engineer group
    // may be accessed by either:
    // $aro->crew->engineer
    // or
    // $aro->passenger->engineer

    // Set up ACO tree having access controls
    $aco = $acl->initACO('deny'); // by default, no one is granted access to anything (whitelist approach)
    $aco->allow($aro->crew); // grants the crew access to everything
    $aco->door->allow(); // grants everyone access to the door
    $aco->door->hatch->deny(); // denies everyone access to the door hatch
    $aco->door->hatch->handle->allow(); // allows everyone access to the door hatch handle
    $aco->engine->fix->deny($aro->crew->chewie); // denies chewie access to fix the engine
    $aco->lounge->allow($aro->passenger); // allows passengers to access the lounge
    $aco->cockpit->allow($aro->passenger->jedi); // allows jedi to access the cockpit
    $aco->guns->allow($aro->passenger->jedi->luke); // allows luke full access to the guns

    // Engineers should be able to start, stop, and fire the guns.
    // This could be done with three leaf nodes:
    $aco->guns->stop->allow($aro->crew->engineer); // recall that engineers are both passengers and crew members
    // or by listing them as "actions" or "operations", which may end up being leaf nodes:
    $aco->guns->allow($aro->passenger->engineer, array('start', 'stop', 'fire'));

    // Authorization questions could then be phrased as:
    echo "Passenger can visit lounge: " . (($acl->valid($aro->passenger, $aco->lounge)) ? 'Yes' : 'No') . "\n";
    // or perhaps:
    echo "Passenger can visit lounge: " . (($aro->passenger->hasAccess($aco->lounge)) ? 'Yes' : 'No') . "\n";

    This is just a slightly different suggested API usage, and it imposes that ARO identifiers are valid PHP object members. This may or may not be preferable to using flat namespaces with arbitrary string identifiers.</li>

  6. Aug 15, 2006

    <p>I think it's a great idea to add an assert method of some kind, that works like valid(), but throws an exception instead of returning false. </p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    try {
    } catch ( Zend_Acl_Exception ) {
        echo "Insufficient permissions!";

    <p> This way, you could assert permissions, say, inside of a Controller_Action, and dispatch any security exceptions to a default access denied view.</p>

    1. Aug 15, 2006

      <p>That sounds like a good idea; the same can be accomplished with an extending class providing the assert() method, however, where such behavior would be desirable. Let's keep this feature in mind, since it may be useful enough to include as a convenience method.</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      public function assertValid(...)
      if (!$this->valid(...))

      Unknown macro: { throw new Zend_Acl_Exception(...); }


  7. Sep 08, 2006

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Comments</ac:parameter><ac:rich-text-body>
    <p>The Zend_Acl proposal has been approved for incubator development.</p></ac:rich-text-body></ac:macro>

  8. Oct 05, 2006

    <p>This is one of the component I am waiting to use desperately. Is there a working example or demo so that I can test it on my website.</p>

    <p>Thank you.</p>


    1. Oct 05, 2006

      <p>We currently have code (and docs) in the incubator for this component:</p>

      <p><a class="external-link" href=""></a></p>

      <p>Acl.php and the Acl/ directory contain the code. Currently I am working on a revision that includes some changes to the code and documentation as well as a first crack at the unit tests. Please feel free to try it out presently, though be warned there are no guarantees to stability at this time, and today I expect to make at least an interstitial commit.</p>

  9. Oct 05, 2006


    <p>Thank you for the quick response. I am looking for an example to learn how to use this component. Simon Mundy had on Aug 02, 2006 posted a code above he used on a site. I wonder if he can share how he implemented it on the site. It may be to early to see a tutorial on this.</p>

    <p>Thanks again.</p>


    1. Oct 05, 2006

      <p>I'll let Simon speak for himself, but I suspect there will be plenty of "real-world" integrated examples once the new MVC components have been added and documented, as well as once we have a few more revisions to the Zend_Acl code and docs.</p>

      <p>Thanks for your continued patience as these parts evolve!</p>

    2. Oct 05, 2006

      <p>Yes, we plan to show an integrated, simple example using Zend_Acl, Zend_Session, Zend_Authenticate, and the new MVC components. However, all must exist in a useable form in the incubator, before the example will work.</p>

  10. Oct 05, 2006

    <p>Thank you very much. Hope to see the integrated, simple example in the near future. Your effort is very much appreciated.</p>