Version Description
- Fixed: Revoking JWT token via UI causes current user to logout https://github.com/aamplugin/advanced-access-manager/issues/224
- Fixed: Notice: Undefined variable: cache https://github.com/aamplugin/advanced-access-manager/issues/223
- Changed: Update JWT vendor https://github.com/aamplugin/advanced-access-manager/issues/221
Download this release
Release Info
Developer | vasyltech |
Plugin | Advanced Access Manager |
Version | 6.9.0 |
Comparing to | |
See all releases |
Code changes from version 6.8.5 to 6.9.0
- aam.php +2 -2
- application/Backend/Feature/Main/Jwt.php +25 -15
- application/Core/Jwt/Issuer.php +32 -4
- application/Core/Jwt/Manager.php +667 -0
- application/Core/Redirect.php +20 -3
- application/Service/Jwt.php +90 -50
- application/Service/Toolbar.php +8 -10
- autoloader.php +14 -5
- readme.txt +6 -1
- vendor/autoload.php +1 -13
- vendor/firebase/BeforeValidException.php +0 -7
- vendor/firebase/ExpiredException.php +0 -7
- vendor/firebase/JWT.php +32 -358
- vendor/firebase/SignatureInvalidException.php +0 -7
aam.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
/**
|
4 |
* Plugin Name: Advanced Access Manager
|
5 |
* Description: Collection of features to manage your WordPress website authentication, authorization and monitoring
|
6 |
-
* Version: 6.
|
7 |
* Author: Vasyl Martyniuk <vasyl@vasyltech.com>
|
8 |
* Author URI: https://vasyltech.com
|
9 |
* Text Domain: advanced-access-manager
|
@@ -264,7 +264,7 @@ if (defined('ABSPATH')) {
|
|
264 |
// Define few common constants
|
265 |
define('AAM_MEDIA', plugins_url('/media', __FILE__));
|
266 |
define('AAM_KEY', 'advanced-access-manager');
|
267 |
-
define('AAM_VERSION', '6.
|
268 |
define('AAM_BASEDIR', __DIR__);
|
269 |
|
270 |
// Load vendor
|
3 |
/**
|
4 |
* Plugin Name: Advanced Access Manager
|
5 |
* Description: Collection of features to manage your WordPress website authentication, authorization and monitoring
|
6 |
+
* Version: 6.9.0
|
7 |
* Author: Vasyl Martyniuk <vasyl@vasyltech.com>
|
8 |
* Author URI: https://vasyltech.com
|
9 |
* Text Domain: advanced-access-manager
|
264 |
// Define few common constants
|
265 |
define('AAM_MEDIA', plugins_url('/media', __FILE__));
|
266 |
define('AAM_KEY', 'advanced-access-manager');
|
267 |
+
define('AAM_VERSION', '6.9.0');
|
268 |
define('AAM_BASEDIR', __DIR__);
|
269 |
|
270 |
// Load vendor
|
application/Backend/Feature/Main/Jwt.php
CHANGED
@@ -10,11 +10,12 @@
|
|
10 |
/**
|
11 |
* JWT UI manager
|
12 |
*
|
|
|
13 |
* @since 6.7.9 https://github.com/aamplugin/advanced-access-manager/issues/192
|
14 |
* @since 6.0.0 Initial implementation of the class
|
15 |
*
|
16 |
* @package AAM
|
17 |
-
* @version 6.
|
18 |
*/
|
19 |
class AAM_Backend_Feature_Main_Jwt
|
20 |
extends AAM_Backend_Feature_Abstract implements AAM_Backend_Feature_ISubjectAware
|
@@ -54,8 +55,11 @@ class AAM_Backend_Feature_Main_Jwt
|
|
54 |
*
|
55 |
* @return string
|
56 |
*
|
|
|
|
|
|
|
57 |
* @access public
|
58 |
-
* @version 6.
|
59 |
*/
|
60 |
public function generate()
|
61 |
{
|
@@ -75,7 +79,8 @@ class AAM_Backend_Feature_Main_Jwt
|
|
75 |
$claims = array(
|
76 |
'userId' => $user->ID,
|
77 |
'revocable' => true,
|
78 |
-
'refreshable' => ($refresh === true)
|
|
|
79 |
);
|
80 |
|
81 |
// If token also should contains the trigger action when it is expires,
|
@@ -86,13 +91,11 @@ class AAM_Backend_Feature_Main_Jwt
|
|
86 |
|
87 |
try {
|
88 |
if ($max >= AAM_Core_API::maxLevel($user->allcaps)) {
|
89 |
-
$jwt =
|
90 |
-
$claims, new DateTime('@' . $expires)
|
91 |
-
);
|
92 |
|
93 |
if ($register === true) {
|
94 |
$status = AAM_Service_Jwt::getInstance()->registerToken(
|
95 |
-
$user->ID, $jwt
|
96 |
);
|
97 |
} else {
|
98 |
$status = true;
|
@@ -100,16 +103,20 @@ class AAM_Backend_Feature_Main_Jwt
|
|
100 |
|
101 |
$result = array(
|
102 |
'status' => (!empty($status) ? 'success' : 'failure'),
|
103 |
-
'jwt'
|
104 |
);
|
105 |
} else {
|
106 |
-
$result['reason'] =
|
|
|
|
|
107 |
}
|
108 |
} catch (Exception $ex) {
|
109 |
$result['reason'] = $ex->getMessage();
|
110 |
}
|
111 |
} else {
|
112 |
-
$result['reason'] =
|
|
|
|
|
113 |
}
|
114 |
|
115 |
return wp_json_encode($result);
|
@@ -178,8 +185,11 @@ class AAM_Backend_Feature_Main_Jwt
|
|
178 |
*
|
179 |
* @return array
|
180 |
*
|
|
|
|
|
|
|
181 |
* @access protected
|
182 |
-
* @version 6.
|
183 |
*/
|
184 |
protected function retrieveList()
|
185 |
{
|
@@ -194,12 +204,12 @@ class AAM_Backend_Feature_Main_Jwt
|
|
194 |
'data' => array(),
|
195 |
);
|
196 |
|
197 |
-
$issuer =
|
198 |
|
199 |
foreach ($tokens as $token) {
|
200 |
-
$claims = $issuer->
|
201 |
|
202 |
-
if ($claims
|
203 |
$expires = new DateTime('@' . $claims->exp, new DateTimeZone('UTC'));
|
204 |
$details = $expires->format('m/d/Y, H:i O');
|
205 |
} else {
|
@@ -209,7 +219,7 @@ class AAM_Backend_Feature_Main_Jwt
|
|
209 |
$response['data'][] = array(
|
210 |
$token,
|
211 |
add_query_arg('aam-jwt', $token, site_url()),
|
212 |
-
$claims
|
213 |
$details,
|
214 |
'view,delete'
|
215 |
);
|
10 |
/**
|
11 |
* JWT UI manager
|
12 |
*
|
13 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
14 |
* @since 6.7.9 https://github.com/aamplugin/advanced-access-manager/issues/192
|
15 |
* @since 6.0.0 Initial implementation of the class
|
16 |
*
|
17 |
* @package AAM
|
18 |
+
* @version 6.9.0
|
19 |
*/
|
20 |
class AAM_Backend_Feature_Main_Jwt
|
21 |
extends AAM_Backend_Feature_Abstract implements AAM_Backend_Feature_ISubjectAware
|
55 |
*
|
56 |
* @return string
|
57 |
*
|
58 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
59 |
+
* @since 6.0.0 Initial implementation of the method
|
60 |
+
*
|
61 |
* @access public
|
62 |
+
* @version 6.9.0
|
63 |
*/
|
64 |
public function generate()
|
65 |
{
|
79 |
$claims = array(
|
80 |
'userId' => $user->ID,
|
81 |
'revocable' => true,
|
82 |
+
'refreshable' => ($refresh === true),
|
83 |
+
'exp' => (new DateTime('@' . $expires))->getTimestamp()
|
84 |
);
|
85 |
|
86 |
// If token also should contains the trigger action when it is expires,
|
91 |
|
92 |
try {
|
93 |
if ($max >= AAM_Core_API::maxLevel($user->allcaps)) {
|
94 |
+
$jwt = AAM_Core_Jwt_Manager::getInstance()->encode($claims);
|
|
|
|
|
95 |
|
96 |
if ($register === true) {
|
97 |
$status = AAM_Service_Jwt::getInstance()->registerToken(
|
98 |
+
$user->ID, $jwt
|
99 |
);
|
100 |
} else {
|
101 |
$status = true;
|
103 |
|
104 |
$result = array(
|
105 |
'status' => (!empty($status) ? 'success' : 'failure'),
|
106 |
+
'jwt' => $jwt
|
107 |
);
|
108 |
} else {
|
109 |
+
$result['reason'] = __(
|
110 |
+
'You are not allowed to generate JWT for this user', AAM_KEY
|
111 |
+
);
|
112 |
}
|
113 |
} catch (Exception $ex) {
|
114 |
$result['reason'] = $ex->getMessage();
|
115 |
}
|
116 |
} else {
|
117 |
+
$result['reason'] = __(
|
118 |
+
'You are not allowed to manage JWT tokens', AAM_KEY
|
119 |
+
);
|
120 |
}
|
121 |
|
122 |
return wp_json_encode($result);
|
185 |
*
|
186 |
* @return array
|
187 |
*
|
188 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
189 |
+
* @since 6.0.0 Initial implementation of the method
|
190 |
+
*
|
191 |
* @access protected
|
192 |
+
* @version 6.9.0
|
193 |
*/
|
194 |
protected function retrieveList()
|
195 |
{
|
204 |
'data' => array(),
|
205 |
);
|
206 |
|
207 |
+
$issuer = AAM_Core_Jwt_Manager::getInstance();
|
208 |
|
209 |
foreach ($tokens as $token) {
|
210 |
+
$claims = $issuer->validate($token);
|
211 |
|
212 |
+
if (!is_wp_error($claims)) {
|
213 |
$expires = new DateTime('@' . $claims->exp, new DateTimeZone('UTC'));
|
214 |
$details = $expires->format('m/d/Y, H:i O');
|
215 |
} else {
|
219 |
$response['data'][] = array(
|
220 |
$token,
|
221 |
add_query_arg('aam-jwt', $token, site_url()),
|
222 |
+
!is_wp_error($claims),
|
223 |
$details,
|
224 |
'view,delete'
|
225 |
);
|
application/Core/Jwt/Issuer.php
CHANGED
@@ -10,6 +10,8 @@
|
|
10 |
/**
|
11 |
* AAM JWT Issuer
|
12 |
*
|
|
|
|
|
13 |
* @since 6.7.2 https://github.com/aamplugin/advanced-access-manager/issues/165
|
14 |
* @since 6.1.0 Enriched error response with more details
|
15 |
* @since 6.0.4 Bug fixing. Timezone was handled incorrectly and ttl did not take in
|
@@ -17,7 +19,10 @@
|
|
17 |
* @since 6.0.0 Initial implementation of the class
|
18 |
*
|
19 |
* @package AAM
|
20 |
-
* @version 6.
|
|
|
|
|
|
|
21 |
*/
|
22 |
class AAM_Core_Jwt_Issuer
|
23 |
{
|
@@ -31,15 +36,22 @@ class AAM_Core_Jwt_Issuer
|
|
31 |
*
|
32 |
* @return object
|
33 |
*
|
|
|
34 |
* @since 6.1.0 Enriched error response with more details
|
35 |
* @since 6.0.4 Making sure that JWT expiration is checked with UTC timezone
|
36 |
* @since 6.0.0 Initial implementation of the method
|
37 |
*
|
38 |
* @access public
|
39 |
-
* @version 6.
|
40 |
*/
|
41 |
public function validateToken($token)
|
42 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
try {
|
44 |
$headers = $this->extractTokenHeaders($token);
|
45 |
|
@@ -98,16 +110,23 @@ class AAM_Core_Jwt_Issuer
|
|
98 |
*
|
99 |
* @return object
|
100 |
*
|
|
|
101 |
* @since 6.0.4 Fixed the bug when `authentication.jwt.expires` is defined in
|
102 |
* seconds
|
103 |
* @since 6.0.0 Initial implementation of the method
|
104 |
*
|
105 |
* @access public
|
106 |
* @throws Exception
|
107 |
-
* @version 6.
|
108 |
*/
|
109 |
public function issueToken($args = array(), $expires = null)
|
110 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
if (!empty($expires)) {
|
112 |
$time = $expires;
|
113 |
} else {
|
@@ -169,11 +188,20 @@ class AAM_Core_Jwt_Issuer
|
|
169 |
*
|
170 |
* @return object
|
171 |
*
|
|
|
|
|
|
|
172 |
* @access public
|
173 |
-
* @version 6.
|
174 |
*/
|
175 |
public function extractTokenClaims($token)
|
176 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
$parts = explode('.', $token);
|
178 |
$claims = array();
|
179 |
|
10 |
/**
|
11 |
* AAM JWT Issuer
|
12 |
*
|
13 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
14 |
+
* @since 6.8.6 https://github.com/aamplugin/advanced-access-manager/issues/221
|
15 |
* @since 6.7.2 https://github.com/aamplugin/advanced-access-manager/issues/165
|
16 |
* @since 6.1.0 Enriched error response with more details
|
17 |
* @since 6.0.4 Bug fixing. Timezone was handled incorrectly and ttl did not take in
|
19 |
* @since 6.0.0 Initial implementation of the class
|
20 |
*
|
21 |
* @package AAM
|
22 |
+
* @version 6.9.0
|
23 |
+
*
|
24 |
+
* @deprecated 6.8.5 Will be removed from AAM in 7.0.0 version
|
25 |
+
* @todo Remove this class in 7.0.0 release
|
26 |
*/
|
27 |
class AAM_Core_Jwt_Issuer
|
28 |
{
|
36 |
*
|
37 |
* @return object
|
38 |
*
|
39 |
+
* @since 6.8.6 https://github.com/aamplugin/advanced-access-manager/issues/221
|
40 |
* @since 6.1.0 Enriched error response with more details
|
41 |
* @since 6.0.4 Making sure that JWT expiration is checked with UTC timezone
|
42 |
* @since 6.0.0 Initial implementation of the method
|
43 |
*
|
44 |
* @access public
|
45 |
+
* @version 6.8.6
|
46 |
*/
|
47 |
public function validateToken($token)
|
48 |
{
|
49 |
+
_deprecated_function(
|
50 |
+
__CLASS__ . '::' . __FUNCTION__,
|
51 |
+
'6.8.5',
|
52 |
+
'AAM_Core_Jwt_Manager::validate'
|
53 |
+
);
|
54 |
+
|
55 |
try {
|
56 |
$headers = $this->extractTokenHeaders($token);
|
57 |
|
110 |
*
|
111 |
* @return object
|
112 |
*
|
113 |
+
* @since 6.8.6 https://github.com/aamplugin/advanced-access-manager/issues/221
|
114 |
* @since 6.0.4 Fixed the bug when `authentication.jwt.expires` is defined in
|
115 |
* seconds
|
116 |
* @since 6.0.0 Initial implementation of the method
|
117 |
*
|
118 |
* @access public
|
119 |
* @throws Exception
|
120 |
+
* @version 6.8.6
|
121 |
*/
|
122 |
public function issueToken($args = array(), $expires = null)
|
123 |
{
|
124 |
+
_deprecated_function(
|
125 |
+
__CLASS__ . '::' . __FUNCTION__,
|
126 |
+
'6.8.5',
|
127 |
+
'AAM_Core_Jwt_Manager::encode'
|
128 |
+
);
|
129 |
+
|
130 |
if (!empty($expires)) {
|
131 |
$time = $expires;
|
132 |
} else {
|
188 |
*
|
189 |
* @return object
|
190 |
*
|
191 |
+
* @since 6.8.6 https://github.com/aamplugin/advanced-access-manager/issues/221
|
192 |
+
* @since 6.0.0 Initial implementation of the method
|
193 |
+
*
|
194 |
* @access public
|
195 |
+
* @version 6.8.6
|
196 |
*/
|
197 |
public function extractTokenClaims($token)
|
198 |
{
|
199 |
+
_deprecated_function(
|
200 |
+
__CLASS__ . '::' . __FUNCTION__,
|
201 |
+
'6.8.5',
|
202 |
+
'AAM_Core_Jwt_Manager::decode'
|
203 |
+
);
|
204 |
+
|
205 |
$parts = explode('.', $token);
|
206 |
$claims = array();
|
207 |
|
application/Core/Jwt/Manager.php
ADDED
@@ -0,0 +1,667 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
* Majority of the work is taken from the Firebase PHP JWT library. The code was
|
14 |
+
* adopted to work with PHP 5.6.0+.
|
15 |
+
*
|
16 |
+
* @package AAM
|
17 |
+
* @since 6.9.0
|
18 |
+
*
|
19 |
+
* @link https://github.com/firebase/php-jwt
|
20 |
+
*/
|
21 |
+
class AAM_Core_Jwt_Manager
|
22 |
+
{
|
23 |
+
|
24 |
+
use AAM_Core_Contract_SingletonTrait;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* When checking nbf, iat or expiration times,
|
28 |
+
* we want to provide some extra leeway time to
|
29 |
+
* account for clock skew.
|
30 |
+
*
|
31 |
+
* @var int
|
32 |
+
* @version 6.9.0
|
33 |
+
*/
|
34 |
+
private $_leeway = 0;
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Collection of supported signing algorithms
|
38 |
+
*
|
39 |
+
* @var array
|
40 |
+
*
|
41 |
+
* @access private
|
42 |
+
* @version 6.9.0
|
43 |
+
*/
|
44 |
+
private $_supported_algs = array(
|
45 |
+
'ES384' => array('openssl', 'SHA384'),
|
46 |
+
'ES256' => array('openssl', 'SHA256'),
|
47 |
+
'HS256' => array('hash_hmac', 'SHA256'),
|
48 |
+
'HS384' => array('hash_hmac', 'SHA384'),
|
49 |
+
'HS512' => array('hash_hmac', 'SHA512'),
|
50 |
+
'RS256' => array('openssl', 'SHA256'),
|
51 |
+
'RS384' => array('openssl', 'SHA384'),
|
52 |
+
'RS512' => array('openssl', 'SHA512'),
|
53 |
+
'EdDSA' => array('sodium_crypto', 'EdDSA'),
|
54 |
+
);
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Verify that the provided token is valid
|
58 |
+
*
|
59 |
+
* @param string $token
|
60 |
+
*
|
61 |
+
* @return boolean
|
62 |
+
*
|
63 |
+
* @access public
|
64 |
+
* @version 6.9.0
|
65 |
+
*/
|
66 |
+
public function validate($token)
|
67 |
+
{
|
68 |
+
try {
|
69 |
+
// Validating headers segment. Make sure that all necessary properties
|
70 |
+
// are defined correctly
|
71 |
+
$this->validateHeaders($token);
|
72 |
+
|
73 |
+
// Get signing attributes
|
74 |
+
$attrs = $this->getSigningAttributes();
|
75 |
+
|
76 |
+
// Verify the signature
|
77 |
+
$this->validateSignature($token, $attrs->key);
|
78 |
+
|
79 |
+
$timestamp = (new DateTime('now', new DateTimeZone('UTC')))->getTimestamp();
|
80 |
+
$payload = $this->decodeSegment($token, 1);
|
81 |
+
|
82 |
+
// Check the nbf if it is defined. This is the time that the
|
83 |
+
// token can actually be used. If it's not yet that time, abort.
|
84 |
+
if (isset($payload->nbf) && $payload->nbf > ($timestamp + $this->_leeway)) {
|
85 |
+
throw new Exception(
|
86 |
+
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
|
87 |
+
);
|
88 |
+
}
|
89 |
+
|
90 |
+
// Check that this token has been created before 'now'. This prevents
|
91 |
+
// using tokens that have been created for later use (and haven't
|
92 |
+
// correctly used the nbf claim).
|
93 |
+
if (isset($payload->iat) && $payload->iat > ($timestamp + $this->_leeway)) {
|
94 |
+
throw new Exception(
|
95 |
+
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
|
96 |
+
);
|
97 |
+
}
|
98 |
+
|
99 |
+
// Check if this token has expired.
|
100 |
+
if (isset($payload->exp) && ($timestamp - $this->_leeway) >= $payload->exp) {
|
101 |
+
throw new Exception('Expired token');
|
102 |
+
}
|
103 |
+
} catch (Exception $e) {
|
104 |
+
$payload = new WP_Error(1, $e->getMessage(), $e);
|
105 |
+
}
|
106 |
+
|
107 |
+
return $payload;
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Encode payload
|
112 |
+
*
|
113 |
+
* @param array $payload
|
114 |
+
*
|
115 |
+
* @return string
|
116 |
+
* @version 6.9.0
|
117 |
+
*/
|
118 |
+
public function encode($payload)
|
119 |
+
{
|
120 |
+
$attrs = $this->getSigningAttributes();
|
121 |
+
|
122 |
+
// Encode the JWT headers first
|
123 |
+
$segments = array($this->urlsafeB64Encode($this->jsonEncode(array(
|
124 |
+
'typ' => 'JWT',
|
125 |
+
'alg' => $attrs->alg
|
126 |
+
))));
|
127 |
+
|
128 |
+
|
129 |
+
$ttl = AAM_Core_Config::get('authentication.jwt.expires', '+24 hours');
|
130 |
+
|
131 |
+
if (is_numeric($ttl)) {
|
132 |
+
$ttl = "+{$ttl} seconds";
|
133 |
+
}
|
134 |
+
|
135 |
+
$time = new DateTime($ttl, new DateTimeZone('UTC'));
|
136 |
+
|
137 |
+
$claims = apply_filters(
|
138 |
+
'aam_jwt_claims_filter',
|
139 |
+
array_merge(
|
140 |
+
array(
|
141 |
+
"iat" => time(),
|
142 |
+
'iss' => get_site_url(),
|
143 |
+
'exp' => $time->getTimestamp(),
|
144 |
+
'jti' => $this->generateUuid()
|
145 |
+
),
|
146 |
+
$payload
|
147 |
+
)
|
148 |
+
);
|
149 |
+
|
150 |
+
// Next, let's encode the payload
|
151 |
+
array_push($segments, $this->urlsafeB64Encode($this->jsonEncode($claims)));
|
152 |
+
|
153 |
+
// Adding signature to as the last segment
|
154 |
+
array_push($segments, $this->urlsafeB64Encode($this->sign(
|
155 |
+
implode('.', $segments),
|
156 |
+
$attrs->key,
|
157 |
+
$attrs->alg
|
158 |
+
)));
|
159 |
+
|
160 |
+
return implode('.', $segments);
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Decode token
|
165 |
+
*
|
166 |
+
* @param string $token
|
167 |
+
*
|
168 |
+
* @return WP_Error|stdClass
|
169 |
+
* @version 6.9.0
|
170 |
+
*/
|
171 |
+
public function decode($token)
|
172 |
+
{
|
173 |
+
return $this->validate($token, $this->getSigningAttributes()->key);
|
174 |
+
}
|
175 |
+
|
176 |
+
/**
|
177 |
+
* Generate random uuid
|
178 |
+
*
|
179 |
+
* @return string
|
180 |
+
*
|
181 |
+
* @access protected
|
182 |
+
* @version 6.9.0
|
183 |
+
*/
|
184 |
+
protected function generateUuid()
|
185 |
+
{
|
186 |
+
return sprintf(
|
187 |
+
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
188 |
+
// 32 bits for "time_low"
|
189 |
+
mt_rand(0, 0xffff),
|
190 |
+
mt_rand(0, 0xffff),
|
191 |
+
|
192 |
+
// 16 bits for "time_mid"
|
193 |
+
mt_rand(0, 0xffff),
|
194 |
+
|
195 |
+
// 16 bits for "time_hi_and_version",
|
196 |
+
// four most significant bits holds version number 4
|
197 |
+
mt_rand(0, 0x0fff) | 0x4000,
|
198 |
+
|
199 |
+
// 16 bits, 8 bits for "clk_seq_hi_res",
|
200 |
+
// 8 bits for "clk_seq_low",
|
201 |
+
// two most significant bits holds zero and one for variant DCE1.1
|
202 |
+
mt_rand(0, 0x3fff) | 0x8000,
|
203 |
+
|
204 |
+
// 48 bits for "node"
|
205 |
+
mt_rand(0, 0xffff),
|
206 |
+
mt_rand(0, 0xffff),
|
207 |
+
mt_rand(0, 0xffff)
|
208 |
+
);
|
209 |
+
}
|
210 |
+
|
211 |
+
/**
|
212 |
+
* Sign a string with a given key and algorithm.
|
213 |
+
*
|
214 |
+
* @param string $msg The message to sign
|
215 |
+
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
|
216 |
+
* @param string $alg Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
|
217 |
+
* 'HS512', 'RS256', 'RS384', and 'RS512'
|
218 |
+
*
|
219 |
+
* @return string An encrypted message
|
220 |
+
* @version 6.9.0
|
221 |
+
*
|
222 |
+
* @throws DomainException Unsupported algorithm or bad key was specified
|
223 |
+
*/
|
224 |
+
protected function sign($msg, $key, $alg)
|
225 |
+
{
|
226 |
+
$signature = null;
|
227 |
+
|
228 |
+
if (empty($this->_supported_algs[$alg])) {
|
229 |
+
throw new DomainException('Algorithm not supported');
|
230 |
+
} elseif (empty($key) || !is_string($key)) {
|
231 |
+
throw new InvalidArgumentException('The signing key must be a string');
|
232 |
+
}
|
233 |
+
|
234 |
+
list($function, $algorithm) = $this->_supported_algs[$alg];
|
235 |
+
|
236 |
+
if ($function === 'hash_hmac') {
|
237 |
+
$signature = hash_hmac($algorithm, $msg, $key, true);
|
238 |
+
} elseif ($function === 'openssl') {
|
239 |
+
$success = openssl_sign($msg, $signature, $key, $algorithm);
|
240 |
+
|
241 |
+
if (!$success) {
|
242 |
+
throw new DomainException('OpenSSL unable to sign data');
|
243 |
+
}
|
244 |
+
if ($alg === 'ES256') {
|
245 |
+
$signature = $this->signatureFromDER($signature, 256);
|
246 |
+
} elseif ($alg === 'ES384') {
|
247 |
+
$signature = $this->signatureFromDER($signature, 384);
|
248 |
+
}
|
249 |
+
} elseif ($function === 'sodium_crypto') {
|
250 |
+
if (!function_exists('sodium_crypto_sign_detached')) {
|
251 |
+
throw new DomainException('libsodium is not available');
|
252 |
+
}
|
253 |
+
|
254 |
+
// The last non-empty line is used as the key.
|
255 |
+
$lines = array_filter(explode("\n", $key));
|
256 |
+
$key = base64_decode((string) end($lines));
|
257 |
+
$signature = sodium_crypto_sign_detached($msg, $key);
|
258 |
+
} else {
|
259 |
+
throw new DomainException('Algorithm not supported');
|
260 |
+
}
|
261 |
+
|
262 |
+
return $signature;
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Encode a string with URL-safe Base64.
|
267 |
+
*
|
268 |
+
* @param string $input The string you want encoded
|
269 |
+
*
|
270 |
+
* @return string The base64 encode of what you passed in
|
271 |
+
* @version 6.9.0
|
272 |
+
*/
|
273 |
+
protected function urlsafeB64Encode($input)
|
274 |
+
{
|
275 |
+
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
|
276 |
+
}
|
277 |
+
|
278 |
+
/**
|
279 |
+
* Encode a PHP array into a JSON string.
|
280 |
+
*
|
281 |
+
* @param array<mixed> $input A PHP array
|
282 |
+
*
|
283 |
+
* @return string JSON representation of the PHP array
|
284 |
+
* @version 6.9.0
|
285 |
+
*
|
286 |
+
* @throws DomainException Provided object could not be encoded to valid JSON
|
287 |
+
*/
|
288 |
+
protected function jsonEncode(array $input): string
|
289 |
+
{
|
290 |
+
if (PHP_VERSION_ID >= 50400) {
|
291 |
+
$json = json_encode($input, JSON_UNESCAPED_SLASHES);
|
292 |
+
} else {
|
293 |
+
// PHP 5.3 only
|
294 |
+
$json = json_encode($input);
|
295 |
+
}
|
296 |
+
|
297 |
+
if ($errno = json_last_error()) {
|
298 |
+
throw new DomainException('Failed to encode JSON with error ' . $errno);
|
299 |
+
} elseif ($json === false) {
|
300 |
+
throw new DomainException('Provided object could not be encoded to valid JSON');
|
301 |
+
}
|
302 |
+
|
303 |
+
return $json;
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Get token signing attributes like algorithm and key
|
308 |
+
*
|
309 |
+
* @return stdClass
|
310 |
+
*
|
311 |
+
* @access protected
|
312 |
+
* @version 6.9.0
|
313 |
+
*/
|
314 |
+
protected function getSigningAttributes()
|
315 |
+
{
|
316 |
+
$alg = strtoupper(
|
317 |
+
AAM_Core_Config::get('authentication.jwt.algorithm', 'HS256')
|
318 |
+
);
|
319 |
+
|
320 |
+
if (strpos($alg, 'RS') === 0) {
|
321 |
+
$path = AAM_Core_Config::get('authentication.jwt.privateKeyPath');
|
322 |
+
$key = (is_readable($path) ? file_get_contents($path) : null);
|
323 |
+
$passphrase = AAM_Core_Config::get('authentication.jwt.passphrase', false);
|
324 |
+
|
325 |
+
if ($passphrase && extension_loaded('openssl')) {
|
326 |
+
$key = openssl_pkey_get_private($key, $passphrase);
|
327 |
+
}
|
328 |
+
} else {
|
329 |
+
$key = AAM_Core_Config::get('authentication.jwt.secret', SECURE_AUTH_KEY);
|
330 |
+
}
|
331 |
+
|
332 |
+
return (object) array(
|
333 |
+
'alg' => $alg,
|
334 |
+
'key' => $key
|
335 |
+
);
|
336 |
+
}
|
337 |
+
|
338 |
+
/**
|
339 |
+
* Validate token headers
|
340 |
+
*
|
341 |
+
* @param string $token
|
342 |
+
*
|
343 |
+
* @return void
|
344 |
+
*
|
345 |
+
* @access protected
|
346 |
+
* @version 6.9.0
|
347 |
+
*/
|
348 |
+
protected function validateHeaders($token)
|
349 |
+
{
|
350 |
+
$headers = $this->decodeSegment($token);
|
351 |
+
|
352 |
+
if (empty($headers->alg)) {
|
353 |
+
throw new UnexpectedValueException('Empty algorithm');
|
354 |
+
}
|
355 |
+
|
356 |
+
if (empty($this->_supported_algs[$headers->alg])) {
|
357 |
+
throw new UnexpectedValueException('Algorithm not supported');
|
358 |
+
}
|
359 |
+
}
|
360 |
+
|
361 |
+
/**
|
362 |
+
* Validate token's signature
|
363 |
+
*
|
364 |
+
* @param string $token
|
365 |
+
* @param string $key
|
366 |
+
*
|
367 |
+
* @return void
|
368 |
+
*
|
369 |
+
* @access protected
|
370 |
+
* @version 6.9.0
|
371 |
+
*/
|
372 |
+
protected function validateSignature($token, $key)
|
373 |
+
{
|
374 |
+
$headers = $this->decodeSegment($token);
|
375 |
+
$signature = $this->decodeSegment($token, 2, true);
|
376 |
+
|
377 |
+
if ($headers->alg === 'ES256' || $headers->alg === 'ES384') {
|
378 |
+
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures
|
379 |
+
$signature = $this->signatureToDER($signature);
|
380 |
+
}
|
381 |
+
|
382 |
+
// Now verifying the token
|
383 |
+
list($a, $b) = explode('.', $token);
|
384 |
+
list($function, $algorithm) = $this->_supported_algs[$headers->alg];
|
385 |
+
$msg = "{$a}.{$b}";
|
386 |
+
|
387 |
+
if ($function === 'openssl') {
|
388 |
+
$success = openssl_verify($msg, $signature, $key, $algorithm) === 1;
|
389 |
+
} else if ($function === 'sodium_crypto') {
|
390 |
+
if (!function_exists('sodium_crypto_sign_verify_detached')) {
|
391 |
+
throw new DomainException('libsodium is not available');
|
392 |
+
}
|
393 |
+
|
394 |
+
// The last non-empty line is used as the key.
|
395 |
+
$lines = array_filter(explode("\n", $key));
|
396 |
+
$key = base64_decode((string) end($lines));
|
397 |
+
$success = sodium_crypto_sign_verify_detached($signature, $msg, $key);
|
398 |
+
} else {
|
399 |
+
$hash = hash_hmac($algorithm, $msg, $key, true);
|
400 |
+
$success = $this->constantTimeEquals($hash, $signature);
|
401 |
+
}
|
402 |
+
|
403 |
+
if ($success !== true) {
|
404 |
+
throw new Exception('Invalid token signature');
|
405 |
+
}
|
406 |
+
}
|
407 |
+
|
408 |
+
/**
|
409 |
+
* Comparing hashes
|
410 |
+
*
|
411 |
+
* @param string $left The string of known length to compare against
|
412 |
+
* @param string $right The user-supplied string
|
413 |
+
*
|
414 |
+
* @return bool
|
415 |
+
*
|
416 |
+
* @access protected
|
417 |
+
* @version 6.9.0
|
418 |
+
*/
|
419 |
+
protected function constantTimeEquals($left, $right)
|
420 |
+
{
|
421 |
+
$response = false;
|
422 |
+
|
423 |
+
if (function_exists('hash_equals')) {
|
424 |
+
$response = hash_equals($left, $right);
|
425 |
+
} else {
|
426 |
+
$len = min($this->safeStrlen($left), $this->safeStrlen($right));
|
427 |
+
|
428 |
+
$status = 0;
|
429 |
+
for ($i = 0; $i < $len; $i++) {
|
430 |
+
$status |= (ord($left[$i]) ^ ord($right[$i]));
|
431 |
+
}
|
432 |
+
|
433 |
+
$status |= ($this->safeStrlen($left) ^ $this->safeStrlen($right));
|
434 |
+
|
435 |
+
$response = $status === 0;
|
436 |
+
}
|
437 |
+
|
438 |
+
return $response;
|
439 |
+
}
|
440 |
+
|
441 |
+
/**
|
442 |
+
* Get the number of bytes in cryptographic strings.
|
443 |
+
*
|
444 |
+
* @param string $str
|
445 |
+
*
|
446 |
+
* @return int
|
447 |
+
* @version 6.9.0
|
448 |
+
*/
|
449 |
+
protected function safeStrlen($str)
|
450 |
+
{
|
451 |
+
$response = 0;
|
452 |
+
|
453 |
+
if (function_exists('mb_strlen')) {
|
454 |
+
$response = mb_strlen($str, '8bit');
|
455 |
+
} else {
|
456 |
+
$response = strlen($str);
|
457 |
+
}
|
458 |
+
|
459 |
+
return $response;
|
460 |
+
}
|
461 |
+
|
462 |
+
/**
|
463 |
+
* Decode a token's segment
|
464 |
+
*
|
465 |
+
* @param string $token
|
466 |
+
* @param integer $segment
|
467 |
+
* @param boolean $returnRaw
|
468 |
+
*
|
469 |
+
* @return mixed
|
470 |
+
*
|
471 |
+
* @access protected
|
472 |
+
* @version 6.9.0
|
473 |
+
*/
|
474 |
+
protected function decodeSegment($token, $segment = 0, $returnRaw = false)
|
475 |
+
{
|
476 |
+
$segments = explode('.', $token);
|
477 |
+
|
478 |
+
if (count($segments) !== 3) {
|
479 |
+
throw new UnexpectedValueException('Wrong number of segments');
|
480 |
+
}
|
481 |
+
|
482 |
+
// Base64 decode the value
|
483 |
+
$decoded = $this->urlsafeB64Decode($segments[$segment]);
|
484 |
+
|
485 |
+
return $returnRaw ? $decoded : $this->jsonDecode($decoded);
|
486 |
+
}
|
487 |
+
|
488 |
+
/**
|
489 |
+
* Decode a string with URL-safe Base64.
|
490 |
+
*
|
491 |
+
* @param string $input A Base64 encoded string
|
492 |
+
*
|
493 |
+
* @return string A decoded string
|
494 |
+
*
|
495 |
+
* @throws InvalidArgumentException invalid base64 characters
|
496 |
+
* @version 6.9.0
|
497 |
+
*/
|
498 |
+
protected function urlsafeB64Decode($input)
|
499 |
+
{
|
500 |
+
$remainder = strlen($input) % 4;
|
501 |
+
|
502 |
+
if ($remainder) {
|
503 |
+
$input .= str_repeat('=', 4 - $remainder);
|
504 |
+
}
|
505 |
+
|
506 |
+
return base64_decode(strtr($input, '-_', '+/'));
|
507 |
+
}
|
508 |
+
|
509 |
+
/**
|
510 |
+
* Decode a JSON string into a PHP object.
|
511 |
+
*
|
512 |
+
* @param string $input JSON string
|
513 |
+
*
|
514 |
+
* @return mixed The decoded JSON string
|
515 |
+
* @version 6.9.0
|
516 |
+
*
|
517 |
+
* @throws DomainException Provided string was invalid JSON
|
518 |
+
*/
|
519 |
+
protected function jsonDecode($input)
|
520 |
+
{
|
521 |
+
$obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
|
522 |
+
|
523 |
+
if (is_array($obj)) { // Covering scenario when value is empty array
|
524 |
+
$obj = (object) $obj;
|
525 |
+
}
|
526 |
+
|
527 |
+
if ($errno = json_last_error()) {
|
528 |
+
throw new DomainException('Failed to decode JSON with error ' . $errno);
|
529 |
+
} elseif (!is_a($obj, stdClass::class)) {
|
530 |
+
throw new UnexpectedValueException('Unexpected segment value');
|
531 |
+
}
|
532 |
+
|
533 |
+
return $obj;
|
534 |
+
}
|
535 |
+
|
536 |
+
/**
|
537 |
+
* Convert an ECDSA signature to an ASN.1 DER sequence
|
538 |
+
*
|
539 |
+
* @param string $sig The ECDSA signature to convert
|
540 |
+
*
|
541 |
+
* @return string The encoded DER object
|
542 |
+
* @version 6.9.0
|
543 |
+
*/
|
544 |
+
protected function signatureToDER($sig)
|
545 |
+
{
|
546 |
+
// Separate the signature into r-value and s-value
|
547 |
+
$length = max(1, (int) (strlen($sig) / 2));
|
548 |
+
list($r, $s) = str_split($sig, $length);
|
549 |
+
|
550 |
+
// Trim leading zeros
|
551 |
+
$r = ltrim($r, "\x00");
|
552 |
+
$s = ltrim($s, "\x00");
|
553 |
+
|
554 |
+
// Convert r-value and s-value from unsigned big-endian integers to
|
555 |
+
// signed two's complement
|
556 |
+
if (ord($r[0]) > 0x7f) {
|
557 |
+
$r = "\x00" . $r;
|
558 |
+
}
|
559 |
+
|
560 |
+
if (ord($s[0]) > 0x7f) {
|
561 |
+
$s = "\x00" . $s;
|
562 |
+
}
|
563 |
+
|
564 |
+
return self::encodeDER(
|
565 |
+
0x10,
|
566 |
+
self::encodeDER(0x02, $r) .
|
567 |
+
self::encodeDER(0x02, $s)
|
568 |
+
);
|
569 |
+
}
|
570 |
+
|
571 |
+
/**
|
572 |
+
* Encodes signature from a DER object.
|
573 |
+
*
|
574 |
+
* @param string $der binary signature in DER format
|
575 |
+
* @param int $keySize the number of bits in the key
|
576 |
+
*
|
577 |
+
* @return string the signature
|
578 |
+
* @version 6.9.0
|
579 |
+
*/
|
580 |
+
protected function signatureFromDER($der, $keySize)
|
581 |
+
{
|
582 |
+
// OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
|
583 |
+
list($offset, $_) = $this->readDER($der);
|
584 |
+
list($offset, $r) = $this->readDER($der, $offset);
|
585 |
+
list($offset, $s) = $this->readDER($der, $offset);
|
586 |
+
|
587 |
+
// Convert r-value and s-value from signed two's compliment to unsigned
|
588 |
+
// big-endian integers
|
589 |
+
$r = ltrim($r, "\x00");
|
590 |
+
$s = ltrim($s, "\x00");
|
591 |
+
|
592 |
+
// Pad out r and s so that they are $keySize bits long
|
593 |
+
$r = str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
|
594 |
+
$s = str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
|
595 |
+
|
596 |
+
return $r . $s;
|
597 |
+
}
|
598 |
+
|
599 |
+
/**
|
600 |
+
* Reads binary DER-encoded data and decodes into a single object
|
601 |
+
*
|
602 |
+
* @param string $der the binary data in DER format
|
603 |
+
* @param int $offset the offset of the data stream containing the object
|
604 |
+
* to decode
|
605 |
+
*
|
606 |
+
* @return array{int, string|null} the new offset and the decoded object
|
607 |
+
* @version 6.9.0
|
608 |
+
*/
|
609 |
+
protected function readDER($der, $offset = 0)
|
610 |
+
{
|
611 |
+
$pos = $offset;
|
612 |
+
$size = strlen($der);
|
613 |
+
$constructed = (ord($der[$pos]) >> 5) & 0x01;
|
614 |
+
$type = ord($der[$pos++]) & 0x1f;
|
615 |
+
|
616 |
+
// Length
|
617 |
+
$len = ord($der[$pos++]);
|
618 |
+
if ($len & 0x80) {
|
619 |
+
$n = $len & 0x1f;
|
620 |
+
$len = 0;
|
621 |
+
while ($n-- && $pos < $size) {
|
622 |
+
$len = ($len << 8) | ord($der[$pos++]);
|
623 |
+
}
|
624 |
+
}
|
625 |
+
|
626 |
+
// Value
|
627 |
+
if ($type === 0x03) {
|
628 |
+
$pos++; // Skip the first contents octet (padding indicator)
|
629 |
+
$data = substr($der, $pos, $len - 1);
|
630 |
+
$pos += $len - 1;
|
631 |
+
} elseif (!$constructed) {
|
632 |
+
$data = substr($der, $pos, $len);
|
633 |
+
$pos += $len;
|
634 |
+
} else {
|
635 |
+
$data = null;
|
636 |
+
}
|
637 |
+
|
638 |
+
return array($pos, $data);
|
639 |
+
}
|
640 |
+
|
641 |
+
/**
|
642 |
+
* Encodes a value into a DER object.
|
643 |
+
*
|
644 |
+
* @param int $type DER tag
|
645 |
+
* @param string $value the value to encode
|
646 |
+
*
|
647 |
+
* @return string the encoded object
|
648 |
+
* @version 6.9.0
|
649 |
+
*/
|
650 |
+
protected function encodeDER($type, $value)
|
651 |
+
{
|
652 |
+
$tag_header = 0;
|
653 |
+
|
654 |
+
if ($type === 0x10) {
|
655 |
+
$tag_header |= 0x20;
|
656 |
+
}
|
657 |
+
|
658 |
+
// Type
|
659 |
+
$der = chr($tag_header | $type);
|
660 |
+
|
661 |
+
// Length
|
662 |
+
$der .= chr(strlen($value));
|
663 |
+
|
664 |
+
return $der . $value;
|
665 |
+
}
|
666 |
+
|
667 |
+
}
|
application/Core/Redirect.php
CHANGED
@@ -100,7 +100,12 @@ class AAM_Core_Redirect
|
|
100 |
array('reason' => 'restricted'),
|
101 |
wp_login_url(AAM_Core_Request::server('REQUEST_URI'))
|
102 |
));
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
104 |
}
|
105 |
|
106 |
/**
|
@@ -125,7 +130,13 @@ class AAM_Core_Redirect
|
|
125 |
$code = !empty($meta['code']) ? $meta['code'] : 307;
|
126 |
|
127 |
if (!empty($dest) && (empty($current) || ($current->ID !== intval($dest)))) {
|
128 |
-
wp_safe_redirect(get_page_link($dest), $code);
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
}
|
130 |
}
|
131 |
|
@@ -151,7 +162,13 @@ class AAM_Core_Redirect
|
|
151 |
$code = !empty($meta['code']) ? $meta['code'] : 307;
|
152 |
|
153 |
if ($dest !== AAM_Core_Request::server('REQUEST_URI')) {
|
154 |
-
wp_safe_redirect($dest, $code);
|
|
|
|
|
|
|
|
|
|
|
|
|
155 |
}
|
156 |
}
|
157 |
|
100 |
array('reason' => 'restricted'),
|
101 |
wp_login_url(AAM_Core_Request::server('REQUEST_URI'))
|
102 |
));
|
103 |
+
|
104 |
+
// Halt the execution. Redirect should carry user away if this is not
|
105 |
+
// a CLI execution (e.g. Unit Test)
|
106 |
+
if (php_sapi_name() !== 'cli') {
|
107 |
+
exit;
|
108 |
+
}
|
109 |
}
|
110 |
|
111 |
/**
|
130 |
$code = !empty($meta['code']) ? $meta['code'] : 307;
|
131 |
|
132 |
if (!empty($dest) && (empty($current) || ($current->ID !== intval($dest)))) {
|
133 |
+
wp_safe_redirect(get_page_link($dest), $code);
|
134 |
+
|
135 |
+
// Halt the execution. Redirect should carry user away if this is not
|
136 |
+
// a CLI execution (e.g. Unit Test)
|
137 |
+
if (php_sapi_name() !== 'cli') {
|
138 |
+
exit;
|
139 |
+
}
|
140 |
}
|
141 |
}
|
142 |
|
162 |
$code = !empty($meta['code']) ? $meta['code'] : 307;
|
163 |
|
164 |
if ($dest !== AAM_Core_Request::server('REQUEST_URI')) {
|
165 |
+
wp_safe_redirect($dest, $code);
|
166 |
+
|
167 |
+
// Halt the execution. Redirect should carry user away if this is not
|
168 |
+
// a CLI execution (e.g. Unit Test)
|
169 |
+
if (php_sapi_name() !== 'cli') {
|
170 |
+
exit;
|
171 |
+
}
|
172 |
}
|
173 |
}
|
174 |
|
application/Service/Jwt.php
CHANGED
@@ -10,6 +10,8 @@
|
|
10 |
/**
|
11 |
* JWT Token service
|
12 |
*
|
|
|
|
|
13 |
* @since 6.6.2 https://github.com/aamplugin/advanced-access-manager/issues/139
|
14 |
* @since 6.6.1 https://github.com/aamplugin/advanced-access-manager/issues/136
|
15 |
* @since 6.6.0 https://github.com/aamplugin/advanced-access-manager/issues/129
|
@@ -26,7 +28,7 @@
|
|
26 |
* @since 6.0.0 Initial implementation of the class
|
27 |
*
|
28 |
* @package AAM
|
29 |
-
* @version 6.
|
30 |
*/
|
31 |
class AAM_Service_Jwt
|
32 |
{
|
@@ -255,24 +257,40 @@ class AAM_Service_Jwt
|
|
255 |
*
|
256 |
* @return WP_REST_Response
|
257 |
*
|
|
|
258 |
* @since 6.1.0 Enriched error response with more details
|
259 |
* @since 6.0.0 Initial implementation of the method
|
260 |
*
|
261 |
* @access public
|
262 |
-
* @version 6.
|
263 |
*/
|
264 |
public function validateToken(WP_REST_Request $request)
|
265 |
{
|
266 |
$jwt = $request->get_param('jwt');
|
267 |
-
$result =
|
268 |
|
269 |
-
if ($result
|
270 |
$response = new WP_REST_Response($result);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
271 |
} else {
|
272 |
$response = new WP_REST_Response(array(
|
273 |
'code' => 'rest_jwt_validation_failure',
|
274 |
-
'reason' => $result->
|
275 |
-
),
|
276 |
}
|
277 |
|
278 |
return $response;
|
@@ -287,7 +305,7 @@ class AAM_Service_Jwt
|
|
287 |
*
|
288 |
* @return WP_REST_Response
|
289 |
* @version 6.0.0
|
290 |
-
* @todo Remove in
|
291 |
*/
|
292 |
public function refreshTokenDeprecated(WP_REST_Request $request)
|
293 |
{
|
@@ -303,18 +321,19 @@ class AAM_Service_Jwt
|
|
303 |
*
|
304 |
* @return WP_REST_Response
|
305 |
*
|
|
|
306 |
* @since 6.1.0 Enriched error response with more details
|
307 |
* @since 6.0.0 Initial implementation of the method
|
308 |
*
|
309 |
* @access public
|
310 |
-
* @version 6.
|
311 |
*/
|
312 |
public function refreshToken(WP_REST_Request $request)
|
313 |
{
|
314 |
$jwt = $request->get_param('jwt');
|
315 |
-
$result =
|
316 |
|
317 |
-
if ($result
|
318 |
if (!empty($result->refreshable)) {
|
319 |
// calculate the new expiration
|
320 |
$issuedAt = new DateTime();
|
@@ -324,11 +343,11 @@ class AAM_Service_Jwt
|
|
324 |
$exp = new DateTime();
|
325 |
$exp->add($issuedAt->diff($expires));
|
326 |
|
327 |
-
$
|
328 |
|
329 |
$response = new WP_REST_Response(array(
|
330 |
-
'token' => $
|
331 |
-
'token_expires' => $
|
332 |
));
|
333 |
} else {
|
334 |
$response = new WP_REST_Response(array(
|
@@ -339,8 +358,8 @@ class AAM_Service_Jwt
|
|
339 |
} else {
|
340 |
$response = new WP_REST_Response(array(
|
341 |
'code' => 'rest_jwt_validation_failure',
|
342 |
-
'reason' => $result->
|
343 |
-
),
|
344 |
}
|
345 |
|
346 |
return $response;
|
@@ -353,18 +372,19 @@ class AAM_Service_Jwt
|
|
353 |
*
|
354 |
* @return WP_REST_Response
|
355 |
*
|
|
|
356 |
* @since 6.1.0 Enriched error response with more details
|
357 |
* @since 6.0.0 Initial implementation of the method
|
358 |
*
|
359 |
* @access public
|
360 |
-
* @version 6.
|
361 |
*/
|
362 |
public function revokeToken(WP_REST_Request $request)
|
363 |
{
|
364 |
$jwt = $request->get_param('jwt');
|
365 |
-
$claims =
|
366 |
|
367 |
-
if ($claims
|
368 |
if ($this->revokeUserToken($claims->userId, $jwt)) {
|
369 |
$response = new WP_REST_Response(
|
370 |
array('message' => 'Token revoked successfully'), 200
|
@@ -378,8 +398,8 @@ class AAM_Service_Jwt
|
|
378 |
} else {
|
379 |
$response = new WP_REST_Response(array(
|
380 |
'code' => 'rest_jwt_validation_failure',
|
381 |
-
'reason' => $claims->
|
382 |
-
),
|
383 |
}
|
384 |
|
385 |
return $response;
|
@@ -397,13 +417,14 @@ class AAM_Service_Jwt
|
|
397 |
*
|
398 |
* @return array
|
399 |
*
|
|
|
400 |
* @since 6.6.2 https://github.com/aamplugin/advanced-access-manager/issues/139
|
401 |
* @since 6.6.0 https://github.com/aamplugin/advanced-access-manager/issues/100
|
402 |
* @since 6.4.0 Added the ability to issue refreshable token
|
403 |
* @since 6.0.0 Initial implementation of the method
|
404 |
*
|
405 |
* @access public
|
406 |
-
* @version 6.
|
407 |
*/
|
408 |
public function prepareLoginResponse(
|
409 |
array $response, WP_REST_Request $request, $user
|
@@ -435,8 +456,7 @@ class AAM_Service_Jwt
|
|
435 |
$jwt = $this->issueToken($user->ID, null, null, $refreshable);
|
436 |
|
437 |
$response['jwt'] = array(
|
438 |
-
'token'
|
439 |
-
'token_expires' => $jwt->claims['exp']
|
440 |
);
|
441 |
}
|
442 |
|
@@ -446,15 +466,18 @@ class AAM_Service_Jwt
|
|
446 |
/**
|
447 |
* Issue JWT token
|
448 |
*
|
449 |
-
* @param int
|
450 |
-
* @param string
|
451 |
-
* @param
|
452 |
-
* @param boolean
|
453 |
*
|
454 |
* @return object
|
455 |
*
|
|
|
|
|
|
|
456 |
* @access public
|
457 |
-
* @version 6.
|
458 |
*/
|
459 |
public function issueToken(
|
460 |
$userId,
|
@@ -462,19 +485,22 @@ class AAM_Service_Jwt
|
|
462 |
$expires = null,
|
463 |
$refreshable = false
|
464 |
) {
|
465 |
-
$
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
'refreshable' => $refreshable
|
470 |
-
),
|
471 |
-
$expires
|
472 |
);
|
473 |
|
|
|
|
|
|
|
|
|
|
|
|
|
474 |
// Finally register token so it can be revoked
|
475 |
-
$this->registerToken($userId, $
|
476 |
|
477 |
-
return $
|
478 |
}
|
479 |
|
480 |
/**
|
@@ -548,11 +574,12 @@ class AAM_Service_Jwt
|
|
548 |
*
|
549 |
* @return bool
|
550 |
*
|
|
|
551 |
* @since 6.6.0 https://github.com/aamplugin/advanced-access-manager/issues/118
|
552 |
* @since 6.0.0 Initial implementation of the method
|
553 |
*
|
554 |
* @access public
|
555 |
-
* @version 6.
|
556 |
*/
|
557 |
public function revokeUserToken($userId, $token)
|
558 |
{
|
@@ -561,7 +588,7 @@ class AAM_Service_Jwt
|
|
561 |
foreach($this->getTokenRegistry($userId) as $item) {
|
562 |
if ($token !== $item) {
|
563 |
$filtered[] = $item;
|
564 |
-
}
|
565 |
// Also delete user session if any is active. The downside here is
|
566 |
// that if user logged in with different token, he still is going to
|
567 |
// be logged out because AAM does not track the token that user used
|
@@ -581,8 +608,11 @@ class AAM_Service_Jwt
|
|
581 |
*
|
582 |
* @return int
|
583 |
*
|
|
|
|
|
|
|
584 |
* @access public
|
585 |
-
* @version 6.
|
586 |
*/
|
587 |
public function determineUser($userId)
|
588 |
{
|
@@ -590,9 +620,9 @@ class AAM_Service_Jwt
|
|
590 |
$token = $this->extractToken();
|
591 |
|
592 |
if (!empty($token)) {
|
593 |
-
$result =
|
594 |
|
595 |
-
if ($result
|
596 |
// Verify that user is can be logged in
|
597 |
$user = apply_filters(
|
598 |
'aam_verify_user_filter', new WP_User($result->userId)
|
@@ -621,19 +651,24 @@ class AAM_Service_Jwt
|
|
621 |
*
|
622 |
* @return mixed
|
623 |
*
|
|
|
|
|
|
|
624 |
* @access public
|
625 |
-
* @version 6.
|
626 |
*/
|
627 |
public function getJwtClaim($value, $prop)
|
628 |
{
|
629 |
$token = $this->extractToken();
|
630 |
|
631 |
if ($token) {
|
632 |
-
$claims =
|
633 |
$token->jwt
|
634 |
);
|
635 |
|
636 |
-
|
|
|
|
|
637 |
}
|
638 |
|
639 |
return $value;
|
@@ -644,22 +679,27 @@ class AAM_Service_Jwt
|
|
644 |
*
|
645 |
* @return void
|
646 |
*
|
|
|
647 |
* @since 6.5.2 Fixed https://github.com/aamplugin/advanced-access-manager/issues/117
|
648 |
* @since 6.5.0 Fixed https://github.com/aamplugin/advanced-access-manager/issues/98
|
649 |
* @since 6.0.0 Initial implementation of the method
|
650 |
*
|
651 |
* @access public
|
652 |
-
* @version 6.
|
653 |
*/
|
654 |
public function authenticateUser()
|
655 |
{
|
656 |
$token = $this->extractToken();
|
657 |
-
$claims =
|
658 |
|
659 |
-
|
660 |
-
|
|
|
|
|
|
|
|
|
661 |
|
662 |
-
if (!is_wp_error($user)) {
|
663 |
wp_set_current_user($claims->userId);
|
664 |
wp_set_auth_cookie($claims->userId);
|
665 |
|
10 |
/**
|
11 |
* JWT Token service
|
12 |
*
|
13 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
14 |
+
* https://github.com/aamplugin/advanced-access-manager/issues/224
|
15 |
* @since 6.6.2 https://github.com/aamplugin/advanced-access-manager/issues/139
|
16 |
* @since 6.6.1 https://github.com/aamplugin/advanced-access-manager/issues/136
|
17 |
* @since 6.6.0 https://github.com/aamplugin/advanced-access-manager/issues/129
|
28 |
* @since 6.0.0 Initial implementation of the class
|
29 |
*
|
30 |
* @package AAM
|
31 |
+
* @version 6.9.0
|
32 |
*/
|
33 |
class AAM_Service_Jwt
|
34 |
{
|
257 |
*
|
258 |
* @return WP_REST_Response
|
259 |
*
|
260 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
261 |
* @since 6.1.0 Enriched error response with more details
|
262 |
* @since 6.0.0 Initial implementation of the method
|
263 |
*
|
264 |
* @access public
|
265 |
+
* @version 6.9.0
|
266 |
*/
|
267 |
public function validateToken(WP_REST_Request $request)
|
268 |
{
|
269 |
$jwt = $request->get_param('jwt');
|
270 |
+
$result = AAM_Core_Jwt_Manager::getInstance()->validate($jwt);
|
271 |
|
272 |
+
if (!is_wp_error($result)) {
|
273 |
$response = new WP_REST_Response($result);
|
274 |
+
|
275 |
+
// If token is "revocable", make sure that claimed user still has
|
276 |
+
// the token in the meta
|
277 |
+
if (!empty($result->revocable)) {
|
278 |
+
$tokens = get_user_option(
|
279 |
+
AAM_Service_Jwt::DB_OPTION, $result->userId
|
280 |
+
);
|
281 |
+
|
282 |
+
if (!is_array($tokens) || !in_array($jwt, $tokens, true)) {
|
283 |
+
$response = new WP_REST_Response(array(
|
284 |
+
'code' => 'rest_jwt_validation_failure',
|
285 |
+
'reason' => __('Token has been revoked', AAM_KEY)
|
286 |
+
), 410);
|
287 |
+
}
|
288 |
+
}
|
289 |
} else {
|
290 |
$response = new WP_REST_Response(array(
|
291 |
'code' => 'rest_jwt_validation_failure',
|
292 |
+
'reason' => $result->get_error_message()
|
293 |
+
), 400);
|
294 |
}
|
295 |
|
296 |
return $response;
|
305 |
*
|
306 |
* @return WP_REST_Response
|
307 |
* @version 6.0.0
|
308 |
+
* @todo Remove in 7.0.0
|
309 |
*/
|
310 |
public function refreshTokenDeprecated(WP_REST_Request $request)
|
311 |
{
|
321 |
*
|
322 |
* @return WP_REST_Response
|
323 |
*
|
324 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
325 |
* @since 6.1.0 Enriched error response with more details
|
326 |
* @since 6.0.0 Initial implementation of the method
|
327 |
*
|
328 |
* @access public
|
329 |
+
* @version 6.9.0
|
330 |
*/
|
331 |
public function refreshToken(WP_REST_Request $request)
|
332 |
{
|
333 |
$jwt = $request->get_param('jwt');
|
334 |
+
$result = AAM_Core_Jwt_Manager::getInstance()->validate($jwt);
|
335 |
|
336 |
+
if (!is_wp_error($result)) {
|
337 |
if (!empty($result->refreshable)) {
|
338 |
// calculate the new expiration
|
339 |
$issuedAt = new DateTime();
|
343 |
$exp = new DateTime();
|
344 |
$exp->add($issuedAt->diff($expires));
|
345 |
|
346 |
+
$token = $this->issueToken($result->userId, $jwt, $exp, true);
|
347 |
|
348 |
$response = new WP_REST_Response(array(
|
349 |
+
'token' => $token,
|
350 |
+
'token_expires' => $exp->getTimestamp(),
|
351 |
));
|
352 |
} else {
|
353 |
$response = new WP_REST_Response(array(
|
358 |
} else {
|
359 |
$response = new WP_REST_Response(array(
|
360 |
'code' => 'rest_jwt_validation_failure',
|
361 |
+
'reason' => $result->get_error_message()
|
362 |
+
), 400);
|
363 |
}
|
364 |
|
365 |
return $response;
|
372 |
*
|
373 |
* @return WP_REST_Response
|
374 |
*
|
375 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
376 |
* @since 6.1.0 Enriched error response with more details
|
377 |
* @since 6.0.0 Initial implementation of the method
|
378 |
*
|
379 |
* @access public
|
380 |
+
* @version 6.9.0
|
381 |
*/
|
382 |
public function revokeToken(WP_REST_Request $request)
|
383 |
{
|
384 |
$jwt = $request->get_param('jwt');
|
385 |
+
$claims = AAM_Core_Jwt_Manager::getInstance()->validate($jwt);
|
386 |
|
387 |
+
if (!is_wp_error($claims)) {
|
388 |
if ($this->revokeUserToken($claims->userId, $jwt)) {
|
389 |
$response = new WP_REST_Response(
|
390 |
array('message' => 'Token revoked successfully'), 200
|
398 |
} else {
|
399 |
$response = new WP_REST_Response(array(
|
400 |
'code' => 'rest_jwt_validation_failure',
|
401 |
+
'reason' => $claims->get_error_message()
|
402 |
+
), 400);
|
403 |
}
|
404 |
|
405 |
return $response;
|
417 |
*
|
418 |
* @return array
|
419 |
*
|
420 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
421 |
* @since 6.6.2 https://github.com/aamplugin/advanced-access-manager/issues/139
|
422 |
* @since 6.6.0 https://github.com/aamplugin/advanced-access-manager/issues/100
|
423 |
* @since 6.4.0 Added the ability to issue refreshable token
|
424 |
* @since 6.0.0 Initial implementation of the method
|
425 |
*
|
426 |
* @access public
|
427 |
+
* @version 6.9.0
|
428 |
*/
|
429 |
public function prepareLoginResponse(
|
430 |
array $response, WP_REST_Request $request, $user
|
456 |
$jwt = $this->issueToken($user->ID, null, null, $refreshable);
|
457 |
|
458 |
$response['jwt'] = array(
|
459 |
+
'token' => $jwt
|
|
|
460 |
);
|
461 |
}
|
462 |
|
466 |
/**
|
467 |
* Issue JWT token
|
468 |
*
|
469 |
+
* @param int $userId
|
470 |
+
* @param string $replace
|
471 |
+
* @param DateTime $expires
|
472 |
+
* @param boolean $refreshable
|
473 |
*
|
474 |
* @return object
|
475 |
*
|
476 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
477 |
+
* @since 6.0.0 Initial implementation of the method
|
478 |
+
*
|
479 |
* @access public
|
480 |
+
* @version 6.9.0
|
481 |
*/
|
482 |
public function issueToken(
|
483 |
$userId,
|
485 |
$expires = null,
|
486 |
$refreshable = false
|
487 |
) {
|
488 |
+
$claims = array(
|
489 |
+
'userId' => $userId,
|
490 |
+
'revocable' => true,
|
491 |
+
'refreshable' => $refreshable
|
|
|
|
|
|
|
492 |
);
|
493 |
|
494 |
+
if (is_a($expires, DateTime::class)) {
|
495 |
+
$claims['exp'] = $expires->getTimestamp();
|
496 |
+
}
|
497 |
+
|
498 |
+
$token = AAM_Core_Jwt_Manager::getInstance()->encode($claims);
|
499 |
+
|
500 |
// Finally register token so it can be revoked
|
501 |
+
$this->registerToken($userId, $token, $replace);
|
502 |
|
503 |
+
return $token;
|
504 |
}
|
505 |
|
506 |
/**
|
574 |
*
|
575 |
* @return bool
|
576 |
*
|
577 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/224
|
578 |
* @since 6.6.0 https://github.com/aamplugin/advanced-access-manager/issues/118
|
579 |
* @since 6.0.0 Initial implementation of the method
|
580 |
*
|
581 |
* @access public
|
582 |
+
* @version 6.9.0
|
583 |
*/
|
584 |
public function revokeUserToken($userId, $token)
|
585 |
{
|
588 |
foreach($this->getTokenRegistry($userId) as $item) {
|
589 |
if ($token !== $item) {
|
590 |
$filtered[] = $item;
|
591 |
+
} elseif (get_current_user_id() !== $userId) {
|
592 |
// Also delete user session if any is active. The downside here is
|
593 |
// that if user logged in with different token, he still is going to
|
594 |
// be logged out because AAM does not track the token that user used
|
608 |
*
|
609 |
* @return int
|
610 |
*
|
611 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
612 |
+
* @since 6.0.0 Initial implementation of the method
|
613 |
+
*
|
614 |
* @access public
|
615 |
+
* @version 6.9.0
|
616 |
*/
|
617 |
public function determineUser($userId)
|
618 |
{
|
620 |
$token = $this->extractToken();
|
621 |
|
622 |
if (!empty($token)) {
|
623 |
+
$result = AAM_Core_Jwt_Manager::getInstance()->validate($token->jwt);
|
624 |
|
625 |
+
if (!is_wp_error($result)) {
|
626 |
// Verify that user is can be logged in
|
627 |
$user = apply_filters(
|
628 |
'aam_verify_user_filter', new WP_User($result->userId)
|
651 |
*
|
652 |
* @return mixed
|
653 |
*
|
654 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
655 |
+
* @since 6.0.0 Initial implementation of the method
|
656 |
+
*
|
657 |
* @access public
|
658 |
+
* @version 6.9.0
|
659 |
*/
|
660 |
public function getJwtClaim($value, $prop)
|
661 |
{
|
662 |
$token = $this->extractToken();
|
663 |
|
664 |
if ($token) {
|
665 |
+
$claims = AAM_Core_Jwt_Manager::getInstance()->validate(
|
666 |
$token->jwt
|
667 |
);
|
668 |
|
669 |
+
if (!is_wp_error($claims)) {
|
670 |
+
$value = (property_exists($claims, $prop) ? $claims->$prop : null);
|
671 |
+
}
|
672 |
}
|
673 |
|
674 |
return $value;
|
679 |
*
|
680 |
* @return void
|
681 |
*
|
682 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
683 |
* @since 6.5.2 Fixed https://github.com/aamplugin/advanced-access-manager/issues/117
|
684 |
* @since 6.5.0 Fixed https://github.com/aamplugin/advanced-access-manager/issues/98
|
685 |
* @since 6.0.0 Initial implementation of the method
|
686 |
*
|
687 |
* @access public
|
688 |
+
* @version 6.9.0
|
689 |
*/
|
690 |
public function authenticateUser()
|
691 |
{
|
692 |
$token = $this->extractToken();
|
693 |
+
$claims = AAM_Core_Jwt_Manager::getInstance()->validate($token->jwt);
|
694 |
|
695 |
+
if (!is_wp_error($claims)) {
|
696 |
+
// Check if account is active
|
697 |
+
$user = apply_filters(
|
698 |
+
'aam_verify_user_filter', new WP_User($claims->userId)
|
699 |
+
);
|
700 |
+
}
|
701 |
|
702 |
+
if (isset($user) && !is_wp_error($user)) {
|
703 |
wp_set_current_user($claims->userId);
|
704 |
wp_set_auth_cookie($claims->userId);
|
705 |
|
application/Service/Toolbar.php
CHANGED
@@ -10,11 +10,12 @@
|
|
10 |
/**
|
11 |
* Toolbar service
|
12 |
*
|
13 |
-
* @since 6.
|
|
|
14 |
* @since 6.0.0 Initial implementation of the class
|
15 |
*
|
16 |
* @package AAM
|
17 |
-
* @version 6.
|
18 |
*/
|
19 |
class AAM_Service_Toolbar
|
20 |
{
|
@@ -86,15 +87,19 @@ class AAM_Service_Toolbar
|
|
86 |
*
|
87 |
* @return void
|
88 |
*
|
|
|
|
|
|
|
89 |
* @access public
|
90 |
* @global object $wp_admin_bar
|
91 |
-
* @version 6.
|
92 |
*/
|
93 |
public function cacheAdminBar()
|
94 |
{
|
95 |
global $wp_admin_bar;
|
96 |
|
97 |
$reflection = new ReflectionClass(get_class($wp_admin_bar));
|
|
|
98 |
|
99 |
if ($reflection->hasProperty('nodes')) {
|
100 |
$prop = $reflection->getProperty('nodes');
|
@@ -103,7 +108,6 @@ class AAM_Service_Toolbar
|
|
103 |
$nodes = $prop->getValue($wp_admin_bar);
|
104 |
|
105 |
if (isset($nodes['root'])) {
|
106 |
-
$cache = array();
|
107 |
foreach ($nodes['root']->children as $node) {
|
108 |
$cache = array_merge($cache, $node->children);
|
109 |
}
|
@@ -116,12 +120,6 @@ class AAM_Service_Toolbar
|
|
116 |
}
|
117 |
AAM_Core_API::updateOption(self::DB_OPTION, $cache);
|
118 |
}
|
119 |
-
} else {
|
120 |
-
_doing_it_wrong(
|
121 |
-
__CLASS__ . '::' . __METHOD__,
|
122 |
-
'Toolbar object does not have "nodes" property',
|
123 |
-
AAM_VERSION
|
124 |
-
);
|
125 |
}
|
126 |
|
127 |
return $cache;
|
10 |
/**
|
11 |
* Toolbar service
|
12 |
*
|
13 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/223
|
14 |
+
* @since 6.4.0 https://github.com/aamplugin/advanced-access-manager/issues/76
|
15 |
* @since 6.0.0 Initial implementation of the class
|
16 |
*
|
17 |
* @package AAM
|
18 |
+
* @version 6.9.0
|
19 |
*/
|
20 |
class AAM_Service_Toolbar
|
21 |
{
|
87 |
*
|
88 |
* @return void
|
89 |
*
|
90 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/223
|
91 |
+
* @since 6.0.0 Initial implementation of the method
|
92 |
+
*
|
93 |
* @access public
|
94 |
* @global object $wp_admin_bar
|
95 |
+
* @version 6.9.0
|
96 |
*/
|
97 |
public function cacheAdminBar()
|
98 |
{
|
99 |
global $wp_admin_bar;
|
100 |
|
101 |
$reflection = new ReflectionClass(get_class($wp_admin_bar));
|
102 |
+
$cache = array();
|
103 |
|
104 |
if ($reflection->hasProperty('nodes')) {
|
105 |
$prop = $reflection->getProperty('nodes');
|
108 |
$nodes = $prop->getValue($wp_admin_bar);
|
109 |
|
110 |
if (isset($nodes['root'])) {
|
|
|
111 |
foreach ($nodes['root']->children as $node) {
|
112 |
$cache = array_merge($cache, $node->children);
|
113 |
}
|
120 |
}
|
121 |
AAM_Core_API::updateOption(self::DB_OPTION, $cache);
|
122 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
}
|
124 |
|
125 |
return $cache;
|
autoloader.php
CHANGED
@@ -5,15 +5,17 @@
|
|
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 |
-
* @version 6.0.0
|
10 |
*/
|
11 |
|
12 |
/**
|
13 |
* Project auto-loader
|
14 |
*
|
15 |
* @package AAM
|
16 |
-
*
|
|
|
|
|
|
|
|
|
17 |
*/
|
18 |
class AAM_Autoloader
|
19 |
{
|
@@ -23,10 +25,17 @@ class AAM_Autoloader
|
|
23 |
*
|
24 |
* @var array
|
25 |
*
|
|
|
|
|
|
|
26 |
* @access protected
|
27 |
-
* @version 6.
|
|
|
|
|
28 |
*/
|
29 |
-
protected static $class_map = array(
|
|
|
|
|
30 |
|
31 |
/**
|
32 |
* Add new index
|
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 |
* Project auto-loader
|
12 |
*
|
13 |
* @package AAM
|
14 |
+
*
|
15 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
16 |
+
* @since 6.0.0 Initial implementation of the class
|
17 |
+
*
|
18 |
+
* @version 6.9.0
|
19 |
*/
|
20 |
class AAM_Autoloader
|
21 |
{
|
25 |
*
|
26 |
* @var array
|
27 |
*
|
28 |
+
* @since 6.9.0 https://github.com/aamplugin/advanced-access-manager/issues/221
|
29 |
+
* @since 6.0.0 Initial implementation of the property
|
30 |
+
*
|
31 |
* @access protected
|
32 |
+
* @version 6.9.0
|
33 |
+
*
|
34 |
+
* @todo Remove in 7.0.0
|
35 |
*/
|
36 |
+
protected static $class_map = array(
|
37 |
+
'Firebase\JWT\JWT' => __DIR__ . '/firebase/JWT.php',
|
38 |
+
);
|
39 |
|
40 |
/**
|
41 |
* Add new index
|
readme.txt
CHANGED
@@ -4,7 +4,7 @@ Tags: access control, membership, backend menu, user role, restricted content, s
|
|
4 |
Requires at least: 4.7.0
|
5 |
Requires PHP: 5.6.0
|
6 |
Tested up to: 6.0.2
|
7 |
-
Stable tag: 6.
|
8 |
|
9 |
All you need to manage access to WordPress websites on the frontend, backend and API levels for any role, user or visitors.
|
10 |
|
@@ -91,6 +91,11 @@ We take security and privacy very seriously, that is why there are several non-n
|
|
91 |
|
92 |
== Changelog ==
|
93 |
|
|
|
|
|
|
|
|
|
|
|
94 |
= 6.8.5 =
|
95 |
* Fixed: Redirect may not always work [https://github.com/aamplugin/advanced-access-manager/issues/214](https://github.com/aamplugin/advanced-access-manager/issues/214)
|
96 |
* Fixed: PHP Notice: Undefined index: 404.redirect.** [https://github.com/aamplugin/advanced-access-manager/issues/215](https://github.com/aamplugin/advanced-access-manager/issues/215)
|
4 |
Requires at least: 4.7.0
|
5 |
Requires PHP: 5.6.0
|
6 |
Tested up to: 6.0.2
|
7 |
+
Stable tag: 6.9.0
|
8 |
|
9 |
All you need to manage access to WordPress websites on the frontend, backend and API levels for any role, user or visitors.
|
10 |
|
91 |
|
92 |
== Changelog ==
|
93 |
|
94 |
+
= 6.9.0 =
|
95 |
+
* Fixed: Revoking JWT token via UI causes current user to logout [https://github.com/aamplugin/advanced-access-manager/issues/224](https://github.com/aamplugin/advanced-access-manager/issues/224)
|
96 |
+
* Fixed: Notice: Undefined variable: cache [https://github.com/aamplugin/advanced-access-manager/issues/223](https://github.com/aamplugin/advanced-access-manager/issues/223)
|
97 |
+
* Changed: Update JWT vendor [https://github.com/aamplugin/advanced-access-manager/issues/221](https://github.com/aamplugin/advanced-access-manager/issues/221)
|
98 |
+
|
99 |
= 6.8.5 =
|
100 |
* Fixed: Redirect may not always work [https://github.com/aamplugin/advanced-access-manager/issues/214](https://github.com/aamplugin/advanced-access-manager/issues/214)
|
101 |
* Fixed: PHP Notice: Undefined index: 404.redirect.** [https://github.com/aamplugin/advanced-access-manager/issues/215](https://github.com/aamplugin/advanced-access-manager/issues/215)
|
vendor/autoload.php
CHANGED
@@ -7,18 +7,6 @@
|
|
7 |
* ======================================================================
|
8 |
*/
|
9 |
|
10 |
-
// Firebase for JWT token handling
|
11 |
-
if (!class_exists('Firebase\JWT')) {
|
12 |
-
spl_autoload_register(function($class_name) {
|
13 |
-
if (strpos($class_name, 'Firebase\JWT') === 0) {
|
14 |
-
require __DIR__ . '/firebase/BeforeValidException.php';
|
15 |
-
require __DIR__ . '/firebase/ExpiredException.php';
|
16 |
-
require __DIR__ . '/firebase/SignatureInvalidException.php';
|
17 |
-
require __DIR__ . '/firebase/JWT.php';
|
18 |
-
}
|
19 |
-
});
|
20 |
-
}
|
21 |
-
|
22 |
//Composer Semver for Policy dependency versioning
|
23 |
if (!class_exists('Composer\Semver')) {
|
24 |
spl_autoload_register(function($class_name) {
|
@@ -32,7 +20,7 @@ if (!class_exists('Composer\Semver')) {
|
|
32 |
}
|
33 |
|
34 |
if (!empty($filename) && file_exists($filename)) {
|
35 |
-
|
36 |
}
|
37 |
});
|
38 |
}
|
7 |
* ======================================================================
|
8 |
*/
|
9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
//Composer Semver for Policy dependency versioning
|
11 |
if (!class_exists('Composer\Semver')) {
|
12 |
spl_autoload_register(function($class_name) {
|
20 |
}
|
21 |
|
22 |
if (!empty($filename) && file_exists($filename)) {
|
23 |
+
require_once $filename;
|
24 |
}
|
25 |
});
|
26 |
}
|
vendor/firebase/BeforeValidException.php
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
namespace Firebase\JWT;
|
3 |
-
|
4 |
-
class BeforeValidException extends \UnexpectedValueException
|
5 |
-
{
|
6 |
-
|
7 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/firebase/ExpiredException.php
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
namespace Firebase\JWT;
|
3 |
-
|
4 |
-
class ExpiredException extends \UnexpectedValueException
|
5 |
-
{
|
6 |
-
|
7 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/firebase/JWT.php
CHANGED
@@ -1,389 +1,63 @@
|
|
1 |
<?php
|
2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
namespace Firebase\JWT;
|
4 |
-
use \DomainException;
|
5 |
-
use \InvalidArgumentException;
|
6 |
-
use \UnexpectedValueException;
|
7 |
-
use \DateTime;
|
8 |
|
9 |
/**
|
10 |
-
*
|
11 |
-
*
|
12 |
*
|
13 |
-
*
|
14 |
*
|
15 |
-
* @
|
16 |
-
* @package Authentication_JWT
|
17 |
-
* @author Neuman Vong <neuman@twilio.com>
|
18 |
-
* @author Anant Narayanan <anant@php.net>
|
19 |
-
* @license https://opensource.org/licenses/BSD-3-Clause 3-clause BSD
|
20 |
-
* @link https://github.com/firebase/php-jwt
|
21 |
*/
|
22 |
class JWT
|
23 |
{
|
24 |
|
25 |
/**
|
26 |
-
*
|
27 |
-
* we want to provide some extra leeway time to
|
28 |
-
* account for clock skew.
|
29 |
-
*/
|
30 |
-
public static $leeway = 0;
|
31 |
-
|
32 |
-
/**
|
33 |
-
* Allow the current timestamp to be specified.
|
34 |
-
* Useful for fixing a value within unit testing.
|
35 |
-
*
|
36 |
-
* Will default to PHP time() value if null.
|
37 |
-
*/
|
38 |
-
public static $timestamp = null;
|
39 |
-
|
40 |
-
public static $supported_algs = array(
|
41 |
-
'HS256' => array('hash_hmac', 'SHA256'),
|
42 |
-
'HS512' => array('hash_hmac', 'SHA512'),
|
43 |
-
'HS384' => array('hash_hmac', 'SHA384'),
|
44 |
-
'RS256' => array('openssl', 'SHA256'),
|
45 |
-
'RS384' => array('openssl', 'SHA384'),
|
46 |
-
'RS512' => array('openssl', 'SHA512'),
|
47 |
-
);
|
48 |
-
|
49 |
-
/**
|
50 |
-
* Decodes a JWT string into a PHP object.
|
51 |
-
*
|
52 |
-
* @param string $jwt The JWT
|
53 |
-
* @param string|array $key The key, or map of keys.
|
54 |
-
* If the algorithm used is asymmetric, this is the public key
|
55 |
-
* @param array $allowed_algs List of supported verification algorithms
|
56 |
-
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
|
57 |
-
*
|
58 |
-
* @return object The JWT's payload as a PHP object
|
59 |
-
*
|
60 |
-
* @throws UnexpectedValueException Provided JWT was invalid
|
61 |
-
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
|
62 |
-
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
|
63 |
-
* @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
|
64 |
-
* @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
|
65 |
-
*
|
66 |
-
* @uses jsonDecode
|
67 |
-
* @uses urlsafeB64Decode
|
68 |
-
*/
|
69 |
-
public static function decode($jwt, $key, array $allowed_algs = array())
|
70 |
-
{
|
71 |
-
$timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;
|
72 |
-
|
73 |
-
if (empty($key)) {
|
74 |
-
throw new InvalidArgumentException('Key may not be empty');
|
75 |
-
}
|
76 |
-
$tks = explode('.', $jwt);
|
77 |
-
if (count($tks) != 3) {
|
78 |
-
throw new UnexpectedValueException('Wrong number of segments');
|
79 |
-
}
|
80 |
-
|
81 |
-
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($tks[0])))) {
|
82 |
-
throw new UnexpectedValueException('Invalid header encoding');
|
83 |
-
}
|
84 |
-
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($tks[1]))) {
|
85 |
-
throw new UnexpectedValueException('Invalid claims encoding');
|
86 |
-
}
|
87 |
-
if (false === ($sig = static::urlsafeB64Decode($tks[2]))) {
|
88 |
-
throw new UnexpectedValueException('Invalid signature encoding');
|
89 |
-
}
|
90 |
-
if (empty($header->alg)) {
|
91 |
-
throw new UnexpectedValueException('Empty algorithm');
|
92 |
-
}
|
93 |
-
if (empty(static::$supported_algs[$header->alg])) {
|
94 |
-
throw new UnexpectedValueException('Algorithm not supported');
|
95 |
-
}
|
96 |
-
if (!in_array($header->alg, $allowed_algs)) {
|
97 |
-
throw new UnexpectedValueException('Algorithm not allowed');
|
98 |
-
}
|
99 |
-
if (is_array($key) || $key instanceof \ArrayAccess) {
|
100 |
-
if (isset($header->kid)) {
|
101 |
-
if (!isset($key[$header->kid])) {
|
102 |
-
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
|
103 |
-
}
|
104 |
-
$key = $key[$header->kid];
|
105 |
-
} else {
|