包含:登录失败锁定、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
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\Form\Form;
|
||||
use MRBS\User;
|
||||
use phpCAS;
|
||||
use function MRBS\auth;
|
||||
use function MRBS\location_header;
|
||||
use function MRBS\this_page;
|
||||
|
||||
|
||||
class SessionCas extends SessionWithLogin
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->checkTypeMatchesSession();
|
||||
$this->samesite = self::SAMESITE_LAX;
|
||||
auth()->init(); // Initialise CAS
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
public function init(int $lifetime) : void
|
||||
{
|
||||
// phpCAS does its own session initialisation and handling
|
||||
}
|
||||
|
||||
|
||||
public function authGet(?string $target_url=null, ?string $returl=null, ?string $error=null, bool $raw=false) : void
|
||||
{
|
||||
if (!phpCAS::isAuthenticated())
|
||||
{
|
||||
phpCAS::forceAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
return (phpCAS::isAuthenticated()) ? auth()->getUser(phpCAS::getUser()) : parent::getCurrentUser();
|
||||
}
|
||||
|
||||
|
||||
public function getLogonFormParams() : ?array
|
||||
{
|
||||
$target_url = this_page(true);
|
||||
|
||||
return array(
|
||||
'action' => $target_url,
|
||||
'method' => Form::METHOD_POST,
|
||||
'hidden_inputs' => array('target_url' => $target_url,
|
||||
'action' => 'QueryName')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function processForm() : void
|
||||
{
|
||||
if (isset($this->form['action']))
|
||||
{
|
||||
// Target of the form with sets the URL argument "action=QueryName".
|
||||
if ($this->form['action'] == 'QueryName')
|
||||
{
|
||||
phpCAS::forceAuthentication();
|
||||
}
|
||||
|
||||
// Target of the form with sets the URL argument "action=SetName".
|
||||
// Will eventually return to URL argument "target_url=whatever".
|
||||
if ($this->form['action'] == 'SetName')
|
||||
{
|
||||
// If we're going to do something then check the CSRF token first
|
||||
Form::checkToken();
|
||||
|
||||
// You should only get here using CAS authentication after clicking the logoff
|
||||
// link, no matter what the value of the form parameters.
|
||||
$this->logoffUser();
|
||||
|
||||
location_header($this->form['target_url']); // Redirect browser to initial page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function logoffUser() : void
|
||||
{
|
||||
phpCAS::logout();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\SessionHandler\Cookie;
|
||||
use MRBS\SessionHandler\SessionHandlerCookie;
|
||||
use SessionHandler;
|
||||
use function MRBS\get_cookie_path;
|
||||
|
||||
/**
|
||||
* Manage sessions via cookies stored in the client browser.
|
||||
*/
|
||||
class SessionCookie extends SessionPhp
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $auth;
|
||||
|
||||
// We have to use output buffering to ensure that the cookies are set before any other output is sent.
|
||||
ob_start();
|
||||
|
||||
// Delete old-style cookies
|
||||
if (!empty($_COOKIE) && isset($_COOKIE["UserName"]))
|
||||
{
|
||||
setcookie('UserName', '', time()-42000, get_cookie_path());
|
||||
}
|
||||
|
||||
// Set the session lifetime
|
||||
$this->lifetime = $auth['session_cookie']['session_expire_time'] ?? 0;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
protected function getSessionHandler() : SessionHandlerCookie
|
||||
{
|
||||
global $auth;
|
||||
|
||||
// Set the session handler
|
||||
return new SessionHandlerCookie(
|
||||
$auth['session_cookie']['secret'],
|
||||
$auth['session_cookie']['hash_algorithm'],
|
||||
$auth['session_cookie']['include_ip']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function init(int $lifetime) : void
|
||||
{
|
||||
$old_session_id = session_id();
|
||||
parent::init($lifetime);
|
||||
$new_session_id = session_id();
|
||||
SessionHandlerCookie::updateExpiry($new_session_id, ($lifetime === 0) ? 0 : time() + $lifetime);
|
||||
// Not entirely sure why this is necessary, but the old session data cookie is sometimes not deleted.
|
||||
if (($old_session_id !== '') && ($old_session_id !== $new_session_id))
|
||||
{
|
||||
Cookie::delete($old_session_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function logoffUser() : void
|
||||
{
|
||||
unset($_SESSION['user']);
|
||||
session_regenerate_id(true);
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
class SessionFactory
|
||||
{
|
||||
|
||||
public static function create(string $type)
|
||||
|
||||
{
|
||||
// Transform the session type from lowercase_separated to LowercaseSeparated
|
||||
$parts = explode('_', $type);
|
||||
$parts = array_map('ucfirst', $parts);
|
||||
$class = __NAMESPACE__ . "\\Session" . implode('', $parts);
|
||||
return new $class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
/**
|
||||
* This is a slight variant of session_ip.
|
||||
* Session management scheme that uses the DNS name of the computer to identify users and administrators.
|
||||
* Anyone who can access the server can make bookings etc.
|
||||
*
|
||||
* To use this authentication scheme set the following things in config.inc.php:
|
||||
*
|
||||
* $auth['type'] = 'none';
|
||||
* $auth['session'] = 'host';
|
||||
*
|
||||
* Then, you may configure admin users:
|
||||
*
|
||||
* $auth['admin'][] = 'DNSname1';
|
||||
* $auth['admin'][] = 'DNSname2';
|
||||
*/
|
||||
class SessionHost extends Session
|
||||
{
|
||||
|
||||
// No need to prompt for a name: if no DNSname is returned, the IP address is used
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
global $server;
|
||||
|
||||
if ((!isset($server['REMOTE_ADDR'])) ||
|
||||
(!is_string($server['REMOTE_ADDR'])) ||
|
||||
(($server['REMOTE_ADDR'] === '')))
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
return auth()->getUser(gethostbyaddr($server['REMOTE_ADDR']));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
/**
|
||||
* Get user identity using the HTTP basic authentication.
|
||||
*/
|
||||
class SessionHttp extends SessionWithLogin
|
||||
{
|
||||
|
||||
public function authGet(?string $target_url=null, ?string $returl=null, ?string $error=null, bool $raw=false) : void
|
||||
{
|
||||
global $auth;
|
||||
|
||||
header("WWW-Authenticate: Basic realm=\"$auth[realm]\"");
|
||||
header("HTTP/1.0 401 Unauthorized");
|
||||
}
|
||||
|
||||
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
global $server;
|
||||
|
||||
if (!isset($server['PHP_AUTH_USER']))
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
// Trim any whitespace because PHP_AUTH_USER can contain it.
|
||||
$php_auth_user = trim($server['PHP_AUTH_USER']);
|
||||
|
||||
if ($php_auth_user === '')
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
if (auth()->validateUser($php_auth_user, self::getAuthPassword()) === false)
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
return auth()->getUser($php_auth_user);
|
||||
}
|
||||
|
||||
|
||||
public function getLogoffFormParams() : ?array
|
||||
{
|
||||
// Just return null - you can't log off
|
||||
// (well, there are ways of achieving a logoff but we haven't implemented them)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static function getAuthPassword() : ?string
|
||||
{
|
||||
global $server;
|
||||
|
||||
return (isset($server['PHP_AUTH_PW'])) ? $server['PHP_AUTH_PW'] : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
/**
|
||||
* Session management scheme that uses IP addresses to identify users.
|
||||
*
|
||||
* Anyone who can access the server can make bookings. Administrators are also identified by their IP address.
|
||||
*
|
||||
* To use this authentication scheme, set the following things in config.inc.php:
|
||||
*
|
||||
* $auth['type'] = 'none';
|
||||
* $auth['session'] = 'ip';
|
||||
*
|
||||
* Then, you may configure admin users:
|
||||
*
|
||||
* $auth['admin'][] = '127.0.0.1'; // Local host = the server you're running on
|
||||
* $auth['admin'][] = '192.168.0.1';
|
||||
*/
|
||||
class SessionIp extends Session
|
||||
{
|
||||
|
||||
// No need to prompt for a name - IP address always there
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
global $server;
|
||||
|
||||
if ((!isset($server['REMOTE_ADDR'])) ||
|
||||
(!is_string($server['REMOTE_ADDR'])) ||
|
||||
(($server['REMOTE_ADDR'] === '')))
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
return auth()->getUser($server['REMOTE_ADDR']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use MRBS\Joomla\JFactory;
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
require_once MRBS_ROOT . '/auth/cms/joomla.inc';
|
||||
|
||||
|
||||
class SessionJoomla extends SessionWithLogin
|
||||
{
|
||||
|
||||
private const NAMESPACE = 'MRBS';
|
||||
|
||||
private $app;
|
||||
private $session;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->checkTypeMatchesSession();
|
||||
|
||||
if (!defined('JVERSION'))
|
||||
{
|
||||
throw new \Exception("Joomla! version not known");
|
||||
}
|
||||
|
||||
if (version_compare(JVERSION, '4.0', '<'))
|
||||
{
|
||||
$this->app = JFactory::getApplication('site');
|
||||
$this->app->initialise();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Thanks to Alex Chartier and Emmanuel Ingelaere.
|
||||
// See https://groups.google.com/g/joomla-dev-general/c/55J2s9hhMxA
|
||||
|
||||
// Boot the DI container
|
||||
$container = Factory::getContainer();
|
||||
|
||||
// Alias the session service keys to the web session service as that is the primary session backend for this application.
|
||||
// In addition to aliasing "common" service keys, we also create aliases for the PHP classes to ensure autowiring objects
|
||||
// is supported. This includes aliases for aliased class names, and the keys for aliased class names should be considered
|
||||
// deprecated to be removed when the class name alias is removed as well.
|
||||
$container->alias('session.web', 'session.web.site')
|
||||
->alias('session', 'session.web.site')
|
||||
->alias('JSession', 'session.web.site')
|
||||
->alias(\Joomla\CMS\Session\Session::class, 'session.web.site')
|
||||
->alias(\Joomla\Session\Session::class, 'session.web.site')
|
||||
->alias(\Joomla\Session\SessionInterface::class, 'session.web.site');
|
||||
|
||||
// Instantiate the application.
|
||||
$this->app = $container->get(\Joomla\CMS\Application\SiteApplication::class);
|
||||
// Build the namespace map and load the language (necessary from Joomla 4.3.0 onwards - see
|
||||
// https://groups.google.com/g/joomla-dev-general/c/55J2s9hhMxA/m/IpBrs3HZAgAJ?utm_medium=email&utm_source=footer&pli=1
|
||||
// and https://joomla.stackexchange.com/questions/32145/joomla-4-error-when-i-use-getarticleroute/32146#32146)
|
||||
if (version_compare(JVERSION, '4.3.0', '>='))
|
||||
{
|
||||
$this->app->createExtensionNamespaceMap();
|
||||
$lang = Language::getInstance('en'); // doesn't matter which language as we never use it
|
||||
$this->app->loadLanguage($lang);
|
||||
}
|
||||
|
||||
// Set the application as global app
|
||||
Factory::$application = $this->app;
|
||||
}
|
||||
|
||||
if (version_compare(JVERSION, '5.0', '<'))
|
||||
{
|
||||
$this->session = JFactory::getSession();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->session = Factory::getSession();
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
public function init(int $lifetime) : void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function get(string $name)
|
||||
{
|
||||
return $this->session->get($name, null, self::NAMESPACE);
|
||||
}
|
||||
|
||||
|
||||
public function isset(string $name) : bool
|
||||
{
|
||||
return ($this->get($name) !== null);
|
||||
}
|
||||
|
||||
public function set(string $name, $value) : void
|
||||
{
|
||||
$this->session->set($name, $value, self::NAMESPACE);
|
||||
}
|
||||
|
||||
|
||||
public function unset(string $name) : void
|
||||
{
|
||||
$this->session->clear($name, self::NAMESPACE);
|
||||
}
|
||||
|
||||
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
return auth()->getCurrentUser() ?? parent::getCurrentUser();
|
||||
}
|
||||
|
||||
|
||||
protected function logonUser(string $username) : void
|
||||
{
|
||||
// Don't need to do anything: the user will have been logged on when the
|
||||
// username and password were validated.
|
||||
}
|
||||
|
||||
|
||||
public function logoffUser() : void
|
||||
{
|
||||
$this->app->logout();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
/**
|
||||
* Session management scheme that uses Windows NT domain users and Internet
|
||||
* Information Server as the source for user authentication.
|
||||
*
|
||||
* To use this authentication scheme, set the following things in config.inc.php:
|
||||
*
|
||||
* $auth['type'] = 'none';
|
||||
* $auth['session'] = 'nt';
|
||||
*
|
||||
* Then, you may configure admin users:
|
||||
*
|
||||
* $auth['admin'][] = 'nt_username1';
|
||||
* $auth['admin'][] = 'nt_username2';
|
||||
*
|
||||
* See AUTHENTICATION for more information.
|
||||
*/
|
||||
class SessionNt extends Session
|
||||
{
|
||||
|
||||
// For this scheme no need to prompt for a name - NT User always there.
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
return auth()->getUser(get_current_user()) ?? parent::getCurrentUser();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
/**
|
||||
* Session management scheme that relies on OmniHttpd security for user
|
||||
* authentication. This is suitable for few users because we have to create all
|
||||
* users connecting to MRBS, since they will have to log in.
|
||||
*
|
||||
* To use this authentication scheme set the following things :
|
||||
* - Edit your virtual server hosting MRBS.
|
||||
* - Select security tab.
|
||||
* - If not yet set, choose "User and Directory" security type.
|
||||
* - Select "Users and groups" tab.
|
||||
* - Here, select "New User" and create as many users (Username/passwords) as you have users using MRBS.
|
||||
* - Select "New Group".
|
||||
* - Type "MRBS" as group name and add all users you just created to this group.
|
||||
* - Now select "Access Control list" tab.
|
||||
* - Select New. ENTER the relative path to MRBS. FOR example, if you created
|
||||
* the MRBS folder on the root web folder, you should type /MRBS/.
|
||||
* - Now go to the" user permission "tab, select " * ",
|
||||
* - Select Properties", and type MRBS (remove the star) and select "Is group".
|
||||
*
|
||||
* That's all! Confirm all windows. Now it is the web server that authenticates each user.
|
||||
*
|
||||
* In config.inc.php:
|
||||
*
|
||||
* $auth['type'] = 'none';
|
||||
* $auth['session'] = 'omni';
|
||||
*
|
||||
* Then, you may configure admin users:
|
||||
*
|
||||
* $auth['admin'][] = 'user1';
|
||||
* $auth['admin'][] = 'user2';
|
||||
*/
|
||||
class SessionOmni extends Session
|
||||
{
|
||||
|
||||
// No need to prompt for a name - this is done by the server.
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
global $server;
|
||||
|
||||
if ((!isset($server['REMOTE_USER'])) ||
|
||||
(!is_string($server['REMOTE_USER'])) ||
|
||||
(($server['REMOTE_USER'] === '')))
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
return auth()->getUser($server['REMOTE_USER']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\Form\Form;
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
/**
|
||||
* Get user identity/password using the REMOTE_USER environment variable. Both identity and password
|
||||
* equal the value of REMOTE_USER.
|
||||
*
|
||||
* To use this session scheme, set in config.inc.php:
|
||||
*
|
||||
* $auth['session'] = 'remote_user';
|
||||
* $auth['type'] = 'none';
|
||||
*
|
||||
* If you want to display a login link, set in config.inc.php:
|
||||
*
|
||||
* $auth['remote_user']['login_link'] = '/login/link.html';
|
||||
*
|
||||
* If you want to display a logout link, set in config.inc.php:
|
||||
*
|
||||
* $auth['remote_user']['logout_link'] = '/logout/link.html';
|
||||
*/
|
||||
class SessionRemoteUser extends SessionWithLogin
|
||||
{
|
||||
|
||||
// User is expected to already be authenticated by the web server, so do nothing
|
||||
public function authGet(?string $target_url=null, ?string $returl=null, ?string $error=null, bool $raw=false) : void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
global $server;
|
||||
|
||||
if ((!isset($server['REMOTE_USER'])) ||
|
||||
(!is_string($server['REMOTE_USER'])) ||
|
||||
(($server['REMOTE_USER'] === '')))
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
return auth()->getUser($server['REMOTE_USER']);
|
||||
}
|
||||
|
||||
|
||||
public function getLogonFormParams() : ?array
|
||||
{
|
||||
global $auth;
|
||||
|
||||
if (isset($auth['remote_user']['login_link']))
|
||||
{
|
||||
return array(
|
||||
'action' => $auth['remote_user']['login_link'],
|
||||
'method' => Form::METHOD_GET,
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getLogoffFormParams() : ?array
|
||||
{
|
||||
global $auth;
|
||||
|
||||
if (isset($auth['remote_user']['logout_link']))
|
||||
{
|
||||
return array(
|
||||
'action' => $auth['remote_user']['logout_link'],
|
||||
'method' => Form::METHOD_GET
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\Form\Form;
|
||||
use MRBS\User;
|
||||
use SimpleSAML\Auth\Simple;
|
||||
use function MRBS\auth;
|
||||
use function MRBS\this_page;
|
||||
use function MRBS\url_base;
|
||||
|
||||
|
||||
/**
|
||||
* Session management scheme that delegates everything to a ready configured
|
||||
* SimpleSamlPhp instance. You should use this scheme, along with the
|
||||
* authentication scheme with the same name, if you want your users to
|
||||
* authenticate using SAML Single Sign-on.
|
||||
*
|
||||
* In config.inc.php (assuming Active Directory attributes):
|
||||
*
|
||||
* $auth['type'] = 'saml';
|
||||
* $auth['session'] = 'saml';
|
||||
* $auth['saml']['ssp_path'] = '/opt/simplesamlphp';
|
||||
* $auth['saml']['authsource'] = 'default-sp';
|
||||
* $auth['saml']['attr']['username'] = 'sAMAccountName';
|
||||
* $auth['saml']['attr']['mail'] = 'mail';
|
||||
* $auth['saml']['attr']['givenName'] = 'givenname';
|
||||
* $auth['saml']['attr']['surname'] = 'sn'
|
||||
* $auth['saml']['admin']['memberOf'] = ['CN=Domain Admins,CN=Users,DC=example,DC=com'];
|
||||
*
|
||||
* This scheme assumes that you've already configured SimpleSamlPhp,
|
||||
* and that you have set up aliases in your webserver so that SimpleSamlPhp
|
||||
* can handle incoming assertions. Refer to the SimpleSamlPhp documentation
|
||||
* for more information on how to do that.
|
||||
*
|
||||
* @see https://simplesamlphp.org/docs/stable/simplesamlphp-install
|
||||
* @see https://simplesamlphp.org/docs/stable/simplesamlphp-sp
|
||||
*/
|
||||
class SessionSaml extends SessionWithLogin
|
||||
{
|
||||
public $ssp;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $auth;
|
||||
|
||||
$this->checkTypeMatchesSession();
|
||||
|
||||
// Check that the config variables have been set
|
||||
if (!isset($auth['saml']['ssp_path']))
|
||||
{
|
||||
throw new \Exception('$auth["saml"]["ssp_path"] must be set in the config file.');
|
||||
}
|
||||
|
||||
if (!isset($auth['saml']['attr']['username']))
|
||||
{
|
||||
throw new \Exception('$auth["saml"]["attr"]["username"] must be set in the config file.');
|
||||
}
|
||||
|
||||
// Include the SimpleSamlPhp autoloader
|
||||
require_once $auth['saml']['ssp_path'] . '/lib/_autoload.php';
|
||||
|
||||
// Get the SimpleSamlPhp instance for the configured auth source
|
||||
$authSource = $auth['saml']['authsource'] ?? 'default-sp';
|
||||
|
||||
$this->ssp = new \SimpleSAML\Auth\Simple($authSource);
|
||||
$this->samesite = self::SAMESITE_LAX;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
public function init(int $lifetime) : void
|
||||
{
|
||||
global $auth;
|
||||
|
||||
if ($auth['saml']['disable_mrbs_session_init'])
|
||||
{
|
||||
// If we're using SAML then initialising sessions here can interfere with
|
||||
// session handling in some SAML libraries
|
||||
return;
|
||||
}
|
||||
|
||||
parent::init($lifetime);
|
||||
}
|
||||
|
||||
// No need to prompt for a name - this is done by SimpleSamlPhp
|
||||
public function authGet(?string $target_url=null, ?string $returl=null, ?string $error=null, bool $raw=false) : void
|
||||
{
|
||||
$this->ssp->requireAuth();
|
||||
}
|
||||
|
||||
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
$current_username = $this->getUsername();
|
||||
|
||||
return (isset($current_username)) ? auth()->getUser($current_username) : parent::getCurrentUser();
|
||||
}
|
||||
|
||||
|
||||
public function getUsername() : ?string
|
||||
{
|
||||
global $auth;
|
||||
|
||||
if (!$this->ssp->isAuthenticated())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$userData = $this->ssp->getAttributes();
|
||||
$userNameAttr = $auth['saml']['attr']['username'];
|
||||
|
||||
return array_key_exists($userNameAttr, $userData) ? $userData[$userNameAttr][0] : null;
|
||||
}
|
||||
|
||||
|
||||
public function getLogonFormParams() : ?array
|
||||
{
|
||||
$target_url = url_base() . this_page(true);
|
||||
$url = $this->ssp->getLoginURL($target_url);
|
||||
$baseURL = strstr($url, '?', true);
|
||||
parse_str(substr(strstr($url, '?'), 1), $params);
|
||||
|
||||
$result = array(
|
||||
'action' => $baseURL,
|
||||
'method' => Form::METHOD_GET
|
||||
);
|
||||
|
||||
if (!empty($params))
|
||||
{
|
||||
$result['hidden_inputs'] = $params;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function getLogoffFormParams() : ?array
|
||||
{
|
||||
$target_url = url_base() . this_page(true);
|
||||
$url = $this->ssp->getLogoutURL($target_url);
|
||||
$baseURL = strstr($url, '?', true);
|
||||
parse_str(substr(strstr($url, '?'), 1), $params);
|
||||
|
||||
$result = array(
|
||||
'action' => $baseURL,
|
||||
'method' => Form::METHOD_GET
|
||||
);
|
||||
|
||||
if (!empty($params))
|
||||
{
|
||||
$result['hidden_inputs'] = $params;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function processForm() : void
|
||||
{
|
||||
// No need to do anything - all handled by SAML
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\Form\ElementA;
|
||||
use MRBS\Form\ElementFieldset;
|
||||
use MRBS\Form\ElementP;
|
||||
use MRBS\Form\FieldDiv;
|
||||
use MRBS\Form\FieldInputPassword;
|
||||
use MRBS\Form\FieldInputSubmit;
|
||||
use MRBS\Form\FieldInputText;
|
||||
use MRBS\Form\Form;
|
||||
use MRBS\Audit;
|
||||
use function MRBS\auth;
|
||||
use function MRBS\get_form_var;
|
||||
use function MRBS\get_vocab;
|
||||
use function MRBS\location_header;
|
||||
use function MRBS\multisite;
|
||||
use function MRBS\print_footer;
|
||||
use function MRBS\print_header;
|
||||
use function MRBS\this_page;
|
||||
|
||||
/**
|
||||
* An abstract class for those session schemes that implement a login form.
|
||||
*/
|
||||
abstract class SessionWithLogin extends Session
|
||||
{
|
||||
protected $form = array();
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Get the non-standard form variables
|
||||
$vars = [
|
||||
'action' => 'string',
|
||||
'username' => 'string',
|
||||
'password' => 'string',
|
||||
'returl' => 'url_local'
|
||||
];
|
||||
|
||||
foreach ($vars as $var => $type)
|
||||
{
|
||||
$this->form[$var] = get_form_var($var, $type, null, INPUT_POST);
|
||||
}
|
||||
|
||||
// Allow the target_url to be a GET or POST value to help password managers (the target_url can
|
||||
// be stored as a query string parameter in the password manager).
|
||||
$this->form['target_url'] = get_form_var('target_url', 'url_local');
|
||||
|
||||
if (isset($this->form['username']))
|
||||
{
|
||||
// It's easy for extra spaces to appear, especially on a mobile device
|
||||
$this->form['username'] = trim($this->form['username']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Gets the username and password. Returns: Nothing
|
||||
//
|
||||
// $target_url The URL to go to after successful login
|
||||
// $returl The URL to return to eventually
|
||||
public function authGet(?string $target_url=null, ?string $returl=null, ?string $error=null, bool $raw=false) : void
|
||||
{
|
||||
if (!isset($target_url))
|
||||
{
|
||||
$target_url = $this->form['target_url'] ?? this_page(true);
|
||||
}
|
||||
|
||||
// Omit the Login link in the header when we're on the login page itself
|
||||
print_header(null, false, true);
|
||||
$action = multisite(this_page());
|
||||
$this->printLoginForm($action, $target_url, $returl, $error, $raw);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// Returns the parameters ('method', 'action' and 'hidden_inputs') for a
|
||||
// Logon form. Returns an array.
|
||||
public function getLogonFormParams() : ?array
|
||||
{
|
||||
return array(
|
||||
'action' => multisite('admin.php'),
|
||||
'method' => Form::METHOD_POST,
|
||||
'hidden_inputs' => array('target_url' => this_page(true),
|
||||
'action' => 'QueryName')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Returns the parameters ('method', 'action' and 'hidden_inputs') for a
|
||||
// logoff form. Returns an array of parameters, or null if no form is to be
|
||||
// shown.
|
||||
public function getLogoffFormParams() : ?array
|
||||
{
|
||||
return array(
|
||||
'action' => multisite('admin.php'),
|
||||
'method' => Form::METHOD_POST,
|
||||
'hidden_inputs' => array('target_url' => this_page(true),
|
||||
'action' => 'SetName',
|
||||
'username' => '',
|
||||
'password' => '')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function processForm() : void
|
||||
{
|
||||
if (isset($this->form['action']))
|
||||
{
|
||||
// Target of the form with sets the URL argument "action=QueryName".
|
||||
// Will eventually return to URL argument "target_url=whatever".
|
||||
if ($this->form['action'] == 'QueryName')
|
||||
{
|
||||
$this->authGet($this->form['target_url']);
|
||||
exit(); // unnecessary because authGet() exits, but just included for clarity
|
||||
}
|
||||
|
||||
// Target of the form with sets the URL argument "action=SetName".
|
||||
// Will eventually return to URL argument "target_url=whatever".
|
||||
if ($this->form['action'] == 'SetName')
|
||||
{
|
||||
// First make sure the password is valid
|
||||
if (!isset($this->form['username']) || ($this->form['username'] == ''))
|
||||
{
|
||||
$this->logoffUser();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're going to do something then check the CSRF token first.
|
||||
// (Don't check the token before logging off the user because if the session has
|
||||
// expired due to inactivity, the token will be invalid, but that won't matter because
|
||||
// the result will be the same anyway - logging off the user - and we avoid
|
||||
// generating an unnecessary CSRF error message.)
|
||||
Form::checkToken();
|
||||
|
||||
// Get a valid user
|
||||
$valid_username = $this->getValidUser($this->form['username'], $this->form['password']);
|
||||
|
||||
// Successful login. You can't get out of getValidUser() without a valid username and password
|
||||
|
||||
// ===== 等保整改:登录成功审计 =====
|
||||
Audit::log('LOGIN_OK', $valid_username);
|
||||
|
||||
// ===== 等保整改:口令到期 / 存量账号首登 → 强制改密 =====
|
||||
// 必须在 logonUser()(其内部调用 session_write_close())之前写入会话,否则会丢失
|
||||
$auth_obj = auth();
|
||||
if (method_exists($auth_obj, 'needsPasswordChange') &&
|
||||
$auth_obj->needsPasswordChange($valid_username))
|
||||
{
|
||||
$_SESSION['mrbs_force_pwd_change'] = 1;
|
||||
}
|
||||
|
||||
$this->logonUser($valid_username);
|
||||
|
||||
if (!empty($this->form['returl']))
|
||||
{
|
||||
// check to see whether there's a query string already
|
||||
$this->form['target_url'] .= (mb_strpos($this->form['target_url'], '?') === false) ? '?' : '&';
|
||||
$this->form['target_url'] .= 'returl=' . urlencode($this->form['returl']);
|
||||
}
|
||||
}
|
||||
|
||||
location_header($this->form['target_url']); // Redirect browser to initial page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Can only return a valid username. If the username and password are not valid it will ask for new ones.
|
||||
protected function getValidUser(
|
||||
#[\SensitiveParameter]
|
||||
?string $username,
|
||||
#[\SensitiveParameter]
|
||||
?string $password) : string
|
||||
{
|
||||
if (!isset($this->form['password']) ||
|
||||
(($valid_username = auth()->validateUser($this->form['username'], $this->form['password'])) === false))
|
||||
{
|
||||
// ===== 等保整改:登录失败 / 账号锁定 审计与提示区分 =====
|
||||
$login_name = $this->form['username'] ?? '';
|
||||
$auth_obj = auth();
|
||||
$error = get_vocab('unknown_user');
|
||||
|
||||
if (method_exists($auth_obj, 'getLoginBlocked') && $auth_obj->getLoginBlocked())
|
||||
{
|
||||
global $login_lock_duration;
|
||||
$minutes = (int)ceil(($login_lock_duration ?? 900) / 60);
|
||||
$error = get_vocab('account_locked', $minutes);
|
||||
Audit::log('LOGIN_BLOCKED', $login_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Audit::log('LOGIN_FAIL', $login_name);
|
||||
}
|
||||
|
||||
$this->authGet($this->form['target_url'], $this->form['returl'], $error);
|
||||
exit(); // unnecessary because authGet() exits, but just included for clarity
|
||||
}
|
||||
|
||||
return $valid_username;
|
||||
}
|
||||
|
||||
|
||||
protected function logonUser(string $username) : void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function logoffUser() : void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Displays the login form.
|
||||
// Will eventually return to $target_url with query string returl=$returl
|
||||
// If $error is set then an $error is printed.
|
||||
// If $raw is true then the message is not HTML escaped
|
||||
private function printLoginForm(string $action, ?string $target_url, ?string $returl, ?string $error=null, bool $raw=false) : void
|
||||
{
|
||||
$form = new Form(Form::METHOD_POST);
|
||||
$form->setAttributes(array('class' => 'standard',
|
||||
'id' => 'logon',
|
||||
'action' => $action));
|
||||
|
||||
// Hidden inputs
|
||||
$hidden_inputs = array('returl' => $returl,
|
||||
'target_url' => $target_url,
|
||||
'action' => 'SetName');
|
||||
$form->addHiddenInputs($hidden_inputs);
|
||||
|
||||
// Now for the visible fields
|
||||
if (isset($error))
|
||||
{
|
||||
$p = new ElementP();
|
||||
$p->setText($error, false, $raw);
|
||||
$form->addElement($p);
|
||||
}
|
||||
|
||||
$fieldset = new ElementFieldset();
|
||||
$fieldset->addLegend(get_vocab('please_login'));
|
||||
|
||||
// The username field
|
||||
if (auth()->canValidateByEmail() && auth()->canValidateByUsername())
|
||||
{
|
||||
$tag = 'username_or_email';
|
||||
}
|
||||
elseif (auth()->canValidateByUsername())
|
||||
{
|
||||
$tag = 'users.name';
|
||||
}
|
||||
else
|
||||
{
|
||||
$tag = 'users.email';
|
||||
}
|
||||
|
||||
$placeholder = get_vocab($tag);
|
||||
|
||||
$field = new FieldInputText();
|
||||
$field->setLabel(get_vocab('user'))
|
||||
->setLabelAttributes(array('title' => $placeholder))
|
||||
->setControlAttributes(array('id' => 'username',
|
||||
'name' => 'username',
|
||||
'placeholder' => $placeholder,
|
||||
'required' => true,
|
||||
'autofocus' => true,
|
||||
'autocomplete' => 'username'));
|
||||
$fieldset->addElement($field);
|
||||
|
||||
// The password field
|
||||
$field = new FieldInputPassword();
|
||||
$field->setLabel(get_vocab('users.password'))
|
||||
->setControlAttributes(array('id' => 'password',
|
||||
'name' => 'password',
|
||||
'autocomplete' => 'current-password'));
|
||||
$fieldset->addElement($field);
|
||||
|
||||
$form->addElement($fieldset);
|
||||
|
||||
// The submit button
|
||||
$fieldset = new ElementFieldset();
|
||||
$field = new FieldInputSubmit();
|
||||
$field->setControlAttributes(array('value' => get_vocab('login')));
|
||||
$fieldset->addElement($field);
|
||||
|
||||
$form->addElement($fieldset);
|
||||
|
||||
if (auth()->canResetPassword())
|
||||
{
|
||||
$fieldset = new ElementFieldset();
|
||||
$field = new FieldDiv();
|
||||
$a = new ElementA();
|
||||
$a->setAttribute('href', multisite('reset_password.php'))
|
||||
->setText(get_vocab('lost_password'));
|
||||
$field->addControl($a);
|
||||
$fieldset->addElement($field);
|
||||
$form->addElement($fieldset);
|
||||
}
|
||||
|
||||
$form->render();
|
||||
|
||||
|
||||
|
||||
// Print footer and exit
|
||||
print_footer(true);
|
||||
}
|
||||
|
||||
|
||||
// Check we've got the right authentication type for the session scheme.
|
||||
// To be called for those session schemes which require the same
|
||||
// authentication type
|
||||
protected function checkTypeMatchesSession() : void
|
||||
{
|
||||
global $auth;
|
||||
|
||||
if ($auth['type'] !== $auth['session'])
|
||||
{
|
||||
$class = get_called_class();
|
||||
$message = "MRBS configuration error: $class needs \$auth['type'] set to '" . $auth['session'] . "'";
|
||||
die($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Session;
|
||||
|
||||
use MRBS\User;
|
||||
use function MRBS\auth;
|
||||
|
||||
require_once MRBS_ROOT . '/auth/cms/wordpress.inc';
|
||||
|
||||
|
||||
class SessionWordpress extends SessionWithLogin
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->checkTypeMatchesSession();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
public function getCurrentUser() : ?User
|
||||
{
|
||||
if (!is_user_logged_in())
|
||||
{
|
||||
return parent::getCurrentUser();
|
||||
}
|
||||
|
||||
$mrbs_user = wp_get_current_user();
|
||||
|
||||
return auth()->getUser($mrbs_user->user_login);
|
||||
}
|
||||
|
||||
|
||||
// Can only return a valid username. If the username and password are not valid it will ask for new ones.
|
||||
protected function getValidUser(
|
||||
#[\SensitiveParameter]
|
||||
?string $username,
|
||||
#[\SensitiveParameter]
|
||||
?string $password) : string
|
||||
{
|
||||
global $errors; // $errors is a WordPress global
|
||||
|
||||
$credentials = array();
|
||||
$credentials['user_login'] = $username;
|
||||
$credentials['user_password'] = $password;
|
||||
$credentials['remember'] = false;
|
||||
$wp_user = wp_signon($credentials);
|
||||
|
||||
if (is_wp_error($wp_user))
|
||||
{
|
||||
$errors = $wp_user;
|
||||
$error_message = apply_filters('login_errors', $wp_user->get_error_message());
|
||||
// The WordPress error message contains HTML so don't escape it.
|
||||
$this->authGet($this->form['target_url'], $this->form['returl'], $error_message, true);
|
||||
exit(); // unnecessary because authGet() exits, but just included for clarity
|
||||
}
|
||||
|
||||
return $username;
|
||||
}
|
||||
|
||||
|
||||
protected function logonUser(string $username) : void
|
||||
{
|
||||
// Don't need to do anything: the user will have been logged on when the
|
||||
// username and password were validated.
|
||||
}
|
||||
|
||||
|
||||
public function logoffUser() : void
|
||||
{
|
||||
wp_logout();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user