Binäres Rechtesystem

B

Bgag

Guten Abend!
Ich stehe mal wieder vor einem größeren Problem. Jeder kennt ihn, den Stress mit der Benutzerverwaltung und dem damit verbundenen Rechtesystem. Früher habe ich einfach in der User-Tabelle eine Spalte rights gehabt, in der durch Komma getrennt die Bereiche standen, die der User betreten darf. Leider ist diese Variante sehr Performance fressend und erfordert eine weitere Tabelle in der alle verfügbaren Bereiche des internen Bereichs stehen, um die Rechte auch ändern zu können. Daher bin ich sehr schnell auf die Variante umgestiegen, bei der in einer Tabelle einerseits das Recht, dass der User besitzt und der jeweilige User stehen. Das heißt für jeden User mehrere Einträge in dieser Tabelle, es sei denn er besitzt überhaupt keine. Aber auch diese Variante scheint mir nicht die optimalste zu sein.

Da fiel mir das UNIX- oder auch Datei-Rechtesystem (chmod) ein. Beide bauen auf dem binären Zahlensystem auf. Die möglichen Rechte beim Zugriff auf Dateien sind euch vermutlich bekannt. Lesen, Schreiben und Ausführen (die Unterscheidung in Besitzer, Gruppen und Öffentliche Berechtigungen lassen wir außer Acht). Diesen drei Rechten wurden Zweierpotenzen zugewiesen.
Ausführen => 1 (2^0)
Schreiben => 2 (2^1)
Lesen => 4 (2^2)
Durch addieren der einzelnen Rechte kann man alle Rechte eines Users auf eine Zahl zusammenführen. Administratoren dürfen ja bekanntlich alles (=1+2+4=7) wohingegen andere User zum Beispiel nur Lesen und Schreiben dürfen (=2+4=6). Das besondere an einem binären Rechtesystem ist nun, dass man aus dieser Zahl (anders als beim Dezimalsystem) wieder alle Rechte extrahieren kann. Dies wird klarer wenn man die drei Zahlen für Lesen, Schreiben und Ausführen ins Binärsystem (Zählung von rechts) übersetzt.
Ausführen => 001
Schreiben => 010
Lesen => 100
Der Administrator besitzt also die rechte 111 und der vorhin erwähnte User 110.

Ein auf dieser Idee aufbauendes Rechtesystem wäre sehr Performance freundlich und würde nur zwei Tabellen erfordern. Die User-Tabelle mit einer Spalte permissions oder perms (Rechte) und einer Tabelle permissions, die alle geschützten Bereiche und das jeweils benötigte Recht (eine Zweierpotenz) beinhaltet. PHP bietet uns auch schon durch die binären Operatoren eine wundervolle Möglichkeit zur Überprüfung, ob ein Benutzer die nötigen Rechte besitzt. Es ist nämlich möglich über das &-Zeichen eine binäre UND-Verknüpfung zu schaffen. Mit Hilfe dieser Operation kann man zum Beispiel nun feststellen, ob an der dritten Stelle (Zählung von rechts) der binären Zahl, die die Rechte eines Users vereint, eine Eins steht, die dem User im obigen Beispiel das Lesen erlauben würde.

Man könnte zum Beispiel wie folgt überprüfen, ob ein User die benötigten Rechte für einen Bereich haben:
PHP:
// permissions from database
$perm = 6;

if($perm >= 6)
{
   // writing allowed
}

if($perm == 1 || $perm == 3 || $perm == 7)
{
   // allowed to take orders
}
Dank dem &-Operator von PHP geht es allerdings auch einfacher:
PHP:
if(($perm & 2) == 2)
{
   // writing allowed
}

if(($perm & 4) == 4)
{
   // reading allowed
}
Genug aber der Theorie. Da ich nun in mehreren neueren Projekten ein ausreichendes Rechtesystem benötige. Habe ich begonnen eine Klasse zu schreiben, die mir die Verwendung eines binären Rechtesystems ermöglicht bzw. erleichtert.

Leider gibt es noch einige Probleme, bei denen ich noch nicht so richtig weiter komme.

Die Methode addPerm(), mit deren Hilfe man neue geschützte Bereiche hinzufügen können soll, funktioniert leider nicht so richtig. Das SQL-Query ist wohl fehlerhaft und ich kann leider nicht sagen, wo da mein Fehler liegt. Kann mir da jemand helfen?

Außerdem scheinen mir noch einige andere Queries wie bei userPerm() und getPerms() noch zu kompliziert zu sein. Kennt jemand eine Möglichkeit der Vereinfachung?

Des weiteren würde ich in den Methoden getPerm() und delPerm() gern optional entweder den Namen oder die Id der Section übergeben lassen. Hat jemand einen Vorschlag, wie ich das am besten lösen könnte?

Zu guter Letzt möchte ich noch die Methoden editUser() und addUser() hinzufügen, die beide die Parameter $fields und $values (beides Arrays) übergeben bekommen sollen. Natürlich wird bei editUser() zusätzlich noch die User-Id mit übergeben. Mein Problem ist allerdings, wie ich bei editUser() diese beiden Arrays richtig in das Query einbinde, damit die Datenbank es auch annimmt. Vielleicht kann mir auch dabei jemand helfen.

Ich wäre euch sehr dankbar, wenn ihr mir den ein oder anderen Tipp oder weitere Anregungen geben könntet. Wäre auch sehr nett, wenn jemand mal über meine Queries schauen könnte. Mysql ist leider ein bisschen mein Stiefkind. Ich beantworte natürlich auch gerne noch weitere Fragen, wenn meine vorhergehende Beschreibungen meines Vorhabens bzw. meines Problems oder eher meiner Probleme nicht ausreichend waren.
MfG, Andy
 
Zuletzt bearbeitet von einem Moderator:
wow das ist ja fast schon ein tutorial.
Muss mich mal einlesen und das ganze auf meinem lokalen server mal testen vielleicht fällt mir dann heut abend noch was dazu ein.
Ist jedenfalls ein guter Gedankenanstoß stehe nämlich auch gerade bei user und rechten.

Was mich jetzt noch interesieren würde: Das mit dem & als Binärer-Operator habe ich im Handbuch nicht gefunden. Hast du einen Link?
Bzw: Kannst du es genauer erklären? (Kann sein das das garnichts PHP Technisches ist sondern simple Mathematik?)
 
Zuletzt bearbeitet:
Moin,
hier der Link zu den Bit-Operatoren

Beim Bit-Operator AND werden alle Bits gesetzt die in beiden Werten gesetzt sind.
Beispiel:
Code:
    10110010
AND 10001110
    --------
    10000010

Zu den anderen Bit-Operatoren findest du im Manual kurze Erklärungen.

Schöne Grüße
Marvin Schmidt
 
Danke Marvin jetzt hats klick gemacht :)
Bin zwar im Manual über díe seite drübergestolpert aber habs nicht wahrgenommen
 
Hört sich schonmal nett an ;)

Hier mal einige Kritikpunkte von mir:

So, nun noch Kleinigkeiten zum Code:

PHP:
while ($row = $result->fetch_assoc())
      {
         $max = $row['perm'];
      }
Warum rufst du hier nochmal extra die while Schleife auf? Du weißt doch bereits, das nur ein Ergebnis zurückkommt, also würde
PHP:
$max = $result->fetch_assoc();
doch genügen?

PHP:
$sql = "INSERT INTO {$this->permTab}
            (id, name, perm)
         VALUES
            ('', {$name}, {$perms})";

Strings werden immer noch maskiert in SQL und der Schönheit halber, würde ich das gleiche auch bei den Tabellen tun.

PHP:
$sql = "INSERT INTO {$this->permTab}
            (`id`, `name`, `perm`)
         VALUES
            ('', '{$name}', '{$perms}')";
Und warum klappt das Query nicht?
Gibt es eine Fehlermeldung?

Die Queries finde ich jetzt nicht wirklich komplex, allerdings würde ich davon abraten den * Operator bei SQL zu verwenden. Du brauchst doch gar nicht alle Felder, oder? Und selbst wenn, lohnt es sich trotzdem alle Felder manuell hinzuschreiben.
 
Erkläre doch noch mal welche Bedeutung die „perm“-Werte haben und was genau die addPerm()-Methode machen soll.
 
Guten Morgen!
Vielen Dank Felix das Script funktioniert nun in seinem Testaufruf fast fehlerfrei. Du hast allerdings noch gefragt wieso ich noch über die Schleife gegangen bin, wenn ich doch weiß, dass nur ein Wert zurückgegeben werden kann. Ganz einfach. So wie du es machen würdest, würde ein Array mit nur einem Eintrag zurückgegeben. Das ist eigentlich nicht Sinn der Sache. Allerdings hast du trotzdem Recht. Ich muss das ganze dann eben so machen:
PHP:
// save max value
$max = $result->fetch_assoc();

return $max[0];
Oder eben so ähnlich, denn der größte Wert wird ja garnicht zurückgegeben sondern sofort weiter verarbeitet.

@Gumbo: Es gibt zwei verschiedene Tabellen, die perm-Werte in einer Spalte perms oder perm enthalten. Die eine ist die Tabelle permissions die alle Sektionen eines geschützten Bereichs und eben den zu jedem dieser Bereiche zugehörigen Binärwert enthält. Die zweite Tabelle die eine solche Spalte enthält ist die Tabelle user, die sonst von Login zu Login unterschiedlich gestaltet werden kann. Nur eben diese Spalte ist wichtig, da sie für jeden User die Summe der Binärwerte enthält, zu deren Sektionen er Zugang besitzt.

Die Methode addPerm() arbeitet mit der Tabelle permissions und erstellt in ihr neue Einträge. Fügt also eine neue Sektion im geschützten Bereich hinzu. Dies kann zum Beispiel bei einem Modul-basierten CMS nützlich sein. Mir ist bei dieser Methode auch gleich noch ein Fehler aufgefallen. Und zwar ist es möglich eine Sektion via addPerm() zu erstellen, die bereits existiert. Wie kann ich das zum Beispiel im entsprechenden Query überprüfen und ggf. verhindern?

Ein weiteres Problem stellt auch noch das wahlweise ermitteln der Rechte für einen Bereich oder eines Users via ID oder Name. Wie kann ich das am geschicktesten verwirklichen? Ich hätte das wohl so gelöst:
PHP:
public function getPerm($ident)
{
   if( filter_var($ident, FILTER_VALIDATE_INT) )
   {
      // do something
   }

   else
   {
      // do alternative
   }
}

Zu guter Letzt bleibt auch noch das Problem mit editUser() und addUser(), bei denen aus zwei Arrays mehrere Felder gefüllt werden sollen. Das soll so gemacht werden, damit eben von Login zu Login unterschiedliche Daten über die Benutzer gespeichert werden können. Wie mache ich das nun aber richtig?
MfG, Andy

//EDIT: Die neuste Version des Scriptes ist im ersten Beitrag zu finden.
 
Zuletzt bearbeitet von einem Moderator:
Hi.

Irgendwie habe ich auch Probleme die addPerm() Methode zu verstehen.
Du schreibst, da kann man eine "Sektion" hinzufügen mit entsprechenden Rechten.
Ist eine Sektion nun sowas wie Modul "News" mit allen möglichen Aktionen die man da ausführen kann (Schreiben, Editieren, Löschen, usw) oder ist eine Sektion eine Aktion, also zum Beispiel "News verfassen"

Im Detail zu addPerm():
In der Tabelle muss ja mindestens 1 Wert stehen und damit die perm Spalte in der Permissionstabelle > 0, da sonst log(0,2) gegen -INF geht.
Gehen wir von
max(permtable.perm) == 1 aus, dann ist 2^(log(1,2)+1) = 3 ?
Ist der erste Wert in der Permissionstable nicht 1 sondern 2 dann
max(permtable.perm) == 2, dann ist 2^(log(2,2)+1) = 0 ?
Hier besteht aber das Problem, das 0 als Permvalue für $name in die DB geschrieben wird und in jedem folgenden Aufruf max(permtable.perm) wieder der Anfangswert 2 rauskommt, da 0 < 2
Wenn der erste Wert nun doch 1 war und $perms == 3 für die neue "Sektion" $name in die DB geschrieben wird, dann sieht der darauf folgende versuch addPerm($name) zu nutzen wie folgt aus:
max(permtable.perm) == 3, also 2^(log(3,2)+1) = 0 ?
Damit sind wir beim gleichen Problem wie beim Startwert 2, die nächsten aufrufe müssten immer mit max(perm) == 3 anfangen wo das ergebnis des neuen $perms == 0 ist.

Ich hoffe ich habe das ganze überhaupt richtig verstanden und ihr versteht gerade was ich überhaupt sagen will ;)
Wäre super, wenn du da mal ein paar Durchläufe hier durchspielen könntest und die Ergebnisse die dann in der DB stehen.

Insgesamt finde ich es seltsam, das in addPerm() der neue "perm" Value aus der grössten Zahl der Permissionstabelle generiert wird und keinen Bezug zu vielleicht bestehenden Sektionen ($name) oder einfach gesagt wird, addperm($name, $neededRights = 4) oder sowas.


Mir isses irgendwie zu hoch, ich bleib dabei Tabellen mit User Roles und User Specific Rights zu erstellen. Ist eine Tabelle mehr und brauch vielleicht 0,05ms mehr in der DB abfrage, aber das verstehe ich sofort :)
 
Hallo!
Ich denke das ist erstmal egal ob eine Funktion ein komplettes Modul oder bestimmte Arbeits-Rechte sind.

Der Wert in der perm-Spalte in der Tabelle permissions wird immer aus dem größten Wert generiert, da es sich in dieser Spalte ja um Zweierpotenzen handelt. Der erste Bereich erhält den Wert 2^0=1, der zweite 2^1=2, der dritte 2^2=4 und so weiter. So sollte eine fortlaufende Reihe entstehen. Natürlich werden auch mal Bereiche gelöscht, weil zum Beispiel ein Modul deinstalliert wird. Dann habe ich mehrere Möglichkeiten. Zum einen kann ich alle Werte updaten um wieder eine fortlaufende Reihe zu erhalten. Das ist leider sehr umständlich. Zudem macht es auch nicht wenn die Reihe nicht fortlaufend ist, da man trotz allem alle Bereiche aus der Summe der Bereiche extrahieren kann. Wieso gehe ich nun immer vom größten Wert aus? Ganz einfach da es eine fortlaufende oder fast fortlaufende Reihe ist, muss ich nur den größten Wert, der für einen Bereich festgelegt ist ermitteln und den Logarithmus zur Basis 2 ermitteln um die neue Zweierpotenz für den neuen Bereich generieren zu können.

Beispiel:
Der binäre Wert für den Bereich News ist 8192.
Der Logarithmus zur Basis zwei von 8192 ist 13.
So muss mein neuer Bereich den binären Wert 16384 haben, der aus 2^14 generiert wird.

Wie du siehst hinkt deshalb auch dein Beispiel. Da es sich in der Tabelle um Zweierpotenzen handelt, kann es einen Eintrag 3, wie du errechnet hast garnicht geben. Einen solchen Eintrag kann es nur in der Spalte perms in der User Tabelle geben. Zudem ist der log(1,2)=0. Daher ist 2^(log(1,2)+1) = 2^1 = 2 und nicht 3. Außerdem ist 2^(log(2,2)+1) = 2^2 = 4 und nicht 0. Du siehst es kann keinen Eintrag mit dem Wert 0 in der permissions-Tabelle geben. Zudem kommen auch nur Zweierpotenzen in Frage. Zweierpotenzen sind zudem immer gerade Zahlen (Ausnahme 1).

Ich hoffe du verstehst jetzt dieses Konzept besser. Dein Fehler war kein Verständnis- sondern ein Rechenfehler.
MfG, Andy
 
Okay, irgendwo war es mir dann doch klar, aber
PHP:
$perms = 2^$a;
Ist doch eine BIT Operation (XOR) und keine Potenzrechnung.
Was du meinst ist hier sicherlich pow()
PHP:
$perms = pow(2, $a);

Meine Berechnungen habe ich zur Sicherheit von PHP berechnen lassen. Hast du das System denn schonmal genau geprüft? Denn dann müsste das doch aufgefallen sein.
 
Zurück