Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
JwtAuth
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
10 / 10
18
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUserResolver
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUser
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 user
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolveUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addClassAuthorization
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addMethodAuthorization
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addAllowAnonymous
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onRouteMatched
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 checkAuthorization
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3namespace Dynart\Micro;
4
5class JwtAuth implements JwtAuthInterface {
6
7    const EVENT_USER_SET = 'jwtauth:user_set';
8    const EVENT_AUTHORIZATION_GRANTED = 'jwtauth:authorization_granted';
9    const EVENT_AUTHORIZATION_DENIED = 'jwtauth:authorization_denied';
10
11    private ?JwtUserInterface $currentUser = null;
12    private mixed $userResolver = null;
13    private array $classAuthorizations = [];
14    private array $methodAuthorizations = [];
15    private array $allowAnonymous = [];
16
17    public function __construct(private EventServiceInterface $eventService) {
18        $eventService->subscribe(WebApp::EVENT_ROUTE_MATCHED, [$this, 'onRouteMatched']);
19    }
20
21    public function setUserResolver(callable $resolver): void {
22        $this->userResolver = $resolver;
23    }
24
25    public function setUser(JwtUserInterface $user): void {
26        $this->currentUser = $user;
27        $this->eventService->emit(self::EVENT_USER_SET, [$user]);
28    }
29
30    public function user(): ?JwtUserInterface {
31        return $this->currentUser;
32    }
33
34    public function resolveUser(string $sub, object $payload): JwtUserInterface {
35        if ($this->userResolver !== null) {
36            return ($this->userResolver)($sub, $payload);
37        }
38        return new JwtUser($sub);
39    }
40
41    public function addClassAuthorization(string $className, string $permission): void {
42        $this->classAuthorizations[$className] = $permission;
43    }
44
45    public function addMethodAuthorization(string $className, string $method, string $permission): void {
46        $this->methodAuthorizations[$className . '::' . $method] = $permission;
47    }
48
49    public function addAllowAnonymous(string $className, string $method): void {
50        $this->allowAnonymous[$className . '::' . $method] = true;
51    }
52
53    public function onRouteMatched(mixed $callable, array $params): void {
54        if (is_array($callable)) {
55            $this->checkAuthorization($callable);
56        }
57    }
58
59    private function checkAuthorization(array $callable): void {
60        $className = get_class($callable[0]);
61        $key = $className . '::' . $callable[1];
62
63        if (array_key_exists($key, $this->allowAnonymous)) {
64            return;
65        }
66
67        if (array_key_exists($key, $this->methodAuthorizations)) {
68            $permission = $this->methodAuthorizations[$key];
69        } elseif (array_key_exists($className, $this->classAuthorizations)) {
70            $permission = $this->classAuthorizations[$className];
71        } else {
72            return;
73        }
74
75        if ($this->currentUser === null) {
76            $this->eventService->emit(self::EVENT_AUTHORIZATION_DENIED, [$callable, 401]);
77            throw new AuthorizationException(401);
78        }
79
80        if ($permission !== '' && !$this->currentUser->hasPermission($permission)) {
81            $this->eventService->emit(self::EVENT_AUTHORIZATION_DENIED, [$callable, 403]);
82            throw new AuthorizationException(403);
83        }
84
85        $this->eventService->emit(self::EVENT_AUTHORIZATION_GRANTED, [$callable]);
86    }
87}