mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-21 16:22:20 +02:00
N°9614 - Add a specific route to display the icons for an EventNotificationNewsroom (#911)
* N°9614 - Add a specific route to display the icons for an EventNotificationNewsroom * N°9614 - Display the icon in EventNotificationNewsroom only for events relating to the logged-in user * N°9614 - Refactor icon download logic in EventNotificationNewsroomService and add unit tests * Add unit test to verify icon download functionality for users without read access
This commit is contained in:
@@ -340,13 +340,14 @@ class ormDocument
|
||||
* @param string $sContentDisposition Either 'inline' or 'attachment'
|
||||
* @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
|
||||
* @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
|
||||
* @param bool $bAllowAllData If true, no rights filtering is applied
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null, $bAllowAllData = false)
|
||||
{
|
||||
try {
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, false);
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, $bAllowAllData);
|
||||
if (!is_object($oObj)) {
|
||||
// If access to the document is not granted, check if the access to the host object is allowed
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, true);
|
||||
|
||||
@@ -16,10 +16,12 @@ use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectSummary;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
use Combodo\iTop\Application\WebPage\DownloadPage;
|
||||
use Combodo\iTop\Application\WebPage\iTopWebPage;
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use Combodo\iTop\Application\WebPage\JsonPPage;
|
||||
use Combodo\iTop\Controller\Notifications\NotificationsCenterController;
|
||||
use Combodo\iTop\Service\Notification\Event\EventNotificationNewsroomService;
|
||||
use Combodo\iTop\Service\Notification\NotificationsRepository;
|
||||
use Combodo\iTop\Service\Router\Router;
|
||||
use CoreException;
|
||||
@@ -27,6 +29,7 @@ use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Dict;
|
||||
use EventNotificationNewsroom;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use SecurityException;
|
||||
use UserRights;
|
||||
@@ -376,9 +379,10 @@ JS
|
||||
$oEventBlock->SetCSSColorClass($sReadColor);
|
||||
$oEventBlock->SetSubTitle($sReadLabel);
|
||||
$oEventBlock->SetClassLabel('');
|
||||
/** @var \ormDocument $oImage */
|
||||
$oImage = $oEvent->Get('icon');
|
||||
if (!$oImage->IsEmpty()) {
|
||||
$sIconUrl = $oImage->GetDisplayURL(get_class($oEvent), $iEventId, 'icon');
|
||||
$sIconUrl = self::GetDisplayIconUrl($iEventId, $oImage->GetSignature());
|
||||
$oEventBlock->SetIcon($sIconUrl, Panel::ENUM_ICON_COVER_METHOD_COVER, true);
|
||||
}
|
||||
|
||||
@@ -542,7 +546,7 @@ $sMessage
|
||||
HTML;
|
||||
|
||||
$sIcon = $oMessage->Get('icon') !== null ?
|
||||
$oMessage->Get('icon')->GetDisplayURL(EventNotificationNewsroom::class, $oMessage->GetKey(), 'icon') :
|
||||
$this->GetDisplayIconUrl($oMessage->GetKey(), $oMessage->Get('icon')->GetSignature()) :
|
||||
Branding::GetCompactMainLogoAbsoluteUrl();
|
||||
$aMessages[] = [
|
||||
'id' => $oMessage->GetKey(),
|
||||
@@ -689,6 +693,35 @@ HTML;
|
||||
return $oPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the icon of an EventNotificationNewsroom
|
||||
* (copy of ajax.render.php?operation=display_document but with the bAllowAllData parameter set to true in order to bypass the data access restrictions since the icon is not a critical information)
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function OperationViewIcon(): void
|
||||
{
|
||||
$sId = utils::ReadParam('id', '');
|
||||
if (!empty($sId)) {
|
||||
$oPage = new DownloadPage('');
|
||||
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
|
||||
// so we're resetting its value ! (see N°3416)
|
||||
$oPage->add_xframe_options('');
|
||||
$iCacheSec = (int)utils::ReadParam('cache', 0);
|
||||
$oPage->set_cache($iCacheSec);
|
||||
|
||||
// N°4129 - Prevent XSS attacks & other script executions
|
||||
if (utils::GetConfig()->Get('security.disable_inline_documents_sandbox') === false) {
|
||||
$oPage->add_header('Content-Security-Policy: sandbox;');
|
||||
}
|
||||
|
||||
if (EventNotificationNewsroomService::DownloadIcon($oPage, $sId, UserRights::GetContactId()) === true) {
|
||||
$oPage->Output();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAction
|
||||
*
|
||||
@@ -781,4 +814,9 @@ HTML;
|
||||
|
||||
return $aReturnData;
|
||||
}
|
||||
|
||||
protected function GetDisplayIconUrl(string $sId, string $sSignature): string
|
||||
{
|
||||
return utils::GetAbsoluteUrlAppRoot()."pages/UI.php?route=itopnewsroom.view_icon&id=$sId&s=$sSignature&cache=86400";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace Combodo\iTop\Service\Notification\Event;
|
||||
|
||||
use Action;
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use EventNotificationNewsroom;
|
||||
use MetaModel;
|
||||
use ormDocument;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
@@ -70,4 +72,31 @@ class EventNotificationNewsroomService
|
||||
|
||||
return $oEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Combodo\iTop\Application\WebPage\WebPage $oPage
|
||||
* @param string $sId
|
||||
* @param int $iContactId
|
||||
*
|
||||
* @return bool Returns true if the download has been launched, false otherwise (e.g. if the event doesn't exist or doesn't belong to the current user)
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function DownloadIcon(WebPage $oPage, string $sId, int $iContactId): bool
|
||||
{
|
||||
$oEvent = MetaModel::GetObject(EventNotificationNewsroom::class, $sId, false, true);
|
||||
if (($oEvent !== null) && ($oEvent->Get('contact_id') === $iContactId)) {
|
||||
ormDocument::DownloadDocument(
|
||||
$oPage,
|
||||
EventNotificationNewsroom::class,
|
||||
$sId,
|
||||
'icon',
|
||||
ormDocument::ENUM_CONTENT_DISPOSITION_INLINE,
|
||||
bAllowAllData: true
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,21 @@ class ormDocumentTest extends ItopDataTestCase
|
||||
$this->assertStringNotContainsString('the object does not exist or you are not allowed to view it', $sAllowedHtml, 'Unexpected error message when rights are sufficient.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testAllowsDownloadingDocumentWhenBypassingRightsChecksWithAllowAllData(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iDeniedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iOrgDifferentFromUser, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageAllowed = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageAllowed, $sTargetClass, $iDeniedDocumentId, $sAttCode, ormDocument::ENUM_CONTENT_DISPOSITION_INLINE, bAllowAllData: true);
|
||||
$sAllowedHtml = $oPageAllowed->GetHtml();
|
||||
|
||||
$this->assertStringContainsString($sData, $sAllowedHtml, 'Expected file data present when bypassing rights checks.');
|
||||
$this->assertStringNotContainsString("Invalid id ($iDeniedDocumentId) for class '$sTargetClass' - the object does not exist or you are not allowed to view it", $sAllowedHtml, 'Unexpected invalid id error message when bypassing rights checks.');
|
||||
}
|
||||
|
||||
public function DownloadDocumentRightsProvider(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Service\Notification\Event;
|
||||
|
||||
use Action;
|
||||
use ActionNewsroom;
|
||||
use Combodo\iTop\Application\WebPage\CaptureWebPage;
|
||||
use Combodo\iTop\Service\Notification\Event\EventNotificationNewsroomService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Contact;
|
||||
use EventNotificationNewsroom;
|
||||
use Person;
|
||||
use Ticket;
|
||||
use Trigger;
|
||||
use TriggerOnObjectMention;
|
||||
use UserRequest;
|
||||
use UserRights;
|
||||
|
||||
class EventNotificationNewsroomServiceTest extends ItopDataTestCase
|
||||
{
|
||||
public const CREATE_TEST_ORG = true;
|
||||
|
||||
private Contact $oContact;
|
||||
private Trigger $oTrigger;
|
||||
private Action $oAction;
|
||||
private Ticket $oTicket;
|
||||
private EventNotificationNewsroom $oEvent;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
/** @var Contact $oContact */
|
||||
$oContact = $this->createObject(Person::class, [
|
||||
'name' => 'Khalo',
|
||||
'first_name' => 'Frida',
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
]);
|
||||
$this->oContact = $oContact;
|
||||
|
||||
/** @var Trigger $oTrigger */
|
||||
$oTrigger = $this->createObject(TriggerOnObjectMention::class, [
|
||||
'description' => 'Person mentioned on Ticket',
|
||||
'target_class' => 'Ticket',
|
||||
]);
|
||||
$this->oTrigger = $oTrigger;
|
||||
|
||||
/** @var Action $oAction */
|
||||
$oAction = $this->createObject(ActionNewsroom::class, [
|
||||
'name' => 'Notification to persons mentioned in logs',
|
||||
'status' => 'enabled',
|
||||
'title' => '$this->friendlyname$',
|
||||
'message' => 'You have been mentioned by $current_contact->friendlyname$',
|
||||
'recipients' => 'SELECT Person WHERE id = :mentioned->id',
|
||||
]);
|
||||
$this->oAction = $oAction;
|
||||
|
||||
/** @var Ticket $oTicket */
|
||||
$oTicket = $this->createObject(UserRequest::class, [
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
'title' => 'Houston, got a problem',
|
||||
'description' => 'Test description',
|
||||
]);
|
||||
$this->oTicket = $oTicket;
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
$this->oEvent->DBDelete();
|
||||
}
|
||||
|
||||
public function testDownloadIsTriggeredWhenDownloaderIsNotificationRecipient(): void
|
||||
{
|
||||
$this->oEvent = EventNotificationNewsroomService::MakeEventFromAction(
|
||||
oAction: $this->oAction,
|
||||
iContactId: $this->oContact->GetKey(),
|
||||
iTriggerId: $this->oTrigger->GetKey(),
|
||||
sMessage: 'Test message',
|
||||
sTitle: 'Test event',
|
||||
sUrl: 'https://localhost/itop/pages/UI.php?operation=details&class=UserRequest&id=1',
|
||||
iObjectId: $this->oTicket->GetKey(),
|
||||
sObjectClass: UserRequest::class,
|
||||
);
|
||||
$this->oEvent->DBInsert();
|
||||
|
||||
$oPage = new CaptureWebPage();
|
||||
$bDownloadIcon = EventNotificationNewsroomService::DownloadIcon($oPage, $this->oEvent->GetKey(), $this->oContact->GetKey());
|
||||
$sHtml = $oPage->GetHtml();
|
||||
|
||||
$this->assertTrue($bDownloadIcon);
|
||||
$this->assertNotEquals('', $sHtml);
|
||||
}
|
||||
|
||||
public function testDownloadIsNotTriggeredWhenDownloaderIsNotNotificationRecipient(): void
|
||||
{
|
||||
$oContact = $this->createObject(Person::class, [
|
||||
'name' => 'Doe',
|
||||
'first_name' => 'John',
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
]);
|
||||
$this->oEvent = EventNotificationNewsroomService::MakeEventFromAction(
|
||||
oAction: $this->oAction,
|
||||
iContactId: $oContact->GetKey(),
|
||||
iTriggerId: $this->oTrigger->GetKey(),
|
||||
sMessage: 'Test message',
|
||||
sTitle: 'Test event',
|
||||
sUrl: 'https://localhost/itop/pages/UI.php?operation=details&class=UserRequest&id=1',
|
||||
iObjectId: $this->oTicket->GetKey(),
|
||||
sObjectClass: UserRequest::class,
|
||||
);
|
||||
$this->oEvent->DBInsert();
|
||||
|
||||
$oPage = new CaptureWebPage();
|
||||
$bDownloadIcon = EventNotificationNewsroomService::DownloadIcon($oPage, $this->oEvent->GetKey(), $this->oContact->GetKey());
|
||||
$sHtml = $oPage->GetHtml();
|
||||
|
||||
$this->assertFalse($bDownloadIcon);
|
||||
$this->assertEquals('', $sHtml);
|
||||
}
|
||||
|
||||
public function testDownloadIconIsTriggeredEvenWhenUserCannotReadIconAttribute(): void
|
||||
{
|
||||
// Create a user with Support Agent Profile
|
||||
$sLogin = uniqid('EventNotificationNewsroomServiceTest');
|
||||
$oUser = $this->CreateContactlessUser($sLogin, self::$aURP_Profiles['Support Agent'], '1234@Abcdefg');
|
||||
$oUser->Set('contactid', $this->oContact->GetKey());
|
||||
UserRights::Login($sLogin);
|
||||
|
||||
$this->oEvent = EventNotificationNewsroomService::MakeEventFromAction(
|
||||
oAction: $this->oAction,
|
||||
iContactId: $this->oContact->GetKey(),
|
||||
iTriggerId: $this->oTrigger->GetKey(),
|
||||
sMessage: 'Test message',
|
||||
sTitle: 'Test event',
|
||||
sUrl: 'https://localhost/itop/pages/UI.php?operation=details&class=UserRequest&id=1',
|
||||
iObjectId: $this->oTicket->GetKey(),
|
||||
sObjectClass: UserRequest::class,
|
||||
);
|
||||
$this->oEvent->DBInsert();
|
||||
|
||||
$iURValue = UserRights::IsActionAllowedOnAttribute(EventNotificationNewsroom::class, 'icon', UR_ACTION_READ, $this->oEvent, $oUser);
|
||||
$this->assertEquals(UR_ALLOWED_NO, $iURValue);
|
||||
|
||||
$oPage = new CaptureWebPage();
|
||||
$bDownloadIcon = EventNotificationNewsroomService::DownloadIcon($oPage, $this->oEvent->GetKey(), $this->oContact->GetKey());
|
||||
$sHtml = $oPage->GetHtml();
|
||||
|
||||
$this->assertTrue($bDownloadIcon);
|
||||
$this->assertNotEquals('', $sHtml);
|
||||
$this->assertStringNotContainsString('the object does not exist or you are not allowed to view it', $sHtml);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user