Zend Framework FAQ: Wie setze ich eine ACL für eine Modul basierte Applikation um?
Montag, 26.10.2009Bei der ersten Abstimmung für die Zend Framework Fragestunde, wurde diese Frage mit 37% der Stimmen auf Platz 1 gewählt: Wie setze ich eine ACL für eine Modul basierte Applikation um?
Hierfür müssen wir als erstes die Grundlage schaffen und festlegen, was eigentlich mit einer Modul basierten Applikation gemeint ist. Das Zend Framework bietet schon seit einen frühen Anfängen die Möglichkeit, eine Applikation in Module aufzuteilen. In einem Modul können somit Action-Controller, View-Skripte, Models, Formulare und mehr gekapselt werden. Die Klassen dieser Module bekommen einen eigenen vorangestellten Namensraum, damit es nicht zu Namenskonflikten kommen kann. Eine Zend Framework Applikation könnte somit aus den Modulen Gästebuch, Forum und CMS (für Ausgabe von Textbeiträgen) bestehen. Zusätzlich zu diesen Modulen gibt es in der Regel immer ein Default-Modul, dass sich unter anderem um die Auslieferung der Homepage kümmert. Zusammenfassend werden wir also mit den folgenden Module arbeiten:
- default
- guestbook
- forum
- cms
Um mit Zend_Acl eine ACL (Access Control List) umsetzen zu können, benötigen wir noch die Benutzerrollen. Wir verwenden an dieser Stelle die drei Benutzerrollen admin, editor und guest und gehen davon aus, dass in allen Modulen die selben Benutzerrollen verwendet werden sollen. Den Aspekt der Wiederverwendbarkeit der Module lasse ich an dieser Stelle bewusst unberücksichtigt. Denn dies würde das Ganze nur noch komplizierter machen.
Als nächstes müssen wir noch überlegen, wie wir die Ressourcen, Privilegien und Rechte in unserer ACL definieren. Hierbei gibt es verschiedene Ansätze. Wir könnten als Ressourcen unsere Models betrachten und deren Methoden als Privilegien einsetzen. Ein anderer Ansatz basiert auf dem Dreigespann Modul, Controller und Action. Diesen Ansatz möchte ich hier vorstellen. Als Privilegien verwenden wir die Namen der Aktionsmethoden und als Ressourcen die Namen der Action-Controller Klassen. Und wo bleibt das Modul? Ganz einfach, für jedes Modul erstellen wir eine eigene ACL. Dies hat auch den Vorteil, dass die ACL insgesamt nicht zu groß wird. Zudem können wir durch geschicktes Cachen der Zend_Acl Objekte zusätzlich die Performance steigern.
Hier folgt als Beispiel die ACL für unser CMS Modul. Die ACLs für die anderen Module sehen ähnlich aus und tragen die Klassennamen Default_Acl, Guestbook_Acl und Forum_Acl.
-
class Cms_Acl extends Zend_Acl
-
{
-
public function __construct()
-
{
-
// Rollen anlegen
-
$this->addRole(new Zend_Acl_Role('guest'));
-
$this->addRole(new Zend_Acl_Role('editor'), 'guest');
-
$this->addRole(new Zend_Acl_Role('admin'), 'editor');
-
-
// Ressourcen anlegen, jede Ressource entspricht einem Action-Controller
-
$this->addResource(new Zend_Acl_Resource('index'));
-
$this->addResource(new Zend_Acl_Resource('article'));
-
$this->addResource(new Zend_Acl_Resource('tag'));
-
$this->addResource(new Zend_Acl_Resource('comment'));
-
-
// Privilegien und Rechte für Gäste anlegen
-
$this->allow('guest', 'index');
-
-
// Privilegien und Rechte für Redakteure anlegen
-
-
// Privilegien und Rechte für Admins anlegen
-
}
-
}
Das ist keine große Sache und sollte jedem klar sein, der sich mit der Zend_Acl Komponente schon ein wenig befasst hat. Jetzt bleibt die Frage, wann wird die entsprechende ACL denn geladen. Dies kann erst passieren, nachdem das Routing statt gefunden hat und klar ist, welches Modul gerade aufgerufen wird. Dafür verwenden wir ein Plugin.
Das Plugin kümmert sich nur um die Benutzerrechte und nicht um die Authentifizierung. Es geht davon aus, dass diese bereits erfolgt ist. Das Plugin in an sich ist relativ selbsterklärend, hervorheben möchte ich nur, dass auf Basis des aktuellen Moduls der Klassenname der entsprechenden ACL erstellt wird. Der Rest sollte Routine sein. Wenn eine Benutzer nicht über entsprechende Rechte verfügt, wird er als Gast gebeten sich einzuloggen und als angemeldeter User bekommt einen entsprechenden Hinweis.
-
class Default_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
-
{
-
public function routeShutdown(Zend_Controller_Request_Abstract $request)
-
{
-
// hole Module, Controller und Action
-
$module = $request->getModuleName();
-
$controller = $request->getControllerName();
-
$action = $request->getActionName();
-
-
// hole Auth
-
$auth = Zend_Auth::getInstance();
-
-
// hole Rolle
-
$role = $auth->hasIdentity()
-
? Zend_Auth::getInstance()->getIdentity()->role
-
: 'guest';
-
-
// erstelle Klassenname für ACL
-
-
// Erstelle ACL Objekt
-
$acl = new $aclClass(); /* @var $acl Zend_Acl */
-
-
// Prüfe Rechte
-
if (!$acl->isAllowed($role, $controller, $action))
-
{
-
// lege neue Action fest
-
$newAction = ('guest' == $role) ? 'login' : 'forbidden';
-
-
// Ändere Request Objekt für neue Action
-
$request->setModuleName('default')
-
->setControllerName('user')
-
->setActionName($newAction);
-
}
-
}
-
}
Das war es schon im Wesentlichen. Das Ganze ließe sich nun noch verfeinern, indem man sich z.B. eine Factory Methode schreibt, welche das Ermitteln des Klassennamens übernimmt. Dadurch kann die ACL auch außerhalb des Plugins anhand eines Moduls ermittelt werden. Diese Methode könnte My_Acl::factory() heißen, als Parameter $module entgegen nehmen und ein von Zend_Acl abgeleitetes Objekt zurückgeben. Diese Factory-Methode könnte sich dabei auch gleich um das Caching kümmern, so dass die ACLs nicht bei jedem Aufruf neu erstellt werden müssen.
Ich hoffe, die Ausgangsfrage ist somit im Wesentlichen beantwortet. Falls nicht, bitte in den Kommentaren melden. Natürlich auch bei Fragen zu diesem Beitrag.
