Browse Source

Add ldap user metadata mappings for full name and email (#102925)

* Add ldap user metadata mappings for full name and email
Johannes Fredén 1 year ago
parent
commit
70b15945a9

+ 5 - 0
docs/changelog/102925.yaml

@@ -0,0 +1,5 @@
+pr: 102925
+summary: Add ldap user metadata mappings for full name and email
+area: Authentication
+type: enhancement
+issues: []

+ 10 - 0
docs/reference/settings/security-settings.asciidoc

@@ -431,6 +431,16 @@ Specifies the attribute to examine on the user for group membership.
 If any `group_search` settings are specified, this setting is ignored. Defaults
 to `memberOf`.
 
+`user_full_name_attribute`::
+(<<static-cluster-setting,Static>>)
+Specifies the attribute to examine on the user for the full name of the user.
+Defaults to `cn`.
+
+`user_email_attribute`::
+(<<static-cluster-setting,Static>>)
+Specifies the attribute to examine on the user for the email address of the user.
+Defaults to `mail`.
+
 `user_search.base_dn`::
 (<<static-cluster-setting,Static>>)
 Specifies a container DN to search for users. Required

+ 11 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/support/LdapMetadataResolverSettings.java

@@ -9,7 +9,6 @@ package org.elasticsearch.xpack.core.security.authc.ldap.support;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.xpack.core.security.authc.RealmSettings;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
 
@@ -19,9 +18,19 @@ public final class LdapMetadataResolverSettings {
         key -> Setting.stringListSetting(key, Setting.Property.NodeScope)
     );
 
+    public static final Function<String, Setting.AffixSetting<String>> FULL_NAME_SETTING = RealmSettings.affixSetting(
+        "user_full_name_attribute",
+        key -> Setting.simpleString(key, "cn", Setting.Property.NodeScope)
+    );
+
+    public static final Function<String, Setting.AffixSetting<String>> EMAIL_SETTING = RealmSettings.affixSetting(
+        "user_email_attribute",
+        key -> Setting.simpleString(key, "mail", Setting.Property.NodeScope)
+    );
+
     private LdapMetadataResolverSettings() {}
 
     public static List<Setting.AffixSetting<?>> getSettings(String type) {
-        return Collections.singletonList(ADDITIONAL_METADATA_SETTING.apply(type));
+        return List.of(ADDITIONAL_METADATA_SETTING.apply(type), EMAIL_SETTING.apply(type), FULL_NAME_SETTING.apply(type));
     }
 }

+ 4 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java

@@ -261,10 +261,13 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
                 metadata.put("ldap_groups", ldapData.groups);
                 metadata.putAll(ldapData.metadata);
                 final UserData user = new UserData(username, session.userDn(), ldapData.groups, metadata, session.realm());
+
                 roleMapper.resolveRoles(user, ActionListener.wrap(roles -> {
                     IOUtils.close(session);
                     String[] rolesArray = roles.toArray(new String[roles.size()]);
-                    listener.onResponse(AuthenticationResult.success(new User(username, rolesArray, null, null, metadata, true)));
+                    listener.onResponse(
+                        AuthenticationResult.success(new User(username, rolesArray, ldapData.fullName, ldapData.email, metadata, true))
+                    );
                 }, onFailure));
             }, onFailure));
             loadingGroups = true;

+ 85 - 24
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapMetadataResolver.java

@@ -13,9 +13,12 @@ import com.unboundid.ldap.sdk.SearchScope;
 
 import org.apache.logging.log4j.Logger;
 import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.core.Nullable;
 import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.xpack.core.security.authc.RealmConfig;
 import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapMetadataResolverSettings;
+import org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySIDUtil;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -24,6 +27,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySIDUtil.TOKEN_GROUPS;
 import static org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySIDUtil.convertToString;
@@ -31,17 +35,36 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJE
 import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
 
 public class LdapMetadataResolver {
-
     private final String[] attributeNames;
     private final boolean ignoreReferralErrors;
+    private final String fullNameAttributeName;
+    private final String emailAttributeName;
+    private final String[] allAttributeNamesToResolve;
 
     public LdapMetadataResolver(RealmConfig realmConfig, boolean ignoreReferralErrors) {
-        this(realmConfig.getSetting(LdapMetadataResolverSettings.ADDITIONAL_METADATA_SETTING), ignoreReferralErrors);
+        this(
+            realmConfig.getSetting(LdapMetadataResolverSettings.FULL_NAME_SETTING),
+            realmConfig.getSetting(LdapMetadataResolverSettings.EMAIL_SETTING),
+            realmConfig.getSetting(LdapMetadataResolverSettings.ADDITIONAL_METADATA_SETTING),
+            ignoreReferralErrors
+        );
     }
 
-    LdapMetadataResolver(Collection<String> attributeNames, boolean ignoreReferralErrors) {
+    LdapMetadataResolver(
+        String fullNameAttributeName,
+        String emailAttributeName,
+        Collection<String> attributeNames,
+        boolean ignoreReferralErrors
+    ) {
+        this.fullNameAttributeName = fullNameAttributeName;
+        this.emailAttributeName = emailAttributeName;
         this.attributeNames = attributeNames.toArray(new String[attributeNames.size()]);
         this.ignoreReferralErrors = ignoreReferralErrors;
+        this.allAttributeNamesToResolve = Stream.concat(
+            Stream.of(this.attributeNames),
+            Stream.of(this.fullNameAttributeName, this.emailAttributeName)
+        ).distinct().toArray(String[]::new);
+
     }
 
     public String[] attributeNames() {
@@ -54,12 +77,12 @@ public class LdapMetadataResolver {
         TimeValue timeout,
         Logger logger,
         Collection<Attribute> attributes,
-        ActionListener<Map<String, Object>> listener
+        ActionListener<LdapMetadataResult> listener
     ) {
-        if (this.attributeNames.length == 0) {
-            listener.onResponse(Map.of());
+        if (Strings.isEmpty(this.fullNameAttributeName) && Strings.isEmpty(this.emailAttributeName) && this.attributeNames.length == 0) {
+            listener.onResponse(LdapMetadataResult.EMPTY);
         } else if (attributes != null) {
-            listener.onResponse(toMap(name -> findAttribute(attributes, name)));
+            listener.onResponse(toLdapMetadataResult(name -> findAttribute(attributes, name)));
         } else {
             searchForEntry(
                 connection,
@@ -70,12 +93,12 @@ public class LdapMetadataResolver {
                 ignoreReferralErrors,
                 ActionListener.wrap((SearchResultEntry entry) -> {
                     if (entry == null) {
-                        listener.onResponse(Map.of());
+                        listener.onResponse(LdapMetadataResult.EMPTY);
                     } else {
-                        listener.onResponse(toMap(entry::getAttribute));
+                        listener.onResponse(toLdapMetadataResult(entry::getAttribute));
                     }
                 }, listener::onFailure),
-                this.attributeNames
+                allAttributeNamesToResolve
             );
         }
     }
@@ -84,21 +107,59 @@ public class LdapMetadataResolver {
         return attributes.stream().filter(attr -> attr.getName().equals(name)).findFirst().orElse(null);
     }
 
-    private Map<String, Object> toMap(Function<String, Attribute> attributes) {
-        return Arrays.stream(this.attributeNames)
+    public static class LdapMetadataResult {
+
+        public static LdapMetadataResult EMPTY = new LdapMetadataResult(null, null, Map.of());
+
+        private final String fullName;
+        private final String email;
+        private final Map<String, Object> metaData;
+
+        public LdapMetadataResult(@Nullable String fullName, @Nullable String email, Map<String, Object> metaData) {
+            this.fullName = fullName;
+            this.email = email;
+            this.metaData = metaData;
+        }
+
+        @Nullable
+        public String getFullName() {
+            return fullName;
+        }
+
+        @Nullable
+        public String getEmail() {
+            return email;
+        }
+
+        public Map<String, Object> getMetaData() {
+            return metaData;
+        }
+    }
+
+    private static Object parseLdapAttributeValue(Attribute attr) {
+        final String[] values = attr.getValues();
+        if (attr.getName().equals(TOKEN_GROUPS)) {
+            return values.length == 1
+                ? convertToString(attr.getValueByteArrays()[0])
+                : Arrays.stream(attr.getValueByteArrays()).map(ActiveDirectorySIDUtil::convertToString).collect(Collectors.toList());
+        }
+        return values.length == 1 ? values[0] : List.of(values);
+
+    }
+
+    private LdapMetadataResult toLdapMetadataResult(Function<String, Attribute> attributes) {
+        Attribute emailAttribute = attributes.apply(this.emailAttributeName);
+        Attribute fullNameAttribute = attributes.apply(this.fullNameAttributeName);
+
+        Map<String, Object> metaData = Arrays.stream(this.attributeNames)
             .map(attributes)
             .filter(Objects::nonNull)
-            .collect(Collectors.toUnmodifiableMap(attr -> attr.getName(), attr -> {
-                final String[] values = attr.getValues();
-                if (attr.getName().equals(TOKEN_GROUPS)) {
-                    return values.length == 1
-                        ? convertToString(attr.getValueByteArrays()[0])
-                        : Arrays.stream(attr.getValueByteArrays())
-                            .map((sidBytes) -> convertToString(sidBytes))
-                            .collect(Collectors.toList());
-                }
-                return values.length == 1 ? values[0] : List.of(values);
-            }));
-    }
+            .collect(Collectors.toUnmodifiableMap(Attribute::getName, LdapMetadataResolver::parseLdapAttributeValue));
 
+        return new LdapMetadataResult(
+            fullNameAttribute == null ? null : LdapMetadataResolver.parseLdapAttributeValue(fullNameAttribute).toString(),
+            emailAttribute == null ? null : LdapMetadataResolver.parseLdapAttributeValue(emailAttribute).toString(),
+            metaData
+        );
+    }
 }

+ 15 - 4
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java

@@ -102,7 +102,7 @@ public class LdapSession implements Releasable {
         groupsResolver.resolve(connection, userDn, timeout, logger, attributes, listener);
     }
 
-    public void metadata(ActionListener<Map<String, Object>> listener) {
+    public void metadata(ActionListener<LdapMetadataResolver.LdapMetadataResult> listener) {
         metadataResolver.resolve(connection, userDn, timeout, logger, attributes, listener);
     }
 
@@ -111,17 +111,28 @@ public class LdapSession implements Releasable {
         groups(ActionListener.wrap(groups -> {
             logger.debug("Resolved {} LDAP groups [{}] for user [{}]", groups.size(), groups, userDn);
             metadata(ActionListener.wrap(meta -> {
-                logger.debug("Resolved {} meta-data fields [{}] for user [{}]", meta.size(), meta, userDn);
-                listener.onResponse(new LdapUserData(groups, meta));
+                logger.debug(
+                    "Resolved full name [{}], email [{}] and {} meta-data [{}] for user [{}]",
+                    meta.getFullName(),
+                    meta.getEmail(),
+                    meta.getMetaData().size(),
+                    meta,
+                    userDn
+                );
+                listener.onResponse(new LdapUserData(meta.getFullName(), meta.getEmail(), groups, meta.getMetaData()));
             }, listener::onFailure));
         }, listener::onFailure));
     }
 
     public static class LdapUserData {
+        public final String fullName;
+        public final String email;
         public final List<String> groups;
         public final Map<String, Object> metadata;
 
-        public LdapUserData(List<String> groups, Map<String, Object> metadata) {
+        public LdapUserData(String fullName, String email, List<String> groups, Map<String, Object> metadata) {
+            this.fullName = fullName;
+            this.email = email;
             this.groups = groups;
             this.metadata = metadata;
         }

+ 36 - 4
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java

@@ -33,7 +33,6 @@ import org.elasticsearch.xpack.core.security.authc.Realm;
 import org.elasticsearch.xpack.core.security.authc.RealmConfig;
 import org.elasticsearch.xpack.core.security.authc.RealmSettings;
 import org.elasticsearch.xpack.core.security.authc.ldap.LdapRealmSettings;
-import org.elasticsearch.xpack.core.security.authc.ldap.LdapSessionFactorySettings;
 import org.elasticsearch.xpack.core.security.authc.ldap.LdapUserSearchSessionFactorySettings;
 import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings;
 import org.elasticsearch.xpack.core.security.authc.ldap.SearchGroupsResolverSettings;
@@ -67,6 +66,7 @@ import java.util.stream.Collectors;
 
 import static org.elasticsearch.test.ActionListenerUtils.anyActionListener;
 import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey;
+import static org.elasticsearch.xpack.core.security.authc.ldap.LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING;
 import static org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings.URLS_SETTING;
 import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.VERIFICATION_MODE_SETTING_REALM;
 import static org.hamcrest.Matchers.arrayContaining;
@@ -139,7 +139,39 @@ public class LdapRealmTests extends LdapTestCase {
         assertThat(user.metadata(), notNullValue());
         assertThat(user.metadata().get("ldap_dn"), equalTo("cn=" + VALID_USERNAME + ",ou=people,o=sevenSeas"));
         assertThat(user.metadata().get("ldap_groups"), instanceOf(List.class));
+        assertThat(user.metadata().get("mail"), nullValue());
+        assertThat(user.metadata().get("cn"), nullValue());
         assertThat((List<?>) user.metadata().get("ldap_groups"), contains("cn=HMS Victory,ou=crews,ou=groups,o=sevenSeas"));
+        assertThat(user.email(), equalTo("thardy@royalnavy.mod.uk"));
+        assertThat(user.fullName(), equalTo("Thomas Masterman Hardy"));
+    }
+
+    public void testAuthenticateMapFullNameAndEmailMetadata() throws Exception {
+        String groupSearchBase = "o=sevenSeas";
+        boolean misssingSetting = randomBoolean();
+        Settings settings = Settings.builder()
+            .put(defaultGlobalSettings)
+            .put(buildLdapSettings(ldapUrls(), VALID_USER_TEMPLATE, groupSearchBase, LdapSearchScope.SUB_TREE))
+            .put(getFullSettingKey(REALM_IDENTIFIER, RealmSettings.ORDER_SETTING), 0)
+            .put(
+                getFullSettingKey(REALM_IDENTIFIER, LdapMetadataResolverSettings.FULL_NAME_SETTING),
+                misssingSetting ? "thisdoesnotexist" : "description"
+            )
+            .put(getFullSettingKey(REALM_IDENTIFIER, LdapMetadataResolverSettings.EMAIL_SETTING), "uid")
+            .build();
+        RealmConfig config = getRealmConfig(REALM_IDENTIFIER, settings);
+        SessionFactory ldapFactory = LdapRealm.sessionFactory(config, sslService, threadPool);
+        LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), threadPool);
+        ldap.initialize(Collections.singleton(ldap), licenseState);
+
+        PlainActionFuture<AuthenticationResult<User>> future = new PlainActionFuture<>();
+        ldap.authenticate(new UsernamePasswordToken("John Samuel", new SecureString(PASSWORD)), future);
+        final AuthenticationResult<User> result = future.actionGet();
+        assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
+        User user = result.getValue();
+        assertThat(user, notNullValue());
+        assertThat(user.email(), equalTo("jsamuel@royalnavy.mod.uk"));
+        assertThat(user.fullName(), equalTo(misssingSetting ? null : "Clerk John Samuel"));
     }
 
     private RealmConfig getRealmConfig(RealmConfig.RealmIdentifier identifier, Settings settings) {
@@ -327,7 +359,7 @@ public class LdapRealmTests extends LdapTestCase {
             .put(defaultGlobalSettings)
 
             .putList(getFullSettingKey(identifier, URLS_SETTING), ldapUrls())
-            .putList(getFullSettingKey(identifier.getName(), LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING), userTemplate)
+            .putList(getFullSettingKey(identifier.getName(), USER_DN_TEMPLATES_SETTING), userTemplate)
             .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.BASE_DN), groupSearchBase)
             .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.SCOPE), LdapSearchScope.SUB_TREE)
             .put(getFullSettingKey(identifier, VERIFICATION_MODE_SETTING_REALM), SslVerificationMode.CERTIFICATE)
@@ -385,7 +417,7 @@ public class LdapRealmTests extends LdapTestCase {
         final Settings.Builder settingsBuilder = Settings.builder()
             .put(defaultGlobalSettings)
             .putList(getFullSettingKey(identifier, URLS_SETTING), ldapUrls())
-            .putList(getFullSettingKey(identifier.getName(), LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING), "cn=foo")
+            .putList(getFullSettingKey(identifier.getName(), USER_DN_TEMPLATES_SETTING), "cn=foo")
             .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.BASE_DN), "")
             .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.SCOPE), LdapSearchScope.SUB_TREE)
             .put(getFullSettingKey(identifier, VERIFICATION_MODE_SETTING_REALM), SslVerificationMode.CERTIFICATE)
@@ -595,7 +627,7 @@ public class LdapRealmTests extends LdapTestCase {
             .put(getFullSettingKey(identifier, PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD), PASSWORD)
             .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.BASE_DN), groupSearchBase)
             .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.SCOPE), LdapSearchScope.SUB_TREE)
-            .put(getFullSettingKey(identifier.getName(), LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING), "--")
+            .put(getFullSettingKey(identifier.getName(), USER_DN_TEMPLATES_SETTING), "--")
             .put(getFullSettingKey(identifier, VERIFICATION_MODE_SETTING_REALM), SslVerificationMode.CERTIFICATE);
 
         int order = randomIntBetween(0, 10);

+ 7 - 7
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapMetadataResolverTests.java

@@ -26,7 +26,7 @@ import java.util.List;
 import java.util.Map;
 
 import static org.hamcrest.Matchers.aMapWithSize;
-import static org.hamcrest.Matchers.arrayContaining;
+import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.instanceOf;
@@ -54,11 +54,11 @@ public class LdapMetadataResolverTests extends ESTestCase {
             .build();
         RealmConfig config = new RealmConfig(realmId, settings, TestEnvironment.newEnvironment(settings), new ThreadContext(settings));
         resolver = new LdapMetadataResolver(config, false);
-        assertThat(resolver.attributeNames(), arrayContaining("cn", "uid"));
+        assertThat(resolver.attributeNames(), arrayContainingInAnyOrder("cn", "uid"));
     }
 
     public void testResolveSingleValuedAttributeFromCachedAttributes() throws Exception {
-        resolver = new LdapMetadataResolver(Arrays.asList("cn", "uid"), true);
+        resolver = new LdapMetadataResolver(null, null, Arrays.asList("cn", "uid"), true);
         final Collection<Attribute> attributes = Arrays.asList(
             new Attribute("cn", "Clint Barton"),
             new Attribute("uid", "hawkeye"),
@@ -72,7 +72,7 @@ public class LdapMetadataResolverTests extends ESTestCase {
     }
 
     public void testResolveMultiValuedAttributeFromCachedAttributes() throws Exception {
-        resolver = new LdapMetadataResolver(Arrays.asList("cn", "uid"), true);
+        resolver = new LdapMetadataResolver(null, null, Arrays.asList("cn", "uid"), true);
         final Collection<Attribute> attributes = Arrays.asList(
             new Attribute("cn", "Clint Barton", "hawkeye"),
             new Attribute("uid", "hawkeye")
@@ -85,7 +85,7 @@ public class LdapMetadataResolverTests extends ESTestCase {
     }
 
     public void testResolveMissingAttributeFromCachedAttributes() throws Exception {
-        resolver = new LdapMetadataResolver(Arrays.asList("cn", "uid"), true);
+        resolver = new LdapMetadataResolver(null, null, Arrays.asList("cn", "uid"), true);
         final Collection<Attribute> attributes = Collections.singletonList(new Attribute("uid", "hawkeye"));
         final Map<String, Object> map = resolve(attributes);
         assertThat(map, aMapWithSize(1));
@@ -94,8 +94,8 @@ public class LdapMetadataResolverTests extends ESTestCase {
     }
 
     private Map<String, Object> resolve(Collection<Attribute> attributes) throws Exception {
-        final PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
+        final PlainActionFuture<LdapMetadataResolver.LdapMetadataResult> future = new PlainActionFuture<>();
         resolver.resolve(null, HAWKEYE_DN, TimeValue.timeValueSeconds(1), logger, attributes, future);
-        return future.get();
+        return future.get().getMetaData();
     }
 }

+ 13 - 0
x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/ldap/support/seven-seas.ldif

@@ -206,6 +206,19 @@ uid: jhallett
 mail: jhallett@royalnavy.mod.uk
 userpassword: pass
 
+dn: cn=John Samuel,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: John Samuel
+description: Clerk John Samuel
+givenname: John
+manager: cn=William Bligh,ou=people,o=sevenSeas
+sn: Samuel
+uid: jsamuel@royalnavy.mod.uk
+userpassword: pass
+
 dn: cn=HMS Bounty,ou=crews,ou=groups,o=sevenSeas
 objectclass: groupOfUniqueNames
 objectclass: top

+ 6 - 4
x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/test/OpenLdapTests.java

@@ -244,7 +244,8 @@ public class OpenLdapTests extends ESTestCase {
             .putList(
                 getFullSettingKey(realmId.getName(), LdapMetadataResolverSettings.ADDITIONAL_METADATA_SETTING.apply("ldap")),
                 "cn",
-                "sn"
+                "sn",
+                "mail"
             )
             .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0)
             .build();
@@ -257,9 +258,10 @@ public class OpenLdapTests extends ESTestCase {
         LdapMetadataResolver resolver = new LdapMetadataResolver(config, true);
         try (LDAPConnection ldapConnection = setupOpenLdapConnection()) {
             final Map<String, Object> map = resolve(ldapConnection, resolver);
-            assertThat(map.size(), equalTo(2));
+            assertThat(map.size(), equalTo(3));
             assertThat(map.get("cn"), equalTo("Clint Barton"));
             assertThat(map.get("sn"), equalTo("Clint Barton"));
+            assertThat(map.get("mail"), equalTo("hawkeye@oldap.test.elasticsearch.com"));
         }
     }
 
@@ -343,9 +345,9 @@ public class OpenLdapTests extends ESTestCase {
     }
 
     private Map<String, Object> resolve(LDAPConnection connection, LdapMetadataResolver resolver) throws Exception {
-        final PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
+        final PlainActionFuture<LdapMetadataResolver.LdapMetadataResult> future = new PlainActionFuture<>();
         resolver.resolve(connection, HAWKEYE_DN, TimeValue.timeValueSeconds(1), logger, null, future);
-        return future.get();
+        return future.get().getMetaData();
     }
 
     private static String getFromProperty(String port) {

+ 2 - 2
x-pack/qa/third-party/active-directory/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactoryTests.java

@@ -511,14 +511,14 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryT
             .put(getFullSettingKey(REALM_ID, LdapMetadataResolverSettings.ADDITIONAL_METADATA_SETTING), "tokenGroups")
             .build();
         RealmConfig config = configureRealm("ad-test", LdapRealmSettings.AD_TYPE, settings);
-        final PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
+        final PlainActionFuture<LdapMetadataResolver.LdapMetadataResult> future = new PlainActionFuture<>();
         LdapMetadataResolver resolver = new LdapMetadataResolver(config, true);
         try (ActiveDirectorySessionFactory sessionFactory = getActiveDirectorySessionFactory(config, sslService, threadPool)) {
             String userName = "hulk";
             try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
                 assertConnectionCanReconnect(ldap.getConnection());
                 resolver.resolve(ldap.getConnection(), BRUCE_BANNER_DN, TimeValue.timeValueSeconds(1), logger, null, future);
-                Map<String, Object> metadataGroupSIDs = future.get();
+                Map<String, Object> metadataGroupSIDs = future.get().getMetaData();
                 assertThat(metadataGroupSIDs.size(), equalTo(1));
                 assertNotNull(metadataGroupSIDs.get("tokenGroups"));
                 List<String> SIDs = ((List<String>) metadataGroupSIDs.get("tokenGroups"));