<?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,silo</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/>
                <style>
                  <icon/>
                </style>
                <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>
