diff --git a/datamodels/2.x/itop-portal-base/portal/src/controllers/managebrickcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/controllers/managebrickcontroller.class.inc.php index a2388f3a41..a3d47ace64 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/controllers/managebrickcontroller.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/controllers/managebrickcontroller.class.inc.php @@ -19,6 +19,7 @@ namespace Combodo\iTop\Portal\Controller; +use Combodo\iTop\Portal\Helper\ScopeValidatorHelper; use \Silex\Application; use \Symfony\Component\HttpFoundation\Request; use \UserRights; @@ -267,8 +268,8 @@ class ManageBrickController extends BrickController } // Restricting query to allowed scope on each classes - // Note : Will need to moved the scope restriction on queries elsewhere when we consider grouping on something else than finalclass - // Note : We now get view scope instead of edit scope as we allowed users to view/edit objects in the brick regarding their rights + // Note: Will need to moved the scope restriction on queries elsewhere when we consider grouping on something else than finalclass + // Note: We now get view scope instead of edit scope as we allowed users to view/edit objects in the brick regarding their rights $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $aGroupingAreasValue['value'], UR_ACTION_READ); if ($oScopeQuery !== null) { @@ -284,7 +285,7 @@ class ManageBrickController extends BrickController $oAreaQuery = null; } - $aQueries[$sKey] = $oAreaQuery; + $aQueries[$sKey] = $oAreaQuery; } // Testing appropriate data loading mode if we are in auto @@ -346,11 +347,12 @@ class ManageBrickController extends BrickController $oSet->OptimizeColumnLoad($aColumnsToLoad); $oSet->SetOrderByClasses(); + SecurityHelper::PreloadForCache($oApp, $oSet->GetFilter(), $aColumnsToLoad[$oQuery->GetClassAlias()] /* preloading only extkeys from the main class */); $aSets[$sKey] = $oSet; } } - // Retrieving and preparing datas for rendering + // Retrieving and preparing data for rendering $aGroupingAreasData = array(); foreach ($aSets as $sKey => $oSet) { @@ -373,6 +375,7 @@ class ManageBrickController extends BrickController // Getting items $aItems = array(); + $aItemsIds = array(); // ... For each item /** @var DBObject $oCurrentRow */ while ($oCurrentRow = $oSet->Fetch()) @@ -457,8 +460,18 @@ class ManageBrickController extends BrickController 'attributes' => $aItemAttrs, 'highlight_class' => $oCurrentRow->GetHilightClass() ); + $aItemsIds = $oCurrentRow->GetKey(); } + // Now that we retrieved items, we check which can be edited, which can be view and which cannot be opened + // + // Note: Now that we do checks here and not through the SecurityHelper while fetching objects, we might bypass datamodel security regarding the object class! +// $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sCurrentClass, UR_ACTION_MODIFY); +// if($oSearchEditableItems !== null) +// { +// $oSearchEditableItems->A +// } + $aGroupingAreasData[$sKey] = array( 'sId' => $sKey, 'sTitle' => $aGroupingAreasValues[$sKey]['label'], diff --git a/datamodels/2.x/itop-portal-base/portal/src/helpers/securityhelper.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/helpers/securityhelper.class.inc.php index d0006209cf..2360e1b091 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/helpers/securityhelper.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/helpers/securityhelper.class.inc.php @@ -26,6 +26,8 @@ use \UserRights; use \Dict; use \IssueLog; use \MetaModel; +use \DBSearch; +use \DBObjectSearch; use \DBObjectSet; use \FieldExpression; use \VariableExpression; @@ -48,6 +50,10 @@ class SecurityHelper /** * Returns true if the current user is allowed to do the $sAction on an $sObjectClass object (with optionnal $sObjectId id) + * Checks are: + * - Has a scope query for the $sObjectClass / $sAction + * - Optionally, if $sObjectId provided: Is object within scope for $sObjectClass / $sObjectId / $sAction + * - Is allowed by datamodel for $sObjectClass / $sAction * * @param Silex\Application $oApp * @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE @@ -155,4 +161,105 @@ class SecurityHelper $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sObjectClass, $sStimulusCode, $oInstanceSet) : UR_ALLOWED_NO; } + /** + * Preloads scope objects cache with objects from $oQuery + * + * @param Application $oApp + * @param DBSearch $oSet + * @param array $aExtKeysToPreload + */ + public static function PreloadForCache(Application $oApp, DBSearch $oSearch, $aExtKeysToPreload = null) + { + $sObjectClass = $oSearch->GetClass(); + $aObjectIds = array(); + $aExtKeysIds = array(); + $aColumnsToLoad = array(); + + if($aExtKeysToPreload !== null) + { + foreach($aExtKeysToPreload as $sAttCode) + { + /** @var \AttributeDefinition $oAttDef */ + $oAttDef = MetaModel::GetAttributeDef($sObjectClass, $sAttCode); + if($oAttDef->IsExternalKey()) + { + $aExtKeysIds[$oAttDef->GetTargetClass()] = array(); + $aColumnsToLoad[] = $sAttCode; + } + } + } + + // Retrieving IDs of all objects + // Note: We have to clone $oSet otherwise the source object will be modified + $oSet = new DBObjectSet($oSearch); + $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => $aColumnsToLoad)); + while($oCurrentRow = $oSet->Fetch()) + { + // Note: By presetting value to false, it is quicker to find which objects where not returned by the scope query later + $aObjectIds[$oCurrentRow->GetKey()] = false; + + // Preparing ExtKeys to preload + foreach($aColumnsToLoad as $sAttCode) + { + $iExtKey = $oCurrentRow->Get($sAttCode); + if($iExtKey > 0) + { + /** @var \AttributeExternalKey $oAttDef */ + $oAttDef = MetaModel::GetAttributeDef($sObjectClass, $sAttCode); + if(!in_array($iExtKey, $aExtKeysIds[$oAttDef->GetTargetClass()])) + { + $aExtKeysIds[$oAttDef->GetTargetClass()][] = $iExtKey; + } + } + } + } + + foreach(array(UR_ACTION_READ, UR_ACTION_MODIFY) as $sScopeAction) + { + // Retrieving scope query + /** @var DBSearch $oScopeQuery */ + $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction); + if($oScopeQuery !== null) + { + // Restricting scope if specified + if(!empty($aObjectIds)) + { + $oScopeQuery->AddCondition('id', array_keys($aObjectIds), 'IN'); + } + + // Preparing object set + $oScopeSet = new DBObjectSet($oScopeQuery); + $oScopeSet->OptimizeColumnLoad(array()); + + // Checking objects status + $aScopeObjectIds = $aObjectIds; + while($oCurrentRow = $oScopeSet->Fetch()) + { + $aScopeObjectIds[$oCurrentRow->GetKey()] = true; + } + + // Updating cache + if(!isset(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass])) + { + static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass] = $aScopeObjectIds; + } + else + { + static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass] = array_merge_recursive(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass], $aScopeObjectIds); + } + } + } + + // Preloading ExtKeys + foreach($aExtKeysIds as $sTargetClass => $aTargetIds) + { + if(!empty($aTargetIds)) + { + $oTargetSearch = new DBObjectSearch($sTargetClass); + $oTargetSearch->AddCondition('id', $aTargetIds, 'IN'); + + static::PreloadForCache($oApp, $oTargetSearch); + } + } + } }