-
-
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) · 10.6 KB
/
authorization.texy
File metadata and controls
292 lines (210 loc) · 10.6 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
Preverjanje dovoljenj (Avtorizacija)
************************************
.[perex]
Avtorizacija ugotavlja, ali ima uporabnik zadostna dovoljenja, na primer za dostop do določenega vira ali za izvedbo neke akcije. Avtorizacija predpostavlja predhodno uspešno avtentikacijo, tj. da je uporabnik prijavljen.
→ [Namestitev in zahteve |@home#Namestitev]
V primerih bomo uporabljali objekt razreda [api:Nette\Security\User], ki predstavlja trenutnega uporabnika in do katerega pridete tako, da si ga pustite predati s pomočjo [dependency injection |dependency-injection:passing-dependencies]. V presenterjih je dovolj samo poklicati `$user = $this->getUser()`.
Pri zelo preprostih spletnih straneh z administracijo, kjer se ne razlikujejo dovoljenja uporabnikov, je mogoče kot avtorizacijski kriterij uporabiti že znano metodo `isLoggedIn()`. Z drugimi besedami: takoj ko je uporabnik prijavljen, ima vsa dovoljenja in obratno.
```php
if ($user->isLoggedIn()) { // je uporabnik prijavljen?
deleteItem(); // potem ima dovoljenje za operacijo
}
```
Vloge
-----
Namen vlog je ponuditi natančnejše upravljanje dovoljenj in ostati neodvisen od uporabniškega imena. Vsakemu uporabniku takoj ob prijavi dodelimo eno ali več vlog, v katerih bo nastopal. Vloge so lahko preprosti nizi, na primer `admin`, `member`, `guest`, ipd. Navajajo se kot drugi parameter konstruktorja `SimpleIdentity`, bodisi kot niz ali polje nizov - vlog.
Kot avtorizacijski kriterij bomo zdaj uporabili metodo `isInRole()`, ki pove, ali uporabnik nastopa v dani vlogi:
```php
if ($user->isInRole('admin')) { // je uporabnik v vlogi admina?
deleteItem(); // potem ima dovoljenje za operacijo
}
```
Kot že veste, se po odjavi uporabnika njegova identiteta ne izbriše nujno. Torej metoda `getIdentity()` še naprej vrača objekt `SimpleIdentity`, vključno z vsemi dodeljenimi vlogami. Nette Framework zagovarja načelo »manj kode, več varnosti«, kjer manj pisanja vodi k bolj varnemu kodu, zato pri ugotavljanju vlog ni treba še preverjati, ali je uporabnik prijavljen. Metoda `isInRole()` dela z **efektivnimi vlogami,** tj. če je uporabnik prijavljen, izhaja iz vlog, navedenih v identiteti, če ni prijavljen, ima samodejno posebno vlogo `guest`.
Avtorizator
-----------
Poleg vlog bomo uvedli še pojma vir in operacija:
- **vloga** je lastnost uporabnika - npr. moderator, urednik, obiskovalec, registriran uporabnik, skrbnik...
- **vir** (*resource*) je nek logični element spletne strani - članek, stran, uporabnik, postavka v meniju, anketa, presenter, ...
- **operacija** (*operation*) je neka konkretna dejavnost, ki jo uporabnik lahko ali ne more opravljati z virom - na primer izbrisati, urediti, ustvariti, glasovati, ...
Avtorizator je objekt, ki odloča, ali ima dana *vloga* dovoljenje za izvedbo določene *operacije* z določenim *virom*. Gre za objekt, ki implementira vmesnik [api:Nette\Security\Authorizator] z edino metodo `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;
}
}
```
Avtorizator dodamo v konfiguracijo [kot storitev |dependency-injection:services] DI vsebnika:
```neon
services:
- MyAuthorizator
```
In sledi primer uporabe. Pozor, tokrat kličemo metodo `Nette\Security\User::isAllowed()`, ne avtorizatorja, zato tam ni prvega parametra `$role`. Ta metoda kliče `MyAuthorizator::isAllowed()` postopoma za vse uporabnikove vloge in vrača true, če vsaj ena od njih ima dovoljenje.
```php
if ($user->isAllowed('file')) { // lahko uporabnik počne karkoli z virom 'file'?
useFile();
}
if ($user->isAllowed('file', 'delete')) { // lahko nad virom 'file' izvede 'delete'?
deleteFile();
}
```
Oba parametra sta neobvezna, privzeta vrednost `null` ima pomen *karkoli*.
Permission ACL
--------------
Nette prihaja z vgrajeno implementacijo avtorizatorja, in sicer razredom [api:Nette\Security\Permission], ki programerju ponuja lahko in fleksibilno ACL (Access Control List) plast za upravljanje dovoljenj in dostopov. Delo z njo temelji na definiciji vlog, virov in posameznih dovoljenj. Pri čemer vloge in viri omogočajo ustvarjanje hierarhij. Za pojasnilo si bomo pokazali primer spletne aplikacije:
- `guest`: neprijavljen obiskovalec, ki lahko bere in brska po javnem delu spletne strani, tj. bere članke, komentarje in voli v anketah
- `registered`: prijavljen registriran uporabnik, ki dodatno lahko komentira
- `admin`: lahko upravlja članke, komentarje in ankete
Definirali smo si torej določene vloge (`guest`, `registered` in `admin`) in omenili vire (`article`, `comment`, `poll`), do katerih lahko uporabniki z neko vlogo dostopajo ali izvajajo določene operacije (`view`, `vote`, `add`, `edit`).
Ustvarimo instanco razreda Permission in definiramo **vloge**. Pri tem lahko izkoristimo t.i. dedovanje vlog, ki zagotovi, da npr. uporabnik z vlogo administratorja (`admin`) lahko dela tudi to, kar navaden obiskovalec spletne strani (in seveda tudi več).
```php
$acl = new Nette\Security\Permission;
$acl->addRole('guest');
$acl->addRole('registered', 'guest'); // 'registered' deduje od 'guest'
$acl->addRole('admin', 'registered'); // in od njega deduje 'admin'
```
Zdaj definiramo tudi seznam **virov**, do katerih lahko uporabniki dostopajo.
```php
$acl->addResource('article');
$acl->addResource('comment');
$acl->addResource('poll');
```
Tudi viri lahko uporabljajo dedovanje, mogoče bi bilo na primer vnesti `$acl->addResource('perex', 'article')`.
In zdaj najpomembnejše. Med njimi definiramo pravila, ki določajo, kdo kaj lahko počne s čim:
```php
// najprej nihče ne more početi ničesar
// naj gost lahko pregleduje članke, komentarje in ankete
$acl->allow('guest', ['article', 'comment', 'poll'], 'view');
// in v anketah dodatno tudi glasuje
$acl->allow('guest', 'poll', 'vote');
// registrirani deduje pravice od gosta, damo mu dodatno pravico komentiranja
$acl->allow('registered', 'comment', 'add');
// administrator lahko pregleduje in ureja karkoli
$acl->allow('admin', $acl::All, ['view', 'edit', 'add']);
```
Kaj pa, če želimo nekomu **preprečiti** dostop do določenega vira?
```php
// administrator ne more urejati anket, to bi bilo nedemokratično
$acl->deny('admin', 'poll', 'edit');
```
Zdaj, ko imamo ustvarjen seznam pravil, lahko preprosto postavljamo avtorizacijska vprašanja:
```php
// lahko gost pregleduje članke?
$acl->isAllowed('guest', 'article', 'view'); // true
// lahko gost ureja članke?
$acl->isAllowed('guest', 'article', 'edit'); // false
// lahko gost glasuje v anketah?
$acl->isAllowed('guest', 'poll', 'vote'); // true
// lahko gost komentira?
$acl->isAllowed('guest', 'comment', 'add'); // false
```
Enako velja za registriranega uporabnika, ta pa lahko tudi komentira:
```php
$acl->isAllowed('registered', 'article', 'view'); // true
$acl->isAllowed('registered', 'comment', 'add'); // true
$acl->isAllowed('registered', 'comment', 'edit'); // false
```
Administrator lahko ureja vse, razen anket:
```php
$acl->isAllowed('admin', 'poll', 'vote'); // true
$acl->isAllowed('admin', 'poll', 'edit'); // false
$acl->isAllowed('admin', 'comment', 'edit'); // true
```
Dovoljenja se lahko tudi dinamično ocenjujejo in odločitev lahko prepustimo lastnemu callbacku, kateremu se predajo vsi parametri:
```php
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
return /* ... */;
};
$acl->allow('registered', 'comment', null, $assertion);
```
Kako pa na primer rešiti situacijo, ko ne zadostujejo samo imena vlog in virov, ampak bi želeli definirati, da na primer vloga `registered` lahko ureja vir `article` samo, če je njegov avtor? Namesto nizov bomo uporabili objekte, vloga bo objekt [api:Nette\Security\Role] in vir [api:Nette\Security\Resource]. Njihovi metodi `getRoleId()` oz. `getResourceId()` bosta vračali prvotne nize:
```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';
}
}
```
In zdaj ustvarimo pravilo:
```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);
```
In poizvedba na ACL se izvede s predajo objektov:
```php
$user = new Registered(/* ... */);
$article = new Article(/* ... */);
$acl->isAllowed($user, $article, 'edit');
```
Vloga lahko deduje od druge vloge ali od več vlog. Kaj pa se zgodi, če ima en prednik akcijo prepovedano in drugi dovoljeno? Kakšne bodo pravice potomca? Določa se glede na težo vloge - zadnja navedena vloga v seznamu prednikov ima največjo težo, prva navedena vloga pa najmanjšo. Bolj nazorno je to iz primera:
```php
$acl = new Nette\Security\Permission;
$acl->addRole('admin');
$acl->addRole('guest');
$acl->addResource('backend');
$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');
// primer A: vloga admin ima manjšo težo kot vloga guest
$acl->addRole('john', ['admin', 'guest']);
$acl->isAllowed('john', 'backend'); // false
// primer B: vloga admin ima večjo težo kot guest
$acl->addRole('mary', ['guest', 'admin']);
$acl->isAllowed('mary', 'backend'); // true
```
Vloge in vire je mogoče tudi odstraniti (`removeRole()`, `removeResource()`), mogoče je revertirati tudi pravila (`removeAllow()`, `removeDeny()`). Polje vseh neposrednih starševskih vlog vrača `getRoleParents()`, ali dve entiteti dedujeta druga od druge, vračata `roleInheritsFrom()` in `resourceInheritsFrom()`.
Dodajanje kot storitve
----------------------
Naš ustvarjeni ACL si moramo predati v konfiguracijo kot storitev, da ga začne uporabljati objekt `$user`, torej da bo mogoče uporabljati v kodi npr. `$user->isAllowed('article', 'view')`. V ta namen si zanj napišemo tovarno:
```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;
}
}
```
In jo dodamo v konfiguracijo:
```neon
services:
- App\Model\AuthorizatorFactory::create
```
V presenterjih lahko nato preverite dovoljenja na primer v metodi `startup()`:
```php
protected function startup()
{
parent::startup();
if (!$this->getUser()->isAllowed('backend')) {
$this->error('Prepovedano', 403);
}
}
```