mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 10:38:45 +02:00
N°6458 Security hardening
This commit is contained in:
@@ -4,4 +4,14 @@
|
||||
- Covers an iTop PHP class or method?
|
||||
- Most likely in "unitary-tests".
|
||||
- Covers the consistency of some data through the app?
|
||||
- Most likely in "integration-tests".
|
||||
- Most likely in "integration-tests".
|
||||
|
||||
## Tips
|
||||
|
||||
### Measure the time spent in a test
|
||||
|
||||
Simply cut'n paste the following line at several places within the test function:
|
||||
|
||||
```php
|
||||
if (isset($fStarted)) {echo 'L'.__LINE__.': '.round(microtime(true) - $fStarted, 3)."\n";} $fStarted = microtime(true);
|
||||
```
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Combodo\iTop\Test\UnitTest;
|
||||
|
||||
use ArchivedObjectException;
|
||||
use CMDBSource;
|
||||
use Config;
|
||||
use Contact;
|
||||
use DBObject;
|
||||
use DBObjectSet;
|
||||
@@ -30,7 +29,6 @@ use lnkFunctionalCIToTicket;
|
||||
use MetaModel;
|
||||
use Person;
|
||||
use Server;
|
||||
use SetupUtils;
|
||||
use TagSetFieldData;
|
||||
use Ticket;
|
||||
use URP_UserProfile;
|
||||
@@ -479,6 +477,35 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sLogin
|
||||
* @param int $iProfileId
|
||||
*
|
||||
* @return \UserLocal
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function CreateContactlessUser($sLogin, $iProfileId, $sPassword = null)
|
||||
{
|
||||
if (empty($sPassword)) {
|
||||
$sPassword = $sLogin;
|
||||
}
|
||||
|
||||
$oUserProfile = new URP_UserProfile();
|
||||
$oUserProfile->Set('profileid', $iProfileId);
|
||||
$oUserProfile->Set('reason', 'UNIT Tests');
|
||||
$oSet = DBObjectSet::FromObject($oUserProfile);
|
||||
/** @var \UserLocal $oUser */
|
||||
$oUser = $this->createObject('UserLocal', array(
|
||||
'login' => $sLogin,
|
||||
'password' => $sPassword,
|
||||
'language' => 'EN US',
|
||||
'profile_list' => $oSet,
|
||||
));
|
||||
$this->debug("Created {$oUser->GetName()} ({$oUser->GetKey()})");
|
||||
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObject $oUser
|
||||
* @param int $iProfileId
|
||||
@@ -658,7 +685,7 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function AddCIToTicket($oCI, $oTicket, $sImpactCode)
|
||||
protected function AddCIToTicket($oCI, $oTicket, $sImpactCode = 'manual')
|
||||
{
|
||||
$oNewLink = new lnkFunctionalCIToTicket();
|
||||
$oNewLink->Set('functionalci_id', $oCI->GetKey());
|
||||
|
||||
@@ -17,18 +17,20 @@
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Eric
|
||||
* Date: 02/10/2017
|
||||
* Time: 13:58
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Attachment;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use DBObject;
|
||||
use InvalidExternalKeyValueException;
|
||||
use lnkPersonToTeam;
|
||||
use MetaModel;
|
||||
use Organization;
|
||||
use Person;
|
||||
use Team;
|
||||
use User;
|
||||
use UserRights;
|
||||
use utils;
|
||||
|
||||
|
||||
/**
|
||||
@@ -39,6 +41,7 @@ use MetaModel;
|
||||
class DBObjectTest extends ItopDataTestCase
|
||||
{
|
||||
const CREATE_TEST_ORG = true;
|
||||
const INVALID_OBJECT_KEY = 123456789;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -121,4 +124,250 @@ class DBObjectTest extends ItopDataTestCase
|
||||
$this->debug("ERROR: N°4967 - 'Previous Values For Updated Attributes' not updated if DBUpdate is called without modifying the object");
|
||||
//$this->assertCount(0, $oOrg->ListPreviousValuesForUpdatedAttributes());
|
||||
}
|
||||
|
||||
private function GetAlwaysTrueCallback(): callable
|
||||
{
|
||||
return static function () {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private function GetAlwaysFalseCallback(): callable
|
||||
{
|
||||
return static function () {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::CheckChangedExtKeysValues()
|
||||
*/
|
||||
public function testCheckExtKeysSiloOnAttributeExternalKey()
|
||||
{
|
||||
//--- Preparing data...
|
||||
$oAlwaysTrueCallback = $this->GetAlwaysTrueCallback();
|
||||
$oAlwaysFalseCallback = $this->GetAlwaysFalseCallback();
|
||||
|
||||
/** @var Organization $oDemoOrg */
|
||||
$oDemoOrg = MetaModel::GetObjectByName(Organization::class, 'Demo');
|
||||
/** @var Organization $oMyCompanyOrg */
|
||||
$oMyCompanyOrg = MetaModel::GetObjectByName(Organization::class, 'My Company/Department');
|
||||
|
||||
/** @var Person $oPersonOfDemoOrg */
|
||||
$oPersonOfDemoOrg = MetaModel::GetObjectByName(Person::class, 'Agatha Christie');
|
||||
/** @var Person $oPersonOfMyCompanyOrg */
|
||||
$oPersonOfMyCompanyOrg = MetaModel::GetObjectByName(Person::class, 'My first name My last name');
|
||||
|
||||
$sConfigurationManagerProfileId = 3; // Access to Person objects
|
||||
$oUserWithAllowedOrgs = $this->CreateDemoOrgUser($oDemoOrg, $sConfigurationManagerProfileId);
|
||||
|
||||
$oAdminUser = MetaModel::GetObjectByName(User::class, 'admin', false);
|
||||
if (is_null($oAdminUser)) {
|
||||
$oAdminUser = $this->CreateUser('admin', 1);
|
||||
}
|
||||
|
||||
/** @var Person $oPersonObject */
|
||||
$oPersonObject = $this->CreatePerson(0, $oMyCompanyOrg->GetKey());
|
||||
|
||||
//--- Now we can do some tests !
|
||||
UserRights::Login($oUserWithAllowedOrgs->Get('login'));
|
||||
try {
|
||||
$oPersonObject->CheckChangedExtKeysValues();
|
||||
} catch (InvalidExternalKeyValueException $eCannotSave) {
|
||||
$this->fail('Should skip external keys already written in Database');
|
||||
}
|
||||
|
||||
$oPersonObject->Set('manager_id', $oPersonOfDemoOrg->GetKey());
|
||||
try {
|
||||
$oPersonObject->CheckChangedExtKeysValues();
|
||||
} catch (InvalidExternalKeyValueException $eCannotSave) {
|
||||
$this->fail('Should allow objects in the same org as the current user');
|
||||
}
|
||||
|
||||
try {
|
||||
$oPersonObject->CheckChangedExtKeysValues($oAlwaysFalseCallback);
|
||||
$this->fail('Should consider the callback returning "false"');
|
||||
} catch (InvalidExternalKeyValueException $eCannotSave) {
|
||||
// Ok, the exception was expected
|
||||
}
|
||||
|
||||
$oPersonObject->Set('manager_id', $oPersonOfMyCompanyOrg->GetKey());
|
||||
try {
|
||||
$oPersonObject->CheckChangedExtKeysValues();
|
||||
$this->fail('Should not allow objects not being in the allowed orgs of the current user');
|
||||
} catch (InvalidExternalKeyValueException $eCannotSave) {
|
||||
$this->assertEquals('manager_id', $eCannotSave->GetAttCode(), 'Should report the wrong external key attcode');
|
||||
$this->assertEquals($oMyCompanyOrg->GetKey(), $eCannotSave->GetAttValue(), 'Should report the unauthorized external key value');
|
||||
}
|
||||
|
||||
try {
|
||||
$oPersonObject->CheckChangedExtKeysValues($oAlwaysTrueCallback);
|
||||
} catch (InvalidExternalKeyValueException $eCannotSave) {
|
||||
$this->fail('Should consider the callback returning "true"');
|
||||
}
|
||||
|
||||
// ugly hack to remove cached SQL queries :(
|
||||
//FIXME In 3.0+ this won't be necessary anymore thanks to UserRights::Logoff
|
||||
$this->SetNonPublicStaticProperty(MetaModel::class, 'aQueryCacheGetObject', []);
|
||||
|
||||
UserRights::Login($oAdminUser->Get('login'));
|
||||
$oPersonObject->CheckChangedExtKeysValues();
|
||||
$this->assertTrue(true, 'Admin user can create objects in any org');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::CheckChangedExtKeysValues()
|
||||
*/
|
||||
public function testCheckExtKeysOnAttributeLinkedSetIndirect()
|
||||
{
|
||||
//--- Preparing data...
|
||||
/** @var Organization $oDemoOrg */
|
||||
$oDemoOrg = MetaModel::GetObjectByName(Organization::class, 'Demo');
|
||||
/** @var Person $oPersonOnItDepartmentOrg */
|
||||
$oPersonOnItDepartmentOrg = MetaModel::GetObjectByName(Person::class, 'Anna Gavalda');
|
||||
/** @var Person $oPersonOnDemoOrg */
|
||||
$oPersonOnDemoOrg = MetaModel::GetObjectByName(Person::class, 'Claude Monet');
|
||||
|
||||
$sConfigManagerProfileId = 3; // access to Team and Contact objects
|
||||
$oUserWithAllowedOrgs = $this->CreateDemoOrgUser($oDemoOrg, $sConfigManagerProfileId);
|
||||
|
||||
//--- Now we can do some tests !
|
||||
UserRights::Login($oUserWithAllowedOrgs->Get('login'));
|
||||
|
||||
$oTeam = MetaModel::NewObject(Team::class, [
|
||||
'name' => 'The A Team',
|
||||
'org_id' => $oDemoOrg->GetKey()
|
||||
]);
|
||||
|
||||
// Part 1 - Test with an invalid id (non-existing object)
|
||||
//
|
||||
$oPersonLinks = \DBObjectSet::FromScratch(lnkPersonToTeam::class);
|
||||
$oPersonLinks->AddObject(MetaModel::NewObject(lnkPersonToTeam::class, [
|
||||
'person_id' => self::INVALID_OBJECT_KEY,
|
||||
]));
|
||||
$oTeam->Set('persons_list', $oPersonLinks);
|
||||
|
||||
try {
|
||||
$oTeam->CheckChangedExtKeysValues();
|
||||
$this->fail('An unknown object should be detected as invalid');
|
||||
} catch (InvalidExternalKeyValueException $e) {
|
||||
// we are getting the exception on the lnk class
|
||||
// In consequence attcode is `lnkPersonToTeam.person_id` instead of `Team.persons_list`
|
||||
$this->assertEquals('person_id', $e->GetAttCode(), 'The reported attcode should be the external key on the link');
|
||||
$this->assertEquals(self::INVALID_OBJECT_KEY, $e->GetAttValue(), 'The reported value should be the external key on the link');
|
||||
}
|
||||
|
||||
try {
|
||||
$oTeam->CheckChangedExtKeysValues($this->GetAlwaysTrueCallback());
|
||||
} catch (InvalidExternalKeyValueException $e) {
|
||||
$this->fail('Should have no error when callback returns true');
|
||||
}
|
||||
|
||||
// Part 2 - Test with an allowed object
|
||||
//
|
||||
$oPersonLinks = \DBObjectSet::FromScratch(lnkPersonToTeam::class);
|
||||
$oPersonLinks->AddObject(MetaModel::NewObject(lnkPersonToTeam::class, [
|
||||
'person_id' => $oPersonOnDemoOrg->GetKey(),
|
||||
]));
|
||||
$oTeam->Set('persons_list', $oPersonLinks);
|
||||
|
||||
try {
|
||||
$oTeam->CheckChangedExtKeysValues();
|
||||
} catch (InvalidExternalKeyValueException $e) {
|
||||
$this->fail('An authorized object should be detected as valid');
|
||||
}
|
||||
|
||||
try {
|
||||
$oTeam->CheckChangedExtKeysValues($this->GetAlwaysFalseCallback());
|
||||
$this->fail('Should cascade the callback result when it is "false"');
|
||||
} catch (InvalidExternalKeyValueException $e) {
|
||||
// Ok, the exception was expected
|
||||
}
|
||||
|
||||
// Part 3 - Test with a not allowed object
|
||||
//
|
||||
$oPersonLinks = \DBObjectSet::FromScratch(lnkPersonToTeam::class);
|
||||
$oPersonLinks->AddObject(MetaModel::NewObject(lnkPersonToTeam::class, [
|
||||
'person_id' => $oPersonOnItDepartmentOrg->GetKey(),
|
||||
]));
|
||||
$oTeam->Set('persons_list', $oPersonLinks);
|
||||
|
||||
try {
|
||||
$oTeam->CheckChangedExtKeysValues();
|
||||
$this->fail('An unauthorized object should be detected as invalid');
|
||||
}
|
||||
catch (InvalidExternalKeyValueException $e) {
|
||||
// Ok, the exception was expected
|
||||
}
|
||||
|
||||
try {
|
||||
$oTeam->CheckChangedExtKeysValues($this->GetAlwaysTrueCallback());
|
||||
} catch (InvalidExternalKeyValueException $e) {
|
||||
$this->fail('Should cascade the callback result when it is "true"');
|
||||
}
|
||||
|
||||
$oTeam->DBInsert(); // persisting invalid value and resets the object changed values
|
||||
try {
|
||||
$oTeam->CheckChangedExtKeysValues();
|
||||
}
|
||||
catch (InvalidExternalKeyValueException $e) {
|
||||
$this->fail('An unauthorized value should be ignored when it is not being modified');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::CheckChangedExtKeysValues()
|
||||
*/
|
||||
public function testCheckExtKeysSiloOnAttributeObjectKey()
|
||||
{
|
||||
//--- Preparing data...
|
||||
/** @var Organization $oDemoOrg */
|
||||
$oDemoOrg = MetaModel::GetObjectByName(Organization::class, 'Demo');
|
||||
/** @var Person $oPersonOnItDepartmentOrg */
|
||||
$oPersonOnItDepartmentOrg = MetaModel::GetObjectByName(Person::class, 'Anna Gavalda');
|
||||
/** @var Person $oPersonOnDemoOrg */
|
||||
$oPersonOnDemoOrg = MetaModel::GetObjectByName(Person::class, 'Claude Monet');
|
||||
|
||||
$sConfigManagerProfileId = 3; // access to Team and Contact objects
|
||||
$oUserWithAllowedOrgs = $this->CreateDemoOrgUser($oDemoOrg, $sConfigManagerProfileId);
|
||||
|
||||
//--- Now we can do some tests !
|
||||
UserRights::Login($oUserWithAllowedOrgs->Get('login'));
|
||||
|
||||
$oAttachment = MetaModel::NewObject(Attachment::class, [
|
||||
'item_class' => Person::class,
|
||||
'item_id' => $oPersonOnDemoOrg->GetKey(),
|
||||
]);
|
||||
try {
|
||||
$oAttachment->CheckChangedExtKeysValues();
|
||||
} catch (InvalidExternalKeyValueException $e) {
|
||||
$this->fail('Should be allowed to create an attachment pointing to a ticket in the allowed org list');
|
||||
}
|
||||
|
||||
$oAttachment = MetaModel::NewObject(Attachment::class, [
|
||||
'item_class' => Person::class,
|
||||
'item_id' => $oPersonOnItDepartmentOrg->GetKey(),
|
||||
]);
|
||||
try {
|
||||
$oAttachment->CheckChangedExtKeysValues();
|
||||
$this->fail('There should be an error on attachment pointing to a non allowed org object');
|
||||
} catch (InvalidExternalKeyValueException $e) {
|
||||
$this->assertEquals('item_id', $e->GetAttCode(), 'Should report the object key attribute');
|
||||
$this->assertEquals($oPersonOnItDepartmentOrg->GetKey(), $e->GetAttValue(), 'Should report the object key value');
|
||||
}
|
||||
}
|
||||
|
||||
private function CreateDemoOrgUser(Organization $oDemoOrg, string $sProfileId): User
|
||||
{
|
||||
utils::GetConfig()->SetModuleSetting('authent-local', 'password_validation.pattern', '');
|
||||
$oUserWithAllowedOrgs = $this->CreateContactlessUser('demo_test_' . __CLASS__, $sProfileId);
|
||||
/** @var \URP_UserOrg $oUserOrg */
|
||||
$oUserOrg = \MetaModel::NewObject('URP_UserOrg', ['allowed_org_id' => $oDemoOrg->GetKey(),]);
|
||||
$oAllowedOrgList = $oUserWithAllowedOrgs->Get('allowed_org_list');
|
||||
$oAllowedOrgList->AddItem($oUserOrg);
|
||||
$oUserWithAllowedOrgs->Set('allowed_org_list', $oAllowedOrgList);
|
||||
$oUserWithAllowedOrgs->DBWrite();
|
||||
|
||||
return $oUserWithAllowedOrgs;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user