Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

namespace Combodo\iTop\Portal\Helper;

use CoreException;
use LogChannels;
use UserRights;
use IssueLog;
Expand Down Expand Up @@ -174,6 +175,123 @@ public function IsActionAllowed($sAction, $sObjectClass, $sObjectId = null)
return true;
}


/**
* Returns object if the current user is allowed to do the $sAction on an $sObjectClass object (with $sObjectId id)
* Checks are:
* - Has a scope query for the $sObjectClass /$sObjectId/ $sAction
* - Is allowed by datamodel for $sObjectClass / $sAction
*
* @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is not meant to be extensible, perhaps this could be turned into an enum?

* @param string $sObjectClass
* @param string $sObjectId
* @param bool $bMustBeFound
*
* @throws \CoreExceptionif no result found and $bMustBeFound=true
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function GetObjectForAction($sAction, $sObjectClass, $sObjectId, $bMustBeFound)
{
$sDebugTracePrefix = __CLASS__.' / '.__METHOD__.' : Returned object for action '.$sAction.' on '.$sObjectClass.'::'.$sObjectId;

// Forcing allowed writing on the object if necessary. This is used in some particular cases.
$bObjectIsCurrentUser = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
if(in_array($sAction , array(UR_ACTION_MODIFY, UR_ACTION_READ)) && $bObjectIsCurrentUser){
return MetaModel::GetObject($sObjectClass, $sObjectId, true,true);
}

// Checking the scopes layer
// - Transforming scope action as there is only 2 values
$sScopeAction = ($sAction === UR_ACTION_READ) ? UR_ACTION_READ : UR_ACTION_MODIFY;
// - Retrieving the query. If user has no scope, it can't access that kind of objects
$oScopeQuery = $this->oScopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction);
if ($oScopeQuery === null)
{
IssueLog::Debug($sDebugTracePrefix.' as there was no scope defined for action '.$sScopeAction.' and profiles '.implode('/', UserRights::ListProfiles()), LogChannels::PORTAL);
if ($bMustBeFound)
{
$sNotFoundErrorMessage = "No result for the single row query";
IssueLog::Info($sNotFoundErrorMessage, LogChannels::CMDB_SOURCE, [
'class' => $sObjectClass,
'key' => $sObjectId
]);
throw new CoreException($sNotFoundErrorMessage);
}
return null;
}

// Checking if object status is in cache (to avoid unnecessary query)
if (isset(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId])) {
if (static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] === false)
{
IssueLog::Debug($sDebugTracePrefix.' as it was denied in the scope objects cache', LogChannels::PORTAL);
if ($bMustBeFound)
{
$sNotFoundErrorMessage = "No result for the single row query";
IssueLog::Info($sNotFoundErrorMessage, LogChannels::CMDB_SOURCE, [
'class' => $sObjectClass,
'key' => $sObjectId
]);
throw new CoreException($sNotFoundErrorMessage);
}
return null;
} else {
return MetaModel::GetObject($sObjectClass, $sObjectId, true,true);
}
}
else
{

// Modifying query to filter on the ID
// - Adding expression
$sObjectKeyAtt = MetaModel::DBGetKey($sObjectClass);
$oFieldExp = new FieldExpression($sObjectKeyAtt, $oScopeQuery->GetClassAlias());
$oBinExp = new BinaryExpression($oFieldExp, '=', new VariableExpression('object_id'));
$oScopeQuery->AddConditionExpression($oBinExp);
// - Setting value
$aQueryParams = $oScopeQuery->GetInternalParams();
$aQueryParams['object_id'] = $sObjectId;
$oScopeQuery->SetInternalParams($aQueryParams);
unset($aQueryParams);
IssueLog::Error('requete:'.$oScopeQuery->ToOQL(true));
// - Checking if query result is null (which means that the user has no right to view this specific object)
$oSet = new DBObjectSet($oScopeQuery);
$oObject = $oSet->Fetch();
if ($oObject === null)
{
// Updating cache
static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] = false;
IssueLog::Debug($sDebugTracePrefix.' as there was no result for the following scope query : '.$oScopeQuery->ToOQL(true), LogChannels::PORTAL);
} else {
// Updating cache
static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] = true;
}
}

// Checking reading security layer. The object could be listed, check if it is actually allowed to view it
if (UserRights::IsActionAllowed($sObjectClass, $sAction) == UR_ALLOWED_NO) {
// For security reasons, we don't want to give the user too many information on why he cannot access the object.
//throw new SecurityException('User not allowed to view this object', array('class' => $sObjectClass, 'id' => $sObjectId));
IssueLog::Debug($sDebugTracePrefix.' as the user is not allowed to access this object according to the datamodel security (cf. Console settings)', LogChannels::PORTAL);
$oObject = null;
}

if ($bMustBeFound && $oObject === null )
{
$sNotFoundErrorMessage = "No result for the single row query";
IssueLog::Info($sNotFoundErrorMessage, LogChannels::CMDB_SOURCE, [
'class' => $sObjectClass,
'key' => $sObjectId
]);
throw new CoreException($sNotFoundErrorMessage);
}

return $oObject;
}

/**
* @param string $sStimulusCode
* @param string $sObjectClass
Expand Down