-
-
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) · 11.4 KB
/
authorization.texy
File metadata and controls
292 lines (210 loc) · 11.4 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
Verificarea permisiunilor (Autorizare)
**************************************
.[perex]
Autorizarea verifică dacă un utilizator are permisiuni suficiente, de exemplu, pentru a accesa o anumită resursă sau pentru a efectua o anumită acțiune. Autorizarea presupune autentificarea prealabilă reușită, adică utilizatorul este conectat.
→ [Instalare și cerințe |@home#Instalare]
În exemple vom folosi obiectul clasei [api:Nette\Security\User], care reprezintă utilizatorul curent și la care ajungeți solicitându-l prin [injecția de dependențe |dependency-injection:passing-dependencies]. În presenteri este suficient doar să apelați `$user = $this->getUser()`.
Pentru site-uri web foarte simple cu administrare, unde permisiunile utilizatorilor nu sunt diferențiate, se poate folosi ca și criteriu de autorizare metoda deja cunoscută `isLoggedIn()`. Cu alte cuvinte: odată ce utilizatorul este conectat, are toate permisiunile și invers.
```php
if ($user->isLoggedIn()) { // este utilizatorul conectat?
deleteItem(); // atunci are permisiunea pentru operație
}
```
Roluri
------
Scopul rolurilor este de a oferi un control mai precis al permisiunilor și de a rămâne independent de numele de utilizator. Fiecărui utilizator, imediat după autentificare, i se atribuie unul sau mai multe roluri în care va acționa. Rolurile pot fi șiruri simple, de exemplu `admin`, `member`, `guest`, etc. Se specifică ca al doilea parametru al constructorului `SimpleIdentity`, fie ca șir, fie ca array de șiruri - roluri.
Ca și criteriu de autorizare vom folosi acum metoda `isInRole()`, care indică dacă utilizatorul acționează în rolul respectiv:
```php
if ($user->isInRole('admin')) { // este utilizatorul în rolul de admin?
deleteItem(); // atunci are permisiunea pentru operație
}
```
După cum știți deja, după deconectarea utilizatorului, identitatea sa nu trebuie neapărat ștearsă. Adică metoda `getIdentity()` returnează în continuare obiectul `SimpleIdentity`, inclusiv toate rolurile acordate. Nette Framework adoptă principiul „less code, more security”, unde mai puțin scris duce la un cod mai sigur, de aceea la verificarea rolurilor nu trebuie să verificați și dacă utilizatorul este conectat. Metoda `isInRole()` lucrează cu **roluri efective,** adică dacă utilizatorul este conectat, se bazează pe rolurile specificate în identitate, dacă nu este conectat, are automat rolul special `guest`.
Autorizator
-----------
Pe lângă roluri, vom introduce și conceptele de resursă și operație:
- **rol** este o proprietate a utilizatorului - de ex. moderator, redactor, vizitator, utilizator înregistrat, administrator...
- **resursă** (*resource*) este un element logic al site-ului - articol, pagină, utilizator, element de meniu, sondaj, presenter, ...
- **operație** (*operation*) este o activitate specifică pe care utilizatorul o poate sau nu o poate face cu resursa - de exemplu, șterge, edita, crea, vota, ...
Autorizatorul este un obiect care decide dacă *rolul* dat are permisiunea de a efectua o anumită *operație* cu o anumită *resursă*. Este un obiect care implementează interfața [api:Nette\Security\Authorizator] cu o singură metodă `isAllowed()`:
```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;
}
}
```
Adăugăm autorizatorul în configurație [ca serviciu |dependency-injection:services] al containerului DI:
```neon
services:
- MyAuthorizator
```
Și urmează exemplul de utilizare. Atenție, de data aceasta apelăm metoda `Nette\Security\User::isAllowed()`, nu autorizatorul, deci nu există primul parametru `$role`. Această metodă apelează `MyAuthorizator::isAllowed()` succesiv pentru toate rolurile utilizatorului și returnează true dacă cel puțin unul dintre ele are permisiunea.
```php
if ($user->isAllowed('file')) { // poate utilizatorul să facă orice cu resursa 'file'?
useFile();
}
if ($user->isAllowed('file', 'delete')) { // poate efectua 'delete' asupra resursei 'file'?
deleteFile();
}
```
Ambii parametri sunt opționali, valoarea implicită `null` are semnificația *orice*.
Permission ACL
--------------
Nette vine cu o implementare încorporată a autorizatorului, și anume clasa [api:Nette\Security\Permission], care oferă programatorului un strat ACL (Access Control List) ușor și flexibil pentru gestionarea permisiunilor și accesului. Lucrul cu aceasta constă în definirea rolurilor, resurselor și permisiunilor individuale. Rolurile și resursele permit crearea de ierarhii. Pentru a explica, vom arăta un exemplu de aplicație web:
- `guest`: vizitator neconectat, care poate citi și naviga partea publică a site-ului, adică citi articole, comentarii și vota în sondaje
- `registered`: utilizator înregistrat conectat, care în plus poate comenta
- `admin`: poate administra articole, comentarii și sondaje
Am definit deci anumite roluri (`guest`, `registered` și `admin`) și am menționat resurse (`article`, `comment`, `poll`), la care utilizatorii cu un anumit rol pot accesa sau efectua anumite operații (`view`, `vote`, `add`, `edit`).
Creăm o instanță a clasei Permission și definim **rolurile**. Se poate utiliza așa-numita moștenire a rolurilor, care asigură că, de exemplu, un utilizator cu rolul de administrator (`admin`) poate face și ceea ce poate face un vizitator obișnuit al site-ului (și, desigur, mai mult).
```php
$acl = new Nette\Security\Permission;
$acl->addRole('guest');
$acl->addRole('registered', 'guest'); // 'registered' moștenește de la 'guest'
$acl->addRole('admin', 'registered'); // și de la el moștenește 'admin'
```
Acum definim și lista de **resurse**, la care utilizatorii pot accesa.
```php
$acl->addResource('article');
$acl->addResource('comment');
$acl->addResource('poll');
```
Și resursele pot folosi moștenirea, ar fi posibil, de exemplu, să specificăm `$acl->addResource('perex', 'article')`.
Și acum cel mai important lucru. Definim între ele regulile care determină cine ce poate face cu ce:
```php
// la început, nimeni nu poate face nimic
// permite guest să vizualizeze articole, comentarii și sondaje
$acl->allow('guest', ['article', 'comment', 'poll'], 'view');
// și în sondaje, în plus, să voteze
$acl->allow('guest', 'poll', 'vote');
// registered moștenește drepturile de la guest, îi dăm în plus dreptul de a comenta
$acl->allow('registered', 'comment', 'add');
// administratorul poate vizualiza și edita orice
$acl->allow('admin', $acl::All, ['view', 'edit', 'add']);
```
Ce se întâmplă dacă vrem să **interzicem** cuiva accesul la o anumită resursă?
```php
// administratorul nu poate edita sondaje, ar fi nedemocratic
$acl->deny('admin', 'poll', 'edit');
```
Acum, când avem creată lista de reguli, putem pune simplu întrebări de autorizare:
```php
// poate guest să vizualizeze articole?
$acl->isAllowed('guest', 'article', 'view'); // true
// poate guest să editeze articole?
$acl->isAllowed('guest', 'article', 'edit'); // false
// poate guest să voteze în sondaje?
$acl->isAllowed('guest', 'poll', 'vote'); // true
// poate guest să comenteze?
$acl->isAllowed('guest', 'comment', 'add'); // false
```
Același lucru este valabil și pentru utilizatorul înregistrat, însă acesta poate și comenta:
```php
$acl->isAllowed('registered', 'article', 'view'); // true
$acl->isAllowed('registered', 'comment', 'add'); // true
$acl->isAllowed('registered', 'comment', 'edit'); // false
```
Administratorul poate edita totul, cu excepția sondajelor:
```php
$acl->isAllowed('admin', 'poll', 'vote'); // true
$acl->isAllowed('admin', 'poll', 'edit'); // false
$acl->isAllowed('admin', 'comment', 'edit'); // true
```
Permisiunile pot fi evaluate și dinamic și putem lăsa decizia pe seama unui callback propriu, căruia i se transmit toți parametrii:
```php
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
return /* ... */;
};
$acl->allow('registered', 'comment', null, $assertion);
```
Dar cum să rezolvăm, de exemplu, situația în care nu sunt suficiente doar numele rolurilor și resurselor, ci am dori să definim că, de exemplu, rolul `registered` poate edita resursa `article` doar dacă este autorul său? În loc de șiruri, vom folosi obiecte, rolul va fi obiectul [api:Nette\Security\Role] și resursa [api:Nette\Security\Resource]. Metodele lor `getRoleId()` respectiv `getResourceId()` vor returna șirurile originale:
```php
class Registered implements Nette\Security\Role
{
public $id;
public function getRoleId(): string
{
return 'registered';
}
}
class Article implements Resource
{
public $authorId;
public function getResourceId(): string
{
return 'article';
}
}
```
Și acum creăm regula:
```php
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
$role = $acl->getQueriedRole(); // obiectul Registered
$resource = $acl->getQueriedResource(); // obiectul Article
return $role->id === $resource->authorId;
};
$acl->allow('registered', 'article', 'edit', $assertion);
```
Și interogarea ACL se efectuează prin transmiterea obiectelor:
```php
$user = new Registered(/* ... */);
$article = new Article(/* ... */);
$acl->isAllowed($user, $article, 'edit');
```
Un rol poate moșteni de la alt rol sau de la mai multe roluri. Dar ce se întâmplă dacă un părinte are acțiunea interzisă și altul permisă? Care vor fi drepturile descendentului? Se determină după greutatea rolului - ultimul rol specificat în lista părinților are cea mai mare greutate, primul rol specificat are cea mai mică. Este mai clar din exemplu:
```php
$acl = new Nette\Security\Permission;
$acl->addRole('admin');
$acl->addRole('guest');
$acl->addResource('backend');
$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');
// cazul A: rolul admin are o greutate mai mică decât rolul guest
$acl->addRole('john', ['admin', 'guest']);
$acl->isAllowed('john', 'backend'); // false
// cazul B: rolul admin are o greutate mai mare decât guest
$acl->addRole('mary', ['guest', 'admin']);
$acl->isAllowed('mary', 'backend'); // true
```
Rolurile și resursele pot fi și eliminate (`removeRole()`, `removeResource()`), pot fi anulate și regulile (`removeAllow()`, `removeDeny()`). Array-ul tuturor rolurilor părinte directe este returnat de `getRoleParents()`, dacă două entități moștenesc una de la alta este returnat de `roleInheritsFrom()` și `resourceInheritsFrom()`.
Adăugarea ca servicii
---------------------
ACL-ul creat de noi trebuie să îl transmitem configurației ca serviciu, pentru ca obiectul `$user` să înceapă să îl folosească, adică să fie posibil să folosim în cod, de exemplu, `$user->isAllowed('article', 'view')`. În acest scop, vom scrie o fabrică pentru el:
```php
namespace App\Model;
class AuthorizatorFactory
{
public static function create(): Nette\Security\Permission
{
$acl = new Permission;
$acl->addRole(/* ... */);
$acl->addResource(/* ... */);
$acl->allow(/* ... */);
return $acl;
}
}
```
Și o adăugăm în configurație:
```neon
services:
- App\Model\AuthorizatorFactory::create
```
În presenteri puteți apoi verifica permisiunile, de exemplu, în metoda `startup()`:
```php
protected function startup()
{
parent::startup();
if (!$this->getUser()->isAllowed('backend')) {
$this->error('Forbidden', 403);
}
}
```