Add search bind support, fix group_identifier typo, fix empty gidNumber query

- Add search_bind_enabled/search_bind_dn/search_bind_password to allow
  using a dedicated LDAP account for user and group searches. This is
  needed when regular LDAP users lack search permissions (common with
  restrictive ACLs on OpenLDAP).
- Support both group_indentifier (original) and group_identifier config
  keys, falling back to 'cn' if neither is set.
- Skip the gidNumber-based primary group query when the attribute is
  empty, avoiding broken LDAP filters on non-posixAccount setups.
This commit is contained in:
Jannik Radix
2026-02-24 22:29:04 +01:00
parent 403350df9b
commit 6155c1fad2
4 changed files with 74 additions and 11 deletions

View File

@@ -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

View File

@@ -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)'
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'

View File

@@ -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', []);
// Dedicated search bind account (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 dedicated 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();

View File

@@ -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: