mysqlHost); if (!defined('DB_USR')) define('DB_USR', $this->mysqlUser); if (!defined('DB_PWD')) define('DB_PWD', $this->mysqlPass); if (!defined('DB_PORT')) define('DB_PORT', $this->mysqlPort); if (!defined('DB_NAME')) define('DB_NAME', $this->mysqlName); // To be replaced by namespaces later!!!! include_once(__DIR__ . "/../www/system-brs-core/config/constants.php"); include_once(__DIR__ . "/../www/system-brs-core/functions/com.funct.php"); // Set handler functions // Setup without namespaces before namespaces ar implemented into project - system autoloader set_error_handler('errorHandler'); if ($this->unitTestsAreRunning) { // Set flag while global tests are running $GLOBALS['coverage_tests_running'] = true; // Load site configuration file $oCfg = new cINI(__CFG__ . 'init.cfg.ini'); $oCfg->loadSystemConstants(); // Set default values host, server port, etc. if ( empty($_SERVER['HTTP_HOST']) && $sDefaultHost = $oCfg->getV('system', 'DEFAULT_HOST') ) { $_SERVER['HTTP_HOST'] = $sDefaultHost; } if (empty($_SERVER['SERVER_PORT'])) { $_SERVER['SERVER_PORT'] = $this->serverPort; } foreach (['SERVER_ADDR', 'REQUEST_URI', 'REMOTE_ADDR'] as $key) { if (empty($_SERVER[$key])) { $_SERVER[$key] = $this->serverPort; } } // Set request type - while testing POST needs to be specified $this->setUnitTestRequestType(); } $this->cDb = cDb::getInstance($this->mysqlHost, $this->mysqlUser, $this->mysqlPass, $this->mysqlPort, $this->mysqlName, 'SeleniumDbConnection'); $this->cDb->disableDispose = true; // Reload base data tables $this->reloadDbTables(['mod_users', 'mod_account_manager']); } protected function tearDown(): void { parent::tearDown(); } protected function dbSetup(array $files): void { try { foreach ($files as $file) { $file = __DIR__ . '/DataSetup/' . $file; if (!file_exists($file)) { throw new Exception('DB setup file not found: ' . $file); } $sql = preg_split('/[.+;][\s]*\n/', file_get_contents($file), -1, PREG_SPLIT_NO_EMPTY); if (!is_array($sql)) { continue; } foreach ($sql as $query) { if (empty(trim($query))) { continue; } $this->cDb->query($query); } } } catch (Exception $e) { $this->fail($e->getMessage()); } } protected function reloadDbTables(array $tables = null, DbTitleEnum $baseTable = null): void { if (is_null($baseTable)) { $baseTable = DbTitleEnum::E_ZUTENIS(); } if (!is_null($tables)) { foreach ($tables as $table) { $this->cDb->query("TRUNCATE TABLE `{$baseTable}`.`{$table}`"); $this->cDb->query("LOAD DATA INFILE '/opt/data/{$baseTable}/$table.txt' INTO TABLE `{$baseTable}`.`{$table}`"); } } } /** * Function to exec insert with a data array * * @param string $table - table name * @param array $data - fields to insert with key to field structure * @return array|bool - array with ids */ protected function insertTestDataMulti(string $table, array $data) { if (empty($data)) { return false; } $ids = $this->cDb->_queryInsertArray($table, $data); return $ids ?: false; } protected function updateTestDataMulti(string $table, array $data, string $idColumn = 'id'): bool { if (empty($data) || !$idColumn) { return false; } return $this->cDb->_queryUpdateMulti($table, $data, $idColumn); } protected function assertExpected(array $expectedData, string $output, bool $convertToLowercase = false): void { foreach ($expectedData as $expected) { if ($convertToLowercase) { $this->assertStringContainsString(mb_strtolower($expected), mb_strtolower($output)); } else { $this->assertStringContainsString($expected, $output); } } } protected function emailHtmlDecode(string $data = ''): string { return html_entity_decode($data, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8'); } protected function getAndSetStructureForFirstMatchedUrl(int $mapId): array { $url = new cUrl(); $urlData = getFirstMatchByMapId($mapId); $_SERVER['SERVER_PORT'] = 80; $_SERVER['HTTPS'] = 'off'; $_SERVER['HTTP_HOST'] = 'local.loreta'; $_SERVER['REQUEST_URI'] = $url->rmSlashesIndex($urlData['p_full_url']); $_SERVER['REMOTE_ADDR'] = ''; $_SERVER['SERVER_ADDR'] = ''; return $urlData; } protected function mprint(): void { // Get all arguments $vPassedArguments = func_get_args(); if(!!$vPassedArguments){ $sDebugOutput = PHP_EOL . 'START DEBUG OUTPUT:' . PHP_EOL; foreach($vPassedArguments as $vData) $sDebugOutput .= PHP_EOL . print_r($vData, true) . PHP_EOL; die($sDebugOutput . PHP_EOL . 'END OF DEBUG!'); } die('NO ARGUMENTS PASSED!'); } function getLoginCredentialsByType(string $userType = siteUsers::loginCmsAdmin): array { return siteUsers::loginData[$userType] ?? []; } protected function setUnitTestRequestType(bool $isGET = true, bool $isPOST = false): void { if ($isPOST) { $_SERVER['REQUEST_METHOD'] = 'POST'; } if (!defined('POST_REQ')) define('POST_REQ', $isPOST); if (!defined('GET_REQ')) define('GET_REQ', $isGET); } protected function initBaseClassObjectBySiteMapId( string $siteMapId = SiteMap::PUB_LOGIN, ?string $loginUser = siteUsers::loginAppSelfEmployed, bool $security2FAEnabled = true, int $selYear = 2022, array $unlockYear = [2026, 2025, 2024, 2023, 2022] ): ?object { $app = new cCoreSystem(); $this->getAndSetStructureForFirstMatchedUrl($siteMapId); // Check if from previous test user is not still logged in - otherwise log out user $this->logOutUnitTestUser(); // System modules requires users to be logged in if (!is_null($loginUser)) { $this->loginUnitTestUserForCoverage($loginUser, false, $security2FAEnabled); } // Return null on 2FA disabled if ($security2FAEnabled) { return $app->launch(true); } else { try { $app->launch(true); } catch (Exception $error) { $this->assertExpected( ['Redirected to Location: /ienakt-gramatvedibas-sistema/divu-faktoru-iestatisana'], $error->getMessage() ); } } return null; } protected function loginUnitTestUserForCoverage( string $loginUser = siteUsers::loginAppSelfEmployed, bool $isCmsUser = false, bool $security2FAEnabled = true ): void { // Simulate user login if ($isCmsUser) { $this->cmsUser = new sysUsers(getFirstMatchByMapId(SiteMap::PUB_LOGIN)); } else { $this->pubUser = new pubUsers(getFirstMatchByMapId(SiteMap::PUB_LOGIN)); // If user already logged in continue if ($this->pubUser->controller->isLoggedIn()) { return; } $userData = $this->getLoginCredentialsByType($loginUser); // Log in & catch redirect exception try { $this->pubUser->controller->loginUser( [ 'chkTerms' => 1, 'user_login' => $userData['user'], 'user_password' => $userData['password'], ] ); } catch (Exception $error) { if ($security2FAEnabled) { $this->assertExpected(['Redirected to Location'], $error->getMessage()); } else { $this->assertExpected( ['"jsonok":true,', '"url":"\/gramatvedibas-sistema\/konta-izvele"'], $error->getMessage() ); } } // Submit 2fa security & catch json send data exception if ($security2FAEnabled) { try { $oGoogle2FA = new cGoogle2FA(); $next2FACode = $oGoogle2FA->getGoogleAuthCode($userData['2fa']); $_POST['code'] = $next2FACode; $this->pubUser->controller->set2FAForm(); } catch (Exception $error) { $this->assertExpected(['"jsonok":true'], $error->getMessage()); } } // Check if user company is set if ($this->pubUser->controller->selCompId) { return; } } } protected function logOutUnitTestUser(bool $isCmsUser = false): void { try { if ($isCmsUser) { $this->cmsUser->controller->logOut(); } else { if (!isset($this->pubUser)) { $this->pubUser = new pubUsers(getFirstMatchByMapId(SiteMap::PUB_LOGIN)); } $this->pubUser->controller->logOut(); } } catch (Exception $error) { if ($isCmsUser) { $this->assertFalse($this->cmsUser->controller->isLoggedIn()); } else { $this->assertFalse($this->pubUser->controller->isLoggedIn()); } } } protected function resetGetAndPost(array $data = [], bool $destroyPrev = true): void { $this->setPost(); $this->setGet(); } protected function destroyUserSession(): void { unset($_SESSION[pubUsersController::USER_SESSION_KEY]); unset($_SESSION[pubUsersController::USER_2FA_SESSION_KEY]); unset($_SESSION[pubUsersController::USER_PWD_SESSION_KEY]); } protected function setPost(array $data = [], bool $destroyPrev = true): void { if ($destroyPrev) { $_POST = []; } if (!!$data) { foreach ($data as $key => $value) { $_POST[$key] = $value; } } } protected function setGet(array $data = [], bool $destroyPrev = true): void { if ($destroyPrev) { $_GET = []; } if (!!$data) { foreach ($data as $key => $value) { $_GET[$key] = $value; } } } protected function testModuleLoginRequirement(string $siteMapId = SiteMap::PUB_LOGIN): void { try { $this->logOutUnitTestUser(); // Create new class $this->initBaseClassObjectBySiteMapId($siteMapId, null); } catch (Exception $error) { $this->assertExpected( ['Redirected to Location: /ienakt-gramatvedibas-sistema'], $error->getMessage() ); } } protected function clearMailHogMessages(): void { $client = new MailhogClient(new Client(), new Psr17Factory(), new Psr17Factory(), $this->mailHogAPIRequestUrl); $client->purgeMessages(); } protected function awaitEmailFromMailHog( string $recipient = null, string $subject = null, int $timeout = 30, bool $presumeMailboxIsEmpty = false ): Message { $reqTime = time() - 5; // Create connection to MailHog $client = new MailhogClient(new Client(), new Psr17Factory(), new Psr17Factory(), $this->mailHogAPIRequestUrl); $time = 0; $error = null; while ($time <= $timeout) { // Init timeout sleep(1); $time++; try { $messages = $client->getLastMessage(); $messageTime = strtotime($messages->headers->get('date')); // Ignore older messages and empty mailbox flag is not set if (!$presumeMailboxIsEmpty && $messageTime < $reqTime) { continue; } # Search for recipient & title if (!is_null($recipient) && !is_null($subject)) { if ( $messages->recipients->contains(Contact::fromString($recipient)) && $messages->subject == $subject ) { return $messages; } continue; } # Search for recipient if ( !is_null($recipient) && $messages->recipients->contains(Contact::fromString($recipient)) ) { return $messages; } # Search for title if ( !is_null($subject) && $messages->subject == $subject ) { return $messages; } // Default if ( is_null($recipient) && is_null($subject) ) { return $messages; } } catch (NoSuchMessageException $error) { // Continue waiting for message } } // Max timeout reached throw new NoSuchMessageException($error); } public function setProtectedProperty($classObject, $property, $value): void { $reflection = new ReflectionClass($classObject); $reflection_property = $reflection->getProperty($property); $reflection_property->setAccessible(true); $reflection_property->setValue($classObject, $value); } public function setUserPermission(string $permissionKey): void { // Add pub user permission without ID $_SESSION['pub-user']['permissions'][] = $permissionKey; } public function lockOrUnlockCompanyDetailsYear(int $year, bool $locked): void { // After usage needs to reload table mod_account_manager && clear apc from db $partners = $this->initBaseClassObjectBySiteMapId(SiteMap::PUB_PARTNERS); $partners->controller->saveYearsSettings(['id' => $year, 'locked' => $locked ? 1 : 0 ]); } public function getSqlDataInjectionCfg(string $tableName = 'some_db_table'): array { return [ 'SMPOL' => "DROP $tableName;--", 'M' => "DROP $tableName;#", 'M2' => "data' --", 'Bypassing login screens (SMO+)' => "admin' --", 'Bypassing login screens (SMO+) 1' => "admin' #", 'Bypassing login screens (SMO+) 2' => "admin'/*", 'Bypassing login screens (SMO+) 3' => "' or 1=1--", 'Bypassing login screens (SMO+) 4' => "' or 1=1#", 'Bypassing login screens (SMO+) 5' => "' or 1=1/*", 'Bypassing login screens (SMO+) 6' => "') or '1'='1--", 'Bypassing login screens (SMO+) 7' => "') or ('1'='1--", 'Obfuscation' => "test;DROP/*comment*/$tableName", 'Breaking up a keyword to avoid filters' => ";DR/**/OP/*bypass blacklisting*/$tableName", 'Removing space characters' => ";SELECT/*avoid-spaces*/password/**/FROM/**/$tableName", 'ID value' => "10; DROP TABLE $tableName /*", 'ID value 2' => "10; DROP TABLE $tableName --", 'Stacking Queries - MSP' => "1; DROP $tableName--", 'Stacking Queries - MSP ID value' => "10;DROP $tableName --", 'Error-based ways to discover column information' => "' HAVING 1=1", 'Simple INSERT payload' => "'; insert into $tableName values( 1, 'hax0r', 'coolpass', 9 )/*", 'sample insert data' => "''ert'' // */=-+12!@#$%^&*()", 'sample insert data 2' => "āēšģīķļžčņ", 'html' => "test data ", ]; } public function updateCfgStructure( string $updateText, array $structure, array $fields = [], array $excludeFields = [] ): array { $updatedFields = $structure; if (empty($structure) || !strlen($updateText)) { return $structure; } // Keep only the requested fields if (!empty($fields)) { $updatedFields = $fields; } // Get all fields except the passed excluded if (!empty($excludeFields)) { $updatedFields = array_diff_key($updatedFields, array_flip($excludeFields)); } // Fill data from config $filledArray = array_fill_keys(array_keys($updatedFields), $updateText); return array_merge($structure , $filledArray); } protected function switchDbSchema(DbTitleEnum $schema) { $this->cDb = cDb::getInstance( $this->mysqlHost, $this->mysqlUser, $this->mysqlPass, $this->mysqlPort, $schema, 'SeleniumDbConnection', ); $this->cDb->disableDispose = true; } }