mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-07 10:04:14 +01:00
Compare commits
9 Commits
develop
...
issue/8543
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69006c1e2f | ||
|
|
a02db83d0a | ||
|
|
fea51d740d | ||
|
|
06be85e142 | ||
|
|
6f5eb69f48 | ||
|
|
67805439f5 | ||
|
|
38d725cc5a | ||
|
|
2067940a37 | ||
|
|
685373c60a |
@@ -1738,6 +1738,14 @@ class Config
|
|||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
],
|
],
|
||||||
|
'security.force_login_when_no_delegated_authentication_endpoints_list' => [
|
||||||
|
'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' => [
|
'behind_reverse_proxy' => [
|
||||||
'type' => 'bool',
|
'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)',
|
'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)',
|
||||||
|
|||||||
@@ -97,4 +97,37 @@ if ($sTargetPage === false) {
|
|||||||
//
|
//
|
||||||
// GO!
|
// GO!
|
||||||
//
|
//
|
||||||
|
// check module white list
|
||||||
|
// check conf param
|
||||||
|
// force login if needed
|
||||||
|
require_once(APPROOT.'/application/startup.inc.php');
|
||||||
|
|
||||||
|
$aModuleDelegatedAuthenticationEndpoints = GetModuleDelegatedAuthenticationEndpoints($sModule);
|
||||||
|
if (is_null($aModuleDelegatedAuthenticationEndpoints) || !in_array($sPage, $aModuleDelegatedAuthenticationEndpoints)) {
|
||||||
|
$bForceLoginWhenNoDelegatedAuthenticationEndpoints = MetaModel::GetConfig()->Get('security.force_login_when_no_delegated_authentication_endpoints_list');
|
||||||
|
if ($bForceLoginWhenNoDelegatedAuthenticationEndpoints) {
|
||||||
|
LoginWebPage::DoLoginEx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_null($aModuleDelegatedAuthenticationEndpoints) && !MetaModel::GetConfig()->Get('security.force_login_when_no_delegated_authentication_endpoints_list')) {
|
||||||
|
// 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 a delegated authentication endpoints for the module as described in https://www.itophub.io/wiki/page?id=latest:customization:new_extension#security.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_array($aModuleDelegatedAuthenticationEndpoints) && !in_array($sPage, $aModuleDelegatedAuthenticationEndpoints)) {
|
||||||
|
// if module defined a delegated authentication endpoints but not for the current page, we consider that the page is not allowed to be executed without login
|
||||||
|
LoginWebPage::DoLoginEx();
|
||||||
|
}
|
||||||
|
|
||||||
require_once($sTargetPage);
|
require_once($sTargetPage);
|
||||||
|
|
||||||
|
function GetModuleDelegatedAuthenticationEndpoints(string $sModuleName): ?array
|
||||||
|
{
|
||||||
|
$sModuleFile = utils::GetAbsoluteModulePath($sModuleName).'/module.'.$sModuleName.'.php';
|
||||||
|
|
||||||
|
$oExtensionMap = new iTopExtensionsMap();
|
||||||
|
$aModuleParam = $oExtensionMap->GetModuleInfo($sModuleFile)[2];
|
||||||
|
return $aModuleParam['delegated_authentication_endpoints'] ?? null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ class iTopExtensionsMap
|
|||||||
* @param string $sModuleFile
|
* @param string $sModuleFile
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function GetModuleInfo($sModuleFile)
|
public function GetModuleInfo($sModuleFile)
|
||||||
{
|
{
|
||||||
static $iDummyClassIndex = 0;
|
static $iDummyClassIndex = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -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-delegated-authentication-endpoints-list';
|
||||||
|
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-delegated-authentication-endpoints-list', $sFolderPath);
|
||||||
|
|
||||||
|
$sFolderPath = APPROOT.'env-production/extension-without-delegated-authentication-endpoints-list';
|
||||||
|
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-delegated-authentication-endpoints-list', $sFolderPath);
|
||||||
|
}
|
||||||
|
public function tearDown(): void
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
$sFolderPath = APPROOT.'env-production/extension-with-delegated-authentication-endpoints-list';
|
||||||
|
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-delegated-authentication-endpoints-list';
|
||||||
|
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 testInDelegatedAuthenticationEndpoints()
|
||||||
|
{
|
||||||
|
$sPageContent = $this->CallItopUri(
|
||||||
|
"pages/exec.php?exec_module=extension-with-delegated-authentication-endpoints-list&exec_page=src/Controller/FileInDelegatedAuthenticationEndpointsList.php",
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertStringNotContainsString('<title>iTop login</title>', $sPageContent, 'File listed in delegated authentication endpoints list (in the module), login should not be requested by exec.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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-delegated-authentication-endpoints-list&exec_page=src/Controller/FileNotInDelegatedAuthenticationEndpointsList.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 delegated authentication endpoints list');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNotInDelegatedAuthenticationEndpointsListWithForceLoginConf()
|
||||||
|
{
|
||||||
|
MetaModel::GetConfig()->Set('security.force_login_when_no_delegated_authentication_endpoints_list', true);
|
||||||
|
|
||||||
|
$sPageContent = $this->CallItopUri(
|
||||||
|
"pages/exec.php?exec_module=extension-with-delegated-authentication-endpoints-list&exec_page=src/Controller/FileNotInDelegatedAuthenticationEndpointsList.php",
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'if itop is configured to force login when no there is no delegated authentication endpoints list, then login should be required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoDelegatedAuthenticationEndpointsListWithDefaultConfiguration()
|
||||||
|
{
|
||||||
|
$sPageContent = $this->CallItopUri(
|
||||||
|
"pages/exec.php?exec_module=extension-without-delegated-authentication-endpoints-list&exec_page=src/Controller/File.php",
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertStringContainsString('Yo', $sPageContent, 'by default (until N°9343) if no delegated authentication endpoints list is defined, not logged in persons should access pages');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNotInDelegatedAuthenticationEndpointsList()
|
||||||
|
{
|
||||||
|
$sPageContent = $this->CallItopUri(
|
||||||
|
"pages/exec.php?exec_module=extension-with-delegated-authentication-endpoints-list&exec_page=src/Controller/FileNotInDelegatedAuthenticationEndpointsList.php",
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'Since an delegated authentication endpoints list is defined and file isn\'t listed in it, login should be required');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider InDelegatedAuthenticationEndpointsWithAdminRequiredProvider
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function testInDelegatedAuthenticationEndpointsWithAdminRequired($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-delegated-authentication-endpoints-list&exec_page=src/Controller/FileInDelegatedAuthenticationEndpointsListAndAdminRequired.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 delegated authentication endpoints list (in the module), login should not be required
|
||||||
|
$this->assertStringContainsString('Yo !', $sPageContent, 'Should execute the file and see its content since user has admin profile');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function InDelegatedAuthenticationEndpointsWithAdminRequiredProvider()
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
'delegated_authentication_endpoints' => [
|
||||||
|
'src/Controller/FileInDelegatedAuthenticationEndpointsList.php',
|
||||||
|
'src/Controller/FileInDelegatedAuthenticationEndpointsAndAdminRequiredList.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',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
@@ -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',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
echo 'Yo !';
|
||||||
Reference in New Issue
Block a user