-
-
Notifications
You must be signed in to change notification settings - Fork 277
Expand file tree
/
Copy pathauthorization.texy
More file actions
292 lines (210 loc) · 12.1 KB
/
authorization.texy
File metadata and controls
292 lines (210 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
Berechtigungsprüfung (Autorisierung)
************************************
.[perex]
Autorisierung stellt fest, ob ein Benutzer ausreichende Berechtigungen hat, beispielsweise um auf eine bestimmte Ressource zuzugreifen oder eine bestimmte Aktion auszuführen. Autorisierung setzt eine vorherige erfolgreiche Authentifizierung voraus, d.h., dass der Benutzer angemeldet ist.
→ [Installation und Anforderungen |@home#Installation]
In den Beispielen verwenden wir das Objekt der Klasse [api:Nette\Security\User], das den aktuellen Benutzer repräsentiert und auf das Sie zugreifen können, indem Sie es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. In Presentern genügt es, `$user = $this->getUser()` aufzurufen.
Bei sehr einfachen Websites mit Administration, bei denen keine Benutzerberechtigungen unterschieden werden, kann als Autorisierungskriterium die bereits bekannte Methode `isLoggedIn()` verwendet werden. Mit anderen Worten: Sobald ein Benutzer angemeldet ist, hat er alle Berechtigungen und umgekehrt.
```php
if ($user->isLoggedIn()) { // ist der Benutzer angemeldet?
deleteItem(); // dann hat er die Berechtigung für die Operation
}
```
Rollen
------
Der Sinn von Rollen besteht darin, eine präzisere Steuerung der Berechtigungen zu ermöglichen und unabhängig vom Benutzernamen zu bleiben. Jedem Benutzer weisen wir gleich bei der Anmeldung eine oder mehrere Rollen zu, in denen er auftreten wird. Rollen können einfache Zeichenketten sein, wie z. B. `admin`, `member`, `guest` usw. Sie werden als zweiter Parameter des Konstruktors `SimpleIdentity` angegeben, entweder als Zeichenkette oder als Array von Zeichenketten – Rollen.
Als Autorisierungskriterium verwenden wir nun die Methode `isInRole()`, die angibt, ob der Benutzer in der gegebenen Rolle auftritt:
```php
if ($user->isInRole('admin')) { // ist der Benutzer in der Rolle admin?
deleteItem(); // dann hat er die Berechtigung für die Operation
}
```
Wie Sie bereits wissen, muss nach der Abmeldung des Benutzers seine Identität nicht gelöscht werden. Das heißt, die Methode `getIdentity()` gibt weiterhin das Objekt `SimpleIdentity` zurück, einschließlich aller gewährten Rollen. Nette Framework folgt dem Prinzip „weniger Code, mehr Sicherheit“, bei dem weniger Schreiben zu sicherem Code führt. Daher müssen Sie bei der Überprüfung von Rollen nicht zusätzlich prüfen, ob der Benutzer angemeldet ist. Die Methode `isInRole()` arbeitet mit **effektiven Rollen**, d. h., wenn der Benutzer angemeldet ist, basiert sie auf den in der Identität angegebenen Rollen; wenn er nicht angemeldet ist, hat er automatisch die spezielle Rolle `guest`.
Autorisator
-----------
Neben Rollen führen wir noch die Begriffe Ressource und Operation ein:
- **Rolle** ist eine Eigenschaft des Benutzers - z. B. Moderator, Redakteur, Besucher, registrierter Benutzer, Administrator...
- **Ressource** (*resource*) ist ein logisches Element der Website - Artikel, Seite, Benutzer, Menüpunkt, Umfrage, Presenter, ...
- **Operation** (*operation*) ist eine bestimmte Tätigkeit, die der Benutzer mit der Ressource tun oder nicht tun darf - z. B. löschen, bearbeiten, erstellen, abstimmen, ...
Ein Autorisator ist ein Objekt, das entscheidet, ob eine gegebene *Rolle* die Berechtigung hat, eine bestimmte *Operation* mit einer bestimmten *Ressource* durchzuführen. Es handelt sich um ein Objekt, das das Interface [api:Nette\Security\Authorizator] mit einer einzigen Methode `isAllowed()` implementiert:
```php
class MyAuthorizator implements Nette\Security\Authorizator
{
public function isAllowed($role, $resource, $operation): bool
{
if ($role === 'admin') {
return true;
}
if ($role === 'user' && $resource === 'article') {
return true;
}
// ...
return false;
}
}
```
Den Autorisator fügen wir zur Konfiguration [als Dienst|dependency-injection:services] des DI-Containers hinzu:
```neon
services:
- MyAuthorizator
```
Und hier ist ein Anwendungsbeispiel. Achtung, diesmal rufen wir die Methode `Nette\Security\User::isAllowed()` auf, nicht den Autorisator direkt, daher fehlt der erste Parameter `$role`. Diese Methode ruft `MyAuthorizator::isAllowed()` nacheinander für alle Rollen des Benutzers auf und gibt `true` zurück, wenn mindestens eine davon die Berechtigung hat.
```php
if ($user->isAllowed('file')) { // darf der Benutzer irgendetwas mit der Ressource 'file' tun?
useFile();
}
if ($user->isAllowed('file', 'delete')) { // darf er über die Ressource 'file' die Operation 'delete' ausführen?
deleteFile();
}
```
Beide Parameter sind optional, der Standardwert `null` bedeutet *alles*.
Permission ACL
--------------
Nette kommt mit einer eingebauten Implementierung eines Autorisators, der Klasse [api:Nette\Security\Permission], die dem Programmierer eine leichte und flexible ACL (Access Control List) Schicht zur Verwaltung von Berechtigungen und Zugriffen bietet. Die Arbeit damit besteht darin, Rollen, Ressourcen und einzelne Berechtigungen zu definieren. Dabei ermöglichen Rollen und Ressourcen die Erstellung von Hierarchien. Zur Erklärung zeigen wir ein Beispiel einer Webanwendung:
- `guest`: nicht angemeldeter Besucher, der den öffentlichen Teil der Website lesen und durchsuchen kann, d.h. Artikel, Kommentare lesen und in Umfragen abstimmen kann
- `registered`: angemeldeter registrierter Benutzer, der zusätzlich kommentieren kann
- `admin`: kann Artikel, Kommentare und Umfragen verwalten
Wir haben also bestimmte Rollen (`guest`, `registered` und `admin`) definiert und Ressourcen (`article`, `comment`, `poll`) erwähnt, auf die Benutzer mit einer bestimmten Rolle zugreifen oder bestimmte Operationen (`view`, `vote`, `add`, `edit`) ausführen können.
Wir erstellen eine Instanz der Klasse `Permission` und definieren die **Rollen**. Dabei kann die sogenannte Rollenvererbung genutzt werden, die sicherstellt, dass z. B. ein Benutzer mit der Rolle Administrator (`admin`) auch das tun kann, was ein normaler Website-Besucher tun kann (und natürlich noch mehr).
```php
$acl = new Nette\Security\Permission;
$acl->addRole('guest');
$acl->addRole('registered', 'guest'); // 'registered' erbt von 'guest'
$acl->addRole('admin', 'registered'); // und davon erbt 'admin'
```
Nun definieren wir auch die Liste der **Ressourcen**, auf die Benutzer zugreifen können.
```php
$acl->addResource('article');
$acl->addResource('comment');
$acl->addResource('poll');
```
Auch Ressourcen können Vererbung verwenden, es wäre beispielsweise möglich, `$acl->addResource('perex', 'article')` anzugeben.
Und jetzt das Wichtigste. Wir definieren zwischen ihnen Regeln, die festlegen, wer was mit was tun darf:
```php
// zuerst darf niemand etwas tun
// Gast darf Artikel, Kommentare und Umfragen anzeigen
$acl->allow('guest', ['article', 'comment', 'poll'], 'view');
// und in Umfragen zusätzlich abstimmen
$acl->allow('guest', 'poll', 'vote');
// Registrierter Benutzer erbt Rechte vom Gast, geben wir ihm zusätzlich das Recht zu kommentieren
$acl->allow('registered', 'comment', 'add');
// Administrator kann alles anzeigen und bearbeiten
$acl->allow('admin', $acl::All, ['view', 'edit', 'add']);
```
Was ist, wenn wir jemandem den Zugriff auf eine bestimmte Ressource **verweigern** wollen?
```php
// Administrator kann Umfragen nicht bearbeiten, das wäre undemokratisch
$acl->deny('admin', 'poll', 'edit');
```
Nun, da wir die Liste der Regeln erstellt haben, können wir einfach Autorisierungsabfragen stellen:
```php
// darf guest Artikel anzeigen?
$acl->isAllowed('guest', 'article', 'view'); // true
// darf guest Artikel bearbeiten?
$acl->isAllowed('guest', 'article', 'edit'); // false
// darf guest in Umfragen abstimmen?
$acl->isAllowed('guest', 'poll', 'vote'); // true
// darf guest kommentieren?
$acl->isAllowed('guest', 'comment', 'add'); // false
```
Dasselbe gilt für einen registrierten Benutzer, dieser kann jedoch auch kommentieren:
```php
$acl->isAllowed('registered', 'article', 'view'); // true
$acl->isAllowed('registered', 'comment', 'add'); // true
$acl->isAllowed('registered', 'comment', 'edit'); // false
```
Der Administrator kann alles bearbeiten, außer Umfragen:
```php
$acl->isAllowed('admin', 'poll', 'vote'); // true
$acl->isAllowed('admin', 'poll', 'edit'); // false
$acl->isAllowed('admin', 'comment', 'edit'); // true
```
Berechtigungen können auch dynamisch ausgewertet werden, und wir können die Entscheidung einem eigenen Callback überlassen, dem alle Parameter übergeben werden:
```php
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
return /* ... */;
};
$acl->allow('registered', 'comment', null, $assertion);
```
Wie löst man aber beispielsweise eine Situation, in der die Namen von Rollen und Ressourcen nicht ausreichen, sondern wir definieren möchten, dass beispielsweise die Rolle `registered` die Ressource `article` nur bearbeiten darf, wenn sie ihr Autor ist? Anstelle von Zeichenketten verwenden wir Objekte, die Rolle ist ein Objekt, das [api:Nette\Security\Role] implementiert, und die Ressource ein Objekt, das [api:Nette\Security\Resource] implementiert. Ihre Methoden `getRoleId()` bzw. `getResourceId()` geben die ursprünglichen Zeichenketten zurück:
```php
class Registered implements Nette\Security\Role
{
public $id;
public function getRoleId(): string
{
return 'registered';
}
}
class Article implements Nette\Security\Resource
{
public $authorId;
public function getResourceId(): string
{
return 'article';
}
}
```
Und nun erstellen wir die Regel:
```php
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
$role = $acl->getQueriedRole(); // Objekt Registered
$resource = $acl->getQueriedResource(); // Objekt Article
return $role->id === $resource->authorId;
};
$acl->allow('registered', 'article', 'edit', $assertion);
```
Und die Abfrage an die ACL erfolgt durch Übergabe der Objekte:
```php
$user = new Registered(/* ... */);
$article = new Article(/* ... */);
$acl->isAllowed($user, $article, 'edit');
```
Eine Rolle kann von einer anderen Rolle oder von mehreren Rollen erben. Was passiert aber, wenn ein Vorfahre die Aktion verboten und ein anderer erlaubt hat? Welche Rechte hat der Nachkomme? Dies wird durch das Gewicht der Rolle bestimmt – die zuletzt in der Liste der Vorfahren angegebene Rolle hat das höchste Gewicht, die zuerst angegebene Rolle das niedrigste. Dies wird anhand eines Beispiels deutlicher:
```php
$acl = new Nette\Security\Permission;
$acl->addRole('admin');
$acl->addRole('guest');
$acl->addResource('backend');
$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');
// Fall A: Rolle 'admin' hat geringeres Gewicht als Rolle 'guest'
$acl->addRole('john', ['admin', 'guest']);
$acl->isAllowed('john', 'backend'); // false
// Fall B: Rolle 'admin' hat höheres Gewicht als 'guest'
$acl->addRole('mary', ['guest', 'admin']);
$acl->isAllowed('mary', 'backend'); // true
```
Rollen und Ressourcen können auch entfernt werden (`removeRole()`, `removeResource()`), Regeln können ebenfalls rückgängig gemacht werden (`removeAllow()`, `removeDeny()`). Das Array aller direkten Elternrollen gibt `getRoleParents()` zurück, ob zwei Entitäten voneinander erben, geben `roleInheritsFrom()` und `resourceInheritsFrom()` zurück.
Hinzufügen als Dienste
----------------------
Wir müssen unsere erstellte ACL als Dienst zur Konfiguration hinzufügen, damit das Objekt `$user` sie verwenden kann, d.h., damit wir im Code z. B. `$user->isAllowed('article', 'view')` verwenden können. Zu diesem Zweck schreiben wir eine Factory dafür:
```php
namespace App\Model;
class AuthorizatorFactory
{
public static function create(): Nette\Security\Permission
{
$acl = new Nette\Security\Permission;
$acl->addRole(/* ... */);
$acl->addResource(/* ... */);
$acl->allow(/* ... */);
return $acl;
}
}
```
Und fügen sie zur Konfiguration hinzu:
```neon
services:
- App\Model\AuthorizatorFactory::create
```
In Presentern können Sie dann die Berechtigungen beispielsweise in der Methode `startup()` überprüfen:
```php
protected function startup()
{
parent::startup();
if (!$this->getUser()->isAllowed('backend')) {
$this->error('Forbidden', 403);
}
}
```