1 <?php
2
3 4 5
6 class rex_login
7 {
8 protected $DB = 1;
9 protected $sessionDuration;
10 protected $loginQuery;
11 protected $userQuery;
12 protected $impersonateQuery;
13 protected $systemId = 'default';
14 protected $userLogin;
15 protected $userPassword;
16 protected $logout = false;
17 protected $idColumn = 'id';
18 protected $passwordColumn = 'password';
19 protected $cache = false;
20 protected $loginStatus = 0;
21 protected $message = '';
22
23
24 protected $user;
25
26
27 protected $impersonator;
28
29 30 31
32 public function __construct()
33 {
34 self::startSession();
35 }
36
37 38 39 40
41 public function setCache($status = true)
42 {
43 $this->cache = $status;
44 }
45
46 47 48
49 public function setSqlDb($DB)
50 {
51 $this->DB = $DB;
52 }
53
54 55 56 57
58 public function setSystemId($system_id)
59 {
60 $this->systemId = $system_id;
61 }
62
63 64 65
66 public function setSessionDuration($sessionDuration)
67 {
68 $this->sessionDuration = $sessionDuration;
69 }
70
71 72 73
74 public function setLogin($login, $password, $isPreHashed = false)
75 {
76 $this->userLogin = $login;
77 $this->userPassword = $isPreHashed ? $password : sha1($password);
78 }
79
80 81 82
83 public function setLogout($logout)
84 {
85 $this->logout = $logout;
86 }
87
88 89 90
91 public function isLoggedOut()
92 {
93 return $this->logout;
94 }
95
96 97 98 99 100 101
102 public function setUserQuery($user_query)
103 {
104 $this->userQuery = $user_query;
105 }
106
107 108 109 110 111
112 public function setImpersonateQuery($impersonateQuery)
113 {
114 $this->impersonateQuery = $impersonateQuery;
115 }
116
117 118 119 120 121 122
123 public function setLoginQuery($login_query)
124 {
125 $this->loginQuery = $login_query;
126 }
127
128 129 130
131 public function setIdColumn($idColumn)
132 {
133 $this->idColumn = $idColumn;
134 }
135
136 137 138 139 140
141 public function setPasswordColumn($passwordColumn)
142 {
143 $this->passwordColumn = $passwordColumn;
144 }
145
146 147 148
149 protected function setMessage($message)
150 {
151 $this->message = $message;
152 }
153
154 155 156 157 158
159 public function getMessage()
160 {
161 return $this->message;
162 }
163
164 165 166 167 168 169
170 public function checkLogin()
171 {
172
173
174
175 $ok = false;
176
177 if (!$this->logout) {
178
179
180
181 if ($this->cache && $this->loginStatus != 0) {
182 return $this->loginStatus > 0;
183 }
184
185 if ($this->userLogin != '') {
186
187
188
189 $this->user = rex_sql::factory($this->DB);
190
191 $this->user->setQuery($this->loginQuery, [':login' => $this->userLogin]);
192 if ($this->user->getRows() == 1 && self::passwordVerify($this->userPassword, $this->user->getValue($this->passwordColumn), true)) {
193 $ok = true;
194 self::regenerateSessionId();
195 $this->setSessionVar('UID', $this->user->getValue($this->idColumn));
196 } else {
197 $this->message = rex_i18n::msg('login_error');
198 }
199 } elseif ($this->getSessionVar('UID') != '') {
200
201
202
203 $ok = true;
204
205 if (($this->getSessionVar('STAMP') + $this->sessionDuration) < time()) {
206 $ok = false;
207 $this->message = rex_i18n::msg('login_session_expired');
208
209 rex_csrf_token::removeAll();
210 }
211
212 if ($ok && $impersonator = $this->getSessionVar('impersonator')) {
213 $this->impersonator = rex_sql::factory($this->DB);
214 $this->impersonator->setQuery($this->userQuery, [':id' => $impersonator]);
215
216 if (!$this->impersonator->getRows()) {
217 $ok = false;
218 $this->message = rex_i18n::msg('login_user_not_found');
219 }
220 }
221
222 if ($ok) {
223 $query = $this->impersonator && $this->impersonateQuery ? $this->impersonateQuery : $this->userQuery;
224 $this->user = rex_sql::factory($this->DB);
225 $this->user->setQuery($query, [':id' => $this->getSessionVar('UID')]);
226
227 if (!$this->user->getRows()) {
228 $ok = false;
229 $this->message = rex_i18n::msg('login_user_not_found');
230 }
231 }
232 }
233 } else {
234 $this->message = rex_i18n::msg('login_logged_out');
235
236 rex_csrf_token::removeAll();
237 }
238
239 if ($ok) {
240
241 $this->setSessionVar('STAMP', time());
242
243
244 $sessUid = $this->getSessionVar('UID');
245 if (empty($sessUid)) {
246 throw new rex_exception('Login considered successfull but no UID found');
247 }
248 } else {
249
250 $this->setSessionVar('STAMP', '');
251 $this->setSessionVar('UID', '');
252 $this->setSessionVar('impersonator', null);
253 }
254
255 if ($ok) {
256 $this->loginStatus = 1;
257 } else {
258 $this->loginStatus = -1;
259 }
260
261 return $ok;
262 }
263
264 public function impersonate($id)
265 {
266 if (!$this->user) {
267 throw new RuntimeException('Can not impersonate a user without valid user session.');
268 }
269 if ($this->user->getValue($this->idColumn) == $id) {
270 throw new RuntimeException('Can not impersonate the current user.');
271 }
272
273 $user = rex_sql::factory($this->DB);
274 $user->setQuery($this->impersonateQuery ?: $this->userQuery, [':id' => $id]);
275
276 if (!$user->getRows()) {
277 throw new RuntimeException(sprintf('User with id "%d" not found.', $id));
278 }
279
280 $this->impersonator = $this->user;
281 $this->user = $user;
282
283 $this->setSessionVar('UID', $id);
284 $this->setSessionVar('impersonator', $this->impersonator->getValue($this->idColumn));
285 }
286
287 public function depersonate()
288 {
289 if (!$this->impersonator) {
290 throw new RuntimeException('There is no current impersonator.');
291 }
292
293 $this->user = $this->impersonator;
294 $this->impersonator = null;
295
296 $this->setSessionVar('UID', $this->user->getValue($this->idColumn));
297 $this->setSessionVar('impersonator', null);
298 }
299
300 301 302
303 public function getUser()
304 {
305 return $this->user;
306 }
307
308 309 310
311 public function getImpersonator()
312 {
313 return $this->impersonator;
314 }
315
316 317 318
319 public function getValue($value, $default = null)
320 {
321 if ($this->user) {
322 return $this->user->getValue($value);
323 }
324
325 return $default;
326 }
327
328 329 330
331 public function setSessionVar($varname, $value)
332 {
333 $_SESSION[static::getSessionNamespace()][$this->systemId][$varname] = $value;
334 }
335
336 337 338
339 public function getSessionVar($varname, $default = '')
340 {
341 static $sessChecked = false;
342
343 if (!$sessChecked) {
344 $rexSessId = !empty($_SESSION['REX_SESSID']) ? $_SESSION['REX_SESSID'] : '';
345
346 if (!empty($rexSessId) && $rexSessId !== session_id()) {
347
348 $_SESSION[static::getSessionNamespace()][$this->systemId] = [];
349 }
350 $sessChecked = true;
351 }
352
353 if (isset($_SESSION[static::getSessionNamespace()][$this->systemId][$varname])) {
354 return $_SESSION[static::getSessionNamespace()][$this->systemId][$varname];
355 }
356
357 return $default;
358 }
359
360 361 362
363 protected static function regenerateSessionId()
364 {
365 if ('' != session_id()) {
366 session_regenerate_id(true);
367
368 $cookieParams = static::getCookieParams();
369 if ($cookieParams['samesite']) {
370 self::rewriteSessionCookie($cookieParams['samesite']);
371 }
372
373 rex_csrf_token::removeAll();
374 }
375
376
377 $_SESSION['REX_SESSID'] = session_id();
378 }
379
380 381 382
383 public static function startSession()
384 {
385 if (session_id() == '') {
386 $cookieParams = static::getCookieParams();
387
388 session_set_cookie_params(
389 $cookieParams['lifetime'],
390 $cookieParams['path'],
391 $cookieParams['domain'],
392 $cookieParams['secure'],
393 $cookieParams['httponly']
394 );
395
396 $started = rex_timer::measure(__METHOD__, function () {
397 return @session_start();
398 });
399 if (!$started) {
400 $error = error_get_last();
401 if ($error) {
402 rex_error_handler::handleError($error['type'], $error['message'], $error['file'], $error['line']);
403 } else {
404 throw new rex_exception('Unable to start session!');
405 }
406 }
407
408 if ($cookieParams['samesite']) {
409 self::rewriteSessionCookie($cookieParams['samesite']);
410 }
411 }
412 }
413
414 415 416 417 418
419 private static function getCookieParams()
420 {
421 $cookieParams = session_get_cookie_params();
422
423 $key = rex::isBackend() ? 'backend' : 'frontend';
424 $sessionConfig = rex::getProperty('session', []);
425
426 if ($sessionConfig) {
427 foreach ($sessionConfig[$key]['cookie'] as $name => $value) {
428 if ($value !== null) {
429 $cookieParams[$name] = $value;
430 }
431 }
432 }
433
434 return $cookieParams;
435 }
436
437 438 439 440 441 442 443 444
445 private static function rewriteSessionCookie($sameSite)
446 {
447 $cookiesHeaders = [];
448
449
450
451 $cookieHeadersPrefix = 'Set-Cookie: ';
452 $sessionCookiePrefix = 'Set-Cookie: '. session_name() .'=';
453 foreach (headers_list() as $rawHeader) {
454
455 if (substr($rawHeader, 0, strlen($sessionCookiePrefix)) === $sessionCookiePrefix) {
456 $rawHeader .= '; SameSite='. $sameSite;
457 }
458
459 if (substr($rawHeader, 0, strlen($cookieHeadersPrefix)) === $cookieHeadersPrefix) {
460 $cookiesHeaders[] = $rawHeader;
461 }
462 }
463
464
465 header_remove('Set-Cookie');
466
467
468 foreach ($cookiesHeaders as $rawHeader) {
469 header($rawHeader);
470 }
471 }
472
473 474 475
476 public static function passwordHash($password, $isPreHashed = false)
477 {
478 $password = $isPreHashed ? $password : sha1($password);
479 return password_hash($password, PASSWORD_DEFAULT);
480 }
481
482 public static function passwordVerify($password, $hash, $isPreHashed = false)
483 {
484 $password = $isPreHashed ? $password : sha1($password);
485 return password_verify($password, $hash);
486 }
487
488 public static function passwordNeedsRehash($hash)
489 {
490 return password_needs_rehash($hash, PASSWORD_DEFAULT);
491 }
492
493 494 495 496 497
498 protected static function getSessionNamespace()
499 {
500 return rex_request::getSessionNamespace();
501 }
502 }
503