Compare commits

...

2 Commits

17 changed files with 209 additions and 23 deletions

View File

@@ -1044,6 +1044,33 @@ class LoginWebPage extends NiceWebPage
exit;
}
}
// Check allowed context for the selected user
// If the user has no context -> allow to connect
// If the entry point has no context -> allow only users with no context specified
// If the entry point has one or more contexts -> allow only users with one of the entry point context specified
$oUser = UserRights::GetUserObject();
$aContexts = $oUser->Get('allowed_contexts')->GetValues();
if (count($aContexts) > 0) {
if (ContextTag::Check($aContexts) === false) {
IssueLog::Error(sprintf(
'User "%s" cannot connect: the current context (%s) does not match current allowed contexts: %s',
UserRights::GetUser(),
implode(',', \ContextTag::GetStack()),
implode(',', $aContexts)));
if ($iOnExit == self::EXIT_RETURN) {
return self::EXIT_CODE_NOTAUTHORIZED;
} else {
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add('<h1>'.Dict::S('UI:Login:Error:AccessRestricted')."</h1>\n");
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu').'</a>');
$oP->output();
exit;
}
}
}
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
}
if ($iOnExit == self::EXIT_RETURN)

View File

@@ -11084,7 +11084,7 @@ class AttributeEnumSet extends AttributeSet
return max(255, $iMaxSize);
}
private function GetRawPossibleValues($aArgs = array(), $sContains = '')
protected function GetRawPossibleValues($aArgs = array(), $sContains = '')
{
/** @var ValueSetEnumPadded $oValSetDef */
$oValSetDef = $this->Get('possible_values');
@@ -11317,6 +11317,65 @@ class AttributeEnumSet extends AttributeSet
}
}
/**
* @since 3.2.0 N°7423
*/
class AttributeContextTagSet extends AttributeEnumSet
{
public static function ListExpectedParams()
{
// allowed_values and possible_values are replaced by context_type and excluded_contexts
return array_diff(
array_merge(parent::ListExpectedParams(), ['is_null_allowed', 'max_items', 'context_type', 'denied_contexts']),
['allowed_values', 'possible_values']);
}
protected function GetRawPossibleValues($aArgs = array(), $sContains = ''): array
{
$sType = $this->Get('context_type');
$aExcludedContexts = $this->Get('denied_contexts');
$aContexts = [];
switch ($sType) {
case 'authentication':
$aContexts = ContextTag::GetTagsForConnection();
break;
case 'all':
$aContexts = ContextTag::GetTags();
break;
}
$aContexts = array_diff($aContexts, $aExcludedContexts);
$oValSetDef = new ValueSetEnumPadded($aContexts);
return $oValSetDef->GetValues([], $sContains);
}
public function GetPossibleValues($aArgs = array(), $sContains = '')
{
return $this->GetRawPossibleValues($aArgs, $sContains);
}
public function GetValueLabel($sValue)
{
if ($sValue instanceof ormSet) {
$sValue = implode(', ', $sValue->GetValues());
}
$aValues = $this->GetRawPossibleValues();
$sLabel = Dict::S('Enum:Undefined');
if (is_string($sValue) && isset($aValues[$sValue])) {
$sLabel = $aValues[$sValue];
}
return $sLabel;
}
public function GetValueDescription($sValue)
{
return '';
}
}
class AttributeClassAttCodeSet extends AttributeSet
{

View File

@@ -58,12 +58,15 @@ class ContextTag
public const TAG_SETUP = 'Setup';
public const TAG_SYNCHRO = 'Synchro';
public const TAG_REST = 'REST/JSON';
/**
* @since 3.2.0 N°7423
*/
public const TAG_GUI = 'GUI';
/**
* @since 3.1.0 N°6047
*/
public const TAG_IMPORT = 'Import';
/**
/**
* @since 3.1.0 N°6047
*/
public const TAG_EXPORT = 'Export';
@@ -101,11 +104,18 @@ class ContextTag
/**
* Check if a given tag is present in the stack
* @param string $sTag
* or check if one of the tags in the array is present
*
* @param array|string $sTag
*
* @return bool
*/
public static function Check($sTag)
public static function Check(array|string $sTag): bool
{
if (is_array($sTag)) {
return (count(array_intersect($sTag, static::$aStack)) > 0);
}
return in_array($sTag, static::$aStack);
}
@@ -118,6 +128,25 @@ class ContextTag
return static::$aStack;
}
public static function GetTagsForConnection(): array
{
$aRawTags = array(
ContextTag::TAG_GUI,
ContextTag::TAG_REST,
ContextTag::TAG_SYNCHRO,
ContextTag::TAG_IMPORT,
ContextTag::TAG_EXPORT);
$aTags = array();
foreach ($aRawTags as $sRawTag)
{
$aTags[$sRawTag] = Dict::S("Core:Context={$sRawTag}");
}
return $aTags;
}
/**
* Get all the predefined context tags
* @return array
@@ -125,11 +154,14 @@ class ContextTag
public static function GetTags()
{
$aRawTags = array(
ContextTag::TAG_GUI,
ContextTag::TAG_REST,
ContextTag::TAG_SYNCHRO,
ContextTag::TAG_SETUP,
ContextTag::TAG_CONSOLE,
ContextTag::TAG_CRON,
ContextTag::TAG_IMPORT,
ContextTag::TAG_EXPORT,
ContextTag::TAG_PORTAL);
$aTags = array();

View File

@@ -808,6 +808,29 @@
</fields>
</class>
</classes>
<attribute_definitions _delta="define">
<attribute_definition id="AttributeContextTagSet">
<properties>
<property id="sql"/>
<property id="is_null_allowed"/>
<property id="max_items"/>
<property id="default_value"/>
<property id="context_type">
<php_param>context_type</php_param>
<mandatory>true</mandatory>
<type>string</type>
</property>
<property id="denied_contexts">
<php_param>denied_contexts</php_param>
<mandatory>false</mandatory>
<type>collection</type>
<collection_element_name>denied_context</collection_element_name>
<collection_type>id</collection_type>
<default/>
</property>
</properties>
</attribute_definition>
</attribute_definitions>
<attribute_properties_definition _delta="define">
<properties>
<property id="sql">
@@ -830,11 +853,6 @@
<mandatory>true</mandatory>
<type>string</type>
</property>
<property id="sql">
<php_param>sql</php_param>
<mandatory>true</mandatory>
<type>string</type>
</property>
<property id="class_attcode">
<php_param>class_attcode</php_param>
<mandatory>true</mandatory>

View File

@@ -252,6 +252,8 @@ abstract class User extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profile_list",array("linked_class" => "URP_UserProfile", "ext_key_to_me" => "userid", "ext_key_to_remote" => "profileid", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array(), "display_style" => 'property', "with_php_constraint" => true, "with_php_computation" => true)));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("allowed_org_list", array("linked_class" => "URP_UserOrg", "ext_key_to_me" => "userid", "ext_key_to_remote" => "allowed_org_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array(), 'with_php_constraint' => true)));
MetaModel::Init_AddAttribute(new AttributeCaseLog("log", array("sql" => 'log', "is_null_allowed" => true, "default_value" => '', "allowed_values" => null, "depends_on" => array(), "always_load_in_tables" => false)));
$aTags = ContextTag::GetTagsForConnection();
MetaModel::Init_AddAttribute(new AttributeEnumSet('allowed_contexts', array('allowed_values' => null, 'possible_values' => new ValueSetEnumPadded($aTags, true), 'sql' => 'allowed_contexts', 'depends_on' => array(), 'is_null_allowed' => true, 'max_items' => 12)));
// Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'language', 'status', 'profile_list', 'allowed_org_list', 'log')); // Unused as it's an abstract class !

View File

@@ -580,6 +580,7 @@ class ValueSetEnumPadded extends ValueSetEnum
$aPaddedValues[$sKey] = $sVal;
}
$this->m_values = $aPaddedValues;
$this->m_bIsLoaded = true;
}
}

View File

@@ -50,6 +50,7 @@ $ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
[data-attribute-type="AttributeCustomFields"],
[data-attribute-type="AttributeTagSet"],
[data-attribute-type="AttributeEnumSet"],
[data-attribute-type="AttributeContextTagSet"],
[data-attribute-type="AttributeLinkedSet"],
[data-attribute-type="AttributeLinkedSetIndirect"],
[data-attribute-type="AttributeClassAttCodeSet"],

File diff suppressed because one or more lines are too long

View File

@@ -209,12 +209,15 @@ Operators:<br/>
'Core:AttributeTag' => 'Tags',
'Core:AttributeTag+' => '',
'Core:Context=REST/JSON' => 'REST',
'Core:Context=REST/JSON' => 'Webservice',
'Core:Context=Synchro' => 'Synchro',
'Core:Context=Setup' => 'Setup',
'Core:Context=GUI:Console' => 'Console',
'Core:Context=CRON' => 'cron',
'Core:Context=CRON' => 'Background tasks',
'Core:Context=GUI:Portal' => 'Portal',
'Core:Context=GUI' => 'GUI',
'Core:Context=Import' => 'Import',
'Core:Context=Export' => 'Export',
));

View File

@@ -175,6 +175,8 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:User/Attribute:status+' => 'Whether the user account is enabled or disabled.',
'Class:User/Attribute:status/Value:enabled' => 'Enabled',
'Class:User/Attribute:status/Value:disabled' => 'Disabled',
'Class:User/Attribute:allowed_contexts' => 'Allowed authentication contexts',
'Class:User/Attribute:allowed_contexts+' => 'List of authentication contexts that the user is allowed to access',
'Class:User/Error:LoginMustBeUnique' => 'Login must be unique - "%1$s" is already being used.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'At least one profile must be assigned to this user.',

View File

@@ -161,12 +161,15 @@ Opérateurs :<br/>
'Core:FriendlyName-Description' => 'Nom complet',
'Core:AttributeTag' => 'Taxon',
'Core:AttributeTag+' => '',
'Core:Context=REST/JSON' => 'REST',
'Core:Context=REST/JSON' => 'Webservice',
'Core:Context=Synchro' => 'Synchro',
'Core:Context=Setup' => 'Setup',
'Core:Context=GUI:Console' => 'Console',
'Core:Context=CRON' => 'cron',
'Core:Context=CRON' => 'Tâche de fond',
'Core:Context=GUI:Portal' => 'Portal',
'Core:Context=GUI' => 'Interface graphique',
'Core:Context=Import' => 'Import',
'Core:Context=Export' => 'Export',
));

View File

@@ -160,6 +160,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:User/Attribute:status+' => 'Est-ce que ce compte utilisateur est actif, ou non?',
'Class:User/Attribute:status/Value:enabled' => 'Actif',
'Class:User/Attribute:status/Value:disabled' => 'Désactivé',
'Class:User/Attribute:allowed_contexts' => 'Contextes de connexion autorisés',
'Class:User/Attribute:allowed_contexts+' => 'Liste des contextes de connexion autorisés pour cet utilisateur',
'Class:User/Error:LoginMustBeUnique' => 'Le login doit être unique - "%1s" est déjà utilisé.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'L\'utilisateur doit avoir au moins un profil.',
'Class:User/Error:ProfileNotAllowed' => 'Le profil "%1$s" ne peux pas être ajouté à son propre utilisateur, il interdit l\'accès à la console',

View File

@@ -39,6 +39,7 @@ return array(
'AttributeClass' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClassAttCodeSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClassState' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeContextTagSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeCustomFields' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDBField' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDBFieldVoid' => $baseDir . '/core/attributedef.class.inc.php',
@@ -423,7 +424,6 @@ return array(
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => $baseDir . '/sources/Core/Kpi/KpiLogData.php',
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => $baseDir . '/sources/Core/MetaModel/FriendlyNameType.php',
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => $baseDir . '/sources/Core/MetaModel/HierarchicalKey.php',
'Combodo\\iTop\\Core\\Trigger\\Enum\\SubscriptionPolicy' => $baseDir . '/sources/Core/Trigger/Enum/SubscriptionPolicy.php',
'Combodo\\iTop\\Dependencies\\AbstractFolderAnalyzer' => $baseDir . '/sources/Dependencies/AbstractFolderAnalyzer.php',
'Combodo\\iTop\\Dependencies\\Composer\\iTopComposer' => $baseDir . '/sources/Dependencies/Composer/iTopComposer.php',
'Combodo\\iTop\\Dependencies\\NPM\\iTopNPM' => $baseDir . '/sources/Dependencies/NPM/iTopNPM.php',
@@ -2057,7 +2057,6 @@ return array(
'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => $vendorDir . '/symfony/console/Messenger/RunCommandContext.php',
'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => $vendorDir . '/symfony/console/Messenger/RunCommandMessage.php',
'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => $vendorDir . '/symfony/console/Messenger/RunCommandMessageHandler.php',
'Symfony\\Component\\Console\\Output\\AnsiColorMode' => $vendorDir . '/symfony/console/Output/AnsiColorMode.php',
'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php',
@@ -2712,7 +2711,6 @@ return array(
'Symfony\\Component\\Routing\\RequestContext' => $vendorDir . '/symfony/routing/RequestContext.php',
'Symfony\\Component\\Routing\\RequestContextAwareInterface' => $vendorDir . '/symfony/routing/RequestContextAwareInterface.php',
'Symfony\\Component\\Routing\\Requirement\\EnumRequirement' => $vendorDir . '/symfony/routing/Requirement/EnumRequirement.php',
'Symfony\\Component\\Routing\\Requirement\\Requirement' => $vendorDir . '/symfony/routing/Requirement/Requirement.php',
'Symfony\\Component\\Routing\\Route' => $vendorDir . '/symfony/routing/Route.php',
'Symfony\\Component\\Routing\\RouteCollection' => $vendorDir . '/symfony/routing/RouteCollection.php',
'Symfony\\Component\\Routing\\RouteCompiler' => $vendorDir . '/symfony/routing/RouteCompiler.php',

View File

@@ -422,6 +422,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'AttributeClass' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClassAttCodeSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClassState' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeContextTagSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeCustomFields' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDBField' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDBFieldVoid' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
@@ -806,7 +807,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => __DIR__ . '/../..' . '/sources/Core/Kpi/KpiLogData.php',
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => __DIR__ . '/../..' . '/sources/Core/MetaModel/FriendlyNameType.php',
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/MetaModel/HierarchicalKey.php',
'Combodo\\iTop\\Core\\Trigger\\Enum\\SubscriptionPolicy' => __DIR__ . '/../..' . '/sources/Core/Trigger/Enum/SubscriptionPolicy.php',
'Combodo\\iTop\\Dependencies\\AbstractFolderAnalyzer' => __DIR__ . '/../..' . '/sources/Dependencies/AbstractFolderAnalyzer.php',
'Combodo\\iTop\\Dependencies\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Dependencies/Composer/iTopComposer.php',
'Combodo\\iTop\\Dependencies\\NPM\\iTopNPM' => __DIR__ . '/../..' . '/sources/Dependencies/NPM/iTopNPM.php',
@@ -2440,7 +2440,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandContext.php',
'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessage.php',
'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessageHandler.php',
'Symfony\\Component\\Console\\Output\\AnsiColorMode' => __DIR__ . '/..' . '/symfony/console/Output/AnsiColorMode.php',
'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php',
@@ -3095,7 +3094,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Routing\\RequestContext' => __DIR__ . '/..' . '/symfony/routing/RequestContext.php',
'Symfony\\Component\\Routing\\RequestContextAwareInterface' => __DIR__ . '/..' . '/symfony/routing/RequestContextAwareInterface.php',
'Symfony\\Component\\Routing\\Requirement\\EnumRequirement' => __DIR__ . '/..' . '/symfony/routing/Requirement/EnumRequirement.php',
'Symfony\\Component\\Routing\\Requirement\\Requirement' => __DIR__ . '/..' . '/symfony/routing/Requirement/Requirement.php',
'Symfony\\Component\\Routing\\Route' => __DIR__ . '/..' . '/symfony/routing/Route.php',
'Symfony\\Component\\Routing\\RouteCollection' => __DIR__ . '/..' . '/symfony/routing/RouteCollection.php',
'Symfony\\Component\\Routing\\RouteCompiler' => __DIR__ . '/..' . '/symfony/routing/RouteCompiler.php',

View File

@@ -314,6 +314,8 @@ try
$oKPI = new ExecutionKPI();
$oContextGUI = new ContextTag(ContextTag::TAG_GUI);
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
$sLoginMessage = LoginWebPage::DoLogin(); // Check user rights and prompt if needed
$oAppContext = new ApplicationContext();

View File

@@ -2421,14 +2421,14 @@ EOF
/**
* @param string $sPropertyName
* @param array $aProperty
* @param \DOMElement $oField
* @param DesignElement $oField
* @param array $aParameters
*
* @return array
* @throws \DOMFormatException
* @since 3.1.0 N°6040
*/
protected function CompileDynamicProperty(string $sPropertyName, array $aProperty, DOMElement $oField, array $aParameters): array
protected function CompileDynamicProperty(string $sPropertyName, array $aProperty, DesignElement $oField, array $aParameters): array
{
$sPHPParam = $aProperty['php_param'] ?? $sPropertyName;
$bMandatory = $aProperty['mandatory'] ?? false;
@@ -2471,6 +2471,34 @@ EOF
$aParameters[$sPHPParam] = 'null';
}
break;
case 'collection':
$sCollectionElementName = $aProperty['collection_element_name'] ?? null;
$sCollectionType = $aProperty['collection_type'] ?? null;
if (is_null($sCollectionElementName) || is_null($sCollectionType)) {
throw new DOMFormatException("Missing 'collection_element_name' or 'collection_type' in collection definition in meta", null, null, $oField);
}
$oValues = $oField->GetOptionalElement($sPropertyName);
if ($oValues) {
$oValuesNodes = $oValues->getElementsByTagName($sCollectionElementName);
$aValues = [];
/** @var \MFElement $oValueNode */
foreach ($oValuesNodes as $oValueNode) {
switch ($sCollectionType) {
case 'id':
$aValues[] = $oValueNode->GetAttribute('id');
break;
case 'text':
$aValues[] = $oValueNode->textContent;
break;
}
}
$aParameters[$sPHPParam] = var_export($aValues, true);
} elseif ($bMandatory) {
$aParameters[$sPHPParam] = var_export([$sDefault], true);
} else {
$aParameters[$sPHPParam] = 'null';
}
break;
case 'null':
$aParameters[$sPHPParam] = 'null';
break;
@@ -3995,6 +4023,10 @@ PHP;
$aDefinition['default'] = $oNode->GetText();
}
}
if (($aDefinition['type'] ?? null) === 'collection') {
$aDefinition['collection_element_name'] = $oProperty->GetChildText('collection_element_name');
$aDefinition['collection_type'] = $oProperty->GetChildText('collection_type');
}
return $aDefinition;
}

View File

@@ -1114,6 +1114,12 @@ class iTopDesignFormat
$this->RemoveNodeFromXPath('/itop_design/branding/main_favicon');
$this->RemoveNodeFromXPath('/itop_design/branding/portal_favicon');
$this->RemoveNodeFromXPath('/itop_design/branding/login_favicon');
// N°7423 - remove attributes Context Tag Set
$sPath = "/itop_design/classes/class/class/fields/field[@xsi:type='AttributeContextTagSet']";
$this->RemoveNodeFromXPath($sPath);
}
/**