-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAccessManager.php
More file actions
178 lines (154 loc) · 5.67 KB
/
AccessManager.php
File metadata and controls
178 lines (154 loc) · 5.67 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
<?php
namespace App\Security;
use App\Model\Entity\User;
use App\Model\Repository\Users;
use App\Exceptions\InvalidAccessTokenException;
use App\Exceptions\ForbiddenRequestException;
use App\Exceptions\FrontendErrorMappings;
use Nette\Http\IRequest;
use Nette\Http\IResponse;
use Nette\Utils\Strings;
use Nette\Utils\Arrays;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use DomainException;
use UnexpectedValueException;
class AccessManager
{
/** @var Users Users repository */
protected $users;
/** @var string Identification of the issuer of the token */
private $issuer;
/** @var string Identification of the audience of the token */
private $audience;
/** @var string Name of the algorithm currently used for encrypting the signature of the token. */
private $usedAlgorithm;
/** @var string Verification key */
private $verificationKey;
/** @var int Expiration time of newly issued tokens (in seconds) */
private $expiration;
public function __construct(array $parameters, Users $users)
{
$this->users = $users;
$this->verificationKey = Arrays::get($parameters, "verificationKey");
$this->expiration = Arrays::get($parameters, "expiration", 24 * 60 * 60); // one day in seconds
$this->issuer = Arrays::get($parameters, "issuer", "<missing-issuer-configuration>");
$this->audience = Arrays::get($parameters, "audience", "<missing-audience-configuration>");
$this->usedAlgorithm = Arrays::get($parameters, "usedAlgorithm", "HS256");
JWT::$leeway = Arrays::get($parameters, "leeway", 10); // 10 seconds
}
public function getExpiration(): int
{
return $this->expiration;
}
/**
* Parse and validate a JWT token and extract the payload.
* @param string $token The potential JWT token
* @return AccessToken The decoded payload
* @throws ForbiddenRequestException
* @throws InvalidAccessTokenException
*/
public function decodeToken(string $token): AccessToken
{
try {
$decodedToken = JWT::decode($token, new Key($this->verificationKey, $this->usedAlgorithm));
} catch (DomainException $e) {
throw new InvalidAccessTokenException($token, $e);
} catch (UnexpectedValueException $e) {
throw new InvalidAccessTokenException($token, $e);
}
if (!isset($decodedToken->sub)) {
throw new InvalidAccessTokenException($token);
}
return new AccessToken($decodedToken);
}
/**
* @param AccessToken $token Valid JWT payload
* @return User
* @throws ForbiddenRequestException
*/
public function getUser(AccessToken $token): User
{
/** @var ?User $user */
$user = $this->users->get($token->getUserId());
if (!$user) {
throw new ForbiddenRequestException(
"Forbidden Request - User does not exist",
IResponse::S403_Forbidden,
FrontendErrorMappings::E403_001__USER_NOT_EXIST
);
}
return $user;
}
/**
* Issue a new JWT for the user with optional scopes and optional explicit expiration time.
* @param User $user
* @param string|null $effectiveRole Effective user role for issued token
* @param string[] $scopes Array of scopes
* @param int $exp Expiration of the token in seconds
* @param array $payload
* @return string
* @throws ForbiddenRequestException
*/
public function issueToken(
User $user,
?string $effectiveRole = null,
array $scopes = [],
?int $exp = null,
array $payload = []
) {
if ($exp === null) {
$exp = $this->expiration;
}
$token = new AccessToken(
(object)array_merge(
$payload,
[
"iss" => $this->issuer,
"aud" => $this->audience,
"iat" => time(),
"nbf" => time(),
"exp" => time() + $exp,
"sub" => $user->getId(),
"effrole" => $effectiveRole,
"scopes" => $scopes
]
)
);
return $token->encode($this->verificationKey, $this->usedAlgorithm);
}
public function issueRefreshedToken(AccessToken $token): string
{
return $this->issueToken(
$this->getUser($token),
null,
$token->getScopes(),
$token->getExpirationTime(),
$token->getPayloadData()
);
}
/**
* Extract the access token from the request.
* @return string|null The access token parsed from the HTTP request, or null if there is no access token.
*/
public static function getGivenAccessToken(IRequest $request)
{
$accessToken = $request->getQuery("access_token");
if ($accessToken !== null && Strings::length($accessToken) > 0) {
return $accessToken; // the token specified in the URL is preferred
}
// if the token is not in the URL, try to find the "Authorization" header with the bearer token
$authorizationHeader = $request->getHeader("Authorization");
if ($authorizationHeader === null) {
return null;
}
$parts = Strings::split($authorizationHeader, "/ /");
if (count($parts) === 2) {
list($bearer, $accessToken) = $parts;
if ($bearer === "Bearer" && !str_contains($accessToken, " ") && Strings::length($accessToken) > 0) {
return $accessToken;
}
}
return null; // there is no access token or it could not be parsed
}
}