Rechtesystem à la UNIX

Bezugnehmend auf den Beitrag von crypt zum Thema Rechteverwaltung möchte ich heute eine andere Lösung vorstellen, die ohne Stored Procedures auskommt. Eigentlich zeige ich nur, wie man das UNIX- und Linux-Rechtesystem (chmod) in eigenen Projekten anwenden kann.

Crypt hat bereits gesagt, dass eine Extra-Spalte für jedes Recht enorme Speicherverschwendung ist. Wie so oft gilt: Der einfachste Weg ist selten der performanteste!

Worauf basiert das Rechtesystem von Unix nun eigentlich? Auf Zweierpotenzen und auf Addition. Beides lernt man bereits ganz zeitig in der Schule. Richtig angewendet kann es uns Programmierern aber das Leben stark vereinfachen.

Bei UNIX gibt es 3 Rechte: Lesen, Schreiben und Ausführen.
Diesen wird jeweils eine Zweierpotenz zugerechnet: Lesen=0; Schreiben=1; Ausführen=2.
„Ausgerechnet“ (=dezimal sowie hier auch oktal) ergebeben sich also Lesen=2^0=1; Schreiben=2^1=2 und Ausführen=2^2=4.

Um einem Benutzer (oder einer -gruppe) nun zum Beispiel die Rechte Lesen und Schreiben einzuräumen, ergibt sich 1+2=3. Lesen und Ausführen wäre 1+4=5. Eine einfache Addition also.

Es ist wichtig, dass hier Zweierpotenzen genutzt werden, sonst kann es zu Überschneidungen kommen. Würde man zum Beispiel einfach Lesen=1, Schreiben=2 und Ausführen=3 wählen, gäbe es bei den Rechten Lesen+Schreiben (1+2=3) und beim Recht Nur-Ausführen (=3) eine Überschneidung. Somit könnten wir nicht feststellen, welche Rechte der user denn nun wirklich hat. Mit den Zweierpotenzen kann das nicht passieren.

Nun übertragen wir das auf ein Beispiel mit 4 Rechten. Diese sind:

  1. User darf neue Beiträge schreiben
  2. User darf Beiträge löschen
  3. User darf Beiträge editieren
  4. User darf Beitrag lesen

Wir haben nun also 4 Rechte und können diesen Zweierpotenzen zuweisen:

  • User darf neue Beiträge schreiben = 2^3 = 8
  • User darf Beiträge löschen = 2^2 = 4
  • User darf Beiträge editieren = 2^1 = 2
  • User darf Beitrag lesen = 2^0 = 1

Ein Administrator könnte nun beispielsweise alles dürfen (= 8+4+2+1 = 15). Ein angemeldeter User darf nur Schreiben und lesen (= 8+1 = 9). So kann man sich zu jeder Konstellation etwas zusammenbauen.

Wie wird das nun aber in der Datenbank umgesetzt?
Wir brauchen in der User-Tabelle eine Spalte rechte. Diese ist vom Typ TINYINT (gern auch UNSIGNED, das bringt noch etwas Performance). NOT NULL ist sie natürlich auch. Habe ich ja schon mal behandelt, warum das schneller ist. Damit haben wir potentiell 7 (= (ld 256)-1 -> ergibt sich aus: Maximum von 1 Byte = 8 1-Bits) mögliche Rechte (0 bedeutet immer keine Rechte). Wem das nicht reicht, wählt SMALLINT.

In diese können wir nun wie oben beschreiben die Werte einfügen. Um zu überprüfen, ob ein User ein bestimmtes Recht hat, tut man folgendes:

// Rechte-Wert des users aus Datenbank ist geholt
$recht = 9; // Schreiben + Lesen
 
if($recht>=8) {
// Schreiben erlaubt
}
 
if(($recht>=4 && $recht<8) || $recht>=12) {
// Löschen erlaubt
}

Das sieht noch etwas amateurhaft aus, schließlich müsste man sich die Dezimalwerte, bei denen das Recht passt, erst mühsam zusammensuchen.
Einfacher geht es über binäre Operationen:

if(($recht & 8)==8) {
// Schreiben erlaubt
}
 
if(($recht & 4)==4) {
// Löschen erlaubt
}

Das wird bestimmt nicht auf den ersten Blick klar, deshalb erkläre ichs:
Über das &-Zeichen kann man eine binäre UND-Verknüpfung durchführen. Ich werde es am Beispiel des Rechtes Löschen aufdröseln:
Mögliche Dezimalwerte, die das Recht Löschen einschließen sind: 4, 5, 6, 7, 12, 13, 14, 15.
Was haben all diese Zahlen nun gemeinsam? Im dezimalen System nicht viel. Binär aber sieht die Geschichte anders aus. Hier nochmal die Zahlen binär: 0100, 0101, 0110, 0111, 1100, 1101, 1110, 1111.
Hier fällt auf, dass bei allen das 3. Bit (beachte: Zählung von rechts) 1 ist. Dieses Bit steht für 2^2=4 – und das wiederum ist genau unser gesuchtes Lösch-Recht.
Wir können nun jede dieser genannten Rechtekombinationen binär mit 0100 (=4) verknüpfen. Als Beispiel soll die 13 dienen:
1101 & 0100 = 0100
Mit allen genannten Kombinationen kommt das gleiche heraus.

Auf diese Art ist man auch sehr flexibel gegenüber Rechteerweiterungen, die später eventuell erfolgen sollen – und das möglichst ohne die gesamte Anwendung umzuschreiben.

Ich hoffe ich konnte dieses Rechtesystem etwas schmeckhaft machen und vielleicht denken Sie ja beim nächsten Mal, wenn Sie so etwas benötigen an diesen Beitrag.

PS: crypts Lösung ist etwas eleganter, weil man problemlos ein bestimmtes Recht setzen kann ohne die anderen zu kennen. Mit dem hier beschriebenen System müsste man den Wert in der Datenbank erst in eine Binärzahl umwandeln, dann das Recht ändern und anschließend wieder dezimal umrechnen. Sicherlich auch keine große Hürde – am Ende würde ein ähnliches System rauskommen wie bei crypt.
Der Vorteil des hier beschriebenen Systems ist die Entlastung der Datenbank (wichtig für Projekte, die dort den Flaschenhals haben).

Jan hat 152 Beiträge geschrieben

9 Kommentare zu “Rechtesystem à la UNIX

  1. Christoph sagt:

    Ich hab ein System mit 162 verschiedenen Rechten. Also bräuchte ich eine Ganzzahl bis 2^162 = 5,8460065493236116728147393308651e+48. Wird leicht unübersichtlich. Da würde ich dann lieber die Binärzahl als String abspeichern.
    Dabei haben 15 Leute Rechte, das sind so grob über den Daumen gepeilt 2500 Datensätze. Das verkraftet MySQL immer noch. Und ich kann die Daten in der DB sogar ohne spezielles Frontend lesen und schreiben.

    Aber hier geht’s ja um Performance. Also würde ich das mal irgendwie benchmarken. Wie lange dauert es, die Rechte auszulesen? Sicher geht’s schneller als mein „SELECT `right` FROM user_rights WHERE user_id = n“ 😉

  2. Martin sagt:

    Dir ist aber schon klar, dass 2^162 auf deinem System ein float ist?!

    Auf einem 32bit System sind Integer nur bis 2^32 möglich (auch mit PHP ist das nicht anders):

    [mas@32bit ~]$ php -r ‚$t = 4294967296; var_dump($t);‘;
    float(4294967296)

    [mas@64bit ~]# php -r ‚$t = 4294967296; var_dump($t);‘;
    int(4294967296)

    Rechtesystem mit Bits ist z.B. für ein CMS nicht wirklich optimal. Für Flags (Konstanten) gibt es aber nichts besseres.

    An deiner Stelle würde ich überlegen, ob du die Rechte nicht einfach als String abspeicherst.

    rights als varchar(1024), damit hast du nicht etwas Platz.

    Der Rechtestring besteht dann einfach aus vielen 0en und 1en.

    rights = 000100010010100010001111
    if rights[5] === 1;

    Das kannst du dann einfach als zusätzliches Feld in deiner User Tabelle speichern.

    Gruss,
    Martin

  3. Synthor sagt:

    Sehr interessante Artikel hier!

    Ich überlege seit heute morgen wie ich am besten das Rechtesystem der Unternehmerdatenbank, die ich entwickeln soll umsetze.
    Auf die Performance kommt es jetzt weniger an, da nur ca. 50-100 Leute damit arbeiten werden.
    Wichtiger ist mir eine gewisse Flexibiliät mit den Rechten, den Gruppen und auch den Usern. Schließlich will ich einem User, der Mitglied in Gruppe XYZ ist auch individuelle Rechte für Modul ZYX geben können.

    Der Artikel hat mir aber einen sehr sehr guten Denkanstoß gegeben. Ich weiß jetzt wie ich es löse…

    Diese Seite wandert zu meinen Programmier-Favoriten. 😉

  4. thgc sagt:

    Man könnte vielleicht noch

    if(($recht & 8)==8) {
    // Schreiben erlaubt
    }

    if(($recht & 4)==4) {
    // Löschen erlaubt
    }

    durch sowas ersetzen:

    define („WRITE_FLAG“, 4);
    define („DELETE_FLAG“, 8);

    if(($recht & WRITE_FLAG) == WRITE_FLAG) {
    // Schreiben erlaubt
    }

    if(($recht & DELETE_FLAG) == DELETE_FLAG) {
    // Löschen erlaubt
    }

    Gruß,
    thgc

  5. Gelle sagt:

    Interessant wäre nun noch die Verknüpfung von Usern, Rollen und Gruppen -> welche Rechte heben sich auf?

    Angenommen: eine Gruppe darf nur Lesen, weil der User aber Administrator ist darf er auch schreiben. Außerdem besitzt der User das individuelle Recht zu löschen, aber zu diesem Recht KEINE Leseerlaubnis.

    Wird das nur durch Subtraktionen und Additionen gelöst?
    Welche Rechte würden sich dann hier erfahrungsgemäß aufheben, addieren oder subtrahieren? Ist ein Gruppenrecht schwächer oder stärker als ein Rollenrecht?

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>