From f0bfa29133ca6b21d385f15220aac48ccd2acd64 Mon Sep 17 00:00:00 2001 From: Fathi BEN NASR Date: Wed, 19 Aug 2026 17:20:21 +0000 Subject: [PATCH] Search contact suggestions across several LDAP branches A directory rarely keeps everything worth suggesting in one branch: people sit under ou=People, while meeting rooms and other bookable resources live elsewhere - ou=Resources on our installations. Because the plugin queries a single base DN, typing part of a room's name while composing an invitation returns nothing, even though the entry exists. Widening the base to the domain root is not a fix: it pulls every service account into the suggestion list. base_dn now accepts several branches separated by '|', each queried with the same filter, results concatenated. That separator cannot appear unescaped in a DN, so existing single-branch configurations keep working untouched. An unreachable branch is logged with its own DN and no longer silences the remaining ones - previously a single failed search discarded the whole lookup. Plugin version bumped to 2.15. --- .../LdapContactsSuggestions.php | 46 ++++++++++++------- plugins/ldap-contacts-suggestions/index.php | 6 +-- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php index d7eb3eb9a..aa5907753 100644 --- a/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php +++ b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php @@ -139,29 +139,41 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges $sFilter .= (1 < count($aItems) ? '(|' : '').$sSubFilter.(1 < count($aItems) ? ')' : ''); $sFilter .= ')'; - $this->logWrite('ldap_search: start: '.$sBaseDn.' / '.$sFilter, \LOG_INFO, 'LDAP'); - $oS = @\ldap_search($oCon, $sBaseDn, $sFilter, $aItems, 0, 30, 30); - if ($oS) { - $aEntries = @\ldap_get_entries($oCon, $oS); - if (is_array($aEntries)) { - if (isset($aEntries['count'])) { - unset($aEntries['count']); - } + // A directory rarely keeps everything worth suggesting in one branch: + // meeting rooms and other bookable resources commonly live outside + // the people branch. Searching a single subtree either misses them, + // or - if the base is widened to the domain root - drags every + // service account into the suggestion list. Base DNs are therefore + // separated by '|', which cannot appear unescaped in a DN, so an + // existing single-branch configuration keeps working unchanged. + $aBaseDns = \array_filter(\array_map('trim', \explode('|', $sBaseDn)), 'strlen'); - foreach ($aEntries as $aItem) { - if ($aItem) { - $sName = $sEmail = ''; - list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames, $aUIDs); - if (!empty($sEmail)) { - $aResult[] = array($sEmail, $sName); + foreach ($aBaseDns as $sOneBaseDn) { + $this->logWrite('ldap_search: start: '.$sOneBaseDn.' / '.$sFilter, \LOG_INFO, 'LDAP'); + $oS = @\ldap_search($oCon, $sOneBaseDn, $sFilter, $aItems, 0, 30, 30); + if ($oS) { + $aEntries = @\ldap_get_entries($oCon, $oS); + if (is_array($aEntries)) { + if (isset($aEntries['count'])) { + unset($aEntries['count']); + } + + foreach ($aEntries as $aItem) { + if ($aItem) { + $sName = $sEmail = ''; + list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames, $aUIDs); + if (!empty($sEmail)) { + $aResult[] = array($sEmail, $sName); + } } } + } else { + $this->logLdapError($oCon, 'ldap_get_entries'); } } else { - $this->logLdapError($oCon, 'ldap_get_entries'); + // One unreachable branch must not silence the others. + $this->logLdapError($oCon, 'ldap_search ('.$sOneBaseDn.')'); } - } else { - $this->logLdapError($oCon, 'ldap_search'); } } diff --git a/plugins/ldap-contacts-suggestions/index.php b/plugins/ldap-contacts-suggestions/index.php index dbbf2d515..5ef78593c 100644 --- a/plugins/ldap-contacts-suggestions/index.php +++ b/plugins/ldap-contacts-suggestions/index.php @@ -4,8 +4,8 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin { const NAME = 'Contacts suggestions (LDAP)', - VERSION = '2.14', - RELEASE = '2024-03-12', + VERSION = '2.15', + RELEASE = '2026-08-19', REQUIRED = '2.35.3', CATEGORY = 'Contacts', DESCRIPTION = 'Get contacts suggestions from LDAP.'; @@ -79,7 +79,7 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) ->SetDefaultValue(''), \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}') + ->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} Several branches may be given, separated by | - useful when meeting rooms or other resources live outside the people branch.') ->SetDefaultValue('ou=People,dc=example,dc=com'), \RainLoop\Plugins\Property::NewInstance('object_classes')->SetLabel('objectClasses') ->SetDescription('LDAP objectClasses to search for, comma separated list')