Subversion Repositories oidplus

Rev

Rev 466 | Rev 564 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
511 daniel-mar 5
 * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft
2 daniel-mar 6
 *
7
 * Licensed under the Apache License, Version 2.0 (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
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
511 daniel-mar 20
if (!defined('INSIDE_OIDPLUS')) die();
21
 
2 daniel-mar 22
class OIDplusAuthUtils {
23
 
457 daniel-mar 24
        public static function getRandomBytes($len) {
25
                if (function_exists('openssl_random_pseudo_bytes')) {
26
                        $a = openssl_random_pseudo_bytes($len);
27
                        if ($a) return $a;
28
                }
29
 
465 daniel-mar 30
                if (function_exists('mcrypt_create_iv')) {
466 daniel-mar 31
                        $a = bin2hex(mcrypt_create_iv($len, MCRYPT_DEV_URANDOM));
465 daniel-mar 32
                        if ($a) return $a;
33
                }
34
 
457 daniel-mar 35
                if (function_exists('random_bytes')) {
36
                        $a = random_bytes($len);
37
                        if ($a) return $a;
38
                }
39
 
40
                // Fallback to non-secure RNG
41
                $a = '';
42
                while (strlen($a) < $len*2) {
43
                        $a .= sha1(uniqid(mt_rand(), true));
44
                }
45
                $a = substr($a, 0, $len*2);
46
                return hex2bin($a);
47
        }
48
 
14 daniel-mar 49
        // RA authentication functions
2 daniel-mar 50
 
51
        public static function raLogin($email) {
52
                if (strpos($email, '|') !== false) return;
53
 
42 daniel-mar 54
                $ses = OIDplus::sesHandler();
2 daniel-mar 55
                $list = $ses->getValue('oidplus_logged_in');
56
                if (is_null($list)) $list = '';
57
 
58
                $ary = ($list == '') ? array() : explode('|', $list);
59
                if (!in_array($email, $ary)) $ary[] = $email;
60
                $list = implode('|', $ary);
61
 
62
                $ses->setValue('oidplus_logged_in', $list);
63
        }
64
 
65
        public static function raLogout($email) {
42 daniel-mar 66
                $ses = OIDplus::sesHandler();
2 daniel-mar 67
                $list = $ses->getValue('oidplus_logged_in');
68
                if (is_null($list)) $list = '';
69
 
70
                $ary = ($list == '') ? array() : explode('|', $list);
71
                $key = array_search($email, $ary);
72
                if ($key !== false) unset($ary[$key]);
73
                $list = implode('|', $ary);
74
 
75
                $ses->setValue('oidplus_logged_in', $list);
85 daniel-mar 76
 
179 daniel-mar 77
                if (($list == '') && (!self::isAdminLoggedIn())) {
85 daniel-mar 78
                        // Nobody logged in anymore. Destroy session cookie to make GDPR people happy
79
                        $ses->destroySession();
80
                }
2 daniel-mar 81
        }
82
 
329 daniel-mar 83
        public static function raCheckPassword($ra_email, $password) {
84
                $ra = new OIDplusRA($ra_email);
453 daniel-mar 85
 
459 daniel-mar 86
                $authInfo = $ra->getAuthInfo();
453 daniel-mar 87
 
88
                $plugins = OIDplus::getAuthPlugins();
89
                if (count($plugins) == 0) {
90
                        throw new OIDplusException(_L('No RA authentication plugins found'));
91
                }
92
                foreach ($plugins as $plugin) {
459 daniel-mar 93
                        if ($plugin->verify($authInfo, $password)) return true;
453 daniel-mar 94
                }
95
 
96
                return false;
329 daniel-mar 97
        }
98
 
85 daniel-mar 99
        public static function raNumLoggedIn() {
329 daniel-mar 100
                return count(self::loggedInRaList());
85 daniel-mar 101
        }
102
 
2 daniel-mar 103
        public static function raLogoutAll() {
42 daniel-mar 104
                $ses = OIDplus::sesHandler();
2 daniel-mar 105
                $ses->setValue('oidplus_logged_in', '');
106
        }
107
 
108
        public static function loggedInRaList() {
329 daniel-mar 109
                if (self::forceAllLoggedOut()) {
282 daniel-mar 110
                        return array();
111
                }
329 daniel-mar 112
 
42 daniel-mar 113
                $ses = OIDplus::sesHandler();
2 daniel-mar 114
                $list = $ses->getValue('oidplus_logged_in');
115
                if (is_null($list)) $list = '';
115 daniel-mar 116
 
117
                $res = array();
329 daniel-mar 118
                foreach (array_unique(explode('|',$list)) as $ra_email) {
115 daniel-mar 119
                        if ($ra_email == '') continue;
120
                        $res[] = new OIDplusRA($ra_email);
121
                }
122
                return $res;
2 daniel-mar 123
        }
124
 
125
        public static function isRaLoggedIn($email) {
115 daniel-mar 126
                foreach (self::loggedInRaList() as $ra) {
127
                        if ($email == $ra->raEmail()) return true;
128
                }
129
                return false;
2 daniel-mar 130
        }
131
 
14 daniel-mar 132
        // Admin authentication functions
2 daniel-mar 133
 
134
        public static function adminLogin() {
42 daniel-mar 135
                $ses = OIDplus::sesHandler();
2 daniel-mar 136
                $ses->setValue('oidplus_admin_logged_in', '1');
137
        }
138
 
139
        public static function adminLogout() {
42 daniel-mar 140
                $ses = OIDplus::sesHandler();
85 daniel-mar 141
                $ses->setValue('oidplus_admin_logged_in', '0');
142
 
143
                if (self::raNumLoggedIn() == 0) {
144
                        // Nobody logged in anymore. Destroy session cookie to make GDPR people happy
145
                        $ses->destroySession();
146
                }
2 daniel-mar 147
        }
148
 
149
        public static function adminCheckPassword($password) {
421 daniel-mar 150
                $passwordData = OIDplus::baseConfig()->getValue('ADMIN_PASSWORD', '');
151
                if (empty($passwordData)) {
360 daniel-mar 152
                        throw new OIDplusException(_L('No admin password set in %1','userdata/baseconfig/config.inc.php'));
261 daniel-mar 153
                }
456 daniel-mar 154
 
421 daniel-mar 155
                if (strpos($passwordData, '$') !== false) {
456 daniel-mar 156
                        if ($passwordData[0] == '$') {
157
                                // Version 3: BCrypt
158
                                return password_verify($password, $passwordData);
159
                        } else {
457 daniel-mar 160
                                // Version 2: SHA3-512 with salt
456 daniel-mar 161
                                list($s_salt, $hash) = explode('$', $passwordData, 2);
162
                        }
421 daniel-mar 163
                } else {
456 daniel-mar 164
                        // Version 1: SHA3-512 without salt
421 daniel-mar 165
                        $s_salt = '';
166
                        $hash = $passwordData;
167
                }
168
                return strcmp(sha3_512($s_salt.$password, true), base64_decode($hash)) === 0;
2 daniel-mar 169
        }
170
 
171
        public static function isAdminLoggedIn() {
329 daniel-mar 172
                if (self::forceAllLoggedOut()) {
282 daniel-mar 173
                        return false;
174
                }
42 daniel-mar 175
                $ses = OIDplus::sesHandler();
2 daniel-mar 176
                return $ses->getValue('oidplus_admin_logged_in') == '1';
177
        }
178
 
310 daniel-mar 179
        // Authentication keys for validating arguments (e.g. sent by mail)
2 daniel-mar 180
 
181
        public static function makeAuthKey($data) {
424 daniel-mar 182
                $data = OIDplus::baseConfig()->getValue('SERVER_SECRET') . '/AUTHKEY/' . $data;
421 daniel-mar 183
                $calc_authkey = sha3_512($data, false);
2 daniel-mar 184
                return $calc_authkey;
185
        }
186
 
187
        public static function validateAuthKey($data, $auth_key) {
421 daniel-mar 188
                return strcmp(self::makeAuthKey($data), $auth_key) === 0;
2 daniel-mar 189
        }
190
 
329 daniel-mar 191
        // "Veto" functions to force logout state
192
 
193
        public static function forceAllLoggedOut() {
194
                if (isset($_SERVER['SCRIPT_FILENAME']) && (basename($_SERVER['SCRIPT_FILENAME']) == 'sitemap.php')) {
195
                        // The sitemap may not contain any confidential information,
196
                        // even if the user is logged in, because the admin could
197
                        // accidentally copy-paste the sitemap to a
198
                        // search engine control panel while they are logged in
199
                        return true;
200
                } else {
201
                        return false;
202
                }
203
        }
427 daniel-mar 204
 
424 daniel-mar 205
        // CSRF functions
427 daniel-mar 206
 
424 daniel-mar 207
        private $enable_csrf = true;
427 daniel-mar 208
 
424 daniel-mar 209
        public function enableCSRF() {
210
                $this->enable_csrf = true;
211
        }
427 daniel-mar 212
 
424 daniel-mar 213
        public function disableCSRF() {
214
                $this->enable_csrf = false;
215
        }
427 daniel-mar 216
 
424 daniel-mar 217
        public function genCSRFToken() {
457 daniel-mar 218
                return bin2hex(OIDplusAuthUtils::getRandomBytes(64));
424 daniel-mar 219
        }
427 daniel-mar 220
 
424 daniel-mar 221
        public function checkCSRF() {
427 daniel-mar 222
                if (!$this->enable_csrf) return;
424 daniel-mar 223
                if (!isset($_REQUEST['csrf_token']) || !isset($_COOKIE['csrf_token']) || ($_REQUEST['csrf_token'] != $_COOKIE['csrf_token'])) {
224
                        throw new Exception(_L('Wrong CSRF Token'));
225
                }
226
        }
329 daniel-mar 227
 
421 daniel-mar 228
        // Generate RA passwords
229
 
461 daniel-mar 230
        public static function raGeneratePassword($password): OIDplusRAAuthInfo {
453 daniel-mar 231
                $def_method = OIDplus::config()->getValue('default_ra_auth_method');
232
 
233
                $plugins = OIDplus::getAuthPlugins();
234
                foreach ($plugins as $plugin) {
235
                        if (basename($plugin->getPluginDirectory()) === $def_method) {
236
                                return $plugin->generate($password);
237
                        }
238
                }
239
                throw new OIDplusException(_L('Default RA auth method/plugin "%1" not found',$def_method));
421 daniel-mar 240
        }
241
 
242
        // Generate admin password
243
 
244
        /* Nothing here; the admin password will be generated in setup_base.js */
245
 
246
}