mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-01 06:28:46 +02:00
Merge branch 'support/3.2' into develop
# Conflicts: # tests/php-unit-tests/unitary-tests/core/ormDocumentTest.php
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use lnkFunctionalCIToTicket;
|
||||
use MetaModel;
|
||||
use ormLinkSet;
|
||||
use UserRequest;
|
||||
use UserRights;
|
||||
|
||||
class DBSearchFilterJoinTest extends ItopDataTestCase
|
||||
{
|
||||
private const RESTRICTED_PROFILE = 'Configuration Manager';
|
||||
private $aData = [];
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('application/startup.inc.php');
|
||||
$this->aData = $this->CreateDBSearchFilterTestData();
|
||||
DBSearch::EnableQueryCache(false, false);
|
||||
$this->LoginRestrictedUser($this->aData['allowed_org_id'], self::RESTRICTED_PROFILE);
|
||||
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testDBSearchFilterAppliedToJoinsWhenEnabled(string $sOql, int $iExpectedCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(true);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testDBSearchFilterAppliedToJoinsWhenDisabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(false);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testAllowAllDataBypassesDBSearchFilterWhenEnabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(true);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testAllowAllDataBypassesDBSearchFilterWhenDisabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(false);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
public function JoinedAndNestedOqlProvider(): array
|
||||
{
|
||||
return [
|
||||
'join-filter-on-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
],
|
||||
'nested-in-select' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF WHERE OSF.id IN (SELECT OSF1 FROM OSFamily AS OSF1 JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF1.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org)",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
|
||||
],
|
||||
'userrequest-join-person-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = VM.id JOIN UserRequest AS UR ON L.ticket_id = UR.id JOIN Person AS P ON UR.caller_id = P.id JOIN Organization AS O ON P.org_id = O.id WHERE O.name = :denied_org",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
],
|
||||
'union-join-filter-on-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org UNION SELECT OSF2 FROM OSFamily AS OSF2 JOIN VirtualMachine AS VM2 ON VM2.osfamily_id = OSF2.id JOIN Organization AS O2 ON VM2.org_id = O2.id WHERE O2.name = :allowed_org",
|
||||
'expected_filtered_count' => 1,
|
||||
'expected_unfiltered_count' => 2,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function EnableJoinFilterConfig(bool $bEnabled): void
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$oConfig->Set('security.disable_joined_classes_filter', !$bEnabled);
|
||||
}
|
||||
|
||||
private function CreateDBSearchFilterTestData(): array
|
||||
{
|
||||
$sSuffix = 'DBSearchFilterJoinTest';
|
||||
|
||||
$sAllowedOrgName = 'DBSearchFilterAllowedOrg-'.$sSuffix;
|
||||
$iAllowedOrgId = $this->GivenObjectInDB('Organization', [
|
||||
'name' => $sAllowedOrgName,
|
||||
]);
|
||||
|
||||
$this->debug("Org allowed id: $iAllowedOrgId");
|
||||
$sDeniedOrgName = 'DBSearchFilterDeniedOrg-'.$sSuffix;
|
||||
$iDeniedOrgId = $this->GivenObjectInDB('Organization', [
|
||||
'name' => $sDeniedOrgName,
|
||||
]);
|
||||
$this->debug("Org denied id: $iDeniedOrgId");
|
||||
|
||||
$iDeniedOsFamilyId = $this->GivenObjectInDB('OSFamily', [
|
||||
'name' => 'DBSearchFilterOsFamilyDenied-'.$sSuffix,
|
||||
]);
|
||||
|
||||
$iAllowedOsFamilyId = $this->GivenObjectInDB('OSFamily', [
|
||||
'name' => 'DBSearchFilterOsFamilyAllowed-'.$sSuffix,
|
||||
]);
|
||||
|
||||
$iDeniedVMId = $this->GivenObjectInDB('VirtualMachine', [
|
||||
'name' => 'DBSearchFilterVmDenied-'.$sSuffix,
|
||||
'org_id' => $iDeniedOrgId,
|
||||
'osfamily_id' => $iDeniedOsFamilyId,
|
||||
'virtualhost_id' => 1,
|
||||
]);
|
||||
|
||||
$iVirtualHostId = $this->GivenObjectInDB('Hypervisor', [
|
||||
'name' => 'DBSearchFilterVHost-'.$sSuffix,
|
||||
'org_id' => $iAllowedOrgId,
|
||||
]);
|
||||
|
||||
$this->GivenObjectInDB('VirtualMachine', [
|
||||
'name' => 'DBSearchFilterVmAllowed-'.$sSuffix,
|
||||
'org_id' => $iAllowedOrgId,
|
||||
'osfamily_id' => $iAllowedOsFamilyId,
|
||||
'virtualhost_id' => $iVirtualHostId,
|
||||
]);
|
||||
|
||||
$oDeniedPerson = $this->CreatePerson('Denied-'.$sSuffix, $iDeniedOrgId);
|
||||
|
||||
$oUserRequest = $this->CreateUserRequest('Denied'.$sSuffix, [
|
||||
'caller_id' => $oDeniedPerson->GetKey(),
|
||||
'org_id' => $iDeniedOrgId,
|
||||
]);
|
||||
|
||||
// Add Virtual Machine to UserRequest lnk
|
||||
$oLinkSet = new ormLinkSet(UserRequest::class, 'functionalcis_list', DBObjectSet::FromScratch(lnkFunctionalCIToTicket::class));
|
||||
|
||||
$oLink = MetaModel::NewObject(lnkFunctionalCIToTicket::class, ['functionalci_id' => $iDeniedVMId]);
|
||||
$oLinkSet->AddItem($oLink);
|
||||
|
||||
$oUserRequest->Set('functionalcis_list', $oLinkSet);
|
||||
$oUserRequest->DBUpdate();
|
||||
|
||||
return [
|
||||
'allowed_org_id' => $iAllowedOrgId,
|
||||
'allowed_org_name' => $sAllowedOrgName,
|
||||
'denied_org_name' => $sDeniedOrgName,
|
||||
];
|
||||
}
|
||||
|
||||
private function LoginRestrictedUser(int $iAllowedOrgId, string $sProfileName): void
|
||||
{
|
||||
$sLogin = $this->GivenUserRestrictedToAnOrganizationInDB($iAllowedOrgId, self::$aURP_Profiles[$sProfileName]);
|
||||
UserRights::Login($sLogin);
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,36 @@
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Application\WebPage\CaptureWebPage;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ormDocument;
|
||||
use UserRights;
|
||||
|
||||
/**
|
||||
* Tests of the ormDocument class
|
||||
*/
|
||||
class ormDocumentTest extends ItopDataTestCase
|
||||
{
|
||||
private const RESTRICTED_PROFILE = 'Configuration Manager';
|
||||
private int $iUserOrg;
|
||||
private int $iOrgDifferentFromUser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->iUserOrg = $this->GivenObjectInDB('Organization', [
|
||||
'name' => 'UserOrg',
|
||||
]);
|
||||
|
||||
$this->iOrgDifferentFromUser = $this->GivenObjectInDB('Organization', [
|
||||
'name' => 'OrgDifferentFromUser',
|
||||
]);
|
||||
|
||||
$this->LoginRestrictedUser($this->iUserOrg, self::RESTRICTED_PROFILE);
|
||||
$this->ResetMetaModelQueyCacheGetObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -248,4 +270,107 @@ class ormDocumentTest extends ItopDataTestCase
|
||||
$this->assertGreaterThanOrEqual($iMaxHeight, $aActualDimensions['height'], 'The new height should not be 0');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that DownloadDocument enforces rights for documents
|
||||
*
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testDownloadDocumentDifferentOrg(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iDeniedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iOrgDifferentFromUser, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageDenied = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageDenied, $sTargetClass, $iDeniedDocumentId, $sAttCode);
|
||||
$sDeniedHtml = (string) $oPageDenied->GetHtml();
|
||||
$this->assertStringContainsString(
|
||||
'the object does not exist or you are not allowed to view it',
|
||||
$sDeniedHtml,
|
||||
'Expected error message when rights are missing.'
|
||||
);
|
||||
$this->assertStringNotContainsString($sData, $sDeniedHtml, 'Unexpected file data present when rights are missing.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that DownloadDocument allows to retrieve document with the same org (or host object org)
|
||||
*
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testDownloadDocumentSameOrg(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iAllowedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iUserOrg, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageAllowed = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageAllowed, $sTargetClass, $iAllowedDocumentId, $sAttCode);
|
||||
$sAllowedHtml = (string) $oPageAllowed->GetHtml();
|
||||
$this->assertStringContainsString($sData, $sAllowedHtml, 'Expected file data present when rights are sufficient.');
|
||||
$this->assertStringNotContainsString('the object does not exist or you are not allowed to view it', $sAllowedHtml, 'Unexpected error message when rights are sufficient.');
|
||||
}
|
||||
|
||||
public function DownloadDocumentRightsProvider(): array
|
||||
{
|
||||
return [
|
||||
'DocumentFile' => [
|
||||
'class' => 'DocumentFile',
|
||||
'data_attribute_id' => 'file',
|
||||
'data' => 'document_data',
|
||||
'file_name' => 'document.txt',
|
||||
'host_class' => null],
|
||||
'Attachment' => [
|
||||
'class' => 'Attachment',
|
||||
'data_attribute_id' => 'contents',
|
||||
'data' => 'attachment_data',
|
||||
'file_name' => 'attachment.txt',
|
||||
'host_class' => 'UserRequest'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to avoid duplicating object creation in tests
|
||||
* Created objects and host objects depending on the Document class
|
||||
* @param string $sTargetClass
|
||||
* @param string $sAttCode
|
||||
* @param int $iOrgId
|
||||
* @param string $sData
|
||||
* @param string $sFileName
|
||||
* @param string|null $sHostClass
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function CreateDownloadTargetInOrg(string $sTargetClass, string $sAttCode, int $iOrgId, string $sData, string $sFileName, ?string $sHostClass): int
|
||||
{
|
||||
|
||||
if ($sTargetClass === 'DocumentFile') {
|
||||
return $this->GivenObjectInDB($sTargetClass, [
|
||||
'name' => 'UnitTestDocFile_'.uniqid(),
|
||||
'org_id' => $iOrgId,
|
||||
$sAttCode => new ormDocument($sData, 'text/plain', $sFileName),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($sTargetClass === 'Attachment') {
|
||||
$iHostId = $this->GivenObjectInDB($sHostClass, [
|
||||
'title' => 'UnitTestUserRequest_'.uniqid(),
|
||||
'org_id' => $iOrgId,
|
||||
'description' => 'A user request for testing attachment download rights',
|
||||
]);
|
||||
|
||||
return $this->GivenObjectInDB('Attachment', [
|
||||
'item_class' => $sHostClass,
|
||||
'item_id' => $iHostId,
|
||||
$sAttCode => new ormDocument($sData, 'text/plain', $sFileName),
|
||||
]);
|
||||
}
|
||||
|
||||
throw new \Exception("Unsupported target class: $sTargetClass");
|
||||
}
|
||||
|
||||
private function LoginRestrictedUser(int $iAllowedOrgId, string $sProfileName): void
|
||||
{
|
||||
if (UserRights::IsLoggedIn()) {
|
||||
UserRights::Logoff();
|
||||
}
|
||||
$sLogin = $this->GivenUserRestrictedToAnOrganizationInDB($iAllowedOrgId, self::$aURP_Profiles[$sProfileName]);
|
||||
UserRights::Login($sLogin);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user