PluginProbe ʕ •ᴥ•ʔ
Advanced Access Manager – Access Governance for WordPress / 5.11
Advanced Access Manager – Access Governance for WordPress v5.11
6.8.4 6.8.5 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13 6.9.14 6.9.15 6.9.16 6.9.17 6.9.18 6.9.19 6.9.2 6.9.20 6.9.21 6.9.22 6.9.23 6.9.24 6.9.25 6.9.26 6.9.27 6.9.28 6.9.29 6.9.3 6.9.30 6.9.31 6.9.32 6.9.33 6.9.34 6.9.35 6.9.36 6.9.37 6.9.38 6.9.39 6.9.4 6.9.41 6.9.42 6.9.43 6.9.44 6.9.45 6.9.46 6.9.47 6.9.48 6.9.49 6.9.5 6.9.51 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.0-alpha.6 7.0.0-alpha.7 7.0.0-beta.1 7.0.0-rc1 7.0.0-rc2 7.0.0-rc3 7.0.1 7.0.10 7.0.11 7.0.2 7.0.3 7.0.4 7.0.5 7.0.6 7.0.7 7.0.8 7.0.9 7.1.0 7.1.1 trunk 3.0 4.0 4.0.1 4.1 4.2 4.3 4.4 4.4.1 4.5 4.6 4.6.1 4.6.2 4.7 4.7.1 4.7.2 4.7.5 4.7.6 4.8 4.8.1 4.9 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.5.1 4.9.5.2 5.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1 5.1.1 5.10 5.11 5.2 5.2.1 5.2.5 5.2.6 5.2.7 5.3 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.4 5.4.1 5.4.2 5.4.3 5.4.3.1 5.4.3.2 5.5 5.5.1 5.5.2 5.6 5.6.1 5.6.1.1 5.7 5.7.1 5.7.2 5.7.3 5.8 5.8.1 5.8.2 5.8.3 5.9 5.9.1 5.9.1.1 5.9.2 5.9.2.1 5.9.3 5.9.4 5.9.5 5.9.6 5.9.6.1 5.9.6.2 5.9.6.3 5.9.7 5.9.7.1 5.9.7.2 5.9.7.3 5.9.8 5.9.8.1 5.9.9 5.9.9.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.2.0 6.2.1 6.2.2 6.3.0 6.3.1 6.3.2 6.3.3 6.4.0 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.6.0 6.6.1 6.6.2 6.6.3 6.6.4 6.7.0 6.7.1 6.7.2 6.7.3 6.7.4 6.7.5 6.7.6 6.7.7 6.7.8 6.7.9 6.8.0 6.8.1 6.8.2 6.8.3
advanced-access-manager / application / Core / Jwt / Manager.php
advanced-access-manager / application / Core / Jwt Last commit date
Auth.php 6 years ago Issuer.php 6 years ago Manager.php 6 years ago
Manager.php
463 lines
1 <?php
2
3 /**
4 * ======================================================================
5 * LICENSE: This file is subject to the terms and conditions defined in *
6 * file 'license.txt', which is part of this source code package. *
7 * ======================================================================
8 */
9
10 /**
11 * AAM JWT Manager
12 *
13 * @package AAM
14 * @author Vasyl Martyniuk <vasyl@vasyltech.com>
15 * @since v5.9.2
16 */
17 class AAM_Core_Jwt_Manager {
18
19 /**
20 * Single instance of itself
21 *
22 * @var AAM_Core_Jwt_Manager
23 *
24 * @access protected
25 * @static
26 */
27 protected static $instance = null;
28
29 /**
30 * Constructor
31 *
32 * @return void
33 *
34 * @access protected
35 */
36 protected function __construct() {
37 //register API endpoint
38 add_action('rest_api_init', array($this, 'registerAPI'));
39
40 //register authentication hook
41 add_filter('determine_current_user', array($this, 'determineUser'), 999);
42
43 //login user if JWT is in the URL
44 add_action('init', array($this, 'loginAccount'), 1);
45 }
46
47 /**
48 * Register APIs
49 *
50 * @return void
51 *
52 * @access public
53 */
54 public function registerAPI() {
55 // Authenticate user
56 register_rest_route('aam/v1', '/authenticate', array(
57 'methods' => 'POST',
58 'callback' => array($this, 'authenticate'),
59 'args' => array(
60 'username' => array(
61 'description' => __('Valid username.', AAM_KEY),
62 'type' => 'string',
63 ),
64 'password' => array(
65 'description' => __('Valid password.', AAM_KEY),
66 'type' => 'string',
67 )
68 ),
69 ));
70
71 // Validate JWT token
72 register_rest_route('aam/v1', '/validate-jwt', array(
73 'methods' => 'POST',
74 'callback' => array($this, 'validateToken'),
75 'args' => array(
76 'jwt' => array(
77 'description' => __('JWT token.', AAM_KEY),
78 'type' => 'string',
79 )
80 ),
81 ));
82
83 // Refresh JWT token
84 register_rest_route('aam/v1', '/refresh-jwt', array(
85 'methods' => 'POST',
86 'callback' => array($this, 'refreshToken'),
87 'args' => array(
88 'jwt' => array(
89 'description' => __('JWT token.', AAM_KEY),
90 'type' => 'string',
91 )
92 ),
93 ));
94 }
95
96 /**
97 * Authenticate user
98 *
99 * @param WP_REST_Request $request
100 *
101 * @return WP_REST_Response
102 *
103 * @access public
104 */
105 public function authenticate(WP_REST_Request $request) {
106 $username = $request->get_param('username');
107 $password = $request->get_param('password');
108 $response = new WP_REST_Response();
109
110 $auth = new AAM_Core_Jwt_Auth();
111 $result = $auth->authenticateWithCredentials($username, $password);
112
113 if (!empty($result->error)) {
114 $response->status = 403;
115 $response->data = new WP_Error(
116 'rest_jwt_auth_failure',
117 strip_tags($result->reason)
118 );
119 } else {
120 $jwt = $this->issueToken($result->user->ID);
121
122 $response->status = 200;
123 $response->data = array(
124 'token' => $jwt->token,
125 'token_expires' => $jwt->claims['exp'],
126 'user' => $result->user
127 );
128 }
129
130 return apply_filters('aam-jwt-response-filter', $response);
131 }
132
133 /**
134 * Validate JWT token
135 *
136 * @param WP_REST_Request $request
137 *
138 * @return WP_REST_Response
139 *
140 * @access public
141 */
142 public function validateToken(WP_REST_Request $request) {
143 $jwt = $request->get_param('jwt');
144 $issuer = new AAM_Core_Jwt_Issuer();
145 $response = new WP_REST_Response();
146
147 $result = $issuer->validateToken($jwt);
148
149 if ($result->status === 'valid') {
150 $response->status = 200;
151 $response->data = $result;
152 } else {
153 $response->status = 400;
154 $response->data = new WP_Error(
155 'rest_jwt_validation_failure',
156 $result->reason
157 );
158 }
159
160 return $response;
161 }
162
163 /**
164 * Refresh/renew JWT token
165 *
166 * @param WP_REST_Request $request
167 *
168 * @return WP_REST_Response
169 *
170 * @access public
171 */
172 public function refreshToken(WP_REST_Request $request) {
173 $jwt = $request->get_param('jwt');
174 $issuer = new AAM_Core_Jwt_Issuer();
175 $response = new WP_REST_Response();
176
177 $result = $issuer->validateToken($jwt);
178
179 if ($result->status === 'valid') {
180 if (!empty($result->refreshable)) {
181 // calculate the new expiration
182 $issuedAt = new DateTime();
183 $issuedAt->setTimestamp($result->iat);
184 $expires = DateTime::createFromFormat('m/d/Y, H:i O', $result->exp);
185
186 $exp = new DateTime();
187 $exp->add($issuedAt->diff($expires));
188
189 $new = $this->issueToken($result->userId, $jwt, $exp);
190
191 $response->status = 200;
192 $response->data = array(
193 'token' => $new->token,
194 'token_expires' => $new->claims['exp'],
195 );
196 } else {
197 $response->status = 400;
198 $response->data = new WP_Error(
199 'rest_jwt_validation_failure',
200 __('Provided JWT token is not refreshable', AAM_KEY)
201 );
202 }
203 } else {
204 $response->status = 400;
205 $response->data = new WP_Error(
206 'rest_jwt_validation_failure',
207 $result->reason
208 );
209 }
210
211 return $response;
212 }
213
214 /**
215 * Determine current user by JWT
216 *
217 * @param int $userId
218 *
219 * @return int
220 *
221 * @access public
222 */
223 public function determineUser($userId) {
224 if (empty($userId)) {
225 $token = $this->extractJwt();
226
227 if (!empty($token)) {
228 $issuer = new AAM_Core_Jwt_Issuer();
229 $result = $issuer->validateToken($token->jwt);
230
231 if ($result->status === 'valid') {
232 $userId = $result->userId;
233 }
234 }
235 }
236
237 return $userId;
238 }
239
240 /**
241 * Undocumented function
242 *
243 * @return void
244 */
245 public function loginAccount() {
246 $jwt = AAM_Core_Request::get('aam-jwt');
247 $method = AAM_Core_Request::server('REQUEST_METHOD');
248
249 if (!empty($jwt) && ($method === 'GET')) {
250 $issuer = new AAM_Core_Jwt_Issuer();
251 $token = $issuer->validateToken($jwt);
252
253
254
255 // Check that JWT token is valid
256 if ($token->status === 'valid') {
257 // Check if Account is active
258 $user = AAM::api()->getUser($token->userId);
259
260 if ($user->getUserStatus()->status === 'active') {
261 wp_set_current_user($token->userId);
262 wp_set_auth_cookie($token->userId);
263
264 // TODO: Remove June 2020
265 $exp = (is_numeric($token->exp) ? date('m/d/Y, H:i O', $token->exp) : $token->exp);
266
267 // determine correct trigger
268 if (!empty($token->trigger)) {
269 update_user_meta(
270 $token->userId,
271 'aam_user_expiration',
272 $exp . "|{$token->trigger->action}|" . (!empty($token->trigger->role) ? $token->trigger->role : '')
273 );
274 }
275
276 do_action('wp_login', $user->user_login, $user->getSubject());
277
278 // finally just redirect user to the homepage
279 wp_safe_redirect(get_home_url()); exit;
280 }
281 }
282 }
283 }
284
285 /**
286 * Register JWT token to user's registry
287 *
288 * @param int $userId
289 * @param string $token
290 * @param string $replaceExisting
291 *
292 * @return bool
293 *
294 * @access public
295 */
296 public function registerToken($userId, $token, $replaceExisting = false) {
297 $registry = $this->getTokenRegistry($userId);
298 $limit = AAM_Core_Config::get('authentication.jwt.registryLimit', 10);
299
300 if ($replaceExisting) {
301 $result = update_user_meta($userId, 'aam-jwt', $token, $replaceExisting);
302 } else {
303 // Make sure that we do not overload the user meta
304 if (count($registry) >= $limit) {
305 $this->revokeToken($userId, array_shift($registry));
306 }
307
308 // Save token
309 $result = add_user_meta($userId, 'aam-jwt', $token);
310 }
311
312
313 return $result;
314 }
315
316 /**
317 * Revoke JWT token
318 *
319 * @param int $userId
320 * @param string $token
321 *
322 * @return bool
323 *
324 * @access public
325 */
326 public function revokeToken($userId, $token) {
327 $result = false;
328 $registry = $this->getTokenRegistry($userId);
329
330 if (in_array($token, $registry, true)) {
331 $result = delete_user_meta($userId, 'aam-jwt', $token);
332 }
333
334 return $result;
335 }
336
337 /**
338 * Get JWT token registry
339 *
340 * @param int $userId
341 *
342 * @return array
343 *
344 * @access public
345 */
346 public function getTokenRegistry($userId) {
347 $registry = get_user_meta($userId, 'aam-jwt', false);
348
349 return (!empty($registry) ? $registry : array());
350 }
351
352 /**
353 * Issue JWT token
354 *
355 * @param int $userId
356 * @param string $replace
357 * @param string $expires
358 *
359 * @return object
360 *
361 * @access protected
362 */
363 protected function issueToken($userId, $replace = null, $expires = null) {
364 $issuer = new AAM_Core_Jwt_Issuer();
365 $result = $issuer->issueToken(
366 array(
367 'userId' => $userId,
368 'revocable' => true,
369 'refreshable' => AAM::api()->getConfig(
370 'authentication.jwt.refreshable', false
371 )
372 ),
373 $expires
374 );
375
376 // Finally register token so it can be revoked
377 $this->registerToken($userId, $result->token, $replace);
378
379 return $result;
380 }
381
382 /**
383 * Extract JWT token from the request
384 *
385 * Based on the `authentication.jwt.container` setting, parse HTTP request and
386 * try to extract the JWT token
387 *
388 * @return object|null
389 *
390 * @access protected
391 */
392 protected function extractJwt() {
393 $container = explode(',', AAM_Core_Config::get(
394 'authentication.jwt.container', 'header,post,cookie'
395 ));
396
397 $jwt = null;
398
399 foreach($container as $method) {
400 switch(strtolower(trim($method))) {
401 case 'header':
402 $jwt = AAM_Core_Request::server('HTTP_AUTHENTICATION');
403 break;
404
405 case 'cookie':
406 $jwt = AAM_Core_Request::cookie('aam-jwt');
407 break;
408
409 case 'post':
410 $jwt = AAM_Core_Request::post('aam-jwt');
411 break;
412
413 default:
414 $jwt = apply_filters('aam-get-jwt-filter', null, $method);
415 break;
416 }
417
418 if (!is_null($jwt)) {
419 break;
420 }
421 }
422
423 if (!empty($jwt)) {
424 $response = (object) array(
425 'jwt' => preg_replace('/^Bearer /', '', $jwt),
426 'method' => $method
427 );
428 } else {
429 $response = null;
430 }
431
432 return $response;
433 }
434
435 /**
436 * Get single instance of itself
437 *
438 * @return AAM_Core_Jwt_Manager
439 *
440 * @access public
441 * @static
442 */
443 public static function getInstance() {
444 if (is_null(self::$instance)) {
445 self::$instance = new self;
446 }
447
448 return self::$instance;
449 }
450
451 /**
452 * Bootstrap AAM JWT Manager
453 *
454 * @return AAM_Core_Jwt_Manager
455 *
456 * @access public
457 * @static
458 */
459 public static function bootstrap() {
460 return self::getInstance();
461 }
462
463 }