diff --git a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml
index c3ce5d71ee..be5e447334 100755
--- a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml
+++ b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml
@@ -85,6 +85,13 @@
+
+
+
+
+
+
+
@@ -205,6 +212,60 @@
+
+ Configuration ReadOnly
+ This read-only profile allows to see CIs objects.
+
+
+
+ allow
+ allow
+
+
+
+
+ allow
+ allow
+
+
+
+
+
+ Ticket ReadOnly
+ This read-only profile allows to see Ticket objects.
+
+
+
+ allow
+ allow
+
+
+
+
+ allow
+ allow
+
+
+
+
+
+ Service Catalog ReadOnly
+ This read-only profile allows to see Service Catalog objects.
+
+
+
+ allow
+ allow
+
+
+
+
+ allow
+ allow
+
+
+
+
SuperUser
This profile allows all actions which are not Administrator restricted.
diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
index 7be4d9d6be..82e0e129f9 100644
--- a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
+++ b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
@@ -87,8 +87,6 @@ abstract class ItopDataTestCase extends ItopTestCase
*/
public const DEFAULT_TEST_ENVIRONMENT = 'production';
public const USE_TRANSACTION = true;
- public const CREATE_TEST_ORG = false;
-
protected static $aURP_Profiles = [
'Administrator' => 1,
'Portal user' => 2,
@@ -102,9 +100,15 @@ abstract class ItopDataTestCase extends ItopTestCase
'Service Manager' => 10,
'Document author' => 11,
'Portal power user' => 12,
+ 'Business partner user' => 40,
'REST Services User' => 1024,
+ 'Configuration ReadOnly' => 5500,
+ 'Ticket ReadOnly' => 5501,
+ 'Service Catalog ReadOnly' => 5502,
];
+ public const CREATE_TEST_ORG = false;
+
/**
* This method is called before the first test of this test class is run (in the current process).
*/
@@ -1463,16 +1467,42 @@ protected function GivenUserWithContactInDB(string $sLogin, string $sProfileId,
]);
}
+ /**
+ * @description To avoid adding finalclasses parameters to GivenUserInDB
+ * @param string $sPassword
+ * @param array $aProfiles Profile names Example: ['Administrator']
+ * @param bool $bReturnLogin
+ *
+ * @return string|int The unique login
+ * @throws \Exception
+ */
+ protected function GivenTokenUserInDB(array $aProfiles, bool $bReturnLogin = true): string|int
+ {
+ $sLogin = 'demo_test_'.uniqid(__CLASS__, true);
+
+ $aProfileList = array_map(function ($sProfileId) {
+ return 'profileid:'.self::$aURP_Profiles[$sProfileId];
+ }, $aProfiles);
+
+ $iUser = $this->GivenObjectInDB('UserToken', [
+ 'login' => $sLogin,
+ 'language' => 'EN US',
+ 'profile_list' => $aProfileList,
+ ]);
+ return $bReturnLogin ? $sLogin : $iUser;
+ }
+
/**
* @param string $sPassword
* @param array $aProfiles Profile names Example: ['Administrator']
* @param string|null $sLogin
* @param string|null $sUserId
+ * @param bool $bReturnLogin
*
* @return string The unique login
* @throws \Exception
*/
- protected function GivenUserInDB(string $sPassword, array $aProfiles, ?string $sLogin = null, ?string &$sUserId = null): string
+ protected function GivenUserInDB(string $sPassword, array $aProfiles, ?string $sLogin = null, ?string &$sUserId = null, bool $bReturnLogin = true): string
{
if (is_null($sLogin)) {
$sLogin = 'demo_test_'.uniqid(__CLASS__, true);
@@ -1489,7 +1519,7 @@ protected function GivenUserInDB(string $sPassword, array $aProfiles, ?string $s
'profile_list' => $aProfileList,
]);
- return $sLogin;
+ return $bReturnLogin ? $sLogin : $sUserId;
}
/**
diff --git a/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php b/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php
index 07e999a841..5c286b91a5 100644
--- a/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php
@@ -29,11 +29,11 @@
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use CoreCannotSaveObjectException;
-use CoreException;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use DeleteException;
+use Dict;
use MetaModel;
use UserLocal;
use UserRights;
@@ -81,6 +81,54 @@ protected function CreateUniqueUserAndLogin(string $sLoginPrefix, int $iProfileI
return $oUser;
}
+ /**
+ * @param array $aProfileIds
+ * @param array $aShouldBeAllowedToSeeClass
+ * @param array $aShouldBeAllowedToEditClass
+ *
+ * @return void
+ * @throws \ArchivedObjectException
+ * @throws \CoreCannotSaveObjectException
+ * @throws \CoreException
+ * @throws \CoreUnexpectedValue
+ * @throws \CoreWarning
+ * @throws \DictExceptionUnknownLanguage
+ * @throws \MySQLException
+ * @throws \OQLException
+ * @dataProvider ReadOnlyProvider
+ */
+ public function testReadOnlyUser(array $aProfileIds, array $aShouldBeAllowedToSeeClass, array $aShouldBeAllowedToEditClass): void
+ {
+
+ $oUser = $this->GivenUserWithProfiles('test1', $aProfileIds);
+ $oUser->DBInsert();
+ $_SESSION = [];
+ UserRights::Login($oUser->Get('login'));
+
+ $aClassesToTest = ['FunctionalCI', 'Ticket', 'ServiceFamily'];
+
+ foreach ($aClassesToTest as $sClass) {
+ $bShouldBeAllowedToSee = in_array($sClass, $aShouldBeAllowedToSeeClass);
+ $bIsAllowedReading = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_READ);
+
+ $this->assertSame(
+ $bShouldBeAllowedToSee,
+ $bIsAllowedReading,
+ "User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToSee ? "" : "NOT ")."be allowed to see class $sClass"
+ );
+
+ $bShouldBeAllowedToEdit = in_array($sClass, $aShouldBeAllowedToEditClass);
+
+ $bIsAllowedEditing = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY);
+
+ $this->assertSame(
+ $bIsAllowedEditing,
+ $bShouldBeAllowedToEdit,
+ "User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToEdit ? "" : "NOT ")."be allowed to edit class $sClass"
+ );
+ }
+ }
+
protected function GivenUserWithProfiles(string $sLogin, array $aProfileIds): DBObject
{
$oProfiles = new \ormLinkSet(\UserLocal::class, 'profile_list', \DBObjectSet::FromScratch(\URP_UserProfile::class));
@@ -433,7 +481,7 @@ public function testPrivilegedUsersMustHaveBackofficeAccess(int $iProfileId)
$oUser = $this->GivenUserWithProfiles('test1', [$iProfileId, 2]);
$this->expectException(CoreCannotSaveObjectException::class);
- $this->expectExceptionMessage('Profile "Portal user" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)');
+ $this->expectExceptionMessage(Dict::Format('Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice', PORTAL_PROFILE_NAME));
$oUser->DBInsert();
}
@@ -572,4 +620,82 @@ public function FindUserAndAssertItWasNotFound($sLogin)
$oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
static::assertNull($oUser, 'FindUser should return null when the login is unknown');
}
+
+ protected function ReadOnlyProvider(): array
+ {
+ return [
+ 'CI' => [
+ 'ProfilesId' => [
+ 5500,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'FunctionalCI',
+ ],
+ 'ShouldBeAllowedToEditClasses' => [],
+ ],
+ 'Tickets' => [
+ 'ProfilesId' => [
+ 5501,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'Ticket',
+ ],
+ 'ShouldBeAllowedToEditClasses' => [],
+ ],
+ 'Catalog' => [
+ 'ProfilesId' => [
+ 5502,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'ServiceFamily',
+ ],
+ 'ShouldBeAllowedToEditClasses' => [],
+ ],
+ 'CI and Tickets' => [
+ 'ProfilesId' => [
+ 5500, 5501,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'FunctionalCI', 'Ticket',
+ ],
+ 'ShouldBeAllowedToEditClasses' => [],
+ ],
+ 'CI and Catalog' => [
+ 'ProfilesId' => [
+ 5500, 5502,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'FunctionalCI', 'ServiceFamily',
+ ],
+ 'ShouldBeAllowedToEditClasses' => [],
+ ],
+ 'Tickets and Catalog' => [
+ 'ProfilesId' => [
+ 5501, 5502,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'Ticket', 'ServiceFamily',
+ ],
+ 'ShouldBeAllowedToEditClasses' => [],
+ ],
+ 'Tickets and Catalog + profile Ccnfiguration Manager' => [
+ 'ProfilesId' => [
+ 5501, 5502, 3,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'FunctionalCI', 'Ticket', 'ServiceFamily',
+ ],
+ 'ShouldBeAllowedToEditClasses' => ['FunctionalCI'],
+ ],
+ 'CI, Tickets and Catalog' => [
+ 'ProfilesId' => [
+ 5500, 5501, 5502,
+ ],
+ 'ShouldBeAllowedToSeeClasses' => [
+ 'FunctionalCI', 'Ticket', 'ServiceFamily',
+ ],
+ 'ShouldBeAllowedToEditClasses' => [],
+ ],
+ ];
+ }
}