Docker image / push (push) Canceled after 0s
包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、登录页JS修复等全部改动
160 lines
5.7 KiB
PHP
160 lines
5.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace MRBS\Session;
|
|
|
|
use MRBS\SessionHandler\Cookie;
|
|
use MRBS\User;
|
|
use function MRBS\auth;
|
|
use function MRBS\get_form_var;
|
|
use function MRBS\is_ajax;
|
|
use function MRBS\str_ends_with_array;
|
|
use function MRBS\url_base;
|
|
|
|
/**
|
|
* Uses PHP's built-in session handling.
|
|
*/
|
|
class SessionPhp extends SessionWithLogin
|
|
{
|
|
|
|
public function __construct()
|
|
{
|
|
global $auth, $server;
|
|
|
|
parent::__construct();
|
|
|
|
// Check to see if we've been inactive for longer than allowed and if so log out the user.
|
|
// Don't log out the user if we're in kiosk mode because the kiosk will normally be inactive.
|
|
// Note that we cannot use is_kiosk_mode() here as that will create an infinite loop calling session().
|
|
if (!empty($auth['session_php']['inactivity_expire_time']) && !isset($_SESSION['kiosk_password_hash']))
|
|
{
|
|
if (isset($_SESSION['LastActivity']) &&
|
|
((time() - $_SESSION['LastActivity']) > $auth['session_php']['inactivity_expire_time']))
|
|
{
|
|
$this->logoffUser();
|
|
}
|
|
// Ajax requests don't count as activity, unless it's the special Ajax request used
|
|
// to record client side activity.
|
|
$activity = get_form_var('activity', 'int');
|
|
if ($activity || !is_ajax() || !isset($_SESSION['LastActivity'])) {
|
|
$_SESSION['LastActivity'] = time();
|
|
}
|
|
}
|
|
|
|
// Move the current page to the last page, so it can be used as a referrer, and store the new current page -
|
|
// but only if (a) we are at the top level of the MRBS web directory (eg index.php) so as to eliminate all
|
|
// the ./js, ./ajax and ./css pages and (b) this is not otherwise an Ajax request, eg one of the prefetch
|
|
// calls to index.php.
|
|
if (isset($server['SCRIPT_FILENAME']) && (MRBS_ROOT === dirname($server['SCRIPT_FILENAME'])) &&
|
|
!(isset($server['HTTP_X_REQUESTED_WITH']) && ($server['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest')))
|
|
{
|
|
$this->updatePage($server['REQUEST_URI'] ?? $server['PHP_SELF'] ?? null);
|
|
}
|
|
}
|
|
|
|
|
|
// If the server has a Referrer-Policy of strict-origin then HTTP_REFERER will be unreliable
|
|
// and it is better to use the last page that we have stored in the $_SESSION variable.
|
|
public function getReferrer(): ?string
|
|
{
|
|
$result = parent::getReferrer();
|
|
|
|
// If the last page is known, and it's well-formed (parse_url() doesn't return false), and it's
|
|
// not already a fully-qualified URL (ie the scheme is empty), and we've got a url base that
|
|
// isn't the empty string, then return everything after the last '/' from the last page, prefixed
|
|
// by the url base, in case there's a proxy in use.
|
|
if (isset($_SESSION['last_page']))
|
|
{
|
|
$last_page = $_SESSION['last_page'];
|
|
$scheme = parse_url($last_page, PHP_URL_SCHEME);
|
|
if (($scheme === null) && ('' !== ($base = url_base())))
|
|
{
|
|
// Get everything after the last '/'
|
|
$array = explode('/', $last_page);
|
|
$result = $base . array_pop($array);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
public function updatePage(?string $url): void
|
|
{
|
|
// Don't update the page if the URL as the same as the one we've already got
|
|
// stored for this page. This will be the case if the user has refreshed the
|
|
// browser, and if we update the page then we'll lose the last page, which is
|
|
// sometimes needed for MRBS to know where to go back to.
|
|
if (isset($_SESSION['this_page']) && ($url === $_SESSION['this_page']))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$_SESSION['last_page'] = $_SESSION['this_page'] ?? null;
|
|
$_SESSION['this_page'] = $url;
|
|
}
|
|
|
|
|
|
public function getCurrentUser() : ?User
|
|
{
|
|
$result = $_SESSION['user'] ?? null;
|
|
|
|
// For some unknown reason the integer value 0 is sometimes stored in the session
|
|
// variable. It's not clear how this can happen.
|
|
if (isset($result) && !(is_object($result) && is_a($result, 'MRBS\User')))
|
|
{
|
|
trigger_error('$_SESSION["user"] is expected to be a User object, not ' . json_encode($result), E_USER_WARNING);
|
|
$result = null;
|
|
}
|
|
|
|
return $result ?? parent::getCurrentUser();
|
|
}
|
|
|
|
|
|
protected function logonUser(string $username) : void
|
|
{
|
|
$user = auth()->getUser($username);
|
|
|
|
// As a defence against session fixation, regenerate
|
|
// the session id and delete the old session.
|
|
$this->regenerate();
|
|
$_SESSION['user'] = $user;
|
|
|
|
// Problems have been reported on Windows IIS with session data not being
|
|
// written out without a call to session_write_close()
|
|
session_write_close();
|
|
}
|
|
|
|
|
|
public function logoffUser() : void
|
|
{
|
|
global $cookie_path_override;
|
|
|
|
if (ini_get("session.use_cookies"))
|
|
{
|
|
// Delete the session cookie
|
|
Cookie::delete(session_name());
|
|
|
|
// Delete any cookies which may have previously been set, incorrectly, before the fixes to get_cookie_path (see
|
|
// https://github.com/meeting-room-booking-system/mrbs-code/commit/90ceeb8a0bc5f4850065695a3e085114c5ecae8e and
|
|
// https://github.com/meeting-room-booking-system/mrbs-code/commit/cb74320048149c4199281b88d50fb69988a41312).
|
|
// Note that the problem didn't occur if $cookie_path_override was set.
|
|
// In time, once all the incorrect cookies have expired naturally, this block can be deleted.
|
|
if (!isset($cookie_path_override))
|
|
{
|
|
$params = session_get_cookie_params();
|
|
$suffixes = array('ajax/', 'js/');
|
|
// If the path ends with one of the suffixes we'll already have deleted it above
|
|
if (!str_ends_with_array($params['path'], $suffixes))
|
|
{
|
|
foreach ($suffixes as $suffix)
|
|
{
|
|
setcookie(session_name(), '', time() - 42000, $params['path'] . $suffix, $params['domain'], $params['secure'], isset($params['httponly']));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->destroy();
|
|
}
|
|
}
|