mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-06 10:08:43 +02:00
Compare commits
13 Commits
feature/fa
...
fix/8758_h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82baa2e5cb | ||
|
|
7cd7ba74d4 | ||
|
|
b23b336d60 | ||
|
|
e467ca83cf | ||
|
|
7791585387 | ||
|
|
3406ca79de | ||
|
|
91ad01055e | ||
|
|
804cdffe42 | ||
|
|
5f4affc896 | ||
|
|
042fee2360 | ||
|
|
7f8ec25977 | ||
|
|
41f8437c23 | ||
|
|
df8b25d4b4 |
@@ -810,6 +810,7 @@ HTML
|
||||
foreach ($aNotificationClasses as $sNotifClass) {
|
||||
$aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev JOIN Trigger AS T ON Ev.trigger_id = T.id WHERE T.id IN (:triggers) AND Ev.object_id = :id");
|
||||
$aNotifSearches[$sNotifClass]->SetInternalParams($aParams);
|
||||
$aNotifSearches[$sNotifClass]->AllowAllData();
|
||||
$oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass], []);
|
||||
$iNotifsCount += $oNotifSet->Count();
|
||||
}
|
||||
@@ -823,6 +824,7 @@ HTML
|
||||
'menu' => false,
|
||||
'panel_title' => MetaModel::GetName($sNotifClass),
|
||||
'panel_icon' => MetaModel::GetClassIcon($sNotifClass, false),
|
||||
'display_unauthorized_objects' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,6 +724,10 @@ class DisplayBlock
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->m_oFilter->IsAllDataAllowed() && ($aExtraParams['display_unauthorized_objects'] ?? false) === true) {
|
||||
$this->m_oFilter->AllowAllData();
|
||||
}
|
||||
|
||||
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
|
||||
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
|
||||
}
|
||||
@@ -1381,7 +1385,10 @@ JS
|
||||
|
||||
// Check the classes that can be read (i.e authorized) by this user...
|
||||
foreach ($aClasses as $sAlias => $sClassName) {
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) != UR_ALLOWED_NO) {
|
||||
if (
|
||||
(UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) !== UR_ALLOWED_NO)
|
||||
|| ($aExtraParams['display_unauthorized_objects'] ?? false) === true
|
||||
) {
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,11 +936,6 @@ CSS;
|
||||
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, $aImportsPaths)
|
||||
{
|
||||
$aThemeParametersVariable = [];
|
||||
if (array_key_exists('variables', $aThemeParameters)) {
|
||||
if (is_array($aThemeParameters['variables'])) {
|
||||
$aThemeParametersVariable = array_merge([], $aThemeParameters['variables']);
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('variable_imports', $aThemeParameters)) {
|
||||
if (is_array($aThemeParameters['variable_imports'])) {
|
||||
@@ -948,6 +943,14 @@ CSS;
|
||||
}
|
||||
}
|
||||
|
||||
// Variables defined in theme XML have the priority over variables defined in XML imports files
|
||||
// They're defined after so they overwrite previous parameters
|
||||
if (array_key_exists('variables', $aThemeParameters)) {
|
||||
if (is_array($aThemeParameters['variables'])) {
|
||||
$aThemeParametersVariable = array_merge($aThemeParametersVariable, $aThemeParameters['variables']);
|
||||
}
|
||||
}
|
||||
|
||||
$aThemeParametersVariable['$version'] = $bSetupCompilationTimestamp;
|
||||
return $aThemeParametersVariable;
|
||||
}
|
||||
|
||||
@@ -1455,6 +1455,12 @@ class utils
|
||||
|
||||
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
|
||||
/** @var \DBObjectSet $param */
|
||||
|
||||
// Check if the user has the right to read the objects of this list, otherwise do not propose any action (eg. configure this list, export, etc.)
|
||||
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_READ, $param) !== UR_ALLOWED_YES) {
|
||||
break;
|
||||
}
|
||||
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink(true);
|
||||
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
|
||||
|
||||
@@ -234,10 +234,11 @@ abstract class Action extends cmdbAbstractObject
|
||||
}
|
||||
|
||||
$oActionFilter = DBObjectSearch::FromOQL($sActionQueryOql, $aActionQueryParams);
|
||||
$oActionFilter->AllowAllData();
|
||||
$oSet = new DBObjectSet($oActionFilter, ['date' => false]);
|
||||
|
||||
$sPanelTitle = Dict::Format('Action:last_executions_tab_panel_title', $sActionQueryLimit);
|
||||
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle]);
|
||||
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle, 'display_unauthorized_objects' => true]);
|
||||
|
||||
$oPage->AddUiBlock($oExecutionsListBlock);
|
||||
}
|
||||
|
||||
@@ -1730,6 +1730,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.disable_joined_classes_filter' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, scope filters aren\'t applied to joined classes or union classes not directly listed in the SELECT clause.',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.hide_administrators' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, non-administrator users will not be able to see the administrator accounts, the Administrator profile and the links between the administrator accounts and their profiles.',
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
</php_parent>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,view_in_gui</category>
|
||||
<category>core/cmdb,grant_by_profile,silo</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>priv_event_newsroom</db_table>
|
||||
@@ -888,7 +888,7 @@
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>Event</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,view_in_gui</category>
|
||||
<category>core/cmdb,grant_by_profile,silo</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="message" xsi:type="AttributeText"/>
|
||||
|
||||
@@ -1925,4 +1925,37 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
return $this->GetCriteria()->ListParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBObjectSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBObjectSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise only filter the selected class(es)
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === false) {
|
||||
$aClassesToFilter = $this->GetJoinedClasses();
|
||||
}
|
||||
|
||||
// Apply filter (this is similar to the one in DBSearch but the factorization could make it less readable)
|
||||
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1122,21 +1122,7 @@ abstract class DBSearch
|
||||
*/
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
|
||||
{
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) {
|
||||
foreach ($this->GetSelectedClasses() as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
}
|
||||
$oSearch = $this->ApplyDataFilters();
|
||||
|
||||
if (is_array($aGroupByExpr)) {
|
||||
foreach ($aGroupByExpr as $sAlias => $oGroupByExp) {
|
||||
@@ -1608,4 +1594,33 @@ abstract class DBSearch
|
||||
* @return array{\VariableExpression}
|
||||
*/
|
||||
abstract public function GetExpectedArguments(): array;
|
||||
|
||||
/**
|
||||
* Apply data filters to the search, if needed
|
||||
*
|
||||
* @return DBSearch
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,4 +673,30 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
return $aVariableCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBUnionSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBUnionSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise fallback on DBSearch filtering
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === true) {
|
||||
return parent::ApplyDataFilters();
|
||||
}
|
||||
|
||||
// Apply filters per sub-search
|
||||
$aFilteredSearches = [];
|
||||
foreach ($this->GetSearches() as $oSubSearch) {
|
||||
// Recursively call ApplyDataFilters on sub-searches
|
||||
$aFilteredSearches[] = $oSubSearch->ApplyDataFilters();
|
||||
}
|
||||
|
||||
$oSearch = new DBUnionSearch($aFilteredSearches);
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class Event extends DBObject implements iDisplay
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -120,7 +120,7 @@ class EventNotification extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -154,7 +154,7 @@ class EventNotificationEmail extends EventNotification
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -190,7 +190,7 @@ class EventIssue extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -284,7 +284,7 @@ class EventWebService extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -319,7 +319,7 @@ class EventRestService extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -354,7 +354,7 @@ class EventLoginUsage extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -392,7 +392,7 @@ class EventOnObject extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
|
||||
@@ -350,15 +350,18 @@ class ormDocument
|
||||
if (!is_object($oObj)) {
|
||||
// If access to the document is not granted, check if the access to the host object is allowed
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, true);
|
||||
$bHasHostRights = false;
|
||||
if ($oObj instanceof Attachment) {
|
||||
$sItemClass = $oObj->Get('item_class');
|
||||
$sItemId = $oObj->Get('item_id');
|
||||
$oHost = MetaModel::GetObject($sItemClass, $sItemId, false, false);
|
||||
if (!is_object($oHost)) {
|
||||
$oObj = null;
|
||||
if (is_object($oHost)) {
|
||||
$bHasHostRights = true;
|
||||
}
|
||||
}
|
||||
if (!is_object($oObj)) {
|
||||
|
||||
// We could neither read the object nor get a host object matching our rights
|
||||
if ($bHasHostRights !== true) {
|
||||
throw new Exception("Invalid id ($id) for class '$sClass' - the object does not exist or you are not allowed to view it");
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -68,47 +68,47 @@ $ibo-color-information-900: #0f172a !default;
|
||||
$ibo-color-information-950: #020617 !default;
|
||||
|
||||
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-yellow-700;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-orange-800;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700;
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-yellow-700 !default;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-orange-800 !default;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200 !default;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700 !default;
|
||||
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-yellow-700;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-yellow-700 !default;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-yellow-700;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-yellow-500;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-yellow-300;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300;
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700 !default;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-yellow-700 !default;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600 !default;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-yellow-500 !default;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500 !default;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-yellow-300 !default;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300 !default;
|
||||
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-warning-700;
|
||||
$ibo-field-validation: $ibo-color-warning-800;
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-warning-700 !default;
|
||||
$ibo-field-validation: $ibo-color-warning-800 !default;
|
||||
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-blue-400;
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-blue-400 !default;
|
||||
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600;
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200 !default;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600 !default;
|
||||
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-danger-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-danger-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-danger-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-danger-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500 !default;
|
||||
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-blue-600;
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-blue-600 !default;
|
||||
@@ -32,47 +32,47 @@ $ibo-color-information-900: #0f172a !default;
|
||||
$ibo-color-information-950: #020617 !default;
|
||||
|
||||
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-red-200;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-red-800;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-red-800;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700;
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-red-200 !default;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-red-800 !default;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-red-800 !default;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200 !default;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700 !default;
|
||||
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-red-700;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-red-700 !default;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-red-700;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-red-500;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-red-300;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300;
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700 !default;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-red-700 !default;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600 !default;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-red-500 !default;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500 !default;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-red-300 !default;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300 !default;
|
||||
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-pink-700;
|
||||
$ibo-field-validation: $ibo-color-pink-800;
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-pink-700 !default;
|
||||
$ibo-field-validation: $ibo-color-pink-800 !default;
|
||||
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-pink-600;
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-pink-600 !default;
|
||||
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600;
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200 !default;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600 !default;
|
||||
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-pink-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-pink-600;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-400;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-pink-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-pink-600 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-400 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500 !default;
|
||||
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-pink-500;
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-pink-500 !default;
|
||||
@@ -1386,19 +1386,27 @@ class ObjectController extends BrickController
|
||||
if ($oField instanceof DateTimeField) {
|
||||
$oField->SetDateTimePickerWidgetParent($sDateTimePickerWidgetParent);
|
||||
}
|
||||
$sFieldRendererClass = BsLinkedSetFieldRenderer::GetFieldRendererClass($oField);
|
||||
|
||||
// View data
|
||||
$sValue = $oAttDef->GetAsHTML($oNewLink->Get($sAttCode));
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode] = [
|
||||
'object_class' => $sLinkClass,
|
||||
'object_id' => $oNewLink->GetKey(),
|
||||
'prefix' => 'lnk__',
|
||||
'attribute_code' => $sAttCode,
|
||||
'attribute_type' => get_class($oAttDef),
|
||||
'value_html' => $sValue,
|
||||
];
|
||||
|
||||
// If the field has a renderer we adjust view data
|
||||
$sFieldRendererClass = BsLinkedSetFieldRenderer::GetFieldRendererClass($oField);
|
||||
if ($sFieldRendererClass !== null) {
|
||||
$oFieldRenderer = new $sFieldRendererClass($oField);
|
||||
$oFieldOutput = $oFieldRenderer->Render();
|
||||
$sValue = $oFieldOutput->GetHtml();
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode]['value_html'] = $oFieldOutput->GetHtml();
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode]['css_inline'] = $oFieldOutput->GetCss();
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode]['js_inline'] = $oFieldOutput->GetJs();
|
||||
}
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode] = [
|
||||
'att_code' => $sAttCode,
|
||||
'value' => $sValue,
|
||||
'css_inline' => $oFieldOutput->GetCss(),
|
||||
'js_inline' => $oFieldOutput->GetJs(),
|
||||
];
|
||||
}
|
||||
|
||||
$aData['items'][] = $aObjectData;
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
<group id="AdminSysReadOnly" _delta="define">
|
||||
<classes>
|
||||
<class id="ItopFenceLogin"/>
|
||||
<class id="ModuleInstallation"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="AdminSys" _delta="define">
|
||||
@@ -195,6 +196,11 @@
|
||||
<class id="RessourceHybridAuthMenu"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="Event" _delta="define">
|
||||
<classes>
|
||||
<class id="Event"/>
|
||||
</classes>
|
||||
</group>
|
||||
</groups>
|
||||
<profiles>
|
||||
<profile id="117" _delta="define">
|
||||
@@ -290,6 +296,16 @@
|
||||
<action id="stimulus:ev_close">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
<group id="Event">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
<action id="action:write">allow</action>
|
||||
<action id="action:bulk write">allow</action>
|
||||
<action id="action:delete">allow</action>
|
||||
<action id="action:bulk delete">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="3" _delta="define">
|
||||
|
||||
@@ -301,88 +301,112 @@ function ValidateField(sFieldId, sPattern, bMandatory, sFormId, nullValue, origi
|
||||
return true; // Do not stop propagation ??
|
||||
}
|
||||
|
||||
function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue)
|
||||
function EvaluateCKEditorValidation(oOptions)
|
||||
{
|
||||
let oField = $('#'+sFieldId);
|
||||
let oField = $('#'+oOptions.sFieldId);
|
||||
if (oField.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let oCKEditor = CombodoCKEditorHandler.GetInstanceSynchronous('#'+sFieldId);
|
||||
let oCKEditor = CombodoCKEditorHandler.GetInstanceSynchronous('#'+oOptions.sFieldId);
|
||||
let bValid = true;
|
||||
let sExplain = '';
|
||||
let sTextContent;
|
||||
let sTextOriginalContents;
|
||||
|
||||
var bValid;
|
||||
var sExplain = '';
|
||||
if (oField.prop('disabled')) {
|
||||
bValid = true; // disabled fields are not checked
|
||||
} else {
|
||||
// If the CKEditor is not yet loaded, we need to wait for it to be ready
|
||||
// but as we need this function to be synchronous, we need to call it again when the CKEditor is ready
|
||||
if (oCKEditor === undefined){
|
||||
CombodoCKEditorHandler.GetInstance('#'+sFieldId).then((oCKEditor) => {
|
||||
ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
|
||||
CombodoCKEditorHandler.GetInstance('#'+oOptions.sFieldId).then((oCKEditor) => {
|
||||
oOptions.onRetry();
|
||||
});
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
let sTextContent;
|
||||
let sFormattedContent = oCKEditor.getData();
|
||||
|
||||
// Get the contents without the tags
|
||||
// Check if we have a formatted content that is HTML, otherwise we just have plain text, and we can use it directly
|
||||
let sFormattedContent = oCKEditor.getData();
|
||||
sTextContent = $(sFormattedContent).length > 0 ? $(sFormattedContent).text() : sFormattedContent;
|
||||
|
||||
if (sTextContent === '') {
|
||||
if (sTextContent === '')
|
||||
{
|
||||
// No plain text, maybe there is just an image
|
||||
let oImg = $(sFormattedContent).find("img");
|
||||
if (oImg.length !== 0) {
|
||||
let oImg = $(sFormattedContent).find('img');
|
||||
if (oImg.length !== 0)
|
||||
{
|
||||
sTextContent = 'image';
|
||||
}
|
||||
}
|
||||
|
||||
// Get the original value without the tags
|
||||
let oFormattedOriginalContents = (originalValue !== undefined) ? $('<div></div>').html(originalValue) : undefined;
|
||||
let sTextOriginalContents = (oFormattedOriginalContents !== undefined) ? oFormattedOriginalContents.text() : undefined;
|
||||
let oFormattedOriginalContents = (oOptions.sOriginalValue !== undefined) ? $('<div></div>').html(oOptions.sOriginalValue) : undefined;
|
||||
sTextOriginalContents = (oFormattedOriginalContents !== undefined) ? oFormattedOriginalContents.text() : undefined;
|
||||
|
||||
if (bMandatory && (sTextContent === nullValue)) {
|
||||
bValid = false;
|
||||
sExplain = Dict.S('UI:ValueMustBeSet');
|
||||
} else if ((sTextOriginalContents !== undefined) && (sTextContent === sTextOriginalContents)) {
|
||||
bValid = false;
|
||||
if (sTextOriginalContents === nullValue) {
|
||||
sExplain = Dict.S('UI:ValueMustBeSet');
|
||||
} else {
|
||||
// Note: value change check is not working well yet as the HTML to Text conversion is not exactly the same when done from the PHP value or the CKEditor value.
|
||||
sExplain = Dict.S('UI:ValueMustBeChanged');
|
||||
}
|
||||
} else {
|
||||
bValid = true;
|
||||
if (oOptions.validate !== undefined) {
|
||||
let oValidation = oOptions.validate(sTextContent, sTextOriginalContents);
|
||||
bValid = oValidation.bValid;
|
||||
sExplain = oValidation.sExplain;
|
||||
}
|
||||
|
||||
// Put and event to check the field when the content changes, remove the event right after as we'll call this same function again, and we don't want to call the event more than once (especially not ^2 times on each call)
|
||||
|
||||
// Put an event to check the field when the content changes, remove the event right after as we'll call this same function again, and we don't want to call the event more than once (especially not ^2 times on each call)
|
||||
oCKEditor.model.document.once('change:data', (event) => {
|
||||
ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
|
||||
oOptions.onChange();
|
||||
});
|
||||
}
|
||||
|
||||
ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain);
|
||||
|
||||
ReportFieldValidationStatus(oOptions.sFieldId, oOptions.sFormId, bValid, sExplain);
|
||||
return bValid;
|
||||
}
|
||||
|
||||
function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue)
|
||||
{
|
||||
return EvaluateCKEditorValidation({
|
||||
sFieldId: sFieldId,
|
||||
sFormId: sFormId,
|
||||
sOriginalValue: originalValue,
|
||||
onRetry: function() {
|
||||
ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
|
||||
},
|
||||
onChange: function() {
|
||||
ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
|
||||
},
|
||||
validate: function(sTextContent, sTextOriginalContents) {
|
||||
var bValid;
|
||||
var sExplain = '';
|
||||
if (bMandatory && (sTextContent === nullValue)) {
|
||||
bValid = false;
|
||||
sExplain = Dict.S('UI:ValueMustBeSet');
|
||||
} else if ((sTextOriginalContents !== undefined) && (sTextContent === sTextOriginalContents)) {
|
||||
bValid = false;
|
||||
if (sTextOriginalContents === nullValue) {
|
||||
sExplain = Dict.S('UI:ValueMustBeSet');
|
||||
} else {
|
||||
// Note: value change check is not working well yet as the HTML to Text conversion is not exactly the same when done from the PHP value or the CKEditor value.
|
||||
sExplain = Dict.S('UI:ValueMustBeChanged');
|
||||
}
|
||||
} else {
|
||||
bValid = true;
|
||||
}
|
||||
return {bValid: bValid, sExplain: sExplain};
|
||||
}
|
||||
});
|
||||
}
|
||||
function ResetPwd(id)
|
||||
{
|
||||
// Reset the values of the password fields
|
||||
$('#'+id).val('*****');
|
||||
$('#'+id+'_confirm').val('*****');
|
||||
// And reset the flag, to tell it that the password remains unchanged
|
||||
$('#'+id+'_changed').val(0);
|
||||
// Visual feedback, None when it's Ok
|
||||
$('#v_'+id).html('');
|
||||
// Reset the values of the password fields
|
||||
$('#'+id).val('*****');
|
||||
$('#'+id+'_confirm').val('*****');
|
||||
// And reset the flag, to tell it that the password remains unchanged
|
||||
$('#'+id+'_changed').val(0);
|
||||
// Visual feedback, None when it's Ok
|
||||
$('#v_'+id).html('');
|
||||
}
|
||||
|
||||
// Called whenever the content of a one way encrypted password changes
|
||||
function PasswordFieldChanged(id)
|
||||
{
|
||||
// Set the flag, to tell that the password changed
|
||||
$('#'+id+'_changed').val(1);
|
||||
// Set the flag, to tell that the password changed
|
||||
$('#'+id+'_changed').val(1);
|
||||
}
|
||||
|
||||
// Special validation function for one way encrypted password fields
|
||||
@@ -415,37 +439,48 @@ function ValidatePasswordField(id, sFormId)
|
||||
// to determine if the field is empty or not
|
||||
function ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue)
|
||||
{
|
||||
var bValid = true;
|
||||
var sExplain = '';
|
||||
var sTextContent;
|
||||
|
||||
if ($('#'+sFieldId).prop('disabled'))
|
||||
{
|
||||
bValid = true; // disabled fields are not checked
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the contents (with tags)
|
||||
// Note: For CaseLog we can't retrieve the formatted contents from CKEditor (unlike in ValidateCKEditorField() method) because of the place holder.
|
||||
sTextContent = $('#' + sFieldId).val();
|
||||
var count = $('#'+sFieldId+'_count').val();
|
||||
return EvaluateCKEditorValidation({
|
||||
sFieldId: sFieldId,
|
||||
sFormId: sFormId,
|
||||
sOriginalValue: originalValue,
|
||||
onRetry: function() {
|
||||
ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);
|
||||
},
|
||||
onChange: function() {
|
||||
ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);
|
||||
},
|
||||
validate: function(sTextContent, sTextOriginalContents) {
|
||||
var bValid;
|
||||
var sExplain = '';
|
||||
// CaseLog is special: history count matters when deciding if the field is empty
|
||||
var count = $('#'+sFieldId+'_count').val();
|
||||
|
||||
if (bMandatory && (count == 0) && (sTextContent == nullValue))
|
||||
{
|
||||
// No previous entry and no content typed
|
||||
bValid = false;
|
||||
sExplain = Dict.S('UI:ValueMustBeSet');
|
||||
if (bMandatory && (count == 0) && (sTextContent === nullValue))
|
||||
{
|
||||
// No previous entry and no content typed
|
||||
bValid = false;
|
||||
sExplain = Dict.S('UI:ValueMustBeSet');
|
||||
}
|
||||
else if ((sTextOriginalContents !== undefined) && (sTextContent === sTextOriginalContents))
|
||||
{
|
||||
bValid = false;
|
||||
if (sTextOriginalContents === nullValue)
|
||||
{
|
||||
sExplain = Dict.S('UI:ValueMustBeSet');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: value change check is not working well yet as the HTML to Text conversion is not exactly the same when done from the PHP value or the CKEditor value.
|
||||
sExplain = Dict.S('UI:ValueMustBeChanged');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bValid = true;
|
||||
}
|
||||
return {bValid: bValid, sExplain: sExplain};
|
||||
}
|
||||
else if ((originalValue != undefined) && (sTextContent == originalValue))
|
||||
{
|
||||
bValid = false;
|
||||
sExplain = Dict.S('UI:ValueMustBeChanged');
|
||||
}
|
||||
}
|
||||
ReportFieldValidationStatus(sFieldId, sFormId, bValid, '' /* sExplain */);
|
||||
|
||||
// We need to check periodically as CKEditor doesn't trigger our events. More details in UIHTMLEditorWidget::Display() @ line 92
|
||||
setTimeout(function(){ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
// Validate the inputs depending on the current setting
|
||||
|
||||
@@ -102,6 +102,7 @@ if ($sTargetPage === false || $sModule === 'core' || $sModule === 'dictionaries'
|
||||
// force login if needed
|
||||
|
||||
$aModuleDelegatedAuthenticationEndpointsList = GetModuleDelegatedAuthenticationEndpoints($sModule);
|
||||
// If module doesn't have the delegated authentication endpoints list defined, we rely on the conf. param. to decide if we force login or not.
|
||||
if (is_null($aModuleDelegatedAuthenticationEndpointsList)) {
|
||||
$bForceLoginWhenNoDelegatedAuthenticationEndpoints = utils::GetConfig()->Get('security.force_login_when_no_delegated_authentication_endpoints_list');
|
||||
if ($bForceLoginWhenNoDelegatedAuthenticationEndpoints) {
|
||||
@@ -109,14 +110,14 @@ if (is_null($aModuleDelegatedAuthenticationEndpointsList)) {
|
||||
LoginWebPage::DoLoginEx();
|
||||
}
|
||||
}
|
||||
// If module defined a delegated authentication endpoints but not for the current page, we consider that the page is not allowed to be executed without login
|
||||
if (is_array($aModuleDelegatedAuthenticationEndpointsList) && !in_array($sPage, $aModuleDelegatedAuthenticationEndpointsList)) {
|
||||
// if module defined a delegated authentication endpoints but not for the current page, we consider that the page is not allowed to be executed without login
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
LoginWebPage::DoLoginEx();
|
||||
}
|
||||
// If user is not logged in, log a warning in the log file as the page is executed without login, which is not recommended for security reason
|
||||
if (is_null($aModuleDelegatedAuthenticationEndpointsList) && !UserRights::IsLoggedIn()) {
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
// check if user is not logged in, if not log a warning in the log file as the page is executed without login, which is not recommended for security reason
|
||||
IssueLog::Debug("The '$sPage' page is executed without logging in. This call will be blocked in the future and will likely cause unwanted behaviour in the '$sModule' module. Please define a delegated authentication endpoint for the module, as described at https://www.itophub.io/wiki/page?id=latest:customization:new_extension#security.");
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class ModuleInstallation extends DBObject
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core,view_in_gui",
|
||||
"category" => "core,view_in_gui,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
'name_attcode' => ['name', 'version'],
|
||||
"state_attcode" => "",
|
||||
|
||||
@@ -340,8 +340,10 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$aClassAliases = $oSet->GetFilter()->GetSelectedClasses();
|
||||
$aAuthorizedClasses = [];
|
||||
foreach ($aClassAliases as $sAlias => $sClassName) {
|
||||
if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) &&
|
||||
((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))) {
|
||||
if (
|
||||
((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) !== UR_ALLOWED_NO) || ($aExtraParams['display_unauthorized_objects'] ?? false) === true)
|
||||
&& ((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))
|
||||
) {
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
@@ -520,6 +522,14 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
if ($aData['checked']) {
|
||||
if ($sAttCode == '_key_') {
|
||||
if ($bViewLink) {
|
||||
$sRenderLink = "return row['".$sClassAlias."/hyperlink'];";
|
||||
if (
|
||||
($aExtraParams['display_unauthorized_objects'] ?? false) === true
|
||||
&& UserRights::IsActionAllowed($sClassName, UR_ACTION_READ) !== UR_ALLOWED_YES
|
||||
) {
|
||||
$sRenderLink = "return row['".$sClassAlias."/friendlyname'];";
|
||||
}
|
||||
|
||||
$aColumnDefinition[] = [
|
||||
'description' => $aData['label'],
|
||||
'object_class' => $sClassName,
|
||||
@@ -527,7 +537,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
'attribute_code' => $sAttCode,
|
||||
'attribute_type' => '_key_',
|
||||
'attribute_label' => MetaModel::GetName($sClassName),
|
||||
'render' => "return row['".$sClassAlias."/hyperlink'];",
|
||||
'render' => $sRenderLink,
|
||||
];
|
||||
|
||||
}
|
||||
@@ -972,6 +982,8 @@ JS;
|
||||
/** Handler to call when trying to create a new object in modal */
|
||||
'creation_disallowed',
|
||||
/** Don't provide the standard object creation feature */
|
||||
'display_unauthorized_objects',
|
||||
/** bool Display objects for which the user has no read rights */
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,10 @@ class AjaxRenderController
|
||||
$oSet->SetShowObsoleteData($bShowObsoleteData);
|
||||
|
||||
// N°8606 : Check user permissions on the main class
|
||||
if (UserRights::IsActionAllowed($oSet->GetClass(), UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES) {
|
||||
if (
|
||||
UserRights::IsActionAllowed($oSet->GetClass(), UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES
|
||||
&& ($aExtraParams['display_unauthorized_objects'] ?? false) === false
|
||||
) {
|
||||
throw new Exception(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $oSet->GetClass()));
|
||||
}
|
||||
|
||||
@@ -103,7 +106,10 @@ class AjaxRenderController
|
||||
}
|
||||
|
||||
// N°8606 : Check user permissions on the current class
|
||||
if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES) {
|
||||
if (
|
||||
UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES
|
||||
&& ($aExtraParams['display_unauthorized_objects'] ?? false) === false
|
||||
) {
|
||||
throw new Exception(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $sClass));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use CoreException;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Dict;
|
||||
use EventNotificationNewsroom;
|
||||
use MetaModel;
|
||||
use SecurityException;
|
||||
use UserRights;
|
||||
@@ -358,6 +359,7 @@ JS
|
||||
// Search for all notifications for the current user
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT EventNotificationNewsroom');
|
||||
$oSearch->AddCondition('contact_id', UserRights::GetContactId(), '=');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch, ['read' => true, 'date' => false], []);
|
||||
|
||||
// Add main content block
|
||||
@@ -526,6 +528,7 @@ JS
|
||||
|
||||
if (utils::IsNotNullOrEmptyString($iContactId)) {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT EventNotificationNewsroom WHERE contact_id = :contact_id AND read = "no"');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch, [], ['contact_id' => $iContactId]);
|
||||
|
||||
while ($oMessage = $oSet->Fetch()) {
|
||||
@@ -539,7 +542,7 @@ $sMessage
|
||||
HTML;
|
||||
|
||||
$sIcon = $oMessage->Get('icon') !== null ?
|
||||
$oMessage->Get('icon')->GetDisplayURL('EventNotificationNewsroom', $oMessage->GetKey(), 'icon') :
|
||||
$oMessage->Get('icon')->GetDisplayURL(EventNotificationNewsroom::class, $oMessage->GetKey(), 'icon') :
|
||||
Branding::GetCompactMainLogoAbsoluteUrl();
|
||||
$aMessages[] = [
|
||||
'id' => $oMessage->GetKey(),
|
||||
@@ -576,6 +579,7 @@ HTML;
|
||||
|
||||
if (utils::IsNotNullOrEmptyString($iContactId)) {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT EventNotificationNewsroom WHERE contact_id = :contact_id AND read = "no"');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch, [], ['contact_id' => $iContactId]);
|
||||
|
||||
while ($oEvent = $oSet->Fetch()) {
|
||||
@@ -605,7 +609,7 @@ HTML;
|
||||
$sEventId = utils::ReadParam('event_id', 0);
|
||||
if ($sEventId > 0) {
|
||||
try {
|
||||
$oEvent = MetaModel::GetObject('EventNotificationNewsroom', $sEventId);
|
||||
$oEvent = MetaModel::GetObject(EventNotificationNewsroom::class, $sEventId, true, true);
|
||||
if ($oEvent !== null && $oEvent->Get('contact_id') === UserRights::GetContactId()) {
|
||||
$oEvent->Set('read', 'yes');
|
||||
$oEvent->SetCurrentDate('read_date');
|
||||
|
||||
@@ -117,6 +117,7 @@ class NotificationsRepository
|
||||
protected function PrepareSearchForNotificationsByContact(int $iContactId, array $aNotificationIds = []): DBSearch
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT EventNotificationNewsroom WHERE contact_id = :contact_id");
|
||||
$oSearch->AllowAllData();
|
||||
$aParams = [
|
||||
"contact_id" => $iContactId,
|
||||
];
|
||||
|
||||
@@ -645,4 +645,54 @@ SCSS;
|
||||
[ '/var/www/html/iTop/css/ui-lightness/images/ui-icons_222222_256x240.png', '/var/www/html/iTop/env-production//branding/themes/light-grey//../../../../css/ui-lightness/images/ui-icons_222222_256x240.png' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aThemeParameters
|
||||
* @param $bSetupCompilationTimestamp
|
||||
* @param $aExpectedClonedParameters
|
||||
* @dataProvider CloneParameterParameterOverloadProvider
|
||||
*/
|
||||
public function testCloneParameterParameterOverload($aThemeParameters, $bSetupCompilationTimestamp, $aExpectedClonedParameters)
|
||||
{
|
||||
$aClonedParameters = ThemeHandler::CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, [APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/imports/']);
|
||||
$this->assertEquals($aExpectedClonedParameters, $aClonedParameters);
|
||||
}
|
||||
|
||||
public function CloneParameterParameterOverloadProvider()
|
||||
{
|
||||
return [
|
||||
"empty parameters" => [
|
||||
'parameters' => [],
|
||||
'timestamp' => '1',
|
||||
'expected' => [
|
||||
'$version' => '1',
|
||||
],
|
||||
],
|
||||
"parameters without variables" => [
|
||||
'parameters' => [
|
||||
'variable_imports' => ['file1' => 'variable_imports.scss'],
|
||||
'utility_imports' => ['util1' => 'path2'],
|
||||
'stylesheets' => ['style1' => 'path3'],
|
||||
],
|
||||
'timestamp' => '2',
|
||||
'expected' => [
|
||||
'var1' => 'value1',
|
||||
'var2' => 'value2',
|
||||
'$version' => '2',
|
||||
],
|
||||
],
|
||||
"parameters with variables overload" => [
|
||||
'parameters' => [
|
||||
'variables' => ['var1' => 'value2'],
|
||||
'variable_imports' => ['file1' => 'variable_imports.scss'],
|
||||
],
|
||||
'timestamp' => '3',
|
||||
'expected' => [
|
||||
'var1' => 'value2',
|
||||
'var2' => 'value2',
|
||||
'$version' => '3',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
$var1: value1;
|
||||
$var2: value2;
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use lnkFunctionalCIToTicket;
|
||||
use MetaModel;
|
||||
use ormLinkSet;
|
||||
use UserRequest;
|
||||
use UserRights;
|
||||
|
||||
class DBSearchFilterJoinTest extends ItopDataTestCase
|
||||
{
|
||||
private const RESTRICTED_PROFILE = 'Configuration Manager';
|
||||
private $aData = [];
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('application/startup.inc.php');
|
||||
$this->aData = $this->CreateDBSearchFilterTestData();
|
||||
DBSearch::EnableQueryCache(false, false);
|
||||
$this->LoginRestrictedUser($this->aData['allowed_org_id'], self::RESTRICTED_PROFILE);
|
||||
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testDBSearchFilterAppliedToJoinsWhenEnabled(string $sOql, int $iExpectedCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(true);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testDBSearchFilterAppliedToJoinsWhenDisabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(false);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testAllowAllDataBypassesDBSearchFilterWhenEnabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(true);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testAllowAllDataBypassesDBSearchFilterWhenDisabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(false);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
public function JoinedAndNestedOqlProvider(): array
|
||||
{
|
||||
return [
|
||||
'join-filter-on-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
],
|
||||
'nested-in-select' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF WHERE OSF.id IN (SELECT OSF1 FROM OSFamily AS OSF1 JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF1.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org)",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
|
||||
],
|
||||
'userrequest-join-person-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = VM.id JOIN UserRequest AS UR ON L.ticket_id = UR.id JOIN Person AS P ON UR.caller_id = P.id JOIN Organization AS O ON P.org_id = O.id WHERE O.name = :denied_org",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
],
|
||||
'union-join-filter-on-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org UNION SELECT OSF2 FROM OSFamily AS OSF2 JOIN VirtualMachine AS VM2 ON VM2.osfamily_id = OSF2.id JOIN Organization AS O2 ON VM2.org_id = O2.id WHERE O2.name = :allowed_org",
|
||||
'expected_filtered_count' => 1,
|
||||
'expected_unfiltered_count' => 2,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function EnableJoinFilterConfig(bool $bEnabled): void
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$oConfig->Set('security.disable_joined_classes_filter', !$bEnabled);
|
||||
}
|
||||
|
||||
private function CreateDBSearchFilterTestData(): array
|
||||
{
|
||||
$sSuffix = 'DBSearchFilterJoinTest';
|
||||
|
||||
$sAllowedOrgName = 'DBSearchFilterAllowedOrg-'.$sSuffix;
|
||||
$iAllowedOrgId = $this->GivenObjectInDB('Organization', [
|
||||
'name' => $sAllowedOrgName,
|
||||
]);
|
||||
|
||||
$this->debug("Org allowed id: $iAllowedOrgId");
|
||||
$sDeniedOrgName = 'DBSearchFilterDeniedOrg-'.$sSuffix;
|
||||
$iDeniedOrgId = $this->GivenObjectInDB('Organization', [
|
||||
'name' => $sDeniedOrgName,
|
||||
]);
|
||||
$this->debug("Org denied id: $iDeniedOrgId");
|
||||
|
||||
$iDeniedOsFamilyId = $this->GivenObjectInDB('OSFamily', [
|
||||
'name' => 'DBSearchFilterOsFamilyDenied-'.$sSuffix,
|
||||
]);
|
||||
|
||||
$iAllowedOsFamilyId = $this->GivenObjectInDB('OSFamily', [
|
||||
'name' => 'DBSearchFilterOsFamilyAllowed-'.$sSuffix,
|
||||
]);
|
||||
|
||||
$iDeniedVMId = $this->GivenObjectInDB('VirtualMachine', [
|
||||
'name' => 'DBSearchFilterVmDenied-'.$sSuffix,
|
||||
'org_id' => $iDeniedOrgId,
|
||||
'osfamily_id' => $iDeniedOsFamilyId,
|
||||
'virtualhost_id' => 1,
|
||||
]);
|
||||
|
||||
$iVirtualHostId = $this->GivenObjectInDB('Hypervisor', [
|
||||
'name' => 'DBSearchFilterVHost-'.$sSuffix,
|
||||
'org_id' => $iAllowedOrgId,
|
||||
]);
|
||||
|
||||
$this->GivenObjectInDB('VirtualMachine', [
|
||||
'name' => 'DBSearchFilterVmAllowed-'.$sSuffix,
|
||||
'org_id' => $iAllowedOrgId,
|
||||
'osfamily_id' => $iAllowedOsFamilyId,
|
||||
'virtualhost_id' => $iVirtualHostId,
|
||||
]);
|
||||
|
||||
$oDeniedPerson = $this->CreatePerson('Denied-'.$sSuffix, $iDeniedOrgId);
|
||||
|
||||
$oUserRequest = $this->CreateUserRequest('Denied'.$sSuffix, [
|
||||
'caller_id' => $oDeniedPerson->GetKey(),
|
||||
'org_id' => $iDeniedOrgId,
|
||||
]);
|
||||
|
||||
// Add Virtual Machine to UserRequest lnk
|
||||
$oLinkSet = new ormLinkSet(UserRequest::class, 'functionalcis_list', DBObjectSet::FromScratch(lnkFunctionalCIToTicket::class));
|
||||
|
||||
$oLink = MetaModel::NewObject(lnkFunctionalCIToTicket::class, ['functionalci_id' => $iDeniedVMId]);
|
||||
$oLinkSet->AddItem($oLink);
|
||||
|
||||
$oUserRequest->Set('functionalcis_list', $oLinkSet);
|
||||
$oUserRequest->DBUpdate();
|
||||
|
||||
return [
|
||||
'allowed_org_id' => $iAllowedOrgId,
|
||||
'allowed_org_name' => $sAllowedOrgName,
|
||||
'denied_org_name' => $sDeniedOrgName,
|
||||
];
|
||||
}
|
||||
|
||||
private function LoginRestrictedUser(int $iAllowedOrgId, string $sProfileName): void
|
||||
{
|
||||
$sLogin = $this->GivenUserRestrictedToAnOrganizationInDB($iAllowedOrgId, self::$aURP_Profiles[$sProfileName]);
|
||||
UserRights::Login($sLogin);
|
||||
}
|
||||
}
|
||||
@@ -214,14 +214,14 @@ class UserRightsTest extends ItopDataTestCase
|
||||
'User Portal UserRequest read' => [2, ['class' => 'UserRequest', 'action' => 1, 'res' => true]],
|
||||
'User Portal URP_UserProfile read' => [2, ['class' => 'URP_UserProfile', 'action' => 1, 'res' => false]],
|
||||
'User Portal UserLocal read' => [2, ['class' => 'UserLocal', 'action' => 1, 'res' => false]],
|
||||
'User Portal ModuleInstallation read' => [2, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => true]],
|
||||
'User Portal ModuleInstallation read' => [2, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => false]],
|
||||
|
||||
/* Configuration manager (1 = UR_ACTION_READ) */
|
||||
'Configuration manager FunctionalCI read' => [3, ['class' => 'FunctionalCI', 'action' => 1, 'res' => true]],
|
||||
'Configuration manager UserRequest read' => [3, ['class' => 'UserRequest', 'action' => 1, 'res' => true]],
|
||||
'Configuration manager URP_UserProfile read' => [3, ['class' => 'URP_UserProfile', 'action' => 1, 'res' => false]],
|
||||
'Configuration manager UserLocal read' => [3, ['class' => 'UserLocal', 'action' => 1, 'res' => false]],
|
||||
'Configuration manager ModuleInstallation read' => [3, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => true]],
|
||||
'Configuration manager ModuleInstallation read' => [3, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => false]],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -267,14 +267,14 @@ class UserRightsTest extends ItopDataTestCase
|
||||
'User Portal UserRequest' => [2, ['class' => 'UserRequest', 'action' => 2, 'res' => true]],
|
||||
'User Portal URP_UserProfile' => [2, ['class' => 'URP_UserProfile', 'action' => 2, 'res' => false]],
|
||||
'User Portal UserLocal' => [2, ['class' => 'UserLocal', 'action' => 2, 'res' => false]],
|
||||
'User Portal ModuleInstallation' => [2, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => true]],
|
||||
'User Portal ModuleInstallation' => [2, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => false]],
|
||||
|
||||
/* Configuration manager (2 = UR_ACTION_MODIFY) */
|
||||
'Configuration manager FunctionalCI' => [3, ['class' => 'FunctionalCI', 'action' => 2, 'res' => true]],
|
||||
'Configuration manager UserRequest' => [3, ['class' => 'UserRequest', 'action' => 2, 'res' => false]],
|
||||
'Configuration manager URP_UserProfile' => [3, ['class' => 'URP_UserProfile', 'action' => 2, 'res' => false]],
|
||||
'Configuration manager UserLocal' => [3, ['class' => 'UserLocal', 'action' => 2, 'res' => false]],
|
||||
'Configuration manager ModuleInstallation' => [3, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => true]],
|
||||
'Configuration manager ModuleInstallation' => [3, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => false]],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,36 @@
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Application\WebPage\CaptureWebPage;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ormDocument;
|
||||
use UserRights;
|
||||
|
||||
/**
|
||||
* Tests of the ormDocument class
|
||||
*/
|
||||
class ormDocumentTest extends ItopDataTestCase
|
||||
{
|
||||
private const RESTRICTED_PROFILE = 'Configuration Manager';
|
||||
private int $iUserOrg;
|
||||
private int $iOrgDifferentFromUser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->iUserOrg = $this->GivenObjectInDB('Organization', [
|
||||
'name' => 'UserOrg',
|
||||
]);
|
||||
|
||||
$this->iOrgDifferentFromUser = $this->GivenObjectInDB('Organization', [
|
||||
'name' => 'OrgDifferentFromUser',
|
||||
]);
|
||||
|
||||
$this->LoginRestrictedUser($this->iUserOrg, self::RESTRICTED_PROFILE);
|
||||
$this->ResetMetaModelQueyCacheGetObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -139,4 +161,107 @@ class ormDocumentTest extends ItopDataTestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that DownloadDocument enforces rights for documents
|
||||
*
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testDownloadDocumentDifferentOrg(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iDeniedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iOrgDifferentFromUser, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageDenied = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageDenied, $sTargetClass, $iDeniedDocumentId, $sAttCode);
|
||||
$sDeniedHtml = (string) $oPageDenied->GetHtml();
|
||||
$this->assertStringContainsString(
|
||||
'the object does not exist or you are not allowed to view it',
|
||||
$sDeniedHtml,
|
||||
'Expected error message when rights are missing.'
|
||||
);
|
||||
$this->assertStringNotContainsString($sData, $sDeniedHtml, 'Unexpected file data present when rights are missing.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that DownloadDocument allows to retrieve document with the same org (or host object org)
|
||||
*
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testDownloadDocumentSameOrg(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iAllowedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iUserOrg, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageAllowed = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageAllowed, $sTargetClass, $iAllowedDocumentId, $sAttCode);
|
||||
$sAllowedHtml = (string) $oPageAllowed->GetHtml();
|
||||
$this->assertStringContainsString($sData, $sAllowedHtml, 'Expected file data present when rights are sufficient.');
|
||||
$this->assertStringNotContainsString('the object does not exist or you are not allowed to view it', $sAllowedHtml, 'Unexpected error message when rights are sufficient.');
|
||||
}
|
||||
|
||||
public function DownloadDocumentRightsProvider(): array
|
||||
{
|
||||
return [
|
||||
'DocumentFile' => [
|
||||
'class' => 'DocumentFile',
|
||||
'data_attribute_id' => 'file',
|
||||
'data' => 'document_data',
|
||||
'file_name' => 'document.txt',
|
||||
'host_class' => null],
|
||||
'Attachment' => [
|
||||
'class' => 'Attachment',
|
||||
'data_attribute_id' => 'contents',
|
||||
'data' => 'attachment_data',
|
||||
'file_name' => 'attachment.txt',
|
||||
'host_class' => 'UserRequest'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to avoid duplicating object creation in tests
|
||||
* Created objects and host objects depending on the Document class
|
||||
* @param string $sTargetClass
|
||||
* @param string $sAttCode
|
||||
* @param int $iOrgId
|
||||
* @param string $sData
|
||||
* @param string $sFileName
|
||||
* @param string|null $sHostClass
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function CreateDownloadTargetInOrg(string $sTargetClass, string $sAttCode, int $iOrgId, string $sData, string $sFileName, ?string $sHostClass): int
|
||||
{
|
||||
|
||||
if ($sTargetClass === 'DocumentFile') {
|
||||
return $this->GivenObjectInDB($sTargetClass, [
|
||||
'name' => 'UnitTestDocFile_'.uniqid(),
|
||||
'org_id' => $iOrgId,
|
||||
$sAttCode => new ormDocument($sData, 'text/plain', $sFileName),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($sTargetClass === 'Attachment') {
|
||||
$iHostId = $this->GivenObjectInDB($sHostClass, [
|
||||
'title' => 'UnitTestUserRequest_'.uniqid(),
|
||||
'org_id' => $iOrgId,
|
||||
'description' => 'A user request for testing attachment download rights',
|
||||
]);
|
||||
|
||||
return $this->GivenObjectInDB('Attachment', [
|
||||
'item_class' => $sHostClass,
|
||||
'item_id' => $iHostId,
|
||||
$sAttCode => new ormDocument($sData, 'text/plain', $sFileName),
|
||||
]);
|
||||
}
|
||||
|
||||
throw new \Exception("Unsupported target class: $sTargetClass");
|
||||
}
|
||||
|
||||
private function LoginRestrictedUser(int $iAllowedOrgId, string $sProfileName): void
|
||||
{
|
||||
if (UserRights::IsLoggedIn()) {
|
||||
UserRights::Logoff();
|
||||
}
|
||||
$sLogin = $this->GivenUserRestrictedToAnOrganizationInDB($iAllowedOrgId, self::$aURP_Profiles[$sProfileName]);
|
||||
UserRights::Login($sLogin);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user