Compare commits

...

15 Commits

Author SHA1 Message Date
Clemens Schwaighofer
fbea8f4aca Fix for Symmetric encryption key handling 2024-12-12 21:11:25 +09:00
Clemens Schwaighofer
346cdaad72 Fix for params regex comment update 2024-12-11 11:23:19 +09:00
Clemens Schwaighofer
6887f17e15 Release: v9.23.2 2024-12-10 15:31:45 +09:00
Clemens Schwaighofer
5b1ca4241c phan min php update to 8.3, add missing phpunit test folder for language check 2024-12-10 15:29:54 +09:00
Clemens Schwaighofer
c8d6263c0f Fix DB IO placeholder count 2024-12-10 14:59:58 +09:00
Clemens Schwaighofer
bd1972d894 Composer keywords 2024-12-10 14:52:50 +09:00
Clemens Schwaighofer
fa29477c80 Release: v9.23.1 2024-12-05 14:08:56 +09:00
Clemens Schwaighofer
20ee958db9 Session class update with many methods and general clean up 2024-12-05 14:07:39 +09:00
Clemens Schwaighofer
157616582f Release: v9.23.0 2024-12-04 16:40:51 +09:00
Clemens Schwaighofer
0f7bf0ab44 Session class rewrite 2024-12-04 14:22:26 +09:00
Clemens Schwaighofer
10dc56c7cb Release: v9.22.0 2024-12-03 13:34:56 +09:00
Clemens Schwaighofer
d19842007c ACL Login cuid and cuuid update and add 2024-12-03 13:29:50 +09:00
Clemens Schwaighofer
c5bd16de74 Release: v9.21.1 2024-12-02 17:20:25 +09:00
Clemens Schwaighofer
e580e5430c Add ECUID (edit user cuid) to session and return in acl array 2024-12-02 17:18:34 +09:00
Clemens Schwaighofer
da9717265c Release: v9.21.0 2024-12-02 15:55:33 +09:00
15 changed files with 1416 additions and 854 deletions

View File

@@ -54,7 +54,8 @@ return [
// Note that the **only** effect of choosing `'5.6'` is to infer that functions removed in php 7.0 exist.
// (See `backward_compatibility_checks` for additional options)
// Automatically inferred from composer.json requirement for "php" of ">=8.2"
'target_php_version' => '8.1',
'target_php_version' => '8.2',
"minimum_target_php_version" => "8.2",
// If enabled, missing properties will be created when
// they are first seen. If false, we'll report an

View File

@@ -3,6 +3,7 @@
"description": "CoreLibs in a composer package",
"type": "library",
"license": "MIT",
"keywords": ["corelib", "logging", "database", "templating", "tools"],
"autoload": {
"psr-4": {
"CoreLibs\\": "src/"

View File

@@ -1 +1 @@
9.20.1
9.23.2

View File

@@ -69,12 +69,17 @@ declare(strict_types=1);
namespace CoreLibs\ACL;
use CoreLibs\Security\Password;
use CoreLibs\Create\Uids;
use CoreLibs\Convert\Json;
class Login
{
/** @var ?int the user id var*/
private ?int $euid;
/** @var ?string the user cuid (note will be super seeded with uuid v4 later) */
private ?string $ecuid;
/** @var ?string UUIDv4, will superseed the ecuid and replace euid as login id */
private ?string $ecuuid;
/** @var string _GET/_POST loginUserId parameter for non password login */
private string $login_user_id = '';
/** @var string source, either _GET or _POST or empty */
@@ -193,6 +198,12 @@ class Login
/** @var bool */
private bool $login_is_ajax_page = false;
// logging
/** @var array<string> list of allowed types for edit log write */
private const WRITE_TYPES = ['BINARY', 'BZIP2', 'LZIP', 'STRING', 'SERIAL', 'JSON'];
/** @var array<string> list of available write types for log */
private array $write_types_available = [];
// settings
/** @var array<string,mixed> options */
private array $options = [];
@@ -361,9 +372,6 @@ class Login
],
];
// init default ACL list array
$_SESSION['DEFAULT_ACL_LIST'] = [];
$_SESSION['DEFAULT_ACL_LIST_TYPE'] = [];
// read the current edit_access_right list into an array
$q = "SELECT level, type, name FROM edit_access_right "
. "WHERE level >= 0 ORDER BY level";
@@ -376,8 +384,12 @@ class Login
$this->default_acl_list_type[(string)$res['type']] = (int)$res['level'];
}
// write that into the session
$_SESSION['DEFAULT_ACL_LIST'] = $this->default_acl_list;
$_SESSION['DEFAULT_ACL_LIST_TYPE'] = $this->default_acl_list_type;
$this->session->setMany([
'DEFAULT_ACL_LIST' => $this->default_acl_list,
'DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type,
]);
$this->loginSetEditLogWriteTypeAvailable();
// this will be deprecated
if ($this->options['auto_login'] === true) {
@@ -567,7 +579,7 @@ class Login
// set path
$options['locale_path'] = BASE . INCLUDES . LOCALE;
}
$_SESSION['LOCALE_PATH'] = $options['locale_path'];
$this->session->set('LOCALE_PATH', $options['locale_path']);
// LANG: LOCALE
if (empty($options['site_locale'])) {
trigger_error(
@@ -602,7 +614,7 @@ class Login
$options['set_domain'] = str_replace(DIRECTORY_SEPARATOR, '', CONTENT_PATH);
}
}
$_SESSION['DEFAULT_DOMAIN'] = $options['site_domain'];
$this->session->set('DEFAULT_DOMAIN', $options['site_domain']);
// LANG: ENCODING
if (empty($options['site_encoding'])) {
trigger_error(
@@ -757,7 +769,7 @@ class Login
}
// have to get the global stuff here for setting it later
// we have to get the themes in here too
$q = "SELECT eu.edit_user_id, eu.username, eu.password, "
$q = "SELECT eu.edit_user_id, eu.cuid, eu.cuuid, eu.username, eu.password, "
. "eu.edit_group_id, "
. "eg.name AS edit_group_name, eu.admin, "
// additinal acl lists
@@ -888,7 +900,14 @@ class Login
}
// normal user processing
// set class var and session var
$_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id'];
$this->euid = (int)$res['edit_user_id'];
$this->ecuid = (string)$res['cuid'];
$this->ecuuid = (string)$res['cuuid'];
$this->session->setMany([
'EUID' => $this->euid,
'ECUID' => $this->ecuid,
'ECUUID' => $this->ecuuid,
]);
// check if user is okay
$this->loginCheckPermissions();
if ($this->login_error == 0) {
@@ -901,27 +920,39 @@ class Login
. "WHERE edit_user_id = " . $this->euid;
$this->db->dbExec($q);
}
// now set all session vars and read page permissions
$_SESSION['DEBUG_ALL'] = $this->db->dbBoolean($res['debug']);
$_SESSION['DB_DEBUG'] = $this->db->dbBoolean($res['db_debug']);
// general info for user logged in
$_SESSION['USER_NAME'] = $res['username'];
$_SESSION['ADMIN'] = $res['admin'];
$_SESSION['GROUP_NAME'] = $res['edit_group_name'];
$_SESSION['USER_ACL_LEVEL'] = $res['user_level'];
$_SESSION['USER_ACL_TYPE'] = $res['user_type'];
$_SESSION['USER_ADDITIONAL_ACL'] = Json::jsonConvertToArray($res['user_additional_acl']);
$_SESSION['GROUP_ACL_LEVEL'] = $res['group_level'];
$_SESSION['GROUP_ACL_TYPE'] = $res['group_type'];
$_SESSION['GROUP_ADDITIONAL_ACL'] = Json::jsonConvertToArray($res['group_additional_acl']);
// deprecated TEMPLATE setting
$_SESSION['TEMPLATE'] = $res['template'] ? $res['template'] : '';
$_SESSION['HEADER_COLOR'] = !empty($res['second_header_color']) ?
$res['second_header_color'] :
$res['first_header_color'];
$locale = $res['locale'] ?? 'en';
$encoding = $res['encoding'] ?? 'UTF-8';
$this->session->setMany([
// now set all session vars and read page permissions
'DEBUG_ALL' => $this->db->dbBoolean($res['debug']),
'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']),
// general info for user logged in
'USER_NAME' => $res['username'],
'ADMIN' => $res['admin'],
'GROUP_NAME' => $res['edit_group_name'],
'USER_ACL_LEVEL' => $res['user_level'],
'USER_ACL_TYPE' => $res['user_type'],
'USER_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['user_additional_acl']),
'GROUP_ACL_LEVEL' => $res['group_level'],
'GROUP_ACL_TYPE' => $res['group_type'],
'GROUP_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['group_additional_acl']),
// deprecated TEMPLATE setting
'TEMPLATE' => $res['template'] ? $res['template'] : '',
'HEADER_COLOR' => !empty($res['second_header_color']) ?
$res['second_header_color'] :
$res['first_header_color'],
// LANGUAGE/LOCALE/ENCODING:
'LANG' => $locale,
'DEFAULT_CHARSET' => $encoding,
'DEFAULT_LOCALE' => $locale . '.' . strtoupper($encoding),
'DEFAULT_LANG' => $locale . '_' . strtolower(str_replace('-', '', $encoding))
]);
// missing # before, this is for legacy data, will be deprecated
if (preg_match("/^[\dA-Fa-f]{6,8}$/", $_SESSION['HEADER_COLOR'])) {
$_SESSION['HEADER_COLOR'] = '#' . $_SESSION['HEADER_COLOR'];
if (
!empty($this->session->get('HEADER_COLOR')) &&
preg_match("/^[\dA-Fa-f]{6,8}$/", $this->session->get('HEADER_COLOR'))
) {
$this->session->set('HEADER_COLOR', '#' . $this->session->get('HEADER_COLOR'));
}
// TODO: make sure that header color is valid:
// # + 6 hex
@@ -930,13 +961,6 @@ class Login
// rgb: nnn.n for each
// hsl: nnn.n for first, nnn.n% for 2nd, 3rd
// Check\Colors::validateColor()
// LANGUAGE/LOCALE/ENCODING:
$_SESSION['LANG'] = $res['locale'] ?? 'en';
$_SESSION['DEFAULT_CHARSET'] = $res['encoding'] ?? 'UTF-8';
$_SESSION['DEFAULT_LOCALE'] = $_SESSION['LANG']
. '.' . strtoupper($_SESSION['DEFAULT_CHARSET']);
$_SESSION['DEFAULT_LANG'] = $_SESSION['LANG'] . '_'
. strtolower(str_replace('-', '', $_SESSION['DEFAULT_CHARSET']));
// reset any login error count for this user
if ($res['login_error_count'] > 0) {
$q = "UPDATE edit_user "
@@ -1026,8 +1050,10 @@ class Login
];
}
// write back the pages data to the output array
$_SESSION['PAGES'] = $pages;
$_SESSION['PAGES_ACL_LEVEL'] = $pages_acl;
$this->session->setMany([
'PAGES' => $pages,
'PAGES_ACL_LEVEL' => $pages_acl,
]);
// load the edit_access user rights
$q = "SELECT ea.edit_access_id, level, type, ea.name, "
. "ea.color, ea.uid, edit_default, ea.additional_acl "
@@ -1039,6 +1065,7 @@ class Login
$unit_access = [];
$eauid = [];
$unit_acl = [];
$unit_uid = [];
while (is_array($res = $this->db->dbReturn($q))) {
// read edit access data fields and drop them into the unit access array
$q_sub = "SELECT name, value "
@@ -1062,16 +1089,19 @@ class Login
];
// set the default unit
if ($res['edit_default']) {
$_SESSION['UNIT_DEFAULT'] = (int)$res['edit_access_id'];
$this->session->set('UNIT_DEFAULT', (int)$res['edit_access_id']);
}
$_SESSION['UNIT_UID'][$res['uid']] = (int)$res['edit_access_id'];
$unit_uid[$res['uid']] = (int)$res['edit_access_id'];
// sub arrays for simple access
array_push($eauid, $res['edit_access_id']);
$unit_acl[$res['edit_access_id']] = $res['level'];
}
$_SESSION['UNIT'] = $unit_access;
$_SESSION['UNIT_ACL_LEVEL'] = $unit_acl;
$_SESSION['EAID'] = $eauid;
$this->session->setMany([
'UNIT_UID' => $unit_uid,
'UNIT' => $unit_access,
'UNIT_ACL_LEVEL' => $unit_acl,
'EAID' => $eauid,
]);
} // user has permission to THIS page
} // user was not enabled or other login error
if ($this->login_error && is_array($res)) {
@@ -1132,6 +1162,9 @@ class Login
// username (login), group name
$this->acl['user_name'] = $_SESSION['USER_NAME'];
$this->acl['group_name'] = $_SESSION['GROUP_NAME'];
// edit user cuid
$this->acl['ecuid'] = $_SESSION['ECUID'];
$this->acl['ecuuid'] = $_SESSION['ECUUID'];
// set additional acl
$this->acl['additional_acl'] = [
'user' => $_SESSION['USER_ADDITIONAL_ACL'],
@@ -1164,7 +1197,7 @@ class Login
$this->acl['base'] = (int)$_SESSION['USER_ACL_LEVEL'];
}
}
$_SESSION['BASE_ACL_LEVEL'] = $this->acl['base'];
$this->session->set('BASE_ACL_LEVEL', $this->acl['base']);
// set the current page acl
// start with base acl
@@ -1425,7 +1458,7 @@ class Login
$data = 'Illegal user for password change: ' . $this->pw_username;
}
// log this password change attempt
$this->writeLog($event, $data, $this->login_error, $this->pw_username);
$this->writeEditLog($event, $data, $this->login_error, $this->pw_username);
}
/**
@@ -1566,7 +1599,7 @@ class Login
$username = $res['username'];
}
} // if euid is set, get username (or try)
$this->writeLog($event, '', $this->login_error, $username);
$this->writeEditLog($event, '', $this->login_error, $username);
} // write log under certain settings
// now close DB connection
// $this->error_msg = $this->_login();
@@ -1722,6 +1755,8 @@ HTML;
}
}
// MARK: LOGGING
/**
* writes detailed data into the edit user log table (keep log what user does)
*
@@ -1731,7 +1766,7 @@ HTML;
* @param string $username login user username
* @return void has no return
*/
private function writeLog(
private function writeEditLog(
string $event,
string $data,
string|int $error = '',
@@ -1749,50 +1784,191 @@ HTML;
'_GET' => $_GET,
'_POST' => $_POST,
'_FILES' => $_FILES,
'error' => $this->login_error
'error' => $this->login_error,
'data' => $data,
];
$data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($_data_binary)));
// SQL querie for log entry
$q = "INSERT INTO edit_log "
. "(username, password, euid, event_date, event, error, data, data_binary, page, "
. "ip, user_agent, referer, script_name, query_string, server_name, http_host, "
. "http_accept, http_accept_charset, http_accept_encoding, session_id, "
. "action, action_id, action_yes, action_flag, action_menu, action_loaded, "
. "action_value, action_error) "
. "VALUES ('" . $this->db->dbEscapeString($username) . "', 'PASSWORD', "
. ($this->euid ? $this->euid : 'NULL') . ", "
. "NOW(), '" . $this->db->dbEscapeString($event) . "', "
. "'" . $this->db->dbEscapeString((string)$error) . "', "
. "'" . $this->db->dbEscapeString($data) . "', '" . $data_binary . "', "
. "'" . $this->page_name . "', ";
foreach (
[
'REMOTE_ADDR', 'HTTP_USER_AGENT', 'HTTP_REFERER', 'SCRIPT_FILENAME',
'QUERY_STRING', 'SERVER_NAME', 'HTTP_HOST', 'HTTP_ACCEPT',
'HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_ENCODING'
] as $server_code
) {
if (array_key_exists($server_code, $_SERVER)) {
$q .= "'" . $this->db->dbEscapeString($_SERVER[$server_code]) . "', ";
} else {
$q .= "NULL, ";
}
$_action_set = [
'action' => $this->action,
'action_id' => $this->username,
'action_flag' => (string)$this->login_error,
'action_value' => (string)$this->permission_okay,
];
$this->writeLog($event, $_data_binary, $_action_set, $error, $username);
}
/**
* writes all action vars plus other info into edit_log table
* this is for public class
*
* phpcs:disable Generic.Files.LineLength
* @param string $event [default=''] any kind of event description,
* @param string|array<mixed> $data [default=''] any kind of data related to that event
* @param array{action?:?string,action_id?:null|string|int,action_sub_id?:null|string|int,action_yes?:null|string|int|bool,action_flag?:?string,action_menu?:?string,action_loaded?:?string,action_value?:?string,action_type?:?string,action_error?:?string} $action_set [default=[]] action set names
* @param string|int $error error id (mostly an int)
* @param string $write_type [default=JSON] write type can be
* JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB
* @param string|null $db_schema [default=null] override target schema
* @return void
* phpcs:enable Generic.Files.LineLength
*/
public function writeLog(
string $event = '',
string|array $data = '',
array $action_set = [],
string|int $error = '',
string $username = '',
string $write_type = 'JSON',
?string $db_schema = null
): void {
$data_binary = '';
$data_write = '';
// check if write type is valid, if not fallback to JSON
if (!in_array(strtoupper($write_type), $this->write_types_available)) {
$this->log->warning('Write type not in allowed array, fallback to JSON', context:[
"write_type" => $write_type,
"write_list" => $this->write_types_available,
]);
$write_type = 'JSON';
}
switch ($write_type) {
case 'BINARY':
case 'BZIP':
$data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($data)));
$data_write = Json::jsonConvertArrayTo([
'type' => 'BZIP',
'message' => 'see bzip compressed data_binary field'
]);
break;
case 'ZLIB':
$data_binary = $this->db->dbEscapeBytea((string)gzcompress(serialize($data)));
$data_write = Json::jsonConvertArrayTo([
'type' => 'ZLIB',
'message' => 'see zlib compressed data_binary field'
]);
break;
case 'STRING':
case 'SERIAL':
$data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([
'type' => 'SERIAL',
'message' => 'see serial string data field'
]));
$data_write = serialize($data);
break;
case 'JSON':
$data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([
'type' => 'JSON',
'message' => 'see json string data field'
]));
// must be converted to array
if (!is_array($data)) {
$data = ["data" => $data];
}
$data_write = Json::jsonConvertArrayTo($data);
break;
default:
$this->log->alert('Invalid type for data compression was set', context:[
"write_type" => $write_type
]);
break;
}
/** @var string $DB_SCHEMA check schema */
$DB_SCHEMA = 'public';
if ($db_schema !== null) {
$DB_SCHEMA = $db_schema;
} elseif (!empty($this->db->dbGetSchema())) {
$DB_SCHEMA = $this->db->dbGetSchema();
}
$q = <<<SQL
INSERT INTO {DB_SCHEMA}.edit_log (
username, euid, ecuid, ecuuid, event_date, event, error, data, data_binary, page,
ip, user_agent, referer, script_name, query_string, server_name, http_host,
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_value, action_type, action_error
) VALUES (
$1, $2, $3, $4, NOW(), $5, $6, $7, $8, $9,
$10, $11, $12, $13, $14, $15, $16,
$17, $18, $19, $20,
$21, $22, $23, $24, $25, $26, $27,
$28, $29, $30
)
SQL;
$this->db->dbExecParams(
str_replace(
['{DB_SCHEMA}'],
[$DB_SCHEMA],
$q
),
[
// row 1
empty($username) ? $this->session->get('USER_NAME') ?? '' : $username,
is_numeric($this->session->get('EUID')) ?
$this->session->get('EUID') : null,
is_string($this->session->get('ECUID')) ?
$this->session->get('ECUID') : null,
!empty($this->session->get('ECUUID')) && Uids::validateUuuidv4($this->session->get('ECUUID')) ?
$this->session->get('ECUUID') : null,
(string)$event,
(string)$error,
$data_write,
$data_binary,
(string)$this->page_name,
// row 2
$_SERVER["REMOTE_ADDR"] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null,
$_SERVER['HTTP_REFERER'] ?? null,
$_SERVER['SCRIPT_FILENAME'] ?? null,
$_SERVER['QUERY_STRING'] ?? null,
$_SERVER['SERVER_NAME'] ?? null,
$_SERVER['HTTP_HOST'] ?? null,
// row 3
$_SERVER['HTTP_ACCEPT'] ?? null,
$_SERVER['HTTP_ACCEPT_CHARSET'] ?? null,
$_SERVER['HTTP_ACCEPT_ENCODING'] ?? null,
$this->session->getSessionId() !== '' ?
$this->session->getSessionId() : null,
// row 4
$action_set['action'] ?? null,
$action_set['action_id'] ?? null,
$action_set['action_sub_id'] ?? null,
$action_set['action_yes'] ?? null,
$action_set['action_flag'] ?? null,
$action_set['action_menu'] ?? null,
$action_set['action_loaded'] ?? null,
$action_set['action_value'] ?? null,
$action_set['action_type'] ?? null,
$action_set['action_error'] ?? null,
],
'NULL'
);
}
/**
* set the write types that are allowed
*
* @return void
*/
private function loginSetEditLogWriteTypeAvailable()
{
// check what edit log data write types are allowed
$this->write_types_available = self::WRITE_TYPES;
if (!function_exists('bzcompress')) {
$this->write_types_available = array_diff($this->write_types_available, ['BINARY', 'BZIP']);
}
if (!function_exists('gzcompress')) {
$this->write_types_available = array_diff($this->write_types_available, ['LZIP']);
}
$q .= "'" . $this->session->getSessionId() . "', ";
$q .= "'" . $this->db->dbEscapeString($this->action) . "', ";
$q .= "'" . $this->db->dbEscapeString($this->username) . "', ";
$q .= "NULL, ";
$q .= "'" . $this->db->dbEscapeString((string)$this->login_error) . "', ";
$q .= "NULL, NULL, ";
$q .= "'" . $this->db->dbEscapeString((string)$this->permission_okay) . "', ";
$q .= "NULL)";
$this->db->dbExec($q, 'NULL');
}
// *************************************************************************
// **** PUBLIC INTERNAL
// *************************************************************************
// MARK: LOGIN CALL
/**
* Main call that needs to be run to actaully check for login
* If this is not called, no login checks are done, unless the class
@@ -1861,7 +2037,10 @@ HTML;
}
}
// if there is none, there is none, saves me POST/GET check
$this->euid = array_key_exists('EUID', $_SESSION) ? (int)$_SESSION['EUID'] : 0;
$this->euid = (int)($this->session->get('EUID') ?? 0);
// TODO: allow load from cuid
// $this->ecuid = (string)($this->session->get('ECUID') ?? '');
// $this->ecuuid = (string)($this->session->get('ECUUID') ?? '');
// get login vars, are so, can't be changed
// prepare
// pass on vars to Object vars
@@ -1942,6 +2121,8 @@ HTML;
$this->loginSetAcl();
}
// MARK: setters/getters
/**
* Returns current set login_html content
*
@@ -2111,6 +2292,8 @@ HTML;
$this->session->sessionDestroy();
// unset euid
$this->euid = null;
$this->ecuid = null;
$this->ecuuid = null;
// then prints the login screen again
$this->permission_okay = false;
}
@@ -2128,11 +2311,12 @@ HTML;
if (empty($this->euid)) {
return $this->permission_okay;
}
// euid must match ecuid and ecuuid
// bail for previous wrong page match, eg if method is called twice
if ($this->login_error == 103) {
return $this->permission_okay;
}
$q = "SELECT ep.filename, "
$q = "SELECT ep.filename, eu.cuid, eu.cuuid, "
// base lock flags
. "eu.deleted, eu.enabled, eu.locked, "
// date based lock
@@ -2198,6 +2382,13 @@ HTML;
} else {
$this->login_error = 103;
}
// set ECUID
$this->ecuid = (string)$res['cuid'];
$this->ecuuid = (string)$res['cuuid'];
$this->session->setMany([
'ECUID' => $this->ecuid,
'ECUUID' => $this->ecuuid,
]);
// if called from public, so we can check if the permissions are ok
return $this->permission_okay;
}
@@ -2343,13 +2534,12 @@ HTML;
{
if (
$edit_access_id !== null &&
isset($_SESSION['UNIT']) &&
is_array($_SESSION['UNIT']) &&
!array_key_exists($edit_access_id, $_SESSION['UNIT'])
is_array($this->session->get('UNIT')) &&
!array_key_exists($edit_access_id, $this->session->get('UNIT'))
) {
$edit_access_id = null;
if (is_numeric($_SESSION['UNIT_DEFAULT'])) {
$edit_access_id = (int)$_SESSION['UNIT_DEFAULT'];
if (is_numeric($this->session->get('UNIT_DEFAULT'))) {
$edit_access_id = (int)$this->session->get('UNIT_DEFAULT');
}
}
return $edit_access_id;
@@ -2480,7 +2670,7 @@ HTML;
*/
public function loginGetHeaderColor(): ?string
{
return $_SESSION['HEADER_COLOR'] ?? null;
return $this->session->get('HEADER_COLOR');
}
/**
@@ -2491,7 +2681,7 @@ HTML;
public function loginGetPages(): array
{
return $_SESSION['PAGES'] ?? [];
return $this->session->get('PAGES');
}
/**
@@ -2503,6 +2693,26 @@ HTML;
{
return (string)$this->euid;
}
/**
* Get the current set ECUID (edit user cuid)
*
* @return string ECUID as string
*/
public function loginGetEcuid(): string
{
return (string)$this->ecuid;
}
/**
* Get the current set ECUUID (edit user cuuid)
*
* @return string ECUUID as string
*/
public function loginGetEcuuid(): string
{
return (string)$this->ecuuid;
}
}
// __END__

View File

@@ -31,6 +31,7 @@ declare(strict_types=1);
namespace CoreLibs\Admin;
use CoreLibs\Create\Uids;
use CoreLibs\Convert\Json;
class Backend
@@ -258,6 +259,27 @@ class Backend
}
}
/**
* return all the action data, if not set, sets entry to null
*
* @return array{action:?string,action_id:null|string|int,action_sub_id:null|string|int,action_yes:null|string|int|bool,action_flag:?string,action_menu:?string,action_loaded:?string,action_value:?string,action_type:?string,action_error:?string}
*/
public function adbGetActionSet(): array
{
return [
'action' => $this->action ?? null,
'action_id' => $this->action_id ?? null,
'action_sub_id' => $this->action_sub_id ?? null,
'action_yes' => $this->action_yes ?? null,
'action_flag' => $this->action_flag ?? null,
'action_menu' => $this->action_menu ?? null,
'action_loaded' => $this->action_loaded ?? null,
'action_value' => $this->action_value ?? null,
'action_type' => $this->action_type ?? null,
'action_error' => $this->action_error ?? null,
];
}
/**
* writes all action vars plus other info into edit_log table
*
@@ -267,6 +289,7 @@ class Backend
* JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB
* @param string|null $db_schema [default=null] override target schema
* @return void
* @deprecated Use $login->writeLog() and set action_set from ->adbGetActionSet()
*/
public function adbEditLog(
string $event = '',
@@ -335,17 +358,17 @@ class Backend
}
$q = <<<SQL
INSERT INTO {DB_SCHEMA}.edit_log (
euid, event_date, event, data, data_binary, page,
username, euid, ecuid, ecuuid, event_date, event, error, data, data_binary, page,
ip, user_agent, referer, script_name, query_string, server_name, http_host,
http_accept, http_accept_charset, http_accept_encoding, session_id,
action, action_id, action_yes, action_flag, action_menu, action_loaded,
action, action_id, action_sub_id, action_yes, action_flag, action_menu, action_loaded,
action_value, action_type, action_error
) VALUES (
$1, NOW(), $2, $3, $4, $5,
$6, $7, $8, $9, $10, $11, $12,
$13, $14, $15, $16,
$17, $18, $19, $20, $21, $22,
$23, $24, $25
$1, $2, $3, $4, NOW(), $5, $6, $7, $8, $9,
$10, $11, $12, $13, $14, $15, $16,
$17, $18, $19, $20,
$21, $22, $23, $24, $25, $26, $27,
$28, $29, $30
)
SQL;
$this->db->dbExecParams(
@@ -356,9 +379,15 @@ class Backend
),
[
// row 1
isset($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ?
$_SESSION['EUID'] : null,
'',
is_numeric($this->session->get('EUID')) ?
$this->session->get('EUID') : null,
is_string($this->session->get('ECUID')) ?
$this->session->get('ECUID') : null,
!empty($this->session->get('ECUUID')) && Uids::validateUuuidv4($this->session->get('ECUID')) ?
$this->session->get('ECUID') : null,
(string)$event,
'',
$data_write,
$data_binary,
(string)$this->page_name,
@@ -374,11 +403,12 @@ class Backend
$_SERVER['HTTP_ACCEPT'] ?? '',
$_SERVER['HTTP_ACCEPT_CHARSET'] ?? '',
$_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',
$this->session->getSessionId() !== false ?
$this->session->getSessionId() !== '' ?
$this->session->getSessionId() : null,
// row 4
$this->action ?? '',
$this->action_id ?? '',
$this->action_sub_id ?? '',
$this->action_yes ?? '',
$this->action_flag ?? '',
$this->action_menu ?? '',
@@ -438,7 +468,7 @@ class Backend
}
// get the session pages array
$PAGES = $_SESSION['PAGES'] ?? null;
$PAGES = $this->session->get('PAGES');
if (!isset($PAGES) || !is_array($PAGES)) {
$PAGES = [];
}

View File

@@ -15,19 +15,27 @@ namespace CoreLibs\Create;
class Session
{
/** @var string current session name */
private string $session_name = '';
/** @var string current session id */
private string $session_id = '';
/** @var bool flag auto write close */
private bool $auto_write_close = false;
/**
* init a session, if array is empty or array does not have session_name set
* then no auto init is run
*
* @param string $session_name if set and not empty, will start session
*/
public function __construct(string $session_name = '')
public function __construct(string $session_name, bool $auto_write_close = false)
{
if (!empty($session_name)) {
$this->startSession($session_name);
}
$this->initSession($session_name);
$this->auto_write_close = $auto_write_close;
}
// MARK: private methods
/**
* Start session
* startSession should be called for complete check
@@ -36,36 +44,32 @@ class Session
*
* @return void
*/
protected function startSessionCall(): void
private function startSessionCall(): void
{
session_start();
}
/**
* check if we are in CLI, we set this, so we can mock this
* Not this is just a wrapper for the static System::checkCLI call
* get current set session id or false if none started
*
* @return bool True if we are in a CLI enviroment, or false for everything else
* @return string|false
*/
public function checkCliStatus(): bool
public function getSessionIdCall(): string|false
{
return \CoreLibs\Get\System::checkCLI();
return session_id();
}
/**
* Set session name call. If not valid session name, will return false
* automatically closes a session if the auto write close flag is set
*
* @param string $session_name A valid string for session name
* @return bool True if session name is valid,
* False if not
* @return bool
*/
public function setSessionName(string $session_name): bool
private function closeSessionCall(): bool
{
if (!$this->checkValidSessionName($session_name)) {
return false;
if ($this->auto_write_close) {
return $this->writeClose();
}
session_name($session_name);
return true;
return false;
}
/**
@@ -94,15 +98,34 @@ class Session
}
/**
* start session with given session name if set
* validate _SESSION key, must be valid variable
*
* @param int|float|string $key
* @return true
*/
private function checkValidSessionEntryKey(int|float|string $key): true
{
if (!is_string($key) || is_numeric($key)) {
throw new \UnexpectedValueException(
'[SESSION] Given key for _SESSION is not a valid value for a varaible: ' . $key,
1
);
}
return true;
}
// MARK: init session (on class start)
/**
* stinitart session with given session name if set
* aborts on command line or if sessions are not enabled
* also aborts if session cannot be started
* On sucess returns the session id
*
* @param string|null $session_name
* @return string|bool
* @param string $session_name
* @return void
*/
public function startSession(?string $session_name = null): string|bool
private function initSession(string $session_name): void
{
// we can't start sessions on command line
if ($this->checkCliStatus()) {
@@ -115,39 +138,85 @@ class Session
// session_status
// initial the session if there is no session running already
if (!$this->checkActiveSession()) {
// if session name is emtpy, check if there is a global set
// this is a deprecated fallback
$session_name = $session_name ?? $GLOBALS['SET_SESSION_NAME'] ?? '';
// DEPRECTED: constant SET_SESSION_NAME is no longer used
// if set, set special session name
if (!empty($session_name)) {
// invalid session name, abort
if (!$this->checkValidSessionName($session_name)) {
throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $session_name, 3);
}
$this->setSessionName($session_name);
// invalid session name, abort
if (!$this->checkValidSessionName($session_name)) {
throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $this->session_name, 3);
}
// set session name
$this->session_name = $session_name;
session_name($this->session_name);
// start session
$this->startSessionCall();
// if we faild to start the session
if (!$this->checkActiveSession()) {
throw new \RuntimeException('[SESSION] Failed to activate session', 5);
}
} elseif ($session_name != $this->getSessionName()) {
throw new \UnexpectedValueException(
'[SESSION] Another session exists with a different name: ' . $this->getSessionName(),
4
);
}
// if we still have no active session
// check session id
if (false === ($session_id = $this->getSessionIdCall())) {
throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 6);
}
// set session id
$this->session_id = $session_id;
// if flagged auto close, write close session
if ($this->auto_write_close) {
$this->writeClose();
}
}
// MARK: public set/get status
/**
* start session, will only run after initSession
*
* @return bool True if started, False if alrady running
*/
public function restartSession(): bool
{
if (!$this->checkActiveSession()) {
throw new \RuntimeException('[SESSION] Failed to activate session', 4);
if (empty($this->session_name)) {
throw new \RuntimeException('[SESSION] Cannot restart session without a session name', 1);
}
$this->startSessionCall();
return true;
}
if (false === ($session_id = $this->getSessionId())) {
throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 5);
}
return $session_id;
return false;
}
/**
* get current set session id or false if none started
* current set session id
*
* @return string|bool
* @return string
*/
public function getSessionId(): string|bool
public function getSessionId(): string
{
return session_id();
return $this->session_id;
}
/**
* set the auto write close flag
*
* @param bool $flag
* @return void
*/
public function setAutoWriteClose(bool $flag): void
{
$this->auto_write_close = $flag;
}
/**
* return the auto write close flag
*
* @return bool
*/
public function checkAutoWriteClose(): bool
{
return $this->auto_write_close;
}
/**
@@ -175,6 +244,34 @@ class Session
}
}
/**
* check if we are in CLI, we set this, so we can mock this
* Not this is just a wrapper for the static System::checkCLI call
*
* @return bool True if we are in a CLI enviroment, or false for everything else
*/
public function checkCliStatus(): bool
{
return \CoreLibs\Get\System::checkCLI();
}
/**
* get session status
* PHP_SESSION_DISABLED if sessions are disabled.
* PHP_SESSION_NONE if sessions are enabled, but none exists.
* PHP_SESSION_ACTIVE if sessions are enabled, and one exists.
*
* https://www.php.net/manual/en/function.session-status.php
*
* @return int See possible return int values above
*/
public function getSessionStatus(): int
{
return session_status();
}
// MARK: write close session
/**
* unlock the session file, so concurrent AJAX requests can be done
* NOTE: after this has been called, no changes in _SESSION will be stored
@@ -188,17 +285,24 @@ class Session
return session_write_close();
}
// MARK: session close and clean up
/**
* Proper destroy a session
* - unset the _SESSION array
* - unset cookie if cookie on and we have not strict mode
* - unset session_name and session_id internal vars
* - destroy session
*
* @return bool
* @return bool True on successful session destroy
*/
public function sessionDestroy(): bool
{
$_SESSION = [];
// abort to false if not unsetable
if (!session_unset()) {
return false;
}
$this->clear();
if (
ini_get('session.use_cookies') &&
!ini_get('session.use_strict_mode')
@@ -218,68 +322,92 @@ class Session
$params['httponly']
);
}
// unset internal vars
$this->session_name = '';
$this->session_id = '';
return session_destroy();
}
/**
* get session status
* PHP_SESSION_DISABLED if sessions are disabled.
* PHP_SESSION_NONE if sessions are enabled, but none exists.
* PHP_SESSION_ACTIVE if sessions are enabled, and one exists.
*
* https://www.php.net/manual/en/function.session-status.php
*
* @return int See possible return int values above
*/
public function getSessionStatus(): int
{
return session_status();
}
// _SESSION set/unset methods
// MARK: _SESSION set/unset methods
/**
* unset all _SESSION entries
*
* @return void
*/
public function unsetAllS(): void
public function clear(): void
{
foreach (array_keys($_SESSION ?? []) as $name) {
unset($_SESSION[$name]);
$this->restartSession();
if (!session_unset()) {
throw new \RuntimeException('[SESSION] Cannot unset session vars', 1);
}
if (!empty($_SESSION)) {
$_SESSION = [];
}
$this->closeSessionCall();
}
/**
* set _SESSION entry 'name' with any value
*
* @param string|int $name array name in _SESSION
* @param mixed $value value to set (can be anything)
* @param string $name array name in _SESSION
* @param mixed $value value to set (can be anything)
* @return void
*/
public function setS(string|int $name, mixed $value): void
public function set(string $name, mixed $value): void
{
$this->checkValidSessionEntryKey($name);
$this->restartSession();
$_SESSION[$name] = $value;
$this->closeSessionCall();
}
/**
* set many session entries in one set
*
* @param array<string,mixed> $set key is the key in the _SESSION, value is any data to set
* @return void
*/
public function setMany(array $set): void
{
$this->restartSession();
// skip any that are not valid
foreach ($set as $key => $value) {
$this->checkValidSessionEntryKey($key);
$_SESSION[$key] = $value;
}
$this->closeSessionCall();
}
/**
* get _SESSION 'name' entry or empty string if not set
*
* @param string|int $name value key to get from _SESSION
* @return mixed value stored in _SESSION
* @param string $name value key to get from _SESSION
* @return mixed value stored in _SESSION, if not found set to null
*/
public function getS(string|int $name): mixed
public function get(string $name): mixed
{
return $_SESSION[$name] ?? '';
return $_SESSION[$name] ?? null;
}
/**
* get multiple session entries
*
* @param array<string> $set
* @return array<string,mixed>
*/
public function getMany(array $set): array
{
return array_intersect_key($_SESSION, array_flip($set));
}
/**
* Check if a name is set in the _SESSION array
*
* @param string|int $name Name to check for
* @return bool True for set, False fornot set
* @param string $name Name to check for
* @return bool True for set, False fornot set
*/
public function issetS(string|int $name): bool
public function isset(string $name): bool
{
return isset($_SESSION[$name]);
}
@@ -287,67 +415,35 @@ class Session
/**
* unset one _SESSION entry 'name' if exists
*
* @param string|int $name _SESSION key name to remove
* @param string $name _SESSION key name to remove
* @return void
*/
public function unsetS(string|int $name): void
public function unset(string $name): void
{
if (isset($_SESSION[$name])) {
unset($_SESSION[$name]);
if (!isset($_SESSION[$name])) {
return;
}
$this->restartSession();
unset($_SESSION[$name]);
$this->closeSessionCall();
}
// set/get below
// ->var = value;
/**
* Undocumented function
* reset many session entry
*
* @param string|int $name
* @param mixed $value
* @param array<string> $set list of session keys to reset
* @return void
*/
public function __set(string|int $name, mixed $value): void
public function unsetMany(array $set): void
{
$_SESSION[$name] = $value;
}
/**
* Undocumented function
*
* @param string|int $name
* @return mixed If name is not found, it will return null
*/
public function __get(string|int $name): mixed
{
if (isset($_SESSION[$name])) {
return $_SESSION[$name];
}
return null;
}
/**
* Undocumented function
*
* @param string|int $name
* @return bool
*/
public function __isset(string|int $name): bool
{
return isset($_SESSION[$name]);
}
/**
* Undocumented function
*
* @param string|int $name
* @return void
*/
public function __unset(string|int $name): void
{
if (isset($_SESSION[$name])) {
unset($_SESSION[$name]);
$this->restartSession();
foreach ($set as $key) {
if (!isset($_SESSION[$key])) {
continue;
}
unset($_SESSION[$key]);
}
$this->closeSessionCall();
}
}

View File

@@ -14,10 +14,24 @@ namespace CoreLibs\DB\Support;
class ConvertPlaceholder
{
/** @var string split regex */
private const PATTERN_QUERY_SPLIT = '[(<>=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-';
// NOTE for missing: range */+ are not iplemented in the regex below, but - is for now
// NOTE some combinations are allowed, but the query will fail before this
/** @var string split regex, entries before $ group */
private const PATTERN_QUERY_SPLIT =
'\?\?|' // UNKNOWN: double ??, is this to avoid something?
. '[\(,]|' // for ',' and '(' mostly in INSERT or ANY()
. '[<>=]|' // general set for <, >, = in any query with any combination
. '\^@|' // text search for start from text with ^@
. '\|\||' // concats two elements
. '&&|' // array overlap
. '\-\|\-|' // range overlap for array
. '[^-]-{1}|' // single -, used in JSON too
. '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-'; //JSON searches, Array searchs, etc
/** @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 */
private const PATTERN_IGNORE =
// digit -> ignore
@@ -34,6 +48,7 @@ class ConvertPlaceholder
/** @var string replace regex for named (:...) entries */
public const REGEX_REPLACE_NAMED = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '('
. self::PATTERN_IGNORE
. self::PATTERN_NAMED
@@ -42,6 +57,7 @@ class ConvertPlaceholder
/** @var string replace regex for question mark (?) entries */
public const REGEX_REPLACE_QUESTION_MARK = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '('
. self::PATTERN_IGNORE
. self::PATTERN_QUESTION_MARK
@@ -50,6 +66,7 @@ class ConvertPlaceholder
/** @var string replace regex for numbered ($n) entries */
public const REGEX_REPLACE_NUMBERED = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '('
. self::PATTERN_IGNORE
. self::PATTERN_NUMBERED
@@ -60,6 +77,7 @@ class ConvertPlaceholder
// prefix string part, must match towards
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15]
. self::PATTERN_ELEMENT
. self::PATTERN_COMMENT
// match for replace part
. '(?:'
// ignore parts

View File

@@ -2,7 +2,7 @@
/*
* sets a form token in the _SESSION variable
* session must be started for this to work
* session must be started and running for this to work
*/
declare(strict_types=1);

View File

@@ -49,7 +49,11 @@ class SymmetricEncryption
*/
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);
}
return self::$instance;
@@ -130,7 +134,7 @@ class SymmetricEncryption
*/
private function encryptData(string $message, ?string $key): string
{
if (empty($this->key) || $key === null) {
if ($key === null) {
throw new \UnexpectedValueException('Key not set');
}
$key = $this->createKey($key);

View File

@@ -243,6 +243,8 @@ final class CoreLibsACLLoginTest extends TestCase
[],
[
'EUID' => 1,
'ECUID' => 'abc',
'ECUUID' => '1233456-1234-1234-1234-123456789012',
],
2,
[],
@@ -260,6 +262,8 @@ final class CoreLibsACLLoginTest extends TestCase
[],
[
'EUID' => 1,
'ECUID' => 'abc',
'ECUUID' => '1233456-1234-1234-1234-123456789012',
'USER_NAME' => '',
'GROUP_NAME' => '',
'ADMIN' => 1,
@@ -1085,9 +1089,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy']
['getSessionId', 'checkActiveSession', 'sessionDestroy']
);
$session_mock->method('startSession')->willReturn('ACLLOGINTEST12');
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST12');
$session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () {
@@ -1788,9 +1792,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy']
['getSessionId', 'checkActiveSession', 'sessionDestroy']
);
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () {
@@ -1902,9 +1906,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy']
['getSessionId', 'checkActiveSession', 'sessionDestroy']
);
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () {
@@ -1990,9 +1994,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy']
['getSessionId', 'checkActiveSession', 'sessionDestroy']
);
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () {
@@ -2086,9 +2090,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy']
['getSessionId', 'checkActiveSession', 'sessionDestroy']
);
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () {

View File

@@ -5,15 +5,15 @@ CREATE FUNCTION random_string(randomLength int)
RETURNS text AS
$$
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random() * 62)::int + 1,
1
)
FROM generate_series(1, randomLength) AS gs(x)
),
''
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random() * 62)::int + 1,
1
)
FROM generate_series(1, randomLength) AS gs(x)
),
''
)
$$
LANGUAGE SQL
@@ -27,15 +27,16 @@ CREATE OR REPLACE FUNCTION set_edit_generic()
RETURNS TRIGGER AS
$$
DECLARE
random_length INT = 12; -- that should be long enough
random_length INT = 12; -- that should be long enough
BEGIN
IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now';
NEW.cuid := random_string(random_length);
ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now';
END IF;
RETURN NEW;
IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now';
NEW.cuid := random_string(random_length);
NEW.cuuid := gen_random_uuid();
ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now';
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
@@ -46,29 +47,29 @@ LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS
$$
DECLARE
myrec RECORD;
v_uid VARCHAR;
myrec RECORD;
v_uid VARCHAR;
BEGIN
-- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN
-- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF;
END IF;
RETURN NEW;
-- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN
-- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
LANGUAGE 'plpgsql';
-- END: function/edit_access_set_uid.sql
-- START: function/edit_group_set_uid.sql
-- add uid add for edit_group table
@@ -76,29 +77,29 @@ $$
CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS
$$
DECLARE
myrec RECORD;
v_uid VARCHAR;
myrec RECORD;
v_uid VARCHAR;
BEGIN
-- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN
-- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF;
END IF;
RETURN NEW;
-- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN
-- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
LANGUAGE 'plpgsql';
-- END: function/edit_group_set_uid.sql
-- START: function/edit_log_partition_insert.sql
-- AUTHOR: Clemens Schwaighofer
@@ -112,142 +113,142 @@ CREATE OR REPLACE FUNCTION edit_log_insert_trigger ()
RETURNS TRIGGER AS
$$
DECLARE
start_date DATE := '2010-01-01';
end_date DATE;
timeformat TEXT := 'YYYY';
selector TEXT := 'year';
base_table TEXT := 'edit_log';
_interval INTERVAL := '1 ' || selector;
_interval_next INTERVAL := '2 ' || selector;
table_name TEXT;
-- compare date column
compare_date DATE := NEW.event_date;
compare_date_name TEXT := 'event_date';
-- the create commands
command_create_table TEXT := 'CREATE TABLE IF NOT EXISTS {TABLE_NAME} (CHECK({COMPARE_DATE_NAME} >= {START_DATE} AND {COMPARE_DATE_NAME} < {END_DATE})) INHERITS ({BASE_NAME})';
command_create_primary_key TEXT := 'ALTER TABLE {TABLE_NAME} ADD PRIMARY KEY ({BASE_TABLE}_id)';
command_create_foreign_key_1 TEXT := 'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT {TABLE_NAME}_euid_fkey FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL';
command_create_trigger_1 TEXT = 'CREATE TRIGGER trg_{TABLE_NAME} BEFORE INSERT OR UPDATE ON {TABLE_NAME} FOR EACH ROW EXECUTE PROCEDURE set_edit_generic()';
start_date DATE := '2010-01-01';
end_date DATE;
timeformat TEXT := 'YYYY';
selector TEXT := 'year';
base_table TEXT := 'edit_log';
_interval INTERVAL := '1 ' || selector;
_interval_next INTERVAL := '2 ' || selector;
table_name TEXT;
-- compare date column
compare_date DATE := NEW.event_date;
compare_date_name TEXT := 'event_date';
-- the create commands
command_create_table TEXT := 'CREATE TABLE IF NOT EXISTS {TABLE_NAME} (CHECK({COMPARE_DATE_NAME} >= {START_DATE} AND {COMPARE_DATE_NAME} < {END_DATE})) INHERITS ({BASE_NAME})';
command_create_primary_key TEXT := 'ALTER TABLE {TABLE_NAME} ADD PRIMARY KEY ({BASE_TABLE}_id)';
command_create_foreign_key_1 TEXT := 'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT {TABLE_NAME}_euid_fkey FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL';
command_create_trigger_1 TEXT = 'CREATE TRIGGER trg_{TABLE_NAME} BEFORE INSERT OR UPDATE ON {TABLE_NAME} FOR EACH ROW EXECUTE PROCEDURE set_edit_generic()';
BEGIN
-- we are in valid start time area
IF (NEW.event_date >= start_date) THEN
-- current table name
table_name := base_table || '_' || to_char(NEW.event_date, timeformat);
BEGIN
EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW;
-- if insert failed because of missing table, create new below
EXCEPTION
WHEN undefined_table THEN
-- another block, so in case the creation fails here too
BEGIN
-- create new table here + all indexes
start_date := date_trunc(selector, NEW.event_date);
end_date := date_trunc(selector, NEW.event_date + _interval);
-- creat table
EXECUTE format(REPLACE( -- end date
REPLACE( -- start date
REPLACE( -- compare date name
REPLACE( -- base name (inherit)
REPLACE( -- table name
command_create_table,
'{TABLE_NAME}',
table_name
),
'{BASE_NAME}',
base_table
),
'{COMPARE_DATE_NAME}',
compare_date_name
),
'{START_DATE}',
quote_literal(start_date)
),
'{END_DATE}',
quote_literal(end_date)
));
-- create all indexes and triggers
EXECUTE format(REPLACE(
REPLACE(
command_create_primary_key,
'{TABLE_NAME}',
table_name
),
'{BASE_TABLE}',
base_table
));
-- FK constraints
EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name));
-- generic trigger
EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name));
-- we are in valid start time area
IF (NEW.event_date >= start_date) THEN
-- current table name
table_name := base_table || '_' || to_char(NEW.event_date, timeformat);
BEGIN
EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW;
-- if insert failed because of missing table, create new below
EXCEPTION
WHEN undefined_table THEN
-- another block, so in case the creation fails here too
BEGIN
-- create new table here + all indexes
start_date := date_trunc(selector, NEW.event_date);
end_date := date_trunc(selector, NEW.event_date + _interval);
-- creat table
EXECUTE format(REPLACE( -- end date
REPLACE( -- start date
REPLACE( -- compare date name
REPLACE( -- base name (inherit)
REPLACE( -- table name
command_create_table,
'{TABLE_NAME}',
table_name
),
'{BASE_NAME}',
base_table
),
'{COMPARE_DATE_NAME}',
compare_date_name
),
'{START_DATE}',
quote_literal(start_date)
),
'{END_DATE}',
quote_literal(end_date)
));
-- create all indexes and triggers
EXECUTE format(REPLACE(
REPLACE(
command_create_primary_key,
'{TABLE_NAME}',
table_name
),
'{BASE_TABLE}',
base_table
));
-- FK constraints
EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name));
-- generic trigger
EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name));
-- insert try again
EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW;
EXCEPTION
WHEN OTHERS THEN
-- if this faled, throw it into the overflow table (so we don't loose anything)
INSERT INTO edit_log_overflow VALUES (NEW.*);
END;
-- other errors, insert into overlow
WHEN OTHERS THEN
-- if this faled, throw it into the overflow table (so we don't loose anything)
INSERT INTO edit_log_overflow VALUES (NEW.*);
END;
-- main insert run done, check if we have to create next months table
BEGIN
-- check if next month table exists
table_name := base_table || '_' || to_char((SELECT NEW.event_date + _interval)::DATE, timeformat);
-- RAISE NOTICE 'SEARCH NEXT: %', table_name;
IF (SELECT to_regclass(table_name)) IS NULL THEN
-- move inner interval same
start_date := date_trunc(selector, NEW.event_date + _interval);
end_date := date_trunc(selector, NEW.event_date + _interval_next);
-- RAISE NOTICE 'CREATE NEXT: %', table_name;
-- create table
EXECUTE format(REPLACE( -- end date
REPLACE( -- start date
REPLACE( -- compare date name
REPLACE( -- base name (inherit)
REPLACE( -- table name
command_create_table,
'{TABLE_NAME}',
table_name
),
'{BASE_NAME}',
base_table
),
'{COMPARE_DATE_NAME}',
compare_date_name
),
'{START_DATE}',
quote_literal(start_date)
),
'{END_DATE}',
quote_literal(end_date)
));
-- create all indexes and triggers
EXECUTE format(REPLACE(
REPLACE(
command_create_primary_key,
'{TABLE_NAME}',
table_name
),
'{BASE_TABLE}',
base_table
));
-- FK constraints
EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name));
-- generic trigger
EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name));
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Failed to create next table: %', table_name;
END;
ELSE
-- if outside valid date, insert into overflow
INSERT INTO edit_log_overflow VALUES (NEW.*);
END IF;
RETURN NULL;
-- insert try again
EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW;
EXCEPTION
WHEN OTHERS THEN
-- if this faled, throw it into the overflow table (so we don't loose anything)
INSERT INTO edit_log_overflow VALUES (NEW.*);
END;
-- other errors, insert into overlow
WHEN OTHERS THEN
-- if this faled, throw it into the overflow table (so we don't loose anything)
INSERT INTO edit_log_overflow VALUES (NEW.*);
END;
-- main insert run done, check if we have to create next months table
BEGIN
-- check if next month table exists
table_name := base_table || '_' || to_char((SELECT NEW.event_date + _interval)::DATE, timeformat);
-- RAISE NOTICE 'SEARCH NEXT: %', table_name;
IF (SELECT to_regclass(table_name)) IS NULL THEN
-- move inner interval same
start_date := date_trunc(selector, NEW.event_date + _interval);
end_date := date_trunc(selector, NEW.event_date + _interval_next);
-- RAISE NOTICE 'CREATE NEXT: %', table_name;
-- create table
EXECUTE format(REPLACE( -- end date
REPLACE( -- start date
REPLACE( -- compare date name
REPLACE( -- base name (inherit)
REPLACE( -- table name
command_create_table,
'{TABLE_NAME}',
table_name
),
'{BASE_NAME}',
base_table
),
'{COMPARE_DATE_NAME}',
compare_date_name
),
'{START_DATE}',
quote_literal(start_date)
),
'{END_DATE}',
quote_literal(end_date)
));
-- create all indexes and triggers
EXECUTE format(REPLACE(
REPLACE(
command_create_primary_key,
'{TABLE_NAME}',
table_name
),
'{BASE_TABLE}',
base_table
));
-- FK constraints
EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name));
-- generic trigger
EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name));
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Failed to create next table: %', table_name;
END;
ELSE
-- if outside valid date, insert into overflow
INSERT INTO edit_log_overflow VALUES (NEW.*);
END IF;
RETURN NULL;
END
$$
LANGUAGE 'plpgsql';
@@ -260,22 +261,22 @@ CREATE OR REPLACE FUNCTION set_login_user_id_set_date()
RETURNS TRIGGER AS
$$
BEGIN
-- if new is not null/empty
-- and old one is null or old one different new one
-- set NOW()
-- if new one is NULL
-- set NULL
IF
NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND
(OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id)
THEN
NEW.login_user_id_set_date = NOW();
NEW.login_user_id_last_revalidate = NOW();
ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN
NEW.login_user_id_set_date = NULL;
NEW.login_user_id_last_revalidate = NULL;
END IF;
RETURN NEW;
-- if new is not null/empty
-- and old one is null or old one different new one
-- set NOW()
-- if new one is NULL
-- set NULL
IF
NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND
(OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id)
THEN
NEW.login_user_id_set_date = NOW();
NEW.login_user_id_last_revalidate = NOW();
ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN
NEW.login_user_id_set_date = NULL;
NEW.login_user_id_last_revalidate = NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
@@ -290,8 +291,8 @@ LANGUAGE 'plpgsql';
-- DROP TABLE temp_files;
CREATE TABLE temp_files (
filename VARCHAR,
folder VARCHAR
filename VARCHAR,
folder VARCHAR
);
-- END: table/edit_temp_files.sql
-- START: table/edit_generic.sql
@@ -304,9 +305,10 @@ CREATE TABLE temp_files (
-- DROP TABLE edit_generic;
CREATE TABLE edit_generic (
cuid VARCHAR,
date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(),
date_updated TIMESTAMP WITHOUT TIME ZONE
cuid VARCHAR,
cuuid UUID,
date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(),
date_updated TIMESTAMP WITHOUT TIME ZONE
);
-- END: table/edit_generic.sql
-- START: table/edit_visible_group.sql
@@ -319,9 +321,9 @@ CREATE TABLE edit_generic (
-- DROP TABLE edit_visible_group;
CREATE TABLE edit_visible_group (
edit_visible_group_id SERIAL PRIMARY KEY,
name VARCHAR,
flag VARCHAR
edit_visible_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR,
flag VARCHAR
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_visible_group.sql
-- START: table/edit_menu_group.sql
@@ -334,10 +336,10 @@ CREATE TABLE edit_visible_group (
-- DROP TABLE edit_menu_group;
CREATE TABLE edit_menu_group (
edit_menu_group_id SERIAL PRIMARY KEY,
name VARCHAR,
flag VARCHAR,
order_number INT NOT NULL
edit_menu_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR,
flag VARCHAR,
order_number INT NOT NULL
) INHERITS (edit_generic) WITHOUT OIDS;
@@ -352,18 +354,18 @@ CREATE TABLE edit_menu_group (
-- DROP TABLE edit_page;
CREATE TABLE edit_page (
edit_page_id SERIAL PRIMARY KEY,
content_alias_edit_page_id INT, -- alias for page content, if the page content is defined on a different page, ege for ajax backend pages
FOREIGN KEY (content_alias_edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE,
filename VARCHAR,
name VARCHAR UNIQUE,
order_number INT NOT NULL,
online SMALLINT NOT NULL DEFAULT 0,
menu SMALLINT NOT NULL DEFAULT 0,
popup SMALLINT NOT NULL DEFAULT 0,
popup_x SMALLINT,
popup_y SMALLINT,
hostname VARCHAR
edit_page_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
content_alias_edit_page_id INT, -- alias for page content, if the page content is defined on a different page, ege for ajax backend pages
FOREIGN KEY (content_alias_edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE,
filename VARCHAR,
name VARCHAR UNIQUE,
order_number INT NOT NULL,
online SMALLINT NOT NULL DEFAULT 0,
menu SMALLINT NOT NULL DEFAULT 0,
popup SMALLINT NOT NULL DEFAULT 0,
popup_x SMALLINT,
popup_y SMALLINT,
hostname VARCHAR
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_page.sql
-- START: table/edit_query_string.sql
@@ -376,13 +378,13 @@ CREATE TABLE edit_page (
-- DROP TABLE edit_query_string;
CREATE TABLE edit_query_string (
edit_query_string_id SERIAL PRIMARY KEY,
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0,
name VARCHAR,
value VARCHAR,
dynamic SMALLINT NOT NULL DEFAULT 0
edit_query_string_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0,
name VARCHAR,
value VARCHAR,
dynamic SMALLINT NOT NULL DEFAULT 0
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_query_string.sql
-- START: table/edit_page_visible_group.sql
@@ -395,10 +397,10 @@ CREATE TABLE edit_query_string (
-- DROP TABLE edit_page_visible_group;
CREATE TABLE edit_page_visible_group (
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_visible_group_id INT NOT NULL,
FOREIGN KEY (edit_visible_group_id) REFERENCES edit_visible_group (edit_visible_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_visible_group_id INT NOT NULL,
FOREIGN KEY (edit_visible_group_id) REFERENCES edit_visible_group (edit_visible_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE
);
-- END: table/edit_page_visible_group.sql
-- START: table/edit_page_menu_group.sql
@@ -411,10 +413,10 @@ CREATE TABLE edit_page_visible_group (
-- DROP TABLE edit_page_menu_group;
CREATE TABLE edit_page_menu_group (
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_menu_group_id INT NOT NULL,
FOREIGN KEY (edit_menu_group_id) REFERENCES edit_menu_group (edit_menu_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_menu_group_id INT NOT NULL,
FOREIGN KEY (edit_menu_group_id) REFERENCES edit_menu_group (edit_menu_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE
);
-- END: table/edit_page_menu_group.sql
-- START: table/edit_access_right.sql
@@ -428,11 +430,11 @@ CREATE TABLE edit_page_menu_group (
-- DROP TABLE edit_access_right;
CREATE TABLE edit_access_right (
edit_access_right_id SERIAL PRIMARY KEY,
name VARCHAR,
level SMALLINT,
type VARCHAR,
UNIQUE (level,type)
edit_access_right_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR,
level SMALLINT,
type VARCHAR,
UNIQUE (level,type)
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_access_right.sql
-- START: table/edit_scheme.sql
@@ -445,12 +447,12 @@ CREATE TABLE edit_access_right (
-- DROP TABLE edit_scheme;
CREATE TABLE edit_scheme (
edit_scheme_id SERIAL PRIMARY KEY,
enabled SMALLINT NOT NULL DEFAULT 0,
name VARCHAR,
header_color VARCHAR,
css_file VARCHAR,
template VARCHAR
edit_scheme_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
enabled SMALLINT NOT NULL DEFAULT 0,
name VARCHAR,
header_color VARCHAR,
css_file VARCHAR,
template VARCHAR
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_scheme.sql
-- START: table/edit_language.sql
@@ -464,13 +466,13 @@ CREATE TABLE edit_scheme (
-- DROP TABLE edit_language;
CREATE TABLE edit_language (
edit_language_id SERIAL PRIMARY KEY,
enabled SMALLINT NOT NULL DEFAULT 0,
lang_default SMALLINT NOT NULL DEFAULT 0,
long_name VARCHAR,
short_name VARCHAR, -- en_US, en or en_US@latin without encoding
iso_name VARCHAR, -- should actually be encoding
order_number INT
edit_language_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
enabled SMALLINT NOT NULL DEFAULT 0,
lang_default SMALLINT NOT NULL DEFAULT 0,
long_name VARCHAR,
short_name VARCHAR, -- en_US, en or en_US@latin without encoding
iso_name VARCHAR, -- should actually be encoding
order_number INT
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_language.sql
-- START: table/edit_group.sql
@@ -483,16 +485,16 @@ CREATE TABLE edit_language (
-- DROP TABLE edit_group;
CREATE TABLE edit_group (
edit_group_id SERIAL PRIMARY KEY,
edit_scheme_id INT,
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0,
deleted SMALLINT DEFAULT 0,
uid VARCHAR,
name VARCHAR,
additional_acl JSONB
edit_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
edit_scheme_id INT,
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0,
deleted SMALLINT DEFAULT 0,
uid VARCHAR,
name VARCHAR,
additional_acl JSONB
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_group.sql
-- START: table/edit_page_access.sql
@@ -505,14 +507,14 @@ CREATE TABLE edit_group (
-- DROP TABLE edit_page_access;
CREATE TABLE edit_page_access (
edit_page_access_id SERIAL PRIMARY KEY,
edit_group_id INT NOT NULL,
FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0
edit_page_access_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
edit_group_id INT NOT NULL,
FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0
) INHERITS (edit_generic) WITHOUT OIDS;
@@ -528,15 +530,15 @@ CREATE TABLE edit_page_access (
-- DROP TABLE edit_page_content;
CREATE TABLE edit_page_content (
edit_page_content_id SERIAL PRIMARY KEY,
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
uid VARCHAR UNIQUE,
name VARCHAR,
order_number INT NOT NULL,
online SMALLINT NOT NULL DEFAULT 0
edit_page_content_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
edit_page_id INT NOT NULL,
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
uid VARCHAR UNIQUE,
name VARCHAR,
order_number INT NOT NULL,
online SMALLINT NOT NULL DEFAULT 0
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_page_content.sql
-- START: table/edit_user.sql
@@ -549,63 +551,63 @@ CREATE TABLE edit_page_content (
-- DROP TABLE edit_user;
CREATE TABLE edit_user (
edit_user_id SERIAL PRIMARY KEY,
connect_edit_user_id INT, -- possible reference to other user
FOREIGN KEY (connect_edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_language_id INT NOT NULL,
FOREIGN KEY (edit_language_id) REFERENCES edit_language (edit_language_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_group_id INT NOT NULL,
FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_scheme_id INT,
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
-- username/password
username VARCHAR UNIQUE,
password VARCHAR,
-- name block
first_name VARCHAR,
last_name VARCHAR,
first_name_furigana VARCHAR,
last_name_furigana VARCHAR,
-- email
email VARCHAR,
-- eanbled/deleted flag
enabled SMALLINT NOT NULL DEFAULT 0,
deleted SMALLINT NOT NULL DEFAULT 0,
-- general flags
strict SMALLINT DEFAULT 0,
locked SMALLINT DEFAULT 0,
protected SMALLINT NOT NULL DEFAULT 0,
-- legacy, debug flags
debug SMALLINT NOT NULL DEFAULT 0,
db_debug SMALLINT NOT NULL DEFAULT 0,
-- is admin user
admin SMALLINT NOT NULL DEFAULT 0,
-- last login log
last_login TIMESTAMP WITHOUT TIME ZONE,
-- login error
login_error_count INT DEFAULT 0,
login_error_date_last TIMESTAMP WITHOUT TIME ZONE,
login_error_date_first TIMESTAMP WITHOUT TIME ZONE,
-- time locked
lock_until TIMESTAMP WITHOUT TIME ZONE,
lock_after TIMESTAMP WITHOUT TIME ZONE,
-- password change
password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed
password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval
password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested
password_reset_uid VARCHAR, -- the uid to access the password reset page
-- _GET login id for direct login
login_user_id VARCHAR UNIQUE, -- the loginUserId, at least 32 chars
login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set
login_user_id_last_revalidate TIMESTAMP WITHOUT TIME ZONE, -- when the last login was done with user name and password
login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid
login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid
login_user_id_revalidate_after INTERVAL, -- user must login to revalidated loginUserId after set days, 0 for forever
login_user_id_locked SMALLINT DEFAULT 0, -- lock for loginUserId, but still allow normal login
-- additional ACL json block
additional_acl JSONB -- additional ACL as JSON string (can be set by other pages)
edit_user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
connect_edit_user_id INT, -- possible reference to other user
FOREIGN KEY (connect_edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_language_id INT NOT NULL,
FOREIGN KEY (edit_language_id) REFERENCES edit_language (edit_language_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_group_id INT NOT NULL,
FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_scheme_id INT,
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
-- username/password
username VARCHAR UNIQUE,
password VARCHAR,
-- name block
first_name VARCHAR,
last_name VARCHAR,
first_name_furigana VARCHAR,
last_name_furigana VARCHAR,
-- email
email VARCHAR,
-- eanbled/deleted flag
enabled SMALLINT NOT NULL DEFAULT 0,
deleted SMALLINT NOT NULL DEFAULT 0,
-- general flags
strict SMALLINT DEFAULT 0,
locked SMALLINT DEFAULT 0,
protected SMALLINT NOT NULL DEFAULT 0,
-- legacy, debug flags
debug SMALLINT NOT NULL DEFAULT 0,
db_debug SMALLINT NOT NULL DEFAULT 0,
-- is admin user
admin SMALLINT NOT NULL DEFAULT 0,
-- last login log
last_login TIMESTAMP WITHOUT TIME ZONE,
-- login error
login_error_count INT DEFAULT 0,
login_error_date_last TIMESTAMP WITHOUT TIME ZONE,
login_error_date_first TIMESTAMP WITHOUT TIME ZONE,
-- time locked
lock_until TIMESTAMP WITHOUT TIME ZONE,
lock_after TIMESTAMP WITHOUT TIME ZONE,
-- password change
password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed
password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval
password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested
password_reset_uid VARCHAR, -- the uid to access the password reset page
-- _GET login id for direct login
login_user_id VARCHAR UNIQUE, -- the loginUserId, at least 32 chars
login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set
login_user_id_last_revalidate TIMESTAMP WITHOUT TIME ZONE, -- when the last login was done with user name and password
login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid
login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid
login_user_id_revalidate_after INTERVAL, -- user must login to revalidated loginUserId after set days, 0 for forever
login_user_id_locked SMALLINT DEFAULT 0, -- lock for loginUserId, but still allow normal login
-- additional ACL json block
additional_acl JSONB -- additional ACL as JSON string (can be set by other pages)
) INHERITS (edit_generic) WITHOUT OIDS;
-- create unique index
@@ -650,37 +652,40 @@ COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List st
-- DROP TABLE edit_log;
CREATE TABLE edit_log (
edit_log_id SERIAL PRIMARY KEY,
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,
username VARCHAR,
password VARCHAR,
event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
ip VARCHAR,
error TEXT,
event TEXT,
data_binary BYTEA,
data TEXT,
page VARCHAR,
action VARCHAR,
action_id VARCHAR,
action_yes VARCHAR,
action_flag VARCHAR,
action_menu VARCHAR,
action_loaded VARCHAR,
action_value VARCHAR,
action_type VARCHAR,
action_error VARCHAR,
user_agent VARCHAR,
referer VARCHAR,
script_name VARCHAR,
query_string VARCHAR,
server_name VARCHAR,
http_host VARCHAR,
http_accept VARCHAR,
http_accept_charset VARCHAR,
http_accept_encoding VARCHAR,
session_id VARCHAR
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
FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL,
ecuid VARCHAR,
ecuuid UUID,
username VARCHAR,
password VARCHAR,
event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
ip VARCHAR,
error TEXT,
event TEXT,
data_binary BYTEA,
data TEXT,
page VARCHAR,
action VARCHAR,
action_id VARCHAR,
action_sub_id VARCHAR,
action_yes VARCHAR,
action_flag VARCHAR,
action_menu VARCHAR,
action_loaded VARCHAR,
action_value VARCHAR,
action_type VARCHAR,
action_error VARCHAR,
user_agent VARCHAR,
referer VARCHAR,
script_name VARCHAR,
query_string VARCHAR,
server_name VARCHAR,
http_host VARCHAR,
http_accept VARCHAR,
http_accept_charset VARCHAR,
http_accept_encoding VARCHAR,
session_id VARCHAR
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_log.sql
-- START: table/edit_log_overflow.sql
@@ -707,15 +712,15 @@ ALTER TABLE edit_log_overflow ADD CONSTRAINT edit_log_overflow_euid_fkey FOREIGN
-- DROP TABLE edit_access;
CREATE TABLE edit_access (
edit_access_id SERIAL PRIMARY KEY,
enabled SMALLINT NOT NULL DEFAULT 0,
protected SMALLINT DEFAULT 0,
deleted SMALLINT DEFAULT 0,
uid VARCHAR,
name VARCHAR UNIQUE,
description VARCHAR,
color VARCHAR,
additional_acl JSONB
edit_access_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
enabled SMALLINT NOT NULL DEFAULT 0,
protected SMALLINT DEFAULT 0,
deleted SMALLINT DEFAULT 0,
uid VARCHAR,
name VARCHAR UNIQUE,
description VARCHAR,
color VARCHAR,
additional_acl JSONB
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_access.sql
-- START: table/edit_access_user.sql
@@ -728,15 +733,15 @@ CREATE TABLE edit_access (
-- DROP TABLE edit_access_user;
CREATE TABLE edit_access_user (
edit_access_user_id SERIAL PRIMARY KEY,
edit_access_id INT NOT NULL,
FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_user_id INT NOT NULL,
FOREIGN KEY (edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_default SMALLINT DEFAULT 0,
enabled SMALLINT NOT NULL DEFAULT 0
edit_access_user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
edit_access_id INT NOT NULL,
FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_user_id INT NOT NULL,
FOREIGN KEY (edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_default SMALLINT DEFAULT 0,
enabled SMALLINT NOT NULL DEFAULT 0
) INHERITS (edit_generic) WITHOUT OIDS;
-- END: table/edit_access_user.sql
-- START: table/edit_access_data.sql
@@ -749,12 +754,12 @@ CREATE TABLE edit_access_user (
-- DROP TABLE edit_access_data;
CREATE TABLE edit_access_data (
edit_access_data_id SERIAL PRIMARY KEY,
edit_access_id INT NOT NULL,
FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0,
name VARCHAR,
value VARCHAR
edit_access_data_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
edit_access_id INT NOT NULL,
FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0,
name VARCHAR,
value VARCHAR
) INHERITS (edit_generic) WITHOUT OIDS;
-- create a unique index for each attached data block for each edit access can

View File

@@ -22,7 +22,6 @@ final class CoreLibsCreateSessionTest extends TestCase
public function sessionProvider(): array
{
// 0: session name as parameter or for GLOBAL value
// 1: type p: parameter, g: global, d: php.ini default
// 2: mock data as array
// checkCliStatus: true/false,
// getSessionStatus: PHP_SESSION_DISABLED for abort,
@@ -31,13 +30,10 @@ final class CoreLibsCreateSessionTest extends TestCase
// checkActiveSession: true/false, [1st call, 2nd call]
// getSessionId: string or false
// 3: exepcted name (session)]
// 4: Exception thrown on error
// 5: exception code, null for none
// 6: expected error string
// 4: auto write close flag
return [
'session parameter' => [
'sessionNameParameter',
'p',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
@@ -47,12 +43,9 @@ final class CoreLibsCreateSessionTest extends TestCase
],
'sessionNameParameter',
null,
null,
'',
],
'session globals' => [
'sessionNameGlobals',
'g',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
@@ -61,13 +54,10 @@ final class CoreLibsCreateSessionTest extends TestCase
'getSessionId' => '1234abcd4567'
],
'sessionNameGlobals',
null,
null,
'',
false,
],
'session name default' => [
'',
'd',
'auto write close' => [
'sessionNameAutoWriteClose',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
@@ -75,109 +65,8 @@ final class CoreLibsCreateSessionTest extends TestCase
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
null,
null,
'',
],
// error checks
// 1: we are in cli
'on cli error' => [
'',
'd',
[
'checkCliStatus' => true,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => true,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'RuntimeException',
1,
'[SESSION] No sessions in php cli'
],
// 2: session disabled
'session disabled error' => [
'',
'd',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_DISABLED,
'setSessionName' => true,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'RuntimeException',
2,
'[SESSION] Sessions are disabled'
],
// 3: invalid session name: string
'invalid name chars error' => [
'1invalid$session#;',
'p',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => false,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'UnexpectedValueException',
3,
'[SESSION] Invalid session name: 1invalid$session#;'
],
// 3: invalid session name: only numbers
'invalid name numbers only error' => [
'123',
'p',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => false,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'UnexpectedValueException',
3,
'[SESSION] Invalid session name: 123'
],
// 3: invalid session name: invalid name short
// 3: invalid session name: too long (128)
// 4: failed to start session (2nd false on check active session)
'invalid name numbers only error' => [
'',
'd',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => true,
'checkActiveSession' => [false, false],
'getSessionId' => '1234abcd4567'
],
'',
'RuntimeException',
4,
'[SESSION] Failed to activate session'
],
// 5: get session id return false
'invalid name numbers only error' => [
'',
'd',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => true,
'checkActiveSession' => [false, true],
'getSessionId' => false
],
'',
'UnexpectedValueException',
5,
'[SESSION] getSessionId did not return a session id'
'sessionNameAutoWriteClose',
true,
],
];
}
@@ -190,32 +79,23 @@ final class CoreLibsCreateSessionTest extends TestCase
* @testdox startSession $input name for $type will be $expected (error: $expected_error) [$_dataName]
*
* @param string $input
* @param string $type
* @param array<mixed> $mock_data
* @param string $expected
* @param string|null $exception
* @param string $expected_error
* @return void
*/
public function testStartSession(
string $input,
string $type,
array $mock_data,
string $expected,
?string $exception,
?int $exception_code,
string $expected_error
?bool $auto_write_close,
): void {
// override expected
if ($type == 'd') {
$expected = ini_get('session.name');
}
/** @var \CoreLibs\Create\Session&MockObject $session_mock */
$session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class,
[
'checkCliStatus', 'getSessionStatus', 'checkActiveSession',
'setSessionName', 'startSessionCall', 'getSessionId',
'checkCliStatus',
'getSessionStatus', 'checkActiveSession',
'getSessionId',
'getSessionName'
]
);
@@ -234,12 +114,8 @@ final class CoreLibsCreateSessionTest extends TestCase
$mock_data['checkActiveSession'][0],
$mock_data['checkActiveSession'][1],
);
// dummy set for session name
$session_mock->method('setSessionName')->with($input)->willReturn($mock_data['setSessionName']);
// set session name & return bsed on request data
$session_mock->method('getSessionName')->willReturn($expected);
// will not return anything
$session_mock->method('startSessionCall');
// in test case only return string
// false: will return false
$session_mock->method('getSessionId')->willReturn($mock_data['getSessionId']);
@@ -247,25 +123,7 @@ final class CoreLibsCreateSessionTest extends TestCase
// regex for session id
$ression_id_regex = "/^\w+$/";
if ($exception !== null) {
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
}
unset($GLOBALS['SET_SESSION_NAME']);
$session_id = '';
switch ($type) {
case 'p':
$session_id = $session_mock->startSession($input);
break;
case 'g':
$GLOBALS['SET_SESSION_NAME'] = $input;
$session_id = $session_mock->startSession();
break;
case 'd':
$session_id = $session_mock->startSession();
break;
}
$session_id = $session_mock->getSessionId();
// asert checks
if (!empty($session_id)) {
$this->assertMatchesRegularExpression(
@@ -284,6 +142,73 @@ final class CoreLibsCreateSessionTest extends TestCase
}
}
/**
* Undocumented function
*
* @return array
*/
public function providerSessionException(): array
{
return [
'not cli' => [
'TEST_EXCEPTION',
\RuntimeException::class,
1,
'/^\[SESSION\] No sessions in php cli$/',
],
/* 'session disabled ' => [
'TEST_EXCEPTION',
\RuntimeException::class,
2,
'/^\[SESSION\] Sessions are disabled/'
],
'invalid session name' => [
'--#as^-292p-',
\UnexpectedValueException::class,
3,
'/^\[SESSION\] Invalid session name: /'
],
'failed to activate session' => [
'TEST_EXCEPTION',
\RuntimeException::class,
4,
'/^\[SESSION\] Failed to activate session/'
],
'not a valid session id returned' => [
\UnexpectedValueException::class,
5,
'/^\[SESSION\] getSessionId did not return a session id/'
], */
];
}
/**
* exception checks
*
* @covers ::initSession
* @dataProvider providerSessionException
* @testdox create session $session_name with exception $exception ($exception_code) [$_dataName]
*
* @param string $session_name
* @param string $exception
* @param int $exception_code
* @param string $expected_error
* @return void
*/
public function testSessionException(
string $session_name,
string $exception,
int $exception_code,
string $expected_error,
): void {
//
// throws only on new Object creation
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
$this->expectExceptionMessageMatches($expected_error);
new \CoreLibs\Create\Session($session_name);
}
/**
* provider for session name check
*
@@ -347,109 +272,147 @@ final class CoreLibsCreateSessionTest extends TestCase
*
* @return array
*/
public function sessionDataProvider(): array
public function providerSessionData(): array
{
return [
'test' => [
'foo',
'bar',
'bar',
null,
],
'int key test' => [
123,
'bar',
'bar',
\UnexpectedValueException::class
],
// more complex value tests
'array values' => [
'array',
[1, 2, 3],
[1, 2, 3],
null,
]
];
}
// NOTE: with auto start session, we cannot test this in the command line
/**
* method call test
*
* @covers ::setS
* @covers ::getS
* @covers ::issetS
* @covers ::unsetS
* @dataProvider sessionDataProvider
* @testdox setS/getS/issetS/unsetS $name with $input is $expected [$_dataName]
* @covers ::set
* @covers ::get
* @covers ::isset
* @covers ::unset
* @dataProvider providerSessionData
* @testdox set/get/isset/unset $name with $input is $expected ($exception) [$_dataName]
*
* @param string|int $name
* @param mixed $input
* @param mixed $expected
* @param ?mixed $exception
* @return void
*/
public function testMethodSetGet($name, $input, $expected): void
public function testMethodSetGet($name, $input, $expected, $exception): void
{
$session = new \CoreLibs\Create\Session();
$session->setS($name, $input);
if (\CoreLibs\Get\System::checkCLI()) {
$this->markTestSkipped('Cannot run testMethodSetGet in CLI');
}
$session = new \CoreLibs\Create\Session('TEST_METHOD');
if ($expected !== null) {
$this->expectException($exception);
}
$session->set($name, $input);
$this->assertEquals(
$expected,
$session->getS($name),
$session->get($name),
'method set assert'
);
// isset true
$this->assertTrue(
$session->issetS($name),
$session->isset($name),
'method isset assert ok'
);
$session->unsetS($name);
$session->unset($name);
$this->assertEquals(
'',
$session->getS($name),
$session->get($name),
'method unset assert'
);
// iset false
// isset false
$this->assertFalse(
$session->issetS($name),
$session->isset($name),
'method isset assert false'
);
}
/**
* magic call test
* Undocumented function
*
* @covers ::__set
* @covers ::__get
* @covers ::__isset
* @covers ::__unset
* @dataProvider sessionDataProvider
* @testdox __set/__get/__iseet/__unset $name with $input is $expected [$_dataName]
* @return array
*/
public function providerSessionDataMany(): array
{
return [
'valid set' => [
[
'foo 1' => 'bar 1',
'foo 2' => 'bar 1',
],
[
'foo 1' => 'bar 1',
'foo 2' => 'bar 1',
],
null,
],
'invalid entry' => [
[
'foo 1' => 'bar 1',
123 => 'bar 1',
],
[
'foo 1' => 'bar 1',
],
\UnexpectedValueException::class
]
];
}
/**
* Undocumented function
*
* @param string|int $name
* @param mixed $input
* @param mixed $expected
* @covers ::setMany
* @covers ::getMany
* @dataProvider providerSessionDataMany
* @testdox setMany/getMany/unsetMany $set is $expected ($exception) [$_dataName]
*
* @param array<string|int,mixed> $set
* @param array<string,mixed> $expected
* @param ?mixed $exception
* @return void
*/
public function testMagicSetGet($name, $input, $expected): void
public function testMany($set, $expected, $exception): void
{
$session = new \CoreLibs\Create\Session();
$session->$name = $input;
if (\CoreLibs\Get\System::checkCLI()) {
$this->markTestSkipped('Cannot run testMethodSetGet in CLI');
}
$session = new \CoreLibs\Create\Session('TEST_METHOD');
if ($expected !== null) {
$this->expectException($exception);
}
$session->setMany($set);
$this->assertEquals(
$expected,
$session->$name,
'magic set assert'
$session->getMany(array_keys($set)),
'set many failed'
);
// isset true
$this->assertTrue(
isset($session->$name),
'magic isset assert ok'
);
unset($session->$name);
$session->unsetMany(array_keys($set));
$this->assertEquals(
'',
$session->$name,
'magic unset assert'
);
// isset true
$this->assertFalse(
isset($session->$name),
'magic isset assert false'
[],
$session->getMany(array_keys($set)),
'unset many failed'
);
}
@@ -463,27 +426,30 @@ final class CoreLibsCreateSessionTest extends TestCase
*/
public function testUnsetAll(): void
{
if (\CoreLibs\Get\System::checkCLI()) {
$this->markTestSkipped('Cannot run testUnsetAll in CLI');
}
$test_values = [
'foo' => 'abc',
'bar' => '123'
];
$session = new \CoreLibs\Create\Session();
$session = new \CoreLibs\Create\Session('TEST_UNSET');
foreach ($test_values as $name => $value) {
$session->setS($name, $value);
$session->set($name, $value);
// confirm set
$this->assertEquals(
$value,
$session->getS($name),
$session->get($name),
'set assert: ' . $name
);
}
// unset all
$session->unsetAllS();
$session->clear();
// check unset
foreach (array_keys($test_values) as $name) {
$this->assertEquals(
'',
$session->getS($name),
$session->get($name),
'unsert assert: ' . $name
);
}

View File

@@ -17,7 +17,7 @@ Table with Primary Key: table_with_primary_key
Table without Primary Key: table_without_primary_key
Table with primary key has additional row:
row_primary_key SERIAL PRIMARY KEY,
row_primary_key INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
Each table has the following rows
row_int INT,
row_numeric NUMERIC,
@@ -160,7 +160,6 @@ final class CoreLibsDBIOTest extends TestCase
// create the tables
$db->dbExec(
// primary key name is table + '_id'
// table_with_primary_key_id SERIAL PRIMARY KEY,
<<<SQL
CREATE TABLE table_with_primary_key (
table_with_primary_key_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -5136,6 +5135,67 @@ final class CoreLibsDBIOTest extends TestCase
SQL,
'count' => 6,
'convert' => false,
],
'comments in insert' => [
'query' => <<<SQL
INSERT INTO table_with_primary_key (
row_int, row_numeric, row_varchar, row_varchar_literal
) VALUES (
-- comment 1 かな
$1, $2,
-- comment 2 -
$3
-- comment 3
, $4
)
SQL,
'count' => 4,
'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
'a complete set of possible' => [
'query' => <<<SQL
UPDATE table_with_primary_key SET
-- ROW
row_varchar = $1
WHERE
row_varchar = ANY($2) AND row_varchar <> $3
AND row_varchar > $4 AND row_varchar < $5
AND row_varchar >= $6 AND row_varchar <=$7
AND row_jsonb->'a' = $8 AND row_jsonb->>$9 = 'a'
AND row_jsonb<@$10 AND row_jsonb@>$11
AND row_varchar ^@ $12
SQL,
'count' => 12,
'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

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

View File

@@ -56,7 +56,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$decrypted,
'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
$encrypted = SymmetricEncryption::getInstance($key)->encrypt($input);
$decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted);
@@ -65,7 +82,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$decrypted,
'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
$encrypted = SymmetricEncryption::encryptKey($input, $key);
$decrypted = SymmetricEncryption::decryptKey($encrypted, $key);
@@ -114,13 +148,51 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$crypt = new SymmetricEncryption($key);
$encrypted = $crypt->encrypt($input);
$this->expectExceptionMessage($exception_message);
$crypt->setKey($key);
$crypt->setKey($wrong_key);
$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
$encrypted = SymmetricEncryption::getInstance($key)->encrypt($input);
$this->expectExceptionMessage($exception_message);
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
$encrypted = SymmetricEncryption::encryptKey($input, $key);
@@ -190,6 +262,56 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
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
*
@@ -232,6 +354,49 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
$this->expectExceptionMessage($exception_message);
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__