Apologies: I sent my first message without proof-reading it... this one
reads (marginally) better ;)
Hi,
If you've been reading the list of late you'll realise that I've been
delving into the ACL system. I've come across a problem and I'm not sure
if I'm doing something fundamentally stupid with my design.
Here is my approach:
I have classes defined in my system which implement
Zend_Acl_Resource_Interface. In my case I always implement my
getResourceId() as a very simple "return __CLASS__".
I'll go back to my earlier example which concerns Articles to try and
put this into context.
Say I have following setup:
class Article implements Zend_Acl_Resource_Interface.
{
public function getResourceId()
{
return __CLASS__;
}
public $author;
public function __construct($articleId)
{
// Load article by id and populate $author.
}
}
(Obviously it does other stuff too!!)
In order to restrict editing to only the author of the article, I could
implement 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
into 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 Zend_Acl implementation seems to forbid this.
Allow me to provide an example. Let's say I then have some code:
$art1 = new Article(1); // An article by current user
$art2 = new Article(2); // An 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 passed through to
the assert method and thus it fails at the "instanceof" check. This is
due to the fact that 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. (see the
variable instance storage: $this->_resources[$resourceId]['instance']).
I can fix this trivially with a small patch (attached) that preserves
the object passed in if it is given and only uses the generic object if
a string is used as the parameter. With this patch applied, my original
Article object makes it through to the assert() and I can perform the
necessary tests.
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;
}
/**