Subversion Repositories oidplus

Rev

Rev 1267 | Rev 1283 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1267 Rev 1282
1
<?php
1
<?php
2
 
2
 
3
/*
3
/*
4
 * OIDplus 2.0
4
 * OIDplus 2.0
5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
5
 * Copyright 2019 - 2023 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
namespace ViaThinkSoft\OIDplus;
20
namespace ViaThinkSoft\OIDplus;
21
 
21
 
22
// phpcs:disable PSR1.Files.SideEffects
22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
24
// phpcs:enable PSR1.Files.SideEffects
25
 
25
 
26
class OIDplusAuthUtils extends OIDplusBaseClass {
26
class OIDplusAuthUtils extends OIDplusBaseClass {
27
 
27
 
28
        // Useful functions
28
        // Useful functions
29
 
29
 
30
        /**
30
        /**
31
         * @param string $password
31
         * @param string $password
32
         * @return string
32
         * @return string
33
         * @throws OIDplusException
33
         * @throws OIDplusException
34
         */
34
         */
35
        private function raPepperProcessing(string $password): string {
35
        private function raPepperProcessing(string $password): string {
36
                // Additional feature: Pepper
36
                // Additional feature: Pepper
37
                // The pepper is stored inside the base configuration file
37
                // The pepper is stored inside the base configuration file
38
                // It prevents that an attacker with SQL write rights can
38
                // It prevents that an attacker with SQL write rights can
39
                // create accounts.
39
                // create accounts.
40
                // ATTENTION!!! If a pepper is used, then the
40
                // ATTENTION!!! If a pepper is used, then the
41
                // hashes are bound to that pepper. If you change the pepper,
41
                // hashes are bound to that pepper. If you change the pepper,
42
                // then ALL passwords of RAs become INVALID!
42
                // then ALL passwords of RAs become INVALID!
43
                $pepper = OIDplus::baseConfig()->getValue('RA_PASSWORD_PEPPER','');
43
                $pepper = OIDplus::baseConfig()->getValue('RA_PASSWORD_PEPPER','');
44
                if ($pepper !== '') {
44
                if ($pepper !== '') {
45
                        $algo = OIDplus::baseConfig()->getValue('RA_PASSWORD_PEPPER_ALGO','sha512'); // sha512 works with PHP 7.0
45
                        $algo = OIDplus::baseConfig()->getValue('RA_PASSWORD_PEPPER_ALGO','sha512'); // sha512 works with PHP 7.0
46
                        if (strtolower($algo) === 'sha3-512') {
46
                        if (strtolower($algo) === 'sha3-512') {
47
                                $hmac = sha3_512_hmac($password, $pepper);
47
                                $hmac = sha3_512_hmac($password, $pepper);
48
                        } else {
48
                        } else {
49
                                $hmac = hash_hmac($algo, $password, $pepper);
49
                                $hmac = hash_hmac($algo, $password, $pepper);
50
                        }
50
                        }
51
                        if ($hmac === "") throw new OIDplusException(_L('HMAC failed'));
51
                        if ($hmac === "") throw new OIDplusException(_L('HMAC failed'));
52
                        return $hmac;
52
                        return $hmac;
53
                } else {
53
                } else {
54
                        return $password;
54
                        return $password;
55
                }
55
                }
56
        }
56
        }
57
 
57
 
58
        // Content provider
58
        // Content provider
59
 
59
 
60
        /**
60
        /**
61
         * @return string
61
         * @return string
62
         * @throws OIDplusException
62
         * @throws OIDplusException
63
         */
63
         */
64
        public function getAuthMethod(): string {
64
        public function getAuthMethod(): string {
65
                $acs = $this->getAuthContentStore();
65
                $acs = $this->getAuthContentStore();
66
                if (is_null($acs)) return 'null';
66
                if (is_null($acs)) return 'null';
67
                return get_class($acs);
67
                return get_class($acs);
68
        }
68
        }
69
 
69
 
70
        /**
70
        /**
71
         * @return OIDplusAuthContentStore|null
71
         * @return OIDplusAuthContentStore|null
72
         * @throws OIDplusException
72
         * @throws OIDplusException
73
         */
73
         */
74
        protected function getAuthContentStore()/*: ?OIDplusAuthContentStore*/ {
74
        protected function getAuthContentStore()/*: ?OIDplusAuthContentStore*/ {
75
                // TODO: Should we implement these AuthContentStore as plugin type, so that there can be more than just JWT and PHP session?
75
                // TODO: Should we implement these AuthContentStore as plugin type, so that there can be more than just JWT and PHP session?
76
 
76
 
77
                // Logged in via JWT
77
                // Logged in via JWT
78
                $tmp = OIDplusAuthContentStoreJWT::getActiveProvider();
78
                $tmp = OIDplusAuthContentStoreJWT::getActiveProvider();
79
                if ($tmp) return $tmp;
79
                if ($tmp) return $tmp;
80
 
80
 
81
                // For REST, we must only allow JWT from Bearer and nothing else! So disable cookies if we are accessing the REST plugin
81
                // For REST, we must only allow JWT from Bearer and nothing else! So disable cookies if we are accessing the REST plugin
82
                $rel_url = substr($_SERVER['REQUEST_URI'], strlen(OIDplus::webpath(null, OIDplus::PATH_RELATIVE_TO_ROOT)));
82
                $rel_url = substr($_SERVER['REQUEST_URI'], strlen(OIDplus::webpath(null, OIDplus::PATH_RELATIVE_TO_ROOT)));
83
                if (!str_starts_with($rel_url, 'rest/')) { // <== TODO: Find a way how to move this into the plugin, since REST does not belong to the core. (Maybe some kind of "stateless mode" that is enabled by the REST plugin)
83
                if (!str_starts_with($rel_url, 'rest/')) { // <== TODO: Find a way how to move this into the plugin, since REST does not belong to the core. (Maybe some kind of "stateless mode" that is enabled by the REST plugin)
84
 
84
 
85
                        // Normal login via web-browser
85
                        // Normal login via web-browser
86
                        // Cookie will only be created once content is stored
86
                        // Cookie will only be created once content is stored
87
                        $tmp = OIDplusAuthContentStoreSession::getActiveProvider();
87
                        $tmp = OIDplusAuthContentStoreSession::getActiveProvider();
88
                        if ($tmp) return $tmp;
88
                        if ($tmp) return $tmp;
89
 
89
 
90
                }
90
                }
91
 
91
 
92
                // No active session and no JWT token available. User is not logged in.
92
                // No active session and no JWT token available. User is not logged in.
93
                return null;
93
                return null;
94
        }
94
        }
95
 
95
 
96
        /**
96
        /**
97
         * @param string $name
97
         * @param string $name
98
         * @param mixed|null $default
98
         * @param mixed|null $default
99
         * @return mixed
99
         * @return mixed
100
         * @throws OIDplusException
100
         * @throws OIDplusException
101
         */
101
         */
102
        public function getExtendedAttribute(string $name, $default=NULL) {
102
        public function getExtendedAttribute(string $name, $default=NULL) {
103
                $acs = $this->getAuthContentStore();
103
                $acs = $this->getAuthContentStore();
104
                if (is_null($acs)) return $default;
104
                if (is_null($acs)) return $default;
105
                return $acs->getValue($name, $default);
105
                return $acs->getValue($name, $default);
106
        }
106
        }
107
 
107
 
108
        // RA authentication functions
108
        // RA authentication functions
109
 
109
 
110
        /**
110
        /**
111
         * @param string $email
111
         * @param string $email
112
         * @return void
112
         * @return void
113
         * @throws OIDplusException
113
         * @throws OIDplusException
114
         */
114
         */
115
        public function raLogin(string $email) {
115
        public function raLogin(string $email) {
116
                $acs = $this->getAuthContentStore();
116
                $acs = $this->getAuthContentStore();
117
                if (is_null($acs)) return;
117
                if (is_null($acs)) return;
118
                $acs->raLogin($email);
118
                $acs->raLogin($email);
119
        }
119
        }
120
 
120
 
121
        /**
121
        /**
122
         * @param string $email
122
         * @param string $email
123
         * @return void
123
         * @return void
124
         * @throws OIDplusException
124
         * @throws OIDplusException
125
         */
125
         */
126
        public function raLogout(string $email) {
126
        public function raLogout(string $email) {
127
                $acs = $this->getAuthContentStore();
127
                $acs = $this->getAuthContentStore();
128
                if (is_null($acs)) return;
128
                if (is_null($acs)) return;
129
                $acs->raLogout($email);
129
                $acs->raLogout($email);
130
        }
130
        }
131
 
131
 
132
        /**
132
        /**
133
         * @param string $ra_email
133
         * @param string $ra_email
134
         * @param string $password
134
         * @param string $password
135
         * @return bool
135
         * @return bool
136
         * @throws OIDplusException
136
         * @throws OIDplusException
137
         */
137
         */
138
        public function raCheckPassword(string $ra_email, string $password): bool {
138
        public function raCheckPassword(string $ra_email, string $password): bool {
139
                $ra = new OIDplusRA($ra_email);
139
                $ra = new OIDplusRA($ra_email);
140
 
140
 
141
                // Get RA info from RA
141
                // Get RA info from RA
142
                $authInfo = $ra->getAuthInfo();
142
                $authInfo = $ra->getAuthInfo();
143
                if (!$authInfo) return false; // user not found
143
                if (!$authInfo) return false; // user not found
144
 
144
 
145
                // Ask plugins if they can verify this hash
145
                // Ask plugins if they can verify this hash
146
                $plugins = OIDplus::getAuthPlugins();
146
                $plugins = OIDplus::getAuthPlugins();
147
                if (count($plugins) == 0) {
147
                if (count($plugins) == 0) {
148
                        throw new OIDplusException(_L('No RA authentication plugins found'));
148
                        throw new OIDplusException(_L('No RA authentication plugins found'));
149
                }
149
                }
150
                foreach ($plugins as $plugin) {
150
                foreach ($plugins as $plugin) {
151
                        if ($plugin->verify($authInfo, $this->raPepperProcessing($password))) return true;
151
                        if ($plugin->verify($authInfo, $this->raPepperProcessing($password))) return true;
152
                }
152
                }
153
 
153
 
154
                return false;
154
                return false;
155
        }
155
        }
156
 
156
 
157
        /**
157
        /**
158
         * @return int
158
         * @return int
159
         * @throws OIDplusException
159
         * @throws OIDplusException
160
         */
160
         */
161
        public function raNumLoggedIn(): int {
161
        public function raNumLoggedIn(): int {
162
                $acs = $this->getAuthContentStore();
162
                $acs = $this->getAuthContentStore();
163
                if (is_null($acs)) return 0;
163
                if (is_null($acs)) return 0;
164
                return $acs->raNumLoggedIn();
164
                return $acs->raNumLoggedIn();
165
        }
165
        }
166
 
166
 
167
        /**
167
        /**
168
         * @return OIDplusRA[]
168
         * @return OIDplusRA[]
169
         * @throws OIDplusException
169
         * @throws OIDplusException
170
         */
170
         */
171
        public function loggedInRaList(): array {
171
        public function loggedInRaList(): array {
172
                if ($this->forceAllLoggedOut()) {
172
                if ($this->forceAllLoggedOut()) {
173
                        return array();
173
                        return array();
174
                } else {
174
                } else {
175
                        $acs = $this->getAuthContentStore();
175
                        $acs = $this->getAuthContentStore();
176
                        if (is_null($acs)) return array();
176
                        if (is_null($acs)) return array();
177
                        return $acs->loggedInRaList();
177
                        return $acs->loggedInRaList();
178
                }
178
                }
179
        }
179
        }
180
 
180
 
181
        /**
181
        /**
182
         * @param string|OIDplusRA $ra
182
         * @param string|OIDplusRA $ra
183
         * @return bool
183
         * @return bool
184
         * @throws OIDplusException
184
         * @throws OIDplusException
185
         */
185
         */
186
        public function isRaLoggedIn($ra): bool {
186
        public function isRaLoggedIn($ra): bool {
187
                $email = $ra instanceof OIDplusRA ? $ra->raEmail() : $ra;
187
                $email = $ra instanceof OIDplusRA ? $ra->raEmail() : $ra;
188
                $acs = $this->getAuthContentStore();
188
                $acs = $this->getAuthContentStore();
189
                if (is_null($acs)) return false;
189
                if (is_null($acs)) return false;
190
                return $acs->isRaLoggedIn($email);
190
                return $acs->isRaLoggedIn($email);
191
        }
191
        }
192
 
192
 
193
        // "High level" function including logging and checking for valid JWT alternations
193
        // "High level" function including logging and checking for valid JWT alternations
194
 
194
 
195
        /**
195
        /**
196
         * @param string $email
196
         * @param string $email
197
         * @param bool $remember_me
197
         * @param bool $remember_me
198
         * @param string $origin
198
         * @param string $origin
199
         * @return void
199
         * @return void
200
         * @throws OIDplusException
200
         * @throws OIDplusException
201
         */
201
         */
202
        public function raLoginEx(string $email, bool $remember_me, string $origin='') {
202
        public function raLoginEx(string $email, bool $remember_me, string $origin='') {
203
                $loginfo = '';
203
                $loginfo = '';
204
                $acs = $this->getAuthContentStore();
204
                $acs = $this->getAuthContentStore();
205
                if (!is_null($acs)) {
205
                if (!is_null($acs)) {
206
                        $acs->raLoginEx($email, $loginfo);
206
                        $acs->raLoginEx($email, $loginfo);
207
                        $acs->activate();
207
                        $acs->activate();
208
                } else {
208
                } else {
209
                        if ($remember_me) {
209
                        if ($remember_me) {
210
                                if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) {
210
                                if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) {
211
                                        throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_USER'));
211
                                        throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_USER'));
212
                                }
212
                                }
213
                                $ttl = OIDplus::baseConfig()->getValue('JWT_TTL_LOGIN_USER', 10*365*24*60*60);
213
                                $ttl = OIDplus::baseConfig()->getValue('JWT_TTL_LOGIN_USER', 10*365*24*60*60);
214
                                $authSimulation = new OIDplusAuthContentStoreJWT();
214
                                $authSimulation = new OIDplusAuthContentStoreJWT();
215
                                $authSimulation->raLoginEx($email, $loginfo);
215
                                $authSimulation->raLoginEx($email, $loginfo);
216
                                $authSimulation->setValue('oidplus_generator', OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN);
216
                                $authSimulation->setValue('oidplus_generator', OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN);
217
                                $authSimulation->setValue('exp', time()+$ttl); // JWT "exp" attribute
217
                                $authSimulation->setValue('exp', time()+$ttl); // JWT "exp" attribute
218
                                $authSimulation->activate();
218
                                $authSimulation->activate();
219
                        } else {
219
                        } else {
220
                                $authSimulation = new OIDplusAuthContentStoreSession();
220
                                $authSimulation = new OIDplusAuthContentStoreSession();
221
                                $authSimulation->raLoginEx($email, $loginfo);
221
                                $authSimulation->raLoginEx($email, $loginfo);
222
                                $authSimulation->activate();
222
                                $authSimulation->activate();
223
                        }
223
                        }
224
                }
224
                }
225
                $logmsg = "RA '$email' logged in";
225
                $logmsg = "RA '$email' logged in";
226
                if ($origin != '') $logmsg .= " via $origin";
226
                if ($origin != '') $logmsg .= " via $origin";
227
                if ($loginfo != '') $logmsg .= " ($loginfo)";
227
                if ($loginfo != '') $logmsg .= " ($loginfo)";
228
                OIDplus::logger()->log("V2:[OK]RA(%1)", "%2", $email, $logmsg);
228
                OIDplus::logger()->log("V2:[OK]RA(%1)", "%2", $email, $logmsg);
229
        }
229
        }
230
 
230
 
231
        /**
231
        /**
232
         * @param string $email
232
         * @param string $email
233
         * @return void
233
         * @return void
234
         * @throws OIDplusException
234
         * @throws OIDplusException
235
         */
235
         */
236
        public function raLogoutEx(string $email) {
236
        public function raLogoutEx(string $email) {
237
                $loginfo = '';
237
                $loginfo = '';
238
 
238
 
239
                $acs = $this->getAuthContentStore();
239
                $acs = $this->getAuthContentStore();
240
                if (is_null($acs)) return;
240
                if (is_null($acs)) return;
241
                $acs->raLogoutEx($email, $loginfo);
241
                $acs->raLogoutEx($email, $loginfo);
242
 
242
 
243
                OIDplus::logger()->log("V2:[OK]RA(%1)", "RA '%1' logged out (%2)", $email, $loginfo);
243
                OIDplus::logger()->log("V2:[OK]RA(%1)", "RA '%1' logged out (%2)", $email, $loginfo);
244
 
244
 
245
                if (($this->raNumLoggedIn() == 0) && (!$this->isAdminLoggedIn())) {
245
                if (($this->raNumLoggedIn() == 0) && (!$this->isAdminLoggedIn())) {
246
                        // Nobody logged in anymore. Destroy session cookie to make GDPR people happy
246
                        // Nobody logged in anymore. Destroy session cookie to make GDPR people happy
247
                        $acs->destroySession();
247
                        $acs->destroySession();
248
                } else {
248
                } else {
249
                        // Get a new token for the remaining users
249
                        // Get a new token for the remaining users
250
                        $acs->activate();
250
                        $acs->activate();
251
                }
251
                }
252
        }
252
        }
253
 
253
 
254
        // Admin authentication functions
254
        // Admin authentication functions
255
 
255
 
256
        /**
256
        /**
257
         * @return void
257
         * @return void
258
         * @throws OIDplusException
258
         * @throws OIDplusException
259
         */
259
         */
260
        public function adminLogin() {
260
        public function adminLogin() {
261
                $acs = $this->getAuthContentStore();
261
                $acs = $this->getAuthContentStore();
262
                if (is_null($acs)) return;
262
                if (is_null($acs)) return;
263
                $acs->adminLogin();
263
                $acs->adminLogin();
264
        }
264
        }
265
 
265
 
266
        /**
266
        /**
267
         * @return void
267
         * @return void
268
         * @throws OIDplusException
268
         * @throws OIDplusException
269
         */
269
         */
270
        public function adminLogout() {
270
        public function adminLogout() {
271
                $acs = $this->getAuthContentStore();
271
                $acs = $this->getAuthContentStore();
272
                if (is_null($acs)) return;
272
                if (is_null($acs)) return;
273
                $acs->adminLogout();
273
                $acs->adminLogout();
274
        }
274
        }
275
 
275
 
276
        /**
276
        /**
277
         * @param string $password
277
         * @param string $password
278
         * @return bool
278
         * @return bool
279
         * @throws OIDplusException
279
         * @throws OIDplusException
280
         */
280
         */
281
        public function adminCheckPassword(string $password): bool {
281
        public function adminCheckPassword(string $password): bool {
282
                $cfgData = OIDplus::baseConfig()->getValue('ADMIN_PASSWORD', '');
282
                $cfgData = OIDplus::baseConfig()->getValue('ADMIN_PASSWORD', '');
283
                if (empty($cfgData)) {
283
                if (empty($cfgData)) {
284
                        throw new OIDplusException(_L('No admin password set in %1','userdata/baseconfig/config.inc.php'));
284
                        throw new OIDplusException(_L('No admin password set in %1','userdata/baseconfig/config.inc.php'));
285
                }
285
                }
286
 
286
 
287
                if (!is_array($cfgData)) {
287
                if (!is_array($cfgData)) {
288
                        $passwordDataArray = array($cfgData);
288
                        $passwordDataArray = array($cfgData);
289
                } else {
289
                } else {
290
                        $passwordDataArray = $cfgData; // Multiple Administrator passwords
290
                        $passwordDataArray = $cfgData; // Multiple Administrator passwords
291
                }
291
                }
292
 
292
 
293
                foreach ($passwordDataArray as $passwordData) {
293
                foreach ($passwordDataArray as $passwordData) {
294
                        if (str_starts_with($passwordData, '$')) {
294
                        if (str_starts_with($passwordData, '$')) {
295
                                // Version 3: BCrypt (or any other crypt)
295
                                // Version 3: BCrypt (or any other crypt)
296
                                $ok = password_verify($password, $passwordData);
296
                                $ok = password_verify($password, $passwordData);
297
                        } else if (strpos($passwordData, '$') !== false) {
297
                        } else if (strpos($passwordData, '$') !== false) {
298
                                // Version 2: SHA3-512 with salt
298
                                // Version 2: SHA3-512 with salt
299
                                list($salt, $hash) = explode('$', $passwordData, 2);
299
                                list($salt, $hash) = explode('$', $passwordData, 2);
300
                                $ok = hash_equals(sha3_512($salt.$password, true), base64_decode($hash));
300
                                $ok = hash_equals(sha3_512($salt.$password, true), base64_decode($hash));
301
                        } else {
301
                        } else {
302
                                // Version 1: SHA3-512 without salt
302
                                // Version 1: SHA3-512 without salt
303
                                $ok = hash_equals(sha3_512($password, true), base64_decode($passwordData));
303
                                $ok = hash_equals(sha3_512($password, true), base64_decode($passwordData));
304
                        }
304
                        }
305
                        if ($ok) return true;
305
                        if ($ok) return true;
306
                }
306
                }
307
 
307
 
308
                return false;
308
                return false;
309
        }
309
        }
310
 
310
 
311
        /**
311
        /**
312
         * @return bool
312
         * @return bool
313
         * @throws OIDplusException
313
         * @throws OIDplusException
314
         */
314
         */
315
        public function isAdminLoggedIn(): bool {
315
        public function isAdminLoggedIn(): bool {
316
                if ($this->forceAllLoggedOut()) {
316
                if ($this->forceAllLoggedOut()) {
317
                        return false;
317
                        return false;
318
                } else {
318
                } else {
319
                        $acs = $this->getAuthContentStore();
319
                        $acs = $this->getAuthContentStore();
320
                        if (is_null($acs)) return false;
320
                        if (is_null($acs)) return false;
321
                        return $acs->isAdminLoggedIn();
321
                        return $acs->isAdminLoggedIn();
322
                }
322
                }
323
        }
323
        }
324
 
324
 
325
        /**
325
        /**
326
         * "High level" function including logging and checking for valid JWT alternations
326
         * "High level" function including logging and checking for valid JWT alternations
327
         * @param bool $remember_me
327
         * @param bool $remember_me
328
         * @param string $origin
328
         * @param string $origin
329
         * @return void
329
         * @return void
330
         * @throws OIDplusException
330
         * @throws OIDplusException
331
         */
331
         */
332
        public function adminLoginEx(bool $remember_me, string $origin='') {
332
        public function adminLoginEx(bool $remember_me, string $origin='') {
333
                $loginfo = '';
333
                $loginfo = '';
334
                $acs = $this->getAuthContentStore();
334
                $acs = $this->getAuthContentStore();
335
                if (!is_null($acs)) {
335
                if (!is_null($acs)) {
336
                        $acs->adminLoginEx($loginfo);
336
                        $acs->adminLoginEx($loginfo);
337
                        $acs->activate();
337
                        $acs->activate();
338
                } else {
338
                } else {
339
                        if ($remember_me) {
339
                        if ($remember_me) {
340
                                if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_ADMIN', true)) {
340
                                if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_ADMIN', true)) {
341
                                        throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_ADMIN'));
341
                                        throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_ADMIN'));
342
                                }
342
                                }
343
                                $ttl = OIDplus::baseConfig()->getValue('JWT_TTL_LOGIN_ADMIN', 10*365*24*60*60);
343
                                $ttl = OIDplus::baseConfig()->getValue('JWT_TTL_LOGIN_ADMIN', 10*365*24*60*60);
344
                                $authSimulation = new OIDplusAuthContentStoreJWT();
344
                                $authSimulation = new OIDplusAuthContentStoreJWT();
345
                                $authSimulation->adminLoginEx($loginfo);
345
                                $authSimulation->adminLoginEx($loginfo);
346
                                $authSimulation->setValue('oidplus_generator', OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN);
346
                                $authSimulation->setValue('oidplus_generator', OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN);
347
                                $authSimulation->setValue('exp', time()+$ttl); // JWT "exp" attribute
347
                                $authSimulation->setValue('exp', time()+$ttl); // JWT "exp" attribute
348
                                $authSimulation->activate();
348
                                $authSimulation->activate();
349
                        } else {
349
                        } else {
350
                                $authSimulation = new OIDplusAuthContentStoreSession();
350
                                $authSimulation = new OIDplusAuthContentStoreSession();
351
                                $authSimulation->adminLoginEx($loginfo);
351
                                $authSimulation->adminLoginEx($loginfo);
352
                                $authSimulation->activate();
352
                                $authSimulation->activate();
353
                        }
353
                        }
354
                }
354
                }
355
                $logmsg = "Admin logged in";
355
                $logmsg = "Admin logged in";
356
                if ($origin != '') $logmsg .= " via $origin";
356
                if ($origin != '') $logmsg .= " via $origin";
357
                if ($loginfo != '') $logmsg .= " ($loginfo)";
357
                if ($loginfo != '') $logmsg .= " ($loginfo)";
358
                OIDplus::logger()->log("V2:[OK]A", "%1", $logmsg);
358
                OIDplus::logger()->log("V2:[OK]A", "%1", $logmsg);
359
        }
359
        }
360
 
360
 
361
        /**
361
        /**
362
         * @return void
362
         * @return void
363
         * @throws OIDplusException
363
         * @throws OIDplusException
364
         */
364
         */
365
        public function adminLogoutEx() {
365
        public function adminLogoutEx() {
366
                $loginfo = '';
366
                $loginfo = '';
367
 
367
 
368
                $acs = $this->getAuthContentStore();
368
                $acs = $this->getAuthContentStore();
369
                if (is_null($acs)) return;
369
                if (is_null($acs)) return;
370
                $acs->adminLogoutEx($loginfo);
370
                $acs->adminLogoutEx($loginfo);
371
 
371
 
372
                if ($this->raNumLoggedIn() == 0) {
372
                if ($this->raNumLoggedIn() == 0) {
373
                        // Nobody here anymore. Destroy the cookie to make GDPR people happy
373
                        // Nobody here anymore. Destroy the cookie to make GDPR people happy
374
                        $acs->destroySession();
374
                        $acs->destroySession();
375
                } else {
375
                } else {
376
                        // Get a new token for the remaining users
376
                        // Get a new token for the remaining users
377
                        $acs->activate();
377
                        $acs->activate();
378
                }
378
                }
379
 
379
 
380
                OIDplus::logger()->log("V2:[OK]A", "Admin logged out (%1)", $loginfo);
380
                OIDplus::logger()->log("V2:[OK]A", "Admin logged out (%1)", $loginfo);
381
        }
381
        }
382
 
382
 
383
        // Authentication keys for validating arguments (e.g. sent by mail)
383
        // Authentication keys for generating secrets or validating arguments (e.g. sent by mail)
-
 
384
 
-
 
385
        /**
-
 
386
         * @param string $data
-
 
387
         * @return string
-
 
388
         * @throws OIDplusException
-
 
389
         */
-
 
390
        public function makeSecret(string $data): string {
-
 
391
                return sha3_512_hmac($data, 'OIDplus:'.OIDplus::baseConfig()->getValue('SERVER_SECRET'), false);
-
 
392
        }
384
 
393
 
385
        /**
394
        /**
386
         * @param string $data
395
         * @param string $data
387
         * @return string
396
         * @return string
388
         * @throws OIDplusException
397
         * @throws OIDplusException
389
         */
398
         */
390
        public function makeAuthKey(string $data): string {
399
        public function makeAuthKey(string $data): string {
391
                return sha3_512_hmac($data, 'authkey:'.OIDplus::baseConfig()->getValue('SERVER_SECRET'), false);
400
                return $this->makeSecret($data);
392
        }
401
        }
393
 
402
 
394
        /**
403
        /**
395
         * @param string $data
404
         * @param string $data
396
         * @param string $auth_key
405
         * @param string $auth_key
397
         * @return bool
406
         * @return bool
398
         * @throws OIDplusException
407
         * @throws OIDplusException
399
         */
408
         */
400
        public function validateAuthKey(string $data, string $auth_key): bool {
409
        public function validateAuthKey(string $data, string $auth_key): bool {
401
                return hash_equals($this->makeAuthKey($data), $auth_key);
410
                return hash_equals($this->makeAuthKey($data), $auth_key);
402
        }
411
        }
403
 
412
 
404
        // "Veto" functions to force logout state
413
        // "Veto" functions to force logout state
405
 
414
 
406
        /**
415
        /**
407
         * @return bool
416
         * @return bool
408
         */
417
         */
409
        protected function forceAllLoggedOut(): bool {
418
        protected function forceAllLoggedOut(): bool {
410
                if (isset($_SERVER['SCRIPT_FILENAME']) && (basename($_SERVER['SCRIPT_FILENAME']) == 'sitemap.php')) {
419
                if (isset($_SERVER['SCRIPT_FILENAME']) && (basename($_SERVER['SCRIPT_FILENAME']) == 'sitemap.php')) {
411
                        // The sitemap may not contain any confidential information,
420
                        // The sitemap may not contain any confidential information,
412
                        // even if the user is logged in, because the admin could
421
                        // even if the user is logged in, because the admin could
413
                        // accidentally copy-paste the sitemap to a
422
                        // accidentally copy-paste the sitemap to a
414
                        // search engine control panel while they are logged in
423
                        // search engine control panel while they are logged in
415
                        return true;
424
                        return true;
416
                } else {
425
                } else {
417
                        return false;
426
                        return false;
418
                }
427
                }
419
        }
428
        }
420
 
429
 
421
        // CSRF functions
430
        // CSRF functions
422
 
431
 
423
        private $enable_csrf = true;
432
        private $enable_csrf = true;
424
 
433
 
425
        /**
434
        /**
426
         * @return void
435
         * @return void
427
         */
436
         */
428
        public function enableCSRF() {
437
        public function enableCSRF() {
429
                $this->enable_csrf = true;
438
                $this->enable_csrf = true;
430
        }
439
        }
431
 
440
 
432
        /**
441
        /**
433
         * @return void
442
         * @return void
434
         */
443
         */
435
        public function disableCSRF() {
444
        public function disableCSRF() {
436
                $this->enable_csrf = false;
445
                $this->enable_csrf = false;
437
        }
446
        }
438
 
447
 
439
        /**
448
        /**
440
         * @return string
449
         * @return string
441
         * @throws \Random\RandomException
450
         * @throws \Random\RandomException
442
         */
451
         */
443
        public function genCSRFToken(): string {
452
        public function genCSRFToken(): string {
444
                return random_bytes_ex(64, false, false);
453
                return random_bytes_ex(64, false, false);
445
        }
454
        }
446
 
455
 
447
        /**
456
        /**
448
         * @return void
457
         * @return void
449
         * @throws OIDplusException
458
         * @throws OIDplusException
450
         */
459
         */
451
        public function checkCSRF() {
460
        public function checkCSRF() {
452
                if (!$this->enable_csrf) return;
461
                if (!$this->enable_csrf) return;
453
 
462
 
454
                $request_token = $_REQUEST['csrf_token'] ?? '';
463
                $request_token = $_REQUEST['csrf_token'] ?? '';
455
                $cookie_token = $_COOKIE['csrf_token'] ?? '';
464
                $cookie_token = $_COOKIE['csrf_token'] ?? '';
456
 
465
 
457
                if (empty($request_token) || empty($cookie_token) || ($request_token !== $cookie_token)) {
466
                if (empty($request_token) || empty($cookie_token) || ($request_token !== $cookie_token)) {
458
                        if (OIDplus::baseConfig()->getValue('DEBUG')) {
467
                        if (OIDplus::baseConfig()->getValue('DEBUG')) {
459
                                throw new OIDplusException(_L('Missing or wrong CSRF Token: Request %1 vs Cookie %2',
468
                                throw new OIDplusException(_L('Missing or wrong CSRF Token: Request %1 vs Cookie %2',
460
                                        isset($_REQUEST['csrf_token']) ? '"'.$_REQUEST['csrf_token'].'"' : 'NULL',
469
                                        isset($_REQUEST['csrf_token']) ? '"'.$_REQUEST['csrf_token'].'"' : 'NULL',
461
                                        $_COOKIE['csrf_token'] ?? 'NULL'
470
                                        $_COOKIE['csrf_token'] ?? 'NULL'
462
                                ));
471
                                ));
463
                        } else {
472
                        } else {
464
                                throw new OIDplusException(_L('Missing or wrong "CSRF Token". To fix the issue, try clearing your browser cache and reload the page. If you visited the page via HTTPS before, try HTTPS in case you are currently connected via HTTP.'));
473
                                throw new OIDplusException(_L('Missing or wrong "CSRF Token". To fix the issue, try clearing your browser cache and reload the page. If you visited the page via HTTPS before, try HTTPS in case you are currently connected via HTTP.'));
465
                        }
474
                        }
466
                }
475
                }
467
        }
476
        }
468
 
477
 
469
        // Generate RA passwords
478
        // Generate RA passwords
470
 
479
 
471
        /**
480
        /**
472
         * @param string $password
481
         * @param string $password
473
         * @return OIDplusRAAuthInfo
482
         * @return OIDplusRAAuthInfo
474
         * @throws OIDplusException
483
         * @throws OIDplusException
475
         */
484
         */
476
        public function raGeneratePassword(string $password): OIDplusRAAuthInfo {
485
        public function raGeneratePassword(string $password): OIDplusRAAuthInfo {
477
                $plugin = OIDplus::getDefaultRaAuthPlugin(true);
486
                $plugin = OIDplus::getDefaultRaAuthPlugin(true);
478
                return $plugin->generate($this->raPepperProcessing($password));
487
                return $plugin->generate($this->raPepperProcessing($password));
479
        }
488
        }
480
 
489
 
481
        // Generate admin password
490
        // Generate admin password
482
 
491
 
483
        /* Nothing here; the admin password will be generated in setup_base.js , purely in the web-browser */
492
        /* Nothing here; the admin password will be generated in setup_base.js , purely in the web-browser */
484
 
493
 
485
}
494
}
486
 
495