UserContext.php 14.4 KB
Newer Older
1
2
3
4
<?php

namespace UnicaenAuth\Service;

5
use BjyAuthorize\Acl\Role;
6
use UnicaenApp\Exception\RuntimeException;
7
use UnicaenApp\Traits\SessionContainerTrait;
8
use UnicaenAuth\Acl\NamedRole;
9
10
11
12
use UnicaenAuth\Authentication\SessionIdentity;
use UnicaenAuth\Authentication\Storage\Auth;
use UnicaenAuth\Authentication\Storage\Usurpation;
use UnicaenAuth\Entity\Db\AbstractUser;
13
use UnicaenAuth\Entity\Ldap\People;
14
use UnicaenAuth\Entity\Shibboleth\ShibUser;
15
use UnicaenAuth\Event\UserRoleSelectedEvent;
16
use UnicaenAuth\Formatter\RoleFormatter;
17
use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
18
use UnicaenAuth\Provider\Identity\Chain;
19
use Zend\Authentication\AuthenticationService;
20
use Zend\EventManager\EventManagerAwareInterface;
21
use Zend\EventManager\EventManagerAwareTrait;
22
23
use Zend\Http\Request;
use Zend\Http\Response;
24
use Zend\Permissions\Acl\Role\RoleInterface;
25
26
27
use ZfcUser\Entity\UserInterface;

/**
28
 * Service centralisant des méthodes utiles concernant l'utilisateur authentifié.
29
 *
30
 * @author Unicaen
31
 */
32
class UserContext extends AbstractService implements EventManagerAwareInterface
33
{
34
    use EventManagerAwareTrait;
35
    use SessionContainerTrait;
36
    use ModuleOptionsAwareTrait;
37
38
39
40
41

    /**
     * @var mixed
     */
    protected $identity;
42

43
    /**
44
     * @var array
45
     */
46
    protected $identityRoles;
47

48
49
50
51
52
53
54
55
56
57
58
59
60
    /**
     * @var AuthenticationService
     */
    protected $authenticationService;

    /**
     * @param AuthenticationService $authenticationService
     */
    public function setAuthenticationService(AuthenticationService $authenticationService)
    {
        $this->authenticationService = $authenticationService;
    }

61
62
63
64
65
66
67
68
69
70
71
72
    /**
     * Retourne l'utilisateur BDD courant
     *
     * @return UserInterface
     */
    public function getDbUser()
    {
        if (($identity = $this->getIdentity())) {
            if (isset($identity['db']) && $identity['db'] instanceof UserInterface) {
                return $identity['db'];
            }
        }
73

74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
        return null;
    }

    /**
     * Retourne l'utilisateur LDAP courant
     *
     * @return People
     */
    public function getLdapUser()
    {
        if (($identity = $this->getIdentity())) {
            if (isset($identity['ldap']) && $identity['ldap'] instanceof People) {
                return $identity['ldap'];
            }
        }
89

90
91
92
        return null;
    }

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
    /**
     * Retourne l'éventuel utilisateur Shibboleth courant.
     *
     * @return ShibUser|null
     */
    public function getShibUser()
    {
        if (($identity = $this->getIdentity())) {
            if (isset($identity['shib']) && $identity['shib'] instanceof ShibUser) {
                return $identity['shib'];
            }
        }

        return null;
    }

109
    /**
110
     * Retourne les données d'identité correspondant à l'utilisateur courant.
111
     *
112
     * @return mixed
113
     */
114
    public function getIdentity()
115
    {
116
        if (null === $this->identity) {
117
118
            if ($this->authenticationService->hasIdentity()) {
                $this->identity = $this->authenticationService->getIdentity();
119
120
            }
        }
121

122
123
        return $this->identity;
    }
124

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
    /**
     * Retourne l'identifiant de connexion de l'utilisateur courant.
     *
     * @return string|null
     */
    public function getIdentityUsername()
    {
        if ($user = $this->getShibUser()) {
            return $user->getUsername();
        }
        if ($user = $this->getLdapUser()) {
            return $user->getUsername();
        }
        if ($user = $this->getDbUser()) {
            return $user->getUsername();
        }

        return null;
    }

145
146
147
148
149
150
151
152
153
154
155
156
    /**
     * Retourne le type de l'authentification effectuée.
     *
     * @return string
     */
    public function getAuthenticationType(): string
    {
        $identityArray = $this->getIdentity();

        return Auth::extractTypeFromIdentityArray($identityArray);
    }

Laurent Lécluse's avatar
Laurent Lécluse committed
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    /**
     * @param string $roleId
     *
     * @return Role
     */
    public function getIdentityRole($roleId)
    {
        $roles = $this->getServiceAuthorize()->getRoles();
        if (isset($roles[$roleId])) {
            return $roles[$roleId];
        }

        return null;
    }

172
    /**
173
     * Retourne tous les rôles de l'utilisateur courant, pas seulement le rôle courant sélectionné.
174
     *
175
176
177
     * Les clés du tableau sont les ID de rôles, les valeurs sont les objets Role
     *
     * @return Role[]
178
179
180
181
     */
    public function getIdentityRoles()
    {
        if (null === $this->identityRoles) {
182
183
184
            $this->identityRoles = [];

            $roles            = $this->getServiceAuthorize()->getRoles();
185
186
            $identityProvider = $this->getIdentityProvider();
            if ($identityProvider instanceof Chain) {
187
                $identityRoles = $identityProvider->getAllIdentityRoles();
188
            } else {
189
                $identityRoles = $identityProvider->getIdentityRoles();
190
            }
191
192
            foreach ($identityRoles as $role) {
                if ($role instanceof RoleInterface) {
193
                    $this->identityRoles[$role->getRoleId()] = $role;
194
195
196
                } elseif (is_string($role) && isset($roles[$role])) {
                    $role = $roles[$role];
                    /** @var RoleInterface $role */
197
198
                    $this->identityRoles[$role->getRoleId()] = $role;
                }
199
            }
200
        }
201

202
203
        return $this->identityRoles;
    }
204

205
206
207
208
209
210
211
212


    public function clearIdentityRoles()
    {
        $this->identityRoles = null;
    }


213
214
215
216
217
218
219
220
221
222
    /**
     * Retourne tous les rôles de l'utilisateur courant au format littéral.
     *
     * @return array
     * @see getIdentityRoles()
     */
    public function getIdentityRolesToString()
    {
        $f = new RoleFormatter();
        $rolesToStrings = [];
223

224
225
226
227
228
229
        foreach ($this->getIdentityRoles() as $identityRole) {
            $rolesToStrings[$identityRole->getRoleId()] = $f->format($identityRole);
        }

        return $rolesToStrings;
    }
230

231
    /**
232
     * Retourne parmi tous les rôles de l'utilisateur courant ceux qui peuvent être sélectionnés.
233
     *
234
235
     * NB: si plus d'un rôle sont sélectionnables, on zappe le rôle "Authentifié".
     *
236
237
238
239
     * @return array
     */
    public function getSelectableIdentityRoles()
    {
240
241
242
        $filter = function ($r) {
            return !($r instanceof NamedRole && !$r->getSelectable());
        };
Bertrand Gauthier's avatar
Bertrand Gauthier committed
243
        $roles  = array_filter($this->getIdentityRoles(), $filter);
244

245
246
247
248
249
        // si plus d'un rôle sont sélectionnables, on zappe le rôle "Authentifié"
        if (count($roles) > 1 && isset($roles['user'])) {
            unset($roles['user']);
        }

Bertrand Gauthier's avatar
Bertrand Gauthier committed
250
        return $roles;
251
    }
252

253
    /**
254
     * Si un utilisateur est authentifié, retourne le rôle utilisateur sélectionné,
255
     * ou alors le premier sélectionnable si aucun n'a été sélectionné.
256
     *
257
     * @return RoleInterface|null
258
259
260
     */
    public function getSelectedIdentityRole()
    {
261
262
263
        // Si aucun utilisateur n'est authentifié, basta !
        if (! $this->getIdentity()) {
            return null;
264
265
        }

266
267
268
269
        // NB: Si un rôle est spécifié en session comme devant être le prochain rôle sélectionné,
        // c'est lui qui est pris en compte.
        if ($next = $this->getNextSelectedIdentityRole()) {
            $this->getSessionContainer()->selectedIdentityRole = $next; // écriture en session
270
        }
271

272
273
274
275
276
277
278
279
        // Si en session aucun rôle n'est sélectionné ou si cette sélection n'est pas valide,
        // on sélectionne le 1er rôle sélectionnable.
        $selectedRoleId = $this->getSessionContainer()->selectedIdentityRole;
        $selectableRoles = $this->getSelectableIdentityRoles();
        if (null === $selectedRoleId || ! isset($selectableRoles[$selectedRoleId])) {
            $firstSelectableRoleId = reset($selectableRoles);
            $this->setSelectedIdentityRole($firstSelectableRoleId); // écriture en session
        }
280

281
282
283
284
        // Rôle sélectionné en session.
        $roleId = $this->getSessionContainer()->selectedIdentityRole; // lecture en session
        if (! $roleId) {
            return null;
285
        }
286

287
288
289
290
291
292
        $role = $selectableRoles[$roleId];
        if (! $this->isRoleValid($role)) {
            return null;
        }

        return $role;
293
    }
294

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
    /**
     * Retourne le rôle utilisateur sélectionné éventuel au format littéral.
     *
     * @return string
     * @see getSelectedIdentityRole()
     */
    public function getSelectedIdentityRoleToString()
    {
        $role = $this->getSelectedIdentityRole();

        if (! $role) {
            return null;
        }

        $f = new RoleFormatter();
310

311
312
        return $f->format($role);
    }
313

314
    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
315
     * Mémorise en session le rôle spécifié comme étant le rôle courant de l'utilisateur.
316
     *
Bertrand Gauthier's avatar
Bertrand Gauthier committed
317
     * NB: seul l'id du rôle est mémorisé en session.
318
     *
Bertrand Gauthier's avatar
Bertrand Gauthier committed
319
     * @param RoleInterface|string $role
320
     *
321
     * @return \UnicaenAuth\Service\UserContext
322
     * @throws RuntimeException
323
324
325
326
327
     */
    public function setSelectedIdentityRole($role)
    {
        if ($role) {
            if (!$this->isRoleValid($role)) {
328
                throw new RuntimeException("Rôle spécifié invalide.");
329
            }
Bertrand Gauthier's avatar
Bertrand Gauthier committed
330
331
332
333
            if ($role instanceof RoleInterface) {
                $role = $role->getRoleId();
            }
            $this->getSessionContainer()->selectedIdentityRole = $role;
334
        } else {
335
336
            unset($this->getSessionContainer()->selectedIdentityRole);
        }
337

338
339
        $this->triggerUserRoleSelectedEvent(UserRoleSelectedEvent::POST_SELECTION, $role);

340
341
342
343
344
345
346
347
        return $this;
    }

    /**
     * Retourne l'éventuel rôle spécifié en session devant être le prochain rôle sélectionné.
     *
     * @return string|null
     */
348
    public function getNextSelectedIdentityRole()
349
350
351
352
353
354
355
356
357
358
    {
        return $this->getSessionContainer()->nextSelectedIdentityRole;
    }

    /**
     * Mémorise en session le rôle devant être le prochain rôle sélectionné.
     *
     * NB: seul l'id du rôle est mémorisé en session ; la durée de vie du stockage est de 1 requête seulement.
     *
     * @param RoleInterface|string $role
359
     *
360
361
362
363
364
365
366
367
368
369
370
     * @return \UnicaenAuth\Service\UserContext
     */
    public function setNextSelectedIdentityRole($role)
    {
        if ($role instanceof RoleInterface) {
            $role = $role->getRoleId();
        }

        if ($role) {
            $this->getSessionContainer()->nextSelectedIdentityRole = $role;
            $this->getSessionContainer()->setExpirationHops(1, 'nextSelectedIdentityRole');
371
        } else {
372
373
374
            unset($this->getSessionContainer()->nextSelectedIdentityRole);
        }

375
376
        $this->triggerUserRoleSelectedEvent(UserRoleSelectedEvent::POST_SELECTION, $role);

377
378
        return $this;
    }
379

380
381
382
383
384
385
386
387
388
389
390
391
    /**
     * Déclenche l'événement donnant à l'application l'opportunité de réagir à la sélection d'un rôle.
     *
     * @param string $name Ex: UserRoleSelectedEvent::POST_SELECTION
     * @param RoleInterface|string|null $role Rôle sélectionné
     */
    private function triggerUserRoleSelectedEvent($name, $role)
    {
        $event = new UserRoleSelectedEvent($name);
        $event
            ->setRole($role)
            ->setTarget($this);
392
        $this->getEventManager()->triggerEvent($event);
393
    }
394

395
    /**
396
     * Teste si le rôle spécifié fait partie des rôles disponibles.
397
398
     *
     * @param RoleInterface|string $role
399
     *
400
     * @return boolean
401
     */
402
    public function isRoleValid($role)
403
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
404
405
406
        if ($role instanceof RoleInterface) {
            $role = $role->getRoleId();
        }
407

408
        foreach ($this->getIdentityRoles() as $r) {
409
            if ($r instanceof RoleInterface) {
410
411
412
413
414
415
                $r = $r->getRoleId();
            }
            if ($role === $r) {
                return true;
            }
        }
416

417
418
        return false;
    }
419

420
    /**
421
     *
422
     * @return Chain
423
424
425
     */
    private function getIdentityProvider()
    {
426
427
        /* @var $identityProvider Chain */
        $identityProvider = $this->getServiceAuthorize()->getIdentityProvider();
428

429
430
        return $identityProvider;
    }
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502

    /**
     * Détermine si une usurpation d'identité est en cours.
     *
     * @return bool
     */
    public function isUsurpationEnCours(): bool
    {
        /** @var array $currentIdentity */
        $currentIdentity = $this->getIdentity();

        return isset($currentIdentity['usurpation']);
    }

    /**
     * Usurpe l'identité d'un autre utilisateur.
     * @param string $usernameUsurpe
     * @return SessionIdentity|null
     */
    public function usurperIdentite(string $usernameUsurpe): ?SessionIdentity
    {
        /** @var array $currentIdentityArray */
        $currentIdentityArray = $this->getIdentity();

        $identity = null;
        if ($identity === null && isset($currentIdentityArray['db'])) {
            /** @var AbstractUser $identity */
            $identity = $currentIdentityArray['db'];
        }
        if ($identity === null && isset($currentIdentityArray['ldap'])) {
            /** @var People $identity */
            $identity = $currentIdentityArray['ldap'];
        }
        if ($identity === null && isset($currentIdentityArray['shib'])) {
            /** @var ShibUser $identity */
            $identity = $currentIdentityArray['shib'];
        }
        if ($identity === null) {
            return null;
        }

        // seuls les logins spécifiés dans la config sont habilités à usurper des identités
        if (! in_array($identity->getUsername(), $this->moduleOptions->getUsurpationAllowedUsernames())) {
            throw new RuntimeException("Usurpation non explicitement autorisée");
        }

        $usernameUsurpateur = $identity->getUsername();

        $sessionIdentity = SessionIdentity::newInstanceForUsurpation($usernameUsurpe, $usernameUsurpateur, $this->getAuthenticationType());
        $this->authenticationService->getStorage()->write($sessionIdentity);

        return $sessionIdentity;
    }

    /**
     * Met fin à l'usurpation d'identité en cours.
     */
    public function stopperUsurpation()
    {
        if (! $this->isUsurpationEnCours()) {
            return;
        }

        /** @var array $currentIdentityArray */
        $currentIdentityArray = $this->getIdentity();

        /** @var AbstractUser $usurpateur */
        $usurpateur = Usurpation::extractUsurpateurFromIdentityArray($currentIdentityArray);

        $sessionIdentity = SessionIdentity::newInstance($usurpateur->getUsername(), $this->getAuthenticationType());
        $this->authenticationService->getStorage()->write($sessionIdentity);
    }
503
}