Chris Morrell writes; In most of my applications I like to handle authorization (querying the ACL) in one (or more) of three ways:
- Authorize access to a model’s method
- Authorize access to a controller action
- Authorize access to an arbitrary “permission”
In general I find it’s best to keep authorization within the domain (querying the ACL within my models when they’re accessed) as this provides the most consistent behavior. For example, if I eventually add a REST API to my application I don’t have to duplicate all my authorization logic in the new REST controllers. When the application calls something like Default_Model_Post::save() it either saves or throws an ACL exception, no matter where it was called from. This is great in that it saves me from having to duplicate code and keeps my system more secure.
On the other hand, there are times when it’s just a lot easier to handle authorization in the controller. For example, if guests should never access my “Admin” module, it doesn’t make sense to ever let them access /admin/ URLs. Also, if you’re using Zend_Navigation, having ACL resources that match controller actions lets you utilize its ACL integration.
If you’re ever going to mix these two techniques, you’ll eventually bump into the case where a model and a controller share the same name. What if you need to set permissions on a “user” controller and different permissions on a “user” model? This is where namespacing comes into play. As suggested by the Zend Framework manual, I always name my controller action resources in the format mvc:module.controller.action. I name my model resources similarly, in the format model:module.modelName.methodName. In both theses cases, “mvc” and “model” are the namespace, and everything following the colon is the actual resource name. Now I can refer to my “admin” module as mvc:admin and the models within my admin module as model:admin.
This is where things get interesting.
Read on; “Namespacing ACL resources & Galahad_Acl”.
Chris continues; Right now I treat my models as resources with a special exception for the user model which is both a resource and a role. Then I actually make the models responsible for managing their own ACL permissions, both setting them up and querying them. To facilitate that, I have a base model class that does a few things. First it has a way to inject an ACL instance into the model as well as a way to pass an ACL instance as the default ACL for all models (which I do in my bootstrap). Second it automatically adds itself to that ACL (with the resource id model:moduleName.modelName). Finally, I have an _initAcl() method which is called when my model is instantiated which adds the appropriate rules to the ACL if they don’t already exist. Whenever my model is doing something that is access-controlled I check the ACL right then.
Here’s a simple code example:
class Default_Model_Post extends Galahad_Model_Entity
{
protected function _initAcl($acl)
{
// Deny permissions to anything on this model unless explicitly allowed
$acl->deny(null, $this);
// Allow guests to fetch the content of posts
$acl->allow('guest', $this, 'fetch');
// Allow admins to save changes to posts
$acl->allow('admin', $this, 'save');
}
public function save()
{
if (!$this->getAcl()->isAllowed($this->getRole(), $this, 'save')) {
throw new Galahad_Acl_Exception('Current user is not allowed to save posts.');
}
$dataMapper = $this->getDataMapper();
return $dataMapper->save($this);
}
}
There’s a little bit more happening in there (for example, I also have helper methods like getRole(), which either gets the role ID stored in the model or grabs it from Zend_Auth if available), but you should get the picture.
That way my access control is happening when the access itself is happening. No matter how my model is used, the ACL is always queried right when it matters. I also like setting up the ACL this way because all rules are loaded into the the ACL only when they could potentially apply (you never need the rules for a Post model if the current request never even loads the Post class).
This is something I’ve been thinking about a lot lately, and I’m just settling into this method. I just blogged about it a little over a week ago: “Namespacing ACL resources & Galahad_Acl” — if anyone has any comments I’d love to hear them