Compare commits

..

25 Commits

Author SHA1 Message Date
Clemens Schwaighofer
4971f62490 ecuid name fix in test file 2024-12-13 11:42:45 +09:00
Clemens Schwaighofer
1cf4fdf31a Fix column named for edit_log to eu prefixed
as eucuid and eucuuid
2024-12-13 11:37:52 +09:00
Clemens Schwaighofer
d16b920966 Update arrayReturnMatchinKeyOnly description 2024-12-13 11:29:37 +09:00
Clemens Schwaighofer
ab52bf59b5 phan/phpstan fixes 2024-12-13 10:38:24 +09:00
Clemens Schwaighofer
a8dd076aac Merge branch 'NewFeatures' into Feature-LoginClassAddUuidv4 2024-12-13 10:27:49 +09:00
Clemens Schwaighofer
c17ca1f847 Merge branch 'development' 2024-12-13 10:21:41 +09:00
Clemens Schwaighofer
e349613d60 phpunit updates
Add testsuits for default run
Fix wording in testdox
add a fallback in the Debugging Support test suit
2024-12-13 10:17:28 +09:00
Clemens Schwaighofer
e9cfdb4bf0 Remove all deprecated tests 2024-12-13 09:35:54 +09:00
Clemens Schwaighofer
f966209e0a phpstan param declration fix for ACL Login user status 2024-12-12 21:20:09 +09:00
Clemens Schwaighofer
c13934de99 Fix for wrong key handling in Symmetric encryption 2024-12-12 21:09:41 +09:00
Clemens Schwaighofer
1e90bb677e Fix Symmetric encryption with wrong key handling
- static call encrypt: do not check pre set key
- indirect call: set new if key is different
2024-12-12 21:07:17 +09:00
Clemens Schwaighofer
540269e61f Fix update script for now to clock_timestamp 2024-12-12 19:04:21 +09:00
Clemens Schwaighofer
e793c3975b Change all db now to clock_timestamp for triggers 2024-12-12 12:02:49 +09:00
Clemens Schwaighofer
7d4c9724fe Fix session options argument declaration for phpstan 2024-12-11 21:10:00 +09:00
Clemens Schwaighofer
d1c4611431 Indent fix for ACL Login 2024-12-11 21:06:59 +09:00
Clemens Schwaighofer
8d3882a6fe 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)
2024-12-11 21:05:56 +09:00
Clemens Schwaighofer
2b0434e36b Merge branch 'NewFeatures' into Feature-AclLoginClassUpdateTokenCheckWithUuidV4 2024-12-11 10:36:57 +09:00
Clemens Schwaighofer
ba11a936db DB IO remove debug placeholder output 2024-12-11 10:36:31 +09:00
Clemens Schwaighofer
df591659cb Merge branch 'NewFeatures' into Feature-AclLoginClassUpdateTokenCheckWithUuidV4 2024-12-11 10:35:19 +09:00
Clemens Schwaighofer
5343034768 Fix DB IO placeholder detect and count regex
comment regex: (?:\-\-[^\r\n]*?\r?\n)*

Which is AFTER the element search as the comment can appear anywhere after the tag trigger
2024-12-11 10:30:41 +09:00
Clemens Schwaighofer
dec56c9559 Merge remote-tracking branch 'all/NewFeatures' into Feature-AclLoginClassUpdateTokenCheckWithUuidV4 2024-12-10 15:28:59 +09:00
Clemens Schwaighofer
880f15ac6f Merge branch 'development' 2024-12-10 15:26:24 +09:00
Clemens Schwaighofer
a46601fe03 Sync folder is master and not trunk 2024-12-10 15:25:17 +09:00
Clemens Schwaighofer
022c39e791 Add missing phpunit test folder for deprecated session var load test 2024-12-10 15:24:45 +09:00
Clemens Schwaighofer
fdefaca301 Missing php unit test path for locale check 2024-12-10 15:22:59 +09:00
34 changed files with 1369 additions and 628 deletions

View File

@@ -13,7 +13,7 @@ if [ "${GO}" != "go" ]; then
fi; fi;
BASE="/storage/var/www/html/developers/clemens/core_data/"; BASE="/storage/var/www/html/developers/clemens/core_data/";
SOURCE="${BASE}php_libraries/trunk/" SOURCE="${BASE}php_libraries/master/"
TARGET="${BASE}composer-packages/CoreLibs-Composer-All/" TARGET="${BASE}composer-packages/CoreLibs-Composer-All/"
rsync ${DRY_RUN}-Plzvrupt --stats --delete ${SOURCE}4dev/tests/ ${TARGET}test/phpunit/ rsync ${DRY_RUN}-Plzvrupt --stats --delete ${SOURCE}4dev/tests/ ${TARGET}test/phpunit/

View File

@@ -5,9 +5,9 @@ RETURNS TRIGGER AS
$$ $$
BEGIN BEGIN
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now'; NEW.date_created := clock_timestamp();
ELSIF TG_OP = 'UPDATE' THEN ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now'; NEW.date_updated := clock_timestamp();
END IF; END IF;
RETURN NEW; RETURN NEW;
END; END;

View File

@@ -7,11 +7,11 @@ DECLARE
random_length INT = 25; -- that should be long enough random_length INT = 25; -- that should be long enough
BEGIN BEGIN
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now'; NEW.date_created := clock_timestamp();
NEW.cuid := random_string(random_length); NEW.cuid := random_string(random_length);
NEW.cuuid := gen_random_uuid(); NEW.cuuid := gen_random_uuid();
ELSIF TG_OP = 'UPDATE' THEN ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now'; NEW.date_updated := clock_timestamp();
END IF; END IF;
RETURN NEW; RETURN NEW;
END; END;

View File

@@ -8,12 +8,12 @@ DECLARE
random_length INT = 32; -- long for massive data random_length INT = 32; -- long for massive data
BEGIN BEGIN
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now'; NEW.date_created := clock_timestamp();
IF NEW.uid IS NULL THEN IF NEW.uid IS NULL THEN
NEW.uid := random_string(random_length); NEW.uid := random_string(random_length);
END IF; END IF;
ELSIF TG_OP = 'UPDATE' THEN ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now'; NEW.date_updated := clock_timestamp();
END IF; END IF;
RETURN NEW; RETURN NEW;
END; END;

View File

@@ -1,19 +0,0 @@
-- adds the created or updated date tags
-- OLD, DEPRECATED, use set_generic.sql
-- CREATE OR REPLACE FUNCTION set_generic()
-- RETURNS TRIGGER AS
-- $$
-- BEGIN
-- IF TG_OP = 'INSERT' THEN
-- NEW.date_created := clock_timestamp();
-- NEW.user_created := current_user;
-- ELSIF TG_OP = 'UPDATE' THEN
-- NEW.date_updated := clock_timestamp();
-- NEW.user_updated := current_user;
-- END IF;
-- RETURN NEW;
-- END;
-- $$
-- LANGUAGE 'plpgsql';

View File

@@ -10,8 +10,8 @@ CREATE TABLE edit_log (
edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
euid INT, -- this is a foreign key, but I don't nedd to reference to it euid INT, -- this is a foreign key, but I don't nedd to reference to it
FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL,
ecuid VARCHAR, eucuid VARCHAR,
ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table eucuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table
-- date_created equal, but can be overridden -- date_created equal, but can be overridden
event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
-- session ID if set -- session ID if set

View File

@@ -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';

View File

@@ -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

View File

@@ -30,11 +30,11 @@ DECLARE
random_length INT = 12; -- that should be long enough random_length INT = 12; -- that should be long enough
BEGIN BEGIN
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now'; NEW.date_created := clock_timestamp();
NEW.cuid := random_string(random_length); NEW.cuid := random_string(random_length);
NEW.cuuid := gen_random_uuid(); NEW.cuuid := gen_random_uuid();
ELSIF TG_OP = 'UPDATE' THEN ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now'; NEW.date_updated := clock_timestamp();
END IF; END IF;
RETURN NEW; RETURN NEW;
END; END;
@@ -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
@@ -650,8 +652,8 @@ CREATE TABLE edit_log (
edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
euid INT, -- this is a foreign key, but I don't nedd to reference to it euid INT, -- this is a foreign key, but I don't nedd to reference to it
FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL,
ecuid VARCHAR, eucuid VARCHAR,
ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table eucuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table
-- date_created equal, but can be overridden -- date_created equal, but can be overridden
event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
-- session ID if set -- session ID if set
@@ -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

View File

@@ -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]);
} }
/** /**

View File

@@ -5141,9 +5141,9 @@ final class CoreLibsDBIOTest extends TestCase
INSERT INTO table_with_primary_key ( INSERT INTO table_with_primary_key (
row_int, row_numeric, row_varchar, row_varchar_literal row_int, row_numeric, row_varchar, row_varchar_literal
) VALUES ( ) VALUES (
-- comment 1 -- comment 1 かな
$1, $2, $1, $2,
-- comment 2 -- comment 2 -
$3 $3
-- comment 3 -- comment 3
, $4 , $4
@@ -5152,6 +5152,23 @@ final class CoreLibsDBIOTest extends TestCase
'count' => 4, 'count' => 4,
'convert' => false 'convert' => false
], ],
'comment in update' => [
'query' => <<<SQL
UPDATE table_with_primary_key SET
row_int =
-- COMMENT 1
$1,
row_numeric =
$2 -- COMMENT 2
,
row_varchar -- COMMENT 3
= $3
WHERE
row_varchar = $4
SQL,
'count' => 4,
'convert' => false,
],
// Note some are not set // Note some are not set
'a complete set of possible' => [ 'a complete set of possible' => [
'query' => <<<SQL 'query' => <<<SQL
@@ -5168,6 +5185,17 @@ final class CoreLibsDBIOTest extends TestCase
SQL, SQL,
'count' => 12, 'count' => 12,
'convert' => false, 'convert' => false,
],
// all the same
'all the same numbered' => [
'query' => <<<SQL
UPDATE table_with_primary_key SET
row_int = $1::INT, row_numeric = $1::NUMERIC, row_varchar = $1
WHERE
row_varchar = $1
SQL,
'count' => 1,
'convert' => false,
] ]
]; ];
} }

View File

@@ -568,6 +568,9 @@ final class CoreLibsDebugSupportTest extends TestCase
'assert expected 12' 'assert expected 12'
); );
break; break;
default:
$this->assertTrue(true, 'Default fallback as true');
break;
} }
} }

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -10,7 +10,7 @@ use CoreLibs\Logging\Logger\Level;
/** /**
* Test class for Logging * Test class for Logging
* @coversDefaultClass \CoreLibs\Logging\ErrorMessages * @coversDefaultClass \CoreLibs\Logging\ErrorMessages
* @testdox \CoreLibs\Logging\ErrorMEssages method tests * @testdox \CoreLibs\Logging\ErrorMessages method tests
*/ */
final class CoreLibsLoggingErrorMessagesTest extends TestCase final class CoreLibsLoggingErrorMessagesTest extends TestCase
{ {

View File

@@ -56,7 +56,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$decrypted, $decrypted,
'Class call', 'Class call',
); );
}
/**
* test encrypt/decrypt produce correct output
*
* @covers ::generateRandomKey
* @covers ::encrypt
* @covers ::decrypt
* @dataProvider providerEncryptDecryptSuccess
* @testdox encrypt/decrypt indirect $input must be $expected [$_dataName]
*
* @param string $input
* @param string $expected
* @return void
*/
public function testEncryptDecryptSuccessIndirect(string $input, string $expected): void
{
$key = CreateKey::generateRandomKey();
// test indirect // test indirect
$encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input);
$decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted); $decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted);
@@ -65,7 +82,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$decrypted, $decrypted,
'Class Instance call', 'Class Instance call',
); );
}
/**
* test encrypt/decrypt produce correct output
*
* @covers ::generateRandomKey
* @covers ::encrypt
* @covers ::decrypt
* @dataProvider providerEncryptDecryptSuccess
* @testdox encrypt/decrypt static $input must be $expected [$_dataName]
*
* @param string $input
* @param string $expected
* @return void
*/
public function testEncryptDecryptSuccessStatic(string $input, string $expected): void
{
$key = CreateKey::generateRandomKey();
// test static // test static
$encrypted = SymmetricEncryption::encryptKey($input, $key); $encrypted = SymmetricEncryption::encryptKey($input, $key);
$decrypted = SymmetricEncryption::decryptKey($encrypted, $key); $decrypted = SymmetricEncryption::decryptKey($encrypted, $key);
@@ -114,13 +148,51 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$crypt = new SymmetricEncryption($key); $crypt = new SymmetricEncryption($key);
$encrypted = $crypt->encrypt($input); $encrypted = $crypt->encrypt($input);
$this->expectExceptionMessage($exception_message); $this->expectExceptionMessage($exception_message);
$crypt->setKey($key); $crypt->setKey($wrong_key);
$crypt->decrypt($encrypted); $crypt->decrypt($encrypted);
}
/**
* Test decryption with wrong key
*
* @covers ::generateRandomKey
* @covers ::encrypt
* @covers ::decrypt
* @dataProvider providerEncryptFailed
* @testdox decrypt indirect with wrong key $input throws $exception_message [$_dataName]
*
* @param string $input
* @param string $exception_message
* @return void
*/
public function testEncryptFailedIndirect(string $input, string $exception_message): void
{
$key = CreateKey::generateRandomKey();
$wrong_key = CreateKey::generateRandomKey();
// class instance // class instance
$encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input);
$this->expectExceptionMessage($exception_message); $this->expectExceptionMessage($exception_message);
SymmetricEncryption::getInstance($wrong_key)->decrypt($encrypted); SymmetricEncryption::getInstance($wrong_key)->decrypt($encrypted);
}
/**
* Test decryption with wrong key
*
* @covers ::generateRandomKey
* @covers ::encrypt
* @covers ::decrypt
* @dataProvider providerEncryptFailed
* @testdox decrypt static with wrong key $input throws $exception_message [$_dataName]
*
* @param string $input
* @param string $exception_message
* @return void
*/
public function testEncryptFailedStatic(string $input, string $exception_message): void
{
$key = CreateKey::generateRandomKey();
$wrong_key = CreateKey::generateRandomKey();
// class static // class static
$encrypted = SymmetricEncryption::encryptKey($input, $key); $encrypted = SymmetricEncryption::encryptKey($input, $key);
@@ -190,6 +262,56 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
SymmetricEncryption::decryptKey($encrypted, $key); SymmetricEncryption::decryptKey($encrypted, $key);
} }
/**
* test invalid key provided to decrypt or encrypt
*
* @covers ::encrypt
* @covers ::decrypt
* @dataProvider providerWrongKey
* @testdox wrong key indirect $key throws $exception_message [$_dataName]
*
* @param string $key
* @param string $exception_message
* @return void
*/
public function testWrongKeyIndirect(string $key, string $exception_message): void
{
$enc_key = CreateKey::generateRandomKey();
// class instance
$this->expectExceptionMessage($exception_message);
SymmetricEncryption::getInstance($key)->encrypt('test');
// we must encrypt valid thing first so we can fail with the wrong key
$encrypted = SymmetricEncryption::getInstance($enc_key)->encrypt('test');
$this->expectExceptionMessage($exception_message);
SymmetricEncryption::getInstance($key)->decrypt($encrypted);
}
/**
* test invalid key provided to decrypt or encrypt
*
* @covers ::encrypt
* @covers ::decrypt
* @dataProvider providerWrongKey
* @testdox wrong key static $key throws $exception_message [$_dataName]
*
* @param string $key
* @param string $exception_message
* @return void
*/
public function testWrongKeyStatic(string $key, string $exception_message): void
{
$enc_key = CreateKey::generateRandomKey();
// class static
$this->expectExceptionMessage($exception_message);
SymmetricEncryption::encryptKey('test', $key);
// we must encrypt valid thing first so we can fail with the wrong key
$encrypted = SymmetricEncryption::encryptKey('test', $enc_key);
$this->expectExceptionMessage($exception_message);
SymmetricEncryption::decryptKey($encrypted, $key);
}
/** /**
* Undocumented function * Undocumented function
* *
@@ -232,6 +354,49 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$this->expectExceptionMessage($exception_message); $this->expectExceptionMessage($exception_message);
SymmetricEncryption::decryptKey($input, $key); SymmetricEncryption::decryptKey($input, $key);
} }
/**
* Undocumented function
*
* @covers ::decrypt
* @dataProvider providerWrongCiphertext
* @testdox too short ciphertext indirect $input throws $exception_message [$_dataName]
*
* @param string $input
* @param string $exception_message
* @return void
*/
public function testWrongCiphertextIndirect(string $input, string $exception_message): void
{
$key = CreateKey::generateRandomKey();
// class instance
$this->expectExceptionMessage($exception_message);
SymmetricEncryption::getInstance($key)->decrypt($input);
// class static
$this->expectExceptionMessage($exception_message);
SymmetricEncryption::decryptKey($input, $key);
}
/**
* Undocumented function
*
* @covers ::decrypt
* @dataProvider providerWrongCiphertext
* @testdox too short ciphertext static $input throws $exception_message [$_dataName]
*
* @param string $input
* @param string $exception_message
* @return void
*/
public function testWrongCiphertextStatic(string $input, string $exception_message): void
{
$key = CreateKey::generateRandomKey();
// class static
$this->expectExceptionMessage($exception_message);
SymmetricEncryption::decryptKey($input, $key);
}
} }
// __END__ // __END__

View File

@@ -1,12 +1,16 @@
-- 20241203: update edit tables -- 20241203: update edit tables
ALTER TABLE edit_generic ADD cuuid UUID DEFAULT gen_random_uuid(); ALTER TABLE edit_generic ADD cuuid UUID DEFAULT gen_random_uuid();
ALTER TABLE edit_log ADD ecuid VARCHAR; ALTER TABLE edit_log ADD eucuid VARCHAR;
ALTER TABLE edit_log ADD ecuuid VARCHAR; ALTER TABLE edit_log ADD eucuuid VARCHAR;
ALTER TABLE edit_log ADD action_sub_id VARCHAR; ALTER TABLE edit_log ADD action_sub_id VARCHAR;
ALTER TABLE edit_log ADD http_data JSONB; 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
@@ -18,13 +22,17 @@ DECLARE
random_length INT = 25; -- that should be long enough random_length INT = 25; -- that should be long enough
BEGIN BEGIN
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now'; NEW.date_created := clock_timestamp();
NEW.cuid := random_string(random_length); NEW.cuid := random_string(random_length);
NEW.cuuid := gen_random_uuid(); NEW.cuuid := gen_random_uuid();
ELSIF TG_OP = 'UPDATE' THEN ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now'; NEW.date_updated := clock_timestamp();
END IF; END IF;
RETURN NEW; RETURN NEW;
END; END;
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';
--
ALTER TABLE edit_log RENAME ecuid TO eucuid;
ALTER TABLE edit_log RENAME ecuuid TO eucuuid;

View File

@@ -5,4 +5,9 @@
convertDeprecationsToExceptions="true" convertDeprecationsToExceptions="true"
bootstrap="4dev/tests/bootstrap.php" bootstrap="4dev/tests/bootstrap.php"
> >
<testsuites>
<testsuite name="deploy">
<directory>4dev/tests</directory>
</testsuite>
</testsuites>
</phpunit> </phpunit>

View File

@@ -18,7 +18,7 @@ require 'config.php';
$LOG_FILE_ID = 'classTest-convert-colors'; $LOG_FILE_ID = 'classTest-convert-colors';
ob_end_flush(); ob_end_flush();
use CoreLibs\Convert\Colors; // use CoreLibs\Convert\Colors;
use CoreLibs\Convert\Color\Color; use CoreLibs\Convert\Color\Color;
use CoreLibs\Convert\Color\Coordinates; use CoreLibs\Convert\Color\Coordinates;
use CoreLibs\Debug\Support as DgS; use CoreLibs\Debug\Support as DgS;
@@ -29,7 +29,6 @@ $log = new CoreLibs\Logging\Logging([
'log_file_id' => $LOG_FILE_ID, 'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true, 'log_per_date' => true,
]); ]);
$color_class = 'CoreLibs\Convert\Colors';
/** /**
* print out a color block with info * print out a color block with info
@@ -131,7 +130,8 @@ try {
} catch (\LengthException $e) { } catch (\LengthException $e) {
print "*Exception: " . $e->getMessage() . "<br><pre>" . print_r($e, true) . "</pre><br>"; print "*Exception: " . $e->getMessage() . "<br><pre>" . print_r($e, true) . "</pre><br>";
} }
print "<hr>";
/* print "<hr>";
print "<h2>LEGACY</h2>"; print "<h2>LEGACY</h2>";
// B(valid) // B(valid)
$rgb = [50, 20, 30]; $rgb = [50, 20, 30];
@@ -173,7 +173,7 @@ $hsb = [0, 0, 5];
print "S::COLOR hsb->rgb: $hsb[0], $hsb[1], $hsb[2]: " print "S::COLOR hsb->rgb: $hsb[0], $hsb[1], $hsb[2]: "
. DgS::printAr(SetVarType::setArray( . DgS::printAr(SetVarType::setArray(
Colors::hsb2rgb($hsb[0], $hsb[1], $hsb[2]) Colors::hsb2rgb($hsb[0], $hsb[1], $hsb[2])
)) . "<br>"; )) . "<br>"; */
print "<hr>"; print "<hr>";

View File

@@ -28,7 +28,6 @@ $log = new CoreLibs\Logging\Logging([
'log_per_date' => true, 'log_per_date' => true,
]); ]);
$PAGE_NAME = 'TEST CLASS: DB CONVERT PLACEHOLDER'; $PAGE_NAME = 'TEST CLASS: DB CONVERT PLACEHOLDER';
print "<!DOCTYPE html>"; print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title></head>"; print "<html><head><title>" . $PAGE_NAME . "</title></head>";

View File

@@ -53,6 +53,9 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) {
} else { } else {
print "NO DB HANDLER<br>"; print "NO DB HANDLER<br>";
} }
// REGEX for placeholder count
print "Placeholder regex: <pre>" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "</pre>";
// turn on debug replace for placeholders // turn on debug replace for placeholders
$db->dbSetDebugReplacePlaceholder(true); $db->dbSetDebugReplacePlaceholder(true);
@@ -62,53 +65,94 @@ $db->dbExec("TRUNCATE test_foo");
$uniqid = \CoreLibs\Create\Uids::uniqIdShort(); $uniqid = \CoreLibs\Create\Uids::uniqIdShort();
$binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
$query_params = [ $query_params = [
$uniqid, $uniqid, // test
true, true, // some_bool
'STRING A', 'STRING A', // string_a
2, 2, // number_a
2.5, 2.5, // numeric_a
1, 1, // smallint
date('H:m:s'), date('H:m:s'), // some_internval
date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), // some_timestamp
json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]), json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]), // json_string
null, null, // null_var
'{"a", "b"}', '{"a", "b"}', // array_char_1
'{1,2}', '{1,2}', // array_int_1
'{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}', '{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}', // array_composite
'("Text", 4, 6.3)', '("Text", 4, 6.3)', // composite_item
$binary_data $binary_data, // some_binary
date('Y-m-d'), // some_date
date('H:i:s'), // some_time
'{"c", "d", "e"}', // array_char_2
'{3,4,5}', // array_int_2
12345667778818, // bigint
1.56, // numbrer_real
3.75, // number_double
124.5, // numeric_3
\CoreLibs\Create\Uids::uuidv4() // uuid_var
]; ];
$query_insert = <<<SQL $query_insert = <<<SQL
INSERT INTO test_foo ( INSERT INTO test_foo (
test, some_bool, string_a, number_a, number_a_numeric, smallint_a, -- row 1
some_time, some_timestamp, json_string, null_var, test, some_bool, string_a, number_a, numeric_a, smallint_a,
-- row 2
some_internval, some_timestamp, json_string, null_var,
-- row 3
array_char_1, array_int_1, array_char_1, array_int_1,
-- row 4
array_composite, array_composite,
-- row 5
composite_item, composite_item,
some_binary -- row 6
some_binary,
-- row 7
some_date, some_time,
-- row 8
array_char_2, array_int_2,
-- row 9
bigint_a, number_real, number_double, numeric_3,
-- row 10
uuid_var
) VALUES ( ) VALUES (
-- row 1
$1, $2, $3, $4, $5, $6, $1, $2, $3, $4, $5, $6,
-- row 2
$7, $8, $9, $10, $7, $8, $9, $10,
-- row 3
$11, $12, $11, $12,
-- row 4
$13, $13,
-- row 5
$14, $14,
$15 -- row 6
$15,
-- row 7
$16, $17,
-- row 8
$18, $19,
-- row 9
$20, $21, $22, $23,
-- row 10
$24
) )
RETURNING RETURNING
test_foo_id, test_foo_id, number_serial, identity_always, identitiy_default, default_uuid,
test, some_bool, string_a, number_a, number_a_numeric, smallint_a, test, some_bool, string_a, number_a, numeric_a, smallint_a,
some_time, some_timestamp, json_string, null_var, some_internval, some_timestamp, json_string, null_var,
array_char_1, array_int_1, array_char_1, array_int_1,
array_composite, array_composite,
composite_item, composite_item,
some_binary some_binary,
some_date,
array_char_2, array_int_2,
bigint_a, number_real, number_double, numeric_3,
uuid_var
SQL; SQL;
$status = $db->dbExecParams($query_insert, $query_params); $status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>"; echo "<b>*</b><br>";
echo "INSERT ALL COLUMN TYPES: " echo "INSERT ALL COLUMN TYPES: "
. Support::printToString($query_params) . " |<br>" . Support::printToString($query_params) . " |<br>"
. "QUERY: " . $db->dbGetQuery() . " |<br>" . "QUERY: <pre>" . $db->dbGetQuery() . "</pre> |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |<br>" . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |<br>"
. "RETURNING EXT: <pre>" . print_r($db->dbGetReturningExt(), true) . "</pre> |<br>" . "RETURNING EXT: <pre>" . print_r($db->dbGetReturningExt(), true) . "</pre> |<br>"
. "RETURNING RETURN: <pre>" . print_r($db->dbGetReturningArray(), true) . "<pre> |<br>" . "RETURNING RETURN: <pre>" . print_r($db->dbGetReturningArray(), true) . "<pre> |<br>"
@@ -146,6 +190,16 @@ SQL,
'params' => [], 'params' => [],
'direction' => 'pg', 'direction' => 'pg',
], ],
'numbers' => [
'query' => <<<SQL
SELECT test, string_a, number_a
FROM test_foo
WHERE
foo = $1 AND bar = $1 AND foobar = $2
SQL,
'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234],
'direction' => 'pdo',
],
'a?' => [ 'a?' => [
'query' => <<<SQL 'query' => <<<SQL
INSERT INTO test_foo ( INSERT INTO test_foo (
@@ -172,6 +226,18 @@ SQL,
], ],
'direction' => 'pg', 'direction' => 'pg',
], ],
'select, compare $' => [
'query' => <<<SQL
SELECT string_a
FROM test_foo
WHERE
number_a >= $1 OR number_a <= $2 OR
number_a > $3 OR number_a < $4
OR number_a = $5 OR number_a <> $6
SQL,
'params' => [1, 2, 3, 4, 5, 6],
'direction' => 'pg'
]
]; ];
$db->dbSetConvertPlaceholder(true); $db->dbSetConvertPlaceholder(true);
@@ -184,11 +250,12 @@ foreach ($test_queries as $info => $data) {
// . "<br>"; // . "<br>";
if ($db->dbCheckQueryForSelect($query)) { if ($db->dbCheckQueryForSelect($query)) {
$row = $db->dbReturnRowParams($query, $params); $row = $db->dbReturnRowParams($query, $params);
print "[$info] SELECT: " . Support::prAr($row) . "<br>"; print "<b>[$info]</b> SELECT: " . Support::prAr($row) . "<br>";
} else { } else {
$db->dbExecParams($query, $params); $db->dbExecParams($query, $params);
} }
print "[$info] " . Support::printAr($db->dbGetPlaceholderConverted()) . "<br>"; print "ERROR: " . $db->dbGetLastError(true) . "<br>";
print "<b>[$info]</b> " . Support::printAr($db->dbGetPlaceholderConverted()) . "<br>";
echo "<hr>"; echo "<hr>";
} }
@@ -203,22 +270,29 @@ SQL,
['string A-1'] ['string A-1']
)) ))
) { ) {
print "RES: " . Support::prAr($res) . "<br>"; print "<b>RES</b>: " . Support::prAr($res) . "<br>";
} }
print "ERROR: " . $db->dbGetLastError(true) . "<br>";
echo "<hr>";
print "CursorExt: " . Support::prAr($db->dbGetCursorExt(<<<SQL print "CursorExt: " . Support::prAr($db->dbGetCursorExt(<<<SQL
SELECT test, string_a, number_a SELECT test, string_a, number_a
FROM test_foo FROM test_foo
WHERE string_a = ? WHERE string_a = ?
SQL, ['string A-1'])); SQL, ['string A-1']));
echo "<hr>";
// ERROR BELOW: missing params
$res = $db->dbReturnRowParams(<<<SQL $res = $db->dbReturnRowParams(<<<SQL
SELECT test, string_a, number_a SELECT test, string_a, number_a
FROM test_foo FROM test_foo
WHERE string_a = $1 WHERE string_a = $1
SQL, []); SQL, []);
print "PL: " . Support::PrAr($db->dbGetPlaceholderConverted()) . "<br>"; print "PL: " . Support::PrAr($db->dbGetPlaceholderConverted()) . "<br>";
print "ERROR: " . $db->dbGetLastError(true) . "<br>";
echo "<hr>";
// ERROR BELOW: LIKE cannot have placeholder
echo "dbReturn read LIKE: <br>"; echo "dbReturn read LIKE: <br>";
while ( while (
is_array($res = $db->dbReturnParams( is_array($res = $db->dbReturnParams(
@@ -232,6 +306,7 @@ SQL,
) { ) {
print "RES: " . Support::prAr($res) . "<br>"; print "RES: " . Support::prAr($res) . "<br>";
} }
print "ERROR: " . $db->dbGetLastError(true) . "<br>";
print "</body></html>"; print "</body></html>";
$db->log->debug('DEBUGEND', '==================================== [END]'); $db->log->debug('DEBUGEND', '==================================== [END]');

View File

@@ -74,8 +74,8 @@ print "EL_O: <pre>" . print_r($el_o, true) . "</pre>";
echo "<hr>"; echo "<hr>";
print "buildHtml(): <pre>" . htmlentities($el_o->buildHtml()) . "</pre>"; print "buildHtml(): <pre>" . htmlentities($el_o->buildHtml()) . "</pre>";
echo "<hr>"; /* echo "<hr>";
print "phfo(\$el_o): <pre>" . htmlentities($el_o::printHtmlFromObject($el_o, true)) . "</pre>"; print "phfo(\$el_o): <pre>" . htmlentities($el_o::printHtmlFromObject($el_o, true)) . "</pre>"; */
echo "<hr>"; echo "<hr>";
print "phfa(\$el_list): <pre>" . htmlentities($el_o::buildHtmlFromList($el_o_list, true)) . "</pre>"; print "phfa(\$el_list): <pre>" . htmlentities($el_o::buildHtmlFromList($el_o_list, true)) . "</pre>";

View File

@@ -34,22 +34,21 @@ use CoreLibs\Debug\Support;
echo "<br><b>LIST LOCALES</b><br>"; echo "<br><b>LIST LOCALES</b><br>";
$locale = 'en_US.UTF-8'; $locale = 'en_US.UTF-8';
$locales = L10n::listLocales($locale); $locales = Language\L10n::listLocales($locale);
print "[" . $locale . "] LOCALES: " . Support::printAr($locales) . "<br>"; print "[" . $locale . "] LOCALES: " . Support::printAr($locales) . "<br>";
$locale = 'en.UTF-8'; $locale = 'en.UTF-8';
$locales = L10n::listLocales($locale); $locales = Language\L10n::listLocales($locale);
print "[" . $locale . "] LOCALES: " . Support::printAr($locales) . "<br>"; print "[" . $locale . "] LOCALES: " . Support::printAr($locales) . "<br>";
echo "<br><b>PARSE LOCAL</b><br>"; echo "<br><b>PARSE LOCAL</b><br>";
$locale = 'en_US.UTF-8'; $locale = 'en_US.UTF-8';
$locale_info = L10n::parseLocale($locale); $locale_info = Language\L10n::parseLocale($locale);
print "[" . $locale . "] INFO: " . Support::printAr($locale_info) . "<br>"; print "[" . $locale . "] INFO: " . Support::printAr($locale_info) . "<br>";
$locale = 'en.UTF-8'; $locale = 'en.UTF-8';
$locale_info = L10n::parseLocale($locale); $locale_info = Language\L10n::parseLocale($locale);
print "[" . $locale . "] INFO: " . Support::printAr($locale_info) . "<br>"; print "[" . $locale . "] INFO: " . Support::printAr($locale_info) . "<br>";
echo "<br><b>AUTO DETECT</b><br>"; /* echo "<br><b>AUTO DETECT</b><br>";
// DEPRECATED // DEPRECATED
// $get_locale = Language\GetLocale::setLocale(); // $get_locale = Language\GetLocale::setLocale();
// print "[AUTO, DEPRECATED]: " . Support::printAr($get_locale) . "<br>"; // print "[AUTO, DEPRECATED]: " . Support::printAr($get_locale) . "<br>";
@@ -103,6 +102,7 @@ $get_locale = Language\GetLocale::setLocaleFromSession(
BASE . INCLUDES . LOCALE BASE . INCLUDES . LOCALE
); );
print "[SESSION SET INVALID]: " . Support::printAr($get_locale) . "<br>"; print "[SESSION SET INVALID]: " . Support::printAr($get_locale) . "<br>";
*/
// try to load non existing // try to load non existing
echo "<br><b>NEW TYPE</b><br>"; echo "<br><b>NEW TYPE</b><br>";

View File

@@ -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'

View File

@@ -91,7 +91,7 @@ HTML;
$test_files = [ $test_files = [
'class_test.db.php' => 'Class Test: DB', 'class_test.db.php' => 'Class Test: DB',
'class_test.db.types.php' => 'Class Test: DB column type convert', 'class_test.db.types.php' => 'Class Test: DB column type convert',
'class_test.db.query-placeholder.php' => 'Class Test: DB query placeholder convert', 'class_test.db.query-placeholder.php' => 'Class Test: DB placeholder queries',
'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn',
'class_test.db.single.php' => 'Class Test: DB single query tests', 'class_test.db.single.php' => 'Class Test: DB single query tests',
'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder', 'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder',

View File

@@ -34,10 +34,12 @@ print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "ALREADY from config.php: " . \CoreLibs\Debug\Support::printAr($_ENV) . "<br>"; print "ALREADY from config.php: " . \CoreLibs\Debug\Support::printAr($_ENV) . "<br>";
// This is now in \gullevek\dotenv\DotEnv::readEnvFile(...)
// test .env in local // test .env in local
$status = \CoreLibs\Get\DotEnv::readEnvFile('.', 'test.env'); /* $status = \CoreLibs\Get\DotEnv::readEnvFile('.', 'test.env');
print "test.env: STATUS: " . $status . "<br>"; print "test.env: STATUS: " . $status . "<br>";
print "AFTER reading test.env file: " . \CoreLibs\Debug\Support::printAr($_ENV) . "<br>"; print "AFTER reading test.env file: " . \CoreLibs\Debug\Support::printAr($_ENV) . "<br>"; */
print "</body></html>"; print "</body></html>";
// ;; // ;;

View File

@@ -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>";

View File

@@ -79,7 +79,7 @@ class Login
private ?int $edit_user_id; private ?int $edit_user_id;
/** @var ?string the user cuid (note will be super seeded with uuid v4 later) */ /** @var ?string the user cuid (note will be super seeded with uuid v4 later) */
private ?string $edit_user_cuid; private ?string $edit_user_cuid;
/** @var ?string UUIDv4, will superseed the ecuid and replace euid as login id */ /** @var ?string UUIDv4, will superseed the eucuid and replace euid as login id */
private ?string $edit_user_cuuid; private ?string $edit_user_cuuid;
/** @var string _GET/_POST loginUserId parameter for non password login */ /** @var string _GET/_POST loginUserId parameter for non password login */
private string $login_user_id = ''; private string $login_user_id = '';
@@ -217,6 +217,16 @@ class Login
'path' => '', 'path' => '',
]; ];
/** @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 +258,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 +298,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 +451,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 +519,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 +735,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 +743,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 +762,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 +847,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 +964,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 +977,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 +1090,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 +1137,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 +1164,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 +1207,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 +1216,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 +1265,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 +1394,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 +1422,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 +1446,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 +1573,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)
@@ -2159,7 +2371,7 @@ HTML;
} }
$q = <<<SQL $q = <<<SQL
INSERT INTO {DB_SCHEMA}.edit_log ( INSERT INTO {DB_SCHEMA}.edit_log (
username, euid, ecuid, ecuuid, event_date, event, error, data, data_binary, page, username, euid, eucuid, eucuuid, event_date, event, error, data, data_binary, page,
ip, ip_address, user_agent, referer, script_name, query_string, request_scheme, server_name, ip, ip_address, user_agent, referer, script_name, query_string, request_scheme, server_name,
http_host, http_data, session_id, http_host, http_data, session_id,
action_data action_data
@@ -2198,7 +2410,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 +2474,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 +2567,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
@@ -2513,7 +2727,7 @@ HTML;
return $this->session->get('LOGIN_PAGES'); return $this->session->get('LOGIN_PAGES');
} }
// MARK: logged in uid(pk)/cuid/ecuuid // MARK: logged in uid(pk)/eucuid/eucuuid
/** /**
* Get the current set EUID (edit user id) * Get the current set EUID (edit user id)
@@ -2724,14 +2938,14 @@ HTML;
if (empty($this->edit_user_cuuid)) { if (empty($this->edit_user_cuuid)) {
return $this->permission_okay; return $this->permission_okay;
} }
// euid must match ecuid and ecuuid // euid must match eucuid and eucuuid
// bail for previous wrong page match, eg if method is called twice // bail for previous wrong page match, eg if method is called twice
if ($this->login_error == 103) { if ($this->login_error == 103) {
return $this->permission_okay; return $this->permission_okay;
} }
$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 +3000,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 +3025,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;
} }

View File

@@ -0,0 +1,68 @@
<?php
/**
* AUTHOR: Clemens Schwaighofer
* CREATED: 2024/12/12
* DESCRIPTION:
* ACL Login user status bitmap list
*/
declare(strict_types=1);
namespace CoreLibs\ACL;
final class LoginUserStatus
{
// 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;
/**
* Returns an array mapping the numerical role values to their descriptive names
*
* @return array<int|string,string>
*/
public static function getMap()
{
return array_flip((new \ReflectionClass(static::class))->getConstants());
}
/**
* Returns the descriptive role names
*
* @return string[]
*/
public static function getNames()
{
return array_keys((new \ReflectionClass(static::class))->getConstants());
}
/**
* Returns the numerical role values
*
* @return int[]
*/
public static function getValues()
{
return array_values((new \ReflectionClass(static::class))->getConstants());
}
}
// __END__

View File

@@ -358,7 +358,7 @@ class Backend
} }
$q = <<<SQL $q = <<<SQL
INSERT INTO {DB_SCHEMA}.edit_log ( INSERT INTO {DB_SCHEMA}.edit_log (
username, euid, ecuid, ecuuid, event_date, event, error, data, data_binary, page, username, euid, eucuid, eucuuid, event_date, event, error, data, data_binary, page,
ip, user_agent, referer, script_name, query_string, server_name, http_host, ip, user_agent, referer, script_name, query_string, server_name, http_host,
http_accept, http_accept_charset, http_accept_encoding, session_id, http_accept, http_accept_charset, http_accept_encoding, session_id,
action, action_id, action_sub_id, action_yes, action_flag, action_menu, action_loaded, action, action_id, action_sub_id, action_yes, action_flag, action_menu, action_loaded,

View File

@@ -527,7 +527,9 @@ class ArrayHandler
} }
/** /**
* From the array with key -> anything values return only the matching entries from key list * From the array with key -> mixed values,
* return only the entries where the key matches the key given in the key list parameter
*
* key list is a list[string] * key list is a list[string]
* if key list is empty, return array as is * if key list is empty, return array as is
* *

View File

@@ -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{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->initSession($session_name);
$this->auto_write_close = $auto_write_close;
} }
// MARK: private methods // 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 * 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();

View File

@@ -1332,7 +1332,6 @@ class IO
*/ */
private function __dbCheckQueryParams(string $query, array $params): bool private function __dbCheckQueryParams(string $query, array $params): bool
{ {
// $this->log->debug('DB QUERY PARAMS REGEX', ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS);
$placeholder_count = $this->__dbCountQueryParams($query); $placeholder_count = $this->__dbCountQueryParams($query);
$params_count = count($params); $params_count = count($params);
if ($params_count != $placeholder_count) { if ($params_count != $placeholder_count) {

View File

@@ -18,17 +18,20 @@ class ConvertPlaceholder
// NOTE some combinations are allowed, but the query will fail before this // NOTE some combinations are allowed, but the query will fail before this
/** @var string split regex, entries before $ group */ /** @var string split regex, entries before $ group */
private const PATTERN_QUERY_SPLIT = private const PATTERN_QUERY_SPLIT =
',|' // for ',' mostly in INSERT '\?\?|' // UNKNOWN: double ??, is this to avoid something?
. '[(<>=]|' // general set for (, <, >, = in any query with any combination . '[\(,]|' // for ',' and '(' mostly in INSERT or ANY()
. '(?:[\(,]\s*\-\-[\s\w]*)\r?\n|' // a comment that starts after a ( or , . '[<>=]|' // general set for <, >, = in any query with any combination
. '\^@|' // text search for start from text with ^@ . '\^@|' // text search for start from text with ^@
. '\|\||' // concats two elements . '\|\||' // concats two elements
. '&&|' // array overlap . '&&|' // array overlap
. '\-\|\-|' // range overlap . '\-\|\-|' // range overlap for array
. '[^-]-{1}|' // single -, used in JSON too . '[^-]-{1}|' // single -, used in JSON too
. '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-'; //JSON searches, Array searchs, etc . '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-'; //JSON searches, Array searchs, etc
/** @var string the main regex including the pattern query split */ /** @var string the main regex including the pattern query split */
private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:\?\?|' . self::PATTERN_QUERY_SPLIT . ')\s*'; private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*';
/** @var string comment regex
* anything that starts with -- and ends with a line break but any character that is not line break inbetween */
private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)*\s*';
/** @var string parts to ignore in the SQL */ /** @var string parts to ignore in the SQL */
private const PATTERN_IGNORE = private const PATTERN_IGNORE =
// digit -> ignore // digit -> ignore
@@ -45,6 +48,7 @@ class ConvertPlaceholder
/** @var string replace regex for named (:...) entries */ /** @var string replace regex for named (:...) entries */
public const REGEX_REPLACE_NAMED = '/' public const REGEX_REPLACE_NAMED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '(' . '('
. self::PATTERN_IGNORE . self::PATTERN_IGNORE
. self::PATTERN_NAMED . self::PATTERN_NAMED
@@ -53,6 +57,7 @@ class ConvertPlaceholder
/** @var string replace regex for question mark (?) entries */ /** @var string replace regex for question mark (?) entries */
public const REGEX_REPLACE_QUESTION_MARK = '/' public const REGEX_REPLACE_QUESTION_MARK = '/'
. '(' . self::PATTERN_ELEMENT . ')' . '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '(' . '('
. self::PATTERN_IGNORE . self::PATTERN_IGNORE
. self::PATTERN_QUESTION_MARK . self::PATTERN_QUESTION_MARK
@@ -61,6 +66,7 @@ class ConvertPlaceholder
/** @var string replace regex for numbered ($n) entries */ /** @var string replace regex for numbered ($n) entries */
public const REGEX_REPLACE_NUMBERED = '/' public const REGEX_REPLACE_NUMBERED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '(' . '('
. self::PATTERN_IGNORE . self::PATTERN_IGNORE
. self::PATTERN_NUMBERED . self::PATTERN_NUMBERED
@@ -71,6 +77,7 @@ class ConvertPlaceholder
// prefix string part, must match towards // prefix string part, must match towards
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15] // seperator for ( = , ? - [and json/jsonb in pg doc section 9.15]
. self::PATTERN_ELEMENT . self::PATTERN_ELEMENT
. self::PATTERN_COMMENT
// match for replace part // match for replace part
. '(?:' . '(?:'
// ignore parts // ignore parts

View File

@@ -49,7 +49,11 @@ class SymmetricEncryption
*/ */
public static function getInstance(string|null $key = null): self public static function getInstance(string|null $key = null): self
{ {
if (empty(self::$instance)) { // new if no instsance or key is different
if (
empty(self::$instance) ||
self::$instance->key != $key
) {
self::$instance = new self($key); self::$instance = new self($key);
} }
return self::$instance; return self::$instance;
@@ -130,7 +134,7 @@ class SymmetricEncryption
*/ */
private function encryptData(string $message, ?string $key): string private function encryptData(string $message, ?string $key): string
{ {
if (empty($this->key) || $key === null) { if ($key === null) {
throw new \UnexpectedValueException('Key not set'); throw new \UnexpectedValueException('Key not set');
} }
$key = $this->createKey($key); $key = $this->createKey($key);