Hi,
If you've been reading the list of late you'll realise that I've been
delving into the ACL system and I've come across a problem and I'm not
sure if I'm doing something fundamentally stupid with my design as I've
not got some problems when trying to roll it out due to the way things
are implemented.
Here is my approach.
I have classes defined in my system which implement
Zend_Acl_Resource_Interface. In my case I ways implement my
getResourceId() as a very simple "return __CLASS__".
I'll go back to my earlier example which concerns Articles.
Say I have following setup:
class Article
{
public function getResourceId()
{
return __CLASS__;
}
public $author;
public function __construct($articleId)
{
// Load article by id and popuplate $author.
}
}
(Obviously it does other stuff too!!)
Now consider me creating the following Acl_Assert
class Own_Article_Assert implements Zend_Acl_Assert_Interface
{
public function assert(
Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$privilege = null)
{
if (!($resource instanceof Article))
return false;
// We now know $resource is of the class "Article"
$auth = Zend_Auth::getInstance();
if (!$auth->hasIdentity())
return false;
return ($auth->getIdentity()->username == $resouce->author);
}
}
Then I had an ACL something like...
$acl = new Zend_Acl;
$acl->addRole(new Zend_Acl_Role('User'));
$acl->add(new Zend_Acl_Resource('Article'));
$acl->allow('User', 'Article' 'view');
$acl->allow('User', 'Article' 'edit', new Own_Article_Assert);
This should allow a user to view any article, but only allow the
original author to edit it.
I've used the generic Zend_Acl_Resource() object to add the resource
inot the Acl as I cannot configure my Acl with the real article object
without instantiating it. This seems like a reasonable thing to do, but
the current implementation seems to forbid this.
Allow me to elaborate a little.
Let's say I then have some code.
$art1 = new Article(1); // Article by current user
$art2 = new Article(2); // Article by someone else
echo 'View 1: '.($acl->isAllowed('User', $art1, 'view') ? 'ACK' :
'NAK')."<br />";
echo 'View 2: '.($acl->isAllowed('User', $art2, 'view') ? 'ACK' :
'NAK')."<br />";
echo 'Edit 1: '.($acl->isAllowed('User', $art1, 'edit') ? 'ACK' :
'NAK')."<br />";
echo 'Edit 2: '.($acl->isAllowed('User', $art2, 'edit') ? 'ACK' :
'NAK')."<br />";
What you'd expect here is ACK, ACK, ACK, NAK. But what actually happens
is ACK, ACK, NAK, NAK.
This is because the article object itself never gets over to the assert
method as the object you pass in to isAllowed, is actually internally
replaced by a the generic object added during ACL construction. This is
done in Acl.php's get() function. Namely the variable instance storage:
$this->_resources[$resourceId]['instance'].
I can fix this trivially with a small patch that preserves the object
passed in if it is given (attached). With this patch applied, my
original object passed in to an isAllowed() method will be passed
through to the assert().
I really hope this is a bug as, to me, I really don't see the point in
using "objects" in the Acl system if the objects themselves are not
going to be preserved and passed about accordingly. If the objects just
represent generic strings then why not just use generic strings in the
first place. Being able to pass a specific object to an assert method is
what makes it powerful/useful.
I hope this is understandable. If you would like me to provide code I
can do.
I'd really appreciate a follow up on this as it will affect how I
approach my ACL implementation.
Col
--
Colin Guthrie
gmane(at)colin.guthr.ie
http://colin.guthr.ie/
Day Job:
Tribalogic Limited [http://www.tribalogic.net/]
Open Source:
Mandriva Linux Contributor [http://www.mandriva.com/]
PulseAudio Hacker [http://www.pulseaudio.org/]
Trac Hacker [http://trac.edgewall.org/]
Index: Acl.php
===================================================================
--- Acl.php (revision 30213)
+++ Acl.php (working copy)
@@ -288,8 +288,10 @@
{
if ($resource instanceof Zend_Acl_Resource_Interface) {
$resourceId = $resource->getResourceId();
+ $lookup = false;
} else {
$resourceId = (string) $resource;
+ $lookup = true;
}
if (!$this->has($resource)) {
@@ -297,7 +299,7 @@
throw new Zend_Acl_Exception("Resource '$resourceId' not found");
}
- return $this->_resources[$resourceId]['instance'];
+ return $lookup ? $this->_resources[$resourceId]['instance'] : $resource;
}
/**