Compare commits

...

7 Commits

Author SHA1 Message Date
jf-cbd
fea51d740d Update test 2026-03-05 16:49:17 +01:00
jf-cbd
06be85e142 WIP 2026-03-05 16:39:17 +01:00
jf-cbd
6f5eb69f48 WIP 2026-03-05 16:10:46 +01:00
jf-cbd
67805439f5 WIP 2026-03-04 18:12:04 +01:00
jf-cbd
38d725cc5a WIP 2026-03-04 16:13:00 +01:00
jf-cbd
2067940a37 WIP 2026-03-03 18:21:52 +01:00
jf-cbd
685373c60a Set GetModuleInfo public so it can be called from outside 2026-03-03 18:03:00 +01:00
10 changed files with 326 additions and 1 deletions

View File

@@ -1738,6 +1738,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.force_login_when_no_authentication_policy' => [
'type' => 'bool',
'description' => 'If true, when no execution policy is defined, the user will be forced to log in (instead of being automatically logged in with the default profile)',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',

View File

@@ -97,4 +97,40 @@ if ($sTargetPage === false) {
//
// GO!
//
// check module white list
// check conf param
// force login if needed
require_once(APPROOT.'/application/startup.inc.php');
$aModuleDelegatedExecutionPolicy = GetModuleDelegatedExecutionPolicy($sModule);
if (is_null($aModuleDelegatedExecutionPolicy) || !in_array($sPage, $aModuleDelegatedExecutionPolicy)) {
// TODO in N°9343 : remove the conf 'security.force_login_when_no_authentication_policy' to perform login by default when no execution policy is defined
$bForceLoginWhenNoExecutionPolicy = MetaModel::GetConfig()->Get('security.force_login_when_no_authentication_policy');
// TODO in N°9343 : remove the conf and this 'if' condition to perform login by default when no execution policy is defined
if ($bForceLoginWhenNoExecutionPolicy) {
LoginWebPage::DoLoginEx();
}
}
if (is_null($aModuleDelegatedExecutionPolicy) && !MetaModel::GetConfig()->Get('security.force_login_when_no_authentication_policy')) {
// TODO in N°9343 : remove this if statement and its content
// check if user is not logged in, if not log a warning in the log file as the page is executed without login, which is not recommended for security reason
if (is_null(UserRights::GetUserId())) {
IssueLog::Warning("The page '$sPage' is called be executed without login. In the future, this call will be blocked, and will likely cause unwanted behavior in the module '$sModule'.
Please define an execution policy for the module as described in https://www.itophub.io/wiki/page?id=3_2_0:customization:new_extension#security.");
}
}
if (is_array($aModuleDelegatedExecutionPolicy) && !in_array($sPage, $aModuleDelegatedExecutionPolicy)) {
// if module defined a delegated execution policy but not for the current page, we consider that the page is not allowed to be executed without login
LoginWebPage::DoLoginEx();
}
require_once($sTargetPage);
function GetModuleDelegatedExecutionPolicy(string $sModuleName): ?array
{
$sModuleFile = utils::GetAbsoluteModulePath($sModuleName).'/module.'.$sModuleName.'.php';
$oExtensionMap = new iTopExtensionsMap();
$aModuleParam = $oExtensionMap->GetModuleInfo($sModuleFile)[2];
return $aModuleParam['execution_policy'] ?? null;
}

View File

@@ -390,7 +390,7 @@ class iTopExtensionsMap
* @param string $sModuleFile
* @return array
*/
protected function GetModuleInfo($sModuleFile)
public function GetModuleInfo($sModuleFile)
{
static $iDummyClassIndex = 0;

View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace Combodo\iTop\Test\UnitTest\Application;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use Exception;
use MetaModel;
class LoginWebPageTest extends ItopDataTestCase
{
public const USE_TRANSACTION = false;
public const PASSWORD = 'a209320P!ù;ralùqpi,pàcqi"nr';
public function setUp(): void
{
parent::setUp();
$this->BackupConfiguration();
$sFolderPath = APPROOT.'env-production/extension-with-execution-policy';
if (file_exists($sFolderPath)) {
throw new Exception("Folder $sFolderPath already exists, please remove it before running the test");
}
mkdir($sFolderPath);
$this->RecurseCopy(__DIR__.'/extension-with-execution-policy', $sFolderPath);
$sFolderPath = APPROOT.'env-production/extension-without-execution-policy';
if (file_exists($sFolderPath)) {
throw new Exception("Folder $sFolderPath already exists, please remove it before running the test");
}
mkdir($sFolderPath);
$this->RecurseCopy(__DIR__.'/extension-without-execution-policy', $sFolderPath);
}
public function tearDown(): void
{
parent::tearDown();
$sFolderPath = APPROOT.'env-production/extension-with-execution-policy';
if (file_exists($sFolderPath)) {
$this->RecurseRmdir($sFolderPath);
} else {
throw new Exception("Folder $sFolderPath does not exist, it should have been created in setUp");
}
$sFolderPath = APPROOT.'env-production/extension-without-execution-policy';
if (file_exists($sFolderPath)) {
$this->RecurseRmdir($sFolderPath);
} else {
throw new Exception("Folder $sFolderPath does not exist, it should have been created in setUp");
}
}
protected function GivenConfigFileAllowedLoginTypes($aAllowedLoginTypes): void
{
@chmod(MetaModel::GetConfig()->GetLoadedFile(), 0770);
MetaModel::GetConfig()->SetAllowedLoginTypes($aAllowedLoginTypes);
MetaModel::GetConfig()->WriteToFile();
@chmod(MetaModel::GetConfig()->GetLoadedFile(), 0444);
}
/**
*
* @throws \Exception
*/
public function testInExecutionPolicyFile()
{
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-execution-policy&exec_page=src/Controller/FileInExecutionPolicy.php",
[],
[],
true
);
$this->assertStringNotContainsString('<title>iTop login</title>', $sPageContent, 'File listed in execution policy file (in the module), login should not be requested by exec, file handle its own policy');
}
public function testUserCanAccessAnyFile()
{
// generate random login
$sUserLogin = 'user-'.date('YmdHis');
$this->CreateUser($sUserLogin, self::$aURP_Profiles['Service Desk Agent'], self::PASSWORD);
$this->GivenConfigFileAllowedLoginTypes(explode('|', 'form'));
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-execution-policy&exec_page=src/Controller/FileNotInExecutionPolicy.php",
[
'auth_user' => $sUserLogin,
'auth_pwd' => self::PASSWORD,
],
[],
true
);
$this->assertStringContainsString('Yo', $sPageContent, 'Logged in user should access any file via exec.php even if the page isn\'t listed in execution policy');
}
public function testNoPolicyFileWithForceLoginConf()
{
MetaModel::GetConfig()->Set('security.force_login_when_no_authentication_policy', true);
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-execution-policy&exec_page=src/Controller/FileNotInExecutionPolicy.php",
);
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'if itop is configured to force login when no execution policy, then login should be required even if there is no policy file');
}
public function testNoPolicyFileWithDefaultConfiguration()
{
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-without-execution-policy&exec_page=src/Controller/File.php",
[],
[],
true
);
$this->assertStringContainsString('Yo', $sPageContent, 'by default (until N°9343) if no execution policy is defined, not logged in persons should access pages');
}
public function testNotInExecutionPolicy()
{
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-execution-policy&exec_page=src/Controller/FileNotInExecutionPolicy.php",
[],
[],
true
);
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'Since an execution policy is defined and file isn\'t listed in it, login should be required');
}
/**
* @dataProvider InExecutionPolicyFileWithAdminRequiredProvider
*
* @throws \Exception
*/
public function testInExecutionPolicyFileWithAdminRequired($iProfileId, $bShouldSeeForbiddenAdminPage)
{
// generate random login
$sUserLogin = 'user-'.date('YmdHis');
$this->CreateUser($sUserLogin, $iProfileId, self::PASSWORD);
$this->GivenConfigFileAllowedLoginTypes(explode('|', 'form'));
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-execution-policy&exec_page=src/Controller/FileInExecutionPolicyAndAdminRequired.php",
[
'auth_user' => $sUserLogin,
'auth_pwd' => self::PASSWORD,
],
[],
true
);
$bShouldSeeForbiddenAdminPage ?
$this->assertStringNotContainsString('<title>Access restricted to people having administrator privileges</title>', $sPageContent, 'Should prevent non admin user to access this page') : // in execution policy file (in the module), login should not be required, file handle its own policy
$this->assertStringContainsString('Yo !', $sPageContent, 'Should execute the file and see its content since user has admin profile');
}
public function InExecutionPolicyFileWithAdminRequiredProvider()
{
return [
'Administrator profile' => [
self::$aURP_Profiles['Administrator'],
'Should see forbidden admin page' => false,
],
'ReadOnly profile' => [
self::$aURP_Profiles['Service Desk Agent'],
'Should see forbidden admin page' => true,
],
];
}
}

View File

@@ -0,0 +1,51 @@
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'extension-with-execution-policy/0.0.1',
[
// Identification
//
'label' => 'Templates foundation',
'category' => 'business',
// Setup
//
'dependencies' => [],
'mandatory' => true,
'visible' => false,
'installer' => 'TemplatesBaseInstaller',
// Security
'execution_policy' => [
'src/Controller/FileInExecutionPolicy.php',
'src/Controller/CheckAnythingButAdminRequired.php',
],
// Components
//
'datamodel' => [
'model.templates-base.php',
],
'webservice' => [],
'data.struct' => [// add your 'structure' definition XML files here,
],
'data.sample' => [// add your sample data XML files here,
],
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => [
// Select where, in the main UI, the extra data should be displayed:
// tab (dedicated tab)
// properties (right after the properties, but before the log if any)
// none (extra data accessed only by programs)
'view_extra_data' => 'relations',
],
]
);

View File

@@ -0,0 +1,45 @@
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'extension-without-execution-policy/0.0.1',
[
// Identification
//
'label' => 'Templates foundation',
'category' => 'business',
// Setup
//
'dependencies' => [],
'mandatory' => true,
'visible' => false,
'installer' => 'TemplatesBaseInstaller',
// Components
//
'datamodel' => [
'model.templates-base.php',
],
'webservice' => [],
'data.struct' => [// add your 'structure' definition XML files here,
],
'data.sample' => [// add your sample data XML files here,
],
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => [
// Select where, in the main UI, the extra data should be displayed:
// tab (dedicated tab)
// properties (right after the properties, but before the log if any)
// none (extra data accessed only by programs)
'view_extra_data' => 'relations',
],
]
);