Resolve an Issue with Openfire when Some Users Are Not Seen in LDAP Directory

Project Description

We have an issue with Openfire not allowing a user with first an last name matching an already existing user. What appears to be happening is that such users, although having unique distinguished names, do not appear listed in groups (as seen on Openfire side). Instead, only one such user is listed. We need help identifying and fixing this problem.

Completion Notes

First, lets provide some details about the problem and the environment.

Due to peculiarities at customer sites, some supervisors in the organization have 2 Windows accounts. These accounts have the same user full name (first and last name together, or CN). However, their Windows logins are different. The accounts security rights in the domain are different, too. The intention was that one of the accounts has more rights (like admin access to the system to install apps).

Client needs both such accounts to be seen in Openfire as separate entities. The problem is that only one account shows up, all others with matching first and last name are dropped. An immediate workaround is, of course to alter the CN, for example, by inserting a middle initial, but this does not fix the original problem.

Also, a large enterprise may employ several people sharing the same CN, when the same problem occurs.

Openfire Testing Environment

To investigate the issue, the following testing environment was created.

Having 2 machines with minimalistic set of users in AD allowed us to isolate the problem from the complexities of a potentially large LDAP directory.

Active Directory Users

Here is how our test users are set up in Active Directory. In the builtin Users container, we'll define a johndoe account. We'll also define a couple of organizational units under root, such as OU1 and OU2. We'll create johndoea and johndoeb in them, one in each. We'll also create a new security gorup in Users called Test and put all three users there. The resulting distinguished names for users are:

CN=John Doe,CN=Users,DC=example,DC=com
CN=John Doe,OU=OU1,DC=example,DC=com
CN=John Doe,OU=OU2,DC=example,DC=com
Notice that the CN part is common to all 3 user accounts.

Now if we try to use Openfire 3.10.3 to list users in the Test group, we'll see only one user there, while we expect to see 3. Why is this happening?

Only one user is visible in group while 3 are expected

Only one user is visible in group while 3 are expected

Building Openfire

To locate the problem we need to examine Openfire source code.

The build procedure is relatively straightforward, we install Ant, download source code, and then use Ant to build the jars.

The result of the build process is a collection of newly created jar files in openfire_src/target/openfire/lib folder. For this project, we are only interested in openfire.jar.

Just to make sure things are still the same, we put the newly built openfire.jar (with unmodified code) into where our Openfire runs to confirm that we see only a single user in the Test group.

Locating the Problem

Now it's time to insert some additional logging into Openfire source to see what's going on. To do it, we can utilize the Log.error() function. We put debug output in various places of the code, rebuild openfire.jar, rerun Openfire, use admin app to see the content of the Test group and examine the log file.

After a few iterations, we can see that the problem is in the LdapGroupProvider::processGroup function located in src/java/org/jivesoftware/openfire/ldap/

What appears to be happening is that an LDAP search is performed by using the first part of user DN (CN=John Doe). Then, the first found result is used to populate the group.

The above explains why we see only one user in the group, not three.

Fixing Openfire

Note: the fix below only works for Active Directory, because distinguishedName is specific to it. Do NOT apply the fix if you use another LDAP server.

To fix the problem, we have to examine all search results and find a match for our user DN. When an exact DN match is found, we may use it instead.

To accomplish this, we need to modify the code in 2 places.

First, we add ditinguishedName to a set of attributes that a search returns:

--- C:/Users/Nik/Desktop/LdapGroupProvider.java_orig	Mon Dec 28 17:11:59 2015
+++ C:/Users/Nik/Desktop/	Mon Dec 28 17:17:04 2015
@@ -218,7 +218,7 @@ public class LdapGroupProvider extends AbstractGro
                 Pattern.compile("(?i)(^" + manager.getUsernameField() + "=)([^,]+)(.+)");
         SearchControls searchControls = new SearchControls();
-        searchControls.setReturningAttributes(new String[] { manager.getUsernameField() });
+        searchControls.setReturningAttributes(new String[] { "distinguishedName", manager.getUsernameField() });
Now, instead of one attribuite, we return 2, one being distinguishedName.

Second, we have to iterate through the entire result set to find a match for returned DN:

--- C:/Users/Nik/Desktop/LdapGroupProvider.java_orig	Mon Dec 28 17:11:59 2015
+++ C:/Users/Nik/Desktop/	Mon Dec 28 17:17:04 2015
@@ -274,10 +274,20 @@ public class LdapGroupProvider extends AbstractGro
                             NamingEnumeration usrAnswer ="",
                                     userFilter.toString(), searchControls);
                             if (usrAnswer != null && usrAnswer.hasMoreElements()) {
-                                Attribute usernameAttr = ((SearchResult);
-                                if (usernameAttr != null) {
-                                    username = (String)usernameAttr.get();
-                                }
+								SearchResult searchResult = null;
+								// We may get multiple search results for the same user CN.
+								// Iterate through the entire set to find a matching distinguished name.
+								while(usrAnswer.hasMoreElements()) {
+									searchResult = (SearchResult) usrAnswer.nextElement();
+									Attributes attrs = searchResult.getAttributes();
+									Attribute userdnAttr = attrs.get("distinguishedName");
+									if (username.equals((String)userdnAttr.get())) {
+										// Exact match found, use it.
+										username = (String)attrs.get(manager.getUsernameField()).get();
+										break;
+									}
+								}
                             // Close the enumeration.

With the above 2 changes in place, we build openfire.jar and try our Test group again. We can now see the all users are listed:

Three users are listed properly in Openfire group

Three users are listed properly in Openfire group

You can download a modified openfire.jar from here.