Das Thema Benutzerrechte ist ja ein eher lästiges Thema, sollte aber schon möglichst früh in die Planung einer Applikation einfließen. Nach einigen Überlegungen und Diskussionen von verschiedenen Vorgehensweisen habe ich mich für einen recht einfachen Ansatz entschieden. Das Prinzip will ich hier kurz beschreiben, vielleicht ist es ja für den ein oder anderen interessant.

Die Lösung sollte möglichst wartungsfrei und einfach sein, zudem sollte natürlich die ACL Komponente vom Zend Framework zum Einsatz kommen. Folgende Rahmenbedingungen gelten für diese Lösung:

  • Es gibt Benutzer und Gruppen
  • Ein Benutzer/Gruppe hat ein Recht oder hat es nicht (keine eingeschränkten Rechte oder feinere Abstufungen)
  • Ein Benutzer ist in keiner, einer oder mehreren Gruppen
  • Ein Recht das einmal vergeben wurde kann nicht mehr wiederufen werden (z.B. durch eine Mitgliedschaft in einer weiteren Gruppe die das Recht explizit nicht hat)

Zend_Acl sieht Rollen und Ressourcen vor. Für Ressourcen können schließlich Rechte vergeben werden. Eine Rolle ist hier ein Benutzer oder eine Gruppe. Die Ressourcen ergeben sich aus den verfügbaren Modulen, Controller und Actions des MVC Patterns. Sollen also Rechte vergeben werden (z.B. in der Administration der Gruppen), so werden alle Module, Controller und Actions ausgelesen. Für diese können dann die Rechte gesetzt und in der Datenbank gespeichert werden. Der Benutzer hat erst einmal keine Rechte und für jede Berechtigung muss explizit ein Eintrag in einer Tabelle erzeugt werden. Hierfür wird eine Tabelle mit folgender Struktur angelegt:
Beispiel Tabelle

Gibt es z.B: für ein Blogsystem ein Modul admin mit dem Controller articles und den Actions write, delete und list, so sind verschiedene Berechtigungen denkbar. Oben stehender Auszug zeigt drei Beispieldatensätze in der Tabelle für Berechtigungen, die auf das Beispiel passen könnten:

  • Benutzer 1 darf alle Actions in allen Controller des Moduls admin ausführen
  • Die Gruppe 55 darf alle Actions im Controller articles des Moduls admin ausführen
  • Benutzer 12 darf nur die Action list im Controller articles des Moduls admin ausführen

Eine Prüfung der Rechte kann zentral an einer Stelle erfolgen. Hier kann ein Plugin beim Front Controller registriert werden. Ich bevorzuge es hier den Zend_Action_Controller in eine eigenen BasisController Klasse abzuleiten und dort in dem Hook init verschiedene Dinge, wie eben diese Berechtigungen zu prüfen. Alle eigenen Controller werden dann wiederum vom BasisController abgeleitet. So wird vor der Ausführung einer jeden Action zuerst die Berechtigung geprüft und ggf. mit einer Fehlerseite abgebrochen, falls diese fehlt.

Um eine Berechtigung zu prüfen, kann die Komponente Zend_Acl genutzt werden. Dies ist allerdings sehr aufwendig, deshalb ist an dieser Stelle unbedingt ein Caching notwendig. Mit der Zend_Cache Komponente ist das allerdings sehr einfach: es wird einmal das ACL Objekt erzeugt und dann für die Dauer der Session gecached (oder direkt in der Session des Benutzers gespeichert).

Ebenso müssen die überhaupt verfügbaren Rechte nicht extra konfiguriert werden, sondern ergeben sich automatisch aus den vorhandenen Modulen, Controller und Actions. Um diese für eine Rechtevergabe in der Administrationsoberfläche auszulesen, verwende ich das seit PHP 5 verfügbare Reflection. Um also alle Module, Controller und Actions zu erhalten kann folgende Funktion genutzt werden:

/**
 * returns all available modules, controllers and actions
 *
 * @param Zend_Controller_Front $fc the current front controller
 * @return array
 */
protected function getRessources($fc) {
	$ressources = array();

	// search all registered modules
	foreach($fc->getControllerDirectory() as $module => $dir) {

		$ressources[$module] = array();

		// search all controller files
		foreach(scandir(realpath(dirname(__FILE__)) . $dir) as $file) {
			$currentFile = realpath(dirname(__FILE__)) . $dir . "/" . $file;

			// search in all controller files for actions
			if(is_file($currentFile) && strpos($file,"Controller")!==false) {
				require_once($currentFile);
				$classname = $module . "_" . str_replace(".php","",$file);
				$controller = new ReflectionClass($classname);

				$methods = array();
				foreach($controller->getMethods() as $method) {
					// don't include error handler
					if($method->getName()=="errorAction")
						continue;

					$length = strpos($method->getName(),"Action");
					if($length!==false)
						$methods[] = substr($method->getName(),0,$length);
				}
				if(count($methods)>0) {
					$controllername = strtolower(substr($file,0,strpos($file,"Controller")));
					$ressources[$module][$controllername] = $methods;
				}
			}
		}
	}

	return $ressources;
}

Als Administrationsoberfläche kann ich den Tree von Extjs empfehlen, der auch Checkboxes unterstützt:

Admin Rechte Extjs Tree

An dieser Stelle möchte ich kein gesamtes Codebeispiel geben, da die Integration ins eigene Projekt und die eigene Umsetzung durchaus variieren kann. Was ich hier als Grundidee vermitteln will ist die Ausrichtung der ACL an der MVC Struktur. Einmal ins eigene Projekt eingebaut ist keine weitere Anpassung mehr notwendig.

Über Feedback, Bedenken und Anmerkungen freue ich mich, da diese Lösung pragmatisch, aber sicherlich auch verbesserungswürdig ist.

2 Kommentare zu “Zend_Acl Integration in MVC basierten Projekten”

  1. christian

    Nett gemacht das ganze…

    Ich hab zu dem ganzen jedoch eine Frage!
    Warum grenzt du die Auswahl der Methoden nicht noch etwas weiter ein?
    Damit meine ich, nur Methoden die Public sind…

    Vereinzelt beinhaltet der Controller ja funktionen welche für den Nutzer keinen Output besitzen…

    Oder ist dies in deinem Beispiel so geregelt, daß nur Methoden eingebunden werden, welche mit Action enden?

     
  2. Tobi

    Hi Christian,

    vielen Dank für deine Anmerkung!

    Die Funktion getRessources() liefert nur Methoden, die auch ein “Action” enthalten (vgl. Zeile 31-33). Für gewöhnlich sind das genau die Methoden, die vom Benutzer auch von extern aus aufgerufen werden können. Allerdings darf dann natürlich nicht der String “Action” in privaten oder internen Methoden verwendet werden (was natürlich mit einer zusätzlichen Prüfung, ob die Methode auch public ist vermieden werden könnte).

    Viele Grüße
    Tobi

     

Hinterlasse eine Antwort