Class ACL Login and Session update

Session:
- can recreate session id periodic (Default never)
- options are set via array like in other classes
- checks for strict session settings on default

ACL Login:
- remove all DEBUG/DB_DEBUG variables, calls, etc
	- removed from the EditBase/EditUsers classes too
- switch to UUIDv4 as the session lookup variable
- all session vars are prefixed with "LOGIN_"
	- the charset ones are left as DEFAULT_CHARSET, DEFAULT_LOCALE, DEFAULT_LANG
	- the old LOGIN_LANG has been removed (deprecated)
	- TEMPLATE session has been removed, there is no template data in the edit class
- session is resynced (ACL lookup), default 5min, adjustable via option
- sets strict header options as default
- moves several methods parts into their own classes
	- plan to split up class into sub classes for certain actions
- new force logout counter in DB
- edit logger is moved into this class
	- plan to move logging into sub class
- all SQL calls user heredoc and params
- update login/change password to new layout for pc/smartphone compatible
	- change password will be replaced with reset password in future
- last login success is now set as timestamp
- all old PK lookups for edit access etc are deprecated and replaced with cuid lookups

ArrayHandling:
- add array return matching key
Give any array with key values and a list of keys and only return matching keys
Wrapper for array_filter call
This commit is contained in:
Clemens Schwaighofer
2024-12-13 10:54:20 +09:00
parent 7e01152bb4
commit a03c7e7319
13 changed files with 1935 additions and 1237 deletions

View File

@@ -21,21 +21,107 @@ class Session
private string $session_id = '';
/** @var bool flag auto write close */
private bool $auto_write_close = false;
/** @var string regenerate option, default never */
private string $regenerate = 'never';
/** @var int regenerate interval either 1 to 100 for random or 0 to 3600 for interval */
private int $regenerate_interval = 0;
/** @var array<string> allowed session id regenerate (rotate) options */
private const ALLOWED_REGENERATE_OPTIONS = ['none', 'random', 'interval'];
/** @var int default random interval */
public const DEFAULT_REGENERATE_RANDOM = 100;
/** @var int default rotate internval in minutes */
public const DEFAULT_REGENERATE_INTERVAL = 5 * 60;
/** @var int maximum time for regenerate interval is one hour */
public const MAX_REGENERATE_INTERAL = 60 * 60;
/**
* init a session, if array is empty or array does not have session_name set
* then no auto init is run
*
* @param string $session_name if set and not empty, will start session
* @param array{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options
*/
public function __construct(string $session_name, bool $auto_write_close = false)
{
public function __construct(
string $session_name,
array $options = []
) {
$this->setOptions($options);
$this->initSession($session_name);
$this->auto_write_close = $auto_write_close;
}
// MARK: private methods
/**
* set session class options
*
* @param array{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options
* @return void
*/
private function setOptions(array $options): void
{
if (
!isset($options['auto_write_close']) ||
!is_bool($options['auto_write_close'])
) {
$options['auto_write_close'] = false;
}
$this->auto_write_close = $options['auto_write_close'];
if (
!isset($options['session_strict']) ||
!is_bool($options['session_strict'])
) {
$options['session_strict'] = true;
}
// set strict options, on not started sessiononly
if (
$options['session_strict'] &&
$this->getSessionStatus() === PHP_SESSION_NONE
) {
// use cookies to store session IDs
ini_set('session.use_cookies', 1);
// use cookies only (do not send session IDs in URLs)
ini_set('session.use_only_cookies', 1);
// do not send session IDs in URLs
ini_set('session.use_trans_sid', 0);
}
// session regenerate id options
if (
empty($options['regenerate']) ||
!in_array($options['regenerate'], self::ALLOWED_REGENERATE_OPTIONS)
) {
$options['regenerate'] = 'never';
}
$this->regenerate = (string)$options['regenerate'];
// for regenerate: 'random' (default 100)
// regenerate_interval must be between (1 = always) and 100 (1 in 100)
// for regenerate: 'interval' (default 5min)
// regenerate_interval must be 0 = always, to 3600 (every hour)
if (
$options['regenerate'] == 'random' &&
(
!isset($options['regenerate_interval']) ||
!is_numeric($options['regenerate_interval']) ||
$options['regenerate_interval'] < 0 ||
$options['regenerate_interval'] > 100
)
) {
$options['regenerate_interval'] = self::DEFAULT_REGENERATE_RANDOM;
}
if (
$options['regenerate'] == 'interval' &&
(
!isset($options['regenerate_interval']) ||
!is_numeric($options['regenerate_interval']) ||
$options['regenerate_interval'] < 1 ||
$options['regenerate_interval'] > self::MAX_REGENERATE_INTERAL
)
) {
$options['regenerate_interval'] = self::DEFAULT_REGENERATE_INTERVAL;
}
$this->regenerate_interval = (int)($options['regenerate_interval'] ?? 0);
}
/**
* Start session
* startSession should be called for complete check
@@ -72,6 +158,72 @@ class Session
return false;
}
// MARK: regenerate session
/**
* auto rotate session id
*
* @return void
* @throws \RuntimeException failure to regenerate session id
* @throws \UnexpectedValueException failed to get new session id
* @throws \RuntimeException failed to set new sesson id
* @throws \UnexpectedValueException new session id generated does not match the new set one
*/
private function sessionRegenerateSessionId()
{
// never
if ($this->regenerate == 'never') {
return;
}
// regenerate
if (
!(
// is not session obsolete
empty($_SESSION['SESSION_REGENERATE_OBSOLETE']) &&
(
(
// random
$this->regenerate == 'random' &&
mt_rand(1, $this->regenerate_interval) == 1
) || (
// interval type
$this->regenerate == 'interval' &&
($_SESSION['SESSION_REGENERATE_TIMESTAMP'] ?? 0) + $this->regenerate_interval < time()
)
)
)
) {
return;
}
// Set current session to expire in 1 minute
$_SESSION['SESSION_REGENERATE_OBSOLETE'] = true;
$_SESSION['SESSION_REGENERATE_EXPIRES'] = time() + 60;
$_SESSION['SESSION_REGENERATE_TIMESTAMP'] = time();
// Create new session without destroying the old one
if (session_regenerate_id(false) === false) {
throw new \RuntimeException('[SESSION] Session id regeneration failed', 1);
}
// Grab current session ID and close both sessions to allow other scripts to use them
if (false === ($new_session_id = $this->getSessionIdCall())) {
throw new \UnexpectedValueException('[SESSION] getSessionIdCall did not return a session id', 2);
}
$this->writeClose();
// Set session ID to the new one, and start it back up again
if (($get_new_session_id = session_id($new_session_id)) === false) {
throw new \RuntimeException('[SESSION] set session_id failed', 3);
}
if ($get_new_session_id != $new_session_id) {
throw new \UnexpectedValueException('[SESSION] new session id does not match the new set one', 4);
}
$this->session_id = $new_session_id;
$this->startSessionCall();
// Don't want this one to expire
unset($_SESSION['SESSION_REGENERATE_OBSOLETE']);
unset($_SESSION['SESSION_REGENERATE_EXPIRES']);
}
// MARK: session validation
/**
* check if session name is valid
*
@@ -151,6 +303,13 @@ class Session
if (!$this->checkActiveSession()) {
throw new \RuntimeException('[SESSION] Failed to activate session', 5);
}
if (
!empty($_SESSION['SESSION_REGENERATE_OBSOLETE']) &&
!empty($_SESSION['SESSION_REGENERATE_EXPIRES']) && $_SESSION['SESSION_REGENERATE_EXPIRES'] < time()
) {
$this->sessionDestroy();
throw new \RuntimeException('[SESSION] Expired session found', 6);
}
} elseif ($session_name != $this->getSessionName()) {
throw new \UnexpectedValueException(
'[SESSION] Another session exists with a different name: ' . $this->getSessionName(),
@@ -159,10 +318,12 @@ class Session
}
// check session id
if (false === ($session_id = $this->getSessionIdCall())) {
throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 6);
throw new \UnexpectedValueException('[SESSION] getSessionIdCall did not return a session id', 7);
}
// set session id
$this->session_id = $session_id;
// run session id re-create from time to time
$this->sessionRegenerateSessionId();
// if flagged auto close, write close session
if ($this->auto_write_close) {
$this->writeClose();