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' => [], + ], + ]; + } }