From e47863e840fa6da779eb0816c5d3ad0ccad13504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Arenaza?= Date: Sat, 6 Aug 2016 21:14:48 +0200 Subject: [PATCH] MDL-50625 auth_ldap: Better check for paged results support There is at least one LDAP server (Sun Directory Server) that doesn't support Paged Results extension, even if it supports LDAP version 3. So checking just for LDAP version is not enough. If possible, we check the supportedControl attribute of the LDAP rootDSE and see if the paged results control is available. This needs an LDAP connection, which might not be possible to establish before we configure some essential LDAP settings (server, bind user, password, etc.). Thus we try to establish the connection and check the supportedControl attribute. But if we fail, we perform only basic checks that are less accurate and err on the side of cautiousness. --- auth/cas/config.html | 26 ++++++++++++++++++++++-- auth/ldap/auth.php | 4 ++-- auth/ldap/config.html | 26 ++++++++++++++++++++++-- auth/ldap/lang/en/auth_ldap.php | 2 +- lib/ldaplib.php | 36 ++++++++++++++++++++++++++++++--- 5 files changed, 84 insertions(+), 10 deletions(-) diff --git a/auth/cas/config.html b/auth/cas/config.html index c0ea85b7148..62799508612 100644 --- a/auth/cas/config.html +++ b/auth/cas/config.html @@ -99,9 +99,31 @@ if (!isset($config->removeuser)) { $yesno = array( get_string('no'), get_string('yes') ); $disabled = ''; -if (!ldap_paged_results_supported($config->ldap_version)) { +$pagedresultssupported = false; +if ($config->host_url !== '') { + /** + * We try to connect each and every time we open the config, because we want to set the Page + * Size setting as enabled or disabled depending on the configured LDAP server supporting + * pagination or not, and to notify the user about it. If the user changed the LDAP server (or + * the LDAP protocol version) last time, it might happen that paged results are no longer + * available and we want to show that to the user the next time she goes to the settings page. + */ + try { + $ldapconn = $this->ldap_connect(); + $pagedresultssupported = ldap_paged_results_supported($config->ldap_version, $ldapconn); + } catch (Exception $e) { + // If we couldn't connect and get the supported options, we can only assume we don't support paged results. + $pagedresultssupported = false; + } +} +/* Make sure we only disable the paged result size setting and show the notification about it if + * there is a configured server that we tried to contact. Othersiwe, if someone's LDAP server does + * support paged results, they won't be able to turn it on the first time they set it up (because + * the field will be disabled). + */ +if (($config->host_url !== '') && (!$pagedresultssupported)) { $disabled = ' disabled="disabled"'; - echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap')); + echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap'), \core\output\notification::NOTIFY_INFO); } ?> diff --git a/auth/ldap/auth.php b/auth/ldap/auth.php index f80d6b3cdbb..b23c9ec99be 100644 --- a/auth/ldap/auth.php +++ b/auth/ldap/auth.php @@ -695,7 +695,7 @@ class auth_plugin_ldap extends auth_plugin_base { array_push($contexts, $this->config->create_context); } - $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version); + $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection); $ldap_cookie = ''; foreach ($contexts as $context) { $context = trim($context); @@ -1540,7 +1540,7 @@ class auth_plugin_ldap extends auth_plugin_base { } $ldap_cookie = ''; - $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version); + $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection); foreach ($contexts as $context) { $context = trim($context); if (empty($context)) { diff --git a/auth/ldap/config.html b/auth/ldap/config.html index 422f474b680..883eb9d0b52 100644 --- a/auth/ldap/config.html +++ b/auth/ldap/config.html @@ -117,9 +117,31 @@ $fastpathoptions = array(AUTH_NTLM_FASTPATH_YESFORM => get_string('auth_ntlmsso_ AUTH_NTLM_FASTPATH_ATTEMPT => get_string('auth_ntlmsso_ie_fastpath_attempt', 'auth_ldap')); $disabled = ''; -if (!ldap_paged_results_supported($config->ldap_version)) { +$pagedresultssupported = false; +if ($config->host_url !== '') { + /** + * We try to connect each and every time we open the config, because we want to set the Page + * Size setting as enabled or disabled depending on the configured LDAP server supporting + * pagination or not, and to notify the user about it. If the user changed the LDAP server (or + * the LDAP protocol version) last time, it might happen that paged results are no longer + * available and we want to show that to the user the next time she goes to the settings page. + */ + try { + $ldapconn = $this->ldap_connect(); + $pagedresultssupported = ldap_paged_results_supported($config->ldap_version, $ldapconn); + } catch (Exception $e) { + // If we couldn't connect and get the supported options, we can only assume we don't support paged results. + $pagedresultssupported = false; + } +} +/* Make sure we only disable the paged result size setting and show the notification about it if + * there is a configured server that we tried to contact. Othersiwe, if someone's LDAP server does + * support paged results, they won't be able to turn it on the first time they set it up (because + * the field will be disabled). + */ +if (($config->host_url !== '') && (!$pagedresultssupported)) { $disabled = ' disabled="disabled"'; - echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap')); + echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap'), \core\output\notification::NOTIFY_INFO); } ?> diff --git a/auth/ldap/lang/en/auth_ldap.php b/auth/ldap/lang/en/auth_ldap.php index 6f235233b31..a5285dda7f9 100644 --- a/auth/ldap/lang/en/auth_ldap.php +++ b/auth/ldap/lang/en/auth_ldap.php @@ -132,7 +132,7 @@ $string['ntlmsso_attempting'] = 'Attempting Single Sign On via NTLM...'; $string['ntlmsso_failed'] = 'Auto-login failed, try the normal login page...'; $string['ntlmsso_isdisabled'] = 'NTLM SSO is disabled.'; $string['ntlmsso_unknowntype'] = 'Unknown ntlmsso type!'; -$string['pagedresultsnotsupp'] = 'LDAP paged results not supported (either your PHP version lacks support or you have configured Moodle to use LDAP protocol version 2)'; +$string['pagedresultsnotsupp'] = 'LDAP paged results not supported (either your PHP version lacks support, you have configured Moodle to use LDAP protocol version 2 or Moodle cannot contact your LDAP server to see if paged support is available.)'; $string['pagesize'] = 'Make sure this value is smaller than your LDAP server result set size limit (the maximum number of entries that can be returned in a single query)'; $string['pagesize_key'] = 'Page size'; $string['pluginname'] = 'LDAP server'; diff --git a/lib/ldaplib.php b/lib/ldaplib.php index ec18f6f23a7..8a5c9652b9b 100644 --- a/lib/ldaplib.php +++ b/lib/ldaplib.php @@ -22,6 +22,11 @@ if (!defined('ROOTDSE')) { define ('ROOTDSE', ''); } +// Paged results control OID value. +if (!defined('LDAP_PAGED_RESULTS_CONTROL')) { + define ('LDAP_PAGED_RESULTS_CONTROL', '1.2.840.113556.1.4.319'); +} + // Default page size when using LDAP paged results if (!defined('LDAP_DEFAULT_PAGESIZE')) { define('LDAP_DEFAULT_PAGESIZE', 250); @@ -452,14 +457,39 @@ function ldap_stripslashes($text) { /** - * Check if we use LDAP version 3, otherwise the server cannot use them. + * Check if we can use paged results (see RFC 2696). We need to use + * LDAP version 3 (or later), otherwise the server cannot use them. If + * we also pass in a valid LDAP connection handle, we also check + * whether the server actually supports them. * * @param ldapversion integer The LDAP protocol version we use. + * @param ldapconnection resource An existing LDAP connection (optional). * * @return boolean true is paged results can be used, false otherwise. */ -function ldap_paged_results_supported($ldapversion) { - if ((int)$ldapversion === 3) { +function ldap_paged_results_supported($ldapversion, $ldapconnection = null) { + if ((int)$ldapversion < 3) { + // Minimun required version: LDAP v3. + return false; + } + + if ($ldapconnection === null) { + // Can't verify it, so assume it isn't supported. + return false; + } + + // Connect to the rootDSE and get the supported controls. + $sr = ldap_read($ldapconnection, ROOTDSE, '(objectClass=*)', array('supportedControl')); + if (!$sr) { + return false; + } + + $entries = ldap_get_entries_moodle($ldapconnection, $sr); + if (empty($entries)) { + return false; + } + $info = array_change_key_case($entries[0], CASE_LOWER); + if (isset($info['supportedcontrol']) && in_array(LDAP_PAGED_RESULTS_CONTROL, $info['supportedcontrol'])) { return true; }