Subversion Repositories oidplus

Rev

Rev 1300 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1300 Rev 1301
Line 23... Line 23...
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 OIDplusAuthContentStoreSession extends OIDplusAuthContentStore {
26
class OIDplusAuthContentStoreSession extends OIDplusAuthContentStore {
27
 
27
 
28
        /**
-
 
29
         * @return OIDplusSessionHandler
-
 
30
         */
-
 
31
        protected static function getSessionHandler(): OIDplusSessionHandler {
-
 
32
                static $sesHandler = null;
-
 
33
                if (is_null($sesHandler)) {
-
 
34
                        $sesHandler = new OIDplusSessionHandler();
-
 
35
                }
-
 
36
                return $sesHandler;
-
 
37
        }
-
 
38
 
-
 
39
        // Override abstract functions
28
        // Override abstract functions
40
        # TODO: shouldn't we just include OIDplusSessionHandler in this class?
-
 
41
 
29
 
42
        /**
30
        /**
43
         * @param string $name
31
         * @param string $name
44
         * @param mixed|null $default
32
         * @param mixed|null $default
45
         * @return mixed|null
33
         * @return mixed|null
46
         * @throws OIDplusException
34
         * @throws OIDplusException
47
         */
35
         */
48
        public function getValue(string $name, $default = NULL) {
36
        public function getValue(string $name, $default = NULL) {
49
                try {
37
                try {
-
 
38
                        if (isset($this->cacheSetValues[$name])) return self::decrypt($this->cacheSetValues[$name], $this->secret);
-
 
39
 
-
 
40
                        if (!$this->isActive()) return $default; // GDPR: Only start a session when we really need one
-
 
41
                        $this->sessionSafeStart();
-
 
42
                        OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
-
 
43
 
-
 
44
                        if (!isset($_SESSION[$name])) return $default;
50
                        return self::getSessionHandler()->getValue($name, $default);
45
                        return self::decrypt($_SESSION[$name], $this->secret);
51
                } catch (\Exception $e) {
46
                } catch (\Exception $e) {
52
                        self::getSessionHandler()->destroySession();
47
                        $this->destroySession();
53
                        // TODO: For some reason If destroySession() is called, we won't get this Exception?!
48
                        // TODO: For some reason If destroySession() is called, we won't get this Exception?!
54
                        throw new OIDplusException(_L('Internal error with session. Please reload the page and log-in again. %1', $e->getMessage()));
49
                        throw new OIDplusException(_L('Internal error with session. Please reload the page and log-in again. %1', $e->getMessage()));
55
                }
50
                }
56
        }
51
        }
57
 
52
 
Line 60... Line 55...
60
         * @param mixed $value
55
         * @param mixed $value
61
         * @return void
56
         * @return void
62
         * @throws OIDplusException
57
         * @throws OIDplusException
63
         */
58
         */
64
        public function setValue(string $name, $value) {
59
        public function setValue(string $name, $value) {
-
 
60
                $enc_data = self::encrypt($value, $this->secret);
-
 
61
 
65
                self::getSessionHandler()->setValue($name, $value);
62
                $this->cacheSetValues[$name] = $enc_data;
-
 
63
 
-
 
64
                $this->sessionSafeStart();
-
 
65
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
-
 
66
 
-
 
67
                $_SESSION[$name] = $enc_data;
66
        }
68
        }
67
 
69
 
68
        /**
70
        /**
69
         * @param string $name
71
         * @param string $name
70
         * @return bool
72
         * @return bool
71
         * @throws OIDplusException
73
         * @throws OIDplusException
72
         */
74
         */
73
        public function exists(string $name): bool {
75
        public function exists(string $name): bool {
-
 
76
                if (isset($this->cacheSetValues[$name])) return true;
-
 
77
 
-
 
78
                if (!$this->isActive()) return false; // GDPR: Only start a session when we really need one
-
 
79
                $this->sessionSafeStart();
-
 
80
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
-
 
81
 
74
                return self::getSessionHandler()->exists($name);
82
                return isset($_SESSION[$name]);
75
        }
83
        }
76
 
84
 
77
        /**
85
        /**
78
         * @param string $name
86
         * @param string $name
79
         * @return void
87
         * @return void
80
         * @throws OIDplusException
88
         * @throws OIDplusException
81
         */
89
         */
82
        public function delete(string $name) {
90
        public function delete(string $name) {
-
 
91
                if (isset($this->cacheSetValues[$name])) unset($this->cacheSetValues[$name]);
-
 
92
 
-
 
93
                if (!$this->isActive()) return; // GDPR: Only start a session when we really need one
83
                self::getSessionHandler()->delete($name);
94
                $this->sessionSafeStart();
-
 
95
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
-
 
96
 
-
 
97
                unset($_SESSION[$name]);
84
        }
98
        }
85
 
99
 
86
        /**
100
        /**
87
         * @return void
101
         * @return void
88
         * @throws OIDplusException
102
         * @throws OIDplusException
89
         */
103
         */
90
        public function destroySession() {
104
        public function destroySession() {
-
 
105
                if (!$this->isActive()) return;
-
 
106
 
-
 
107
                $this->sessionSafeStart();
-
 
108
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
-
 
109
 
-
 
110
                $_SESSION = array();
91
                self::getSessionHandler()->destroySession();
111
                session_destroy();
-
 
112
                session_write_close();
-
 
113
                OIDplus::cookieUtils()->unsetcookie(session_name()); // remove cookie, so GDPR people are happy
92
        }
114
        }
93
 
115
 
94
        /**
116
        /**
95
         * @return OIDplusAuthContentStoreSession|null
117
         * @return OIDplusAuthContentStoreSession|null
-
 
118
         * @throws OIDplusException
96
         */
119
         */
97
        public static function getActiveProvider()/*: ?OIDplusAuthContentStore*/ {
120
        public static function getActiveProvider()/*: ?OIDplusAuthContentStore*/ {
98
                static $contentProvider = null;
121
                static $contentProvider = null;
99
 
122
 
100
                $rel_url = substr($_SERVER['REQUEST_URI'], strlen(OIDplus::webpath(null, OIDplus::PATH_RELATIVE_TO_ROOT)));
123
                $rel_url = substr($_SERVER['REQUEST_URI'], strlen(OIDplus::webpath(null, OIDplus::PATH_RELATIVE_TO_ROOT)));
Line 102... Line 125...
102
                        // For REST, we must only allow JWT from Bearer and nothing else! So disable cookies if we are accessing the REST plugin
125
                        // For REST, we must only allow JWT from Bearer and nothing else! So disable cookies if we are accessing the REST plugin
103
                        return null;
126
                        return null;
104
                }
127
                }
105
 
128
 
106
                if (!$contentProvider) {
129
                if (!$contentProvider) {
107
                        if (self::getSessionHandler()->isActive()) {
130
                        if (self::isActive()) {
108
                                $contentProvider = new OIDplusAuthContentStoreSession();
131
                                $contentProvider = new OIDplusAuthContentStoreSession();
109
                        }
132
                        }
110
                }
133
                }
111
 
134
 
112
                return $contentProvider;
135
                return $contentProvider;
Line 114... Line 137...
114
 
137
 
115
        /**
138
        /**
116
         * @param string $email
139
         * @param string $email
117
         * @param string $loginfo
140
         * @param string $loginfo
118
         * @return void
141
         * @return void
-
 
142
         * @throws OIDplusException
119
         */
143
         */
120
        public function raLoginEx(string $email, string &$loginfo) {
144
        public function raLoginEx(string $email, string &$loginfo) {
121
                $this->raLogin($email);
145
                $this->raLogin($email);
122
                if (is_null(self::getActiveProvider())) {
146
                if (is_null(self::getActiveProvider())) {
123
                        $loginfo = 'into new PHP session';
147
                        $loginfo = 'into new PHP session';
Line 127... Line 151...
127
        }
151
        }
128
 
152
 
129
        /**
153
        /**
130
         * @param string $loginfo
154
         * @param string $loginfo
131
         * @return void
155
         * @return void
-
 
156
         * @throws OIDplusException
132
         */
157
         */
133
        public function adminLoginEx(string &$loginfo) {
158
        public function adminLoginEx(string &$loginfo) {
134
                $this->adminLogin();
159
                $this->adminLogin();
135
                if (is_null(self::getActiveProvider())) {
160
                if (is_null(self::getActiveProvider())) {
136
                        $loginfo = 'into new PHP session';
161
                        $loginfo = 'into new PHP session';
Line 163... Line 188...
163
         */
188
         */
164
        public function activate() {
189
        public function activate() {
165
                # Sessions automatically activate during setValue()
190
                # Sessions automatically activate during setValue()
166
        }
191
        }
167
 
192
 
-
 
193
        # ------------------------------------------------------------------------------------------------------------------
-
 
194
 
-
 
195
        /**
-
 
196
         * @var string|null
-
 
197
         */
-
 
198
        private $secret = '';
-
 
199
 
-
 
200
        /**
-
 
201
         * @var int|null
-
 
202
         */
-
 
203
        protected $sessionLifetime = 0;
-
 
204
 
-
 
205
        /**
-
 
206
         * @throws OIDplusException
-
 
207
         */
-
 
208
        public function __construct() {
-
 
209
                $this->sessionLifetime = OIDplus::baseConfig()->getValue('SESSION_LIFETIME', 30*60);
-
 
210
                $this->secret = OIDplus::authUtils()->makeSecret(['b118abc8-f4ec-11ed-86ca-3c4a92df8582']);
-
 
211
 
-
 
212
                // **PREVENTING SESSION HIJACKING**
-
 
213
                // Prevents javascript XSS attacks aimed to steal the session ID
-
 
214
                @ini_set('session.cookie_httponly', '1');
-
 
215
 
-
 
216
                // **PREVENTING SESSION FIXATION**
-
 
217
                // Session ID cannot be passed through URLs
-
 
218
                @ini_set('session.use_only_cookies', '1');
-
 
219
 
-
 
220
                @ini_set('session.use_trans_sid', '0');
-
 
221
 
-
 
222
                // Uses a secure connection (HTTPS) if possible
-
 
223
                @ini_set('session.cookie_secure', OIDplus::isSslAvailable() ? '1' : '0');
-
 
224
 
-
 
225
                $path = OIDplus::webpath(null,OIDplus::PATH_RELATIVE);
-
 
226
                if (empty($path)) $path = '/';
-
 
227
                @ini_set('session.cookie_path', $path);
-
 
228
 
-
 
229
                @ini_set('session.cookie_samesite', OIDplus::baseConfig()->getValue('COOKIE_SAMESITE_POLICY', 'Strict'));
-
 
230
 
-
 
231
                @ini_set('session.use_strict_mode', '1');
-
 
232
 
-
 
233
                @ini_set('session.gc_maxlifetime', $this->sessionLifetime);
-
 
234
        }
-
 
235
 
-
 
236
        /**
-
 
237
         * @return void
-
 
238
         * @throws OIDplusException
-
 
239
         */
-
 
240
        protected function sessionSafeStart() {
-
 
241
                if (!isset($_SESSION)) {
-
 
242
                        // TODO: session_name() makes some problems. Leave it away for now.
-
 
243
                        //session_name('OIDplus_SESHDLR');
-
 
244
                        if (!session_start()) {
-
 
245
                                throw new OIDplusException(_L('Session could not be started'));
-
 
246
                        }
-
 
247
                }
-
 
248
 
-
 
249
                if (!isset($_SESSION['ip'])) {
-
 
250
                        if (!isset($_SERVER['REMOTE_ADDR'])) return;
-
 
251
 
-
 
252
                        // Remember the IP address of the user
-
 
253
                        $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
-
 
254
                } else {
-
 
255
                        if ($_SERVER['REMOTE_ADDR'] != $_SESSION['ip']) {
-
 
256
                                // Was the session hijacked?! Get out of here!
-
 
257
 
-
 
258
                                // We don't use $this->destroySession(), because this calls sessionSafeStart() again
-
 
259
                                $_SESSION = array();
-
 
260
                                session_destroy();
-
 
261
                                session_write_close();
-
 
262
                                OIDplus::cookieUtils()->unsetcookie(session_name()); // remove cookie, so GDPR people are happy
-
 
263
                        }
-
 
264
                }
-
 
265
        }
-
 
266
 
-
 
267
        /**
-
 
268
         * @return void
-
 
269
         */
-
 
270
        function __destruct() {
-
 
271
                session_write_close();
-
 
272
        }
-
 
273
 
-
 
274
        private $cacheSetValues = array(); // Important if you do a setValue() followed by an getValue()
-
 
275
 
-
 
276
        /**
-
 
277
         * @return bool
-
 
278
         */
-
 
279
        public static function isActive(): bool {
-
 
280
                return isset($_COOKIE[session_name()]);
-
 
281
        }
-
 
282
 
-
 
283
        /**
-
 
284
         * @param string $data
-
 
285
         * @param string $key
-
 
286
         * @return string
-
 
287
         * @throws \Exception
-
 
288
         */
-
 
289
        protected static function encrypt(string $data, string $key): string {
-
 
290
                if (function_exists('openssl_encrypt')) {
-
 
291
                        $iv = random_bytes(16); // AES block size in CBC mode
-
 
292
                        // Encryption
-
 
293
                        $ciphertext = openssl_encrypt(
-
 
294
                                $data,
-
 
295
                                'AES-256-CBC',
-
 
296
                                hash_pbkdf2('sha512', $key, '', 10000, 32/*256bit*/, true),
-
 
297
                                OPENSSL_RAW_DATA,
-
 
298
                                $iv
-
 
299
                        );
-
 
300
                        // Authentication
-
 
301
                        $hmac = sha3_512_hmac($iv . $ciphertext, $key, true);
-
 
302
                        return $hmac . $iv . $ciphertext;
-
 
303
                } else {
-
 
304
                        // When OpenSSL is not available, then we just do a HMAC
-
 
305
                        $hmac = sha3_512_hmac($data, $key, true);
-
 
306
                        return $hmac . $data;
-
 
307
                }
-
 
308
        }
-
 
309
 
-
 
310
        /**
-
 
311
         * @param string $data
-
 
312
         * @param string $key
-
 
313
         * @return string
-
 
314
         * @throws OIDplusException
-
 
315
         */
-
 
316
        protected static function decrypt(string $data, string $key): string {
-
 
317
                if (function_exists('openssl_decrypt')) {
-
 
318
                        $hmac       = mb_substr($data, 0, 64, '8bit');
-
 
319
                        $iv         = mb_substr($data, 64, 16, '8bit');
-
 
320
                        $ciphertext = mb_substr($data, 80, null, '8bit');
-
 
321
                        // Authentication
-
 
322
                        $hmacNew = sha3_512_hmac($iv . $ciphertext, $key, true);
-
 
323
                        if (!hash_equals($hmac, $hmacNew)) {
-
 
324
                                throw new OIDplusException(_L('Authentication failed'));
-
 
325
                        }
-
 
326
                        // Decryption
-
 
327
                        $cleartext = openssl_decrypt(
-
 
328
                                $ciphertext,
-
 
329
                                'AES-256-CBC',
-
 
330
                                hash_pbkdf2('sha512', $key, '', 10000, 32/*256bit*/, true),
-
 
331
                                OPENSSL_RAW_DATA,
-
 
332
                                $iv
-
 
333
                        );
-
 
334
                        if ($cleartext === false) {
-
 
335
                                throw new OIDplusException(_L('Decryption failed'));
-
 
336
                        }
-
 
337
                        return $cleartext;
-
 
338
                } else {
-
 
339
                        // When OpenSSL is not available, then we just do a HMAC
-
 
340
                        $hmac       = mb_substr($data, 0, 64, '8bit');
-
 
341
                        $cleartext  = mb_substr($data, 64, null, '8bit');
-
 
342
                        $hmacNew    = sha3_512_hmac($cleartext, $key, true);
-
 
343
                        if (!hash_equals($hmac, $hmacNew)) {
-
 
344
                                throw new OIDplusException(_L('Authentication failed'));
-
 
345
                        }
-
 
346
                        return $cleartext;
-
 
347
                }
-
 
348
        }
-
 
349
 
-
 
350
 
168
}
351
}