From d1ae2e7012899170d8f7419e61ffdf30a05a14e1 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 10 May 2018 05:12:42 -0600 Subject: [PATCH] made search_dn & group_dn optional. Added group_query --- README.md | 36 ++++++++++++++++--- blueprints.yaml | 15 ++++---- login-ldap.php | 92 ++++++++++++++++++++++++++++++------------------- login-ldap.yaml | 7 ++-- 4 files changed, 100 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index c5d9112..91b357d 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,10 @@ version: 3 ssl: false start_tls: false opt_referrals: false -user_dn: uid=[username],dc=company,dc=com -search_dn: dc=company,dc=com +user_dn: 'uid=[username],dc=company,dc=com' +search_dn: +group_dn: +group_query: '(&(cn=*)(memberUid=[username]))' map_username: uid map_fullname: givenName lastName map_email: mail @@ -74,8 +76,9 @@ default_access_levels: |Key |Description | Values | |:---------------------|:---------------------------|:-------| |user_dn|DN String used to authenticate a user, where `[username]` is replaced by username value entered via login | e.g. `uid=[username],dc=company,dc=com` | -|search_dn|DN String used to retrieve user data | e.g. `ou=users,dc=company,dc=com` | -|group_dn|DN String used to retrieve user group data [OPTIONAL] | e.g. `ou=groups,dc=company,dc=com` | +|search_dn|DN String used to retrieve user data. If not provided, extra LDAP user data will not be stored in Grav user account file [OPTIONAL]| e.g. `ou=users,dc=company,dc=com` | +|group_dn|DN String used to retrieve user group data. If not provided, extra LDAP group data will not be stored in Grav user account file [OPTIONAL] | e.g. `ou=groups,dc=company,dc=com` | +|group_query|The query used to search Groups. Only change this if you know what you are doing| e.g. `(&(cn=*)(memberUid=[username]))`| |map_username|LDAP Attribute(s) that contains the user's username | [default: **uid**] | |map_fullname|LDAP Attribute(s) that contains the user's full name | [default: **givenName lastName**] | |map_email|LDAP Attribute(s) that contains the user's email address | [default: **mail**] | @@ -95,4 +98,29 @@ default_access_levels: Once properly configured, the functionality of the LDAP plugin is transparent to the user. A user will be able to login via the normal login process and have access based on their account setup. +For the most basic of authentication, only the `user_dn` is required. This uses LDAP **bind** to simply map a full user **DN** to an entry in the LDAP directory with an associated password. If no `search_dn` is provided, once authenticated, the only information available about the user is the `username` provided during login. + +#### LDAP User Data + +In order to obtain data about the user a valid `search_dn` is required. This will search the LDAP directory at the level indicated in the DN and search for a userid with the `username` provided. the `map_username` field is used in this LDAP search query, so it's important that the `map_username` field is one that properly maps the `username` provided during login to the LDAP user entry. + +#### LDAP Group Data + +To be able to know the groups a user is associated with, a valid `group_dn` and `group_query` is required. Any invalid information will throw an exception stating that the search could not complete. + +### Storing Grav User + +By default the lDAP plugin does not store any local user information. Upon successfully authenticating against the LDAP user, a user is created and is available during the session. However, upon returning, the user must re-authenticate and the LDAP data is retrieved again. + +If you want to be able to set user data (extra fields, or specific user access) for a particular user, you can enable the `save_grav_user` option, and this will create a local Grav user in the `accounts/` folder. This is a local record of the user and attributes can be set here. + +> NOTE: Any attribute stored under the `ldap:` key in the user account file will be overwritten by the plugin during the next login. This information is always in sync with latest data in the LDAP server. The same rule goes for the **mapped** fields. So updating `email` in your LDAP directory will ensure the entry in the local Grav user is updated on next login. + +### Troubleshooting + +If a user is simply unable to authenticate against the LDAP server, an entry will be logged into the Grav log (`logs/grav.log`) file with the attempted `dn`. This can be used to ensure the `user_dn` entry is correct and can be tested against any other LDAP login system. + +If either the `user_dn`, `search_dn`, `group_dn` or `group_query` are incorrect an error will be thrown during login, and a message with the error stored in the `logs/grav.log` file. + +If you expect `fullname`, or `email` to be stored in the Grav user object, but they are not appearing, it's probably a problem with your field mappings. Double check with your LDAP administrator that these are the correct mappings. diff --git a/blueprints.yaml b/blueprints.yaml index f4954fb..bb2e930 100644 --- a/blueprints.yaml +++ b/blueprints.yaml @@ -130,8 +130,6 @@ form: size: large placeholder: ou=users,dc=company,dc=com help: String used to retrieve user data. If not provided, extra LDAP user data will not be stored in Grav user account file - validate: - required: true group_dn: type: text @@ -140,14 +138,19 @@ form: placeholder: ou=groups,dc=company,dc=com help: String used to retrieve user group data. If not provided, extra LDAP group data will not be stored in Grav user account file + group_query: + type: text + label: Group Query + size: large + placeholder: '(&(cn=*)(memberUid=[username]))' + help: The query used to search Groups. Only change this if you know what you are doing + map_username: type: text label: Username Mapping size: large help: LDAP Attribute(s) that contains the user's username placeholder: uid - validate: - required: true map_fullname: type: text @@ -155,8 +158,6 @@ form: size: large help: LDAP Attribute(s) that contains the user's full name placeholder: givenName lastName - validate: - required: true map_email: type: text @@ -164,8 +165,6 @@ form: size: large help: LDAP Attribute that contains the user's email placeholder: mail - validate: - required: true advanced_section: type: section diff --git a/login-ldap.php b/login-ldap.php index 0aa27fe..50ed2b7 100644 --- a/login-ldap.php +++ b/login-ldap.php @@ -69,9 +69,11 @@ class LoginLDAPPlugin extends Plugin $credentials = $event->getCredentials(); // Get Proper username - $user_dn = $this->config->get('plugins.login-ldap.user_dn'); - $group_dn = $this->config->get('plugins.login-ldap.group_dn'); - $search_dn = $this->config->get('plugins.login-ldap.search_dn'); + $user_dn = $this->config->get('plugins.login-ldap.user_dn'); + $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'); + $username = str_replace('[username]', $credentials['username'], $user_dn); // Get Host info @@ -112,46 +114,61 @@ class LoginLDAPPlugin extends Plugin $map_fullname = $this->config->get('plugins.login-ldap.map_fullname'); $map_email = $this->config->get('plugins.login-ldap.map_email'); + // Try to login via LDAP $ldap->bind($username, $credentials['password']); - $query = $ldap->query($search_dn, $map_username .'='. $credentials['username']); - $results = $query->execute()->toArray(); - // Create Grav User $grav_user = User::load($username); - // Get LDAP Data - $ldap_data = array_shift($results)->getAttributes(); - $userdata = []; + // Set defaults with only thing we know... username provided + $grav_user['login'] = $credentials['username']; + $grav_user['fullname'] = $credentials['username']; - $userdata['login'] = $this->getLDAPMappedItem($map_username, $ldap_data); - $userdata['fullname'] = $this->getLDAPMappedItem($map_fullname, $ldap_data); - $userdata['email'] = $this->getLDAPMappedItem($map_email, $ldap_data); + // If search_dn is set we can try to get information from LDAP + if ($search_dn) { - // Get LDAP Data if required - if ($this->config->get('plugins.login-ldap.store_ldap_data', false)) { - foreach($ldap_data as $key => $data) { - $userdata['ldap'][$key] = array_shift($data); + $query = $ldap->query($search_dn, $map_username .'='. $credentials['username']); + $results = $query->execute()->toArray(); + + // Get LDAP Data + $ldap_data = array_shift($results)->getAttributes(); + $userdata = []; + + $userdata['login'] = $this->getLDAPMappedItem($map_username, $ldap_data); + $userdata['fullname'] = $this->getLDAPMappedItem($map_fullname, $ldap_data); + $userdata['email'] = $this->getLDAPMappedItem($map_email, $ldap_data); + + // Get LDAP Data if required + if ($this->config->get('plugins.login-ldap.store_ldap_data', false)) { + foreach($ldap_data as $key => $data) { + $userdata['ldap'][$key] = array_shift($data); + } + unset($userdata['ldap']['userPassword']); } - unset($userdata['ldap']['userPassword']); + + // Get Groups if group_dn if set + if ($group_dn) { + // retrieves all extra groups for user + $group_query = str_replace('[username]', $credentials['username'], $group_query); + $query = $ldap->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()); + + if ($this->config->get('plugins.login-ldap.store_ldap_data', false)) { + foreach ($groups as $group) { + $attributes = $group->getAttributes(); + $userdata['ldap']['groups'][] = array_shift($attributes['cn']); + } + } + } + + // Merge the LDAP user data with Grav user + $grav_user->merge($userdata); } - // Get Groups - // retrieves all extra groups for user - $query = $ldap->query($group_dn, "(&(cn=*)(memberUid=" . $credentials['username'] . "))"); - $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()); - - foreach($groups as $group) { - $attributes = $group->getAttributes(); - $userdata['ldap']['groups'][] = array_shift($attributes['cn']); - } - - $grav_user->merge($userdata); - // Set Groups $current_groups = $grav_user->get('groups'); if (!$current_groups) { @@ -186,9 +203,14 @@ class LoginLDAPPlugin extends Plugin return; } catch (ConnectionException $e) { - print $e->getMessage(); + $message = $e->getMessage(); - $this->grav['log']->error('plugin.login-ldap: ' . $username . ' - ' . $e->getMessage()); + $this->grav['log']->error('plugin.login-ldap: ['. $e->getCode() . '] ' . $username . ' - ' . $message); + + // Just return so other authenticators can take a shot... + if ($message = "Invalid credentials") { + return; + } $event->setStatus($event::AUTHENTICATION_FAILURE); $event->stopPropagation(); diff --git a/login-ldap.yaml b/login-ldap.yaml index e49bd37..cf720b6 100644 --- a/login-ldap.yaml +++ b/login-ldap.yaml @@ -5,9 +5,10 @@ version: 3 ssl: false start_tls: false opt_referrals: false -user_dn: uid=[username],dc=company,dc=com -search_dn: ou=users,dc=company,dc=com -group_dn: ou=groups,dc=company,dc=com +user_dn: 'uid=[username],dc=company,dc=com' +search_dn: +group_dn: +group_query: '(&(cn=*)(memberUid=[username]))' map_username: uid map_fullname: givenName lastName map_email: mail