mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-17 10:25:15 +00:00
Failed LDAP SearchResults should be Exceptions (elastic/x-pack-elasticsearch#773)
When the LDAP SDK returns a SearchResult that has a non-success ResultCode, convert it to an exception and call onFailure A configuration setting controls whether failures in referrals should be fatal (defaults to ignoring errors) Closes: elastic/x-pack-elasticsearch#717 Original commit: elastic/x-pack-elasticsearch@4159758c2a
This commit is contained in:
parent
7c45cb7ccf
commit
7f0fd9e1a3
@ -475,12 +475,9 @@
|
|||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapRealm.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapRealm.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapSessionFactory.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapSessionFactory.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapUserSearchSessionFactory.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapUserSearchSessionFactory.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]SearchGroupsResolver.java" checks="LineLength" />
|
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]UserAttributeGroupsResolver.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]UserAttributeGroupsResolver.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapLoadBalancing.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapLoadBalancing.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapSession.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapSession.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapUtils.java" checks="LineLength" />
|
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]SessionFactory.java" checks="LineLength" />
|
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]pki[/\\]PkiRealm.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]pki[/\\]PkiRealm.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]support[/\\]BCrypt.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]support[/\\]BCrypt.java" checks="LineLength" />
|
||||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]support[/\\]CachingRealm.java" checks="LineLength" />
|
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]support[/\\]CachingRealm.java" checks="LineLength" />
|
||||||
|
@ -33,22 +33,26 @@ class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
|||||||
private static final String TOKEN_GROUPS = "tokenGroups";
|
private static final String TOKEN_GROUPS = "tokenGroups";
|
||||||
private final String baseDn;
|
private final String baseDn;
|
||||||
private final LdapSearchScope scope;
|
private final LdapSearchScope scope;
|
||||||
|
private final boolean ignoreReferralErrors;
|
||||||
|
|
||||||
ActiveDirectoryGroupsResolver(Settings settings, String baseDnDefault) {
|
ActiveDirectoryGroupsResolver(Settings settings, String baseDnDefault,
|
||||||
|
boolean ignoreReferralErrors) {
|
||||||
this.baseDn = settings.get("base_dn", baseDnDefault);
|
this.baseDn = settings.get("base_dn", baseDnDefault);
|
||||||
this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE);
|
this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE);
|
||||||
|
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolve(LDAPInterface connection, String userDn, TimeValue timeout, Logger logger, Collection<Attribute> attributes,
|
public void resolve(LDAPInterface connection, String userDn, TimeValue timeout, Logger logger, Collection<Attribute> attributes,
|
||||||
ActionListener<List<String>> listener) {
|
ActionListener<List<String>> listener) {
|
||||||
buildGroupQuery(connection, userDn, timeout,
|
buildGroupQuery(connection, userDn, timeout,
|
||||||
ActionListener.wrap((filter) -> {
|
ignoreReferralErrors, ActionListener.wrap((filter) -> {
|
||||||
if (filter == null) {
|
if (filter == null) {
|
||||||
listener.onResponse(Collections.emptyList());
|
listener.onResponse(Collections.emptyList());
|
||||||
} else {
|
} else {
|
||||||
logger.debug("group SID to DN [{}] search filter: [{}]", userDn, filter);
|
logger.debug("group SID to DN [{}] search filter: [{}]", userDn, filter);
|
||||||
search(connection, baseDn, scope.scope(), filter, Math.toIntExact(timeout.seconds()),
|
search(connection, baseDn, scope.scope(), filter,
|
||||||
|
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||||
ActionListener.wrap((results) -> {
|
ActionListener.wrap((results) -> {
|
||||||
List<String> groups = results.stream()
|
List<String> groups = results.stream()
|
||||||
.map(SearchResultEntry::getDN)
|
.map(SearchResultEntry::getDN)
|
||||||
@ -67,8 +71,10 @@ class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void buildGroupQuery(LDAPInterface connection, String userDn, TimeValue timeout, ActionListener<Filter> listener) {
|
static void buildGroupQuery(LDAPInterface connection, String userDn, TimeValue timeout,
|
||||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, Math.toIntExact(timeout.seconds()),
|
boolean ignoreReferralErrors, ActionListener<Filter> listener) {
|
||||||
|
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER,
|
||||||
|
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||||
ActionListener.wrap((entry) -> {
|
ActionListener.wrap((entry) -> {
|
||||||
if (entry == null || entry.hasAttribute(TOKEN_GROUPS) == false) {
|
if (entry == null || entry.hasAttribute(TOKEN_GROUPS) == false) {
|
||||||
listener.onResponse(null);
|
listener.onResponse(null);
|
||||||
|
@ -65,13 +65,18 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
Settings settings = config.settings();
|
Settings settings = config.settings();
|
||||||
String domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
String domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
||||||
if (domainName == null) {
|
if (domainName == null) {
|
||||||
throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING + "] setting for active directory");
|
throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING +
|
||||||
|
"] setting for active directory");
|
||||||
}
|
}
|
||||||
String domainDN = buildDnFromDomain(domainName);
|
String domainDN = buildDnFromDomain(domainName);
|
||||||
GroupsResolver groupResolver = new ActiveDirectoryGroupsResolver(settings.getAsSettings("group_search"), domainDN);
|
GroupsResolver groupResolver = new ActiveDirectoryGroupsResolver(
|
||||||
defaultADAuthenticator = new DefaultADAuthenticator(settings, timeout, logger, groupResolver, domainDN);
|
settings.getAsSettings("group_search"), domainDN, ignoreReferralErrors);
|
||||||
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout, logger, groupResolver, domainDN, sslService);
|
defaultADAuthenticator = new DefaultADAuthenticator(settings, timeout,
|
||||||
upnADAuthenticator = new UpnADAuthenticator(settings, timeout, logger, groupResolver, domainDN);
|
ignoreReferralErrors, logger, groupResolver, domainDN);
|
||||||
|
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout,
|
||||||
|
ignoreReferralErrors, logger, groupResolver, domainDN, sslService);
|
||||||
|
upnADAuthenticator = new UpnADAuthenticator(settings, timeout,
|
||||||
|
ignoreReferralErrors, logger, groupResolver, domainDN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -135,13 +140,16 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
abstract static class ADAuthenticator {
|
abstract static class ADAuthenticator {
|
||||||
|
|
||||||
final TimeValue timeout;
|
final TimeValue timeout;
|
||||||
|
final boolean ignoreReferralErrors;
|
||||||
final Logger logger;
|
final Logger logger;
|
||||||
final GroupsResolver groupsResolver;
|
final GroupsResolver groupsResolver;
|
||||||
final String userSearchDN;
|
final String userSearchDN;
|
||||||
final LdapSearchScope userSearchScope;
|
final LdapSearchScope userSearchScope;
|
||||||
|
|
||||||
ADAuthenticator(Settings settings, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
ADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
|
||||||
|
Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
|
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.groupsResolver = groupsResolver;
|
this.groupsResolver = groupsResolver;
|
||||||
userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN);
|
userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN);
|
||||||
@ -195,19 +203,22 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
final String userSearchFilter;
|
final String userSearchFilter;
|
||||||
|
|
||||||
final String domainName;
|
final String domainName;
|
||||||
DefaultADAuthenticator(Settings settings, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
DefaultADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
|
||||||
super(settings, timeout, logger, groupsResolver, domainDN);
|
Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||||
|
super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN);
|
||||||
domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
||||||
userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" +
|
userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" +
|
||||||
"(userPrincipalName={0}@" + domainName + ")))");
|
"(userPrincipalName={0}@" + domainName + ")))");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void searchForDN(LDAPConnection connection, String username, SecuredString password, int timeLimitSeconds,
|
void searchForDN(LDAPConnection connection, String username, SecuredString password,
|
||||||
ActionListener<SearchResultEntry> listener) {
|
int timeLimitSeconds, ActionListener<SearchResultEntry> listener) {
|
||||||
try {
|
try {
|
||||||
searchForEntry(connection, userSearchDN, userSearchScope.scope(), createFilter(userSearchFilter, username),
|
searchForEntry(connection, userSearchDN, userSearchScope.scope(),
|
||||||
timeLimitSeconds, listener, attributesToSearchFor(groupsResolver.attributes()));
|
createFilter(userSearchFilter, username), timeLimitSeconds,
|
||||||
|
ignoreReferralErrors, listener,
|
||||||
|
attributesToSearchFor(groupsResolver.attributes()));
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
@ -220,8 +231,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Active Directory calls the format <code>DOMAIN\\username</code> down-level credentials and this class contains the logic necessary
|
* Active Directory calls the format <code>DOMAIN\\username</code> down-level credentials and
|
||||||
* to authenticate this form of a username
|
* this class contains the logic necessary to authenticate this form of a username
|
||||||
*/
|
*/
|
||||||
static class DownLevelADAuthenticator extends ADAuthenticator {
|
static class DownLevelADAuthenticator extends ADAuthenticator {
|
||||||
Cache<String, String> domainNameCache = CacheBuilder.<String, String>builder().setMaximumWeight(100).build();
|
Cache<String, String> domainNameCache = CacheBuilder.<String, String>builder().setMaximumWeight(100).build();
|
||||||
@ -231,9 +242,12 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
final SSLService sslService;
|
final SSLService sslService;
|
||||||
final RealmConfig config;
|
final RealmConfig config;
|
||||||
|
|
||||||
DownLevelADAuthenticator(RealmConfig config, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN,
|
DownLevelADAuthenticator(RealmConfig config, TimeValue timeout,
|
||||||
|
boolean ignoreReferralErrors, Logger logger,
|
||||||
|
GroupsResolver groupsResolver, String domainDN,
|
||||||
SSLService sslService) {
|
SSLService sslService) {
|
||||||
super(config.settings(), timeout, logger, groupsResolver, domainDN);
|
super(config.settings(), timeout, ignoreReferralErrors, logger, groupsResolver,
|
||||||
|
domainDN);
|
||||||
this.domainDN = domainDN;
|
this.domainDN = domainDN;
|
||||||
this.settings = config.settings();
|
this.settings = config.settings();
|
||||||
this.sslService = sslService;
|
this.sslService = sslService;
|
||||||
@ -255,8 +269,9 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(),
|
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(),
|
||||||
createFilter("(&(objectClass=user)(sAMAccountName={0}))", accountName), timeLimitSeconds, listener,
|
createFilter("(&(objectClass=user)(sAMAccountName={0}))",
|
||||||
attributesToSearchFor(groupsResolver.attributes()));
|
accountName), timeLimitSeconds, ignoreReferralErrors,
|
||||||
|
listener, attributesToSearchFor(groupsResolver.attributes()));
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
IOUtils.closeWhileHandlingException(connection);
|
IOUtils.closeWhileHandlingException(connection);
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
@ -274,8 +289,9 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
if (cachedName != null) {
|
if (cachedName != null) {
|
||||||
listener.onResponse(cachedName);
|
listener.onResponse(cachedName);
|
||||||
} else if (usingGlobalCatalog(settings, connection)) {
|
} else if (usingGlobalCatalog(settings, connection)) {
|
||||||
// the global catalog does not replicate the necessary information to map a netbios dns name to a DN so we need to instead
|
// the global catalog does not replicate the necessary information to map a netbios
|
||||||
// connect to the normal ports. This code uses the standard ports to avoid adding even more settings and is probably ok as
|
// dns name to a DN so we need to instead connect to the normal ports. This code
|
||||||
|
// uses the standard ports to avoid adding even more settings and is probably ok as
|
||||||
// most AD users do not use non-standard ports
|
// most AD users do not use non-standard ports
|
||||||
final LDAPConnectionOptions options = connectionOptions(config, sslService, logger);
|
final LDAPConnectionOptions options = connectionOptions(config, sslService, logger);
|
||||||
boolean startedSearching = false;
|
boolean startedSearching = false;
|
||||||
@ -283,7 +299,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
try {
|
try {
|
||||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||||
if (connection.getSSLSession() != null) {
|
if (connection.getSSLSession() != null) {
|
||||||
searchConnection = LdapUtils.privilegedConnect(() -> new LDAPConnection(connection.getSocketFactory(), options,
|
searchConnection = LdapUtils.privilegedConnect(
|
||||||
|
() -> new LDAPConnection(connection.getSocketFactory(), options,
|
||||||
connection.getConnectedAddress(), 636));
|
connection.getConnectedAddress(), 636));
|
||||||
} else {
|
} else {
|
||||||
searchConnection = LdapUtils.privilegedConnect(() ->
|
searchConnection = LdapUtils.privilegedConnect(() ->
|
||||||
@ -291,14 +308,16 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
}
|
}
|
||||||
searchConnection.bind(username, new String(password.internalChars()));
|
searchConnection.bind(username, new String(password.internalChars()));
|
||||||
final LDAPConnection finalConnection = searchConnection;
|
final LDAPConnection finalConnection = searchConnection;
|
||||||
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds,
|
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||||
ActionListener.wrap((results) -> {
|
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||||
IOUtils.close(finalConnection);
|
(results) -> {
|
||||||
handleSearchResults(results, netBiosDomainName, domainNameCache, listener);
|
IOUtils.close(finalConnection);
|
||||||
}, (e) -> {
|
handleSearchResults(results, netBiosDomainName,
|
||||||
IOUtils.closeWhileHandlingException(connection);
|
domainNameCache, listener);
|
||||||
listener.onFailure(e);
|
}, (e) -> {
|
||||||
}),
|
IOUtils.closeWhileHandlingException(connection);
|
||||||
|
listener.onFailure(e);
|
||||||
|
}),
|
||||||
"ncname");
|
"ncname");
|
||||||
startedSearching = true;
|
startedSearching = true;
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
@ -311,8 +330,10 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||||
search(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds,
|
search(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||||
ActionListener.wrap((results) -> handleSearchResults(results, netBiosDomainName, domainNameCache, listener),
|
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||||
|
(results) -> handleSearchResults(results, netBiosDomainName,
|
||||||
|
domainNameCache, listener),
|
||||||
(e) -> {
|
(e) -> {
|
||||||
IOUtils.closeWhileHandlingException(connection);
|
IOUtils.closeWhileHandlingException(connection);
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
@ -324,9 +345,12 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleSearchResults(List<SearchResultEntry> results, String netBiosDomainName, Cache<String, String> domainNameCache,
|
static void handleSearchResults(List<SearchResultEntry> results, String netBiosDomainName,
|
||||||
|
Cache<String, String> domainNameCache,
|
||||||
ActionListener<String> listener) {
|
ActionListener<String> listener) {
|
||||||
Optional<SearchResultEntry> entry = results.stream().filter((r) -> r.hasAttribute("ncname")).findFirst();
|
Optional<SearchResultEntry> entry = results.stream()
|
||||||
|
.filter((r) -> r.hasAttribute("ncname"))
|
||||||
|
.findFirst();
|
||||||
if (entry.isPresent()) {
|
if (entry.isPresent()) {
|
||||||
final String value = entry.get().getAttributeValue("ncname");
|
final String value = entry.get().getAttributeValue("ncname");
|
||||||
try {
|
try {
|
||||||
@ -353,8 +377,9 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
|
|
||||||
private static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))";
|
private static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))";
|
||||||
|
|
||||||
UpnADAuthenticator(Settings settings, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
UpnADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
|
||||||
super(settings, timeout, logger, groupsResolver, domainDN);
|
Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||||
|
super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN);
|
||||||
}
|
}
|
||||||
|
|
||||||
void searchForDN(LDAPConnection connection, String username, SecuredString password, int timeLimitSeconds,
|
void searchForDN(LDAPConnection connection, String username, SecuredString password, int timeLimitSeconds,
|
||||||
@ -366,7 +391,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||||||
final String domainDN = buildDnFromDomain(domainName);
|
final String domainDN = buildDnFromDomain(domainName);
|
||||||
try {
|
try {
|
||||||
Filter filter = createFilter(UPN_USER_FILTER, accountName, username);
|
Filter filter = createFilter(UPN_USER_FILTER, accountName, username);
|
||||||
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds, listener,
|
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||||
|
timeLimitSeconds, ignoreReferralErrors, listener,
|
||||||
attributesToSearchFor(groupsResolver.attributes()));
|
attributesToSearchFor(groupsResolver.attributes()));
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
|
@ -16,7 +16,6 @@ import com.unboundid.ldap.sdk.ServerSet;
|
|||||||
import com.unboundid.ldap.sdk.SimpleBindRequest;
|
import com.unboundid.ldap.sdk.SimpleBindRequest;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.lucene.util.IOUtils;
|
import org.apache.lucene.util.IOUtils;
|
||||||
import org.elasticsearch.SpecialPermission;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
@ -294,7 +293,8 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||||||
|
|
||||||
private void findUser(String user, LDAPInterface ldapInterface, ActionListener<SearchResultEntry> listener) {
|
private void findUser(String user, LDAPInterface ldapInterface, ActionListener<SearchResultEntry> listener) {
|
||||||
searchForEntry(ldapInterface, userSearchBaseDn, scope.scope(),
|
searchForEntry(ldapInterface, userSearchBaseDn, scope.scope(),
|
||||||
createEqualityFilter(userAttribute, encodeValue(user)), Math.toIntExact(timeout.seconds()), listener,
|
createEqualityFilter(userAttribute, encodeValue(user)),
|
||||||
|
Math.toIntExact(timeout.seconds()), ignoreReferralErrors, listener,
|
||||||
attributesToSearchFor(groupResolver.attributes()));
|
attributesToSearchFor(groupResolver.attributes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import org.elasticsearch.common.settings.Settings;
|
|||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
||||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver;
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver;
|
||||||
|
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -33,29 +34,34 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJE
|
|||||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.createFilter;
|
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.createFilter;
|
||||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.search;
|
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.search;
|
||||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
||||||
|
import static org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.IGNORE_REFERRAL_ERRORS_SETTING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the groups for a user by executing a search with a filter usually that contains a group object class with a attribute that
|
* Resolves the groups for a user by executing a search with a filter usually that contains a group
|
||||||
* matches an ID of the user
|
* object class with a attribute that matches an ID of the user
|
||||||
*/
|
*/
|
||||||
class SearchGroupsResolver implements GroupsResolver {
|
class SearchGroupsResolver implements GroupsResolver {
|
||||||
|
|
||||||
private static final String GROUP_SEARCH_DEFAULT_FILTER = "(&" +
|
private static final String GROUP_SEARCH_DEFAULT_FILTER = "(&" +
|
||||||
"(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group)(objectclass=posixGroup))" +
|
"(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)" +
|
||||||
|
"(objectclass=group)(objectclass=posixGroup))" +
|
||||||
"(|(uniqueMember={0})(member={0})(memberUid={0})))";
|
"(|(uniqueMember={0})(member={0})(memberUid={0})))";
|
||||||
|
|
||||||
static final Setting<String> BASE_DN = Setting.simpleString("group_search.base_dn", Setting.Property.NodeScope);
|
static final Setting<String> BASE_DN = Setting.simpleString("group_search.base_dn",
|
||||||
|
Setting.Property.NodeScope);
|
||||||
static final Setting<LdapSearchScope> SCOPE = new Setting<>("group_search.scope", (String) null,
|
static final Setting<LdapSearchScope> SCOPE = new Setting<>("group_search.scope", (String) null,
|
||||||
s -> LdapSearchScope.resolve(s, LdapSearchScope.SUB_TREE), Setting.Property.NodeScope);
|
s -> LdapSearchScope.resolve(s, LdapSearchScope.SUB_TREE), Setting.Property.NodeScope);
|
||||||
static final Setting<String> USER_ATTRIBUTE = Setting.simpleString("group_search.user_attribute", Setting.Property.NodeScope);
|
static final Setting<String> USER_ATTRIBUTE = Setting.simpleString(
|
||||||
|
"group_search.user_attribute", Setting.Property.NodeScope);
|
||||||
|
|
||||||
static final Setting<String> FILTER = new Setting<>("group_search.filter", GROUP_SEARCH_DEFAULT_FILTER,
|
static final Setting<String> FILTER = new Setting<>("group_search.filter",
|
||||||
Function.identity(), Setting.Property.NodeScope);
|
GROUP_SEARCH_DEFAULT_FILTER, Function.identity(), Setting.Property.NodeScope);
|
||||||
|
|
||||||
private final String baseDn;
|
private final String baseDn;
|
||||||
private final String filter;
|
private final String filter;
|
||||||
private final String userAttribute;
|
private final String userAttribute;
|
||||||
private final LdapSearchScope scope;
|
private final LdapSearchScope scope;
|
||||||
|
private final boolean ignoreReferralErrors;
|
||||||
|
|
||||||
SearchGroupsResolver(Settings settings) {
|
SearchGroupsResolver(Settings settings) {
|
||||||
if (BASE_DN.exists(settings)) {
|
if (BASE_DN.exists(settings)) {
|
||||||
@ -66,6 +72,7 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||||||
filter = FILTER.get(settings);
|
filter = FILTER.get(settings);
|
||||||
userAttribute = USER_ATTRIBUTE.get(settings);
|
userAttribute = USER_ATTRIBUTE.get(settings);
|
||||||
scope = SCOPE.get(settings);
|
scope = SCOPE.get(settings);
|
||||||
|
this.ignoreReferralErrors = IGNORE_REFERRAL_ERRORS_SETTING.get(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,9 +84,14 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
Filter userFilter = createFilter(filter, userId);
|
Filter userFilter = createFilter(filter, userId);
|
||||||
search(connection, baseDn, scope.scope(), userFilter, Math.toIntExact(timeout.seconds()),
|
search(connection, baseDn, scope.scope(), userFilter,
|
||||||
|
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||||
ActionListener.wrap(
|
ActionListener.wrap(
|
||||||
(results) -> listener.onResponse(results.stream().map((r) -> r.getDN()).collect(Collectors.toList())),
|
(results) -> listener.onResponse(results
|
||||||
|
.stream()
|
||||||
|
.map((r) -> r.getDN())
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
),
|
||||||
listener::onFailure),
|
listener::onFailure),
|
||||||
SearchRequest.NO_ATTRIBUTES);
|
SearchRequest.NO_ATTRIBUTES);
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
@ -96,12 +108,13 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getUserId(String dn, Collection<Attribute> attributes, LDAPInterface connection, TimeValue timeout,
|
private void getUserId(String dn, Collection<Attribute> attributes, LDAPInterface connection,
|
||||||
ActionListener<String> listener) {
|
TimeValue timeout, ActionListener<String> listener) {
|
||||||
if (isNullOrEmpty(userAttribute)) {
|
if (isNullOrEmpty(userAttribute)) {
|
||||||
listener.onResponse(dn);
|
listener.onResponse(dn);
|
||||||
} else if (attributes != null) {
|
} else if (attributes != null) {
|
||||||
final String value = attributes.stream().filter((attribute) -> attribute.getName().equals(userAttribute))
|
final String value = attributes.stream()
|
||||||
|
.filter((attribute) -> attribute.getName().equals(userAttribute))
|
||||||
.map(Attribute::getValue)
|
.map(Attribute::getValue)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
@ -111,8 +124,10 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ActionListener<String> listener) {
|
void readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout,
|
||||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, Math.toIntExact(timeout.seconds()),
|
ActionListener<String> listener) {
|
||||||
|
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER,
|
||||||
|
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||||
ActionListener.wrap((entry) -> {
|
ActionListener.wrap((entry) -> {
|
||||||
if (entry == null || entry.hasAttribute(userAttribute) == false) {
|
if (entry == null || entry.hasAttribute(userAttribute) == false) {
|
||||||
listener.onResponse(null);
|
listener.onResponse(null);
|
||||||
|
@ -26,6 +26,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER;
|
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER;
|
||||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
||||||
|
import static org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.IGNORE_REFERRAL_ERRORS_SETTING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the groups of a user based on the value of a attribute of the user's ldap entry
|
* Resolves the groups of a user based on the value of a attribute of the user's ldap entry
|
||||||
@ -35,13 +36,15 @@ class UserAttributeGroupsResolver implements GroupsResolver {
|
|||||||
private static final Setting<String> ATTRIBUTE = new Setting<>("user_group_attribute", "memberOf",
|
private static final Setting<String> ATTRIBUTE = new Setting<>("user_group_attribute", "memberOf",
|
||||||
Function.identity(), Setting.Property.NodeScope);
|
Function.identity(), Setting.Property.NodeScope);
|
||||||
private final String attribute;
|
private final String attribute;
|
||||||
|
private final boolean ignoreReferralErrors;
|
||||||
|
|
||||||
UserAttributeGroupsResolver(Settings settings) {
|
UserAttributeGroupsResolver(Settings settings) {
|
||||||
this(ATTRIBUTE.get(settings));
|
this(ATTRIBUTE.get(settings), IGNORE_REFERRAL_ERRORS_SETTING.get(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserAttributeGroupsResolver(String attribute) {
|
private UserAttributeGroupsResolver(String attribute, boolean ignoreReferralErrors) {
|
||||||
this.attribute = Objects.requireNonNull(attribute);
|
this.attribute = Objects.requireNonNull(attribute);
|
||||||
|
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -53,7 +56,7 @@ class UserAttributeGroupsResolver implements GroupsResolver {
|
|||||||
listener.onResponse(Collections.unmodifiableList(list));
|
listener.onResponse(Collections.unmodifiableList(list));
|
||||||
} else {
|
} else {
|
||||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, Math.toIntExact(timeout.seconds()),
|
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, Math.toIntExact(timeout.seconds()),
|
||||||
ActionListener.wrap((entry) -> {
|
ignoreReferralErrors, ActionListener.wrap((entry) -> {
|
||||||
if (entry == null || entry.hasAttribute(attribute) == false) {
|
if (entry == null || entry.hasAttribute(attribute) == false) {
|
||||||
listener.onResponse(Collections.emptyList());
|
listener.onResponse(Collections.emptyList());
|
||||||
} else {
|
} else {
|
||||||
|
@ -44,12 +44,14 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public final class LdapUtils {
|
public final class LdapUtils {
|
||||||
|
|
||||||
public static final Filter OBJECT_CLASS_PRESENCE_FILTER = Filter.createPresenceFilter("objectClass");
|
public static final Filter OBJECT_CLASS_PRESENCE_FILTER =
|
||||||
|
Filter.createPresenceFilter("objectClass");
|
||||||
|
|
||||||
|
private static final Logger LOGGER = ESLoggerFactory.getLogger(LdapUtils.class);
|
||||||
|
|
||||||
private LdapUtils() {
|
private LdapUtils() {
|
||||||
}
|
}
|
||||||
@ -62,7 +64,8 @@ public final class LdapUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T privilegedConnect(CheckedSupplier<T, LDAPException> supplier) throws LDAPException {
|
public static <T> T privilegedConnect(CheckedSupplier<T, LDAPException> supplier)
|
||||||
|
throws LDAPException {
|
||||||
SpecialPermission.check();
|
SpecialPermission.check();
|
||||||
try {
|
try {
|
||||||
return AccessController.doPrivileged((PrivilegedExceptionAction<T>) supplier::get);
|
return AccessController.doPrivileged((PrivilegedExceptionAction<T>) supplier::get);
|
||||||
@ -83,27 +86,40 @@ public final class LdapUtils {
|
|||||||
/**
|
/**
|
||||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||||
*/
|
*/
|
||||||
public static void searchForEntry(LDAPInterface ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
public static void searchForEntry(LDAPInterface ldap, String baseDN, SearchScope scope,
|
||||||
ActionListener<SearchResultEntry> listener, String... attributes) {
|
Filter filter, int timeLimitSeconds,
|
||||||
|
boolean ignoreReferralErrors,
|
||||||
|
ActionListener<SearchResultEntry> listener,
|
||||||
|
String... attributes) {
|
||||||
if (ldap instanceof LDAPConnection) {
|
if (ldap instanceof LDAPConnection) {
|
||||||
searchForEntry((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
searchForEntry((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||||
|
ignoreReferralErrors, listener, attributes);
|
||||||
} else if (ldap instanceof LDAPConnectionPool) {
|
} else if (ldap instanceof LDAPConnectionPool) {
|
||||||
searchForEntry((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
searchForEntry((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||||
|
ignoreReferralErrors, listener, attributes);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unsupported LDAPInterface implementation: " + ldap);
|
throw new IllegalArgumentException("unsupported LDAPInterface implementation: " + ldap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method performs an asynchronous ldap search operation that only expects at most one result. If more than one result is found
|
* This method performs an asynchronous ldap search operation that only expects at most one
|
||||||
* then this is an error. If no results are found, then {@code null} will be returned.
|
* result.
|
||||||
|
* If more than one result is found then this is an error
|
||||||
|
* If no results are found, then {@code null} will be returned.
|
||||||
|
* If the LDAP server returns an error {@link ResultCode} then this is handled as a
|
||||||
|
* {@link ActionListener#onFailure(Exception) failure}
|
||||||
*/
|
*/
|
||||||
public static void searchForEntry(LDAPConnection ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
public static void searchForEntry(LDAPConnection ldap, String baseDN, SearchScope scope,
|
||||||
ActionListener<SearchResultEntry> listener, String... attributes) {
|
Filter filter, int timeLimitSeconds,
|
||||||
LdapSearchResultListener searchResultListener = new SingleEntryListener(ldap, listener, filter);
|
boolean ignoreReferralErrors,
|
||||||
|
ActionListener<SearchResultEntry> listener,
|
||||||
|
String... attributes) {
|
||||||
|
LdapSearchResultListener searchResultListener = new SingleEntryListener(ldap, listener,
|
||||||
|
filter, ignoreReferralErrors);
|
||||||
try {
|
try {
|
||||||
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope, DereferencePolicy.NEVER, 0, timeLimitSeconds,
|
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope,
|
||||||
false, filter, attributes);
|
DereferencePolicy.NEVER, 0, timeLimitSeconds, false, filter, attributes);
|
||||||
searchResultListener.setSearchRequest(request);
|
searchResultListener.setSearchRequest(request);
|
||||||
ldap.asyncSearch(request);
|
ldap.asyncSearch(request);
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
@ -112,25 +128,35 @@ public final class LdapUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method performs an asynchronous ldap search operation that only expects at most one result. If more than one result is found
|
* This method performs an asynchronous ldap search operation that only expects at most one
|
||||||
* then this is an error. If no results are found, then {@code null} will be returned.
|
* result.
|
||||||
|
* If more than one result is found then this is an error.
|
||||||
|
* If no results are found, then {@code null} will be returned.
|
||||||
|
* If the LDAP server returns an error {@link ResultCode} then this is handled as a
|
||||||
|
* {@link ActionListener#onFailure(Exception) failure}
|
||||||
*/
|
*/
|
||||||
public static void searchForEntry(LDAPConnectionPool ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
public static void searchForEntry(LDAPConnectionPool ldap, String baseDN, SearchScope scope,
|
||||||
ActionListener<SearchResultEntry> listener, String... attributes) {
|
Filter filter, int timeLimitSeconds,
|
||||||
|
boolean ignoreReferralErrors,
|
||||||
|
ActionListener<SearchResultEntry> listener,
|
||||||
|
String... attributes) {
|
||||||
boolean searching = false;
|
boolean searching = false;
|
||||||
LDAPConnection ldapConnection = null;
|
LDAPConnection ldapConnection = null;
|
||||||
try {
|
try {
|
||||||
ldapConnection = privilegedConnect(ldap::getConnection);
|
ldapConnection = privilegedConnect(ldap::getConnection);
|
||||||
final LDAPConnection finalConnection = ldapConnection;
|
final LDAPConnection finalConnection = ldapConnection;
|
||||||
searchForEntry(finalConnection, baseDN, scope, filter, timeLimitSeconds, ActionListener.wrap(
|
searchForEntry(finalConnection, baseDN, scope, filter, timeLimitSeconds,
|
||||||
(entry) -> {
|
ignoreReferralErrors, ActionListener.wrap(
|
||||||
IOUtils.close(() -> ldap.releaseConnection(finalConnection));
|
(entry) -> {
|
||||||
listener.onResponse(entry);
|
IOUtils.close(() -> ldap.releaseConnection(finalConnection));
|
||||||
},
|
listener.onResponse(entry);
|
||||||
(e) -> {
|
},
|
||||||
IOUtils.closeWhileHandlingException(() -> ldap.releaseConnection(finalConnection));
|
(e) -> {
|
||||||
listener.onFailure(e);
|
IOUtils.closeWhileHandlingException(
|
||||||
}), attributes);
|
() -> ldap.releaseConnection(finalConnection)
|
||||||
|
);
|
||||||
|
listener.onFailure(e);
|
||||||
|
}), attributes);
|
||||||
searching = true;
|
searching = true;
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
@ -145,12 +171,17 @@ public final class LdapUtils {
|
|||||||
/**
|
/**
|
||||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||||
*/
|
*/
|
||||||
public static void search(LDAPInterface ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
public static void search(LDAPInterface ldap, String baseDN, SearchScope scope,
|
||||||
ActionListener<List<SearchResultEntry>> listener, String... attributes) {
|
Filter filter, int timeLimitSeconds,
|
||||||
|
boolean ignoreReferralErrors,
|
||||||
|
ActionListener<List<SearchResultEntry>> listener,
|
||||||
|
String... attributes) {
|
||||||
if (ldap instanceof LDAPConnection) {
|
if (ldap instanceof LDAPConnection) {
|
||||||
search((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
search((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||||
|
ignoreReferralErrors, listener, attributes);
|
||||||
} else if (ldap instanceof LDAPConnectionPool) {
|
} else if (ldap instanceof LDAPConnectionPool) {
|
||||||
search((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
search((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||||
|
ignoreReferralErrors, listener, attributes);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unsupported LDAPInterface implementation: " + ldap);
|
throw new IllegalArgumentException("unsupported LDAPInterface implementation: " + ldap);
|
||||||
}
|
}
|
||||||
@ -159,14 +190,23 @@ public final class LdapUtils {
|
|||||||
/**
|
/**
|
||||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||||
*/
|
*/
|
||||||
public static void search(LDAPConnection ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
public static void search(LDAPConnection ldap, String baseDN, SearchScope scope,
|
||||||
ActionListener<List<SearchResultEntry>> listener, String... attributes) {
|
Filter filter, int timeLimitSeconds,
|
||||||
LdapSearchResultListener searchResultListener = new LdapSearchResultListener(ldap,
|
boolean ignoreReferralErrors,
|
||||||
(asyncRequestID, searchResult) -> listener.onResponse(Collections.unmodifiableList(searchResult.getSearchEntries())), 1);
|
ActionListener<List<SearchResultEntry>> listener,
|
||||||
|
String... attributes) {
|
||||||
|
LdapSearchResultListener searchResultListener = new LdapSearchResultListener(
|
||||||
|
ldap,
|
||||||
|
ignoreReferralErrors,
|
||||||
|
ActionListener.wrap(
|
||||||
|
searchResult -> listener.onResponse(
|
||||||
|
Collections.unmodifiableList(searchResult.getSearchEntries())
|
||||||
|
),
|
||||||
|
listener::onFailure),
|
||||||
|
1);
|
||||||
try {
|
try {
|
||||||
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope, DereferencePolicy.NEVER, 0, timeLimitSeconds,
|
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope,
|
||||||
false, filter, attributes);
|
DereferencePolicy.NEVER, 0, timeLimitSeconds, false, filter, attributes);
|
||||||
searchResultListener.setSearchRequest(request);
|
searchResultListener.setSearchRequest(request);
|
||||||
ldap.asyncSearch(request);
|
ldap.asyncSearch(request);
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
@ -177,20 +217,35 @@ public final class LdapUtils {
|
|||||||
/**
|
/**
|
||||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||||
*/
|
*/
|
||||||
public static void search(LDAPConnectionPool ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
public static void search(LDAPConnectionPool ldap, String baseDN, SearchScope scope,
|
||||||
ActionListener<List<SearchResultEntry>> listener, String... attributes) {
|
Filter filter, int timeLimitSeconds,
|
||||||
|
boolean ignoreReferralErrors,
|
||||||
|
ActionListener<List<SearchResultEntry>> listener,
|
||||||
|
String... attributes) {
|
||||||
boolean searching = false;
|
boolean searching = false;
|
||||||
LDAPConnection ldapConnection = null;
|
LDAPConnection ldapConnection = null;
|
||||||
try {
|
try {
|
||||||
ldapConnection = ldap.getConnection();
|
ldapConnection = ldap.getConnection();
|
||||||
final LDAPConnection finalConnection = ldapConnection;
|
final LDAPConnection finalConnection = ldapConnection;
|
||||||
LdapSearchResultListener ldapSearchResultListener = new LdapSearchResultListener(ldapConnection,
|
LdapSearchResultListener ldapSearchResultListener = new LdapSearchResultListener(
|
||||||
(asyncRequestID, searchResult) -> {
|
ldapConnection, ignoreReferralErrors,
|
||||||
IOUtils.closeWhileHandlingException(() -> ldap.releaseConnection(finalConnection));
|
ActionListener.wrap(
|
||||||
listener.onResponse(Collections.unmodifiableList(searchResult.getSearchEntries()));
|
searchResult -> {
|
||||||
}, 1);
|
IOUtils.closeWhileHandlingException(
|
||||||
SearchRequest request = new SearchRequest(ldapSearchResultListener, baseDN, scope, DereferencePolicy.NEVER, 0, timeLimitSeconds,
|
() -> ldap.releaseConnection(finalConnection)
|
||||||
false, filter, attributes);
|
);
|
||||||
|
listener.onResponse(Collections.unmodifiableList(
|
||||||
|
searchResult.getSearchEntries()
|
||||||
|
));
|
||||||
|
}, (e) -> {
|
||||||
|
IOUtils.closeWhileHandlingException(
|
||||||
|
() -> ldap.releaseConnection(finalConnection)
|
||||||
|
);
|
||||||
|
listener.onFailure(e);
|
||||||
|
}),
|
||||||
|
1);
|
||||||
|
SearchRequest request = new SearchRequest(ldapSearchResultListener, baseDN, scope,
|
||||||
|
DereferencePolicy.NEVER, 0, timeLimitSeconds, false, filter, attributes);
|
||||||
ldapSearchResultListener.setSearchRequest(request);
|
ldapSearchResultListener.setSearchRequest(request);
|
||||||
finalConnection.asyncSearch(request);
|
finalConnection.asyncSearch(request);
|
||||||
searching = true;
|
searching = true;
|
||||||
@ -204,9 +259,51 @@ public final class LdapUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Filter createFilter(String filterTemplate, String... arguments) throws LDAPException {
|
/**
|
||||||
return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT).format((Object[]) encodeFilterValues(arguments),
|
* Returns <code>true</code> if the provide {@link SearchResult} was successfully completed
|
||||||
new StringBuffer(), null).toString());
|
* by the server.
|
||||||
|
* <strong>Note:</strong> Referrals are <em>not</em> considered a successful response for the
|
||||||
|
* purposes of this method.
|
||||||
|
*/
|
||||||
|
private static boolean isSuccess(SearchResult searchResult) {
|
||||||
|
switch (searchResult.getResultCode().intValue()) {
|
||||||
|
case ResultCode.SUCCESS_INT_VALUE:
|
||||||
|
case ResultCode.COMPARE_FALSE_INT_VALUE:
|
||||||
|
case ResultCode.COMPARE_TRUE_INT_VALUE:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SearchResult emptyResult(SearchResult parentResult) {
|
||||||
|
return new SearchResult(
|
||||||
|
parentResult.getMessageID(),
|
||||||
|
ResultCode.SUCCESS,
|
||||||
|
"Empty result",
|
||||||
|
parentResult.getMatchedDN(),
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LDAPException toException(SearchResult searchResult) {
|
||||||
|
return new LDAPException(
|
||||||
|
searchResult.getResultCode(),
|
||||||
|
searchResult.getDiagnosticMessage(),
|
||||||
|
searchResult.getMatchedDN(),
|
||||||
|
searchResult.getReferralURLs(),
|
||||||
|
searchResult.getResponseControls()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Filter createFilter(String filterTemplate, String... arguments)
|
||||||
|
throws LDAPException {
|
||||||
|
return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT)
|
||||||
|
.format((Object[]) encodeFilterValues(arguments), new StringBuffer(), null)
|
||||||
|
.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] attributesToSearchFor(String[] attributes) {
|
public static String[] attributesToSearchFor(String[] attributes) {
|
||||||
@ -222,35 +319,41 @@ public final class LdapUtils {
|
|||||||
|
|
||||||
private static class SingleEntryListener extends LdapSearchResultListener {
|
private static class SingleEntryListener extends LdapSearchResultListener {
|
||||||
|
|
||||||
SingleEntryListener(LDAPConnection ldapConnection, ActionListener<SearchResultEntry> listener, Filter filter) {
|
SingleEntryListener(LDAPConnection ldapConnection,
|
||||||
super(ldapConnection, ((asyncRequestID, searchResult) -> {
|
ActionListener<SearchResultEntry> listener, Filter filter,
|
||||||
final List<SearchResultEntry> entryList = searchResult.getSearchEntries();
|
boolean ignoreReferralErrors) {
|
||||||
if (entryList.size() > 1) {
|
super(ldapConnection, ignoreReferralErrors, ActionListener.wrap(searchResult -> {
|
||||||
listener.onFailure(Exceptions.authenticationError("multiple search results found for [{}]", filter));
|
final List<SearchResultEntry> entryList = searchResult.getSearchEntries();
|
||||||
} else if (entryList.size() == 1) {
|
if (entryList.size() > 1) {
|
||||||
listener.onResponse(entryList.get(0));
|
listener.onFailure(Exceptions.authenticationError(
|
||||||
} else {
|
"multiple search results found for [{}]", filter));
|
||||||
listener.onResponse(null);
|
} else if (entryList.size() == 1) {
|
||||||
}
|
listener.onResponse(entryList.get(0));
|
||||||
}), 1);
|
} else {
|
||||||
|
listener.onResponse(null);
|
||||||
|
}
|
||||||
|
}, listener::onFailure)
|
||||||
|
, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LdapSearchResultListener implements AsyncSearchResultListener {
|
private static class LdapSearchResultListener implements AsyncSearchResultListener {
|
||||||
private static final Logger LOGGER = ESLoggerFactory.getLogger(LdapUtils.class);
|
|
||||||
|
|
||||||
private final List<SearchResultEntry> entryList = new ArrayList<>();
|
private final List<SearchResultEntry> entryList = new ArrayList<>();
|
||||||
private final List<SearchResultReference> referenceList = new ArrayList<>();
|
private final List<SearchResultReference> referenceList = new ArrayList<>();
|
||||||
protected final SetOnce<SearchRequest> searchRequestRef = new SetOnce<>();
|
protected final SetOnce<SearchRequest> searchRequestRef = new SetOnce<>();
|
||||||
private final BiConsumer<AsyncRequestID, SearchResult> consumer;
|
|
||||||
private final LDAPConnection ldapConnection;
|
private final LDAPConnection ldapConnection;
|
||||||
|
private final boolean ignoreReferralErrors;
|
||||||
|
private final ActionListener<SearchResult> listener;
|
||||||
private final int depth;
|
private final int depth;
|
||||||
|
|
||||||
LdapSearchResultListener(LDAPConnection ldapConnection, BiConsumer<AsyncRequestID, SearchResult> consumer, int depth) {
|
LdapSearchResultListener(LDAPConnection ldapConnection, boolean ignoreReferralErrors,
|
||||||
|
ActionListener<SearchResult> listener, int depth) {
|
||||||
this.ldapConnection = ldapConnection;
|
this.ldapConnection = ldapConnection;
|
||||||
this.consumer = consumer;
|
this.listener = listener;
|
||||||
this.depth = depth;
|
this.depth = depth;
|
||||||
|
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -265,72 +368,97 @@ public final class LdapUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void searchResultReceived(AsyncRequestID requestID, SearchResult searchResult) {
|
public void searchResultReceived(AsyncRequestID requestID, SearchResult searchResult) {
|
||||||
// whenever we get a search result we need to check for a referral. A referral is a mechanism for an LDAP server to reference
|
// Whenever we get a search result we need to check for a referral.
|
||||||
// an object stored in a different LDAP server/partition. There are cases where we need to follow a referral in order to get
|
// A referral is a mechanism for an LDAP server to reference an object stored in a
|
||||||
// the actual object we are searching for
|
// different LDAP server/partition. There are cases where we need to follow a referral
|
||||||
|
// in order to get the actual object we are searching for
|
||||||
final String[] referralUrls = referenceList.stream()
|
final String[] referralUrls = referenceList.stream()
|
||||||
.flatMap((ref) -> Arrays.stream(ref.getReferralURLs()))
|
.flatMap((ref) -> Arrays.stream(ref.getReferralURLs()))
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
.toArray(Strings.EMPTY_ARRAY);
|
.toArray(Strings.EMPTY_ARRAY);
|
||||||
final SearchRequest searchRequest = searchRequestRef.get();
|
final SearchRequest request = searchRequestRef.get();
|
||||||
if (referralUrls.length == 0 || searchRequest.followReferrals(ldapConnection) == false) {
|
if (referralUrls.length == 0 || request.followReferrals(ldapConnection) == false) {
|
||||||
// either no referrals to follow or we have explicitly disabled referral following on the connection so we just create
|
// either no referrals to follow or we have explicitly disabled referral following
|
||||||
// a new search result that has the values we've collected. The search result passed to this method will not have of the
|
// on the connection so we just create a new search result that has the values we've
|
||||||
// entries as we are using a result listener and the results are not being collected by the LDAP library
|
// collected. The search result passed to this method will not have of the entries
|
||||||
LOGGER.trace("LDAP Search {} => {} ({})", searchRequest, searchResult, entryList);
|
// as we are using a result listener and the results are not being collected by the
|
||||||
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(), searchResult.getResultCode(), searchResult
|
// LDAP library
|
||||||
.getDiagnosticMessage(), searchResult.getMatchedDN(), referralUrls, entryList, referenceList, entryList.size(),
|
LOGGER.trace("LDAP Search {} => {} ({})", request, searchResult, entryList);
|
||||||
referenceList.size(), searchResult.getResponseControls());
|
if (isSuccess(searchResult)) {
|
||||||
consumer.accept(requestID, resultWithValues);
|
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(),
|
||||||
|
searchResult.getResultCode(), searchResult.getDiagnosticMessage(),
|
||||||
|
searchResult.getMatchedDN(), referralUrls, entryList, referenceList,
|
||||||
|
entryList.size(), referenceList.size(),
|
||||||
|
searchResult.getResponseControls());
|
||||||
|
listener.onResponse(resultWithValues);
|
||||||
|
} else {
|
||||||
|
listener.onFailure(toException(searchResult));
|
||||||
|
}
|
||||||
} else if (depth >= ldapConnection.getConnectionOptions().getReferralHopLimit()) {
|
} else if (depth >= ldapConnection.getConnectionOptions().getReferralHopLimit()) {
|
||||||
// we've gone through too many levels of referrals so we terminate with the values collected so far and the proper result
|
// we've gone through too many levels of referrals so we terminate with the values
|
||||||
// code to indicate the search was terminated early
|
// collected so far and the proper result code to indicate the search was
|
||||||
LOGGER.trace("Referral limit exceeded {} => {} ({})", searchRequest, searchResult, entryList);
|
// terminated early
|
||||||
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(), ResultCode.REFERRAL_LIMIT_EXCEEDED,
|
LOGGER.trace("Referral limit exceeded {} => {} ({})",
|
||||||
searchResult.getDiagnosticMessage(), searchResult.getMatchedDN(), referralUrls, entryList, referenceList,
|
request, searchResult, entryList);
|
||||||
entryList.size(), referenceList.size(), searchResult.getResponseControls());
|
listener.onFailure(new LDAPException(ResultCode.REFERRAL_LIMIT_EXCEEDED,
|
||||||
consumer.accept(requestID, resultWithValues);
|
"Referral limit exceeded (" + depth + ")",
|
||||||
|
searchResult.getMatchedDN(), referralUrls,
|
||||||
|
searchResult.getResponseControls()));
|
||||||
} else {
|
} else {
|
||||||
if (LOGGER.isTraceEnabled()) {
|
if (LOGGER.isTraceEnabled()) {
|
||||||
LOGGER.trace("LDAP referred elsewhere {} => {}", searchRequest, Arrays.toString(referralUrls));
|
LOGGER.trace("LDAP referred elsewhere {} => {}",
|
||||||
|
request, Arrays.toString(referralUrls));
|
||||||
}
|
}
|
||||||
// there are referrals to follow, so we start the process to follow the referrals
|
// there are referrals to follow, so we start the process to follow the referrals
|
||||||
final CountDown countDown = new CountDown(referralUrls.length);
|
final CountDown countDown = new CountDown(referralUrls.length);
|
||||||
final List<String> referralUrlsList = new ArrayList<>(Arrays.asList(referralUrls));
|
final List<String> referralUrlsList = new ArrayList<>(Arrays.asList(referralUrls));
|
||||||
|
|
||||||
BiConsumer<AsyncRequestID, SearchResult> referralConsumer = (reqID, innerResult) -> {
|
ActionListener<SearchResult> referralListener = ActionListener.wrap(
|
||||||
// synchronize here since we are possibly sending out a lot of requests and the result lists are not thread safe and
|
innerResult -> {
|
||||||
// this also provides us with a consistent view
|
// synchronize here since we are possibly sending out a lot of requests
|
||||||
synchronized (this) {
|
// and the result lists are not thread safe and this also provides us
|
||||||
if (innerResult.getSearchEntries() != null) {
|
// with a consistent view
|
||||||
entryList.addAll(innerResult.getSearchEntries());
|
synchronized (this) {
|
||||||
}
|
if (innerResult.getSearchEntries() != null) {
|
||||||
if (innerResult.getSearchReferences() != null) {
|
entryList.addAll(innerResult.getSearchEntries());
|
||||||
referenceList.addAll(innerResult.getSearchReferences());
|
}
|
||||||
}
|
if (innerResult.getSearchReferences() != null) {
|
||||||
}
|
referenceList.addAll(innerResult.getSearchReferences());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// count down and once all referrals have been traversed then we can create the results
|
// count down and once all referrals have been traversed then we can
|
||||||
if (countDown.countDown()) {
|
// create the results
|
||||||
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(), searchResult.getResultCode(),
|
if (countDown.countDown()) {
|
||||||
searchResult.getDiagnosticMessage(), searchResult.getMatchedDN(),
|
SearchResult resultWithValues = new SearchResult(
|
||||||
referralUrlsList.toArray(Strings.EMPTY_ARRAY), entryList, referenceList,
|
searchResult.getMessageID(), searchResult.getResultCode(),
|
||||||
entryList.size(), referenceList.size(), searchResult.getResponseControls());
|
searchResult.getDiagnosticMessage(),
|
||||||
consumer.accept(requestID, resultWithValues);
|
searchResult.getMatchedDN(),
|
||||||
}
|
referralUrlsList.toArray(Strings.EMPTY_ARRAY), entryList,
|
||||||
};
|
referenceList, entryList.size(), referenceList.size(),
|
||||||
|
searchResult.getResponseControls());
|
||||||
|
listener.onResponse(resultWithValues);
|
||||||
|
}
|
||||||
|
}, listener::onFailure);
|
||||||
|
|
||||||
for (String referralUrl : referralUrls) {
|
for (String referralUrl : referralUrls) {
|
||||||
try {
|
try {
|
||||||
// for each referral follow it and any other referrals returned until we get to a depth that is greater than or
|
// for each referral follow it and any other referrals returned until we
|
||||||
// equal to the referral hop limit or all referrals have been followed. Each time referrals are followed from a
|
// get to a depth that is greater than or equal to the referral hop limit
|
||||||
// search result, the depth increases by 1
|
// or all referrals have been followed. Each time referrals are followed
|
||||||
followReferral(ldapConnection, referralUrl, searchRequest, referralConsumer, depth + 1, searchResult, requestID);
|
// from a search result, the depth increases by 1
|
||||||
|
followReferral(ldapConnection, referralUrl, request, referralListener,
|
||||||
|
depth + 1, ignoreReferralErrors, searchResult);
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
LOGGER.warn((Supplier<?>)
|
LOGGER.warn((Supplier<?>) () -> new ParameterizedMessage(
|
||||||
() -> new ParameterizedMessage("caught exception while trying to follow referral [{}]", referralUrl), e);
|
"caught exception while trying to follow referral [{}]",
|
||||||
referralConsumer.accept(requestID, new SearchResult(searchResult.getMessageID(), e.getResultCode(),
|
referralUrl), e);
|
||||||
e.getDiagnosticMessage(), e.getMatchedDN(), e.getReferralURLs(), 0, 0, e.getResponseControls()));
|
if (ignoreReferralErrors) {
|
||||||
|
// Needed in order for the countDown to be correct
|
||||||
|
referralListener.onResponse(emptyResult(searchResult));
|
||||||
|
} else {
|
||||||
|
listener.onFailure(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,68 +470,88 @@ public final class LdapUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the actual connection and following of a referral given a URL string. This referral is being followed as it may contain a
|
* Performs the actual connection and following of a referral given a URL string.
|
||||||
* result that is relevant to our search
|
* This referral is being followed as it may contain a result that is relevant to our search
|
||||||
*/
|
*/
|
||||||
private static void followReferral(LDAPConnection ldapConnection, String urlString, SearchRequest searchRequest,
|
private static void followReferral(LDAPConnection ldapConnection, String urlString,
|
||||||
BiConsumer<AsyncRequestID, SearchResult> consumer, int depth,
|
SearchRequest searchRequest,
|
||||||
SearchResult originatingResult, AsyncRequestID asyncRequestID) throws LDAPException {
|
ActionListener<SearchResult> listener, int depth,
|
||||||
|
boolean ignoreErrors, SearchResult originatingResult)
|
||||||
|
throws LDAPException {
|
||||||
|
|
||||||
final LDAPURL referralURL = new LDAPURL(urlString);
|
final LDAPURL referralURL = new LDAPURL(urlString);
|
||||||
final String host = referralURL.getHost();
|
final String host = referralURL.getHost();
|
||||||
|
|
||||||
// the host must be present in order to follow a referral
|
// the host must be present in order to follow a referral
|
||||||
if (host != null) {
|
if (host == null) {
|
||||||
// the referral URL often contains information necessary about the LDAP request such as the base DN, scope, and filter. If it
|
// nothing to really do since a null host cannot really be handled, so we treat it as
|
||||||
// does not, then we reuse the values from the originating search request
|
// an error
|
||||||
final String requestBaseDN;
|
throw new LDAPException(ResultCode.UNAVAILABLE, "Null referral host in " + urlString);
|
||||||
if (referralURL.baseDNProvided()) {
|
}
|
||||||
requestBaseDN = referralURL.getBaseDN().toString();
|
|
||||||
} else {
|
|
||||||
requestBaseDN = searchRequest.getBaseDN();
|
|
||||||
}
|
|
||||||
|
|
||||||
final SearchScope requestScope;
|
// the referral URL often contains information necessary about the LDAP request such as
|
||||||
if (referralURL.scopeProvided()) {
|
// the base DN, scope, and filter. If it does not, then we reuse the values from the
|
||||||
requestScope = referralURL.getScope();
|
// originating search request
|
||||||
} else {
|
final String requestBaseDN;
|
||||||
requestScope = searchRequest.getScope();
|
if (referralURL.baseDNProvided()) {
|
||||||
}
|
requestBaseDN = referralURL.getBaseDN().toString();
|
||||||
|
|
||||||
final Filter requestFilter;
|
|
||||||
if (referralURL.filterProvided()) {
|
|
||||||
requestFilter = referralURL.getFilter();
|
|
||||||
} else {
|
|
||||||
requestFilter = searchRequest.getFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// in order to follow the referral we need to open a new connection and we do so using the referral connector on the ldap
|
|
||||||
// connection
|
|
||||||
final LDAPConnection referralConn =
|
|
||||||
ldapConnection.getReferralConnector().getReferralConnection(referralURL, ldapConnection);
|
|
||||||
final LdapSearchResultListener listener = new LdapSearchResultListener(referralConn,
|
|
||||||
(reqId, searchResult) -> {
|
|
||||||
IOUtils.closeWhileHandlingException(referralConn);
|
|
||||||
consumer.accept(reqId, searchResult);
|
|
||||||
}, depth);
|
|
||||||
boolean success = false;
|
|
||||||
try {
|
|
||||||
final SearchRequest referralSearchRequest =
|
|
||||||
new SearchRequest(listener, searchRequest.getControls(),
|
|
||||||
requestBaseDN, requestScope, searchRequest.getDereferencePolicy(),
|
|
||||||
searchRequest.getSizeLimit(), searchRequest.getTimeLimitSeconds(), searchRequest.typesOnly(),
|
|
||||||
requestFilter, searchRequest.getAttributes());
|
|
||||||
listener.setSearchRequest(searchRequest);
|
|
||||||
referralConn.asyncSearch(referralSearchRequest);
|
|
||||||
success = true;
|
|
||||||
} finally {
|
|
||||||
if (success == false) {
|
|
||||||
IOUtils.closeWhileHandlingException(referralConn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// nothing to really do since a null host cannot really be handled, so we just return with a response that is empty...
|
requestBaseDN = searchRequest.getBaseDN();
|
||||||
consumer.accept(asyncRequestID, new SearchResult(originatingResult.getMessageID(), ResultCode.UNAVAILABLE,
|
}
|
||||||
null, null, null, Collections.emptyList(), Collections.emptyList(), 0, 0, null));
|
|
||||||
|
final SearchScope requestScope;
|
||||||
|
if (referralURL.scopeProvided()) {
|
||||||
|
requestScope = referralURL.getScope();
|
||||||
|
} else {
|
||||||
|
requestScope = searchRequest.getScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Filter requestFilter;
|
||||||
|
if (referralURL.filterProvided()) {
|
||||||
|
requestFilter = referralURL.getFilter();
|
||||||
|
} else {
|
||||||
|
requestFilter = searchRequest.getFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// in order to follow the referral we need to open a new connection and we do so using the
|
||||||
|
// referral connector on the ldap connection
|
||||||
|
final LDAPConnection referralConn = ldapConnection.getReferralConnector()
|
||||||
|
.getReferralConnection(referralURL, ldapConnection);
|
||||||
|
final LdapSearchResultListener ldapListener = new LdapSearchResultListener(
|
||||||
|
referralConn, ignoreErrors,
|
||||||
|
ActionListener.wrap(
|
||||||
|
searchResult -> {
|
||||||
|
IOUtils.closeWhileHandlingException(referralConn);
|
||||||
|
listener.onResponse(searchResult);
|
||||||
|
},
|
||||||
|
e -> {
|
||||||
|
if (ignoreErrors) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug(new ParameterizedMessage(
|
||||||
|
"Failed to retrieve results from referral URL [{}]." +
|
||||||
|
" Treating as 'no results'",
|
||||||
|
referralURL), e);
|
||||||
|
}
|
||||||
|
listener.onResponse(emptyResult(originatingResult));
|
||||||
|
} else {
|
||||||
|
listener.onFailure(e);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
depth);
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
final SearchRequest referralSearchRequest =
|
||||||
|
new SearchRequest(ldapListener, searchRequest.getControls(),
|
||||||
|
requestBaseDN, requestScope, searchRequest.getDereferencePolicy(),
|
||||||
|
searchRequest.getSizeLimit(), searchRequest.getTimeLimitSeconds(),
|
||||||
|
searchRequest.typesOnly(), requestFilter,
|
||||||
|
searchRequest.getAttributes());
|
||||||
|
ldapListener.setSearchRequest(searchRequest);
|
||||||
|
referralConn.asyncSearch(referralSearchRequest);
|
||||||
|
success = true;
|
||||||
|
} finally {
|
||||||
|
if (success == false) {
|
||||||
|
IOUtils.closeWhileHandlingException(referralConn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,8 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This factory holds settings needed for authenticating to LDAP and creating LdapConnections.
|
* This factory holds settings needed for authenticating to LDAP and creating LdapConnections.
|
||||||
* Each created LdapConnection needs to be closed or else connections will pill up consuming resources.
|
* Each created LdapConnection needs to be closed or else connections will pill up consuming
|
||||||
|
* resources.
|
||||||
* <p>
|
* <p>
|
||||||
* A standard looking usage pattern could look like this:
|
* A standard looking usage pattern could look like this:
|
||||||
* <pre>
|
* <pre>
|
||||||
@ -52,9 +53,14 @@ public abstract class SessionFactory {
|
|||||||
public static final String TIMEOUT_LDAP_SETTING = "timeout.ldap_search";
|
public static final String TIMEOUT_LDAP_SETTING = "timeout.ldap_search";
|
||||||
public static final String HOSTNAME_VERIFICATION_SETTING = "hostname_verification";
|
public static final String HOSTNAME_VERIFICATION_SETTING = "hostname_verification";
|
||||||
public static final String FOLLOW_REFERRALS_SETTING = "follow_referrals";
|
public static final String FOLLOW_REFERRALS_SETTING = "follow_referrals";
|
||||||
|
public static final Setting<Boolean> IGNORE_REFERRAL_ERRORS_SETTING = Setting.boolSetting(
|
||||||
|
"ignore_referral_errors", true, Setting.Property.NodeScope);
|
||||||
|
|
||||||
public static final TimeValue TIMEOUT_DEFAULT = TimeValue.timeValueSeconds(5);
|
public static final TimeValue TIMEOUT_DEFAULT = TimeValue.timeValueSeconds(5);
|
||||||
private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*", Pattern.CASE_INSENSITIVE);
|
private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*",
|
||||||
private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE);
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
protected final Logger logger;
|
protected final Logger logger;
|
||||||
protected final RealmConfig config;
|
protected final RealmConfig config;
|
||||||
@ -63,36 +69,43 @@ public abstract class SessionFactory {
|
|||||||
|
|
||||||
protected final ServerSet serverSet;
|
protected final ServerSet serverSet;
|
||||||
protected final boolean sslUsed;
|
protected final boolean sslUsed;
|
||||||
|
protected final boolean ignoreReferralErrors;
|
||||||
|
|
||||||
protected SessionFactory(RealmConfig config, SSLService sslService) {
|
protected SessionFactory(RealmConfig config, SSLService sslService) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.logger = config.logger(getClass());
|
this.logger = config.logger(getClass());
|
||||||
TimeValue searchTimeout = config.settings().getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT);
|
final Settings settings = config.settings();
|
||||||
|
TimeValue searchTimeout = settings.getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT);
|
||||||
if (searchTimeout.millis() < 1000L) {
|
if (searchTimeout.millis() < 1000L) {
|
||||||
logger.warn("ldap_search timeout [{}] is less than the minimum supported search timeout of 1s. using 1s",
|
logger.warn("ldap_search timeout [{}] is less than the minimum supported search " +
|
||||||
|
"timeout of 1s. using 1s",
|
||||||
searchTimeout.millis());
|
searchTimeout.millis());
|
||||||
searchTimeout = TimeValue.timeValueSeconds(1L);
|
searchTimeout = TimeValue.timeValueSeconds(1L);
|
||||||
}
|
}
|
||||||
this.timeout = searchTimeout;
|
this.timeout = searchTimeout;
|
||||||
this.sslService = sslService;
|
this.sslService = sslService;
|
||||||
LDAPServers ldapServers = ldapServers(config.settings());
|
LDAPServers ldapServers = ldapServers(settings);
|
||||||
this.serverSet = serverSet(config, sslService, ldapServers);
|
this.serverSet = serverSet(config, sslService, ldapServers);
|
||||||
this.sslUsed = ldapServers.ssl;
|
this.sslUsed = ldapServers.ssl;
|
||||||
|
this.ignoreReferralErrors = IGNORE_REFERRAL_ERRORS_SETTING.get(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates the given user and opens a new connection that bound to it (meaning, all operations
|
* Authenticates the given user and opens a new connection that bound to it (meaning, all
|
||||||
* under the returned connection will be executed on behalf of the authenticated user.
|
* operations under the returned connection will be executed on behalf of the authenticated
|
||||||
|
* user.
|
||||||
*
|
*
|
||||||
* @param user The name of the user to authenticate the connection with.
|
* @param user The name of the user to authenticate the connection with.
|
||||||
* @param password The password of the user
|
* @param password The password of the user
|
||||||
* @param listener the listener to call on a failure or result
|
* @param listener the listener to call on a failure or result
|
||||||
*/
|
*/
|
||||||
public abstract void session(String user, SecuredString password, ActionListener<LdapSession> listener);
|
public abstract void session(String user, SecuredString password,
|
||||||
|
ActionListener<LdapSession> listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a flag to indicate if this session factory supports unauthenticated sessions. This means that a session can
|
* Returns a flag to indicate if this session factory supports unauthenticated sessions.
|
||||||
* be established without providing any credentials in a call to {@link #unauthenticatedSession(String, ActionListener)}
|
* This means that a session can be established without providing any credentials in a call to
|
||||||
|
* {@link #unauthenticatedSession(String, ActionListener)}
|
||||||
*
|
*
|
||||||
* @return true if the factory supports unauthenticated sessions
|
* @return true if the factory supports unauthenticated sessions
|
||||||
*/
|
*/
|
||||||
@ -110,29 +123,43 @@ public abstract class SessionFactory {
|
|||||||
throw new UnsupportedOperationException("unauthenticated sessions are not supported");
|
throw new UnsupportedOperationException("unauthenticated sessions are not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static LDAPConnectionOptions connectionOptions(RealmConfig config, SSLService sslService, Logger logger) {
|
protected static LDAPConnectionOptions connectionOptions(RealmConfig config,
|
||||||
|
SSLService sslService, Logger logger) {
|
||||||
Settings realmSettings = config.settings();
|
Settings realmSettings = config.settings();
|
||||||
LDAPConnectionOptions options = new LDAPConnectionOptions();
|
LDAPConnectionOptions options = new LDAPConnectionOptions();
|
||||||
options.setConnectTimeoutMillis(Math.toIntExact(realmSettings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis()));
|
options.setConnectTimeoutMillis(Math.toIntExact(
|
||||||
|
realmSettings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis()
|
||||||
|
));
|
||||||
options.setFollowReferrals(realmSettings.getAsBoolean(FOLLOW_REFERRALS_SETTING, true));
|
options.setFollowReferrals(realmSettings.getAsBoolean(FOLLOW_REFERRALS_SETTING, true));
|
||||||
options.setResponseTimeoutMillis(realmSettings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis());
|
options.setResponseTimeoutMillis(
|
||||||
|
realmSettings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis()
|
||||||
|
);
|
||||||
options.setAllowConcurrentSocketFactoryUse(true);
|
options.setAllowConcurrentSocketFactoryUse(true);
|
||||||
SSLConfigurationSettings sslConfigurationSettings = SSLConfigurationSettings.withoutPrefix();
|
|
||||||
|
final SSLConfigurationSettings sslConfigurationSettings =
|
||||||
|
SSLConfigurationSettings.withoutPrefix();
|
||||||
final Settings realmSSLSettings = realmSettings.getByPrefix("ssl.");
|
final Settings realmSSLSettings = realmSettings.getByPrefix("ssl.");
|
||||||
final boolean verificationModeExists = sslConfigurationSettings.verificationMode.exists(realmSSLSettings);
|
final boolean verificationModeExists =
|
||||||
final boolean hostnameVerficationExists = realmSettings.get(HOSTNAME_VERIFICATION_SETTING, null) != null;
|
sslConfigurationSettings.verificationMode.exists(realmSSLSettings);
|
||||||
|
final boolean hostnameVerficationExists =
|
||||||
|
realmSettings.get(HOSTNAME_VERIFICATION_SETTING, null) != null;
|
||||||
|
|
||||||
if (verificationModeExists && hostnameVerficationExists) {
|
if (verificationModeExists && hostnameVerficationExists) {
|
||||||
throw new IllegalArgumentException("[" + HOSTNAME_VERIFICATION_SETTING + "] and [" +
|
throw new IllegalArgumentException("[" + HOSTNAME_VERIFICATION_SETTING + "] and [" +
|
||||||
sslConfigurationSettings.verificationMode.getKey() + "] may not be used at the same time");
|
sslConfigurationSettings.verificationMode.getKey() +
|
||||||
|
"] may not be used at the same time");
|
||||||
} else if (verificationModeExists) {
|
} else if (verificationModeExists) {
|
||||||
VerificationMode verificationMode = sslService.getVerificationMode(realmSSLSettings, Settings.EMPTY);
|
VerificationMode verificationMode = sslService.getVerificationMode(realmSSLSettings,
|
||||||
|
Settings.EMPTY);
|
||||||
if (verificationMode == VerificationMode.FULL) {
|
if (verificationMode == VerificationMode.FULL) {
|
||||||
options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true));
|
options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true));
|
||||||
}
|
}
|
||||||
} else if (hostnameVerficationExists) {
|
} else if (hostnameVerficationExists) {
|
||||||
new DeprecationLogger(logger).deprecated("the setting [{}] has been deprecated and will be removed in a future version. use " +
|
new DeprecationLogger(logger).deprecated("the setting [{}] has been deprecated and " +
|
||||||
"[{}] instead", RealmSettings.getFullSettingKey(config, HOSTNAME_VERIFICATION_SETTING),
|
"will be removed in a future version. use [{}] instead",
|
||||||
RealmSettings.getFullSettingKey(config, "ssl." + sslConfigurationSettings.verificationMode.getKey()));
|
RealmSettings.getFullSettingKey(config, HOSTNAME_VERIFICATION_SETTING),
|
||||||
|
RealmSettings.getFullSettingKey(config, "ssl." +
|
||||||
|
sslConfigurationSettings.verificationMode.getKey()));
|
||||||
if (realmSettings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
|
if (realmSettings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
|
||||||
options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true));
|
options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true));
|
||||||
}
|
}
|
||||||
@ -146,7 +173,8 @@ public abstract class SessionFactory {
|
|||||||
// Parse LDAP urls
|
// Parse LDAP urls
|
||||||
String[] ldapUrls = settings.getAsArray(URLS_SETTING, getDefaultLdapUrls(settings));
|
String[] ldapUrls = settings.getAsArray(URLS_SETTING, getDefaultLdapUrls(settings));
|
||||||
if (ldapUrls == null || ldapUrls.length == 0) {
|
if (ldapUrls == null || ldapUrls.length == 0) {
|
||||||
throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]");
|
throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING +
|
||||||
|
"]");
|
||||||
}
|
}
|
||||||
return new LDAPServers(ldapUrls);
|
return new LDAPServers(ldapUrls);
|
||||||
}
|
}
|
||||||
@ -155,7 +183,8 @@ public abstract class SessionFactory {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerSet serverSet(RealmConfig realmConfig, SSLService clientSSLService, LDAPServers ldapServers) {
|
private ServerSet serverSet(RealmConfig realmConfig, SSLService clientSSLService,
|
||||||
|
LDAPServers ldapServers) {
|
||||||
Settings settings = realmConfig.settings();
|
Settings settings = realmConfig.settings();
|
||||||
SocketFactory socketFactory = null;
|
SocketFactory socketFactory = null;
|
||||||
if (ldapServers.ssl()) {
|
if (ldapServers.ssl()) {
|
||||||
@ -166,8 +195,8 @@ public abstract class SessionFactory {
|
|||||||
logger.debug("using encryption for LDAP connections without hostname verification");
|
logger.debug("using encryption for LDAP connections without hostname verification");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return LdapLoadBalancing.serverSet(ldapServers.addresses(), ldapServers.ports(), settings, socketFactory,
|
return LdapLoadBalancing.serverSet(ldapServers.addresses(), ldapServers.ports(), settings,
|
||||||
connectionOptions(realmConfig, sslService, logger));
|
socketFactory, connectionOptions(realmConfig, sslService, logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
// package private to use for testing
|
// package private to use for testing
|
||||||
@ -182,12 +211,19 @@ public abstract class SessionFactory {
|
|||||||
protected static Set<Setting<?>> getSettings() {
|
protected static Set<Setting<?>> getSettings() {
|
||||||
Set<Setting<?>> settings = new HashSet<>();
|
Set<Setting<?>> settings = new HashSet<>();
|
||||||
settings.addAll(LdapLoadBalancing.getSettings());
|
settings.addAll(LdapLoadBalancing.getSettings());
|
||||||
settings.add(Setting.listSetting(URLS_SETTING, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope));
|
settings.add(Setting.listSetting(URLS_SETTING, Collections.emptyList(), Function.identity(),
|
||||||
settings.add(Setting.timeSetting(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope));
|
Setting.Property.NodeScope));
|
||||||
settings.add(Setting.timeSetting(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope));
|
settings.add(Setting.timeSetting(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT,
|
||||||
settings.add(Setting.timeSetting(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope));
|
Setting.Property.NodeScope));
|
||||||
settings.add(Setting.boolSetting(HOSTNAME_VERIFICATION_SETTING, true, Setting.Property.NodeScope));
|
settings.add(Setting.timeSetting(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT,
|
||||||
settings.add(Setting.boolSetting(FOLLOW_REFERRALS_SETTING, true, Setting.Property.NodeScope));
|
Setting.Property.NodeScope));
|
||||||
|
settings.add(Setting.timeSetting(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT,
|
||||||
|
Setting.Property.NodeScope));
|
||||||
|
settings.add(Setting.boolSetting(HOSTNAME_VERIFICATION_SETTING, true,
|
||||||
|
Setting.Property.NodeScope));
|
||||||
|
settings.add(Setting.boolSetting(FOLLOW_REFERRALS_SETTING, true,
|
||||||
|
Setting.Property.NodeScope));
|
||||||
|
settings.add(IGNORE_REFERRAL_ERRORS_SETTING);
|
||||||
settings.addAll(SSLConfigurationSettings.withPrefix("ssl.").getAllSettings());
|
settings.addAll(SSLConfigurationSettings.withPrefix("ssl.").getAllSettings());
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
@ -208,7 +244,8 @@ public abstract class SessionFactory {
|
|||||||
addresses[i] = url.getHost();
|
addresses[i] = url.getHost();
|
||||||
ports[i] = url.getPort();
|
ports[i] = url.getPort();
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
throw new IllegalArgumentException("unable to parse configured LDAP url [" + urls[i] + "]", e);
|
throw new IllegalArgumentException("unable to parse configured LDAP url [" +
|
||||||
|
urls[i] + "]", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,13 +270,16 @@ public abstract class SessionFactory {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean allSecure = Arrays.stream(ldapUrls).allMatch(s -> STARTS_WITH_LDAPS.matcher(s).find());
|
final boolean allSecure = Arrays.stream(ldapUrls)
|
||||||
final boolean allClear = Arrays.stream(ldapUrls).allMatch(s -> STARTS_WITH_LDAP.matcher(s).find());
|
.allMatch(s -> STARTS_WITH_LDAPS.matcher(s).find());
|
||||||
|
final boolean allClear = Arrays.stream(ldapUrls)
|
||||||
|
.allMatch(s -> STARTS_WITH_LDAP.matcher(s).find());
|
||||||
|
|
||||||
if (!allSecure && !allClear) {
|
if (!allSecure && !allClear) {
|
||||||
//No mixing is allowed because we use the same socketfactory
|
//No mixing is allowed because we use the same socketfactory
|
||||||
throw new IllegalArgumentException("configured LDAP protocols are not all equal (ldaps://.. and ldap://..): [" +
|
throw new IllegalArgumentException(
|
||||||
Strings.arrayToCommaDelimitedString(ldapUrls) + "]");
|
"configured LDAP protocols are not all equal (ldaps://.. and ldap://..): ["
|
||||||
|
+ Strings.arrayToCommaDelimitedString(ldapUrls) + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return allSecure;
|
return allSecure;
|
||||||
|
@ -24,15 +24,17 @@ import static org.hamcrest.Matchers.is;
|
|||||||
@Network
|
@Network
|
||||||
public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
||||||
|
|
||||||
private static final String BRUCE_BANNER_DN = "cn=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
private static final String BRUCE_BANNER_DN =
|
||||||
|
"cn=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
|
|
||||||
public void testResolveSubTree() throws Exception {
|
public void testResolveSubTree() throws Exception {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put("scope", LdapSearchScope.SUB_TREE)
|
.put("scope", LdapSearchScope.SUB_TREE)
|
||||||
.build();
|
.build();
|
||||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com");
|
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10),
|
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||||
NoOpLogger.INSTANCE, null);
|
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||||
|
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
containsString("Avengers"),
|
containsString("Avengers"),
|
||||||
containsString("SHIELD"),
|
containsString("SHIELD"),
|
||||||
@ -48,9 +50,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||||||
.put("scope", LdapSearchScope.ONE_LEVEL)
|
.put("scope", LdapSearchScope.ONE_LEVEL)
|
||||||
.put("base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com")
|
.put("base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com")
|
||||||
.build();
|
.build();
|
||||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com");
|
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10),
|
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||||
NoOpLogger.INSTANCE, null);
|
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||||
|
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||||
assertThat(groups, hasItem(containsString("Users")));
|
assertThat(groups, hasItem(containsString("Users")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,9 +62,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||||||
.put("scope", LdapSearchScope.BASE)
|
.put("scope", LdapSearchScope.BASE)
|
||||||
.put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com")
|
.put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com")
|
||||||
.build();
|
.build();
|
||||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com");
|
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10),
|
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||||
NoOpLogger.INSTANCE, null);
|
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||||
|
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||||
assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin")));
|
assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +78,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||||||
};
|
};
|
||||||
final String dn = "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com";
|
final String dn = "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com";
|
||||||
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
||||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future);
|
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn,
|
||||||
|
TimeValue.timeValueSeconds(10), false, future);
|
||||||
Filter query = future.actionGet();
|
Filter query = future.actionGet();
|
||||||
assertValidSidQuery(query, expectedSids);
|
assertValidSidQuery(query, expectedSids);
|
||||||
}
|
}
|
||||||
@ -87,7 +92,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||||||
"S-1-5-21-3510024162-210737641-214529065-1117"}; //Gods group
|
"S-1-5-21-3510024162-210737641-214529065-1117"}; //Gods group
|
||||||
final String dn = "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com";
|
final String dn = "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com";
|
||||||
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
||||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future);
|
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn,
|
||||||
|
TimeValue.timeValueSeconds(10), false, future);
|
||||||
Filter query = future.actionGet();
|
Filter query = future.actionGet();
|
||||||
assertValidSidQuery(query, expectedSids);
|
assertValidSidQuery(query, expectedSids);
|
||||||
}
|
}
|
||||||
@ -105,7 +111,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||||||
|
|
||||||
final String dn = BRUCE_BANNER_DN;
|
final String dn = BRUCE_BANNER_DN;
|
||||||
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
||||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future);
|
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn,
|
||||||
|
TimeValue.timeValueSeconds(10), false, future);
|
||||||
Filter query = future.actionGet();
|
Filter query = future.actionGet();
|
||||||
assertValidSidQuery(query, expectedSids);
|
assertValidSidQuery(query, expectedSids);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package org.elasticsearch.xpack.security.authc.ldap;
|
package org.elasticsearch.xpack.security.authc.ldap;
|
||||||
|
|
||||||
import com.unboundid.ldap.sdk.LDAPException;
|
import com.unboundid.ldap.sdk.LDAPException;
|
||||||
|
import com.unboundid.ldap.sdk.ResultCode;
|
||||||
import org.elasticsearch.action.support.PlainActionFuture;
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
|
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
|
||||||
@ -32,6 +33,8 @@ import static org.hamcrest.Matchers.is;
|
|||||||
@Network
|
@Network
|
||||||
public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryIntegTests {
|
public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryIntegTests {
|
||||||
|
|
||||||
|
private final SecuredString SECURED_PASSWORD = SecuredStringTests.build(PASSWORD);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean enableWarningsCheck() {
|
public boolean enableWarningsCheck() {
|
||||||
return false;
|
return false;
|
||||||
@ -39,11 +42,14 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testAdAuth() throws Exception {
|
public void testAdAuth() throws Exception {
|
||||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings);
|
RealmConfig config = new RealmConfig("ad-test",
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false),
|
||||||
|
globalSettings);
|
||||||
|
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config,
|
||||||
|
sslService);
|
||||||
|
|
||||||
String userName = "ironman";
|
String userName = "ironman";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
containsString("Geniuses"),
|
containsString("Geniuses"),
|
||||||
@ -64,7 +70,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||||
|
|
||||||
String userName = "ades\\ironman";
|
String userName = "ades\\ironman";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
containsString("Geniuses"),
|
containsString("Geniuses"),
|
||||||
@ -91,7 +97,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||||
|
|
||||||
PlainActionFuture<List<String>> groups = new PlainActionFuture<>();
|
PlainActionFuture<List<String>> groups = new PlainActionFuture<>();
|
||||||
session(sessionFactory, "ironman", SecuredStringTests.build(PASSWORD)).groups(groups);
|
session(sessionFactory, "ironman", SECURED_PASSWORD).groups(groups);
|
||||||
LDAPException expected = expectThrows(LDAPException.class, groups::actionGet);
|
LDAPException expected = expectThrows(LDAPException.class, groups::actionGet);
|
||||||
assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting"));
|
assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting"));
|
||||||
}
|
}
|
||||||
@ -102,7 +108,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
|
|
||||||
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
|
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
|
||||||
for(String user: users) {
|
for(String user: users) {
|
||||||
try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||||
assertThat("group avenger test for user "+user, groups(ldap), hasItem(containsString("Avengers")));
|
assertThat("group avenger test for user "+user, groups(ldap), hasItem(containsString("Avengers")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +122,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||||
|
|
||||||
String userName = "hulk";
|
String userName = "hulk";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
|
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
@ -138,7 +144,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||||
|
|
||||||
String userName = "hulk";
|
String userName = "hulk";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
|
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
@ -164,7 +170,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||||
|
|
||||||
String userName = "hulk";
|
String userName = "hulk";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
|
|
||||||
assertThat(groups, hasItem(containsString("Avengers")));
|
assertThat(groups, hasItem(containsString("Avengers")));
|
||||||
@ -180,7 +186,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
|
|
||||||
//Login with the UserPrincipalName
|
//Login with the UserPrincipalName
|
||||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
assertThat(ldap.userDn(), is(userDN));
|
assertThat(ldap.userDn(), is(userDN));
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
@ -198,7 +204,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
|
|
||||||
//login with sAMAccountName
|
//login with sAMAccountName
|
||||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
try (LdapSession ldap = session(sessionFactory, "selvig", SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, "selvig", SECURED_PASSWORD)) {
|
||||||
assertThat(ldap.userDn(), is(userDN));
|
assertThat(ldap.userDn(), is(userDN));
|
||||||
|
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
@ -221,7 +227,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||||
|
|
||||||
//Login with the UserPrincipalName
|
//Login with the UserPrincipalName
|
||||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
containsString("CN=Geniuses"),
|
containsString("CN=Geniuses"),
|
||||||
@ -235,7 +241,13 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
public void testStandardLdapConnection() throws Exception {
|
public void testStandardLdapConnection() throws Exception {
|
||||||
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
Settings settings = LdapTestCase.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE);
|
Settings settings = LdapTestCase.buildLdapSettings(
|
||||||
|
new String[] { AD_LDAP_URL },
|
||||||
|
new String[] { userTemplate },
|
||||||
|
groupSearchBase,
|
||||||
|
LdapSearchScope.SUB_TREE,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
if (useGlobalSSL == false) {
|
if (useGlobalSSL == false) {
|
||||||
settings = Settings.builder()
|
settings = Settings.builder()
|
||||||
.put(settings)
|
.put(settings)
|
||||||
@ -247,7 +259,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||||
|
|
||||||
String user = "Bruce Banner";
|
String user = "Bruce Banner";
|
||||||
try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
|
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
@ -258,6 +270,42 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testHandlingLdapReferralErrors() throws Exception {
|
||||||
|
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
|
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
|
final boolean ignoreReferralErrors = false;
|
||||||
|
Settings settings = LdapTestCase.buildLdapSettings(
|
||||||
|
new String[] { AD_LDAP_URL },
|
||||||
|
new String[] { userTemplate },
|
||||||
|
groupSearchBase,
|
||||||
|
LdapSearchScope.SUB_TREE,
|
||||||
|
null,
|
||||||
|
ignoreReferralErrors);
|
||||||
|
if (useGlobalSSL == false) {
|
||||||
|
settings = Settings.builder()
|
||||||
|
.put(settings)
|
||||||
|
.put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks"))
|
||||||
|
.put("ssl.truststore.password", "changeit")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings);
|
||||||
|
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||||
|
|
||||||
|
String user = "Bruce Banner";
|
||||||
|
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||||
|
final UncategorizedExecutionException exception = expectThrows(
|
||||||
|
UncategorizedExecutionException.class,
|
||||||
|
() -> groups(ldap)
|
||||||
|
);
|
||||||
|
final Throwable cause = exception.getCause();
|
||||||
|
assertThat(cause, instanceOf(ExecutionException.class));
|
||||||
|
assertThat(cause.getCause(), instanceOf(LDAPException.class));
|
||||||
|
final LDAPException ldapException = (LDAPException) cause.getCause();
|
||||||
|
assertThat(ldapException.getResultCode(), is(ResultCode.INVALID_CREDENTIALS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testStandardLdapWithAttributeGroups() throws Exception {
|
public void testStandardLdapWithAttributeGroups() throws Exception {
|
||||||
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
@ -273,7 +321,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||||
|
|
||||||
String user = "Bruce Banner";
|
String user = "Bruce Banner";
|
||||||
try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) {
|
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||||
List<String> groups = groups(ldap);
|
List<String> groups = groups(ldap);
|
||||||
|
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
@ -290,7 +338,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
|
|
||||||
String userName = "ironman";
|
String userName = "ironman";
|
||||||
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
||||||
() -> session(sessionFactory, userName, SecuredStringTests.build(PASSWORD)));
|
() -> session(sessionFactory, userName, SECURED_PASSWORD));
|
||||||
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
||||||
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
||||||
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
||||||
@ -309,7 +357,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||||||
|
|
||||||
String user = "Bruce Banner";
|
String user = "Bruce Banner";
|
||||||
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
||||||
() -> session(sessionFactory, user, SecuredStringTests.build(PASSWORD)));
|
() -> session(sessionFactory, user, SECURED_PASSWORD));
|
||||||
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
||||||
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
||||||
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
||||||
|
@ -21,6 +21,7 @@ import org.elasticsearch.xpack.security.authc.RealmConfig;
|
|||||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
||||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
|
||||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase;
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase;
|
||||||
|
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||||
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
||||||
import org.elasticsearch.xpack.security.authc.support.SecuredStringTests;
|
import org.elasticsearch.xpack.security.authc.support.SecuredStringTests;
|
||||||
import org.elasticsearch.xpack.ssl.SSLService;
|
import org.elasticsearch.xpack.ssl.SSLService;
|
||||||
@ -307,8 +308,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||||||
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put(LdapTestCase.buildLdapSettings(new String[] { ActiveDirectorySessionFactoryTests.AD_LDAP_URL }, Strings.EMPTY_ARRAY,
|
.put(LdapTestCase.buildLdapSettings(
|
||||||
groupSearchBase, LdapSearchScope.SUB_TREE))
|
new String[] { ActiveDirectorySessionFactoryTests.AD_LDAP_URL },
|
||||||
|
Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE, null,
|
||||||
|
true))
|
||||||
.put("user_search.base_dn", userSearchBase)
|
.put("user_search.base_dn", userSearchBase)
|
||||||
.put("bind_dn", "ironman@ad.test.elasticsearch.com")
|
.put("bind_dn", "ironman@ad.test.elasticsearch.com")
|
||||||
.put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD)
|
.put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD)
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.security.authc.ldap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||||
|
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
|
||||||
|
import com.unboundid.ldap.sdk.LDAPException;
|
||||||
|
import com.unboundid.ldap.sdk.LDAPURL;
|
||||||
|
import com.unboundid.ldap.sdk.ResultCode;
|
||||||
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
||||||
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase;
|
||||||
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
public class SearchGroupsResolverInMemoryTests extends LdapTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a client-side timeout in the asynchronous LDAP SDK is treated as a failure, rather
|
||||||
|
* than simply returning no results.
|
||||||
|
*/
|
||||||
|
public void testSearchTimeoutIsFailure() throws Exception {
|
||||||
|
|
||||||
|
ldapServers[0].setProcessingDelayMillis(100);
|
||||||
|
|
||||||
|
final LDAPConnectionOptions options = new LDAPConnectionOptions();
|
||||||
|
options.setConnectTimeoutMillis(500);
|
||||||
|
options.setResponseTimeoutMillis(5);
|
||||||
|
|
||||||
|
final LDAPURL ldapurl = new LDAPURL(ldapUrls()[0]);
|
||||||
|
final LDAPConnection connection = LdapUtils.privilegedConnect(
|
||||||
|
() -> new LDAPConnection(options,
|
||||||
|
ldapurl.getHost(), ldapurl.getPort())
|
||||||
|
);
|
||||||
|
|
||||||
|
final Settings settings = Settings.builder()
|
||||||
|
.put("group_search.base_dn", "ou=groups,o=sevenSeas")
|
||||||
|
.put("group_search.scope", LdapSearchScope.SUB_TREE)
|
||||||
|
.build();
|
||||||
|
final SearchGroupsResolver resolver = new SearchGroupsResolver(settings);
|
||||||
|
final PlainActionFuture<List<String>> future = new PlainActionFuture<>();
|
||||||
|
resolver.resolve(connection,
|
||||||
|
"cn=William Bush,ou=people,o=sevenSeas",
|
||||||
|
TimeValue.timeValueSeconds(30),
|
||||||
|
logger,
|
||||||
|
null, future);
|
||||||
|
|
||||||
|
final ExecutionException exception = expectThrows(ExecutionException.class, future::get);
|
||||||
|
final Throwable cause = exception.getCause();
|
||||||
|
assertThat(cause, instanceOf(LDAPException.class));
|
||||||
|
assertThat(((LDAPException) cause).getResultCode(), is(ResultCode.TIMEOUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,7 +25,6 @@ import org.junit.Before;
|
|||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -92,17 +91,27 @@ public abstract class LdapTestCase extends ESTestCase {
|
|||||||
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope, null);
|
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, LdapSearchScope scope,
|
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate,
|
||||||
|
String groupSearchBase, LdapSearchScope scope,
|
||||||
LdapLoadBalancing serverSetType) {
|
LdapLoadBalancing serverSetType) {
|
||||||
|
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope,
|
||||||
|
serverSetType, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate,
|
||||||
|
String groupSearchBase, LdapSearchScope scope,
|
||||||
|
LdapLoadBalancing serverSetType,
|
||||||
|
boolean ignoreReferralErrors) {
|
||||||
Settings.Builder builder = Settings.builder()
|
Settings.Builder builder = Settings.builder()
|
||||||
.putArray(URLS_SETTING, ldapUrl)
|
.putArray(URLS_SETTING, ldapUrl)
|
||||||
.putArray(USER_DN_TEMPLATES_SETTING_KEY, userTemplate)
|
.putArray(USER_DN_TEMPLATES_SETTING_KEY, userTemplate)
|
||||||
|
.put(SessionFactory.IGNORE_REFERRAL_ERRORS_SETTING.getKey(), ignoreReferralErrors)
|
||||||
.put("group_search.base_dn", groupSearchBase)
|
.put("group_search.base_dn", groupSearchBase)
|
||||||
.put("group_search.scope", scope)
|
.put("group_search.scope", scope)
|
||||||
.put("ssl.verification_mode", VerificationMode.CERTIFICATE);
|
.put("ssl.verification_mode", VerificationMode.CERTIFICATE);
|
||||||
if (serverSetType != null) {
|
if (serverSetType != null) {
|
||||||
builder.put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING,
|
builder.put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." +
|
||||||
serverSetType.toString());
|
LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, serverSetType.toString());
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user