-
-
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.3 KB
/
authorization.texy
File metadata and controls
292 lines (210 loc) · 12.3 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
Vérification des autorisations (Autorisation)
*********************************************
.[perex]
L'autorisation détermine si un utilisateur dispose des autorisations suffisantes, par exemple pour accéder à une ressource spécifique ou pour effectuer une action donnée. L'autorisation présuppose une authentification préalable réussie, c'est-à-dire que l'utilisateur est connecté.
→ [Installation et prérequis |@home#Installation]
Dans les exemples, nous utiliserons l'objet de la classe [api:Nette\Security\User], qui représente l'utilisateur actuel et auquel vous pouvez accéder en le faisant passer via [l'injection de dépendances |dependency-injection:passing-dependencies]. Dans les presenters, il suffit d'appeler `$user = $this->getUser()`.
Pour les sites web très simples avec une administration, où les autorisations des utilisateurs ne sont pas différenciées, il est possible d'utiliser la méthode déjà connue `isLoggedIn()` comme critère d'autorisation. En d'autres termes : dès que l'utilisateur est connecté, il dispose de toutes les autorisations et inversement.
```php
if ($user->isLoggedIn()) { // l'utilisateur est-il connecté ?
deleteItem(); // alors il a l'autorisation pour l'opération
}
```
Rôles
-----
L'objectif des rôles est d'offrir un contrôle plus précis des autorisations et de rester indépendant du nom d'utilisateur. À chaque utilisateur, dès sa connexion, nous attribuons un ou plusieurs rôles dans lesquels il agira. Les rôles peuvent être de simples chaînes de caractères, par exemple `admin`, `member`, `guest`, etc. Ils sont indiqués comme deuxième paramètre du constructeur `SimpleIdentity`, soit comme une chaîne, soit comme un tableau de chaînes - rôles.
Comme critère d'autorisation, nous utiliserons maintenant la méthode `isInRole()`, qui indique si l'utilisateur agit dans le rôle donné :
```php
if ($user->isInRole('admin')) { // l'utilisateur est-il dans le rôle admin ?
deleteItem(); // alors il a l'autorisation pour l'opération
}
```
Comme vous le savez déjà, après la déconnexion de l'utilisateur, son identité ne doit pas nécessairement être supprimée. Ainsi, la méthode `getIdentity()` continue de renvoyer l'objet `SimpleIdentity`, y compris tous les rôles attribués. Le Nette Framework adhère au principe « less code, more security », où moins d'écriture conduit à un code plus sécurisé, donc lors de la vérification des rôles, vous n'avez pas besoin de vérifier en plus si l'utilisateur est connecté. La méthode `isInRole()` travaille avec les **rôles effectifs,** c'est-à-dire que si l'utilisateur est connecté, elle se base sur les rôles indiqués dans l'identité, s'il n'est pas connecté, il a automatiquement le rôle spécial `guest`.
Autorisateur
------------
En plus des rôles, nous introduirons également les concepts de ressource et d'opération :
- **rôle** est une propriété de l'utilisateur - par exemple, modérateur, rédacteur, visiteur, utilisateur enregistré, administrateur...
- **ressource** (*resource*) est un élément logique du site web - article, page, utilisateur, élément de menu, sondage, presenter, ...
- **opération** (*operation*) est une activité spécifique que l'utilisateur peut ou ne peut pas effectuer avec la ressource - par exemple, supprimer, modifier, créer, voter, ...
L'autorisateur est un objet qui décide si un *rôle* donné a la permission d'effectuer une *opération* spécifique sur une *ressource* donnée. Il s'agit d'un objet implémentant l'interface [api:Nette\Security\Authorizator] avec une seule méthode `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;
}
}
```
Nous ajoutons l'autorisateur à la configuration [en tant que service|dependency-injection:services] du conteneur DI :
```neon
services:
- MyAuthorizator
```
Et voici un exemple d'utilisation. Attention, cette fois nous appelons la méthode `Nette\Security\User::isAllowed()`, et non l'autorisateur, donc le premier paramètre `$role` n'est pas là. Cette méthode appelle `MyAuthorizator::isAllowed()` successivement pour tous les rôles de l'utilisateur et renvoie true si au moins l'un d'eux a la permission.
```php
if ($user->isAllowed('file')) { // l'utilisateur peut-il faire n'importe quoi avec la ressource 'file' ?
useFile();
}
if ($user->isAllowed('file', 'delete')) { // peut-il effectuer 'delete' sur la ressource 'file' ?
deleteFile();
}
```
Les deux paramètres sont facultatifs, la valeur par défaut `null` signifie *n'importe quoi*.
Permission ACL
--------------
Nette fournit une implémentation intégrée de l'autorisateur, la classe [api:Nette\Security\Permission] qui fournit au programmeur une couche ACL (Access Control List) légère et flexible pour gérer les autorisations et les accès. Son utilisation consiste à définir des rôles, des ressources et des autorisations individuelles. Les rôles et les ressources permettent de créer des hiérarchies. Pour expliquer, prenons l'exemple d'une application web :
- `guest` : visiteur non connecté, qui peut lire et parcourir la partie publique du site, c'est-à-dire lire les articles, les commentaires et voter aux sondages
- `registered` : utilisateur enregistré connecté, qui peut en plus commenter
- `admin` : peut gérer les articles, les commentaires et les sondages
Nous avons donc défini certains rôles (`guest`, `registered` et `admin`) et mentionné des ressources (`article`, `comment`, `poll`), auxquelles les utilisateurs avec un certain rôle peuvent accéder ou effectuer certaines opérations (`view`, `vote`, `add`, `edit`).
Nous créons une instance de la classe Permission et définissons les **rôles**. Il est possible d'utiliser l'héritage de rôles, qui garantit que, par exemple, un utilisateur avec le rôle d'administrateur (`admin`) peut faire ce qu'un visiteur ordinaire du site peut faire (et bien sûr plus).
```php
$acl = new Nette\Security\Permission;
$acl->addRole('guest');
$acl->addRole('registered', 'guest'); // 'registered' hérite de 'guest'
$acl->addRole('admin', 'registered'); // et 'admin' hérite de lui
```
Maintenant, définissons également la liste des **ressources** auxquelles les utilisateurs peuvent accéder.
```php
$acl->addResource('article');
$acl->addResource('comment');
$acl->addResource('poll');
```
Les ressources peuvent également utiliser l'héritage, il serait possible par exemple de spécifier `$acl->addResource('perex', 'article')`.
Et maintenant, le point le plus important. Définissons entre eux les règles déterminant qui peut faire quoi avec quoi :
```php
// d'abord, personne ne peut rien faire
// que guest puisse consulter les articles, les commentaires et les sondages
$acl->allow('guest', ['article', 'comment', 'poll'], 'view');
// et dans les sondages, en plus, voter
$acl->allow('guest', 'poll', 'vote');
// l'utilisateur enregistré hérite des droits de guest, donnons-lui en plus le droit de commenter
$acl->allow('registered', 'comment', 'add');
// l'administrateur peut consulter et modifier n'importe quoi
$acl->allow('admin', $acl::All, ['view', 'edit', 'add']);
```
Et si nous voulons **empêcher** quelqu'un d'accéder à une ressource spécifique ?
```php
// l'administrateur ne peut pas modifier les sondages, ce serait antidémocratique
$acl->deny('admin', 'poll', 'edit');
```
Maintenant que nous avons créé la liste des règles, nous pouvons simplement poser des questions d'autorisation :
```php
// guest peut-il consulter les articles ?
$acl->isAllowed('guest', 'article', 'view'); // true
// guest peut-il modifier les articles ?
$acl->isAllowed('guest', 'article', 'edit'); // false
// guest peut-il voter aux sondages ?
$acl->isAllowed('guest', 'poll', 'vote'); // true
// guest peut-il commenter ?
$acl->isAllowed('guest', 'comment', 'add'); // false
```
La même chose s'applique à l'utilisateur enregistré, mais il peut aussi commenter :
```php
$acl->isAllowed('registered', 'article', 'view'); // true
$acl->isAllowed('registered', 'comment', 'add'); // true
$acl->isAllowed('registered', 'comment', 'edit'); // false
```
L'administrateur peut tout modifier, sauf les sondages :
```php
$acl->isAllowed('admin', 'poll', 'vote'); // true
$acl->isAllowed('admin', 'poll', 'edit'); // false
$acl->isAllowed('admin', 'comment', 'edit'); // true
```
Les autorisations peuvent également être évaluées dynamiquement et nous pouvons laisser la décision à notre propre rappel, auquel tous les paramètres sont transmis :
```php
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
return /* ... */;
};
$acl->allow('registered', 'comment', null, $assertion);
```
Mais comment gérer, par exemple, une situation où les noms des rôles et des ressources ne suffisent pas, mais où nous voudrions définir que, par exemple, le rôle `registered` ne peut modifier la ressource `article` que s'il en est l'auteur ? Au lieu de chaînes de caractères, nous utiliserons des objets, le rôle sera un objet [api:Nette\Security\Role] et la ressource un objet [api:Nette\Security\Resource]. Leurs méthodes `getRoleId()` resp. `getResourceId()` renverront les chaînes originales :
```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';
}
}
```
Et maintenant, créons la règle :
```php
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
$role = $acl->getQueriedRole(); // objet Registered
$resource = $acl->getQueriedResource(); // objet Article
return $role->id === $resource->authorId;
};
$acl->allow('registered', 'article', 'edit', $assertion);
```
Et la requête à l'ACL se fait en passant les objets :
```php
$user = new Registered(/* ... */);
$article = new Article(/* ... */);
$acl->isAllowed($user, $article, 'edit');
```
Un rôle peut hériter d'un autre rôle ou de plusieurs rôles. Mais que se passe-t-il si un ancêtre a une action interdite et un autre autorisée ? Quels seront les droits du descendant ? Ceci est déterminé par le poids du rôle - le dernier rôle mentionné dans la liste des ancêtres a le poids le plus élevé, le premier rôle mentionné a le poids le plus faible. C'est plus clair avec un exemple :
```php
$acl = new Nette\Security\Permission;
$acl->addRole('admin');
$acl->addRole('guest');
$acl->addResource('backend');
$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');
// cas A : le rôle admin a moins de poids que le rôle guest
$acl->addRole('john', ['admin', 'guest']);
$acl->isAllowed('john', 'backend'); // false
// cas B : le rôle admin a plus de poids que guest
$acl->addRole('mary', ['guest', 'admin']);
$acl->isAllowed('mary', 'backend'); // true
```
Les rôles et les ressources peuvent également être supprimés (`removeRole()`, `removeResource()`), les règles peuvent également être annulées (`removeAllow()`, `removeDeny()`). Le tableau de tous les rôles parents directs est renvoyé par `getRoleParents()`, si deux entités héritent l'une de l'autre est renvoyé par `roleInheritsFrom()` et `resourceInheritsFrom()`.
Ajout en tant que services
--------------------------
Nous devons enregistrer notre ACL créée dans la configuration en tant que service, afin que l'objet `$user` commence à l'utiliser, c'est-à-dire pour pouvoir utiliser dans le code par exemple `$user->isAllowed('article', 'view')`. À cette fin, nous écrirons une factory pour cela :
```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;
}
}
```
Et nous l'ajoutons à la configuration :
```neon
services:
- App\Model\AuthorizatorFactory::create
```
Dans les presenters, vous pouvez ensuite vérifier les autorisations, par exemple dans la méthode `startup()` :
```php
protected function startup()
{
parent::startup();
if (!$this->getUser()->isAllowed('backend')) {
$this->error('Forbidden', 403);
}
}
```