# Binäres Rechtesystem



## Bgag (23. Juli 2008)

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:

```
// 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:

```
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


----------



## Da_Chris (24. Juli 2008)

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?)


----------



## Marvin Schmidt (24. Juli 2008)

Moin,
hier der Link zu den Bit-Operatoren

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

```
10110010
AND 10001110
    --------
    10000010
```

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

Schöne Grüße
Marvin Schmidt


----------



## Da_Chris (24. Juli 2008)

Danke Marvin jetzt hats klick gemacht  
Bin zwar im Manual über díe seite drübergestolpert aber habs nicht wahrgenommen


----------



## Flex (24. Juli 2008)

Hört sich schonmal nett an 

Hier mal einige Kritikpunkte von mir:

So, nun noch Kleinigkeiten zum Code:


```
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

```
$max = $result->fetch_assoc();
```
doch genügen?


```
$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.


```
$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.


----------



## Gumbo (24. Juli 2008)

Erkläre doch noch mal welche Bedeutung die „perm“-Werte haben und was genau die addPerm()-Methode machen soll.


----------



## Bgag (24. Juli 2008)

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:

```
// 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:

```
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.


----------



## Mairhofer (24. Juli 2008)

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


----------



## Bgag (24. Juli 2008)

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


----------



## Mairhofer (24. Juli 2008)

Okay, irgendwo war es mir dann doch klar, aber 

```
$perms = 2^$a;
```
Ist doch eine BIT Operation (XOR) und keine Potenzrechnung.
Was du meinst ist hier sicherlich pow()

```
$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.


----------



## Bgag (24. Juli 2008)

Wo du Recht hast, hast du Recht. Ich werde es gleich mal ändern.
MfG, Andy

*//EDIT:* Der Fehler wurde im Quellcode behoben.


----------



## Bgag (26. Juli 2008)

Hallo!
Ich entschuldige mich für das doppelte Posting, aber es hat sich etwas getan und es sind neue Fragen entstanden. Ich habe nun die Klasse so umgeschrieben, dass es dem Benutzer möglich ist die Rechte für einen Bereich oder die eines Benutzers, optional anhand einer Id oder eines Namens, abzufragen. 

Zudem ist es nun möglich einen neuen Benutzer zu erstellen bzw. einen schon bestehenden User zu editieren. Das Problem liegt jetzt auf der einen Seite bei den verschiedenen Queries, bei denen ich nun leider nicht weiß, ob diese so korrekt bzw. optimal sind. Zudem bin ich mir nicht ganz sicher ob bei den Methoden _addUser()_ bzw. _editUser()_ die Verarbeitung der Arrays _$fields_ und _$values_ via _implode()_ korrekt sind. Kann mir da vielleicht jemand helfen? 

Außerdem wurde das Problem der doppelten Einträge in einer der beiden Tabellen, über den zusätzlichen Parameter _UNSIGNED_ für das Feld _name_, behoben. Haltet ihr das für ausreichend oder wäre eine Überprüfung durch eine Abfrage besser?

Leider tritt noch ein Fehler beim Löschen eines "Bereichs" aus der Tabelle permissions auf. Woran kann das liegen. Das betreffende Query sieht für mich vollkommen richtig aus.

Des weiteren würde mich eure Meinung zu der Fehlerbehandlung in dieser Klasse interessieren. Sollen Fehler mit Exceptions behandelt werden oder soll alles über boolesche Werte geregelt werden, sodass der Nutzer der Klasse eigene Fehlermeldungen generieren kann? 

Die Klasse ist in ihrer neuen Form angehängt. Testen konnte ich sie leider noch nicht, da mir an meinem momentanen Aufenthaltsort keine Testumgebung zur Verfügung steht. Ich bitte daher um Entschuldigung, falls kleinere Fehler übersehen wurden. Ich würde mich trotzdem sehr über Hilfe oder weitere Anregungen zur Optimierung oder Erweiterung der Klasse freuen.
MfG, Andy


----------



## Gumbo (26. Juli 2008)

Ich frage mich gerade, ob das gesamte System überhaupt sinnvoll beziehungsweise schlüssig ist. Denn einerseits sind damit nur maximal 64 Einträge möglich (BIGINT UNSIGNED ist auf 2?? beschränkt). Andererseits verstehe ich nicht, wieso bei einem neuen Eintrag zwar das nächste Bit verwendet wird, der neue Wert aber einen eigenen Datensatz bekommt.


----------



## Bgag (26. Juli 2008)

Ich denke 64 interne Bereiche oder Rechte sind für meine Bedürfnisse vollkommen ausreichen. Außerdem kann man da ja auch etwas tricksen. Man könnte zum Beispiel einfach Varchar in der Datenbank benutzen und vor dem Eintragen prüfen ob es sich bei der übergebenen Variable um einen Integer handelt. Nötig sollten solche Schludereien aber meiner Meinung nach garnicht sein. 

Zudem würde mich interessieren, welche Alternative du siehst, als jedem neuen Wert einen eigenen Datensatz zu geben. Es geht ja momentan speziell darum es möglichst einfach zu machen die Rechte für Bereiche wie News, Statistiken, generelle Einstellungen, Artikel und was ein CMS noch alles haben kann, zu unterscheiden. Verfeinern kann man das natürlich auch noch.

Mir fällt des weiteren gerade ein, dass es ja reichen würde den Exponenten jedes Bereichs in der Datenbank zu speichern bzw. die Spalte perms in der Tabelle permissions komplett entfallen zu lassen und die Id für einen Bereich als Exponenten zu nutzen. So würde die Tabelle Schlanker, die Klasse vereinfacht und die Anzahl der möglichen Bereiche mehr als verzehnfacht.

Was meinst du? Wäre das eine akzeptable Lösung?
MfG, Andy


----------



## Gumbo (26. Juli 2008)

Ich weiß ja eben nicht, wie du dir das Ganze gedacht hast. Wie sähe beispielsweise eine solche Tabelle samt Beispielwerte aus und welche Bedeutung haben sie?


----------



## Bgag (27. Juli 2008)

Also es gibt zwei Tabellen. Die Benutzer-Tabelle und die Rechte-Tabelle. Die Benutzer-Tabelle kann unterscheidlich aussehen. Der einfachste Fall wäre aber wohl der folgende.

```
id	| name	| pwd	| rights
-------------------------------------------------------------
1	| avedo	| 104bn      | 31 (11111)
2	| peter	| 110dm     | 16 (10000)
3	| klaus	| md640     | 12 (01100)
4	| maic	| 8d9f0       | 11 (01011)
```
Um die Werte in der letzten Spalte rights verstehen zu können muss man jetzt auf die Tabelle permissions einen kurzen Blick werfen.

```
id	| name		| rights
------------------------------------------------------------------
1	| news		| 1 (00001)
2	| articles		| 2 (00010)
3	| database		| 4 (00100)
4	| user		| 8 (01000)
5	| settings		| 16 (10000)
```
Wie man schnell erkennt stehen in der dritten Spalte der Tabelle nur Zweierpotenzen. In Klammern hinter den Werten in den Spalten rights in beiden Tabellen steht die jeweilige Zahl als Binärwert (wird von rechts gelesen). Beim Vergleich der Werte und der Tabellen im Allgemeinen stellt man einiges fest. Addiert man alle Bereiche erhält man die Rechte von avedo (31). Addiert man allerdings nur die Rechte von news, articles und user, erhält man die Rechte von maic (16).  Interessanter wird es nun wenn man anstatt der Dezimalzahlen die Binärwerte.

```
01000 (2^3)
+00010 (2^1)
+10100 (2^4 + 2^2)
-------------
=11110 (2^1 + 2^2 + 2^3 + 2^4)
```
Man kann also genau feststellen welche Zweierpotenzen hier addiert werden. Geht man also über das binärsystem kann man aus jeder beliebigen Zahl die Zweierpotenzen und somit in unserem Beispiel die Rechte der User für die verschiedenen Bereiche extrahieren. PHP liefert uns für diese Aufgabe, wie schon erwähnt die Bit-Operatoren. In unserem Fall den &-Operator. Den mathematischen Hintergrund dafür bildet der binäre UND-Operator. 
Hoffe dass dir das geholfen hat das ganze etwas besser zu verstehen.
MfG, Andy


----------



## Klein0r (29. Juli 2008)

So müsste man aber genausoviele Bits zur verfügung haben wie man Bereiche hat... Ich denke eine Lösung mit User-Gruppen wär sicher einfacher und würde auch gut in das System passen.

Dann könnte man beispielsweise sagen:
Gruppe 1 = News schreiben
Gruppe 2 = Gästebucheinträge löschen, Downloads hinzufügen
Gruppe 3 = Dies und das...

Dann könnte man sich die Userrechte nach dem von Catull ausführlich beschriebenen Rechtesystem zusammenstellen...

Andererseits könnte man auch Userlevel nehmen - also wer Bit 2 Hat hat auch automatisch alle Userlevel darunter. Dafür müsste man aber nicht unbedingt Bitweise nehmen sondern könnte genausogut einfach Integerwerte nehmen... Und das ist hier ja nicht gefragt

Habe den Thread nicht komplett gelesen - hoffe ich wiederhole nicht zuviel 

lg


----------



## Bgag (11. August 2008)

Guten Abend!
Ich habe das komplette System nochmals überarbeitet und optimiert. leider funktioniert das gnaze noch nicht wirklich und daher möchte ich euch zunächst das neue System vorstellen und euch dann um Hilfe bitten.

Wie schon zuvor soll es zwei Tabellen, eine für die User-Daten und eine für die Daten der "privaten" Bereiche geben. Die User-Tabelle enthält also im einfachsten Fall _id_ | _name_ | _password_ | _permissions_. Die Tabelle für die "priavten" oder einfacher gesagt, für die geschützten Bereiche, enthält allerdings nun nur noch den _Namen_ des Bereichs und eine zugehörige _ID_.

Wie findet man allerdings nun mit dem binären Wert aus der Permissions-Spalte der User Tabelle heraus, ob ein Benutzer zugriff auf einen bestimmten Bereich hat oder nicht? Oder auf welche Bereiche er überhaupt zugriff hat? Die Lösung ist eigentlich einfach. Nimmt man die ID für einen bestimmten bereich in zum Beispiel einem CM-System und nutzt diesen als Exponent zur Basis zwei, so erhält man den binären Wert, den der User als Teil seines binären Wertes besitzen muss.

Möchte man nun also alle IDs der Bereiche herausfinden, zu denen ein User Zugriff besitz, kann man dies relativ einfach auszählen. Ein Beispiel:

Es gibt die Bereiche *bla*(_ID 1_), *lol*(_ID 2_), *omg*(_ID 3_) und *nop*(_ID 4_).
Möchte nun jemand zum Beispiel den benötigten Binärwert zu *bla* wissen, muss er nur 2^ID(lol) = 2^2 = 4 rechnen.
Möchte man also alle Bereiche wissen, zu denen ein Benutzer Zugriff besitzt, muss man einfach nur nach dem größten möglichen Exponenten zur Basis zwei suchen, bei dessen Subtraktion vom binären Permissions-Wert des Users nicht null herauskommt. Man durchläuft also eine Schleife solange, bis kein Restwert der Permissions mehr da ist. Gibt es also einen Nutzer *Klaus* mit dem binären Permissions-Wert *20* in meinem oben begonnenen pseudo CM-System, so setzen sich seine Werte wie folgt zusammen.
*P*(_Klaus_) = 2^*ID*(_nop_) + 2^*ID*(_lol_)
Der Benutzer Klaus hat also Zugriff auf die Bereiche nop und lol. 

In PHP kann man dies, wie schon gesagt mit einer Schleife leicht errechnen.

```
// permissions of Klaus
$perms = 20;

//start counter
$i = 0;

// initiate array of ids
$mods = array();

// extrahate specific permissions
do
{
    if( (($p-pow(2,$i)) < 0) )
    {
        $e = $i-1;
        $p = $p-pow(2,$e);
        $mods[] = $e;
        $i = 0;
    }

    else
    {
        $i++;
    }
}
while( (($p-pow(2,$i)) < 0)  && ($p > 0) )
```
Nach Durchlaufen der Schleife enthält das Array _$mods_ alle IDs zu den geschützten bereichen zu denen Klaus Zutritt besitzt, also 2 und 4.

Ich hoffe, dass das neue System klar geworden ist. Bei Fragen stehe ich natürlich gerne zur Verfügung und für Anregungen bin ich auch immer offen.

Doch, wie schon gesagt, funktioniert die umgeschriebene Klasse noch nicht ganz. Es tritt folgender Fehler auf und ich finde ihn einfach nicht, wodurch ich die Klasse auch noch nicht testen konnte.


```
Parse error: syntax error, unexpected T_RETURN, expecting ';' in /home/www/web193/html/permissions.php on line 180
```

Ich hoffe mir kann jemand helfen. Ich wäre sehr dankbar. Die betreffende Zeile ist übrigens die mit der _while()_-Schleife in der _getPerms()_-Methode.
MfG, Andy

*Übersicht über die Methoden*
___construct()_ - Setzt die wichtigen Variablen
_addMod()_ - Fügt einen geschützten Bereich hinzu
_delMod()_ - Entfernt einen geschützten Bereich
_getMods()_ - Gibt die IDs aller geschützter bereiche zurück
_getPerms()_ - Gibt die IDs zu den Bereichen zu denen ein Benutzer Zutritt besitzt zurück
_addUser()_ -  Fügt einen Benutzer hinzu
_check()_ - Überprüft die Zutritts-Berechtigung eines Nutzers
_getId()_ - Gibt die ID zu einem Bereich oder einem Nutzer anhand der ID oder des Namens zurück


----------



## Mairhofer (12. August 2008)

Hi,

schön das du hier weiter machst.
Um den Fehler zu beheben einfach ein Semikolon nach der while() Schleife setzen.

```
while( (($p-pow(2,$i)) < 0)  && ($p > 0) );
```
Grüsse


----------



## Bgag (12. August 2008)

Danke für den Tipp. Den Fehler hätte ich in den nächsten Stunden wohl nicht gefunden. So konnte ich nun wenigstens das Script testen, auch wenn es noch nicht wirklich funktioniert. Und zwar Funktioniert das Löschen eines Bereichs via _delMod()_ noch nicht und die IDs aller Bereiche können auch noch nicht via _getMods()_ zurückgegeben werden. Die Methode gibt zwar die erste ID korrekt zurück, aber nur die erste, obwohl sie, wie im Testaufruf am Ende des Scripts zu sehen ist, später noch weitere IDs von neuen Bereichen ausgeben soll. Ich vermute mal stark, dass mein Problem bei den Queries liegt, kann dies aber nicht mit Sicherheit sagen. Kann mir da noch jemand weiterhelfen?
MfG, Andy


----------



## Mairhofer (12. August 2008)

Hi,

da du mit mysqli arbeitest liegt es wohl an folgendem:

MySqli liefert als Ergebnis ein Object vom Typ Mysqli_result zurück.
Wenn du per $result->fetch_assoc() auf das Result Object zurückgreifst, wird nur das erste Ergebnis aus dem Objekt gezogen.

Möglich wäre

```
// same perms to array
$ids = array();
WHILE($row = $result->fetch_assoc()) {
   $ids[] = $row['id'];
}
```
Bei der delMod() Methode kann ich auf den ersten Blick keinen Fehler finden. Was geht denn genau nicht? Wird der Eintrag nicht gelöscht? Gibts nen Warning? Schonmal debugged ob der geparste SQL String (echo $sql) auch korrekt ist (z.b. mal in phpmyadmin testen)
Wenn ein Fehler im SQL ist ist $this->db->affected_rows == -1, würde prüfen ob es -1 (fehler in SQL) oder 0 ist (keine betroffenen Zeilen). Eventuell den Fehler per var_dump($this->db->error); ausgeben lassen nach dem ausführen von $this->db->query($sql);


----------



## Bgag (13. August 2008)

Dank deiner Hilfe konnte ich nun den Fehler in der Methode _getMods()_ behen. Zudem konnte ich herausfinden, dass der Fehler der Methode _delMod()_ garnicht in dieser Methode, sondern in der Methode _getId()_ liegt. Wo dort allerdings der Fehler versteckt ist weiß ich leider selbst nicht, allerdings gibt die Methode nicht, wie gewünscht eine ID zurück.
MfG, Andy

//EDIT: Leider fällt mir gerade auch auf, dass ich keine Benutzer via _addUser()_ erstellen kann. Kann mir jemand sagen, woran das liegt? Ich bekomme auch keinen Fehler. Er wird einfach nicht in die Datenbank eingetragen.


----------



## Bgag (22. August 2008)

Hallo!
Also erstes möchte ich mich für das doppelte Posting und das freudengeladene Blabla, das nun folgen wird entschuldigen. Des weiteren möchte ich mich bei den fleißigen Helfern nochmals bedanken. Die Klasse ist nun (in ihrem jetzigen Status) fehlerfrei. Nun ist natürlich die Frage, ob das alles so ausreichend ist. Darum würde mich nun interessieren, welche Methoden ihr in einer Klasse zur Rechte und Benutzerverwaltung noch missen würdet. Die folgenden Methoden sind bisher implementiert worden.

*Übersicht über die Methoden*
___construct()_ - Setzt die wichtigen Variablen
_addMod()_ - Fügt einen geschützten Bereich hinzu
_delMod()_ - Entfernt einen geschützten Bereich
_getMods()_ - Gibt die Daten aller geschützter Bereiche zurück (Id, Name)
_getPerms()_ - Gibt die IDs zu den Bereichen zu denen ein Benutzer Zutritt besitzt zurück
_addUser()_ - Fügt einen Benutzer hinzu
_check()_ - Überprüft die Zutritts-Berechtigung eines Nutzers
_getId()_ - Gibt die ID zu einem Bereich oder einem Nutzer anhand der ID oder des Namens zurück

Die Letzte Methode erscheint auf den ersten Blick sehr unsinnig, ist aber sehr praktisch. Sie ermöglicht es, dass man zum Beispiel beim Aufruf der Methode check entweder den Namen oder die Id eines Nutzers übergeben kann und dass man den Namen oder die Id eines geschützten Bereichs übergeben kann. So wird dem Nutzer die Arbeit erleichtert.

Ich würde mich also über Rückmeldungen und vorallem Anregungen freuen.
MfG, Andy


----------



## Mairhofer (22. August 2008)

Hi,

interessant wäre nochmal der aktualisierte Quellcode der Klasse.
Habe mich mal an dem letzten geposteten Quellcode gehalten, da fällt mir auf, das ich nicht wüsste, wie ich einem Benutzer die Berechtigungen für einen Bereich zuweisen kann.
Beispiel: 
- Ich kann User adden mit direktem "perm" wert
- Ich kann nur einen Bereich im ModTable adden

Wie kann ich nun einem User Zugriff auf diesen neuen Bereich gewähren? (editUser ?)
Und wie ist das, wenn ein Bereich via delmod() gelöscht wird? Werden dann auch die "perm" Werte in der Usertabelle der user aktualisiert die da mal zugriff hatten? 

Soweit erstmal, schau nochmal drüber wenn mehr zeit da ist


----------



## Loomis (22. August 2008)

Würde auch gerne mal den Code sehen


----------



## Bgag (22. August 2008)

Du hast Recht da fehlt noch was. Ich werde gleich mal drei weitere Methoden hinzufügen. Eine _editUser()_-Methode zum ändern der Benutzer-Daten, eine _calcPerms()_-Methode zum errechnen der neuen Rechte anhand eines Arrays, dass die Ids der zugriffsberechtigten Bereiche enthält, und eine Methode _getUser()_, die alle gespeicherten Daten eines Benutzers zurück gibt.

Zu deiner Frage ob ich die Permissions eines Users update, wenn ein Bereich gelöscht wird kann ich nur sagen - Nein. Wieso auch? Es ändert sich ja nichts dadurch. Das wird automatisch gemacht, Fügt man neue Rechte hinzu oder nimmt ihm welche.

Wie seht ihr das eigentlich mit dem Exception-Handling? Haltet ihr das in einer solchen Klasse für angebracht oder soll ich in einer solchen Klasse lieber mit Rückgabewerten arbeiten?

Den aktuellen Quellcode und eine SQL-Query zum Testen habe ich mal angehängt.
MfG, Andy

Die SQL-Datei zum testen, damit ihr nicht so viel Stress habt.

```
CREATE TABLE `permissions` (
  `id` tinyint(4) NOT NULL auto_increment,
  `name` varchar(30) NOT NULL default '',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=0;

INSERT INTO `permissions` VALUES (1, 'settings');
INSERT INTO `permissions` VALUES (2, 'articles');

CREATE TABLE `user` (
  `id` tinyint(4) NOT NULL auto_increment,
  `name` varchar(20) NOT NULL default '',
  `password` varchar(32) NOT NULL default '',
  `email` varchar(60) NOT NULL default '',
  `perm` tinyint(3) unsigned NOT NULL default '0',
  PRIMARY KEY  (`id`)
  UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=0;

INSERT INTO `user` VALUES (1, 'avedo', 'ddd6acc47c23379394ee7cdcbb4ea22b', 'andreas2209@web.de', 30);
INSERT INTO `user` VALUES (2, 'klaus', '0f5f53ea8fe0e8eeb72db72b47ddf351', 'klaus@avedo.net', 14);
```


----------



## Bgag (22. August 2008)

Guten Abend!
Ich habe die drei Methoden _calcPerms()_, _editUser()_ und _getUser()_ nun hinzugefügt, auch wenn ich gestehen muss, dass ich sie bisher noch nicht testen konnte. Dies werde ichaber noch nachholen.Nun ist es allerdings relativ einfach möglich die Zugriffsrechte eines Nutzers zu ändern. Als erstes holt man sich mit _getMods()_ alle existierenden Bereiche und dann mit _getPerms()_ die Ids zu den Bereichen, zu denen der bearbeitete Nutzer momentan Zugriffsrechte besitzt. Nun gibt man für alle Bereiche eine Checkbox aus, wobei man mit _in_array()_ überprüft, ob der Benutzer zum Zeitpunkt des erstellens der Checkboxen die Zugriffsrechte zu diesem Bereich besitzt. Ist dies der Fall wird die Checkbox als _checked_ erstellt. nach der Ausgabe sind nun also für alle geschützten Bereiche erstellt worden, wobei die  zu denen der Nutzer Zugriffsrechte besitzt makiert sind. Nun kann man die Zugriffsrechte durch markieren der Checkboxen beliebig ändern. Nach dem Absenden des Formulars steht also ein Array mit den Ids der Bereiche, zu denen der Benutzer zukünftig Zugriff haben soll, zur Verfügung. Mit der Methode _calcPerms()_ errechnet man nun den binären Wert, der in die Spalte permissions in die Benutzer-tabelle eingetragen werden soll. Dies geschieht dann über die Methode _editUser()_, der man einfach alle zu ändernden Felder und ihre zugehörigen Values übergibt.

Ich hoffe, dass dies deine Frage bezüglich der Eintragung neuer Rechte für einen User beantwortet, Mairhofer.

Wie gesagt, werde ich die drei neuen Methoden sobald es mir möglich ist noch testen. Vielleicht möchte es aber auch noch jemand anderes versuchen und mir eine Rückmeldung geben. Ich würde mich sehr freuen.
MfG, Andy

*//EDIT:* Werde noch die Methode _delUser()_ hinzufügen. _calcPerms()_ und _getUser()_ funktionieren hervorragend. _editUser()_ muss ich noch überarbeiten.

*//EDIT:* _delUser()_ wurde hinzugefügt!


----------



## ZodiacXP (22. August 2008)

Steinaltes Thema aber ich möcht einfach mein Senf dazu geben 

Wenn ich mal eine Rechteverwaltung auf binärer Basis mache dann definiere ich mir ein paar Sachen (damit der Code übersichtlich bleibt, auch für andere):


```
define('WRITE_FLAG', 8);
define('DELETE_FLAG', 4);
define('EDIT_FLAG', 2);
define('READ_FLAG', 1);
```

und dazu - weil es performativer ist als alles andere - die if:


```
if (($rights & WRITE_FLAG) == WRITE_FLAG) echo "schreibrechte";

if (($rights & DELETE_FLAG) == DELETE_FLAG) echo "löschrechte";

if (($rights & EDIT_FLAG) == EDIT_FLAG) echo "bearbeitungs... das wort gibt's nicht oder?";

if (($rights & READ_FLAG) == READ_FLAG) echo "lesen - mehr nich";
```

Vielleicht hilft dir das Catull deine Klasse etwas zu beschleunigen. Hab sie nur schnell überflogen und diese Bit-Operatoren nicht gesehen.
Das ändern der Rechte ist ja auch einfach. Das Ergebnis eines binären Oders zweier Rechte fügt diese beiden zusammen und (wenn ich nicht irre) kann man durch ein Exklusives Oder ein Recht wieder entfernen.


----------



## Bgag (22. August 2008)

Ich muss gestehen, dass ich deinen Einwand nicht im geringsten verstehe. Die von mir geschriebene Klasse beschäftigt sich mit ein paar mehr Rechten, als nur Lesen, Schreiben und Ausführen. Zudem können durch AddOns neue geschützte Bereiche hinzukommen. Daher macht es nur wenig sinn Konstanten zu definieren. Außerdem können über diese Klasse auch die Benutzer verwaltet werden. Die Bit-Operatoren kommen in der Methode check() ziemlich am Ende der Klasse zum Einsatz.
MfG, Andy


----------



## ZodiacXP (22. August 2008)

Die Konstanten waren nur als Beispiel / zum Verdeutlichen.

(ich hab ein  deutsch heute drauf... )

Und da ich dachte das du auch ein Fan der Performance bist, hab ich mal bisschen was geschrieben. Is Freitagabend, die Doppelkopfrunde is grad vorbei und das Bier is alle ^ ^ was soll ich sonst tun?  Langeweile... 

Die Operatoren am Ende der Klasse hab ich wohl übersehen.
Aber im Moment überlege ich ob man die do-Schleife in Permissions::getPerms vielleicht schneller machen kann indem man nicht $i und ein pow benutzt sondern eine Verschiebung (>>) i-wie nutzt.

Eigentlich sollte ich auch besser pennen gehen 
Jut Nacht.

PS - Nur ne Idee - bin zu voll um zu gucken ob das klappt:

```
$i = 1;
do
        {
            if( ($perm & $i) = $i )
            {
                $mods[] = $i;
            }
            $i <<= 1;
        }
        while( $perm >= $i );
```


----------



## Bgag (28. August 2008)

Also ich habs mal probiert, Fehler sind drin, die kann man beheben, aber ich weiß nichts mit Zeile 8 anzufangen. In Zeile 4 müsste == stehen.
MfG, Andy

*//EDIT:* Ich überlege übrigens ob ich anstatt Exceptions zu werfen, die Fehler mit loggen sollte, damit man sie besser in das jeweilige System integrieren kann. Was haltet ihr davon?


----------



## ZodiacXP (28. August 2008)

Wow. Sorry ersma das ich abends im dichten kopp hier rumgetextet hab.
Daher kommt auch der grobe Patzer in Zeile 4 

Zeile 8: $i <<= 1; bewirkt das die Zahl, die in $i ist mit 2 multipliziert wird - binäre Verschiebung. Quasi ist:

$i = pow(2, $exponent);

das gleiche wie: 

$i = 1 << $exponent;

(für alle $exponent > 0) 
Vorteil ist das keine Funktion sich drum kümmern muss sondern binär alles stumpf verschoben wird.


----------



## Bgag (28. August 2008)

Als erstes mal vielebn vielen Dank für deine Hilfe. Der Schnipsel ist super und er funktioniert auch hervorragend. Dank deiner kurzen Erklärung konnte ich ihn nun auch getrost in die Methode integrieren (hasse es, wenn ich meine eigenen Scripte nicht mehr verstehe). Gut dass du noch mit vollem Kopf noch soetwas hinbekommst.  Hast du vielleicht noch irgendwelche Tipps für mich, wie ich die Klasse weiter optimieren könnte? Kleine Optimierungen im Bereich Performance oder weitere Methoden, die die Arbeit etwas erleichtern?

Achso und dann habe ich noch ein kleines Problem. Wie bekomme ich bei der Methode _editUser()_ die Daten aus den Arrays _$fields_ und _$values_ am geschicktesten in das UPDATE-Query?
MfG, Andy


----------



## ZodiacXP (28. August 2008)

Habe zunächst die Klasse angesehen und alles was mir sofort an Performancebremsen da ist umgeschrieben. Ich war so frei auch kleinste Millisekunden zu beachten  Wirkt penibel - ist aber nicht so gemeint. Alles optionale Änderungen.
Siehe dir dazu die Kommentare an, hab mich in den letzten 24h schonmal woanders gut geirrt 


```
<?php
class Permissions
{
    private $db;
    /*
     * schon mal sagen das es ein string wird
     * sonst prüft er den datentyp beim setzen
     */
    private $userTab = "";
    private $modTab = "";

    public function __construct(mysqli $db, $userTab, $modTab)
    {
        $this->db = $db;
        $this->modTab = trim($modTab);
        $this->userTab = trim($userTab);
    }

    public function addMod($name)
    {
        /*
         * $var = "text" . $andere . "text"
         * ist n tacken schneller
         *
         * Und ID ist bestimmt auto_increment.
         * weglassen in PHP und mySQL die arbeit
         * überlassen ;)
         */
        $sql = "INSERT INTO " . $this->modTab . "
                (`name`)
            VALUES
                ('" . $name . "')";


        $this->db->query($sql);

        /*
         * deprecated : if($this->db->affected_rows <= 0)
         * errno ist 0 (somit FALSE) wenn alles glatt
         * läuft. besser als ihn zählen zu lassen
         */
        if($this->db->errno)
        {
            throw new Exception("Cannot create section " . $name);
        }

        return true;
    }

    public function delMod($ident)
    {
        $id = $this->getId($ident, $this->modTab);

        /**
         * string "..." . $var . "..."
         * siehe oben
         */
        $sql = "DELETE
            FROM
                " . $this->modTab . "
            WHERE
                id = " . $id;

        $this->db->query($sql);

        /**
         * siehe oben
         */
        if($this->db->errno)
        {
        	/**
        	 * $id? hab eigentlich $ident erwartet ^^
        	 */
            throw new Exception("Cannot delete Mod ".$id);
        }

        return true;
    }

    public function getMods()
    {
    	/**
    	 * kennst ja nun die tricks und
    	 * die schleife:
    	 * 		$mods[] = $row;
    	 * das übernimmt [id] und [name]
    	 * von $row und brauch kein zähler
    	 */
    }

    public function addUser($fields = array(), $values = array())
    {
        /*
         * nun wird die größe öfter gebraucht
         * daher in variable speichern
         */
        $i = count($fields);
        $j = count($values);
        /*
         * hab mal gelesen === ist 10% schneller
         * geht hier gut da der typ passt
         */
        if( $i === $j)
        {
        	$sql = "INSERT INTO " . $this->userTab . " (";
        	/*
			 * die trinität ()? : ; ist
        	 * langsamer als ein if
        	 */
        	if ($i)
        	{
        		/*
        		 * nicht konform aber " geht
        		 * immernoch schneller als '
        		 */
        		$sql .= "`" . implode("`, `", $fields) . "`";
        		/*
        		 * so bleibt $fields auch ein array
        		 * und musst nicht umgecastet werden
        		 * auf einen string - was wieder zeit
        		 * brauch
        		 */
        	}
        	$sql .= ") VALUES (";

        	if ($j) {
				$sql .= "'" . implode("', '", $values) . "'";
        	}
        	$sql .= ")";
        }

        else
        {
            throw new Exception("Invalid user data.");
        }

        $this->db->query($sql);

        /*
         * siehe oben
         */
        if($this->db->errno)
        {
            throw new Exception("Cannot create user.");
        }
    }

    /*
     * bei den folgenden funktionen reichen
     * die oben erklärten tricks:
     * delUser($ident)
     * getUser($ident)
     */

    public function getPerms($ident)
    {
        $id = $this->getId($ident, $this->userTab);

        // $sql = ...... - übliche optimierung durch "..." . $var . "..."

        if ( $result = $this->db->query($sql) )
        {
            /*
             * im query ist nur perm gefragt
             * von daher reicht die schnellste
             * variante zum auslesen: fetch_row
             */
            $array = $result->fetch_row();

            // get perm from array
            $perm = $array[0];
        }

        else
        {
            throw new Exception("Cannot get permissions of user " . $userId);
        }

        /*
         * hier die schon optimierte
         * schleife
         */

        return $mods;
    }

    public function calcPerms($mods)
    {
        $perms = 0;

        // create binary permission data
        foreach($mods as $id)
        {
            /*
             * binärer ODER-Vergleich
             * Bsp. einer hat recht 4 und 8:
             * Recht 4 -> 0100
             * ODER
             * Recht 8 -> 1000
             * ergibt
             * Recht 12-> 1100
             * usw.
             */
            $perms |= $id;
        }

        return $perms;
    }


    public function check($user, $mod)
    {
        $userId = $this->getId($user, $this->userTab);

        // bei $sql = ... noch übliche sache mit dem string machen:

        if ( $result = $this->db->query($sql) )
        {
            /*
             * wieder mit fetch_row()
             */
            $array = $result->fetch_row();

            // get perm from array
            $perms = $array[0];
        }

        else
        {
            throw new Exception("Cannot get permissions of user {$userId}.");
        }

        /*
         * ein bisschen variablen
         * und speicher sparen:
         */
        $perms >>= $this->getId($mod, $this->modTab);
        /*
         * nun steht das recht was man
         * prüfen will ganz "vorn"
         * im binären.
         * bsp. :
         * recht 12-> 1100
         * hat er recht 4? ($this->getId liefert 2)
         * 12 >> 2 -> 110
         * ist am ende eine 1?
         * 110 & 1 -> 0 (FALSE)
         */

        /*
         * true, false macht der sich schon
         * wieder eine trinität weniger ;)
         */
        return $perms & 1;
    }

    private function getId($ident, $table)
    {
        if( is_numeric($ident) )
        {
        	// direkt rausspringen
            return $ident;
        }
        else
        {
            // übliche sache noch mit string machen
            $sql = "SELECT
                    `id`
                FROM
                    {$table}
                WHERE
                    name = '{$ident}'
                LIMIT 1";

            if ( $result = $this->db->query($sql) )
            {
                // fetch_row
                $array = $result->fetch_row();

                // direkt return
                return $array[0];
            }

            else
            {
                return false;
            }
        }
    }
}

?>
```

woah. heidenarbeit.
alles funktion für funktion testen 
hab da n komisches gefühl im bauch :suspekt:

Es gibt noch einen optimierungsschritt für die letzte Funktion.
Sie ist private und gibt nur eine Zahl wieder. Wenn du das erste Argument als Referenz machst
und dementsprechend behandelst in allen anderen Funktionen sparst du wieder etwas speicher und rechenzeit.


----------



## Bgag (28. August 2008)

Wow Danke! Ich werde es sobald ich Zeit habe nochmal alles testen. Ich habe das ganze schon einmal überflogen und das ganze sieht sehr vielversprechend aus. Also vielen Dank für deine Mühen.

Wegen der _editUser()_-Methode habe ich mir überlegt, dass man ja einfach beide Arrays auf einmal in einer for()-Schleife durchlaufen könnte und somit das Query erstellen könnte. Was meinst du?
MfG, Andy


----------



## ZodiacXP (28. August 2008)

Ehrlich gesagt weis ich nicht so recht was du mit editUsers noch besonderes vor hast.
Für meinen Teil würde ich es genau so wie AddUser anlegen nur statt INSERT ein UPDATE.


----------



## Bgag (29. August 2008)

Guten Morgen!
Also ich habe die Klasse nun mal überarbeitet. Es waren zwar ein paar Fehler drin, die aber sehr einfach zu beheben waren. Leider gibt es nun doch zwei Probleme. Die Methode _editUser()_ erzeugt einen Fehler, obwohl der entsprechende Nutzer existiert und die Methode _calcPerms()_ errechnet völlig falsche Werte. Gibt man ihr die Ids der Bereiche 1,2,3,4 gibt sie als Ergebnis 7 zurück. Richtig wäre 2 + 4 + 8 + 16 = 30. Woran liegt das?
MfG, Andy


----------



## ZodiacXP (29. August 2008)

Achso sollte das ^^
Weil in der 7 sind alle Rechts quasi drin:
rechte: 1|2|3|4
7 binär->1111 (alle "an")
Das hab ich zunächst unter binär verstanden

Wenn du die 30 haben willst das foreach entsprechend abändern:

```
foreach($mods as $id)
  {
    // create mod perms
    $perms += 1 << $id;
  }
```

Joa. Das dürfte das sein. Ist gleichbedeutent mit $perms += pow(2,$id); nur schneller.


----------



## Gumbo (29. August 2008)

Die Erklärung ist eigentlich simpel:
	
	
	



```
0001
ODER 0010
ODER 0011
ODER 0100
   = 0111
```
Was du brauchst:
	
	
	



```
$perms += 1 << $id;
```


----------



## Bgag (29. August 2008)

Gumbo hat den Fehler gefunden. Stimmt jetzt. Allerdings funktioniert die Methode _editUser()_ noch nicht und ich weiß auch nicht wieso. Muss ich das ganze vielleicht irgendwie so abändern?
MfG, Andy


```
/**
    * editUser() - Changes the data of an user
    *
    * @access public
    * @param Str $user
    * @param Arr $fields
    * @param Arr $values
    * @return NONE
    */
    public function editUser($user, $fields = array(), $values = array())
    {
        // get id of the user
        $id = $this->getId($user, $this->userTab);
        
        // check count of fields and values
        if( count($fields) === count($values))
        {
            // create sql-query
            $sql = "UPDATE " . $this->userTab;
            
            $sql .= " SET (";

            // update values
            for($i=0; $i < count($fields); $i++)
            {
                 $sql .= $fields[$i] . "'" . $values[$i] . "'";
            }
            
            $sql .= ") ";
            
            $sql .= "WHERE id = '" . $id . "'";
            
            echo $sql;
        }

        else
        {
            throw new Exception("Invalid user data.");
        }
        
        // edit user data
        $this->db->query($sql);

        // check result
        if( $this->db->errno )
        {
            throw new Exception("Cannot change user data.");
        }
    }
```


----------



## ZodiacXP (29. August 2008)

Sieht vielversprechend aus. 
Nur vermisse ich Gleichheits-Zeichen in deiner UPDATE-Anweisung.

Wenn du wieder ein paar Millisekunden haben willst ein Vorschlag für die for:

```
for($i=count($fields); $i != 0; $i--)
```
oder

```
$i = count($fields);
for($i=$i; $i != 0; $i--)
```

Fällt dem Parser einfacher etwas mit 0 zu vergleichen und die Größe wird nicht ständig neu berechnet.


```
$i = count($fields);
```
 kannst du sogar ganz am Anfang deiner Funktion machen und schon für die if verwenden. Dort ist das count($fields) ja auch, wäre es nicht doppelt berechnet.


----------



## Bgag (29. August 2008)

Ok jetzt funktioniert es. Allerdings musste es wie folgt heißen, da PHP ja von 0 an zählt.

```
for($i = $i-1; $i >= 0; $i--)
```
Zudem mussten die Klammern bei SET weg.
MfG, Andy


```
<?php

/***
* Class Permissions
* 
* The Permissions class enables the easy management 
* of access rights an user data. Reading and editting 
* access rights to secured areas and the creation
* of such secured areas is one part of this class.
* On the other hand this class accounts for the 
* creation and editting of user data.
* ZodiacXP from tutorials.de optimized the performance 
* of the class. Thank you for your help.
*
* @package Permissions
* @version 0.5
* @author Andreas Wilhelm <Andreas2209@web.de>
* @copyright Andreas Wilhelm
**/
class Permissions
{
    // private class variables
    private $db;
    private $userTab = "";
    private $modTab = "";

    /**
    * Constructor - Is called when the class is instanced
    *
    * @access public
    * @param Obj $db
    * @param Str $userTab
    * @param Str $modTab
    * @return NONE
    */
    public function __construct(mysqli $db, $userTab, $modTab)
    {
        // save parameters to class variables
        $this->db = $db;
        $this->modTab = trim($modTab);
        $this->userTab = trim($userTab);
    }

    /**
    * addMod() - Adds a new protected section
    *
    * @access public
    * @param Str $name
    * @return Boolean
    */
    public function addMod($name)
    {
        // add new section
        $sql = "INSERT INTO " . $this->modTab . "
                (`name`)
            VALUES
                ('" . $name . "')";

        // send sql-query
        $this->db->query($sql);

        // check result
        if( $this->db->errno )
        {
            throw new Exception("Cannot create section " . $name);
        }

        return true;
    }

    /**
    * delMod() - Removes section from table
    *
    * @access public
    * @param Mix $ident
    * @return Boolean
    */
    public function delMod($ident)
    {
        // get id of the section
        $id = $this->getId($ident, $this->modTab);
        
        // delete section
        $sql = "DELETE
            FROM
                " . $this->modTab . "
            WHERE
                id = " . $id;

        // send sql-query
        $this->db->query($sql);

        // check result
        if( $this->db->errno )
        {
            throw new Exception("Cannot delete Mod " . $ident);
        }

        return true;
    }

    /**
    * getMods() - Returns all section-ids
    *
    * @access public
    * @return Array
    */
    public function getMods()
    {
        // get section-ids
        $sql = "SELECT
                *
            FROM
                " . $this->modTab;

        // send sql-query
        $result = $this->db->query($sql);

        // check result
        if(!$result)
        {
            throw new Exception("Cannot get sections.");
        }
        
        // save perms to array
        $mods = array();
        while( $row = $result->fetch_assoc() ) 
        {
            $mods[] = $row;
        }

        return $mods;
    }

    /**
    * addUser() - Updates the permissions of an user
    *
    * @access public
    * @param Arr $fields
    * @param Arr $values
    * @return NONE
    */
    public function addUser($fields = array(), $values = array())
    {
        // save size of arrays
        $i = count($fields);
        $j = count($values);
        
        // check count of fields and values
        if( $i === $j)
        {
            // create sql-query
            $sql = "INSERT INTO " . $this->userTab . " (";
            
            if ($i)
            {
                // insert field names
                $sql .= "`" . implode("`, `", $fields) . "`";
            }
            
            $sql .= ") VALUES (";

            if ($j) 
            {
                // insert values
                $sql .= "'" . implode("', '", $values) . "'";
            }
            
            $sql .= ")";
        }

        else
        {
            throw new Exception("Invalid user data.");
        }
        
        // create an new user
        $this->db->query($sql);

        // check result
        if( $this->db->errno )
        {
            throw new Exception("Cannot create user.");
        }
    }

    /**
    * delUser() - Removes user from table
    *
    * @access public
    * @param Mix $ident
    * @return Boolean
    */
    public function delUser($ident)
    {
        // get id of the section
        $id = $this->getId($ident, $this->userTab);
        
        // delete section
        $sql = "DELETE
            FROM
                " . $this->userTab . "
            WHERE
                id = " . $id;

        // send sql-query
        $this->db->query($sql);

        // check result
        if( $this->db->errno )
        {
            throw new Exception("Cannot delete user " . $ident);
        }

        return true;
    }

    /**
    * editUser() - Changes the data of an user
    *
    * @access public
    * @param Str $user
    * @param Arr $fields
    * @param Arr $values
    * @return NONE
    */
    public function editUser($user, $fields = array(), $values = array())
    {
        // get id of the user
        $id = $this->getId($user, $this->userTab);
        
        // get number of entries
        $i = count($fields);
        $j = count($values);
        
        // check count of fields and values
        if( $i === $j )
        {
            // create sql-query
            $sql = "UPDATE " . $this->userTab;
            
            $sql .= " SET ";

            // update values
            for($i = $i-1; $i >= 0; $i--)
            {
                 $sql .= $fields[$i] . " = '" . $values[$i] . "'";
            }
            
            $sql .= " WHERE id = '" . $id . "'";
        }

        else
        {
            throw new Exception("Invalid user data.");
        }
        
        // edit user data
        $this->db->query($sql);

        // check result
        if( $this->db->errno )
        {
            throw new Exception("Cannot change user data.");
        }
    }  

    /**
    * getUser() - Returns data of the given user
    *
    * @access public
    * @param Mix $ident
    * @return Array
    */
    public function getUser($ident)
    {
        // get id of the user
        $id = $this->getId($ident, $this->userTab);

        // get user data
        $sql = "SELECT
                *
            FROM
                " . $this->userTab . "
            WHERE
                id = '" . $id . "'
            LIMIT 1";

        // send sql-query
        $result = $this->db->query($sql);

        // check result
        if(!$result)
        {
            throw new Exception("Cannot get " . $ident);
        }
        
        // save user data to array
        $user = array();
        while( $row = $result->fetch_assoc() ) 
        {
            $user[] = $row;
        }

        return $user;
    } 

    /**
    * getPerms() - Returns all rights of an user
    *
    * @access public
    * @param Mix $ident
    * @return Array
    */
    public function getPerms($ident)
    {
        // get id of the user
        $id = $this->getId($ident, $this->userTab);

        // get permissions of the user
        $sql = "SELECT
                `perm`
            FROM
                " . $this->userTab . "
            WHERE
                id = " . $id . "
            LIMIT 1";
        
        // send sql-query
        if ( $result = $this->db->query($sql) ) 
        {
            // save result into an array
            $array = $result->fetch_row();

            // get perm from array
            $perm = $array[0];
        }
        
        else
        {
            throw new Exception("Cannot get permissions of user " . $ident);
        }

        //start counter
        $i = 0;

        // initiate array of ids
        $mods = array();

        // extrahate specific permissions
        $i = 1;
        do
        {
            if( ($perm & $i) == $i )
            {
                $mods[] = $i;
            }
            
            $i <<= 1;
        }
        while( $perm >= $i );  

        return $mods;
    }

    /**
    * calcPerms() - Calculates permission integer of an user
    *
    * @access public
    * @param Arr $mods
    * @return Integer
    */
    public function calcPerms($mods)
    {
        // create permission string
        $perms = 0;
    
        // create binary permission data
        foreach($mods as $id)
        {
            // create mod perms
            $perms += 1 << $id; 
        }

        return $perms;
    }


    /**
    * check() - Checks access rights
    *
    * @access public
    * @param Mix $user
    * @param Mix $mod
    * @return Boolean
    */
    public function check($user, $mod)
    {
        // get id of the user
        $userId = $this->getId($user, $this->userTab);

        // get permissions of the user
        $sql = "SELECT
                `perm`
            FROM
                " . $this->userTab . "
            WHERE
                id = " . $userId . "
            LIMIT 1";
        
        // send sql-query
        if ( $result = $this->db->query($sql) ) 
        {
            // save result into an array
            $array = $result->fetch_row();
                
            // get perm from array
            $perms = $array[0];
        }
        
        else
        {
            throw new Exception("Cannot get permissions of " . $user);
        }

        // get needed perms
        $perms >>= $this->getId($mod, $this->modTab);

        return $perms & 1;
    }

    /**
    * getId() - Returns the id to a user or secured area 
    *
    * @access private
    * @param Mix $ident
    * @param Str $table
    * @return Integer
    */
    private function getId($ident, $table)
    {
        // check if identifier is an integer
        if( is_numeric($ident) )
        {
            return $ident;
        }

        else
        {            
            // get id
            $sql = "SELECT 
                    `id`
                FROM
                    " . $table . "
                WHERE
                    name = '" . $ident . "'
                LIMIT 1";
        
            // send sql-query
            if ( $result = $this->db->query($sql) ) 
            {
                // save result into an array
                $array = $result->fetch_row();
                
                // get id from array
                return $array[0];
            }
        
            else
            {
                return false;
            }
        }
    }
}

?>
```


----------



## timmeyy (13. März 2009)

Hallo Leute!
ich bin neu hier und habe ein Frage die gut in diesen Thread passt und zwar möchte ich auch eine Nutzerverwaltung mit Rechtevergabe machen,
Mein Problem ist das ich nicht genau weiß wie ich die Recht zuordnen soll,dh. mache ich das mit einem Array so zum Bsp:

```
$rechte = array("1" => Ausführen,"2"=> schreiben,"4"=>lesen,"7"=>Admin );
```

oder anders?

mfg tim


----------



## ZodiacXP (13. März 2009)

Die 7 wäre bei einem binären Rechtesystem überflüssig.
Weil (z.B. binär):

```
1 - 0001 - Lesen
2 - 0010 - Schreiben
4 - 0100 - Ausführen

1 ODER 2 = 3 bzw.
  0001 
| 0010
= 0011 ->3

1 ODER 2 ODER 4 = 7 bzw.
  0001 
| 0010
| 0100
= 0111 -> 7
```

Lies dir ein Teil dieses Threads durch oder suche mal nach binärem Rechtesystem (z.B. Linux).


----------



## timmeyy (13. März 2009)

super danke dir!

mfg

Also ich habe jetzt folgende Rechte Tabelle erstellt

```
+----+-------------------+--------+
     | id | name              | rechte |
     +----+-------------------+--------+
  |  1 | ordner zugriff    |      1 |
  |  2 | Bilder upload     |      2 |
  |  3 | Ordner erstellen  |      4 |
  |  4 | Datenbank Zugriff |      8 |
 |  5 | settings          |     16 |
  +----+-------------------+--------+
```
und folgende Gruppen Tabelle:

```
+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| rights_id | int(10) unsigned | NO   | MUL | NULL    |                |
| name      | varchar(50)      | NO   |     | NULL    |                |
+-----------+------------------+------+-----+---------+----------------+
```

Wie kann ich denn jetzt den angelegten Gruppen die Rechte zu weißen?
Bsp der Admingruppe muss ich dem jetz das recht 31 geben? aber wie mach ich das per sql oder muss ich das mit php machen? also alle werte addieren und dann einfügen?

mfg


----------

