Compare commits

...

10 Commits

Author SHA1 Message Date
Clemens Schwaighofer
ae125ea45e DB IO cache reset flag add to ignore warning, and ignore cache reset warning in Login class 2025-04-16 17:44:42 +09:00
Clemens Schwaighofer
94eb1c7697 Release: v9.32.0 2025-04-15 18:43:49 +09:00
Clemens Schwaighofer
aff4944ffd ACL Login update with pages in acl array and file in allowed access list lookup 2025-04-15 18:42:55 +09:00
Clemens Schwaighofer
1a4c8e188f Release: v9.31.1 2025-04-15 17:49:25 +09:00
Clemens Schwaighofer
c603922fca Bug fix in edit user control array for custom error example data missing 2025-04-15 17:48:35 +09:00
Clemens Schwaighofer
7ac13c2ba6 Release: v9.31.0 2025-04-09 11:43:26 +09:00
Clemens Schwaighofer
1c66ee34a1 DB IO rename dbGetQueryHash to dbBuildQueryHash, store last query hash 2025-04-09 11:40:07 +09:00
Clemens Schwaighofer
2e101d55d2 Release: v9.30.0 2025-04-07 19:54:30 +09:00
Clemens Schwaighofer
4b699d753d DB placeholder comment fix, add hash hmac to Hashlib 2025-04-07 19:52:01 +09:00
Clemens Schwaighofer
254a0e4802 Release: v9.29.0 2025-04-04 16:03:34 +09:00
12 changed files with 454 additions and 154 deletions

View File

@@ -1 +1 @@
9.28.1
9.32.0

View File

@@ -924,7 +924,9 @@ class Login
$mandatory_session_vars = [
'LOGIN_USER_NAME', 'LOGIN_GROUP_NAME', 'LOGIN_EUCUID', 'LOGIN_EUCUUID',
'LOGIN_USER_ADDITIONAL_ACL', 'LOGIN_GROUP_ADDITIONAL_ACL',
'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL', 'LOGIN_PAGES_ACL_LEVEL', 'LOGIN_USER_ACL_LEVEL',
'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL',
'LOGIN_PAGES', 'LOGIN_PAGES_LOOKUP', 'LOGIN_PAGES_ACL_LEVEL',
'LOGIN_USER_ACL_LEVEL',
'LOGIN_UNIT', 'LOGIN_UNIT_DEFAULT_EACUID'
];
$force_reauth = false;
@@ -1152,7 +1154,7 @@ class Login
$q
);
// reset any query data that might exist
$this->db->dbCacheReset($q, $params);
$this->db->dbCacheReset($q, $params, show_warning:false);
// never cache return data
$res = $this->db->dbReturnParams($q, $params, $this->db::NO_CACHE);
// query was not run successful
@@ -1264,6 +1266,7 @@ class Login
}
$edit_page_ids = [];
$pages = [];
$pages_lookup = [];
$pages_acl = [];
// set pages access
$q = <<<SQL
@@ -1307,6 +1310,7 @@ class Login
'query' => [],
'visible' => []
];
$pages_lookup[$res['filename']] = $res['cuid'];
// make reference filename -> level
$pages_acl[$res['filename']] = $res['level'];
} // for each page
@@ -1367,6 +1371,7 @@ class Login
// write back the pages data to the output array
$this->session->setMany([
'LOGIN_PAGES' => $pages,
'LOGIN_PAGES_LOOKUP' => $pages_lookup,
'LOGIN_PAGES_ACL_LEVEL' => $pages_acl,
]);
// load the edit_access user rights
@@ -1526,6 +1531,8 @@ class Login
) {
$this->acl['page'] = $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name];
}
$this->acl['pages_detail'] = $_SESSION['LOGIN_PAGES'];
$this->acl['pages_lookup_cuid'] = $_SESSION['LOGIN_PAGES_LOOKUP'];
$this->acl['unit_cuid'] = null;
$this->acl['unit_name'] = null;
@@ -2728,6 +2735,31 @@ HTML;
return $this->session->get('LOGIN_PAGES');
}
/**
* Return the current loaded list of pages the user can access
*
* @return array<mixed>
*/
public function loginGetPageLookupList(): array
{
return $this->session->get('LOGIN_PAGES_LOOKUP');
}
/**
* Check access to a file in the pages list
*
* @param string $filename File name to check
* @return bool True if page in list and anything other than None access, False if failed
*/
public function loginPageAccessAllowed(string $filename): bool
{
return (
$this->session->get('LOGIN_PAGES')[
$this->session->get('LOGIN_PAGES_LOOKUP')[$filename] ?? ''
] ?? 0
) != 0 ? true : false;
}
// MARK: logged in uid(pk)/eucuid/eucuuid
/**

View File

@@ -49,7 +49,7 @@ class Hash
* replacement for __crc32b call
*
* @param string $string string to hash
* @param bool $use_sha use sha1 instead of crc32b (default false)
* @param bool $use_sha [default=false] use sha1 instead of crc32b
* @return string hash of the string
* @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true
*/
@@ -81,7 +81,7 @@ class Hash
* all that create 8 char long hashes
*
* @param string $string string to hash
* @param string $hash_type hash type (default adler32)
* @param string $hash_type [default=STANDARD_HASH_SHORT] hash type (default adler32)
* @return string hash of the string
* @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types
*/
@@ -92,12 +92,40 @@ class Hash
return self::hash($string, $hash_type);
}
/**
* check if hash type is valid, returns false if not
*
* @param string $hash_type
* @return bool
*/
public static function isValidHashType(string $hash_type): bool
{
if (!in_array($hash_type, hash_algos())) {
return false;
}
return true;
}
/**
* check if hash hmac type is valid, returns false if not
*
* @param string $hash_hmac_type
* @return bool
*/
public static function isValidHashHmacType(string $hash_hmac_type): bool
{
if (!in_array($hash_hmac_type, hash_hmac_algos())) {
return false;
}
return true;
}
/**
* creates a hash over string if any valid hash given.
* if no hash type set use sha256
*
* @param string $string string to ash
* @param string $hash_type hash type (default sha256)
* @param string $string string to hash
* @param string $hash_type [default=STANDARD_HASH] hash type (default sha256)
* @return string hash of the string
*/
public static function hash(
@@ -108,12 +136,36 @@ class Hash
empty($hash_type) ||
!in_array($hash_type, hash_algos())
) {
// fallback to default hash type if none set or invalid
// fallback to default hash type if empty or invalid
$hash_type = self::STANDARD_HASH;
}
return hash($hash_type, $string);
}
/**
* creates a hash mac key
*
* @param string $string string to hash mac
* @param string $key key to use
* @param string $hash_type [default=STANDARD_HASH]
* @return string hash mac string
*/
public static function hashHmac(
string $string,
#[\SensitiveParameter]
string $key,
string $hash_type = self::STANDARD_HASH
): string {
if (
empty($hash_type) ||
!in_array($hash_type, hash_hmac_algos())
) {
// fallback to default hash type if e or invalid
$hash_type = self::STANDARD_HASH;
}
return hash_hmac($hash_type, $string, $key);
}
/**
* short hash with max length of 8, uses adler32
*

View File

@@ -303,6 +303,8 @@ class IO
private string $query = '';
/** @var array<mixed> current params for query */
private array $params = [];
/** @var string current hash build from query and params */
private string $query_hash = '';
// if we do have a convert call, store the convert data in here, else it will be empty
/** @var array{}|array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} */
private array $placeholder_converted = [];
@@ -1319,7 +1321,7 @@ class IO
*/
private function __dbCountQueryParams(string $query): int
{
return $this->db_functions->__dbCountQueryParams($query);
return count($this->db_functions->__dbGetQueryParams($query));
}
/**
@@ -1382,6 +1384,8 @@ class IO
$this->query = $query;
// current params
$this->params = $params;
// empty on new
$this->query_hash = '';
// no query set
if (empty($this->query)) {
$this->__dbError(11);
@@ -1441,7 +1445,7 @@ class IO
$this->returning_id = true;
}
// import protection, hash needed
$query_hash = $this->dbGetQueryHash($this->query, $this->params);
$query_hash = $this->dbBuildQueryHash($this->query, $this->params);
// QUERY PARAMS: run query params check and rewrite
if ($this->dbGetConvertPlaceholder() === true) {
try {
@@ -1475,7 +1479,8 @@ class IO
return false;
}
}
// set query hash
$this->query_hash = $query_hash;
// $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id);
// for DEBUG, only on first time ;)
$this->__dbDebug(
@@ -1959,7 +1964,7 @@ class IO
{
// set start array
if ($query) {
$array = $this->cursor_ext[$this->dbGetQueryHash($query)] ?? [];
$array = $this->cursor_ext[$this->dbBuildQueryHash($query)] ?? [];
} else {
$array = $this->cursor_ext;
}
@@ -2361,7 +2366,7 @@ class IO
return false;
}
// create hash from query ...
$query_hash = $this->dbGetQueryHash($query, $params);
$query_hash = $this->dbBuildQueryHash($query, $params);
// pre declare array
if (!isset($this->cursor_ext[$query_hash])) {
$this->cursor_ext[$query_hash] = [
@@ -2937,12 +2942,14 @@ class IO
* data to create a unique call one, optional
* @return bool False if query not found, true if success
*/
public function dbCacheReset(string $query, array $params = []): bool
public function dbCacheReset(string $query, array $params = [], bool $show_warning = true): bool
{
$this->__dbErrorReset();
$query_hash = $this->dbGetQueryHash($query, $params);
$query_hash = $this->dbBuildQueryHash($query, $params);
// clears cache for this query
if (empty($this->cursor_ext[$query_hash]['query'])) {
if (
$show_warning &&
empty($this->cursor_ext[$query_hash]['query'])
) {
$this->__dbWarning(18, context: [
'query' => $query,
'params' => $params,
@@ -2982,7 +2989,7 @@ class IO
if ($query === null) {
return $this->cursor_ext;
}
$query_hash = $this->dbGetQueryHash($query, $params);
$query_hash = $this->dbBuildQueryHash($query, $params);
if (
!empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash])
@@ -3012,7 +3019,7 @@ class IO
$this->__dbError(11);
return false;
}
$query_hash = $this->dbGetQueryHash($query, $params);
$query_hash = $this->dbBuildQueryHash($query, $params);
if (
!empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash])
@@ -3038,7 +3045,7 @@ class IO
$this->__dbError(11);
return false;
}
$query_hash = $this->dbGetQueryHash($query, $params);
$query_hash = $this->dbBuildQueryHash($query, $params);
if (
!empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash])
@@ -3064,7 +3071,7 @@ class IO
*/
public function dbResetQueryCalled(string $query, array $params = []): void
{
$this->query_called[$this->dbGetQueryHash($query, $params)] = 0;
$this->query_called[$this->dbBuildQueryHash($query, $params)] = 0;
}
/**
@@ -3077,7 +3084,7 @@ class IO
*/
public function dbGetQueryCalled(string $query, array $params = []): int
{
$query_hash = $this->dbGetQueryHash($query, $params);
$query_hash = $this->dbBuildQueryHash($query, $params);
if (!empty($this->query_called[$query_hash])) {
return $this->query_called[$query_hash];
} else {
@@ -4046,7 +4053,7 @@ class IO
}
/**
* Returns hash for query
* Creates hash for query and parameters
* Hash is used in all internal storage systems for return data
*
* @param string $query The query to create the hash from
@@ -4054,7 +4061,7 @@ class IO
* data to create a unique call one, optional
* @return string Hash, as set by hash long
*/
public function dbGetQueryHash(string $query, array $params = []): string
public function dbBuildQueryHash(string $query, array $params = []): string
{
return Hash::hashLong(
$query . (
@@ -4104,6 +4111,26 @@ class IO
$this->params = [];
}
/**
* get the current set query hash
*
* @return string Current Query hash
*/
public function dbGetQueryHash(): string
{
return $this->query_hash;
}
/**
* reset query hash
*
* @return void
*/
public function dbResetQueryHash(): void
{
$this->query_hash = '';
}
/**
* Returns the placeholder convert set or empty
*
@@ -4283,6 +4310,17 @@ class IO
return $this->field_names[$pos] ?? false;
}
/**
* get all the $ placeholders
*
* @param string $query
* @return array<string>
*/
public function dbGetQueryParamPlaceholders(string $query): array
{
return $this->db_functions->__dbGetQueryParams($query);
}
/**
* Return a field type for a field name or pos,
* will return false if field is not found in list

View File

@@ -379,9 +379,9 @@ interface SqlFunctions
* Undocumented function
*
* @param string $query
* @return int
* @return array<string>
*/
public function __dbCountQueryParams(string $query): int;
public function __dbGetQueryParams(string $query): array;
}
// __END__

View File

@@ -978,12 +978,12 @@ class PgSQL implements Interface\SqlFunctions
}
/**
* Count placeholder queries. $ only
* Get the all the $ params, as a unique list
*
* @param string $query
* @return int
* @return array<string>
*/
public function __dbCountQueryParams(string $query): int
public function __dbGetQueryParams(string $query): array
{
$matches = [];
// regex for params: only stand alone $number allowed
@@ -998,11 +998,11 @@ class PgSQL implements Interface\SqlFunctions
// Matches in 1:, must be array_filtered to remove empty, count with array_unique
// Regex located in the ConvertPlaceholder class
preg_match_all(
ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS,
ConvertPlaceholder::REGEX_LOOKUP_NUMBERED,
$query,
$matches
);
return count(array_unique(array_filter($matches[3])));
return array_unique(array_filter($matches[ConvertPlaceholder::MATCHING_POS]));
}
}

View File

@@ -14,76 +14,57 @@ namespace CoreLibs\DB\Support;
class ConvertPlaceholder
{
// 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
. 'THEN|ELSE' // command parts (CASE)
;
/** @var string the main regex including the pattern query split */
private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*';
/** @var string text block in SQL, single quited
* Note that does not include $$..$$ strings or anything with token name or nested ones
*/
private const PATTERN_TEXT_BLOCK_SINGLE_QUOTE = '(?:\'(?:[^\'\\\\]|\\\\.)*\')';
/** @var string text block in SQL, dollar quoted
* NOTE: if this is added everything shifts by one lookup number
*/
private const PATTERN_TEXT_BLOCK_DOLLAR = '(?:\$(\w*)\$.*?\$\1\$)';
/** @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
'\d+|'
// other string -> ignore
. '(?:\'.*?\')|';
/** @var string named parameters */
private const PATTERN_NAMED = '(:\w+)';
/** @var string question mark parameters */
private const PATTERN_QUESTION_MARK = '(?:(?:\?\?)?\s*(\?{1}))';
/** @var string numbered parameters */
* anything that starts with -- and ends with a line break but any character that is not line break inbetween
* this is the FIRST thing in the line and will skip any further lookups */
private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)';
// below are the params lookups
/** @var string named parameters, must start with single : */
private const PATTERN_NAMED = '((?<!:):(?:\w+))';
/** @var string question mark parameters, will catch any */
private const PATTERN_QUESTION_MARK = '(\?{1})';
/** @var string numbered parameters, can only start 1 to 9, second and further digits can be 0-9
* This ignores the $$ ... $$ escape syntax. If we find something like this will fail
* It is recommended to use proper string escape quiting for writing data to the DB
*/
private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)';
// below here are full regex that will be used
/** @var string replace regex for named (:...) entries */
public const REGEX_REPLACE_NAMED = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '('
. self::PATTERN_IGNORE
. self::PATTERN_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_NAMED
. ')'
. '/s';
/** @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_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_QUESTION_MARK
. ')'
. '/s';
/** @var string replace regex for numbered ($n) entries */
public const REGEX_REPLACE_NUMBERED = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. self::PATTERN_COMMENT
. '('
. self::PATTERN_IGNORE
. self::PATTERN_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_NUMBERED
. ')'
. '/s';
/** @var string the main lookup query for all placeholders */
public const REGEX_LOOKUP_PLACEHOLDERS = '/'
// prefix string part, must match towards
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15]
. self::PATTERN_ELEMENT
. self::PATTERN_COMMENT
. self::PATTERN_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
// match for replace part
. '(?:'
// ignore parts
. self::PATTERN_IGNORE
// :name named part (PDO) [1]
. self::PATTERN_NAMED . '|'
// ? question mark part (PDO) [2]
@@ -94,6 +75,26 @@ class ConvertPlaceholder
. ')'
// single line -> add line break to matches in "."
. '/s';
/** @var string lookup for only numbered placeholders */
public const REGEX_LOOKUP_NUMBERED = '/'
. self::PATTERN_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
// match for replace part
. '(?:'
// $n numbered part (\PG php) [1]
. self::PATTERN_NUMBERED
// end match
. ')'
. '/s';
/** @var int position for regex in full placeholder lookup: named */
public const LOOOKUP_NAMED_POS = 2;
/** @var int position for regex in full placeholder lookup: question mark */
public const LOOOKUP_QUESTION_MARK_POS = 3;
/** @var int position for regex in full placeholder lookup: numbered */
public const LOOOKUP_NUMBERED_POS = 4;
/** @var int matches position for replacement and single lookup */
public const MATCHING_POS = 2;
/**
* Convert PDO type query with placeholders to \PG style and vica versa
@@ -132,11 +133,12 @@ class ConvertPlaceholder
$found = -1;
}
/** @var array<string> 1: named */
$named_matches = array_filter($matches[1]);
$named_matches = array_filter($matches[self::LOOOKUP_NAMED_POS]);
/** @var array<string> 2: open ? */
$qmark_matches = array_filter($matches[2]);
$qmark_matches = array_filter($matches[self::LOOOKUP_QUESTION_MARK_POS]);
/** @var array<string> 3: $n matches */
$numbered_matches = array_filter($matches[3]);
$numbered_matches = array_filter($matches[self::LOOOKUP_NUMBERED_POS]);
// print "**MATCHES**: <pre>" . print_r($matches, true) . "</pre>";
// count matches
$count_named = count(array_unique($named_matches));
$count_qmark = count($qmark_matches);
@@ -235,38 +237,37 @@ class ConvertPlaceholder
$empty_params = $converted_placeholders['original']['empty_params'];
switch ($converted_placeholders['type']) {
case 'named':
// 0: full
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part :named
// 1: replace part :named
$pos = 0;
$query_new = preg_replace_callback(
self::REGEX_REPLACE_NAMED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
if (!isset($matches[self::MATCHING_POS])) {
throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
209
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++;
$params_lookup[$matches[3]] = '$' . $pos;
$params_lookup[$match] = '$' . $pos;
// skip params setup if param list is empty
if (!$empty_params) {
$params_new[] = $params[$matches[3]] ??
$params_new[] = $params[$match] ??
throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params list',
'Cannot lookup ' . $match . ' in params list',
210
);
}
}
// add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . (
empty($matches[3]) ?
$matches[2] :
$params_lookup[$matches[3]] ??
throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params lookup list',
211
)
);
return $params_lookup[$match] ??
throw new \RuntimeException(
'Cannot lookup ' . $match . ' in params lookup list',
211
);
},
$converted_placeholders['original']['query']
);
@@ -276,61 +277,61 @@ class ConvertPlaceholder
// order and data stays the same
$params_new = $params ?? [];
}
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part ?
// 1: replace part ?
$pos = 0;
$query_new = preg_replace_callback(
self::REGEX_REPLACE_QUESTION_MARK,
function ($matches) use (&$pos, &$params_lookup) {
if (!isset($matches[self::MATCHING_POS])) {
throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
229
);
}
$match = $matches[self::MATCHING_POS];
// only count pos up for actual replacements we will do
if (!empty($matches[3])) {
if (!empty($match)) {
$pos++;
$params_lookup[] = '$' . $pos;
}
// add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . (
empty($matches[3]) ?
$matches[2] :
'$' . $pos
);
return '$' . $pos;
},
$converted_placeholders['original']['query']
);
break;
case 'numbered':
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part $numbered
// 1: replace part $numbered
$pos = 0;
$query_new = preg_replace_callback(
self::REGEX_REPLACE_NUMBERED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
if (!isset($matches[self::MATCHING_POS])) {
throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
239
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++;
$params_lookup[$matches[3]] = ':' . $pos . '_named';
$params_lookup[$match] = ':' . $pos . '_named';
// skip params setup if param list is empty
if (!$empty_params) {
$params_new[] = $params[($pos - 1)] ??
throw new \RuntimeException(
'Cannot lookup ' . ($pos - 1) . ' in params list',
220
230
);
}
}
// add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . (
empty($matches[3]) ?
$matches[2] :
$params_lookup[$matches[3]] ??
throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params lookup list',
221
)
);
return $params_lookup[$match] ??
throw new \RuntimeException(
'Cannot lookup ' . $match . ' in params lookup list',
231
);
},
$converted_placeholders['original']['query']
);

View File

@@ -1371,7 +1371,7 @@ class Generate
) {
$this->msg .= sprintf(
$this->l->__('Please enter a valid (%s) input for the <b>%s</b> Field!<br>'),
$this->dba->getTableArray()[$key]['error_example'],
$this->dba->getTableArray()[$key]['error_example'] ?? '[MISSING]',
$this->dba->getTableArray()[$key]['output_name']
);
}
@@ -2602,7 +2602,7 @@ class Generate
}
}
// add lost error ones
$this->log->error('P: ' . $data['prefix'] . ', '
$this->log->error('Prefix: ' . $data['prefix'] . ', '
. Support::prAr($_POST['ERROR'][$data['prefix']] ?? []));
if ($this->error && !empty($_POST['ERROR'][$data['prefix']])) {
$prfx = $data['prefix']; // short

View File

@@ -50,7 +50,8 @@ class EditUsers implements Interface\TableArraysInterface
'HIDDEN_value' => $_POST['HIDDEN_password'] ?? '',
'CONFIRM_value' => $_POST['CONFIRM_password'] ?? '',
'output_name' => 'Password',
'mandatory' => 1,
// make it not mandatory to create dummy accounts that can only login via login url id
'mandatory' => 0,
'type' => 'password', // later has to be password for encryption in database
'update' => [ // connected field updates, and update data
'password_change_date' => [ // db row to update
@@ -182,6 +183,7 @@ class EditUsers implements Interface\TableArraysInterface
'type' => 'text',
'error_check' => 'unique|custom',
'error_regex' => "/^[A-Za-z0-9]+$/",
'error_example' => "ABCdef123",
'emptynull' => 1,'min_edit_acl' => '100',
'min_show_acl' => '100',
],

View File

@@ -12,6 +12,8 @@ Not yet covered tests:
- loginGetLocale
- loginGetHeaderColor
- loginGetPages
- loginGetPageLookupList
- loginPageAccessAllowed
- loginGetEuid
*/

View File

@@ -21,8 +21,10 @@ final class CoreLibsCreateHashTest extends TestCase
public function hashData(): array
{
return [
'any string' => [
'hash tests' => [
// this is the string
'text' => 'Some String Text',
// hash list special
'crc32b_reverse' => 'c5c21d91', // crc32b (in revere)
'sha1Short' => '4d2bc9ba0', // sha1Short
// via hash
@@ -31,6 +33,8 @@ final class CoreLibsCreateHashTest extends TestCase
'fnv132' => '9df444f9', // hash: fnv132
'fnv1a32' => '2c5f91b9', // hash: fnv1a32
'joaat' => '50dab846', // hash: joaat
'ripemd160' => 'aeae3f041b20136451519edd9361570909300342', // hash: ripemd160,
'sha256' => '9055080e022f224fa835929b80582b3c71c672206fa3a49a87412c25d9d42ceb', // hash: sha256
]
];
}
@@ -81,7 +85,7 @@ final class CoreLibsCreateHashTest extends TestCase
{
$list = [];
foreach ($this->hashData() as $name => $values) {
foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat'] as $_hash_type) {
foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256'] as $_hash_type) {
// default value test
if ($_hash_type === null) {
$hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT;
@@ -288,7 +292,7 @@ final class CoreLibsCreateHashTest extends TestCase
* Undocumented function
*
* @covers ::hash
* @testdox hash with invalid type [$_dataName]
* @testdox hash with invalid type
*
* @return void
*/
@@ -301,6 +305,122 @@ final class CoreLibsCreateHashTest extends TestCase
\CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST')
);
}
/**
* Note: this only tests default sha256
*
* @covers ::hashHmac
* @testdox hash hmac test
*
* @return void
*/
public function testHashMac(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = '16479b3ef6fa44e1cdd8b2dcfaadf314d1a7763635e8738f1e7996d714d9b6bf';
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key)
);
}
/**
* Undocumented function
*
* @covers ::hashHmac
* @testdox hash hmac with invalid type
*
* @return void
*/
public function testInvalidHashMacType(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = hash_hmac(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source, $hash_key);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key, 'DOES_NOT_EXIST')
);
}
/**
* Undocumented function
*
* @return array<mixed>
*/
public function providerHashTypes(): array
{
return [
'Hash crc32b' => [
'crc32b',
true,
false,
],
'Hash adler32' => [
'adler32',
true,
false,
],
'HAsh fnv132' => [
'fnv132',
true,
false,
],
'Hash fnv1a32' => [
'fnv1a32',
true,
false,
],
'Hash: joaat' => [
'joaat',
true,
false,
],
'Hash: ripemd160' => [
'ripemd160',
true,
true,
],
'Hash: sha256' => [
'sha256',
true,
true,
],
'Hash: invalid' => [
'invalid',
false,
false
]
];
}
/**
* Undocumented function
*
* @covers ::isValidHashType
* @covers ::isValidHashHmacType
* @dataProvider providerHashTypes
* @testdox check if $hash_type is valid for hash $hash_ok and hash hmac $hash_hmac_ok [$_dataName]
*
* @param string $hash_type
* @param bool $hash_ok
* @param bool $hash_hmac_ok
* @return void
*/
public function testIsValidHashAndHashHmacTypes(string $hash_type, bool $hash_ok, bool $hash_hmac_ok): void
{
$this->assertEquals(
$hash_ok,
\CoreLibs\Create\Hash::isValidHashType($hash_type),
'hash valid'
);
$this->assertEquals(
$hash_hmac_ok,
\CoreLibs\Create\Hash::isValidHashHmacType($hash_type),
'hash hmac valid'
);
}
}
// __END__

View File

@@ -135,6 +135,7 @@ final class CoreLibsDBIOTest extends TestCase
}
// check if they already exist, drop them
if ($db->dbShowTableMetaData('table_with_primary_key') !== false) {
$db->dbExec("CREATE EXTENSION IF NOT EXISTS pgcrypto");
$db->dbExec("DROP TABLE table_with_primary_key");
$db->dbExec("DROP TABLE table_without_primary_key");
$db->dbExec("DROP TABLE test_meta");
@@ -4744,7 +4745,7 @@ final class CoreLibsDBIOTest extends TestCase
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
// all hast to be string
foreach ($res as $key => $value) {
$this->assertIsString($value, 'Aseert string for column: ' . $key);
$this->assertIsString($value, 'Assert string for column: ' . $key);
}
// convert base only
$db->dbSetConvertFlag(Convert::on);
@@ -4757,10 +4758,10 @@ final class CoreLibsDBIOTest extends TestCase
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
$this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
$this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break;
}
}
@@ -4774,13 +4775,13 @@ final class CoreLibsDBIOTest extends TestCase
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
$this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
$this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
$this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break;
}
}
@@ -4794,17 +4795,17 @@ final class CoreLibsDBIOTest extends TestCase
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
$this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
$this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break;
case 'json':
case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name);
$this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
$this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break;
}
}
@@ -4818,25 +4819,25 @@ final class CoreLibsDBIOTest extends TestCase
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
$this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
$this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break;
case 'json':
case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name);
$this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break;
case 'bytea':
// for hex types it must not start with \x
$this->assertStringStartsNotWith(
'\x',
$value,
'Aseert bytes not starts with \x for column: ' . $key . '/' . $name
'Assert bytes not starts with \x for column: ' . $key . '/' . $name
);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
$this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break;
}
}
@@ -5008,8 +5009,8 @@ final class CoreLibsDBIOTest extends TestCase
)
),
($params === null ?
$db->dbGetQueryHash($query) :
$db->dbGetQueryHash($query, $params)
$db->dbBuildQueryHash($query) :
$db->dbBuildQueryHash($query, $params)
),
'Failed assertdbGetQueryHash '
);
@@ -5235,6 +5236,9 @@ final class CoreLibsDBIOTest extends TestCase
$3
-- comment 3
, $4
-- ignore $5, $6
-- $7, $8
-- digest($9, 10)
)
SQL,
'count' => 4,
@@ -5305,8 +5309,57 @@ final class CoreLibsDBIOTest extends TestCase
SQL,
'count' => 2,
'convert' => false,
],
// special $$ string case
'text string, with $ placehoders that could be seen as $$ string' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_bytea = digest($3::VARCHAR, $4) OR
row_varchar = encode(digest($3, $4), 'hex') OR
row_bytea = hmac($3, $5, $4) OR
row_varchar = encode(hmac($3, $5, $4), 'hex') OR
row_bytea = pgp_sym_encrypt($3, $6) OR
row_varchar = encode(pgp_sym_encrypt($1, $6), 'hex') OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 6,
'convert' => false,
],
// NOTE, in SQL heredoc we cannot write $$ strings parts
'text string, with $ placehoders are in $$ strings' => [
'query' => '
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = $$some string$$ OR
row_varchar = $tag$some string$tag$ OR
row_varchar = $btag$some $1 string$btag$ OR
row_varchar = $btag$some $1 $subtag$ something $subtag$string$btag$ OR
row_varchar = $1
',
'count' => 1,
'convert' => false,
],
// a text string with escaped quite
'text string, with escaped quote' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = 'foo bar bar baz $5' OR
row_varchar = 'foo bar '' barbar $6' OR
row_varchar = E'foo bar \' barbar $7' OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 2,
'convert' => false,
]
];
$string = <<<SQL
'''
SQL;
}
/**