<ac:macro ac:<ac:plain-text-body><![CDATA[
 Framework: Zend_Acl & MVC Integration Component Proposal
Table of Contents
1. Overview
2. References
3. Component Requirements, Constraints, and Acceptance Criteria
.
- This component will allow developers to stop validating if an user has access to certain page by just setting up the component in the bootstrap or when a user login.
- This component will help applications to be more secure.
- This component will work with both module/controller/action and controller/action configurations.
- This component will be available via an action helper.
4. Dependencies on Other Framework Components
- Zend_Acl
- Zend_Config
- Zend_Controller_Front
- Zend_Acl_Exception
5. Theory of Operation
This component can be instantiated using an ACL object, it must have a role (which name can be set up on the plugin) and the allowed/denied operations (module/controller/action combination). After created, it must be registered as a Front Controller Plugin, optionally, if you want to access ACL inside the controller you will have to register the Action Helper._Controller_Plugin_Acl
- Zend_Controller_Action_Helper_Acl
8. Use Cases
index.php
- Trying to access
- Expected result: the view is rendered.
- Trying to access
- Expected result: the site will forward to
index.php
- Trying to access
- Expected result: the view is rendered.
- Trying to access
- Expected result: the site will forward to
index.php
- Trying to access
- Expected result: the view is rendered.
- Trying to access
- Expected result: the view is rendered.
index.php
- Trying to access
- Expected result: the view is rendered.
- Trying to access
- Expected result: the site will forward to
index.php
NewsController.php
- Expected result: the view is rendered.
- Expected result: the site will forward to
9. Class Skeletons
34 Commentscomments.show.hide
Nov 16, 2007
Jurrien Stutterheim
<p>I like the idea of having a more concrete ACL implementation in the framework. I had <a href="">a shot at something like this myself</a> based on <a href="">Simon's excellent article</a>.</p>
<p>To allow for more flexibility, you can register a controller like this: 'modulename:controllername' (much like you do now) and register a controller action as privilege in the allow() or deny() method. </p>
<p>The idea to allow Zend_Config for configuring the ACL is a good thing. However, the XML string loading looks a bit redundant to me, seeing as there's already an excellent Zend_Config_Xml component.<br />
Have you thought about a DB config of some sorts?</p>
<p>Also, would this component provide support for access groups? e.g. a user might be in the "documentation" and "testing" groups, but not in the "developers" group. The user would need access to the recourses of both the "documentation" and the "testing" group.</p>
Nov 17, 2007
Aldemar Bernal
<p>Hi Jurrien, thanks for your comments,</p>
<ul>
<li>I didn't think about it but I agree with the idea of create a Zend_Resource for the module/controller and then other Zend_Resource for each action making it child of the module/controller Zend_Resource, I'm a little worried about naming convention since it could bring conflicts e.g:</li>
</ul>
<ac:macro ac:<ac:plain-text-body><![CDATA[
<?php
require_once 'Zend/Acl.php';
$acl = new Zend_Acl();
require_once 'Zend/Acl/Resource.php';
$acl->add(new Zend_Acl_Resource('default:news'));
$acl->add(new Zend_Acl_Resource('default:blog'));
/** Define the index action for news */
$acl->add(new Zend_Acl_Resource('index'), 'default:news');
/** Here is where the conflict is */
$acl->add(new Zend_Acl_Resource('index'), 'default:blog');
/** The blog/index action resource will overwrite news/index action */
]]></ac:plain-text-body></ac:macro>
<p>So, I'll see if I can figure something out to achieve this.</p>
<ul>
<li>Well I thought about the XML string idea since sometimes you will have to make persistence in your ACL, I mean, if you have a special user with certain privileges you could save the ACL data as XML into the user db table. Anyway as we talk at #zftalk maybe it's better idea to just serialize the ACL as a Zend_Acl object or as an Array, I'll give a second thought about it.</li>
</ul>
<ul>
<li>The idea of using a Zend_Config object is to be able to load the guess privileges at bootstrap in a easy way, it comes from my own implementation of my own 'framework'.</li>
</ul>
<ul>
<li>Hmm, actually if you have a role that have access to 'documentation' and 'testing' you'd be able to make that work with this component since you can define which role will be used. I think that may be this is what you are expecting, tell me if it is not please:</li>
</ul>
<ac:macro ac:<ac:plain-text-body><![CDATA[
<?php
require_once 'Zend/Acl.php';
$acl = new Zend_Acl();
require_once 'Zend/Acl/Role.php';
$acl->addRole(new Zend_Acl_Role('documentation'));
$acl->addRole(new Zend_Acl_Role('testing'));
$acl->addRole(new Zend_Acl_Role('developers'));
$acl->addRole(new Zend_Acl_Role('mygroup'), array('documentation', 'testing'));
...
require_once 'Zend/Controller/Plugin/Acl.php';
$aclPlugin = new Zend_Controller_Plugin_Acl($acl);
/** Setting up the plugin to use mygroup role */
$aclPlugin->setRoleName('mygroup');
$front->registerPlugin($aclPlugin);
]]></ac:plain-text-body></ac:macro>
Nov 17, 2007
Jurrien Stutterheim
<p>Hi Aldemar,</p>
<p>What I meant with the resource/privilege thing was this:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
$acl->add(new Zend_Acl_Resource('default:news'));
$acl->add(new Zend_Acl_Resource('default:blog'));
// role, resource, privilege (the controller action)
$acl->allow('guest', 'default:news', 'index');
$acl->allow('guest', 'default:blog', 'index');
$acl->deny('guest', 'default:news', 'delete');
]]></ac:plain-text-body></ac:macro>
<p>As for groups... sure it's possible to create a role with multiple parents, but it isn't really that flexible.<br />
Let's say the role 'mygroup' has 'documentation' and 'testing' as parent, because a user requires those two permissions. Now, that same user needs to be added to the 'coffee' group. This would mean either two things: the 'coffee' role would need to be added to 'mygroup' or a new role would need to be created with 'documentation', 'testing' and 'coffee' as parent.<br />
The former isn't an option, because other people that have the 'mygroup' role don't necessarily need to be part of the 'coffee' group. The latter isn't an option either... it will create an explosion of roles in even a moderately complex system, which increases ACL size (thus slowing it down) and makes it hard to maintain.<br />
This problem can be solved by creating groups that contain on or more roles and/or a system where a user can have multiple roles.</p>
Nov 17, 2007
Aldemar Bernal
<p>Hi Jurrien,</p>
<p>the idea of resource/privilege is great, I'll implement it in the next working prototype (already <a href="">one released</a> =D).</p>
<p>About groups, I think you could create basic roles (documentation, testing, developers, coffee) and then implement one or more of these into a guess role, but when a user do login then a list of roles for that user can be loaded from the user DB table, I mean:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
/** Basic roles */
$acl->addRole(new Zend_Acl_Role('documentation'));
$acl->addRole(new Zend_Acl_Role('testing'));
$acl->addRole(new Zend_Acl_Role('developers'));
$acl->addRole(new Zend_Acl_Role('coffee'));
/** Setting up the default user */
$acl->addRole(new Zend_Acl_Role('guess'), array('coffee');
...
$aclPlugin->setRoleName('guess');
]]></ac:plain-text-body></ac:macro>
<p>and then in the login action:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
public function loginAction()
{
...
$aclPlugin = $this->_helper->getHelper('acl');
$acl = $aclPlugin->getAcl();
/**
*/
$acl->addRole('authuser', $user->roles);
$aclPlugin->setRoleName('authuser');
...
}
public function logoutAction()
{
...
$aclPlugin = $this->_helper->getHelper('acl');
$aclPlugin->setRoleName('guess');
...
}
]]></ac:plain-text-body></ac:macro>
<p>About ACL size and time, I think the correct way to implement this is to create session persistence of the Acl object (in order to skip Acl configuration time each time a page is requested), by now, I'm not implementing a session singleton but maybe I'll do that later.</p>
Nov 16, 2007
Steven Truppe
<p>hi, first of all i like your work on this component.</p>
<p>I'm also a bit into the concepts of a good acl implementation, and i think a nice addition to your classtree would be an interface like Zend_Acl_Resource where the ->test() method must get implemented. So the users can write their own acl validator test for custom resources (like a room that is only available in a certain time interval). </p>
<p>So with the test "validator" we could store the certain conditions for the test where we want (or read them out of an allready existing web system that must get integrated for example).</p>
<p>Let me know what you think.</p>
Nov 17, 2007
Aldemar Bernal
<p>Hi Steven, thanks for your comment,</p>
<p>I think you are talking about Zend_Acl_Assert_Interface, there won't be any problem about implementing this kind of functionality using a Zend_Acl object, but it would require some extra job to make this work when using a Zend_Config object. Maybe it could be something like this:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
<?xml version="1.0" encoding="UTF-8" ?>
<configdata>
<default>
<acl default="senior">
<resources>
<resource name="default:school:room1" assertion="My9To15Assertion" />
<resource name="default:school:room2" assertion="My10To13Assertion" />
<resource name="default:school:restroom" />
</resources>
<roles>
<senior>
<allow>
<resource>default:school:room1</resource>
<resource>default:school:restroom</resource>
</allow>
<deny>
<resource>default:school:room2</resource>
</deny>
</senior>
</roles>
</acl>
</default>
</configdata>
]]></ac:plain-text-body></ac:macro>
Dec 14, 2007
Steven Truppe
<p>Thanks for the reply, i hope i get my acl implementation more general this time =). Thank you.</p>
Jan 12, 2008
Aldemar Bernal
<p>Well, I just modified the proporsal:</p>
<ul class="alternate">
<li>Xml and Zend_Config things are gone, I'll do the Zend_Config thing in another proporsal.</li>
<li>UCs are implemented.</li>
<li>The proporsal now is in 'Ready for review' state.</li>
</ul>
Apr 21, 2008
Christopher Manning
<p>Excellent proposal.</p>
<p>Looking through the code I noticed something that may need to be addressed.</p>
<p>On line 152 under preDispatch(); if an application does not use modules and is the request is being dispatched through a custom route, getmodulename() could be null.</p>
Apr 22, 2008
Aldemar Bernal
<p>Hi Christopher,</p>
<p>Thanks for your positive feedback, you are right about that preDispatch issue, after checking out my internal svn trunk I found that line 152 is:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
if ($request->getModuleName() != 'default' && $request->getModuleName() != '') {
]]></ac:plain-text-body></ac:macro>
<p>which address the problem you are talking about, I have some projects using this component (with this line changed and using custom routes).</p>
<p>Aldemar</p>
Jun 17, 2008
Jordan Ryan Moore
<p>I have a fully functional ACL front controller plugin and controller action helper that you might want to steal a few ideas from. It's a little more flexible than your implementation.</p>
<p><a class="external-link" href=""></a>
<a class="external-link" href=""></a></p>
Jun 23, 2008
Aldemar Bernal
<p>Hi Jordan, would you please mail me those files, links seems to be broken.</p>
<p>Thanks,</p>
<p>Aldemar</p>
Jun 23, 2008
Jordan Ryan Moore
<p>Sorry, I did some SVN restructuring and forgot that I posted those links. Here are the updated URLs:</p>
<p><a class="external-link" href=""></a>
<a class="external-link" href=""></a></p>
Jul 19, 2008
Aldemar Bernal
<p>=) our implementations are really similar, I like the idea of throwing an exception, I think I'll give that option like that one in the front controller:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
$myAclPlugin->throwExceptions(true);
]]></ac:plain-text-body></ac:macro>
<p>by default it could be false and redirect to the given accessDenied action.</p>
<p>Aldemar</p>
Jul 01, 2008
Matthew Weier O'Phinney
<ac:macro ac:<ac:parameter ac:Zend Comments</ac:parameter><ac:rich-text-body>
<p>We would like to see this proposal developed in the Laboratory to see the API fleshed out a bit more. When the author feels that the API has settled somewhat and received testing by the community, please move it back to 'Ready for Recommendation'.</p></ac:rich-text-body></ac:macro>
Jul 14, 2008
Justin Hendrickson
<p>I've written something similar (<a class="external-link" href=""></a>), so here's my two cents:</p>
<p>I really don't like the idea of any kind of module:controller:action name to resource name mapping.</p>
<p>I think the general problem is that people create modules based on roles, instead of self-contained modules. As a result, they see their module and controller names as good candidates for resources and actions as good candidates for permissions and they try to form ACL mappings around that.</p>
<p>This leads to a number of messy cases like having default:news and admin:news, or worse yet, resource names that have nothing to do with the actual resource they act on (ie default:dashboard). This clutters up the ACL rule definitions, duplicates a lot of effort and requires developers to carefully analyze actions before they can understand what exactly a rule is granting access to.</p>
<p>I think the best solution is a bit of education about how to properly use modules and an easy way to define what resource and permission is required to access an action from within a controller.</p>
<p>In my helper, I use a public $acls property in the controller to define what resource/permission an action requires. This is very similiar to how the ContextSwitch and AjaxContext work.</p>
<p>Contrived example usage:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
class Some_IndexController extends Zend_Controller_Action
{
public $acls = array(
'one' => array('article', 'view'),
'two' => array('car', 'edit')
);
public function one()
public function two() {
$cars = new Car();
$car = $cars->find($this->_getParam('id'))->current();
if ($this->getRequest()->isPost())
$this->view->car = $car;
}
}
]]></ac:plain-text-body></ac:macro>
<p>Anyway, that's my two cents.</p>
<p>PS. I would not suggest whole sale adoption of everything I've done with my ACL helper, just the naming aspect for acl resources name and the use of the public $acls property.</p>
Jul 19, 2008
Aldemar Bernal
<p>Justin, thanks for your feedback. Some thoughts about this proposal:</p>
<ul class="alternate">
<li>module:controller:action mapping is done because we talk about someone trying to access something, and in a MVC application the way you know what is that user trying to access is by checking which module:controller:action has been requested.</li>
<li>If you want to apply a role permission to a whole module, you are free to do it:</li>
</ul>
<ac:macro ac:<ac:plain-text-body><![CDATA[
/** Admin module */
$myAcl->add(new Zend_Acl_Resource('admin'))
->add(new Zend_Acl_Resource('admin:article', 'admin'))
->add(new Zend_Acl_Resource('admin:quick-link', 'admin'))
->add(new Zend_Acl_Resource('admin:category', 'admin'));
/** Creating permissions */
$myAcl->allow('adminUser', 'admin');
]]></ac:plain-text-body></ac:macro>
<p>Since, all controllers members of admin module are children of it, to give access to admin would be enough for giving it to the whole module.</p>
<p>The idea behind defining the whole module:controller map is because it's a good way to ensure that there are no security holes in the application.</p>
<ul class="alternate">
<li>Btw, I prefer to have my acl definition in just one file instead of spreading it in every controller, why? because I prefer to change one file that going controller to controller changing stuff (e.g. a change in a module|role permission)</li>
</ul>
<p>Aldemar</p>
Sep 04, 2008
Bruno Friedmann
<p>Hi Aldemar,</p>
<p>I'm just creating a new project which need this type of component.<br />
So I've integrate the zion zip ( rename to correspond to the api proposal )<br />
and ( I know that's not good ) put them into the just new 1.6.0 tree.</p>
<p>Work nicely ... after trying to adjust between this proposal and your articles on devzone.</p>
<p>So I think we should move on this ... </p>
<p>But now I've a need to insert Zend_Acl_Assertion and search some docs about how to write them correctly, and where to place them.</p>
<p>Use case : member is allowed to call users controller, show action but it need to be restricted to it's own user id.</p>
Sep 04, 2008
Aldemar Bernal
<p>Hi Bruno,</p>
<p>I'm happy that my proposal has been a help for you. Now, you can instead of loading (zip)Zion library, go to <a class="external-link" href=""></a> and download it from the svn laboratory repository, it's almost the same but works with the ZendL namespace and has some fixes (working with modules) and I'll be adding in the following days a view helper in order to access the aclPlugin from any view.</p>
<p>About the use case, check the UC-05, it's the same, correct me if I'm wrong.</p>
<p>Hope it helps,</p>
<p>Aldemar</p>
<p>BTW, you are suppose to add any assertion to the Acl without any problem</p>
Sep 04, 2008
Bruno Friedmann
<p>Thanks for the svn link, would be more sane way <ac:emoticon ac:</p>
<p>For the uc5 it's in reality the same but what I want is to made this directly in the pre dispatch so I'm breaking my brain to have a nice Assertion class which can do it<br />
using the getParams.<br />
As I've a lot's of action and controllers build on the same api. It would be the cool way for me.</p>
<p>— </p>
Sep 04, 2008
Bruno Friedmann
<p>So I'm trying with the Assertion but I encounter a strange behaviour.</p>
<p>I'm affecting this rules inside the bootstrap before the dispatch.</p>
<p>$acl->allow('userRole', 'usersRes', array('index', 'show', 'showedit', 'save'), new AclIsOwner() )</p>
<p>AclIsOwner for the moment only return false for test <ac:emoticon ac:</p>
<p>When I try to access the users/index I'm obtaining a access denied as expected.<br />
So everything seems to goes smoothly.</p>
Sep 05, 2008
Bruno Friedmann
<p>Case to resolve :</p>
<p>If the acl knows everything it's ok but if module, controleur or action call an unknown part Acl throw an execption and ErrorHandler are not used ... </p>
<p>Seems to miss a try catch Acl Exception in the preDispatch method.</p>
Sep 05, 2008
Aldemar Bernal
<p>Hi Bruno,</p>
<p>thanks for your feedback, I just updated the proposal in the repository, now you can use the method denyUnknown (similar use to throwExceptions) to set whether you want to give an Access Denied page in a non-existing action (or acl rule) or just allow the error handler do its job.</p>
<p>Aldemar</p>
Jul 08, 2009
-.-
<p>Any news on this?</p>
<p>It would be very nice to have this in the library!</p>
Sep 10, 2009
Artur Bodera
<p>Please consider using acl resource namespaces, as "suggested" in ZF manual for Zend_Navigation view helpers. Take a look at <a href=""></a>. The basic idea is to use resource namespace name with ':' separator, then use dots '.' for mvc structure separation.</p>
<p>For example, taken from the above manual page, when constructing a page you use the syntax:</p>
<ac:macro ac:<ac:plain-text-body><![CDATA[
new Zend_Navigation_Page(array(
array(
'label' => 'My Account',
'module' => 'community',
'controller' => 'account',
'action' => 'index',
'resource' => 'mvc:community.account' // resource
));]]></ac:plain-text-body></ac:macro>
<p>Note the resource is supplied as <strong>mvc:moduleName.controllerName</strong>. Extrapolating this, you could also use syntax <strong>mvc:moduleName.controllerName.actionName</strong> for fine-grained control.</p>
<p>The basic idea is to leave the <strong>mvc:</strong> as a namespace pointer. Your acl could have other namespaces, for example: with a blog post object, you could have <strong>blogpost:1140</strong>, with an attachment you could have <strong>localfile:myreport.pdf</strong> etc.</p>
<p>If you don't use namespaces, the plugin will be vulnerable to name conflicts, for example if a controller is named "index" and you have other ACL resource (for example blog post) named "index" you could end up granting all access to the controller, or something worse.</p>
<p>So my proposal is to stick with the syntax for resource naming:</p>
<p><strong>namespace:resourceId</strong></p>
<p>In case of MVC and ACL plugin it should read:<br class="atl-forced-newline" />
<strong>mvc:controller.action</strong></p>
<ul>
<li>mvc:index.index</li>
<li>mvc:index.browse</li>
<li>mvc:user.index</li>
<li>mvc:user.forgotpassword</li>
</ul>
<p>with modules: <strong>mvc:module.controller.action</strong></p>
<ul>
<li>mvc:admin.index.index</li>
<li>mvc.admin.users.delete</li>
</ul>
<p>etc.</p>
Jan 19, 2010
Alexandre Gomes Gaigalas
<p>This component is a must have for ZF! Any news on its development?</p>
Jan 19, 2010
Artur Bodera
<p>Well.. it's taking forever. As I already have a working one I will probably do it in couple of weeks as soon as I sign in as dev.</p>
Jan 19, 2010
Aldemar Bernal
<p>This proporsal has been moved back to Ready for recommendation folder. Btw, like the idea of using also Zend_Navigation_Page as a source for the acl =)</p>
Aug 03, 2010
Dolf Schimmel (Freeaqingme)
<ac:macro ac:<ac:rich-text-body><p><strong>Community Review Team Recommendation</strong></p>
<p>The CR Team advises against accepting this proposal because it feels the component forces one single way of checking ACL, meaning that it's not flexible enough compared to the rest of the Zend Framework</p></ac:rich-text-body></ac:macro>
Aug 12, 2010
Matthew Weier O'Phinney
<p>While I don't particularly agree with the approach, I've also heard lots of feedback regarding developers wanting a "drop-in," conventions-based, ACL solution for the framework. Since it would be an opt-in component, and serves only to integrate other components, I think it would be a reasonable addition to the framework.</p>
<p>That said, ZF2 architecture may make this approach untenable. If accepted for ZF 1.11, the author (a) would need to understand and accept that it may need to be dropped in ZF2, and/or (b) would need to be willing to substantially rewrite the work to conform with ZF2 architecture. I would like to hear from the author regarding these points.</p>
<p>Additionally, if accepted, please note the following:</p>
<ul>
<li>Changes in Zend_Acl in recent versions may simplify usage – you can now pass strings as roles and resources.</li>
<li>The proposal MUST include a bootstrap resource plugin, with support for defining ACLs via configuration.</li>
<li>The proposal MUST include a section in its documentation detailing why the approach may not be suitable for all situations (e.g., re-using ACLs for services), and pointing to some alternate approaches (such as ACL integration in service/domain layers, etc.).</li>
</ul>
Aug 12, 2010
Aldemar Bernal
<p>Matthew, all I need is green light <ac:emoticon ac:, if so, I'd make needed changes to work in both 1.11 and 2.0 (even this means rewrite from scratch for 2.0)</p>
Aug 12, 2010
Grayson Koonce
<p>While I love the idea of an ACL "drop-in" solution, increasing the migration pain to ZF2 (even in opt-in situations) is not desirable. If every component is breaking or creating non-backwards compatibility than refactoring for migration becomes more difficult and might cause certain developers, companies come to mind, to stay with ZF1. Of course if a rewrite was done to offer a consistent API in ZF2 than what's the harm, assuming the client code doesn't change or change is minimal.</p>
<p>Just a thought.</p>
Aug 13, 2010
Aldemar Bernal
<p>Since Zend_Acl is not longer in 2.0, now it'll be Zend\Access, Changes to code must be done, even if it works the same (which we still don't know). Depending on how much Zend\Access differs of Zend_Acl, the approach of ACL/MVC might also change. Anyway, I'm not worry about those changes talking about the proposal, as I said before, all I need is green light <ac:emoticon ac:.</p>
Aug 13, 2010
Matthew Weier O'Phinney
<p>Don't know where you got the idea that Zend_Acl won't be in ZF2 – we do have it currently. The only real proposed change is to offer additional implementations (see Dolf's RBAC proposal in the proposers wiki). If that happens, we'd simply have the current implementation pushed down a level.</p>
<p>Thanks for getting back to me on my questions; I still need to discuss the feedback with my team and the CR team.</p>