maison_alexandra/tests/BasePhpUnitCase.php

626 lines
20 KiB
PHP

<?php
namespace ElixirAudit\Tests;
use BRSystems\Core\Library\cCoreSystem\cCoreSystem;
use BRSystems\Core\Library\cDb\cDb;
use BRSystems\Core\Library\cDefuseCrypto\cDefuseCrypto;
use BRSystems\Core\Library\cGoogle2FA\cGoogle2FA;
use BRSystems\Core\Library\cINI\cINI;
use BRSystems\Core\Library\cUrl\cUrl;
use Exception;
use Http\Client\Curl\Client;
use MyCLabs\Enum\Enum;
use Nyholm\Psr7\Factory\Psr17Factory;
use PHPUnit\Framework\TestCase;
use BRSystems\Core\App\pubUsers\pubUsers;
use BRSystems\Core\App\pubUsers\inc\pubUsersController;
use ReflectionClass;
use rpkamp\Mailhog\MailhogClient;
use rpkamp\Mailhog\Message\Contact;
use rpkamp\Mailhog\Message\Message;
use rpkamp\Mailhog\NoSuchMessageException;
use BRSystems\Core\Sys\sysUsers\sysUsers;
use ElixirAudit\Tests\DataSetup\Constants\siteMap;
use ElixirAudit\Tests\DataSetup\Constants\siteUsers;
/**
* @method static DbTitleEnum E_ELIXIR()
*/
final class DbTitleEnum extends Enum
{
private const E_ELIXIR = 'elixir';
}
abstract class BasePhpUnitCase extends TestCase
{
/**
* @var cDb
*/
protected cDb $cDb;
/**
* @var String - Mysql DB title
*/
protected string $mysqlName = 'maison_alexandra_lv';
/**
* @var String - Mysql connection user
*/
protected string $mysqlUser = 'dAccGlobal';
/**
* @var String - Mysql connection user password
*/
protected string $mysqlPass = 'dAccGlobal12!@';
/**
* @var String - Mysql connection host
*/
protected string $mysqlHost = 'maisonalexandra_mysql';
/**
* @var String - Mysql connection port
*/
protected string $mysqlPort = '3306';
/**
* @var Int - Mail send delay timeout
*/
protected int $mailSendTimeout = 14;
/**
* @var Int - Server port
*/
protected int $serverPort = 80;
/**
* @var bool - Unit test case
*/
protected bool $unitTestsAreRunning = false;
/**
* @var string
*/
protected string $mailHogAPIRequestUrl = 'http://maisonalexandra_mailhog:8025/';
protected pubUsers $pubUser;
protected sysUsers $cmsUser;
protected function setUp(): void
{
parent::setUp();
/*** define the site path constant ***/
if(!defined('__MDIR__')) {
define('__MDIR__', realpath(__DIR__ . '/../www/') . '/');
}
// Set default
ini_set('date.timezone', 'Europe/Riga');
date_default_timezone_set('Europe/Riga');
ini_set('display_errors', 'On');
ini_set('error_reporting', E_ALL);
// Increase time & memory limit
set_time_limit(60 * 60 * 3);
ini_set('memory_limit', '2048M');
// Define base sql constants for system to use
if (!defined('DB_HOST')) define('DB_HOST', $this->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.elixir';
$_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' => "<b>test data </b>",
];
}
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;
}
}