包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、登录页JS修复等全部改动
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\SessionHandler\SessionHandlerDb;
|
||||
use MRBS\SessionHandler\SessionHandlerDbException;
|
||||
use MRBS\User;
|
||||
use SessionHandler;
|
||||
use function MRBS\db;
|
||||
use function MRBS\db_schema_version;
|
||||
use function MRBS\get_cookie_path;
|
||||
use function MRBS\is_https;
|
||||
|
||||
abstract class Session
|
||||
{
|
||||
protected const SAMESITE_NONE = 'None';
|
||||
protected const SAMESITE_LAX = 'Lax';
|
||||
protected const SAMESITE_STRICT = 'Strict';
|
||||
|
||||
protected $lifetime;
|
||||
protected $samesite = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $auth, $cookie_samesite_lax;
|
||||
|
||||
// Child classes can set $this->samesite
|
||||
if (!isset($this->samesite))
|
||||
{
|
||||
$this->samesite = ($cookie_samesite_lax) ? self::SAMESITE_LAX : self::SAMESITE_STRICT;
|
||||
}
|
||||
|
||||
// Set the session lifetime
|
||||
if (!isset($this->lifetime))
|
||||
{
|
||||
$this->lifetime = $auth['session_php']['session_expire_time'] ?? 0;
|
||||
}
|
||||
|
||||
// Start up sessions
|
||||
$this->init($this->lifetime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the session handler to use.
|
||||
*
|
||||
* If the database session handler is not available, use the ordinary PHP session handler.
|
||||
*
|
||||
* @return SessionHandlerDb|SessionHandler
|
||||
*/
|
||||
protected function getSessionHandler()
|
||||
{
|
||||
// The sessions table was only created in Upgrade 56. We test for the schema version rather than the existence of
|
||||
// the table, because the table is renamed in later upgrades.
|
||||
if (db_schema_version(db()) < 56)
|
||||
{
|
||||
return new SessionHandler();
|
||||
}
|
||||
|
||||
// The DB session handler uses locks, and because we use locks elsewhere, this means we need support for multiple
|
||||
// locks. We need to test now, rather than catching an exception later, because resetting the session handler
|
||||
// will reset the session id causing us to lose session data.
|
||||
if (!db()->supportsMultipleLocks())
|
||||
{
|
||||
$message = "The database server does not support multiple locks, so the database session handler " .
|
||||
"cannot be used. Using ordinary PHP sessions instead.";
|
||||
trigger_error($message);
|
||||
return new SessionHandler();
|
||||
}
|
||||
|
||||
// Otherwise use the DB session handler.
|
||||
return new SessionHandlerDb();
|
||||
}
|
||||
|
||||
|
||||
// Normally there's no need to call init() from outside the Session classes.
|
||||
// It only needs to be called to restart sessions, after, for example, a user
|
||||
// has been logged off, and you need to use session variables.
|
||||
public function init(int $lifetime) : void
|
||||
{
|
||||
global $auth;
|
||||
|
||||
if (session_status() === PHP_SESSION_ACTIVE)
|
||||
{
|
||||
// We've already started sessions
|
||||
return;
|
||||
}
|
||||
|
||||
// Session settings, for security
|
||||
// ini_set() only accepts string values prior to PHP 8.1.0
|
||||
ini_set('session.cookie_httponly', '1');
|
||||
if (version_compare(PHP_VERSION, '7.3', '>='))
|
||||
{
|
||||
// Only introduced in PHP Version 7.3
|
||||
ini_set('session.cookie_samesite', $this->samesite);
|
||||
}
|
||||
ini_set('session.cookie_secure', (is_https()) ? '1' : '0');
|
||||
|
||||
// More settings, as a defence against session fixation.
|
||||
ini_set('session.use_only_cookies', '1');
|
||||
ini_set('session.use_strict_mode', '1');
|
||||
ini_set('session.use_trans_sid', '0');
|
||||
|
||||
$cookie_path = get_cookie_path();
|
||||
|
||||
// We don't want the session garbage collector to delete the session before it has expired
|
||||
if ($lifetime !== 0)
|
||||
{
|
||||
assert(version_compare(MRBS_MIN_PHP_VERSION, '8.1') < 0, 'The strval() in the line below is no longer required.');
|
||||
ini_set('session.gc_maxlifetime', strval(max(ini_get('session.gc_maxlifetime'), $lifetime)));
|
||||
}
|
||||
|
||||
if (isset($auth['session_php']['session_name']))
|
||||
{
|
||||
// call before session_set_cookie_params() - see PHP manual
|
||||
session_name($auth['session_php']['session_name']);
|
||||
}
|
||||
session_set_cookie_params($lifetime, $cookie_path);
|
||||
|
||||
// Set the session handler and start up sessions
|
||||
try
|
||||
{
|
||||
session_set_save_handler($this->getSessionHandler(), true);
|
||||
if (false === session_start())
|
||||
{
|
||||
throw new \Exception("session_start() failed");
|
||||
}
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
$message = "Could not start sessions ('" . $e->getMessage() . "').";
|
||||
$message .= " Trying ordinary PHP sessions.";
|
||||
trigger_error($message, E_USER_WARNING);
|
||||
session_set_save_handler(new SessionHandler(), true);
|
||||
if (false === session_start())
|
||||
{
|
||||
throw new \Exception("MRBS: could not start sessions");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function destroy() : void
|
||||
{
|
||||
// Delete the session data encryption key cookie. If we don't do this then, when
|
||||
// a new session is created, unless the expiry is set to 0 (ie on browser close),
|
||||
// it will have a longer lifetime than the key cookie, which when it was created
|
||||
// was given the same lifetime as the session cookie. Once the key cookie expires,
|
||||
// the session handler will create a new cookie with a new key. So when the session
|
||||
// handler comes to decrypt the session data it will be doing so with the new key,
|
||||
// and not the key used to encrypt it. This will result in the Crypto library
|
||||
// throwing a WrongKeyOrModifiedCiphertextException with the message "Integrity
|
||||
// check failed".
|
||||
//
|
||||
// This needs to be done before the session is destroyed, otherwise the
|
||||
// deleteKeyCookie method won't be able to get the session name (which it needs
|
||||
// in order to delete the key cookie).
|
||||
SessionHandlerDb::deleteKeyCookie();
|
||||
|
||||
// Unset the session variables
|
||||
// Note that session_unset() only works if a session is active.
|
||||
$_SESSION = [];
|
||||
// Check whether a session is active before destroying it in order to avoid a
|
||||
// "Trying to destroy uninitialized session" warning.
|
||||
if (session_status() === PHP_SESSION_ACTIVE)
|
||||
{
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
// Problems have been reported on Windows IIS with session data not being
|
||||
// written out without a call to session_write_close(). [Is this necessary
|
||||
// after session_destroy() ??]
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
|
||||
protected function regenerate() : void
|
||||
{
|
||||
// Regenerate the session id
|
||||
session_regenerate_id(true);
|
||||
|
||||
// Change the lifetime of the key cookie to match the new expiry - see the
|
||||
// comment in destroy().
|
||||
SessionHandlerDb::regenerateKeyCookie();
|
||||
}
|
||||
|
||||
|
||||
public function get(string $name)
|
||||
{
|
||||
return $_SESSION[$name] ?? null;
|
||||
}
|
||||
|
||||
|
||||
public function isset(string $name) : bool
|
||||
{
|
||||
return isset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
public function set(string $name, $value) : void
|
||||
{
|
||||
$_SESSION[$name] = $value;
|
||||
}
|
||||
|
||||
|
||||
public function unset(string $name) : void
|
||||
{
|
||||
unset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
|
||||
// Returns the currently logged-in user
|
||||
// This method provides the fallback user for un-logged in users.
|
||||
// Subclasses are expected to override this method, calling it as the parent
|
||||
// if they cannot find a current user.
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
global $auth;
|
||||
|
||||
if (empty($auth['allow_anonymous_booking']))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use an empty string for anonymous bookings
|
||||
return new User('');
|
||||
}
|
||||
|
||||
|
||||
// Allows this to be extended with strategies for getting the referer when
|
||||
// HTTP_REFERER is going to be unreliable, eg when the Referrer-Policy is
|
||||
// set to strict-origin.
|
||||
public function getReferrer() : ?string
|
||||
{
|
||||
global $server;
|
||||
|
||||
return $server['HTTP_REFERER'] ?? null;
|
||||
}
|
||||
|
||||
|
||||
// Updates the current and previous pages
|
||||
public function updatePage(string $url) : void
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user