From 55a1efcb4ef15472e3275ae3ba5241e21bba406f Mon Sep 17 00:00:00 2001 From: Jannik Radix Date: Tue, 24 Feb 2026 22:03:14 +0100 Subject: [PATCH] Add search bind support, fix group_identifier typo, fix gidNumber query - Add search_bind_enabled/search_bind_dn/search_bind_password config - Support both group_indentifier (original typo) and group_identifier - Skip gidNumber query when attribute is empty - Use searchLdap for all search/group queries --- blueprints.yaml | 34 +++++++++++++++++++++++++++++++++- languages/en.yaml | 9 ++++++++- login-ldap.php | 41 +++++++++++++++++++++++++++++++---------- login-ldap.yaml | 3 +++ 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/blueprints.yaml b/blueprints.yaml index 66b63e8..6a57f78 100644 --- a/blueprints.yaml +++ b/blueprints.yaml @@ -1,5 +1,5 @@ name: Login LDAP -version: 1.1.0 +version: 1.2.0 description: Allows for Grav user authentication against an LDAP Server such as OpenLDAP or ActiveDirectory icon: user-circle-o author: @@ -108,6 +108,38 @@ form: validate: type: bool + search_bind_section: + type: section + title: PLUGIN_LOGIN_LDAP.SEARCH_BIND_CONFIGURATION + underline: true + + fields: + + search_bind_enabled: + type: toggle + label: PLUGIN_LOGIN_LDAP.SEARCH_BIND_ENABLE + help: PLUGIN_LOGIN_LDAP.SEARCH_BIND_ENABLE_DESC + default: 0 + highlight: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + search_bind_dn: + type: text + label: PLUGIN_LOGIN_LDAP.SEARCH_BIND_DN + size: large + placeholder: cn=readonly,dc=company,dc=com + help: PLUGIN_LOGIN_LDAP.SEARCH_BIND_DN_DESC + + search_bind_password: + type: password + label: PLUGIN_LOGIN_LDAP.SEARCH_BIND_PASSWORD + size: large + help: PLUGIN_LOGIN_LDAP.SEARCH_BIND_PASSWORD_DESC + config_section: type: section title: LDAP Configuration diff --git a/languages/en.yaml b/languages/en.yaml index eb846a5..772a3c6 100644 --- a/languages/en.yaml +++ b/languages/en.yaml @@ -42,4 +42,11 @@ PLUGIN_LOGIN_LDAP: VERSION_DESC: 'LDAP Version 3 is most popular, only change this if you know what you are doing' BLACKLIST_FIELDS: 'Blacklist Fields' BLACKLIST_FIELDS_HELP: 'A list of LDAP fields to be skipped and ignored' - BLACKLIST_FIELDS_PLACEHOLDER: 'Field (ie, jpegPhoto, homePostalAddress)' \ No newline at end of file + BLACKLIST_FIELDS_PLACEHOLDER: 'Field (ie, jpegPhoto, homePostalAddress)' + SEARCH_BIND_CONFIGURATION: 'Search Bind Configuration' + SEARCH_BIND_ENABLE: 'Use Search Bind' + SEARCH_BIND_ENABLE_DESC: 'Use a dedicated LDAP account for user/group searches (useful when regular users lack search permissions)' + SEARCH_BIND_DN: 'Search Bind DN' + SEARCH_BIND_DN_DESC: 'Full DN of the LDAP account used for searches (e.g. cn=readonly,dc=company,dc=com)' + SEARCH_BIND_PASSWORD: 'Search Bind Password' + SEARCH_BIND_PASSWORD_DESC: 'Password for the search bind account' \ No newline at end of file diff --git a/login-ldap.php b/login-ldap.php index 0819f50..7a58853 100644 --- a/login-ldap.php +++ b/login-ldap.php @@ -83,7 +83,9 @@ class LoginLDAPPlugin extends Plugin $search_dn = $this->config->get('plugins.login-ldap.search_dn'); $group_dn = $this->config->get('plugins.login-ldap.group_dn'); $group_query = $this->config->get('plugins.login-ldap.group_query'); - $group_indentifier = $this->config->get('plugins.login-ldap.group_indentifier'); + // Support both the original typo (group_indentifier) and the correct spelling (group_identifier) + $group_indentifier = $this->config->get('plugins.login-ldap.group_indentifier') + ?? $this->config->get('plugins.login-ldap.group_identifier', 'cn'); $username = str_replace('[username]', $credentials['username'], $user_dn); @@ -96,6 +98,11 @@ class LoginLDAPPlugin extends Plugin $opt_referrals = $this->config->get('plugins.login-ldap.opt_referrals'); $blacklist = $this->config->get('plugins.login-ldap.blacklist_ldap_fields', []); + // Alternate search user (for when regular users lack search permissions) + $search_bind_enabled = $this->config->get('plugins.login-ldap.search_bind_enabled', false); + $search_bind_dn = $this->config->get('plugins.login-ldap.search_bind_dn'); + $search_bind_password = $this->config->get('plugins.login-ldap.search_bind_password'); + if (is_null($host)) { throw new ConnectionException('FATAL: LDAP host entry missing in plugin configuration...'); } @@ -110,8 +117,7 @@ class LoginLDAPPlugin extends Plugin } try { - /** @var Ldap $ldap */ - $ldap = Ldap::create('ext_ldap', array( + $ldap_config = array( 'host' => $host, 'port' => $port, 'encryption' => $encryption, @@ -119,7 +125,10 @@ class LoginLDAPPlugin extends Plugin 'protocol_version' => $version, 'referrals' => (bool) $opt_referrals, ), - )); + ); + + /** @var Ldap $ldap */ + $ldap = Ldap::create('ext_ldap', $ldap_config); // Map Info $map_username = $this->config->get('plugins.login-ldap.map_username'); @@ -130,6 +139,15 @@ class LoginLDAPPlugin extends Plugin // Try to login via LDAP $ldap->bind($username, $credentials['password']); + // Set up search LDAP connection (use separate bind if configured) + if ($search_bind_enabled && $search_bind_dn) { + /** @var Ldap $searchLdap */ + $searchLdap = Ldap::create('ext_ldap', $ldap_config); + $searchLdap->bind($search_bind_dn, $search_bind_password); + } else { + $searchLdap = $ldap; + } + // Create Grav User $grav_user = User::load(strtolower($credentials['username'])); @@ -141,7 +159,7 @@ class LoginLDAPPlugin extends Plugin // If search_dn is set we can try to get information from LDAP if ($search_dn) { $query_string = $map_username .'='. $credentials['username']; - $query = $ldap->query($search_dn, $query_string); + $query = $searchLdap->query($search_dn, $query_string); $results = $query->execute()->toArray(); // Get LDAP Data @@ -179,13 +197,16 @@ class LoginLDAPPlugin extends Plugin if ($group_dn) { // retrieves all extra groups for user $group_query = str_replace('[username]', $credentials['username'], $group_query); - $group_query = str_replace('[dn]', $ldap->escape($userdata['dn'], '', LdapInterface::ESCAPE_FILTER), $group_query); - $query = $ldap->query($group_dn, $group_query); + $group_query = str_replace('[dn]', $searchLdap->escape($userdata['dn'], '', LdapInterface::ESCAPE_FILTER), $group_query); + $query = $searchLdap->query($group_dn, $group_query); $groups = $query->execute()->toArray(); - // retrieve current primary group for user - $query = $ldap->query($group_dn, 'gidnumber=' . $this->getLDAPMappedItem('gidNumber', $ldap_data)); - $groups = array_merge($groups, $query->execute()->toArray()); + // retrieve current primary group for user (posixGroup support) + $gidNumber = $this->getLDAPMappedItem('gidNumber', $ldap_data); + if (!empty($gidNumber)) { + $query = $searchLdap->query($group_dn, 'gidnumber=' . $gidNumber); + $groups = array_merge($groups, $query->execute()->toArray()); + } foreach ($groups as $group) { $attributes = $group->getAttributes(); diff --git a/login-ldap.yaml b/login-ldap.yaml index 60f4f14..82ea29a 100644 --- a/login-ldap.yaml +++ b/login-ldap.yaml @@ -5,6 +5,9 @@ version: 3 ssl: false start_tls: false opt_referrals: false +search_bind_enabled: false +search_bind_dn: +search_bind_password: user_dn: 'uid=[username],dc=company,dc=com' search_dn: group_dn: