Rev 679 | Rev 826 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 679 | Rev 699 | ||
---|---|---|---|
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | /* |
3 | /* |
4 | * OIDplus 2.0 |
4 | * OIDplus 2.0 |
5 | * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft |
5 | * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft |
6 | * |
6 | * |
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | * you may not use this file except in compliance with the License. |
8 | * you may not use this file except in compliance with the License. |
9 | * You may obtain a copy of the License at |
9 | * You may obtain a copy of the License at |
10 | * |
10 | * |
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
12 | * |
12 | * |
13 | * Unless required by applicable law or agreed to in writing, software |
13 | * Unless required by applicable law or agreed to in writing, software |
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | * See the License for the specific language governing permissions and |
16 | * See the License for the specific language governing permissions and |
17 | * limitations under the License. |
17 | * limitations under the License. |
18 | */ |
18 | */ |
19 | 19 | ||
20 | if (!defined('INSIDE_OIDPLUS')) die(); |
20 | if (!defined('INSIDE_OIDPLUS')) die(); |
21 | 21 | ||
22 | class OIDplusAuthContentStoreJWT extends OIDplusAuthContentStoreDummy { |
22 | class OIDplusAuthContentStoreJWT extends OIDplusAuthContentStoreDummy { |
23 | 23 | ||
24 | const COOKIE_NAME = 'OIDPLUS_AUTH_JWT'; |
24 | const COOKIE_NAME = 'OIDPLUS_AUTH_JWT'; |
25 | 25 | ||
26 | const JWT_GENERATOR_AJAX = 0; // "Automated AJAX" plugin |
26 | const JWT_GENERATOR_AJAX = 0; // "Automated AJAX" plugin |
27 | const JWT_GENERATOR_LOGIN = 1; // "Remember me" login method |
27 | const JWT_GENERATOR_LOGIN = 1; // "Remember me" login method |
28 | const JWT_GENERATOR_MANUAL = 2; // "Manually crafted" JWT tokens |
28 | const JWT_GENERATOR_MANUAL = 2; // "Manually crafted" JWT tokens |
29 | 29 | ||
30 | private static function jwtGetBlacklistConfigKey($gen, $sub) { |
30 | private static function jwtGetBlacklistConfigKey($gen, $sub) { |
31 | // Note: Needs to be <= 50 characters! |
31 | // Note: Needs to be <= 50 characters! |
32 | return 'jwt_blacklist_gen('.$gen.')_sub('.trim(base64_encode(md5($sub,true)),'=').')'; |
32 | return 'jwt_blacklist_gen('.$gen.')_sub('.trim(base64_encode(md5($sub,true)),'=').')'; |
33 | } |
33 | } |
34 | 34 | ||
35 | public static function jwtBlacklist($gen, $sub) { |
35 | public static function jwtBlacklist($gen, $sub) { |
36 | $cfg = self::jwtGetBlacklistConfigKey($gen, $sub); |
36 | $cfg = self::jwtGetBlacklistConfigKey($gen, $sub); |
37 | $bl_time = time()-1; |
37 | $bl_time = time()-1; |
38 | 38 | ||
39 | $gen_desc = 'Unknown'; |
39 | $gen_desc = 'Unknown'; |
40 | if ($gen === self::JWT_GENERATOR_AJAX) $gen_desc = 'Automated AJAX calls'; |
40 | if ($gen === self::JWT_GENERATOR_AJAX) $gen_desc = 'Automated AJAX calls'; |
41 | if ($gen === self::JWT_GENERATOR_LOGIN) $gen_desc = 'Login ("Remember me")'; |
41 | if ($gen === self::JWT_GENERATOR_LOGIN) $gen_desc = 'Login ("Remember me")'; |
42 | if ($gen === self::JWT_GENERATOR_MANUAL) $gen_desc = 'Manually created'; |
42 | if ($gen === self::JWT_GENERATOR_MANUAL) $gen_desc = 'Manually created'; |
43 | 43 | ||
44 | OIDplus::config()->prepareConfigKey($cfg, 'Revoke timestamp of all JWT tokens for $sub with generator $gen ($gen_desc)', $bl_time, OIDplusConfig::PROTECTION_HIDDEN, function($value) {}); |
44 | OIDplus::config()->prepareConfigKey($cfg, 'Revoke timestamp of all JWT tokens for $sub with generator $gen ($gen_desc)', $bl_time, OIDplusConfig::PROTECTION_HIDDEN, function($value) {}); |
45 | OIDplus::config()->setValue($cfg, $bl_time); |
45 | OIDplus::config()->setValue($cfg, $bl_time); |
46 | } |
46 | } |
47 | 47 | ||
48 | public static function jwtGetBlacklistTime($gen, $sub) { |
48 | public static function jwtGetBlacklistTime($gen, $sub) { |
49 | $cfg = self::jwtGetBlacklistConfigKey($gen, $sub); |
49 | $cfg = self::jwtGetBlacklistConfigKey($gen, $sub); |
50 | return OIDplus::config()->getValue($cfg,0); |
50 | return OIDplus::config()->getValue($cfg,0); |
51 | } |
51 | } |
52 | 52 | ||
53 | private static function jwtSecurityCheck($contentProvider) { |
53 | private static function jwtSecurityCheck($contentProvider) { |
54 | // Check if the token is intended for us |
54 | // Check if the token is intended for us |
55 | if ($contentProvider->getValue('aud','') !== "http://oidplus.com") { |
55 | if ($contentProvider->getValue('aud','') !== OIDplus::getEditionInfo()['jwtaud']) { |
56 | throw new OIDplusException(_L('Token has wrong audience')); |
56 | throw new OIDplusException(_L('Token has wrong audience')); |
57 | } |
57 | } |
58 | $gen = $contentProvider->getValue('oidplus_generator', -1); |
58 | $gen = $contentProvider->getValue('oidplus_generator', -1); |
59 | 59 | ||
60 | $has_admin = $contentProvider->isAdminLoggedIn(); |
60 | $has_admin = $contentProvider->isAdminLoggedIn(); |
61 | $has_ra = $contentProvider->raNumLoggedIn() > 0; |
61 | $has_ra = $contentProvider->raNumLoggedIn() > 0; |
62 | 62 | ||
63 | // Check if the token generator is allowed |
63 | // Check if the token generator is allowed |
64 | if ($gen === self::JWT_GENERATOR_AJAX) { |
64 | if ($gen === self::JWT_GENERATOR_AJAX) { |
65 | if (($has_admin) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_AJAX_ADMIN', true)) { |
65 | if (($has_admin) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_AJAX_ADMIN', true)) { |
66 | // Generator: plugins/viathinksoft/adminPages/910_automated_ajax_calls/OIDplusPageAdminAutomatedAJAXCalls.class.php |
66 | // Generator: plugins/viathinksoft/adminPages/910_automated_ajax_calls/OIDplusPageAdminAutomatedAJAXCalls.class.php |
67 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_AJAX_ADMIN')); |
67 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_AJAX_ADMIN')); |
68 | } |
68 | } |
69 | if (($has_ra) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_AJAX_USER', true)) { |
69 | if (($has_ra) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_AJAX_USER', true)) { |
70 | // Generator: plugins/viathinksoft/raPages/910_automated_ajax_calls/OIDplusPageRaAutomatedAJAXCalls.class.php |
70 | // Generator: plugins/viathinksoft/raPages/910_automated_ajax_calls/OIDplusPageRaAutomatedAJAXCalls.class.php |
71 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_AJAX_USER')); |
71 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_AJAX_USER')); |
72 | } |
72 | } |
73 | } |
73 | } |
74 | else if ($gen === self::JWT_GENERATOR_LOGIN) { |
74 | else if ($gen === self::JWT_GENERATOR_LOGIN) { |
75 | // Used for feature "Remember me" (use JWT token in a cookie as alternative to PHP session): |
75 | // Used for feature "Remember me" (use JWT token in a cookie as alternative to PHP session): |
76 | // - No PHP session will be used |
76 | // - No PHP session will be used |
77 | // - Session will not be bound to IP address (therefore, you can switch between mobile/WiFi for example) |
77 | // - Session will not be bound to IP address (therefore, you can switch between mobile/WiFi for example) |
78 | // - No server-side session needed |
78 | // - No server-side session needed |
79 | if (($has_admin) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_ADMIN', true)) { |
79 | if (($has_admin) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_ADMIN', true)) { |
80 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_ADMIN')); |
80 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_ADMIN')); |
81 | } |
81 | } |
82 | if (($has_ra) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) { |
82 | if (($has_ra) && !OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) { |
83 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_USER')); |
83 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_USER')); |
84 | } |
84 | } |
85 | } |
85 | } |
86 | else if ($gen === self::JWT_GENERATOR_MANUAL) { |
86 | else if ($gen === self::JWT_GENERATOR_MANUAL) { |
87 | // Generator 2 are "hand-crafted" tokens |
87 | // Generator 2 are "hand-crafted" tokens |
88 | if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_MANUAL', false)) { |
88 | if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_MANUAL', false)) { |
89 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_MANUAL')); |
89 | throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_MANUAL')); |
90 | } |
90 | } |
91 | } else { |
91 | } else { |
92 | throw new OIDplusException(_L('Token generator %1 not recognized',$gen)); |
92 | throw new OIDplusException(_L('Token generator %1 not recognized',$gen)); |
93 | } |
93 | } |
94 | 94 | ||
95 | // Make sure that the IAT (issued at time) isn't in a blacklisted timeframe |
95 | // Make sure that the IAT (issued at time) isn't in a blacklisted timeframe |
96 | // When an user believes that a token was compromised, then they can blacklist the tokens identified by their "iat" ("Issued at") property |
96 | // When an user believes that a token was compromised, then they can blacklist the tokens identified by their "iat" ("Issued at") property |
97 | // When a user logs out of a "remember me" session, the JWT token will be blacklisted as well |
97 | // When a user logs out of a "remember me" session, the JWT token will be blacklisted as well |
98 | // Small side effect: All "remember me" sessions of that user will be revoked then |
98 | // Small side effect: All "remember me" sessions of that user will be revoked then |
99 | $sublist = $contentProvider->loggedInRaList(); |
99 | $sublist = $contentProvider->loggedInRaList(); |
100 | foreach ($sublist as &$sub) { |
100 | foreach ($sublist as &$sub) { |
101 | $sub = $sub->raEmail(); |
101 | $sub = $sub->raEmail(); |
102 | } |
102 | } |
103 | if ($has_admin) $sublist[] = 'admin'; |
103 | if ($has_admin) $sublist[] = 'admin'; |
104 | foreach ($sublist as $sub) { |
104 | foreach ($sublist as $sub) { |
105 | $bl_time = self::jwtGetBlacklistTime($gen, $sub); |
105 | $bl_time = self::jwtGetBlacklistTime($gen, $sub); |
106 | $iat = $contentProvider->getValue('iat',0); |
106 | $iat = $contentProvider->getValue('iat',0); |
107 | if ($iat <= $bl_time) { |
107 | if ($iat <= $bl_time) { |
108 | throw new OIDplusException(_L('The JWT token was blacklisted on %1. Please generate a new one',date('d F Y, H:i:s',$bl_time))); |
108 | throw new OIDplusException(_L('The JWT token was blacklisted on %1. Please generate a new one',date('d F Y, H:i:s',$bl_time))); |
109 | } |
109 | } |
110 | } |
110 | } |
111 | 111 | ||
112 | // Optional feature: Limit the JWT to a specific IP address |
112 | // Optional feature: Limit the JWT to a specific IP address |
113 | // Currently not used in OIDplus |
113 | // Currently not used in OIDplus |
114 | $ip = $contentProvider->getValue('ip',''); |
114 | $ip = $contentProvider->getValue('ip',''); |
115 | if ($ip !== '') { |
115 | if ($ip !== '') { |
116 | if (isset($_SERVER['REMOTE_ADDR']) && ($ip !== $_SERVER['REMOTE_ADDR'])) { |
116 | if (isset($_SERVER['REMOTE_ADDR']) && ($ip !== $_SERVER['REMOTE_ADDR'])) { |
117 | throw new OIDplusException(_L('Your IP address is not allowed to use this token')); |
117 | throw new OIDplusException(_L('Your IP address is not allowed to use this token')); |
118 | } |
118 | } |
119 | } |
119 | } |
120 | 120 | ||
121 | // Checks which are dependent on the generator |
121 | // Checks which are dependent on the generator |
122 | if ($gen === self::JWT_GENERATOR_LOGIN) { |
122 | if ($gen === self::JWT_GENERATOR_LOGIN) { |
123 | if (!isset($_COOKIE[self::COOKIE_NAME])) { |
123 | if (!isset($_COOKIE[self::COOKIE_NAME])) { |
124 | throw new OIDplusException(_L('This kind of JWT token can only be used with the %1 request type','COOKIE')); |
124 | throw new OIDplusException(_L('This kind of JWT token can only be used with the %1 request type','COOKIE')); |
125 | } |
125 | } |
126 | } |
126 | } |
127 | if ($gen === self::JWT_GENERATOR_AJAX) { |
127 | if ($gen === self::JWT_GENERATOR_AJAX) { |
128 | if (!isset($_GET[self::COOKIE_NAME]) && !isset($_POST[self::COOKIE_NAME])) { |
128 | if (!isset($_GET[self::COOKIE_NAME]) && !isset($_POST[self::COOKIE_NAME])) { |
129 | throw new OIDplusException(_L('This kind of JWT token can only be used with the %1 request type','GET/POST')); |
129 | throw new OIDplusException(_L('This kind of JWT token can only be used with the %1 request type','GET/POST')); |
130 | } |
130 | } |
131 | if (isset($_SERVER['SCRIPT_FILENAME']) && (strtolower(basename($_SERVER['SCRIPT_FILENAME'])) !== 'ajax.php')) { |
131 | if (isset($_SERVER['SCRIPT_FILENAME']) && (strtolower(basename($_SERVER['SCRIPT_FILENAME'])) !== 'ajax.php')) { |
132 | throw new OIDplusException(_L('This kind of JWT token can only be used in ajax.php')); |
132 | throw new OIDplusException(_L('This kind of JWT token can only be used in ajax.php')); |
133 | } |
133 | } |
134 | } |
134 | } |
135 | } |
135 | } |
136 | 136 | ||
137 | // Override abstract functions |
137 | // Override abstract functions |
138 | 138 | ||
139 | public function activate() { |
139 | public function activate() { |
140 | // Send cookie at the end of the HTTP request, in case there are multiple activate() calls |
140 | // Send cookie at the end of the HTTP request, in case there are multiple activate() calls |
141 | OIDplus::register_shutdown_function(array($this,'activateNow')); |
141 | OIDplus::register_shutdown_function(array($this,'activateNow')); |
142 | } |
142 | } |
143 | 143 | ||
144 | public function activateNow() { |
144 | public function activateNow() { |
145 | $token = $this->getJWTToken(); |
145 | $token = $this->getJWTToken(); |
146 | $exp = $this->getValue('exp',0); |
146 | $exp = $this->getValue('exp',0); |
147 | OIDplus::cookieUtils()->setcookie(self::COOKIE_NAME, $token, $exp, false); |
147 | OIDplus::cookieUtils()->setcookie(self::COOKIE_NAME, $token, $exp, false); |
148 | } |
148 | } |
149 | 149 | ||
150 | public function destroySession() { |
150 | public function destroySession() { |
151 | OIDplus::cookieUtils()->unsetcookie(self::COOKIE_NAME); |
151 | OIDplus::cookieUtils()->unsetcookie(self::COOKIE_NAME); |
152 | } |
152 | } |
153 | 153 | ||
154 | public function raLogout($email) { |
154 | public function raLogout($email) { |
155 | $gen = $this->getValue('oidplus_generator', -1); |
155 | $gen = $this->getValue('oidplus_generator', -1); |
156 | if ($gen >= 0) self::jwtBlacklist($gen, $email); |
156 | if ($gen >= 0) self::jwtBlacklist($gen, $email); |
157 | parent::raLogout($email); |
157 | parent::raLogout($email); |
158 | } |
158 | } |
159 | 159 | ||
160 | public function raLogoutEx($email, &$loginfo) { |
160 | public function raLogoutEx($email, &$loginfo) { |
161 | $this->raLogout($email); |
161 | $this->raLogout($email); |
162 | $loginfo = 'from JWT session'; |
162 | $loginfo = 'from JWT session'; |
163 | } |
163 | } |
164 | 164 | ||
165 | public function adminLogout() { |
165 | public function adminLogout() { |
166 | $gen = $this->getValue('oidplus_generator', -1); |
166 | $gen = $this->getValue('oidplus_generator', -1); |
167 | if ($gen >= 0) self::jwtBlacklist($gen, 'admin'); |
167 | if ($gen >= 0) self::jwtBlacklist($gen, 'admin'); |
168 | parent::adminLogout(); |
168 | parent::adminLogout(); |
169 | } |
169 | } |
170 | 170 | ||
171 | public function adminLogoutEx(&$loginfo) { |
171 | public function adminLogoutEx(&$loginfo) { |
172 | $this->adminLogout(); |
172 | $this->adminLogout(); |
173 | $loginfo = 'from JWT session'; |
173 | $loginfo = 'from JWT session'; |
174 | } |
174 | } |
175 | 175 | ||
176 | private static $contentProvider = null; |
176 | private static $contentProvider = null; |
177 | public static function getActiveProvider() { |
177 | public static function getActiveProvider() { |
178 | if (!self::$contentProvider) { |
178 | if (!self::$contentProvider) { |
179 | $jwt = ''; |
179 | $jwt = ''; |
180 | if (isset($_COOKIE[self::COOKIE_NAME])) $jwt = $_COOKIE[self::COOKIE_NAME]; |
180 | if (isset($_COOKIE[self::COOKIE_NAME])) $jwt = $_COOKIE[self::COOKIE_NAME]; |
181 | if (isset($_POST[self::COOKIE_NAME])) $jwt = $_POST[self::COOKIE_NAME]; |
181 | if (isset($_POST[self::COOKIE_NAME])) $jwt = $_POST[self::COOKIE_NAME]; |
182 | if (isset($_GET[self::COOKIE_NAME])) $jwt = $_GET[self::COOKIE_NAME]; |
182 | if (isset($_GET[self::COOKIE_NAME])) $jwt = $_GET[self::COOKIE_NAME]; |
183 | 183 | ||
184 | if (!empty($jwt)) { |
184 | if (!empty($jwt)) { |
185 | $tmp = new OIDplusAuthContentStoreJWT(); |
185 | $tmp = new OIDplusAuthContentStoreJWT(); |
186 | 186 | ||
187 | try { |
187 | try { |
188 | // Decode the JWT. In this step, the signature as well as EXP/NBF times will be checked |
188 | // Decode the JWT. In this step, the signature as well as EXP/NBF times will be checked |
189 | $tmp->loadJWT($jwt); |
189 | $tmp->loadJWT($jwt); |
190 | 190 | ||
191 | // Do various checks if the token is allowed and not blacklisted |
191 | // Do various checks if the token is allowed and not blacklisted |
192 | self::jwtSecurityCheck($tmp); |
192 | self::jwtSecurityCheck($tmp); |
193 | } catch (Exception $e) { |
193 | } catch (Exception $e) { |
194 | if (isset($_GET[self::COOKIE_NAME]) || isset($_POST[self::COOKIE_NAME])) { |
194 | if (isset($_GET[self::COOKIE_NAME]) || isset($_POST[self::COOKIE_NAME])) { |
195 | // Most likely an AJAX request. We can throw an Exception |
195 | // Most likely an AJAX request. We can throw an Exception |
196 | throw new OIDplusException(_L('The JWT token was rejected: %1',$e->getMessage())); |
196 | throw new OIDplusException(_L('The JWT token was rejected: %1',$e->getMessage())); |
197 | } else { |
197 | } else { |
198 | // Most likely an expired Cookie/Login session. We must not throw an Exception, otherwise we will break jsTree |
198 | // Most likely an expired Cookie/Login session. We must not throw an Exception, otherwise we will break jsTree |
199 | OIDplus::cookieUtils()->unsetcookie(self::COOKIE_NAME); |
199 | OIDplus::cookieUtils()->unsetcookie(self::COOKIE_NAME); |
200 | return null; |
200 | return null; |
201 | } |
201 | } |
202 | } |
202 | } |
203 | 203 | ||
204 | self::$contentProvider = $tmp; |
204 | self::$contentProvider = $tmp; |
205 | } |
205 | } |
206 | } |
206 | } |
207 | 207 | ||
208 | return self::$contentProvider; |
208 | return self::$contentProvider; |
209 | } |
209 | } |
210 | 210 | ||
211 | public function raLoginEx($email, &$loginfo) { |
211 | public function raLoginEx($email, &$loginfo) { |
212 | if (is_null(self::getActiveProvider())) { |
212 | if (is_null(self::getActiveProvider())) { |
213 | $this->raLogin($email); |
213 | $this->raLogin($email); |
214 | $loginfo = 'into new JWT session'; |
214 | $loginfo = 'into new JWT session'; |
215 | self::$contentProvider = $this; |
215 | self::$contentProvider = $this; |
216 | } else { |
216 | } else { |
217 | $gen = $this->getValue('oidplus_generator',-1); |
217 | $gen = $this->getValue('oidplus_generator',-1); |
218 | switch ($gen) { |
218 | switch ($gen) { |
219 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_AJAX : |
219 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_AJAX : |
220 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_MANUAL : |
220 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_MANUAL : |
221 | throw new OIDplusException(_L('This kind of JWT token cannot be altered. Therefore you cannot do this action.')); |
221 | throw new OIDplusException(_L('This kind of JWT token cannot be altered. Therefore you cannot do this action.')); |
222 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN : |
222 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN : |
223 | if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) { |
223 | if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) { |
224 | throw new OIDplusException(_L('You cannot add this login credential to your existing "remember me" session. You need to log-out first.')); |
224 | throw new OIDplusException(_L('You cannot add this login credential to your existing "remember me" session. You need to log-out first.')); |
225 | } |
225 | } |
226 | break; |
226 | break; |
227 | default: |
227 | default: |
228 | assert(false); // This cannot happen because jwtSecurityCheck will check for unknown generators |
228 | assert(false); // This cannot happen because jwtSecurityCheck will check for unknown generators |
229 | break; |
229 | break; |
230 | } |
230 | } |
231 | $this->raLogin($email); |
231 | $this->raLogin($email); |
232 | $loginfo = 'into existing JWT session'; |
232 | $loginfo = 'into existing JWT session'; |
233 | } |
233 | } |
234 | } |
234 | } |
235 | 235 | ||
236 | public function adminLoginEx(&$loginfo) { |
236 | public function adminLoginEx(&$loginfo) { |
237 | if (is_null(self::getActiveProvider())) { |
237 | if (is_null(self::getActiveProvider())) { |
238 | $this->adminLogin(); |
238 | $this->adminLogin(); |
239 | $loginfo = 'into new JWT session'; |
239 | $loginfo = 'into new JWT session'; |
240 | self::$contentProvider = $this; |
240 | self::$contentProvider = $this; |
241 | } else { |
241 | } else { |
242 | $gen = $this->getValue('oidplus_generator',-1); |
242 | $gen = $this->getValue('oidplus_generator',-1); |
243 | switch ($gen) { |
243 | switch ($gen) { |
244 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_AJAX : |
244 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_AJAX : |
245 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_MANUAL : |
245 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_MANUAL : |
246 | throw new OIDplusException(_L('This kind of JWT token cannot be altered. Therefore you cannot do this action.')); |
246 | throw new OIDplusException(_L('This kind of JWT token cannot be altered. Therefore you cannot do this action.')); |
247 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN : |
247 | case OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN : |
248 | if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_ADMIN', true)) { |
248 | if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_ADMIN', true)) { |
249 | throw new OIDplusException(_L('You cannot add this login credential to your existing "remember me" session. You need to log-out first.')); |
249 | throw new OIDplusException(_L('You cannot add this login credential to your existing "remember me" session. You need to log-out first.')); |
250 | } |
250 | } |
251 | break; |
251 | break; |
252 | default: |
252 | default: |
253 | assert(false); // This cannot happen because jwtSecurityCheck will check for unknown generators |
253 | assert(false); // This cannot happen because jwtSecurityCheck will check for unknown generators |
254 | break; |
254 | break; |
255 | } |
255 | } |
256 | $this->adminLogin(); |
256 | $this->adminLogin(); |
257 | $loginfo = 'into existing JWT session'; |
257 | $loginfo = 'into existing JWT session'; |
258 | } |
258 | } |
259 | } |
259 | } |
260 | 260 | ||
261 | // Individual functions |
261 | // Individual functions |
262 | 262 | ||
263 | public function loadJWT($jwt) { |
263 | public function loadJWT($jwt) { |
264 | \Firebase\JWT\JWT::$leeway = 60; // leeway in seconds |
264 | \Firebase\JWT\JWT::$leeway = 60; // leeway in seconds |
265 | if (OIDplus::getPkiStatus()) { |
265 | if (OIDplus::getPkiStatus()) { |
266 | $pubKey = OIDplus::config()->getValue('oidplus_public_key'); |
266 | $pubKey = OIDplus::config()->getValue('oidplus_public_key'); |
267 | $k = new \Firebase\JWT\Key($pubKey, 'RS256'); // RSA+SHA256 ist hardcoded in getPkiStatus() generation |
267 | $k = new \Firebase\JWT\Key($pubKey, 'RS256'); // RSA+SHA256 ist hardcoded in getPkiStatus() generation |
268 | $this->content = (array) \Firebase\JWT\JWT::decode($jwt, $k); |
268 | $this->content = (array) \Firebase\JWT\JWT::decode($jwt, $k); |
269 | } else { |
269 | } else { |
270 | $key = OIDplus::baseConfig()->getValue('SERVER_SECRET', '').'/OIDplusAuthContentStoreJWT'; |
270 | $key = OIDplus::baseConfig()->getValue('SERVER_SECRET', '').'/OIDplusAuthContentStoreJWT'; |
271 | $key = hash_pbkdf2('sha512', $key, '', 10000, 64/*256bit*/, false); |
271 | $key = hash_pbkdf2('sha512', $key, '', 10000, 64/*256bit*/, false); |
272 | $k = new \Firebase\JWT\Key($key, 'HS512'); // HMAC+SHA512 is hardcoded here |
272 | $k = new \Firebase\JWT\Key($key, 'HS512'); // HMAC+SHA512 is hardcoded here |
273 | $this->content = (array) \Firebase\JWT\JWT::decode($jwt, $k); |
273 | $this->content = (array) \Firebase\JWT\JWT::decode($jwt, $k); |
274 | } |
274 | } |
275 | } |
275 | } |
276 | 276 | ||
277 | public function getJWTToken() { |
277 | public function getJWTToken() { |
278 | $payload = $this->content; |
278 | $payload = $this->content; |
279 | $payload["iss"] = "http://oidplus.com"; |
279 | $payload["iss"] = OIDplus::getEditionInfo()['jwtaud']; |
280 | $payload["aud"] = "http://oidplus.com"; |
280 | $payload["aud"] = OIDplus::getEditionInfo()['jwtaud']; |
281 | $payload["jti"] = gen_uuid(); |
281 | $payload["jti"] = gen_uuid(); |
282 | $payload["iat"] = time(); |
282 | $payload["iat"] = time(); |
283 | 283 | ||
284 | if (OIDplus::getPkiStatus()) { |
284 | if (OIDplus::getPkiStatus()) { |
285 | $privKey = OIDplus::config()->getValue('oidplus_private_key'); |
285 | $privKey = OIDplus::config()->getValue('oidplus_private_key'); |
286 | return \Firebase\JWT\JWT::encode($payload, $privKey, 'RS256'); // RSA+SHA256 ist hardcoded in getPkiStatus() generation |
286 | return \Firebase\JWT\JWT::encode($payload, $privKey, 'RS256'); // RSA+SHA256 ist hardcoded in getPkiStatus() generation |
287 | } else { |
287 | } else { |
288 | $key = OIDplus::baseConfig()->getValue('SERVER_SECRET', '').'/OIDplusAuthContentStoreJWT'; |
288 | $key = OIDplus::baseConfig()->getValue('SERVER_SECRET', '').'/OIDplusAuthContentStoreJWT'; |
289 | $key = hash_pbkdf2('sha512', $key, '', 10000, 64/*256bit*/, false); |
289 | $key = hash_pbkdf2('sha512', $key, '', 10000, 64/*256bit*/, false); |
290 | return \Firebase\JWT\JWT::encode($payload, $key, 'HS512'); // HMAC+SHA512 is hardcoded here |
290 | return \Firebase\JWT\JWT::encode($payload, $key, 'HS512'); // HMAC+SHA512 is hardcoded here |
291 | } |
291 | } |
292 | } |
292 | } |
293 | 293 | ||
294 | } |
294 | } |
295 | 295 |