Session and ACL Login Class update
Session: regenerate session id after some time or random. Default is 'never', can be 'interval' form 0 to 1h and random from always to 1 in 100 Session also checks that strict session settings are enabled Login class: Automatic re-read of acl settings after some time (default 5min, can be chnaged via option). Default set strict headers, can be turned off via option Moved various parts into their own methods and cleaned up double call logic. Login is now recorded in the last login entry no more debug flags are read from the database anymore All options are set via array and not with a single option (was auto login)
This commit is contained in:
@@ -37,6 +37,8 @@ CREATE TABLE edit_user (
|
|||||||
protected SMALLINT NOT NULL DEFAULT 0,
|
protected SMALLINT NOT NULL DEFAULT 0,
|
||||||
-- is admin user
|
-- is admin user
|
||||||
admin SMALLINT NOT NULL DEFAULT 0,
|
admin SMALLINT NOT NULL DEFAULT 0,
|
||||||
|
-- force lgout counter
|
||||||
|
force_logout INT DEFAULT 0,
|
||||||
-- last login log
|
-- last login log
|
||||||
last_login TIMESTAMP WITHOUT TIME ZONE,
|
last_login TIMESTAMP WITHOUT TIME ZONE,
|
||||||
-- login error
|
-- login error
|
||||||
@@ -74,6 +76,7 @@ COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be lo
|
|||||||
COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins';
|
COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins';
|
||||||
COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user';
|
COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user';
|
||||||
COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin';
|
COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin';
|
||||||
|
COMMENT ON COLUMN edit_user.force_logout IS 'Counter for forced log out, if this one is higher than the session set one the session gets terminated';
|
||||||
COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp';
|
COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp';
|
||||||
COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login';
|
COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login';
|
||||||
COMMENT ON COLUMN edit_user.login_error_date_last IS 'Last login error date';
|
COMMENT ON COLUMN edit_user.login_error_date_last IS 'Last login error date';
|
||||||
|
|||||||
@@ -1185,7 +1185,6 @@ final class CoreLibsACLLoginTest extends TestCase
|
|||||||
foreach ($session as $session_var => $session_value) {
|
foreach ($session as $session_var => $session_value) {
|
||||||
$_SESSION[$session_var] = $session_value;
|
$_SESSION[$session_var] = $session_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var \CoreLibs\ACL\Login&MockObject */
|
/** @var \CoreLibs\ACL\Login&MockObject */
|
||||||
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
|
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
|
||||||
->setConstructorArgs([
|
->setConstructorArgs([
|
||||||
@@ -1204,7 +1203,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
|||||||
. 'locale' . DIRECTORY_SEPARATOR,
|
. 'locale' . DIRECTORY_SEPARATOR,
|
||||||
]
|
]
|
||||||
])
|
])
|
||||||
->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin'])
|
->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin', 'loginEnhanceHttpSecurity'])
|
||||||
->getMock();
|
->getMock();
|
||||||
$login_mock->expects($this->any())
|
$login_mock->expects($this->any())
|
||||||
->method('loginTerminate')
|
->method('loginTerminate')
|
||||||
@@ -1222,6 +1221,10 @@ final class CoreLibsACLLoginTest extends TestCase
|
|||||||
->method('loginPrintLogin')
|
->method('loginPrintLogin')
|
||||||
->willReturnCallback(function () {
|
->willReturnCallback(function () {
|
||||||
});
|
});
|
||||||
|
$login_mock->expects($this->any())
|
||||||
|
->method('loginEnhanceHttpSecurity')
|
||||||
|
->willReturnCallback(function () {
|
||||||
|
});
|
||||||
|
|
||||||
// if mock_settings: enabled OFF
|
// if mock_settings: enabled OFF
|
||||||
// run DB update and set off
|
// run DB update and set off
|
||||||
|
|||||||
@@ -581,6 +581,8 @@ CREATE TABLE edit_user (
|
|||||||
protected SMALLINT NOT NULL DEFAULT 0,
|
protected SMALLINT NOT NULL DEFAULT 0,
|
||||||
-- is admin user
|
-- is admin user
|
||||||
admin SMALLINT NOT NULL DEFAULT 0,
|
admin SMALLINT NOT NULL DEFAULT 0,
|
||||||
|
-- forced logout counter
|
||||||
|
force_logout INT DEFAULT 0,
|
||||||
-- last login log
|
-- last login log
|
||||||
last_login TIMESTAMP WITHOUT TIME ZONE,
|
last_login TIMESTAMP WITHOUT TIME ZONE,
|
||||||
-- login error
|
-- login error
|
||||||
@@ -697,6 +699,7 @@ CREATE TABLE edit_log (
|
|||||||
action_value VARCHAR, -- in action_data
|
action_value VARCHAR, -- in action_data
|
||||||
action_type VARCHAR, -- in action_data
|
action_type VARCHAR, -- in action_data
|
||||||
action_error VARCHAR -- in action_data
|
action_error VARCHAR -- in action_data
|
||||||
|
) INHERITS (edit_generic) WITHOUT OIDS;
|
||||||
-- END: table/edit_log.sql
|
-- END: table/edit_log.sql
|
||||||
-- START: table/edit_log_overflow.sql
|
-- START: table/edit_log_overflow.sql
|
||||||
-- AUTHOR: Clemens Schwaighofer
|
-- AUTHOR: Clemens Schwaighofer
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ final class CoreLibsCreateSessionTest extends TestCase
|
|||||||
'getSessionId' => '1234abcd4567'
|
'getSessionId' => '1234abcd4567'
|
||||||
],
|
],
|
||||||
'sessionNameGlobals',
|
'sessionNameGlobals',
|
||||||
false,
|
[
|
||||||
|
'auto_write_close' => false,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'auto write close' => [
|
'auto write close' => [
|
||||||
'sessionNameAutoWriteClose',
|
'sessionNameAutoWriteClose',
|
||||||
@@ -66,7 +68,9 @@ final class CoreLibsCreateSessionTest extends TestCase
|
|||||||
'getSessionId' => '1234abcd4567'
|
'getSessionId' => '1234abcd4567'
|
||||||
],
|
],
|
||||||
'sessionNameAutoWriteClose',
|
'sessionNameAutoWriteClose',
|
||||||
true,
|
[
|
||||||
|
'auto_write_close' => true,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -81,13 +85,14 @@ final class CoreLibsCreateSessionTest extends TestCase
|
|||||||
* @param string $input
|
* @param string $input
|
||||||
* @param array<mixed> $mock_data
|
* @param array<mixed> $mock_data
|
||||||
* @param string $expected
|
* @param string $expected
|
||||||
|
* @param array<string,mixed> $options
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function testStartSession(
|
public function testStartSession(
|
||||||
string $input,
|
string $input,
|
||||||
array $mock_data,
|
array $mock_data,
|
||||||
string $expected,
|
string $expected,
|
||||||
?bool $auto_write_close,
|
?array $options,
|
||||||
): void {
|
): void {
|
||||||
/** @var \CoreLibs\Create\Session&MockObject $session_mock */
|
/** @var \CoreLibs\Create\Session&MockObject $session_mock */
|
||||||
$session_mock = $this->createPartialMock(
|
$session_mock = $this->createPartialMock(
|
||||||
@@ -174,9 +179,14 @@ final class CoreLibsCreateSessionTest extends TestCase
|
|||||||
4,
|
4,
|
||||||
'/^\[SESSION\] Failed to activate session/'
|
'/^\[SESSION\] Failed to activate session/'
|
||||||
],
|
],
|
||||||
|
'expired session' => [
|
||||||
|
\RuntimeException::class,
|
||||||
|
5,
|
||||||
|
'/^\[SESSION\] Expired session found/'
|
||||||
|
],
|
||||||
'not a valid session id returned' => [
|
'not a valid session id returned' => [
|
||||||
\UnexpectedValueException::class,
|
\UnexpectedValueException::class,
|
||||||
5,
|
6,
|
||||||
'/^\[SESSION\] getSessionId did not return a session id/'
|
'/^\[SESSION\] getSessionId did not return a session id/'
|
||||||
], */
|
], */
|
||||||
];
|
];
|
||||||
@@ -206,7 +216,8 @@ final class CoreLibsCreateSessionTest extends TestCase
|
|||||||
$this->expectException($exception);
|
$this->expectException($exception);
|
||||||
$this->expectExceptionCode($exception_code);
|
$this->expectExceptionCode($exception_code);
|
||||||
$this->expectExceptionMessageMatches($expected_error);
|
$this->expectExceptionMessageMatches($expected_error);
|
||||||
new \CoreLibs\Create\Session($session_name);
|
// cannot set ini after header sent, plus we are on command line there are no headers
|
||||||
|
new \CoreLibs\Create\Session($session_name, ['session_strict' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ ALTER TABLE edit_log ADD http_data JSONB;
|
|||||||
ALTER TABLE edit_log ADD ip_address JSONB;
|
ALTER TABLE edit_log ADD ip_address JSONB;
|
||||||
ALTER TABLE edit_log ADD action_data JSONB;
|
ALTER TABLE edit_log ADD action_data JSONB;
|
||||||
ALTER TABLE edit_log ADD request_scheme VARCHAR;
|
ALTER TABLE edit_log ADD request_scheme VARCHAR;
|
||||||
|
ALTER TABLE edit_user ADD force_logout INT DEFAULT 0;
|
||||||
|
COMMENT ON COLUMN edit_user.force_logout IS 'Counter for forced log out, if this one is higher than the session set one the session gets terminated';
|
||||||
|
ALTER TABLE edit_user ADD last_login TIMESTAMP WITHOUT TIME ZONE;
|
||||||
|
COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp';
|
||||||
|
|
||||||
-- update set_edit_gneric
|
-- update set_edit_gneric
|
||||||
-- adds the created or updated date tags
|
-- adds the created or updated date tags
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ $SET_SESSION_NAME = EDIT_SESSION_NAME;
|
|||||||
use CoreLibs\Debug\Support;
|
use CoreLibs\Debug\Support;
|
||||||
|
|
||||||
// init login & backend class
|
// init login & backend class
|
||||||
$session = new CoreLibs\Create\Session($SET_SESSION_NAME);
|
$session = new CoreLibs\Create\Session($SET_SESSION_NAME, [
|
||||||
|
'regenerate' => 'interval',
|
||||||
|
'regenerate_interval' => 10, // every 10 seconds
|
||||||
|
]);
|
||||||
$log = new CoreLibs\Logging\Logging([
|
$log = new CoreLibs\Logging\Logging([
|
||||||
'log_folder' => BASE . LOG,
|
'log_folder' => BASE . LOG,
|
||||||
'log_file_id' => $LOG_FILE_ID,
|
'log_file_id' => $LOG_FILE_ID,
|
||||||
@@ -90,6 +93,8 @@ print <<<HTML
|
|||||||
</div>
|
</div>
|
||||||
HTML;
|
HTML;
|
||||||
|
|
||||||
|
echo "SESSION ID: " . $session->getSessionIdCall() . "<br>";
|
||||||
|
|
||||||
echo "CHECK PERMISSION: " . ($login->loginCheckPermissions() ? 'OK' : 'BAD') . "<br>";
|
echo "CHECK PERMISSION: " . ($login->loginCheckPermissions() ? 'OK' : 'BAD') . "<br>";
|
||||||
echo "IS ADMIN: " . ($login->loginIsAdmin() ? 'OK' : 'BAD') . "<br>";
|
echo "IS ADMIN: " . ($login->loginIsAdmin() ? 'OK' : 'BAD') . "<br>";
|
||||||
echo "MIN ACCESS BASE: " . ($login->loginCheckAccessBase('admin') ? 'OK' : 'BAD') . "<br>";
|
echo "MIN ACCESS BASE: " . ($login->loginCheckAccessBase('admin') ? 'OK' : 'BAD') . "<br>";
|
||||||
@@ -118,8 +123,7 @@ if (isset($login->loginGetAcl()['unit'])) {
|
|||||||
print "Something went wrong with the login<br>";
|
print "Something went wrong with the login<br>";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "<hr>";
|
// echo "<hr>";
|
||||||
|
|
||||||
// IP check: 'REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP' in _SERVER
|
// IP check: 'REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP' in _SERVER
|
||||||
// Agent check: 'HTTP_USER_AGENT'
|
// Agent check: 'HTTP_USER_AGENT'
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ $_SESSION['this_will_be_written'] = 'not empty';
|
|||||||
// open again with same name
|
// open again with same name
|
||||||
$session_name = 'class-test-session';
|
$session_name = 'class-test-session';
|
||||||
try {
|
try {
|
||||||
$session_alt = new Session($session_name, auto_write_close:true);
|
$session_alt = new Session($session_name, ['auto_write_close' => true]);
|
||||||
print "[4 SET] Current session id: " . $session_alt->getSessionId() . "<br>";
|
print "[4 SET] Current session id: " . $session_alt->getSessionId() . "<br>";
|
||||||
print "[4 SET] Current session auto write close: " . ($session_alt->checkAutoWriteClose() ? 'Yes' : 'No') . "<br>";
|
print "[4 SET] Current session auto write close: " . ($session_alt->checkAutoWriteClose() ? 'Yes' : 'No') . "<br>";
|
||||||
print "[START AGAIN] Current session id: " . $session_alt->getSessionId() . "<br>";
|
print "[START AGAIN] Current session id: " . $session_alt->getSessionId() . "<br>";
|
||||||
|
|||||||
@@ -217,6 +217,36 @@ class Login
|
|||||||
'path' => '',
|
'path' => '',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// lock status bitmap (smallint, 256)
|
||||||
|
/** @var int enabled flag */
|
||||||
|
public const ENABLED = 1;
|
||||||
|
/** @var int deleted flag */
|
||||||
|
public const DELETED = 2;
|
||||||
|
/** @var int locked flag */
|
||||||
|
public const LOCKED = 4;
|
||||||
|
/** @var int banned/suspened flag [not implemented] */
|
||||||
|
public const BANNED = 8;
|
||||||
|
/** @var int password reset in progress [not implemented] */
|
||||||
|
public const RESET = 16;
|
||||||
|
/** @var int confirm/paending, eg waiting for confirm of email [not implemented] */
|
||||||
|
public const CONFIRM = 32;
|
||||||
|
/** @var int strict, on error lock */
|
||||||
|
public const STRICT = 64;
|
||||||
|
/** @var int proected, cannot delete */
|
||||||
|
public const PROTECTED = 128;
|
||||||
|
/** @var int master admin flag */
|
||||||
|
public const ADMIN = 256;
|
||||||
|
|
||||||
|
/** @var int resync interval time in minutes */
|
||||||
|
private const DEFAULT_AUTH_RESYNC_INTERVAL = 5 * 60;
|
||||||
|
/** @var int the session max garbage collection life time */
|
||||||
|
// private const DEFAULT_SESSION_GC_MAXLIFETIME = ;
|
||||||
|
private int $default_session_gc_maxlifetime;
|
||||||
|
/** @var int in how many minutes an auth resync is done */
|
||||||
|
private int $auth_resync_interval;
|
||||||
|
/** @var bool set the enhanced header security */
|
||||||
|
private bool $header_enhance_security = false;
|
||||||
|
|
||||||
/** @var \CoreLibs\Logging\Logging logger */
|
/** @var \CoreLibs\Logging\Logging logger */
|
||||||
public \CoreLibs\Logging\Logging $log;
|
public \CoreLibs\Logging\Logging $log;
|
||||||
/** @var \CoreLibs\DB\IO database */
|
/** @var \CoreLibs\DB\IO database */
|
||||||
@@ -248,156 +278,19 @@ class Login
|
|||||||
// attach session class
|
// attach session class
|
||||||
$this->session = $session;
|
$this->session = $session;
|
||||||
|
|
||||||
|
$this->default_session_gc_maxlifetime = (int)ini_get("session.gc_maxlifetime");
|
||||||
|
|
||||||
// set and check options
|
// set and check options
|
||||||
if (false === $this->loginSetOptions($options)) {
|
if (false === $this->loginSetOptions($options)) {
|
||||||
// on failure, exit
|
// on failure, exit
|
||||||
echo "<b>Could not set options</b>";
|
echo "<b>Could not set options</b>";
|
||||||
$this->loginTerminate('Could not set options', 3000);
|
$this->loginTerminate('Could not set options', 3000);
|
||||||
}
|
}
|
||||||
|
// init error array
|
||||||
// string key, msg: string, flag: e (error), o (ok)
|
$this->loginInitErrorMessages();
|
||||||
$this->login_error_msg = [
|
// acess right list
|
||||||
'0' => [
|
$this->loginLoadAccessRightList();
|
||||||
'msg' => 'No error',
|
// log allowed write flags
|
||||||
'flag' => 'o'
|
|
||||||
],
|
|
||||||
// actually obsolete
|
|
||||||
'100' => [
|
|
||||||
'msg' => '[EUCUUID] set from GET/POST!',
|
|
||||||
'flag' => 'e',
|
|
||||||
],
|
|
||||||
// query errors
|
|
||||||
'1009' => [
|
|
||||||
'msg' => 'Login query reading failed',
|
|
||||||
'flag' => 'e',
|
|
||||||
],
|
|
||||||
// user not found
|
|
||||||
'1010' => [
|
|
||||||
'msg' => 'Login Failed - Wrong Username or Password',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
// blowfish password wrong
|
|
||||||
/* '1011' => [
|
|
||||||
'msg' => 'Login Failed - Wrong Username or Password',
|
|
||||||
'flag' => 'e'
|
|
||||||
], */
|
|
||||||
// fallback md5 password wrong
|
|
||||||
'1012' => [
|
|
||||||
'msg' => 'Login Failed - Wrong Username or Password',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
// new password_hash wrong
|
|
||||||
'1013' => [
|
|
||||||
'msg' => 'Login Failed - Wrong Username or Password',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'1101' => [
|
|
||||||
'msg' => 'Login Failed - Login User ID must be validated',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'1102' => [
|
|
||||||
'msg' => 'Login Failed - Login User ID is outside valid date range',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'102' => [
|
|
||||||
'msg' => 'Login Failed - Please enter username and password',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'103' => [
|
|
||||||
'msg' => 'You do not have the rights to access this Page',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'104' => [
|
|
||||||
'msg' => 'Login Failed - User not enabled',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'105' => [
|
|
||||||
'msg' => 'Login Failed - User is locked',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'106' => [
|
|
||||||
'msg' => 'Login Failed - User is deleted',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'107' => [
|
|
||||||
'msg' => 'Login Failed - User in locked via date period',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'108' => [
|
|
||||||
'msg' => 'Login Failed - User is locked via Login User ID',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'109' => [
|
|
||||||
'msg' => 'Check permission query reading failed',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
// actually this is an illegal user, but I mask it
|
|
||||||
'220' => [
|
|
||||||
'msg' => 'Password change - The user could not be found',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'200' => [
|
|
||||||
'msg' => 'Password change - Please enter username and old password',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'201' => [
|
|
||||||
'msg' => 'Password change - The user could not be found',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'202' => [
|
|
||||||
'msg' => 'Password change - The old password is not correct',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'203' => [
|
|
||||||
'msg' => 'Password change - Please fill out both new password fields',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
'204' => [
|
|
||||||
'msg' => 'Password change - The new passwords do not match',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
// we should also not here WHAT is valid
|
|
||||||
'205' => [
|
|
||||||
'msg' => 'Password change - The new password is not in a valid format',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
// for OK password change
|
|
||||||
'300' => [
|
|
||||||
'msg' => 'Password change successful',
|
|
||||||
'flag' => 'o'
|
|
||||||
],
|
|
||||||
// this is bad bad error
|
|
||||||
'9999' => [
|
|
||||||
'msg' => 'Necessary crypt engine could not be found. Login is impossible',
|
|
||||||
'flag' => 'e'
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
// read the current edit_access_right list into an array
|
|
||||||
$q = <<<SQL
|
|
||||||
SELECT
|
|
||||||
level, type, name
|
|
||||||
FROM
|
|
||||||
edit_access_right
|
|
||||||
WHERE
|
|
||||||
level >= 0
|
|
||||||
ORDER BY
|
|
||||||
level
|
|
||||||
SQL;
|
|
||||||
while (is_array($res = $this->db->dbReturn($q))) {
|
|
||||||
// level to description format (numeric)
|
|
||||||
$this->default_acl_list[$res['level']] = [
|
|
||||||
'type' => $res['type'],
|
|
||||||
'name' => $res['name']
|
|
||||||
];
|
|
||||||
$this->default_acl_list_type[(string)$res['type']] = (int)$res['level'];
|
|
||||||
}
|
|
||||||
// write that into the session
|
|
||||||
$this->session->setMany([
|
|
||||||
'LOGIN_DEFAULT_ACL_LIST' => $this->default_acl_list,
|
|
||||||
'LOGIN_DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->loginSetEditLogWriteTypeAvailable();
|
$this->loginSetEditLogWriteTypeAvailable();
|
||||||
|
|
||||||
// this will be deprecated
|
// this will be deprecated
|
||||||
@@ -425,6 +318,7 @@ class Login
|
|||||||
} else {
|
} else {
|
||||||
$this->log->critical($message, ['code' => $code]);
|
$this->log->critical($message, ['code' => $code]);
|
||||||
}
|
}
|
||||||
|
// TODO throw error and not exit
|
||||||
exit($code);
|
exit($code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,6 +471,20 @@ class Login
|
|||||||
}
|
}
|
||||||
$this->password_forgot = $options['forgot_flow'];
|
$this->password_forgot = $options['forgot_flow'];
|
||||||
|
|
||||||
|
// sync _SESSION acl settings
|
||||||
|
if (
|
||||||
|
!isset($options['auth_resync_interval']) ||
|
||||||
|
!is_numeric($options['auth_resync_interval']) ||
|
||||||
|
$options['auth_resync_interval'] < 0 ||
|
||||||
|
$options['auth_resync_interval'] > $this->default_session_gc_maxlifetime
|
||||||
|
) {
|
||||||
|
// default 5 minutues
|
||||||
|
$options['auth_resync_interval'] = self::DEFAULT_AUTH_RESYNC_INTERVAL;
|
||||||
|
} else {
|
||||||
|
$options['auth_resync_interval'] = (int)$options['auth_resync_interval'];
|
||||||
|
}
|
||||||
|
$this->auth_resync_interval = $options['auth_resync_interval'];
|
||||||
|
|
||||||
// *** LANGUAGE
|
// *** LANGUAGE
|
||||||
// LANG: LOCALE PATH
|
// LANG: LOCALE PATH
|
||||||
if (empty($options['locale_path'])) {
|
if (empty($options['locale_path'])) {
|
||||||
@@ -631,12 +539,210 @@ class Login
|
|||||||
$options['site_encoding'] = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ?
|
$options['site_encoding'] = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ?
|
||||||
SITE_ENCODING : 'UTF-8';
|
SITE_ENCODING : 'UTF-8';
|
||||||
}
|
}
|
||||||
|
// set enhancded security flag
|
||||||
|
if (
|
||||||
|
empty($options['enhanced_security']) ||
|
||||||
|
!is_bool($options['enhanced_security'])
|
||||||
|
) {
|
||||||
|
$options['enhanced_security'] = true;
|
||||||
|
}
|
||||||
|
$this->header_enhance_security = $options['enhanced_security'];
|
||||||
|
|
||||||
// write array to options
|
// write array to options
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the login error message array
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loginInitErrorMessages()
|
||||||
|
{
|
||||||
|
// string key, msg: string, flag: e (error), o (ok)
|
||||||
|
$this->login_error_msg = [
|
||||||
|
'0' => [
|
||||||
|
'msg' => 'No error',
|
||||||
|
'flag' => 'o'
|
||||||
|
],
|
||||||
|
// actually obsolete
|
||||||
|
'100' => [
|
||||||
|
'msg' => '[EUCUUID] set from GET/POST!',
|
||||||
|
'flag' => 'e',
|
||||||
|
],
|
||||||
|
// query errors
|
||||||
|
'1009' => [
|
||||||
|
'msg' => 'Login query reading failed',
|
||||||
|
'flag' => 'e',
|
||||||
|
],
|
||||||
|
// user not found
|
||||||
|
'1010' => [
|
||||||
|
'msg' => 'Login Failed - Wrong Username or Password',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
// general login error
|
||||||
|
'1011' => [
|
||||||
|
'msg' => 'Login Failed - General authentication error',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
// fallback md5 password wrong
|
||||||
|
'1012' => [
|
||||||
|
'msg' => 'Login Failed - Wrong Username or Password',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
// new password_hash wrong
|
||||||
|
'1013' => [
|
||||||
|
'msg' => 'Login Failed - Wrong Username or Password',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'1101' => [
|
||||||
|
'msg' => 'Login Failed - Login User ID must be validated',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'1102' => [
|
||||||
|
'msg' => 'Login Failed - Login User ID is outside valid date range',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'102' => [
|
||||||
|
'msg' => 'Login Failed - Please enter username and password',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'103' => [
|
||||||
|
'msg' => 'You do not have the rights to access this Page',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'104' => [
|
||||||
|
'msg' => 'Login Failed - User not enabled',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'105' => [
|
||||||
|
'msg' => 'Login Failed - User is locked',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'106' => [
|
||||||
|
'msg' => 'Login Failed - User is deleted',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'107' => [
|
||||||
|
'msg' => 'Login Failed - User in locked via date period',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'108' => [
|
||||||
|
'msg' => 'Login Failed - User is locked via Login User ID',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'109' => [
|
||||||
|
'msg' => 'Check permission query reading failed',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'110' => [
|
||||||
|
'msg' => 'Forced logout',
|
||||||
|
'flag' => '',
|
||||||
|
],
|
||||||
|
// actually this is an illegal user, but I mask it
|
||||||
|
'220' => [
|
||||||
|
'msg' => 'Password change - The user could not be found',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'200' => [
|
||||||
|
'msg' => 'Password change - Please enter username and old password',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'201' => [
|
||||||
|
'msg' => 'Password change - The user could not be found',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'202' => [
|
||||||
|
'msg' => 'Password change - The old password is not correct',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'203' => [
|
||||||
|
'msg' => 'Password change - Please fill out both new password fields',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
'204' => [
|
||||||
|
'msg' => 'Password change - The new passwords do not match',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
// we should also not here WHAT is valid
|
||||||
|
'205' => [
|
||||||
|
'msg' => 'Password change - The new password is not in a valid format',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
// for OK password change
|
||||||
|
'300' => [
|
||||||
|
'msg' => 'Password change successful',
|
||||||
|
'flag' => 'o'
|
||||||
|
],
|
||||||
|
// this is bad bad error
|
||||||
|
'9999' => [
|
||||||
|
'msg' => 'Necessary crypt engine could not be found. Login is impossible',
|
||||||
|
'flag' => 'e'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loads the access right list from the database
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loginLoadAccessRightList(): void
|
||||||
|
{
|
||||||
|
// read the current edit_access_right list into an array
|
||||||
|
$q = <<<SQL
|
||||||
|
SELECT
|
||||||
|
level, type, name
|
||||||
|
FROM
|
||||||
|
edit_access_right
|
||||||
|
WHERE
|
||||||
|
level >= 0
|
||||||
|
ORDER BY
|
||||||
|
level
|
||||||
|
SQL;
|
||||||
|
while (is_array($res = $this->db->dbReturn($q))) {
|
||||||
|
// level to description format (numeric)
|
||||||
|
$this->default_acl_list[$res['level']] = [
|
||||||
|
'type' => $res['type'],
|
||||||
|
'name' => $res['name']
|
||||||
|
];
|
||||||
|
$this->default_acl_list_type[(string)$res['type']] = (int)$res['level'];
|
||||||
|
}
|
||||||
|
// write that into the session
|
||||||
|
$this->session->setMany([
|
||||||
|
'LOGIN_DEFAULT_ACL_LIST' => $this->default_acl_list,
|
||||||
|
'LOGIN_DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Improves the application's security over HTTP(S) by setting specific headers
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function loginEnhanceHttpSecurity(): void
|
||||||
|
{
|
||||||
|
// skip if not wanted
|
||||||
|
if (!$this->header_enhance_security) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// remove exposure of PHP version (at least where possible)
|
||||||
|
header_remove('X-Powered-By');
|
||||||
|
// if the user is signed in
|
||||||
|
if ($this->permission_okay) {
|
||||||
|
// prevent clickjacking
|
||||||
|
header('X-Frame-Options: sameorigin');
|
||||||
|
// prevent content sniffing (MIME sniffing)
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
|
||||||
|
// disable caching of potentially sensitive data
|
||||||
|
header('Cache-Control: no-store, no-cache, must-revalidate', true);
|
||||||
|
header('Expires: Thu, 19 Nov 1981 00:00:00 GMT', true);
|
||||||
|
header('Pragma: no-cache', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: validation checks
|
// MARK: validation checks
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -649,6 +755,7 @@ class Login
|
|||||||
* @param int $locked Locked because of too many invalid passwords
|
* @param int $locked Locked because of too many invalid passwords
|
||||||
* @param int $locked_period Locked because of time period set
|
* @param int $locked_period Locked because of time period set
|
||||||
* @param int $login_user_id_locked Locked from using Login User Id
|
* @param int $login_user_id_locked Locked from using Login User Id
|
||||||
|
* @param int $force_logout Force logout counter, if higher than session, permission is false
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function loginValidationCheck(
|
private function loginValidationCheck(
|
||||||
@@ -656,7 +763,8 @@ class Login
|
|||||||
int $enabled,
|
int $enabled,
|
||||||
int $locked,
|
int $locked,
|
||||||
int $locked_period,
|
int $locked_period,
|
||||||
int $login_user_id_locked
|
int $login_user_id_locked,
|
||||||
|
int $force_logout
|
||||||
): bool {
|
): bool {
|
||||||
$validation = false;
|
$validation = false;
|
||||||
if ($deleted) {
|
if ($deleted) {
|
||||||
@@ -674,6 +782,8 @@ class Login
|
|||||||
} elseif ($login_user_id_locked) {
|
} elseif ($login_user_id_locked) {
|
||||||
// user is locked, either set or auto set
|
// user is locked, either set or auto set
|
||||||
$this->login_error = 108;
|
$this->login_error = 108;
|
||||||
|
} elseif ($force_logout > $this->session->get('LOGIN_FORCE_LOGOUT')) {
|
||||||
|
$this->login_error = 110;
|
||||||
} else {
|
} else {
|
||||||
$validation = true;
|
$validation = true;
|
||||||
}
|
}
|
||||||
@@ -757,7 +867,112 @@ class Login
|
|||||||
return $login_id_ok;
|
return $login_id_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: login user action
|
/**
|
||||||
|
* write error data for login errors
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $res
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loginWriteLoginError(array $res)
|
||||||
|
{
|
||||||
|
if (!$this->login_error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$login_error_date_first = '';
|
||||||
|
if ($res['login_error_count'] == 0) {
|
||||||
|
$login_error_date_first = ", login_error_date_first = NOW()";
|
||||||
|
}
|
||||||
|
// update login error count for this user
|
||||||
|
$q = <<<SQL
|
||||||
|
UPDATE edit_user
|
||||||
|
SET
|
||||||
|
login_error_count = login_error_count + 1,
|
||||||
|
login_error_date_last = NOW()
|
||||||
|
{LOGIN_ERROR_SQL}
|
||||||
|
WHERE edit_user_id = $1
|
||||||
|
SQL;
|
||||||
|
$this->db->dbExecParams(
|
||||||
|
str_replace('{LOGIN_ERROR_SQL}', $login_error_date_first, $q),
|
||||||
|
[$res['edit_user_id']]
|
||||||
|
);
|
||||||
|
// totally lock the user if error max is reached
|
||||||
|
if (
|
||||||
|
$this->max_login_error_count != -1 &&
|
||||||
|
$res['login_error_count'] + 1 > $this->max_login_error_count
|
||||||
|
) {
|
||||||
|
// do some alert reporting in case this error is too big
|
||||||
|
// if strict is set, lock this user
|
||||||
|
// this needs manual unlocking by an admin user
|
||||||
|
if ($res['strict'] && !in_array($this->username, $this->lock_deny_users)) {
|
||||||
|
$q = <<<SQL
|
||||||
|
UPDATE edit_user
|
||||||
|
SET locked = 1
|
||||||
|
WHERE edit_user_id = $1
|
||||||
|
SQL;
|
||||||
|
// [$res['edit_user_id']]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the core edit_user table id/cuid/cuuid
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $res
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loginSetEditUserUidData(array $res)
|
||||||
|
{
|
||||||
|
// normal user processing
|
||||||
|
// set class var and session var
|
||||||
|
$this->edit_user_id = (int)$res['edit_user_id'];
|
||||||
|
$this->edit_user_cuid = (string)$res['cuid'];
|
||||||
|
$this->edit_user_cuuid = (string)$res['cuuid'];
|
||||||
|
$this->session->setMany([
|
||||||
|
'LOGIN_EUID' => $this->edit_user_id,
|
||||||
|
'LOGIN_EUCUID' => $this->edit_user_cuid,
|
||||||
|
'LOGIN_EUCUUID' => $this->edit_user_cuuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check for re-loading of ACL data after a period of time
|
||||||
|
* or if any of the core session vars is not set
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loginAuthResync()
|
||||||
|
{
|
||||||
|
if (!$this->session->get('LOGIN_LAST_AUTH_RESYNC')) {
|
||||||
|
$this->session->set('LOGIN_LAST_AUTH_RESYNC', 0);
|
||||||
|
}
|
||||||
|
// reauth on missing session vars and timed out re-sync interval
|
||||||
|
$mandatory_session_vars = [
|
||||||
|
'LOGIN_USER_NAME', 'LOGIN_GROUP_NAME', 'LOGIN_EUCUID', 'LOGIN_EUCUUID',
|
||||||
|
'LOGIN_USER_ADDITIONAL_ACL', 'LOGIN_GROUP_ADDITIONAL_ACL',
|
||||||
|
'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL', 'LOGIN_PAGES_ACL_LEVEL', 'LOGIN_USER_ACL_LEVEL',
|
||||||
|
'LOGIN_UNIT', 'LOGIN_UNIT_DEFAULT_EACUID'
|
||||||
|
];
|
||||||
|
$force_reauth = false;
|
||||||
|
foreach ($mandatory_session_vars as $_session_var) {
|
||||||
|
if (!isset($_SESSION[$_session_var])) {
|
||||||
|
$force_reauth = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
$this->session->get('LOGIN_LAST_AUTH_RESYNC') + $this->auth_resync_interval <= time() &&
|
||||||
|
$force_reauth == false
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (($res = $this->loginLoadUserData($this->edit_user_cuuid)) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// set the session vars
|
||||||
|
$this->loginSetSession($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: MAIN LOGIN ACTION
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if user pressed login button this script is called,
|
* if user pressed login button this script is called,
|
||||||
@@ -769,6 +984,10 @@ class Login
|
|||||||
{
|
{
|
||||||
// if pressed login at least and is not yet loggined in
|
// if pressed login at least and is not yet loggined in
|
||||||
if ($this->edit_user_cuuid || (!$this->login && !$this->login_user_id)) {
|
if ($this->edit_user_cuuid || (!$this->login && !$this->login_user_id)) {
|
||||||
|
// run reload user data based on re-auth timeout, but only if we got a set cuuid
|
||||||
|
if ($this->edit_user_cuuid) {
|
||||||
|
$this->loginAuthResync();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if not username AND password where given
|
// if not username AND password where given
|
||||||
@@ -778,16 +997,105 @@ class Login
|
|||||||
$this->permission_okay = false;
|
$this->permission_okay = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// have to get the global stuff here for setting it later
|
// load user data, abort on error
|
||||||
// we have to get the themes in here too
|
if (($res = $this->loginLoadUserData()) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if login errors is half of max errors and the last login error
|
||||||
|
// was less than 10s ago, forbid any new login try
|
||||||
|
|
||||||
|
// check flow
|
||||||
|
// - user is enabled
|
||||||
|
// - user is not locked
|
||||||
|
// - password is readable
|
||||||
|
// - encrypted password matches
|
||||||
|
// - plain password matches
|
||||||
|
if (
|
||||||
|
!$this->loginValidationCheck(
|
||||||
|
(int)$res['deleted'],
|
||||||
|
(int)$res['enabled'],
|
||||||
|
(int)$res['locked'],
|
||||||
|
(int)$res['locked_period'],
|
||||||
|
(int)$res['login_user_id_locked'],
|
||||||
|
(int)$res['force_logout']
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// error set in method (104, 105, 106, 107, 108)
|
||||||
|
} elseif (
|
||||||
|
empty($this->username) &&
|
||||||
|
!empty($this->login_user_id) &&
|
||||||
|
!$this->loginLoginUserIdCheck(
|
||||||
|
(int)$res['login_user_id_valid_date'],
|
||||||
|
(int)$res['login_user_id_revalidate']
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// check done in loginLoginIdCheck method
|
||||||
|
// aborts on must revalidate and not valid (date range)
|
||||||
|
} elseif (
|
||||||
|
!empty($this->username) &&
|
||||||
|
!$this->loginPasswordCheck($res['password'])
|
||||||
|
) {
|
||||||
|
// none to be set, set in login password check
|
||||||
|
// this is not valid password input error here
|
||||||
|
// all error codes are set in loginPasswordCheck method
|
||||||
|
// also valid if login_user_id is ok
|
||||||
|
} else {
|
||||||
|
// check if the current password is an invalid hash and do a rehash and set password
|
||||||
|
// $this->debug('LOGIN', 'Hash: '.$res['password'].' -> VERIFY: '
|
||||||
|
// .($Password::passwordVerify($this->password, $res['password']) ? 'OK' : 'FAIL')
|
||||||
|
// .' => HASH: '.(Password::passwordRehashCheck($res['password']) ? 'NEW NEEDED' : 'OK'));
|
||||||
|
if (Password::passwordRehashCheck($res['password'])) {
|
||||||
|
// update password hash to new one now
|
||||||
|
$q = <<<SQL
|
||||||
|
UPDATE edit_user
|
||||||
|
SET password = $1
|
||||||
|
WHERE edit_user_id = $2
|
||||||
|
SQL;
|
||||||
|
$this->db->dbExecParams($q, [
|
||||||
|
Password::passwordSet($this->password),
|
||||||
|
$res['edit_user_id']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// normal user processing
|
||||||
|
// set class var and session var
|
||||||
|
$this->loginSetEditUserUidData($res);
|
||||||
|
// set the last login time stamp for normal login only (not for reauthenticate)
|
||||||
|
$this->db->dbExecParams(<<<SQL
|
||||||
|
UPDATE edit_user SET
|
||||||
|
last_login = NOW()
|
||||||
|
WHERE
|
||||||
|
edit_user_id = $1
|
||||||
|
SQL, [$this->edit_user_id]);
|
||||||
|
// set the session vars
|
||||||
|
$this->loginSetSession($res);
|
||||||
|
} // user was not enabled or other login error
|
||||||
|
// check for login error and write to the user
|
||||||
|
$this->loginWriteLoginError($res);
|
||||||
|
// if there was an login error, show login screen
|
||||||
|
if ($this->login_error) {
|
||||||
|
// reset the perm var, to confirm logout
|
||||||
|
$this->permission_okay = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load user data and all connect4ed settings
|
||||||
|
*
|
||||||
|
* @param ?string $edit_user_cuuid for re-auth
|
||||||
|
* @return array<string,mixed>|false
|
||||||
|
*/
|
||||||
|
private function loginLoadUserData(?string $edit_user_cuuid = null): array|false
|
||||||
|
{
|
||||||
$q = <<<SQL
|
$q = <<<SQL
|
||||||
SELECT
|
SELECT
|
||||||
eu.edit_user_id, eu.cuid, eu.cuuid, eu.username, eu.password,
|
eu.edit_user_id, eu.cuid, eu.cuuid, eu.username, eu.password, eu.email,
|
||||||
eu.edit_group_id,
|
eu.edit_group_id,
|
||||||
eg.name AS edit_group_name, eu.admin,
|
eg.name AS edit_group_name, eu.admin,
|
||||||
-- additinal acl lists
|
-- additinal acl lists
|
||||||
eu.additional_acl AS user_additional_acl,
|
eu.additional_acl AS user_additional_acl,
|
||||||
eg.additional_acl AS group_additional_acl,
|
eg.additional_acl AS group_additional_acl,
|
||||||
|
-- force logoutp counter
|
||||||
|
eu.force_logout,
|
||||||
-- login error + locked
|
-- login error + locked
|
||||||
eu.login_error_count, eu.login_error_date_last,
|
eu.login_error_count, eu.login_error_date_last,
|
||||||
eu.login_error_date_first, eu.strict, eu.locked,
|
eu.login_error_date_first, eu.strict, eu.locked,
|
||||||
@@ -802,8 +1110,6 @@ class Login
|
|||||||
OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after)
|
OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after)
|
||||||
)
|
)
|
||||||
) THEN 0::INT ELSE 1::INT END locked_period,
|
) THEN 0::INT ELSE 1::INT END locked_period,
|
||||||
-- debug (legacy)
|
|
||||||
eu.debug, eu.db_debug,
|
|
||||||
-- enabled
|
-- enabled
|
||||||
eu.enabled, eu.deleted,
|
eu.enabled, eu.deleted,
|
||||||
-- for checks only
|
-- for checks only
|
||||||
@@ -851,8 +1157,12 @@ class Login
|
|||||||
SQL;
|
SQL;
|
||||||
$params = [];
|
$params = [];
|
||||||
$replace_string = '';
|
$replace_string = '';
|
||||||
// either login_user_id OR password must be given
|
// if login is OK and we have edit_user_cuuid as parameter, then this is internal re-auth
|
||||||
if (!empty($this->login_user_id && empty($this->username))) {
|
// else login_user_id OR password must be given
|
||||||
|
if (!empty($edit_user_cuuid)) {
|
||||||
|
$replace_string = 'eu.cuuid = $1';
|
||||||
|
$params = [$this->edit_user_cuuid];
|
||||||
|
} elseif (!empty($this->login_user_id) && empty($this->username)) {
|
||||||
// check with login id if set and NO username
|
// check with login id if set and NO username
|
||||||
$replace_string = 'eu.login_user_id = $1';
|
$replace_string = 'eu.login_user_id = $1';
|
||||||
$params = [$this->login_user_id];
|
$params = [$this->login_user_id];
|
||||||
@@ -874,83 +1184,39 @@ class Login
|
|||||||
if (!empty($this->db->dbGetLastError())) {
|
if (!empty($this->db->dbGetLastError())) {
|
||||||
$this->login_error = 1009;
|
$this->login_error = 1009;
|
||||||
$this->permission_okay = false;
|
$this->permission_okay = false;
|
||||||
return;
|
return false;
|
||||||
} elseif (!is_array($res)) {
|
} elseif (!is_array($res)) {
|
||||||
// username is wrong, but we throw for wrong username
|
// username is wrong, but we throw for wrong username
|
||||||
// and wrong password the same error
|
// and wrong password the same error
|
||||||
|
// unless with have edit user cuuid set then we run an general ACL error
|
||||||
|
if (empty($edit_user_cuuid)) {
|
||||||
$this->login_error = 1010;
|
$this->login_error = 1010;
|
||||||
|
} else {
|
||||||
|
$this->login_error = 1011;
|
||||||
|
}
|
||||||
$this->permission_okay = false;
|
$this->permission_okay = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: login set all session variables
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set all the _SESSION variables
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $res user data loaded query result
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function loginSetSession(array $res): void
|
||||||
|
{
|
||||||
|
// user has permission to THIS page
|
||||||
|
if ($this->login_error != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if login errors is half of max errors and the last login error
|
|
||||||
// was less than 10s ago, forbid any new login try
|
|
||||||
|
|
||||||
// check flow
|
|
||||||
// - user is enabled
|
|
||||||
// - user is not locked
|
|
||||||
// - password is readable
|
|
||||||
// - encrypted password matches
|
|
||||||
// - plain password matches
|
|
||||||
if (
|
|
||||||
!$this->loginValidationCheck(
|
|
||||||
(int)$res['deleted'],
|
|
||||||
(int)$res['enabled'],
|
|
||||||
(int)$res['locked'],
|
|
||||||
(int)$res['locked_period'],
|
|
||||||
(int)$res['login_user_id_locked']
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// error set in method (104, 105, 106, 107, 108)
|
|
||||||
} elseif (
|
|
||||||
empty($this->username) &&
|
|
||||||
!empty($this->login_user_id) &&
|
|
||||||
!$this->loginLoginUserIdCheck(
|
|
||||||
(int)$res['login_user_id_valid_date'],
|
|
||||||
(int)$res['login_user_id_revalidate']
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// check done in loginLoginIdCheck method
|
|
||||||
// aborts on must revalidate and not valid (date range)
|
|
||||||
} elseif (
|
|
||||||
!empty($this->username) &&
|
|
||||||
!$this->loginPasswordCheck($res['password'])
|
|
||||||
) {
|
|
||||||
// none to be set, set in login password check
|
|
||||||
// this is not valid password input error here
|
|
||||||
// all error codes are set in loginPasswordCheck method
|
|
||||||
// also valid if login_user_id is ok
|
|
||||||
} else {
|
|
||||||
// check if the current password is an invalid hash and do a rehash and set password
|
|
||||||
// $this->debug('LOGIN', 'Hash: '.$res['password'].' -> VERIFY: '
|
|
||||||
// .($Password::passwordVerify($this->password, $res['password']) ? 'OK' : 'FAIL')
|
|
||||||
// .' => HASH: '.(Password::passwordRehashCheck($res['password']) ? 'NEW NEEDED' : 'OK'));
|
|
||||||
if (Password::passwordRehashCheck($res['password'])) {
|
|
||||||
// update password hash to new one now
|
|
||||||
$q = <<<SQL
|
|
||||||
UPDATE edit_user
|
|
||||||
SET password = $1
|
|
||||||
WHERE edit_user_id = $2
|
|
||||||
SQL;
|
|
||||||
$this->db->dbExecParams($q, [
|
|
||||||
Password::passwordSet($this->password),
|
|
||||||
$res['edit_user_id']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// normal user processing
|
|
||||||
// set class var and session var
|
|
||||||
$this->edit_user_id = (int)$res['edit_user_id'];
|
|
||||||
$this->edit_user_cuid = (string)$res['cuid'];
|
|
||||||
$this->edit_user_cuuid = (string)$res['cuuid'];
|
|
||||||
$this->session->setMany([
|
|
||||||
'LOGIN_EUID' => $this->edit_user_id, // DEPRECATED
|
|
||||||
'LOGIN_EUCUID' => $this->edit_user_cuid,
|
|
||||||
'LOGIN_EUCUUID' => $this->edit_user_cuuid,
|
|
||||||
]);
|
|
||||||
// check if user is okay
|
|
||||||
$this->loginCheckPermissions();
|
|
||||||
if ($this->login_error == 0) {
|
|
||||||
// set the dit group id
|
// set the dit group id
|
||||||
$edit_group_id = $res["edit_group_id"];
|
$edit_group_id = $res["edit_group_id"];
|
||||||
|
$edit_user_id = (int)$res['edit_user_id'];
|
||||||
// update last revalidate flag
|
// update last revalidate flag
|
||||||
if (
|
if (
|
||||||
!empty($res['login_user_id']) &&
|
!empty($res['login_user_id']) &&
|
||||||
@@ -961,7 +1227,7 @@ class Login
|
|||||||
SET login_user_id_last_revalidate = NOW()
|
SET login_user_id_last_revalidate = NOW()
|
||||||
WHERE edit_user_id = $1
|
WHERE edit_user_id = $1
|
||||||
SQL;
|
SQL;
|
||||||
$this->db->dbExecParams($q, [$this->edit_user_id]);
|
$this->db->dbExecParams($q, [$edit_user_id]);
|
||||||
}
|
}
|
||||||
$locale = $res['locale'] ?? 'en';
|
$locale = $res['locale'] ?? 'en';
|
||||||
$encoding = $res['encoding'] ?? 'UTF-8';
|
$encoding = $res['encoding'] ?? 'UTF-8';
|
||||||
@@ -970,8 +1236,13 @@ class Login
|
|||||||
// DEBUG flag is deprecated
|
// DEBUG flag is deprecated
|
||||||
// 'DEBUG_ALL' => $this->db->dbBoolean($res['debug']),
|
// 'DEBUG_ALL' => $this->db->dbBoolean($res['debug']),
|
||||||
// 'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']),
|
// 'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']),
|
||||||
|
// login timestamp
|
||||||
|
'LOGIN_LAST_AUTH_RESYNC' => time(),
|
||||||
|
// current forced logout counter
|
||||||
|
'LOGIN_FORCE_LOGOUT' => $res['force_logout'],
|
||||||
// general info for user logged in
|
// general info for user logged in
|
||||||
'LOGIN_USER_NAME' => $res['username'],
|
'LOGIN_USER_NAME' => $res['username'],
|
||||||
|
'LOGIN_EMAIL' => $res['email'],
|
||||||
'LOGIN_ADMIN' => $res['admin'],
|
'LOGIN_ADMIN' => $res['admin'],
|
||||||
'LOGIN_GROUP_NAME' => $res['edit_group_name'],
|
'LOGIN_GROUP_NAME' => $res['edit_group_name'],
|
||||||
'LOGIN_USER_ACL_LEVEL' => $res['user_level'],
|
'LOGIN_USER_ACL_LEVEL' => $res['user_level'],
|
||||||
@@ -1014,7 +1285,7 @@ class Login
|
|||||||
login_error_date_first = NULL
|
login_error_date_first = NULL
|
||||||
WHERE edit_user_id = $1
|
WHERE edit_user_id = $1
|
||||||
SQL;
|
SQL;
|
||||||
$this->db->dbExecParams($q, [$this->edit_user_id]);
|
$this->db->dbExecParams($q, [$edit_user_id]);
|
||||||
}
|
}
|
||||||
$edit_page_ids = [];
|
$edit_page_ids = [];
|
||||||
$pages = [];
|
$pages = [];
|
||||||
@@ -1143,7 +1414,7 @@ class Login
|
|||||||
$eacuid = [];
|
$eacuid = [];
|
||||||
$unit_acl = [];
|
$unit_acl = [];
|
||||||
$unit_uid_lookup = [];
|
$unit_uid_lookup = [];
|
||||||
while (is_array($res = $this->db->dbReturnParams($q, [$this->edit_user_id]))) {
|
while (is_array($res = $this->db->dbReturnParams($q, [$edit_user_id]))) {
|
||||||
// read edit access data fields and drop them into the unit access array
|
// read edit access data fields and drop them into the unit access array
|
||||||
$q_sub = <<<SQL
|
$q_sub = <<<SQL
|
||||||
SELECT name, value
|
SELECT name, value
|
||||||
@@ -1171,6 +1442,10 @@ class Login
|
|||||||
'cuid' => $res['cuid'],
|
'cuid' => $res['cuid'],
|
||||||
];
|
];
|
||||||
// set the default unit
|
// set the default unit
|
||||||
|
$this->session->setMany([
|
||||||
|
'LOGIN_UNIT_DEFAULT_EAID' => null,
|
||||||
|
'LOGIN_UNIT_DEFAULT_EACUID' => null,
|
||||||
|
]);
|
||||||
if ($res['edit_default']) {
|
if ($res['edit_default']) {
|
||||||
$this->session->set('LOGIN_UNIT_DEFAULT_EAID', (int)$res['edit_access_id']); // DEPRECATED
|
$this->session->set('LOGIN_UNIT_DEFAULT_EAID', (int)$res['edit_access_id']); // DEPRECATED
|
||||||
$this->session->set('LOGIN_UNIT_DEFAULT_EACUID', (int)$res['cuid']);
|
$this->session->set('LOGIN_UNIT_DEFAULT_EACUID', (int)$res['cuid']);
|
||||||
@@ -1191,49 +1466,6 @@ class Login
|
|||||||
'LOGIN_EAID' => $eaid, // DEPRECATED
|
'LOGIN_EAID' => $eaid, // DEPRECATED
|
||||||
'LOGIN_EACUID' => $eacuid,
|
'LOGIN_EACUID' => $eacuid,
|
||||||
]);
|
]);
|
||||||
} // user has permission to THIS page
|
|
||||||
} // user was not enabled or other login error
|
|
||||||
if ($this->login_error && is_array($res)) {
|
|
||||||
$login_error_date_first = '';
|
|
||||||
if ($res['login_error_count'] == 0) {
|
|
||||||
$login_error_date_first = ", login_error_date_first = NOW()";
|
|
||||||
}
|
|
||||||
// update login error count for this user
|
|
||||||
$q = <<<SQL
|
|
||||||
UPDATE edit_user
|
|
||||||
SET
|
|
||||||
login_error_count = login_error_count + 1,
|
|
||||||
login_error_date_last = NOW()
|
|
||||||
{LOGIN_ERROR_SQL}
|
|
||||||
WHERE edit_user_id = $1
|
|
||||||
SQL;
|
|
||||||
$this->db->dbExecParams(
|
|
||||||
str_replace('{LOGIN_ERROR_SQL}', $login_error_date_first, $q),
|
|
||||||
[$res['edit_user_id']]
|
|
||||||
);
|
|
||||||
// totally lock the user if error max is reached
|
|
||||||
if (
|
|
||||||
$this->max_login_error_count != -1 &&
|
|
||||||
$res['login_error_count'] + 1 > $this->max_login_error_count
|
|
||||||
) {
|
|
||||||
// do some alert reporting in case this error is too big
|
|
||||||
// if strict is set, lock this user
|
|
||||||
// this needs manual unlocking by an admin user
|
|
||||||
if ($res['strict'] && !in_array($this->username, $this->lock_deny_users)) {
|
|
||||||
$q = <<<SQL
|
|
||||||
UPDATE edit_user
|
|
||||||
SET locked = 1
|
|
||||||
WHERE edit_user_id = $1
|
|
||||||
SQL;
|
|
||||||
// [$res['edit_user_id']]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if there was an login error, show login screen
|
|
||||||
if ($this->login_error) {
|
|
||||||
// reset the perm var, to confirm logout
|
|
||||||
$this->permission_okay = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: login set ACL
|
// MARK: login set ACL
|
||||||
@@ -1361,7 +1593,7 @@ class Login
|
|||||||
$this->acl['show_ea_extra'] = false;
|
$this->acl['show_ea_extra'] = false;
|
||||||
}
|
}
|
||||||
// set the default edit access
|
// set the default edit access
|
||||||
$this->acl['default_edit_access'] = $_SESSION['UNIT_DEFAULT'] ?? null;
|
$this->acl['default_edit_access'] = $_SESSION['LOGIN_UNIT_DEFAULT_EACUID'];
|
||||||
// integrate the type acl list, but only for the keyword -> level
|
// integrate the type acl list, but only for the keyword -> level
|
||||||
$this->acl['min'] = $this->default_acl_list_type;
|
$this->acl['min'] = $this->default_acl_list_type;
|
||||||
// set the full acl list too (lookup level number and get level data)
|
// set the full acl list too (lookup level number and get level data)
|
||||||
@@ -2198,7 +2430,7 @@ HTML;
|
|||||||
// row 2
|
// row 2
|
||||||
$_SERVER["REMOTE_ADDR"] ?? null,
|
$_SERVER["REMOTE_ADDR"] ?? null,
|
||||||
Json::jsonConvertArrayTo([
|
Json::jsonConvertArrayTo([
|
||||||
'REMOTE_ADDR' => $_SERVER["REMOTE_ADDR"],
|
'REMOTE_ADDR' => $_SERVER["REMOTE_ADDR"] ?? null,
|
||||||
'HTTP_X_FORWARDED_FOR' => !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ?
|
'HTTP_X_FORWARDED_FOR' => !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ?
|
||||||
explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])
|
explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])
|
||||||
: [],
|
: [],
|
||||||
@@ -2262,7 +2494,7 @@ HTML;
|
|||||||
// **** PUBLIC INTERNAL
|
// **** PUBLIC INTERNAL
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
|
|
||||||
// MARK: PUBLIC LOGIN CALL
|
// MARK: MASTER PUBLIC LOGIN CALL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main call that needs to be run to actaully check for login
|
* Main call that needs to be run to actaully check for login
|
||||||
@@ -2355,10 +2587,12 @@ HTML;
|
|||||||
|
|
||||||
// if username & password & !$euid start login
|
// if username & password & !$euid start login
|
||||||
$this->loginLoginUser();
|
$this->loginLoginUser();
|
||||||
// checks if $euid given check if user is okay for that side
|
// checks if $euid given check if user is okay for that site
|
||||||
$this->loginCheckPermissions();
|
$this->loginCheckPermissions();
|
||||||
// logsout user
|
// logout user
|
||||||
$this->loginLogoutUser();
|
$this->loginLogoutUser();
|
||||||
|
// set headers for enhanced security
|
||||||
|
$this->loginEnhanceHttpSecurity();
|
||||||
// ** LANGUAGE SET AFTER LOGIN **
|
// ** LANGUAGE SET AFTER LOGIN **
|
||||||
$this->loginSetLocale();
|
$this->loginSetLocale();
|
||||||
// load translator
|
// load translator
|
||||||
@@ -2731,7 +2965,7 @@ HTML;
|
|||||||
}
|
}
|
||||||
$q = <<<SQL
|
$q = <<<SQL
|
||||||
SELECT
|
SELECT
|
||||||
ep.filename, eu.edit_user_id, eu.cuid, eu.cuuid,
|
ep.filename, eu.edit_user_id, eu.cuid, eu.cuuid, eu.force_logout,
|
||||||
-- base lock flags
|
-- base lock flags
|
||||||
eu.deleted, eu.enabled, eu.locked,
|
eu.deleted, eu.enabled, eu.locked,
|
||||||
-- date based lock
|
-- date based lock
|
||||||
@@ -2786,7 +3020,8 @@ HTML;
|
|||||||
(int)$res['enabled'],
|
(int)$res['enabled'],
|
||||||
(int)$res['locked'],
|
(int)$res['locked'],
|
||||||
(int)$res['locked_period'],
|
(int)$res['locked_period'],
|
||||||
(int)$res['login_user_id_locked']
|
(int)$res['login_user_id_locked'],
|
||||||
|
(int)$res['force_logout']
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// errors set in method
|
// errors set in method
|
||||||
@@ -2810,14 +3045,7 @@ HTML;
|
|||||||
$this->login_error = 103;
|
$this->login_error = 103;
|
||||||
}
|
}
|
||||||
// set all the internal vars
|
// set all the internal vars
|
||||||
$this->edit_user_id = (int)$res['edit_user_id'];
|
$this->loginSetEditUserUidData($res);
|
||||||
$this->edit_user_cuid = (string)$res['cuid'];
|
|
||||||
$this->edit_user_cuuid = (string)$res['cuuid'];
|
|
||||||
$this->session->setMany([
|
|
||||||
'LOGIN_EUID' => $this->edit_user_id, // DEPRECATED
|
|
||||||
'LOGIN_EUCUID' => $this->edit_user_cuid,
|
|
||||||
'LOGIN_EUCUUID' => $this->edit_user_cuuid,
|
|
||||||
]);
|
|
||||||
// if called from public, so we can check if the permissions are ok
|
// if called from public, so we can check if the permissions are ok
|
||||||
return $this->permission_okay;
|
return $this->permission_okay;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,21 +21,107 @@ class Session
|
|||||||
private string $session_id = '';
|
private string $session_id = '';
|
||||||
/** @var bool flag auto write close */
|
/** @var bool flag auto write close */
|
||||||
private bool $auto_write_close = false;
|
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
|
* init a session, if array is empty or array does not have session_name set
|
||||||
* then no auto init is run
|
* then no auto init is run
|
||||||
*
|
*
|
||||||
* @param string $session_name if set and not empty, will start session
|
* @param string $session_name if set and not empty, will start session
|
||||||
|
* @param array<string,bool> $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->initSession($session_name);
|
||||||
$this->auto_write_close = $auto_write_close;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: private methods
|
// MARK: private methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set session class options
|
||||||
|
*
|
||||||
|
* @param array<string,bool> $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
|
* Start session
|
||||||
* startSession should be called for complete check
|
* startSession should be called for complete check
|
||||||
@@ -72,6 +158,72 @@ class Session
|
|||||||
return false;
|
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
|
* check if session name is valid
|
||||||
*
|
*
|
||||||
@@ -151,6 +303,13 @@ class Session
|
|||||||
if (!$this->checkActiveSession()) {
|
if (!$this->checkActiveSession()) {
|
||||||
throw new \RuntimeException('[SESSION] Failed to activate session', 5);
|
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()) {
|
} elseif ($session_name != $this->getSessionName()) {
|
||||||
throw new \UnexpectedValueException(
|
throw new \UnexpectedValueException(
|
||||||
'[SESSION] Another session exists with a different name: ' . $this->getSessionName(),
|
'[SESSION] Another session exists with a different name: ' . $this->getSessionName(),
|
||||||
@@ -159,10 +318,12 @@ class Session
|
|||||||
}
|
}
|
||||||
// check session id
|
// check session id
|
||||||
if (false === ($session_id = $this->getSessionIdCall())) {
|
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
|
// set session id
|
||||||
$this->session_id = $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 flagged auto close, write close session
|
||||||
if ($this->auto_write_close) {
|
if ($this->auto_write_close) {
|
||||||
$this->writeClose();
|
$this->writeClose();
|
||||||
|
|||||||
Reference in New Issue
Block a user