diff --git a/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php index 9e8a45982..8a10443b2 100644 --- a/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php +++ b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php @@ -5,47 +5,47 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges /** * @var string */ - private $sHostName = '127.0.0.1'; + private $sLdapUri = 'ldap://localhost:389'; /** - * @var int + * @var bool */ - private $iHostPort = 389; + private $bUseStartTLS = True; /** * @var string */ - private $sAccessDn = null; + private $sBindDn = null; /** * @var string */ - private $sAccessPassword = null; + private $sBindPassword = null; /** * @var string */ - private $sUsersDn = ''; + private $sBaseDn = 'ou=People,dc=example,dc=com'; /** * @var string */ - private $sObjectClass = 'inetOrgPerson'; + private $sObjectClasses = 'inetOrgPerson'; /** * @var string */ - private $sUidField = 'uid'; + private $sUidAttributes = 'uid'; /** * @var string */ - private $sNameField = 'givenname'; + private $sNameAttributes = 'displayName,cn,givenName,sn'; /** * @var string */ - private $sEmailField = 'mail'; + private $sEmailAttributes = 'mailAddress,mail,mailAlternateAddress,mailAlias'; /** * @var \MailSo\Log\Logger @@ -55,45 +55,36 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges /** * @var string */ - private $sAllowedEmails = ''; - - /** - * @param string $sHostName - * @param int $iHostPort - * @param string $sAccessDn - * @param string $sAccessPassword - * @param string $sUsersDn - * @param string $sObjectClass - * @param string $sNameField - * @param string $sEmailField - * - * @return \LdapContactsSuggestions - */ - public function SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sUidField, $sNameField, $sEmailField) - { - $this->sHostName = $sHostName; - $this->iHostPort = $iHostPort; - if (0 < \strlen($sAccessDn)) - { - $this->sAccessDn = $sAccessDn; - $this->sAccessPassword = $sAccessPassword; - } - $this->sUsersDn = $sUsersDn; - $this->sObjectClass = $sObjectClass; - $this->sUidField = $sUidField; - $this->sNameField = $sNameField; - $this->sEmailField = $sEmailField; - - return $this; - } + private $sAllowedEmails = '*'; /** + * @param string $sLdapUri + * @param bool $bUseStartTLS + * @param string $sBindDn + * @param string $sBindPassword + * @param string $sBaseDn + * @param string $sObjectClasses + * @param string $sNameAttributes + * @param string $sEmailAttributes + * @param string $sUidAttributes * @param string $sAllowedEmails * * @return \LdapContactsSuggestions */ - public function SetAllowedEmails($sAllowedEmails) + public function SetConfig($sLdapUri, $bUseStartTLS, $sBindDn, $sBindPassword, $sBaseDn, $sObjectClasses, $sUidAttributes, $sNameAttributes, $sEmailAttributes, $sAllowedEmails) { + $this->sLdapUri = $sLdapUri; + $this->bUseStartTLS = $bUseStartTLS; + if (0 < \strlen($sBindDn)) + { + $this->sBindDn = $sBindDn; + $this->sBindPassword = $sBindPassword; + } + $this->sBaseDn = $sBaseDn; + $this->sObjectClasses = $sObjectClasses; + $this->sUidAttributes = $sUidAttributes; + $this->sNameAttributes = $sNameAttributes; + $this->sEmailAttributes = $sEmailAttributes; $this->sAllowedEmails = $sAllowedEmails; return $this; @@ -132,18 +123,20 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges /** * @param array $aLdapItem - * @param array $aEmailFields - * @param array $aNameFields + * @param array $aEmailAttributes + * @param array $aNameAttributes + * @param array $aUidAttributes * * @return array */ - private function findNameAndEmail($aLdapItem, $aEmailFields, $aNameFields, $aUidFields) + private function findNameAndEmail($aLdapItem, $aEmailAttributes, $aNameAttributes, $aUidAttributes) { $sEmail = $sName = $sUid = ''; if ($aLdapItem) { - foreach ($aEmailFields as $sField) + foreach ($aEmailAttributes as $sField) { + $sField = \strtolower($sField); if (!empty($aLdapItem[$sField][0])) { $sEmail = \trim($aLdapItem[$sField][0]); @@ -154,8 +147,9 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges } } - foreach ($aNameFields as $sField) + foreach ($aNameAttributes as $sField) { + $sField = \strtolower($sField); if (!empty($aLdapItem[$sField][0])) { $sName = \trim($aLdapItem[$sField][0]); @@ -166,8 +160,9 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges } } - foreach ($aUidFields as $sField) + foreach ($aUidAttributes as $sField) { + $sField = \strtolower($sField); if (!empty($aLdapItem[$sField][0])) { $sUid = \trim($aLdapItem[$sField][0]); @@ -193,16 +188,22 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges $sSearchEscaped = $this->escape($sQuery); $aResult = array(); - $oCon = @\ldap_connect($this->sHostName, $this->iHostPort); + $oCon = @\ldap_connect($this->sLdapUri); if ($oCon) { $this->oLogger->Write('ldap_connect: connected', \MailSo\Log\Enumerations\Type::INFO, 'LDAP'); @\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3); - if (!@\ldap_bind($oCon, $this->sAccessDn, $this->sAccessPassword)) + if ($this->bUseStartTLS && !@\ldap_start_tls($oCon)) { - if (is_null($this->sAccessDn)) + $this->logLdapError($oCon, 'ldap_start_tls'); + return $aResult; + } + + if (!@\ldap_bind($oCon, $this->sBindDn, $this->sBindPassword)) + { + if (is_null($this->sBindDn)) { $this->logLdapError($oCon, 'ldap_bind (anonymous)'); } @@ -215,7 +216,7 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges } $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email()); - $sSearchDn = \strtr($this->sUsersDn, array( + $sBaseDn = \strtr($this->sBaseDn, array( '{domain}' => $sDomain, '{domain:dc}' => 'dc='.\strtr($sDomain, array('.' => ',dc=')), '{email}' => $oAccount->Email(), @@ -227,16 +228,30 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges '{imap:port}' => $oAccount->DomainIncPort() )); - $aEmails = empty($this->sEmailField) ? array() : \explode(',', $this->sEmailField); - $aNames = empty($this->sNameField) ? array() : \explode(',', $this->sNameField); - $aUIDs = empty($this->sUidField) ? array() : \explode(',', $this->sUidField); + $aObjectClasses = empty($this->sObjectClasses) ? array() : \explode(',', $this->sObjectClasses); + $aEmails = empty($this->sEmailAttributes) ? array() : \explode(',', $this->sEmailAttributes); + $aNames = empty($this->sNameAttributes) ? array() : \explode(',', $this->sNameAttributes); + $aUIDs = empty($this->sUidAttributes) ? array() : \explode(',', $this->sUidAttributes); + $aObjectClasses = \array_map('trim', $aObjectClasses); $aEmails = \array_map('trim', $aEmails); $aNames = \array_map('trim', $aNames); $aUIDs = \array_map('trim', $aUIDs); $aFields = \array_merge($aEmails, $aNames, $aUIDs); + $iObjCount = 0; + $sObjFilter = ''; + foreach ($aObjectClasses as $sItem) + { + if (!empty($sItem)) + { + $iObjCount++; + $sObjFilter .= '(objectClass='.$sItem.')'; + } + } + + $aItems = array(); $sSubFilter = ''; foreach ($aFields as $sItem) @@ -248,12 +263,13 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges } } - $sFilter = '(&(objectclass='.$this->sObjectClass.')'; + $sFilter = '(&'; + $sFilter .= (1 < $iObjCount ? '(|' : '').$sObjFilter.(1 < $iObjCount ? ')' : ''); $sFilter .= (1 < count($aItems) ? '(|' : '').$sSubFilter.(1 < count($aItems) ? ')' : ''); $sFilter .= ')'; - $this->oLogger->Write('ldap_search: start: '.$sSearchDn.' / '.$sFilter, \MailSo\Log\Enumerations\Type::INFO, 'LDAP'); - $oS = @\ldap_search($oCon, $sSearchDn, $sFilter, $aItems, 0, 30, 30); + $this->oLogger->Write('ldap_search: start: '.$sBaseDn.' / '.$sFilter, \MailSo\Log\Enumerations\Type::INFO, 'LDAP'); + $oS = @\ldap_search($oCon, $sBaseDn, $sFilter, $aItems, 0, 30, 30); if ($oS) { $aEntries = @\ldap_get_entries($oCon, $oS); diff --git a/plugins/ldap-contacts-suggestions/index.php b/plugins/ldap-contacts-suggestions/index.php index 97aa4177b..67ad954de 100644 --- a/plugins/ldap-contacts-suggestions/index.php +++ b/plugins/ldap-contacts-suggestions/index.php @@ -4,9 +4,9 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin { const NAME = 'Contacts suggestions (LDAP)', - VERSION = '2.0', + VERSION = '2.1', CATEGORY = 'Security', - DESCRIPTION = 'Plugin that adds functionality to get contacts from LDAP on compose page.'; + DESCRIPTION = 'Plugin to get contacts suggestions from LDAP.'; public function Init() : void { @@ -41,22 +41,23 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin $mResult = array(); } - $sHostName = \trim($this->Config()->Get('plugin', 'hostname', '')); - $iHostPort = (int) $this->Config()->Get('plugin', 'port', 389); - $sAccessDn = \trim($this->Config()->Get('plugin', 'access_dn', '')); - $sAccessPassword = \trim($this->Config()->Get('plugin', 'access_password', '')); - $sUsersDn = \trim($this->Config()->Get('plugin', 'users_dn_format', '')); - $sObjectClass = \trim($this->Config()->Get('plugin', 'object_class', '')); - $sSearchField = \trim($this->Config()->Get('plugin', 'search_field', '')); - $sNameField = \trim($this->Config()->Get('plugin', 'name_field', '')); - $sEmailField = \trim($this->Config()->Get('plugin', 'mail_field', '')); + $sLdapUri = \trim($this->Config()->Get('plugin', 'ldap_uri', '')); + $bUseStartTLS = (bool) $this->Config()->Get('plugin', 'use_start_tls', True); + $sBindDn = \trim($this->Config()->Get('plugin', 'bind_dn', '')); + $sBindPassword = \trim($this->Config()->Get('plugin', 'bind_password', '')); + $sBaseDn = \trim($this->Config()->Get('plugin', 'base_dn', '')); + $sObjectClasses = \trim($this->Config()->Get('plugin', 'object_classes', '')); + $sUidAttributes = \trim($this->Config()->Get('plugin', 'uid_attributes', '')); + $sNameAttributes = \trim($this->Config()->Get('plugin', 'name_attributes', '')); + $sEmailAttributes = \trim($this->Config()->Get('plugin', 'mail_attributes', '')); + $sAllowedEmails = \trim($this->Config()->Get('plugin', 'allowed_emails', '')); - if (0 < \strlen($sUsersDn) && 0 < \strlen($sObjectClass) && 0 < \strlen($sEmailField)) + if (0 < \strlen($sLdapUri) && 0 < \strlen($sBaseDn) && 0 < \strlen($sObjectClasses) && 0 < \strlen($sEmailAttributes)) { include_once __DIR__.'/LdapContactsSuggestions.php'; $oProvider = new LdapContactsSuggestions(); - $oProvider->SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sSearchField, $sNameField, $sEmailField); + $oProvider->SetConfig($sLdapUri, $bUseStartTLS, $sBindDn, $sBindPassword, $sBaseDn, $sObjectClasses, $sUidAttributes, $sNameAttributes, $sEmailAttributes, $sAllowedEmails); $mResult[] = $oProvider; } @@ -71,30 +72,35 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin protected function configMapping() : array { return array( - \RainLoop\Plugins\Property::NewInstance('hostname')->SetLabel('LDAP hostname') - ->SetDefaultValue('127.0.0.1'), - \RainLoop\Plugins\Property::NewInstance('port')->SetLabel('LDAP port') - ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) - ->SetDefaultValue(389), - \RainLoop\Plugins\Property::NewInstance('access_dn')->SetLabel('Access dn (login)') - ->SetDescription('LDAP bind DN to authentifcate with. If left blank, anonymous bind will be tried and Access password will be ignored') + \RainLoop\Plugins\Property::NewInstance('ldap_uri')->SetLabel('LDAP URI') + ->SetDescription('LDAP server URI(s), space separated') + ->SetDefaultValue('ldap://localhost:389'), + \RainLoop\Plugins\Property::NewInstance('use_start_tls')->SetLabel('Use StartTLS') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ->SetDefaultValue(True), + \RainLoop\Plugins\Property::NewInstance('bind_dn')->SetLabel('Bind DN') + ->SetDescription('DN to bind (login) with. If left blank, anonymous bind will be tried and the password will be ignored') ->SetDefaultValue(''), - \RainLoop\Plugins\Property::NewInstance('access_password')->SetLabel('Access password') + \RainLoop\Plugins\Property::NewInstance('bind_password')->SetLabel('Bind password') ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) ->SetDefaultValue(''), - \RainLoop\Plugins\Property::NewInstance('users_dn_format')->SetLabel('Users DN format') - ->SetDescription('LDAP users dn format. Supported tokens: {email}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}') - ->SetDefaultValue('ou=People,dc=domain,dc=com'), - \RainLoop\Plugins\Property::NewInstance('object_class')->SetLabel('objectClass value') + \RainLoop\Plugins\Property::NewInstance('base_dn')->SetLabel('Search base DN') + ->SetDescription('DN to use as the search base. Supported tokens: {domain}, {domain:dc}, {email}, {email:user}, {email:domain}, {login}, {imap:login}, {imap:host}, {imap:port}') + ->SetDefaultValue('ou=People,dc=example,dc=com'), + \RainLoop\Plugins\Property::NewInstance('object_classes')->SetLabel('objectClasses to use') + ->SetDescription('LDAP objectClasses to search for, comma separated list') ->SetDefaultValue('inetOrgPerson'), - \RainLoop\Plugins\Property::NewInstance('search_field')->SetLabel('Search field') + \RainLoop\Plugins\Property::NewInstance('uid_attributes')->SetLabel('uid attributes') + ->SetDescription('LDAP attributes for userids, comma separated list in order of preference') ->SetDefaultValue('uid'), - \RainLoop\Plugins\Property::NewInstance('name_field')->SetLabel('Name field') - ->SetDefaultValue('givenname'), - \RainLoop\Plugins\Property::NewInstance('mail_field')->SetLabel('Mail field') - ->SetDefaultValue('mail'), + \RainLoop\Plugins\Property::NewInstance('name_attributes')->SetLabel('Name attributes') + ->SetDescription('LDAP attributes for user names, comma separated list in order of preference') + ->SetDefaultValue('displayName,cn,givenName,sn'), + \RainLoop\Plugins\Property::NewInstance('mail_attributes')->SetLabel('Mail attributes') + ->SetDescription('LDAP attributes for user email addresses, comma separated list in order of preference') + ->SetDefaultValue('mailAddress,mail,mailAlternateAddress,mailAlias'), \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') - ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') + ->SetDescription('Email addresses of users which should be allowed to do LDAP lookups, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') ->SetDefaultValue('*') ); }