Merge branch 'support/3.0.0-beta5' into develop

This commit is contained in:
Molkobain
2021-09-28 12:58:08 +02:00
9 changed files with 371 additions and 213 deletions

View File

@@ -1012,9 +1012,10 @@ JS
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aValues = $oAttDef->GetAllowedValues();
foreach ($aStates as $sStateValue) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aStateLabels[$sStateValue] = $oAttDef->GetAsPlainText($sStateValue);
$aStateLabels[$sStateValue] = $aValues[$sStateValue];
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
? $aCountsQueryResults[$sStateValue]
: 0;

View File

@@ -7718,14 +7718,18 @@ abstract class MetaModel
/**
* @param string $sClass
* @param string $sAttCode
* @param string $sValue
* @param string|null $sValue Code of the state value, can be null if allowed by the attribute definition
*
* @return \ormStyle|null
* @throws \Exception
* @throws \CoreException
*/
public static function GetEnumStyle(string $sClass, string $sAttCode, string $sValue = ''): ?ormStyle
public static function GetEnumStyle(string $sClass, string $sAttCode, ?string $sValue = ''): ?ormStyle
{
if (strlen($sAttCode) === 0) {
return null;
}
$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
if (!$oAttDef instanceof AttributeEnum) {
throw new CoreException("MetaModel::GetEnumStyle() Attribute $sAttCode of class $sClass is not an AttributeEnum\n");

View File

@@ -0,0 +1,328 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
<classes>
<class id="UserLDAP" _delta="define">
<parent>cmdbAbstractObject</parent>
<php_parent>
<name>UserInternal</name>
</php_parent>
<properties>
<comment>/**
* LDAP Authentication
* User authentication Module, no password at all!
*
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/</comment>
<category>addon/authentication,grant_by_profile</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>priv_user_ldap</db_table>
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<format>%1$s</format>
<attributes>
<attribute id="login"/>
</attributes>
</naming>
<display_template/>
<icon/>
<reconciliation>
<attributes>
<attribute id="login"/>
</attributes>
</reconciliation>
</properties>
<fields>
<field id="ldap_server" xsi:type="AttributeString">
<sql>ldap_server</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
</fields>
<methods>
<method id="CheckCredentials">
<comment><![CDATA[/**
* Check the user's password against the LDAP server
* Algorithm:
* 1) Connect to the LDAP server, using a predefined account (or anonymously)
* 2) Search for the specified user, based on a specific search query/pattern
* 3) If exactly one user is found, continue, otherwise return false (wrong user or wrong query configured)
* 3) Bind again to LDAP using the DN of the found user and the password
* 4) If the bind is successful return true, otherwise return false (wrong password)
*
* @param string $sPassword The user's password to validate against the LDAP server
*
* @return boolean True if the password is Ok, false otherwise
* @throws \ArchivedObjectException
* @throws \CoreException
*/]]></comment>
<static>false</static>
<access>public</access>
<type>OQLMenuNode</type>
<code><![CDATA[ public function CheckCredentials($sPassword)
{
$sServer = $this->Get('ldap_server');
if (empty($sServer))
{
$sLDAPHost = MetaModel::GetModuleSetting('authent-ldap', 'host', 'localhost');
$iLDAPPort = MetaModel::GetModuleSetting('authent-ldap', 'port', 389);
$sDefaultLDAPUser = MetaModel::GetModuleSetting('authent-ldap', 'default_user', '');
$sDefaultLDAPPwd = MetaModel::GetModuleSetting('authent-ldap', 'default_pwd', '');
$bLDAPStartTLS = MetaModel::GetModuleSetting('authent-ldap', 'start_tls', false);
$aOptions = MetaModel::GetModuleSetting('authent-ldap', 'options', array());
$sLDAPUserQuery = MetaModel::GetModuleSetting('authent-ldap', 'user_query', '');
$sBaseDN = MetaModel::GetModuleSetting('authent-ldap', 'base_dn', '');
$bDebug = MetaModel::GetModuleSetting('authent-ldap', 'debug', false);
}
else
{
$aServers = MetaModel::GetModuleSetting('authent-ldap', 'servers', array());
if (!array_key_exists($sServer, $aServers))
{
$bDebug = MetaModel::GetModuleSetting('authent-ldap', 'debug', false);
$this->LogIssue($bDebug, "ldap_authentication: bad LDAP server configuration: '$sServer' not found");
return false;
}
$aServerParams = $aServers[$sServer];
$sLDAPHost = isset($aServerParams['host']) ? $aServerParams['host'] : 'localhost';
$iLDAPPort = isset($aServerParams['port']) ? $aServerParams['port'] : 389;
$sDefaultLDAPUser = isset($aServerParams['default_user']) ? $aServerParams['default_user'] : '';
$sDefaultLDAPPwd = isset($aServerParams['default_pwd']) ? $aServerParams['default_pwd'] : '';
$bLDAPStartTLS = isset($aServerParams['start_tls']) ? $aServerParams['start_tls'] : false;
$aOptions = isset($aServerParams['options']) ? $aServerParams['options'] : array();
$sLDAPUserQuery = isset($aServerParams['user_query']) ? $aServerParams['user_query'] : '';
$sBaseDN = isset($aServerParams['base_dn']) ? $aServerParams['base_dn'] : '';
$bDebug = isset($aServerParams['debug']) ? $aServerParams['debug'] : false;
}
$hDS = @ldap_connect($sLDAPHost, $iLDAPPort);
if ($hDS === false)
{
$this->LogIssue($bDebug, "ldap_authentication: can not connect to the LDAP server '$sLDAPHost' (port: $iLDAPPort). Check the configuration file config-itop.php.");
return false;
}
if (array_key_exists(LDAP_OPT_DEBUG_LEVEL, $aOptions))
{
// Set debug level before trying to connect, so that debug info appear in the PHP error log if ldap_connect goes wrong
$bRet = ldap_set_option($hDS, LDAP_OPT_DEBUG_LEVEL, $aOptions[LDAP_OPT_DEBUG_LEVEL]);
$this->LogInfo($bDebug, "ldap_set_option('LDAP_OPT_DEBUG_LEVEL', '{$aOptions[LDAP_OPT_DEBUG_LEVEL]}') returned ".($bRet ? 'true' : 'false'));
}
foreach($aOptions as $name => $value)
{
$bRet = ldap_set_option($hDS, $name, $value);
$this->LogInfo($bDebug, "ldap_set_option('$name', '$value') returned ".($bRet ? 'true' : 'false'));
}
if ($bLDAPStartTLS)
{
$this->LogInfo($bDebug, "ldap_authentication: start tls required.");
$hStartTLS = ldap_start_tls($hDS);
//$this->LogIssue($bDebug, "ldap_authentication: hStartTLS = '$hStartTLS'");
if (!$hStartTLS)
{
$this->LogIssue($bDebug, "ldap_authentication: start tls failed.");
return false;
}
}
if ($bind = @ldap_bind($hDS, $sDefaultLDAPUser, $sDefaultLDAPPwd))
{
// Search for the person, using the specified query expression
$sLogin = $this->Get('login');
$iContactId = $this->Get('contactid');
$sFirstName = '';
$sLastName = '';
$sEMail = '';
if ($iContactId > 0)
{
$oPerson = MetaModel::GetObject('Person', $iContactId);
if (is_object($oPerson))
{
$sFirstName = $oPerson->Get('first_name');
$sLastName = $oPerson->Get('name');
$sEMail = $oPerson->Get('email');
}
}
// %1$s => login
// %2$s => first name
// %3$s => last name
// %4$s => email
$sQuery = sprintf($sLDAPUserQuery, $sLogin, $sFirstName, $sLastName, $sEMail);
$hSearchResult = @ldap_search($hDS, $sBaseDN, $sQuery);
$iCountEntries = ($hSearchResult !== false) ? @ldap_count_entries($hDS, $hSearchResult) : 0;
switch($iCountEntries)
{
case 1:
// Exactly one entry found, let's check the password by trying to bind with this user
$aEntry = @ldap_get_entries($hDS, $hSearchResult);
$sUserDN = $aEntry[0]['dn'];
$bUserBind = @ldap_bind($hDS, $sUserDN, $sPassword);
if (($bUserBind !== false) && !empty($sPassword))
{
@ldap_unbind($hDS);
return true; // Password Ok
}
$this->LogIssue($bDebug, "ldap_authentication: wrong password for user: '$sUserDN'.");
return false; // Wrong password
break;
case 0:
// User not found...
$this->LogIssue($bDebug, "ldap_authentication: no entry found with the query '$sQuery', base_dn = '$sBaseDN'. User not found in LDAP.");
break;
default:
// More than one entry... maybe the query is not specific enough...
$this->LogIssue($bDebug, "ldap_authentication: several (".@ldap_count_entries($hDS, $hSearchResult).") entries match the query '$sQuery', base_dn = '$sBaseDN', check that the query defined in config-itop.php is specific enough.");
}
return false;
}
else
{
// Trace: invalid default user for LDAP initial binding
$this->LogIssue($bDebug, "ldap_authentication: cannot bind to the LDAP server '$sLDAPHost' (port: $iLDAPPort), user='$sDefaultLDAPUser', pwd='****'. Error: '".ldap_error($hDS)."'. Check the configuration file config-itop.php.");
return false;
}
}]]></code>
</method>
<method id="TrustWebServerContext">
<static>false</static>
<access>public</access>
<type>OQLMenuNode</type>
<code> public function TrustWebServerContext()
{
return false;
}</code>
</method>
<method id="CanChangePassword">
<static>false</static>
<access>public</access>
<type>OQLMenuNode</type>
<code> public function CanChangePassword()
{
return false;
}</code>
</method>
<method id="ChangePassword">
<static>false</static>
<access>public</access>
<type>OQLMenuNode</type>
<code> public function ChangePassword($sOldPassword, $sNewPassword)
{
return false;
}</code>
</method>
<method id="LogIssue">
<static>false</static>
<access>protected</access>
<type>OQLMenuNode</type>
<code><![CDATA[ protected function LogIssue($bDebug, $sMessage, $aData = array())
{
if ($bDebug)
{
if (MetaModel::IsLogEnabledIssue() && MetaModel::IsValidClass('EventIssue'))
{
$oLog = new EventIssue();
$oLog->Set('message', $sMessage);
$oLog->Set('userinfo', '');
$oLog->Set('issue', 'LDAP Authentication');
$oLog->Set('impact', 'User login rejected');
$oLog->Set('data', $aData);
$oLog->DBInsertNoReload();
}
}
IssueLog::Error($sMessage);
}]]></code>
</method>
<method id="LogInfo">
<static>false</static>
<access>protected</access>
<type>OQLMenuNode</type>
<code><![CDATA[ protected function LogInfo($bDebug, $sMessage)
{
if ($bDebug)
{
IssueLog::Info($sMessage);
}
}]]></code>
</method>
</methods>
<presentation>
<details>
<items>
<item id="contactid">
<rank>10</rank>
</item>
<item id="org_id">
<rank>20</rank>
</item>
<item id="email">
<rank>30</rank>
</item>
<item id="login">
<rank>40</rank>
</item>
<item id="language">
<rank>50</rank>
</item>
<item id="status">
<rank>60</rank>
</item>
<item id="profile_list">
<rank>70</rank>
</item>
<item id="allowed_org_list">
<rank>80</rank>
</item>
<item id="ldap_server">
<rank>90</rank>
</item>
</items>
</details>
<search>
<items>
<item id="login">
<rank>10</rank>
</item>
<item id="contactid">
<rank>20</rank>
</item>
<item id="status">
<rank>30</rank>
</item>
<item id="org_id">
<rank>40</rank>
</item>
<item id="ldap_server">
<rank>50</rank>
</item>
</items>
</search>
<list>
<items>
<item id="first_name">
<rank>10</rank>
</item>
<item id="last_name">
<rank>20</rank>
</item>
<item id="status">
<rank>30</rank>
</item>
<item id="org_id">
<rank>40</rank>
</item>
<item id="ldap_server">
<rank>50</rank>
</item>
</items>
</list>
</presentation>
</class>
</classes>
</itop_design>

View File

@@ -1,205 +1,16 @@
<?php
// Copyright (C) 2010-2021 Combodo SARL
// PHP Data Model definition file
// WARNING - WARNING - WARNING
// DO NOT EDIT THIS FILE (unless you know what you are doing)
//
// This file is part of iTop.
// If you provide a datamodel.xxxx.xml file with your module,
// this file WILL BE overwritten by the compilation of the
// module (during the setup) if the datamodel.xxxx.xml file
// contains the definition of new classes or menus.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Authent LDAP
* User authentication Module, no password at all!
*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class UserLDAP extends UserInternal
{
public static function Init()
{
$aParams = array
(
"category" => "addon/authentication,grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "login",
"state_attcode" => "",
"reconc_keys" => array('login'),
"db_table" => "",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'language', 'status', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'status', 'org_id')); // Criteria of the std search form
}
/**
* Check the user's password against the LDAP server
* Algorithm:
* 1) Connect to the LDAP server, using a predefined account (or anonymously)
* 2) Search for the specified user, based on a specific search query/pattern
* 3) If exactly one user is found, continue, otherwise return false (wrong user or wrong query configured)
* 3) Bind again to LDAP using the DN of the found user and the password
* 4) If the bind is successful return true, otherwise return false (wrong password)
* @param string $sPassword The user's password to validate against the LDAP server
* @return boolean True if the password is Ok, false otherwise
*/
public function CheckCredentials($sPassword)
{
$sLDAPHost = MetaModel::GetModuleSetting('authent-ldap', 'host', 'localhost');
$iLDAPPort = MetaModel::GetModuleSetting('authent-ldap', 'port', 389);
$sDefaultLDAPUser = MetaModel::GetModuleSetting('authent-ldap', 'default_user', '');
$sDefaultLDAPPwd = MetaModel::GetModuleSetting('authent-ldap', 'default_pwd', '');
$bLDAPStartTLS = MetaModel::GetModuleSetting('authent-ldap', 'start_tls', false);
$aOptions = MetaModel::GetModuleSetting('authent-ldap', 'options', array());
if (array_key_exists(LDAP_OPT_DEBUG_LEVEL, $aOptions))
{
// Set debug level before trying to connect, so that debug info appear in the PHP error log if ldap_connect goes wrong
$bRet = ldap_set_option($hDS, LDAP_OPT_DEBUG_LEVEL, $aOptions[LDAP_OPT_DEBUG_LEVEL]);
$this->LogMessage("ldap_set_option('$name', '$value') returned ".($bRet ? 'true' : 'false'));
}
$hDS = @ldap_connect($sLDAPHost, $iLDAPPort);
if ($hDS === false)
{
$this->LogMessage("ldap_authentication: can not connect to the LDAP server '$sLDAPHost' (port: $iLDAPPort). Check the configuration file config-itop.php.");
return false;
}
foreach($aOptions as $name => $value)
{
$bRet = ldap_set_option($hDS, $name, $value);
$this->LogMessage("ldap_set_option('$name', '$value') returned ".($bRet ? 'true' : 'false'));
}
if ($bLDAPStartTLS)
{
$this->LogMessage("ldap_authentication: start tls required.");
$hStartTLS = ldap_start_tls($hDS);
//$this->LogMessage("ldap_authentication: hStartTLS = '$hStartTLS'");
if (!$hStartTLS)
{
$this->LogMessage("ldap_authentication: start tls failed.");
return false;
}
}
if ($bind = @ldap_bind($hDS, $sDefaultLDAPUser, $sDefaultLDAPPwd))
{
// Search for the person, using the specified query expression
$sLDAPUserQuery = MetaModel::GetModuleSetting('authent-ldap', 'user_query', '');
$sBaseDN = MetaModel::GetModuleSetting('authent-ldap', 'base_dn', '');
$sLogin = $this->Get('login');
$iContactId = $this->Get('contactid');
$sFirstName = '';
$sLastName = '';
$sEMail = '';
if ($iContactId > 0)
{
$oPerson = MetaModel::GetObject('Person', $iContactId);
if (is_object($oPerson))
{
$sFirstName = $oPerson->Get('first_name');
$sLastName = $oPerson->Get('name');
$sEMail = $oPerson->Get('email');
}
}
// %1$s => login
// %2$s => first name
// %3$s => last name
// %4$s => email
$sQuery = sprintf($sLDAPUserQuery, $sLogin, $sFirstName, $sLastName, $sEMail);
$hSearchResult = @ldap_search($hDS, $sBaseDN, $sQuery);
$iCountEntries = ($hSearchResult !== false) ? @ldap_count_entries($hDS, $hSearchResult) : 0;
switch($iCountEntries)
{
case 1:
// Exactly one entry found, let's check the password by trying to bind with this user
$aEntry = ldap_get_entries($hDS, $hSearchResult);
$sUserDN = $aEntry[0]['dn'];
$bUserBind = @ldap_bind($hDS, $sUserDN, $sPassword);
if (($bUserBind !== false) && !empty($sPassword))
{
ldap_unbind($hDS);
return true; // Password Ok
}
$this->LogMessage("ldap_authentication: wrong password for user: '$sUserDN'.");
return false; // Wrong password
break;
case 0:
// User not found...
$this->LogMessage("ldap_authentication: no entry found with the query '$sQuery', base_dn = '$sBaseDN'. User not found in LDAP.");
break;
default:
// More than one entry... maybe the query is not specific enough...
$this->LogMessage("ldap_authentication: several (".ldap_count_entries($hDS, $hSearchResult).") entries match the query '$sQuery', base_dn = '$sBaseDN', check that the query defined in config-itop.php is specific enough.");
}
return false;
}
else
{
// Trace: invalid default user for LDAP initial binding
$this->LogMessage("ldap_authentication: can not bind to the LDAP server '$sLDAPHost' (port: $iLDAPPort), user='$sDefaultLDAPUser', pwd='$sDefaultLDAPPwd'. Error: '".ldap_error($hDS)."'. Check the configuration file config-itop.php.");
return false;
}
}
public function TrustWebServerContext()
{
return false;
}
public function CanChangePassword()
{
return false;
}
public function ChangePassword($sOldPassword, $sNewPassword)
{
return false;
}
protected function LogMessage($sMessage, $aData = array())
{
if (MetaModel::GetModuleSetting('authent-ldap', 'debug', false) && MetaModel::IsLogEnabledIssue())
{
if (MetaModel::IsValidClass('EventIssue'))
{
$oLog = new EventIssue();
$oLog->Set('message', $sMessage);
$oLog->Set('userinfo', '');
$oLog->Set('issue', 'LDAP Authentication');
$oLog->Set('impact', 'User login rejected');
$oLog->Set('data', $aData);
$oLog->DBInsertNoReload();
}
IssueLog::Error($sMessage);
}
}
}
?>
// The recommended way to define new classes (for iTop 2.0 and later) is via the XML definition.
// This file remains in the module's template only for the cases where there is:
// - either no new class or menu defined in the XML file
// - or no XML file at all supplied by the module

View File

@@ -22,6 +22,7 @@ SetupWebPage::AddModule(
),
'mandatory' => false,
'visible' => true,
'installer' => 'AuthentLDAPInstaller',
// Components
//
@@ -58,8 +59,21 @@ SetupWebPage::AddModule(
),
'start_tls' => false,
'debug' => false,
'servers' => array(),
),
)
);
// Module installation handler
//
class AuthentLDAPInstaller extends ModuleInstallerAPI
{
public static function AfterDataLoad(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
// Create missing table entries
$sSQL = "insert into priv_user_ldap (id) select U.id from priv_user as U left join priv_user_ldap as L on U.id = L.id where U.finalclass='UserLDAP' and isnull(L.id);";
CMDBSource::Query($sSQL);
}
}
} // if (function_exists('ldap_connect'))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -59,7 +59,7 @@ class ObjectDetails extends Panel implements iKeyboardShortcut
protected $sObjectMode;
/** @var string */
protected $sIconUrl;
/** @var string Code of the current value of the attribute carrying the state for $sClassName */
/** @var string|null Code of the current value of the attribute carrying the state for $sClassName */
protected $sStatusCode;
/** @var string Label of the current value of the attribute carrying the state for $sClassName */
protected $sStatusLabel;

View File

@@ -19,12 +19,12 @@ namespace Combodo\iTop\Application\UI\Helper;
class UIHelper
{
/**
* @param string $sStateCode Code of the state value
* @param string|null $sStateCode Code of the state value, can be null if allowed by the attribute definition
* @param bool $bAllowFallbackIfNoMatch If set to true, a fallback semantic color code will be returned in case of no matching mappping. Otherwise it will return null to indicate there was no match.
*
* @return string|null A semantic status color name (eg. success, pending, failure, neutral, ...) depending on the value's code. Usefull to try to find a semantic match when a class has no style defined on its state attribute.
*/
public static function GetColorNameFromStatusCode(string $sStateCode, bool $bAllowFallbackIfNoMatch = true): ?string
public static function GetColorNameFromStatusCode(?string $sStateCode, bool $bAllowFallbackIfNoMatch = true): ?string
{
$sStatusColor = null;