Jun
3

Ein einfaches ACL System über Zend_Config_Ini

Ich will hier ein knappes und einfaches ACL und Auth System vorstellen, das ich mir gebastelt habe. Natürlich ist es hier sehr viel vereinfachter dargestellt, aber es funktioniert ;)

Erstmal die, welche in meinem Beispiel in /application/configs/ lagert. Dort dürfte es nichts zu erklären geben.

; Die existierenden Rollen angeben
role.0 = "guest"
role.1 = "user"
role.2 = "admin"

; Die existierenden Resourcen angeben
res.0 = "index"
res.1 = "error"
res.2 = "user"
res.3 = "test"

; Nun die genehmigungen für die einzelnen ROllen
access.guest.0 = "index"
access.guest.1 = "error"
access.guest.2 = "user"

access.user.0 = "index"
access.user.1 = "error"
access.user.2 = "user"
access.user.3 = "test"

access.admin.0 = "index"
access.admin.1 = "error"
access.admin.2 = "user"
access.admin.3 = "test"

Dazu gibt es eine Model Datei. Man kann diese natürlich auch anderweitig lagern. Ich habe es aber vorerst so gelöst. Die Klasse erweitert Zend_Acl. Als Parameter kann man den Standort einer Konfigurationsdatei angeben. Standard ist aber der o.g. Pfad. Es wird dann die Konfiguration geladen und in ein Array umgewandelt. Dieses Array wird nun durchgegangen. Die Resourcen angelegt und die Rollen. Wenn es nun für eine Rolle auch Einstellungen gibt, dann werden diese ebenso angelegt. Da Zend_Acl grundsätzlich erstmal alle Resourcen verbietet werden wir auch all jene erlauben, die erlaubt sein dürfen.
Wenn keine passende Konfiguration vorliegt, dann wird eine Zend_Acl_Exception geworfen.

<?php

/**
 * Festlegen der ACL Strukturen
 */
class Model_Acl extends Zend_Acl{

    private $_config;

    public function __construct($config = null){

        if($config != null)
        {
            $this->_config = (string) $config;
        }
        else
        {
            $this->_config = (string) APPLICATION_PATH . "/configs/acl.ini";
        }

        # Ini Datei muss im Construct mitgegebenw erden
       if(file_exists($this->_config))
       {
           # Konfig einlesen und in ein Array wandeln
           $config = new Zend_Config_Ini($this->_config);
           $config = $config->toArray();

            # Die Ressourcen dem ACL Syste4m hinzugüen
           foreach ($config["res"] as $res)
           {
               $this->add(new Zend_Acl_Resource($res));
           }

           # DIe Rollen dem ACL System hinzufügen
           foreach($config["role"] as $role)
           {
               $this->addRole(new Zend_Acl_Role($role));

               # Wenn es zu der Rolle auch was in der Accesslist gibt, dann füge das hinzu
               if(isset($config["access"][$role]))
               {
                   foreach($config["access"][$role] as $access)
                   {
                       $this->allow($role, $access);
                   }
               }
           }
       }
       else
       {
          throw Zend_Acl_Exception('Keine gültige Konfig angegeben!');
       }

    }

}

Irgendwo muss das Acl System aber auch initialisiert werden. Dafür habe ich ein Auth Plugin. In diesem Plugin nutze ich öfters ein Log, das ich mit “firelog” in der Registry benannt habe. Entweder man ändert dies in sein eigenes Log oder nimmt das Logging gänzlich raus. Die ganze Routine läuft nach dem Routing und vor dem Dispatch, damit Controller und Action noch beeinflusst werden können.
Eigentlich sollte das ganze Selbsterklärend sein für jemanden, der zumindest schon ein wenig gelesen hat. Ich empfehle hier das Kapitel Zend_Auth im Reference Guide.
Ansonsten sei nur gesagt, dass zu anlässen wie der nicht funktionierende Login oder das erfolgte einloggen Zielcontroller und Zielaction geändert werden, damit andere Seiten erscheinen. So braucht man sich in den Controllern selbst keine Gedanken mehr über das Auth System machen.

<?php
class Zorta_Plugin_Auth extends Zend_Controller_Plugin_Abstract {

	/**
	 * Nachdem das Routing durchgeführt wurde geht es los mit dem Auth System
	 * @uses Zend_Controller_Request_Abstract
	 */
	public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
	{

        # Lade eine Instanz von Zend_Auth
		$auth = Zend_Auth::getInstance();	

		# Wenn der Request ein POST ist und die Action Login aufgerufen wurde, dann versuche den Login
		if($request->isPost() && $request->getActionName() == "login"){

	        # POST Daten checken
			$username = $request->getParam('username');
			if($username == "")
				$username = "Dummy";

			$userpass = $request->getParam('userpass');
			if($userpass == "")
				$userpass = "Dummy";

			# Zend_DB_Table aus der Registry holen als Datenbankadapter für die Authorisation
			$db = Zend_Registry::get('Zend_Db_Table');
			$adapter = new Zend_Auth_Adapter_DbTable($db,'user','user_name','user_pass','MD5(?)');

			# Username und Passwort setzen
			$adapter->setCredential($userpass);
			$adapter->setIdentity($username);

			# Wenn die Nutzerdaten Ok waren, dann schreibe das Auth in den Storage
			if($auth->authenticate($adapter) && $auth->getIdentity() != false){

				# Schreibe die Identität in die Session
				$auth->getStorage()->write($adapter->getResultRowObject(null, 'password'));

                # Log, dass der Login durchgeführt wurde
				Zend_Registry::get('firelog')->log('Login accomplished!', Zend_Log::INFO);

				# Ab zum Index (Willkommensseite)
				$request->setControllerName('index');
				$request->setActionName('index');

			}
			else
			{

				# Wenn es nicht ok war, log und ab zurück zum Login
				Zend_Registry::get('firelog')->log('Login failed!', Zend_Log::INFO);
				$request->setControllerName('user');
				$request->setActionName('login');
			}
		}

		# Wenn keine Identität vorliegt
		if(!$auth->hasIdentity())
		{
			# Keine Identität, loggen und auf Zugriff prüfen mit der Gruppe "Gast"
			Zend_Registry::get('firelog')->log('User is not logged in!', Zend_Log::INFO);
			$this->testAcl($request, "guest");
		}
		else
		{
		   	# Authorisiert, log und Zugriffsprüfung
			Zend_Registry::get('firelog')->log('User is logged in!', Zend_Log::INFO);
			$this->testAcl($request, $auth->getIdentity()->user_group);
		}

	}

	/**
	 * Methode zum prüfen der Authorisation mittels ACL
	 * @param Zend_Controller_Request_Abstract $request
	 * @param $group Benutzergruppe
	 */
	public function testAcl(Zend_Controller_Request_Abstract $request, $group = null)
	{

	    # Laden der ACL Struktur und erstellen einer Instanz
	    require_once APPLICATION_PATH . '/model/Acl.php';
	    $acl = new Model_Acl();

	    # Prüfe den Status der Zugriffsberechtigung
	    if($acl->isAllowed($group,$request->getControllerName()))
	    {
	        # Wenn die Ressource erlaubt ist
	        Zend_Registry::get('firelog')->log('User has Auth for: '. $request->getControllerName(), Zend_Log::INFO);

	    }
	    else
	    {
	        # Wenn die Resource nicht erlaubt ist
	        Zend_Registry::get('firelog')->log('User has no Auth for: '. $request->getControllerName(), Zend_Log::ALERT);

	        $request->setControllerName('user');
			$request->setActionName('noauth');
	    }
	}
}
  • Florian Fackler 9. Juni 2009 um 02:36

    Hallo Denis,
    ein großes Lob und herzlichen für Deine Mühe!
    Ich Sachen Zend bin ich noch Anfänger – beschäftige mich erst seit 2 Monaten damit – und komme mit Hilfe Deiner “HowTo”s ein gutes Stück weiter.
    Mach weiter so – Du bist einer der Wenigen, die auch mal die 1.8er Features anwenden. Das ist meiner Meinung nach nämlich noch ein großes Manko bei den Tutorials von 2007 ;)
    Beste Grüße aus dem Süden,
    Florian

  • Piccolino81 18. Dezember 2009 um 13:57

    Hallo,

    ich bin seit längerem auf der Suche nach einer Lösung. Vielleicht kannst Du mir ja weiterhelfen? Ich würde gerne in der INI-Datei auch die “Vererbung” der einzelnen Rollen realisieren – geht das?

    Gruß
    Piccolino81

  • Rene Pardon 7. Oktober 2010 um 11:57

    @Piccolino81
    Es ist möglich. Du kannst deine ACL.ini genauso wie die application.ini aufbauen. Minimalistisches Beispiel:

    [user1]

    [user2 : user1]

    vG René

  • evilmonkey 24. Januar 2011 um 05:57

    stimme Rene zu. Folgende ini-Struktur ist auch denkbar:

    [role1]
    {|*}.{|*}.{|*} = {allow|deny}

    Sind bestimmte Ressourcen nicht definiert, werden sie standardmäßig als “deny” gewertet.
    Bei diesem Schema ist allerdings etwas zusätzliche Arbeit nötig, um zu gewährleisten, dass das Überschreiben bestehender ‘*’-Regeln mittels neuer ‘*’-Regeln konsistent bleibt. Beispielsweise:

    [visitor]
    default.index.* = allow
    *.*.index = deny

    Je nach Reihenfolge, in der die Regeln interpretiert werden, ist der Zugriff auf default.index.index erlaubt oder nicht erlaubt. Hier müssen zusätzliche Maßnahmen getroffen werden (vorzugsweise überschreiben der Zend_Acl-Klasse).

  • Denis Zunke 9. Mai 2011 um 21:37

    Hey evilmonkey,

    danke für deinen Kommentar. Es gibt nun einen neuen Artikel in dem ich deinen Vorschlag verarbeitet habe. Du bist da aber bestimmt schon weiter :)

Kommentar schreiben