Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.3] Fix LDAP "Bind Directly as User" #2684

Closed
jgerman-bot opened this issue Dec 2, 2022 · 0 comments · Fixed by #2685
Closed

[4.3] Fix LDAP "Bind Directly as User" #2684

jgerman-bot opened this issue Dec 2, 2022 · 0 comments · Fixed by #2685

Comments

@jgerman-bot
Copy link

New language relevant PR in upstream repo: joomla/joomla-cms#37959 Here are the upstream changes:

Click to expand the diff!
diff --git a/administrator/language/en-GB/plg_authentication_ldap.ini b/administrator/language/en-GB/plg_authentication_ldap.ini
index 6ed7d8d41f7b..98cdb50dd9cb 100644
--- a/administrator/language/en-GB/plg_authentication_ldap.ini
+++ b/administrator/language/en-GB/plg_authentication_ldap.ini
@@ -14,18 +14,18 @@ PLG_LDAP_FIELD_HOST_LABEL="Host"
 PLG_LDAP_FIELD_LDAPDEBUG_DESC="Enables debug hardcoded to level 7"
 PLG_LDAP_FIELD_LDAPDEBUG_LABEL="Debug"
 PLG_LDAP_FIELD_NEGOCIATE_LABEL="Negotiate TLS"
-PLG_LDAP_FIELD_PASSWORD_DESC="The Connect Password is the password of an administrative account. This is used in Authenticate then Bind and Authenticated Compare authorisation methods."
+PLG_LDAP_FIELD_PASSWORD_DESC="The Connect Password is the password of an administrative account."
 PLG_LDAP_FIELD_PASSWORD_LABEL="Connect Password"
 PLG_LDAP_FIELD_PORT_LABEL="Port"
 PLG_LDAP_FIELD_REFERRALS_DESC="This option sets the value of the LDAP_OPT_REFERRALS flag. You will need to set it to No for Windows 2003 servers."
 PLG_LDAP_FIELD_REFERRALS_LABEL="Follow Referrals"
-PLG_LDAP_FIELD_SEARCHSTRING_DESC="A query string used to search for a given User. The [search] keyword is dynamically replaced by the User-provided login. An example string is: uid=[search]. Several strings can be used separated by semicolons. Only used when searching."
+PLG_LDAP_FIELD_SEARCHSTRING_DESC="A query string used to search for a given User. The [search] keyword is dynamically replaced by the User-provided login. An example string is: uid=[search]. Several strings can be used separated by semicolons. Used after initial bind for all methods."
 PLG_LDAP_FIELD_SEARCHSTRING_LABEL="Search String"
 PLG_LDAP_FIELD_UID_DESC="LDAP Attribute which has the User's Login ID. For Active Directory this is sAMAccountName."
 PLG_LDAP_FIELD_UID_LABEL="Map: User ID"
-PLG_LDAP_FIELD_USERNAME_DESC="The Connect Username and Connect Password define connection parameters for the DN lookup phase. Two options are available:- Anonymous DN lookup (leave both fields blank); Administrative connection: Connect Username is the username of an administrative account, for example Administrator. Connect password is the actual password of your administrative account."
+PLG_LDAP_FIELD_USERNAME_DESC="The Connect Username and Connect Password define connection parameters for the DN lookup phase. Two options are available: anonymous DN lookup (Leave both fields blank) and administrative connection (Connect Username is the username of an administrative account, for example Administrator). Only used in Bind and Search method."
 PLG_LDAP_FIELD_USERNAME_LABEL="Connect Username"
-PLG_LDAP_FIELD_USERSDN_DESC="The [username] keyword is dynamically replaced by the User-provided login. An example string is: uid=[username], dc=my-domain, dc=com. Several strings can be used, separated by semicolons. Only used for direct binds."
+PLG_LDAP_FIELD_USERSDN_DESC="The [username] keyword is dynamically replaced by the User-provided login. An example string is: uid=[username], dc=my-domain, dc=com. Several strings can be used, separated by semicolons. Only used for Bind Directly as User method."
 PLG_LDAP_FIELD_USERSDN_LABEL="User's DN"
 PLG_LDAP_FIELD_V3_DESC="Default is LDAP2, but the latest versions of OpenLdap require clients to use LDAPV3."
 PLG_LDAP_FIELD_V3_LABEL="LDAP V3"
diff --git a/plugins/authentication/ldap/ldap.php b/plugins/authentication/ldap/ldap.php
index ceaba31d71df..0f2679635beb 100644
--- a/plugins/authentication/ldap/ldap.php
+++ b/plugins/authentication/ldap/ldap.php
@@ -12,6 +12,7 @@
 
 use Joomla\CMS\Authentication\Authentication;
 use Joomla\CMS\Language\Text;
+use Joomla\CMS\Log\Log;
 use Joomla\CMS\Plugin\CMSPlugin;
 use Symfony\Component\Ldap\Entry;
 use Symfony\Component\Ldap\Exception\ConnectionException;
@@ -48,7 +49,8 @@ public function onUserAuthenticate($credentials, $options, &$response)
         }
 
         // For JLog
-        $response->type = 'LDAP';
+        $logcategory = "ldap";
+        $response->type = $logcategory;
 
         // Strip null bytes from the password
         $credentials['password'] = str_replace(chr(0), '', $credentials['password']);
@@ -67,60 +69,74 @@ public function onUserAuthenticate($credentials, $options, &$response)
         $ldap_uid      = $this->params->get('ldap_uid');
         $auth_method   = $this->params->get('auth_method');
 
-        $ldap = Ldap::create(
-            'ext_ldap',
-            [
+        $options = [
                 'host'       => $this->params->get('host'),
                 'port'       => (int) $this->params->get('port'),
                 'version'    => $this->params->get('use_ldapV3', '0') == '1' ? 3 : 2,
                 'referrals'  => (bool) $this->params->get('no_referrals', '0'),
                 'encryption' => $this->params->get('negotiate_tls', '0') == '1' ? 'tls' : 'none',
-            ]
+            ];
+        Log::add(sprintf('Creating LDAP session with options: %s', json_encode($options)), Log::DEBUG, $logcategory);
+        $connection_string = sprintf('ldap%s://%s:%s', 'ssl' === $options['encryption'] ? 's' : '', $options['host'], $options['port']);
+        Log::add(sprintf('Creating LDAP session to connect to "%s" while binding', $connection_string), Log::DEBUG, $logcategory);
+        $ldap = Ldap::create(
+            'ext_ldap',
+            $options
         );
 
         switch ($auth_method) {
             case 'search':
                 try {
-                    $dn = str_replace('[username]', $this->params->get('username', ''), $this->params->get('users_dn', ''));
-
+                    $dn = $this->params->get('username', '');
+                    Log::add(sprintf('Binding to LDAP server with administrative dn "%s" and given administrative password (anonymous if user dn is blank)', $dn), Log::DEBUG, $logcategory);
                     $ldap->bind($dn, $this->params->get('password', ''));
                 } catch (ConnectionException | LdapException $exception) {
                     $response->status = Authentication::STATUS_FAILURE;
                     $response->error_message = Text::_('JGLOBAL_AUTH_NOT_CONNECT');
+                    Log::add($exception->getMessage(), Log::ERROR, $logcategory);
 
                     return;
                 }
 
                 // Search for users DN
                 try {
+                    $searchstring = str_replace(
+                        '[search]',
+                        str_replace(';', '\3b', $ldap->escape($credentials['username'], '', LDAP_ESCAPE_FILTER)),
+                        $this->params->get('search_string')
+                    );
+                    Log::add(sprintf('Searching LDAP entry with filter: "%s"', $searchstring), Log::DEBUG, $logcategory);
                     $entry = $this->searchByString(
-                        str_replace(
-                            '[search]',
-                            str_replace(';', '\3b', $ldap->escape($credentials['username'], '', LDAP_ESCAPE_FILTER)),
-                            $this->params->get('search_string')
-                        ),
+                        $searchstring,
                         $ldap
                     );
                 } catch (LdapException $exception) {
                     $response->status = Authentication::STATUS_FAILURE;
                     $response->error_message = Text::_('JGLOBAL_AUTH_UNKNOWN_ACCESS_DENIED');
+                    Log::add($exception->getMessage(), Log::ERROR, $logcategory);
 
                     return;
                 }
 
                 if (!$entry) {
+                    // we did not find the login in LDAP
                     $response->status = Authentication::STATUS_FAILURE;
-                    $response->error_message = Text::_('JGLOBAL_AUTH_NOT_CONNECT');
+                    $response->error_message = Text::_('JGLOBAL_AUTH_NO_USER');
+                    Log::add(Text::_('JGLOBAL_AUTH_USER_NOT_FOUND'), Log::ERROR, $logcategory);
 
                     return;
+                } else {
+                    Log::add(sprintf('LDAP entry found at "%s"', $entry->getDn()), Log::DEBUG, $logcategory);
                 }
 
                 try {
                     // Verify Users Credentials
+                    Log::add(sprintf('Binding to LDAP server with found user dn "%s" and user entered password', $entry->getDn()), Log::DEBUG, $logcategory);
                     $ldap->bind($entry->getDn(), $credentials['password']);
                 } catch (ConnectionException $exception) {
                     $response->status = Authentication::STATUS_FAILURE;
                     $response->error_message = Text::_('JGLOBAL_AUTH_INVALID_PASS');
+                    Log::add($exception->getMessage(), Log::ERROR, $logcategory);
 
                     return;
                 }
@@ -130,26 +146,41 @@ public function onUserAuthenticate($credentials, $options, &$response)
             case 'bind':
                 // We just accept the result here
                 try {
-                    $ldap->bind($ldap->escape($credentials['username'], '', LDAP_ESCAPE_DN), $credentials['password']);
+                    if ($this->params->get('users_dn', '') == '') {
+                        $dn = $credentials['username'];
+                    } else {
+                        $dn = str_replace(
+                            '[username]',
+                            $ldap->escape($credentials['username'], '', LDAP_ESCAPE_DN),
+                            $this->params->get('users_dn', '')
+                        );
+                    }
+
+                    Log::add(sprintf('Direct binding to LDAP server with entered user dn "%s" and user entered password', $dn), Log::DEBUG, $logcategory);
+                    $ldap->bind($dn, $credentials['password']);
                 } catch (ConnectionException | LdapException $exception) {
                     $response->status = Authentication::STATUS_FAILURE;
                     $response->error_message = Text::_('JGLOBAL_AUTH_INVALID_PASS');
+                    Log::add($exception->getMessage(), Log::ERROR, $logcategory);
 
                     return;
                 }
 
                 try {
+                    $searchstring = str_replace(
+                        '[search]',
+                        str_replace(';', '\3b', $ldap->escape($credentials['username'], '', LDAP_ESCAPE_FILTER)),
+                        $this->params->get('search_string')
+                    );
+                    Log::add(sprintf('Searching LDAP entry with filter: "%s"', $searchstring), Log::DEBUG, $logcategory);
                     $entry = $this->searchByString(
-                        str_replace(
-                            '[search]',
-                            str_replace(';', '\3b', $ldap->escape($credentials['username'], '', LDAP_ESCAPE_FILTER)),
-                            $this->params->get('search_string')
-                        ),
+                        $searchstring,
                         $ldap
                     );
                 } catch (LdapException $exception) {
                     $response->status = Authentication::STATUS_FAILURE;
                     $response->error_message = Text::_('JGLOBAL_AUTH_UNKNOWN_ACCESS_DENIED');
+                    Log::add($exception->getMessage(), Log::ERROR, $logcategory);
 
                     return;
                 }
@@ -160,6 +191,7 @@ public function onUserAuthenticate($credentials, $options, &$response)
                 // Unsupported configuration
                 $response->status = Authentication::STATUS_FAILURE;
                 $response->error_message = Text::_('JGLOBAL_AUTH_UNKNOWN_ACCESS_DENIED');
+                Log::add($response->error_message, Log::ERROR, $logcategory);
 
                 return;
         }
@@ -170,6 +202,7 @@ public function onUserAuthenticate($credentials, $options, &$response)
         $response->fullname = $entry->getAttribute($ldap_fullname)[0] ?? trim($entry->getAttribute($ldap_fullname)[0]) ?: $credentials['username'];
 
         // Were good - So say so.
+        Log::add(sprintf('LDAP login succeeded; username: "%s", email: "%s", fullname: "%s"', $response->username, $response->email, $response->fullname), Log::DEBUG, $logcategory);
         $response->status        = Authentication::STATUS_SUCCESS;
         $response->error_message = '';
 
diff --git a/plugins/authentication/ldap/ldap.xml b/plugins/authentication/ldap/ldap.xml
index 3174603109f4..2b0e4f42596c 100644
--- a/plugins/authentication/ldap/ldap.xml
+++ b/plugins/authentication/ldap/ldap.xml
@@ -23,6 +23,7 @@
 					name="host"
 					type="text"
 					label="PLG_LDAP_FIELD_HOST_LABEL"
+					required="true"
 				/>
 
 				<field
@@ -88,6 +89,7 @@
 					name="base_dn"
 					type="text"
 					label="PLG_LDAP_FIELD_BASEDN_LABEL"
+					required="true"
 				/>
 
 				<field
@@ -95,6 +97,7 @@
 					type="text"
 					label="PLG_LDAP_FIELD_SEARCHSTRING_LABEL"
 					description="PLG_LDAP_FIELD_SEARCHSTRING_DESC"
+					required="true"
 				/>
 
 				<field
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

4 participants