PluginProbe ʕ •ᴥ•ʔ
Advanced Access Manager – Access Governance for WordPress / trunk
Advanced Access Manager – Access Governance for WordPress vtrunk
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 / Service / Jwt.php
advanced-access-manager / application / Service Last commit date
Shortcode 5 months ago AccessDeniedRedirect.php 1 year ago AdminToolbar.php 1 year ago ApiRoute.php 1 year ago BackendMenu.php 1 year ago BaseTrait.php 1 year ago Capability.php 1 year ago Content.php 5 months ago Core.php 10 months ago Hooks.php 1 year ago Identity.php 3 months ago Jwt.php 3 months ago LoginRedirect.php 1 year ago LogoutRedirect.php 3 months ago Metaboxes.php 1 year ago NotFoundRedirect.php 1 year ago Policies.php 1 year ago SecureLogin.php 1 year ago SecurityAudit.php 1 year ago Shortcodes.php 5 months ago Urls.php 1 year ago Welcome.php 1 year ago Widgets.php 1 year ago
Jwt.php
417 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 * JWT Token service
12 *
13 * @package AAM
14 * @version 7.1.0
15 */
16 class AAM_Service_Jwt
17 {
18 use AAM_Service_BaseTrait;
19
20 /**
21 * JWT Registry DB option
22 *
23 * @version 7.0.0
24 */
25 const DB_OPTION = 'aam_jwt_registry';
26
27 /**
28 * Default configurations
29 *
30 * @version 7.0.0
31 */
32 const DEFAULT_CONFIG = [
33 'service.jwt.bearer' => 'header,query_param,post_param,cookie',
34 'service.jwt.header_name' => 'HTTP_AUTHENTICATION',
35 'service.jwt.cookie_name' => 'aam_jwt_token',
36 'service.jwt.post_param_name' => 'aam-jwt',
37 'service.jwt.query_param_name' => 'aam-jwt'
38 ];
39
40 /**
41 * Constructor
42 *
43 * @return void
44 * @access protected
45 *
46 * @version 7.0.4
47 */
48 protected function __construct()
49 {
50 add_filter('aam_get_config_filter', function($result, $key) {
51 if (empty($result) && array_key_exists($key, self::DEFAULT_CONFIG)) {
52 $result = self::DEFAULT_CONFIG[$key];
53 }
54
55 return $result;
56 }, 10, 2);
57
58 // WP Core current user definition
59 add_filter('determine_current_user', function($user_id){
60 return $this->_determine_current_user($user_id);
61 }, PHP_INT_MAX);
62
63 // Register RESTful API
64 AAM_Restful_Jwt::bootstrap();
65
66 add_action('init', function() {
67 $this->initialize_hooks();
68 }, PHP_INT_MAX);
69 }
70
71 /**
72 * Initialize service hooks
73 *
74 * @return void
75 * @access protected
76 *
77 * @version 7.1.0
78 */
79 protected function initialize_hooks()
80 {
81 if (is_admin()) {
82 // Hook that initialize the AAM UI part of the service
83 add_action('aam_initialize_ui_action', function () {
84 AAM_Backend_Feature_Main_Jwt::register();
85 });
86
87 add_action('aam_post_edit_user_modal_action', function () {
88 if (current_user_can(AAM_Backend_Feature_Main_Jwt::ACCESS_CAPABILITY)) {
89 echo AAM_Backend_View::get_instance()->loadPartial(
90 'jwt-login-url'
91 );
92 }
93 });
94 }
95
96 add_action('aam_reset_action', function() {
97 global $wpdb;
98
99 // Run the query, will return true if deleted, false otherwise
100 $wpdb->delete($wpdb->usermeta, [
101 'meta_key' => $wpdb->prefix . AAM_Framework_Service_Jwts::DB_OPTION
102 ]);
103 });
104
105 add_filter(
106 'aam_rest_authenticated_user_data_filter',
107 function($result, $request, $user) {
108 return $this->_prepare_login_response($result, $request, $user);
109 }, 10, 3
110 );
111
112 // Allow other implementations to work with JWT token
113 add_filter('aam_current_jwt_filter', function($result) {
114 if (empty($result)) {
115 $token = $this->_extract_token();
116 $result = !empty($token) ? $token->jwt : null;
117 }
118
119 return $result;
120 });
121 }
122
123 /**
124 * Extern authentication request with JWT token
125 *
126 * @param array $response
127 * @param WP_REST_Request $request
128 * @param WP_User $user
129 *
130 * @return array
131 * @access public
132 *
133 * @version 7.0.0
134 */
135 private function _prepare_login_response($response, $request, $user)
136 {
137 $issue_jwt = $this->_get_backward_compatible_request_param(
138 'issue_jwt', 'issueJWT', $request
139 );
140 $issue_refreshable_jwt = $this->_get_backward_compatible_request_param(
141 'issue_refreshable_jwt',
142 'refreshableJWT',
143 $request
144 );
145
146 if (is_array($response) && ($issue_jwt || $issue_refreshable_jwt)) {
147 if ($issue_refreshable_jwt) {
148 if (!current_user_can('aam_issue_refreshable_jwt')) {
149 throw new DomainException(
150 'You are not allowed to issue refreshable JWT token'
151 );
152 }
153 }
154
155 $result = AAM::api()->jwts('user:' . $user->ID)->issue([], [
156 'refreshable' => $issue_refreshable_jwt
157 ]);
158
159 $response['jwt'] = [
160 'token' => $result['token'],
161 'token_expires' => $result['claims']['exp']
162 ];
163 }
164
165 return $response;
166 }
167
168 /**
169 * Get backward compatible param from request
170 *
171 * @param string $new_param
172 * @param string $legacy_param
173 * @param WP_REST_Request $request
174 * @param bool $default
175 *
176 * @return string|null
177 * @access private
178 *
179 * @version 7.0.1
180 */
181 private function _get_backward_compatible_request_param(
182 $new_param, $legacy_param, $request, $default = false
183 ) {
184 $result = $request->get_param($new_param);
185
186 if (empty($result)) {
187 $result = $request->get_param($legacy_param);
188
189 if (!empty($result)) {
190 _deprecated_argument('/authenticate', AAM_VERSION, sprintf(
191 'The REST %s parameter is deprecated. Replace it with %s',
192 $legacy_param,
193 $new_param
194 ));
195 }
196 }
197
198 return is_null($result) ? $default : $result;
199 }
200
201 /**
202 * Determine current user by JWT
203 *
204 * @param int $user_id
205 *
206 * @return int
207 * @access private
208 *
209 * @version 7.0.4
210 */
211 private function _determine_current_user($user_id)
212 {
213 if (empty($user_id)) {
214 $token = $this->_extract_token();
215
216 if (!empty($token)) {
217 $claims = AAM::api()->jwt->decode($token->jwt);
218
219 if (!is_wp_error($claims)) {
220 // Backward compatibility
221 if (array_key_exists('userId', $claims)) {
222 $cuid = $claims['userId'];
223 } else {
224 $cuid = $claims['user_id'];
225 }
226
227 // Get JWT service and verify that token is valid
228 $service = AAM::api()->jwts(
229 'user:' . $cuid,
230 [ 'error_handling' => 'wp_error' ]
231 );
232
233 if (!is_wp_error($service)) {
234 $is_valid = $service->validate($token->jwt);
235
236 if ($is_valid === true) {
237 if ($this->_is_user_active($cuid)) {
238 $this->_maybe_authenticate($cuid, $token, $claims);
239
240 $user_id = $cuid;
241 }
242 }
243 }
244 }
245 }
246 }
247
248 return $user_id;
249 }
250
251 /**
252 * Determine if JWT token is used in password-less URL and if so - authenticate
253 *
254 * @param int $user_id
255 * @param object $token
256 * @param array $claims
257 *
258 * @return void
259 * @access private
260 *
261 * @version 7.0.4
262 */
263 private function _maybe_authenticate($user_id, $token, $claims)
264 {
265 if (in_array($token->method, [ 'get', 'query', 'query_param' ], true)) {
266 $this->_authenticate_user($user_id, $claims);
267 }
268 }
269
270 /**
271 * Authenticate user with JWT
272 *
273 * @return void
274 * @access private
275 *
276 * @version 7.0.0
277 */
278 private function _authenticate_user($user_id, $token_claims)
279 {
280 wp_set_current_user($user_id);
281 wp_set_auth_cookie($user_id);
282
283 // If we are authenticating with passwordless, that manually set user's
284 // expiration attributes
285 $data = [
286 'expiration' => [
287 'expires_at' => $token_claims['exp']
288 ]
289 ];
290
291 if (array_key_exists('trigger', $token_claims)) {
292 $data['expiration']['trigger'] = $token_claims['trigger'];
293 }
294
295 $user = AAM::api()->user($user_id);
296
297 $user->update($data);
298
299 do_action('wp_login', $user->user_login, $user->get_core_instance());
300
301 // Determine where to redirect user and safely redirect & finally just
302 // redirect user to the homepage
303 $redirect_to = AAM::api()->misc->get($_GET, 'redirect_to');
304
305 wp_safe_redirect(
306 apply_filters(
307 'login_redirect',
308 (!empty($redirect_to) ? $redirect_to : admin_url()),
309 '',
310 $user->get_core_instance()
311 )
312 );
313
314 // Halt the execution. Redirect should carry user away if this is not
315 // a CLI execution (e.g. Unit Test)
316 if (php_sapi_name() !== 'cli') {
317 exit;
318 }
319 }
320
321 /**
322 * Extract JWT token from the request
323 *
324 * Based on the `authentication.jwt.container` setting, parse HTTP request and
325 * try to extract the JWT token
326 *
327 * @return object|null
328 * @access protected
329 *
330 * @version 7.0.4
331 */
332 private function _extract_token()
333 {
334 $configs = AAM::api()->config;
335 $container = wp_parse_list($configs->get('service.jwt.bearer', ''));
336
337 foreach ($container as $method) {
338 switch (strtolower(trim($method))) {
339 case 'header':
340 // Fallback for Authorization header
341 $possibles = array(
342 'HTTP_AUTHORIZATION',
343 'REDIRECT_HTTP_AUTHORIZATION',
344 $configs->get('service.jwt.header_name')
345 );
346
347 foreach($possibles as $h) {
348 $jwt = AAM::api()->misc->get($_SERVER, $h);
349
350 if (!empty($jwt)) {
351 break;
352 }
353 }
354 break;
355
356 case 'cookie':
357 $jwt = AAM::api()->misc->get(
358 $_COOKIE, $configs->get('service.jwt.cookie_name')
359 );
360 break;
361
362 case 'post':
363 case 'post_param':
364 $jwt = AAM::api()->misc->get(
365 $_POST, $configs->get('service.jwt.post_param_name')
366 );
367 break;
368
369 case 'get':
370 case 'query':
371 case 'query_param':
372 $jwt = AAM::api()->misc->get(
373 $_GET, $configs->get('service.jwt.query_param_name')
374 );
375 break;
376
377 default:
378 $jwt = apply_filters('aam_extract_jwt_filter', null, $method);
379 break;
380 }
381
382 if (!empty($jwt)) {
383 break;
384 }
385 }
386
387 if (!empty($jwt)) {
388 $response = (object) array(
389 'jwt' => preg_replace('/^Bearer /', '', $jwt),
390 'method' => $method
391 );
392 } else {
393 $response = null;
394 }
395
396 return $response;
397 }
398
399 /**
400 * Verify user's status
401 *
402 * @param int $user_id
403 *
404 * @return bool
405 * @access private
406 *
407 * @version 7.0.4
408 */
409 private function _is_user_active($user_id)
410 {
411 $user = AAM::api()->user($user_id);
412
413 // Verify that user is active and is not expired
414 return $user->is_user_active() && !$user->is_user_access_expired();
415 }
416
417 }