8160768: Add capability to custom resolve host/domain names within the default JNDI LDAP provider
Reviewed-by: alanb, dfuchs, chegar, mchung, vtewari
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/DefaultLdapDnsProvider.java Mon Nov 12 08:33:59 2018 -0800
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.jndi.ldap;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.spi.LdapDnsProvider;
+import javax.naming.ldap.spi.LdapDnsProviderResult;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public class DefaultLdapDnsProvider {
+
+ public Optional<LdapDnsProviderResult> lookupEndpoints(String url,
+ Map<?,?> env)
+ throws NamingException
+ {
+ if (url == null || env == null) {
+ throw new NullPointerException();
+ }
+
+ String domainName;
+ List<String> endpoints = new ArrayList<>();
+ LdapURL ldapUrl = new LdapURL(url);
+ String dn = ldapUrl.getDN();
+ String host = ldapUrl.getHost();
+ int port = ldapUrl.getPort();
+ String[] hostports;
+
+ // handle a URL with no hostport (ldap:/// or ldaps:///)
+ // locate the LDAP service using the URL's distinguished name
+ if (host == null
+ && port == -1
+ && dn != null
+ && (domainName = ServiceLocator.mapDnToDomainName(dn)) != null
+ && (hostports = ServiceLocator.getLdapService(domainName, env)) != null) {
+ // Generate new URLs that include the discovered hostports.
+ // Reuse the original URL scheme.
+ String scheme = ldapUrl.getScheme() + "://";
+ String query = ldapUrl.getQuery();
+ String urlSuffix = ldapUrl.getPath() + (query != null ? query : "");
+ for (String hostPort : hostports) {
+ // the hostports come from the DNS SRV records
+ // we assume the SRV record is scheme aware
+ endpoints.add(scheme + hostPort + urlSuffix);
+ }
+ } else {
+ // we don't have enough information to set the domain name
+ // correctly
+ domainName = "";
+ endpoints.add(url);
+ }
+
+ LdapDnsProviderResult res = new LdapDnsProviderResult(domainName, endpoints);
+ if (res.getEndpoints().size() == 0 && res.getDomainName().isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.of(res);
+ }
+ }
+
+}
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtxFactory.java Mon Nov 12 16:40:25 2018 +0100
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtxFactory.java Mon Nov 12 08:33:59 2018 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -31,6 +31,7 @@
import javax.naming.*;
import javax.naming.directory.*;
+import javax.naming.ldap.spi.LdapDnsProviderResult;
import javax.naming.spi.ObjectFactory;
import javax.naming.spi.InitialContextFactory;
import javax.naming.ldap.Control;
@@ -158,41 +159,73 @@
}
private static DirContext getUsingURL(String url, Hashtable<?,?> env)
- throws NamingException {
- DirContext ctx = null;
- LdapURL ldapUrl = new LdapURL(url);
- String dn = ldapUrl.getDN();
- String host = ldapUrl.getHost();
- int port = ldapUrl.getPort();
- String[] hostports;
- String domainName = null;
+ throws NamingException
+ {
+ try {
+ LdapDnsProviderResult r =
+ LdapDnsProviderService.getInstance().lookupEndpoints(url, env);
+ LdapCtx ctx;
+ NamingException lastException = null;
- // handle a URL with no hostport (ldap:/// or ldaps:///)
- // locate the LDAP service using the URL's distinguished name
- if (host == null &&
- port == -1 &&
- dn != null &&
- (domainName = ServiceLocator.mapDnToDomainName(dn)) != null &&
- (hostports = ServiceLocator.getLdapService(domainName, env))
- != null) {
- // Generate new URLs that include the discovered hostports.
- // Reuse the original URL scheme.
- String scheme = ldapUrl.getScheme() + "://";
- String[] newUrls = new String[hostports.length];
- String query = ldapUrl.getQuery();
- String urlSuffix = ldapUrl.getPath() + (query != null ? query : "");
- for (int i = 0; i < hostports.length; i++) {
- newUrls[i] = scheme + hostports[i] + urlSuffix;
+ /*
+ * Prior to this change we had been assuming that the url.getDN()
+ * should be converted to a domain name via
+ * ServiceLocator.mapDnToDomainName(url.getDN())
+ *
+ * However this is incorrect as we can't assume that the supplied
+ * url.getDN() is the same as the dns domain for the directory
+ * server.
+ *
+ * This means that we depend on the dnsProvider to return both
+ * the list of urls of individual hosts from which we attempt to
+ * create an LdapCtx from *AND* the domain name that they serve
+ *
+ * In order to do this the dnsProvider must return an
+ * {@link LdapDnsProviderResult}.
+ *
+ */
+ for (String u : r.getEndpoints()) {
+ try {
+ ctx = getLdapCtxFromUrl(
+ r.getDomainName(), url, new LdapURL(u), env);
+ return ctx;
+ } catch (NamingException e) {
+ // try the next element
+ lastException = e;
+ }
}
- ctx = getUsingURLs(newUrls, env);
- // Associate the derived domain name with the context
- ((LdapCtx)ctx).setDomainName(domainName);
+
+ if (lastException != null) {
+ throw lastException;
+ }
- } else {
- ctx = new LdapCtx(dn, host, port, env, ldapUrl.useSsl());
- // Record the URL that created the context
- ((LdapCtx)ctx).setProviderUrl(url);
+ // lookupEndpoints returned an LdapDnsProviderResult with an empty
+ // list of endpoints
+ throw new NamingException("Could not resolve a valid ldap host");
+ } catch (NamingException e) {
+ // lookupEndpoints(url, env) may throw a NamingException, which
+ // there is no need to wrap.
+ throw e;
+ } catch (Exception e) {
+ NamingException ex = new NamingException();
+ ex.setRootCause(e);
+ throw ex;
}
+ }
+
+ private static LdapCtx getLdapCtxFromUrl(String domain,
+ String url,
+ LdapURL u,
+ Hashtable<?,?> env)
+ throws NamingException
+ {
+ String dn = u.getDN();
+ String host = u.getHost();
+ int port = u.getPort();
+ LdapCtx ctx = new LdapCtx(dn, host, port, env, u.useSsl());
+ ctx.setDomainName(domain);
+ // Record the URL that created the context
+ ctx.setProviderUrl(url);
return ctx;
}
@@ -202,19 +235,17 @@
* Not pretty, but potentially more informative than returning null.
*/
private static DirContext getUsingURLs(String[] urls, Hashtable<?,?> env)
- throws NamingException {
- NamingException ne = null;
- DirContext ctx = null;
- for (int i = 0; i < urls.length; i++) {
+ throws NamingException
+ {
+ NamingException ex = null;
+ for (String u : urls) {
try {
- return getUsingURL(urls[i], env);
- } catch (AuthenticationException e) {
- throw e;
+ return getUsingURL(u, env);
} catch (NamingException e) {
- ne = e;
+ ex = e;
}
}
- throw ne;
+ throw ex;
}
/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapDnsProviderService.java Mon Nov 12 08:33:59 2018 -0800
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.jndi.ldap;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import javax.naming.NamingException;
+import javax.naming.ldap.spi.LdapDnsProvider;
+import javax.naming.ldap.spi.LdapDnsProviderResult;
+import sun.security.util.SecurityConstants;
+
+/**
+ * The {@code LdapDnsProviderService} is responsible for creating and providing
+ * access to the registered {@code LdapDnsProvider}s. The {@link ServiceLoader}
+ * is used to find and register any implementations of {@link LdapDnsProvider}.
+ */
+final class LdapDnsProviderService {
+
+ private static volatile LdapDnsProviderService service;
+ private static final Object LOCK = new int[0];
+ private final ServiceLoader<LdapDnsProvider> providers;
+
+ /**
+ * Creates a new instance of LdapDnsProviderService
+ */
+ private LdapDnsProviderService() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ providers = ServiceLoader.load(
+ LdapDnsProvider.class,
+ ClassLoader.getSystemClassLoader());
+ } else {
+ final PrivilegedAction<ServiceLoader<LdapDnsProvider>> pa =
+ () -> ServiceLoader.load(
+ LdapDnsProvider.class,
+ ClassLoader.getSystemClassLoader());
+
+ providers = AccessController.doPrivileged(
+ pa,
+ null,
+ new RuntimePermission("ldapDnsProvider"),
+ SecurityConstants.GET_CLASSLOADER_PERMISSION);
+ }
+ }
+
+ /**
+ * Retrieve the singleton static instance of LdapDnsProviderService.
+ */
+ static LdapDnsProviderService getInstance() {
+ if (service != null) return service;
+ synchronized(LOCK) {
+ if (service != null) return service;
+ service = new LdapDnsProviderService();
+ }
+ return service;
+ }
+
+ /**
+ * Retrieve result from the first provider that successfully resolves
+ * the endpoints. If no results are found when calling installed
+ * subclasses of {@code LdapDnsProvider} then this method will fall back
+ * to the {@code DefaultLdapDnsProvider}.
+ *
+ * @throws NamingException if the {@code url} in not valid or an error
+ * occurred while performing the lookup.
+ */
+ LdapDnsProviderResult lookupEndpoints(String url, Hashtable<?,?> env)
+ throws NamingException
+ {
+ Iterator<LdapDnsProvider> iterator = providers.iterator();
+ Hashtable<?, ?> envCopy = new Hashtable<>(env);
+ LdapDnsProviderResult result = null;
+
+ while (result == null && iterator.hasNext()) {
+ result = iterator.next().lookupEndpoints(url, envCopy)
+ .filter(r -> r.getEndpoints().size() > 0)
+ .orElse(null);
+ }
+
+ if (result == null) {
+ return new DefaultLdapDnsProvider().lookupEndpoints(url, env)
+ .orElse(new LdapDnsProviderResult("", List.of()));
+ }
+ return result;
+ }
+
+}
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/ServiceLocator.java Mon Nov 12 16:40:25 2018 +0100
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/ServiceLocator.java Mon Nov 12 08:33:59 2018 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,11 +25,7 @@
package com.sun.jndi.ldap;
-import java.util.Arrays;
-import java.util.Hashtable;
-import java.util.Random;
-import java.util.StringTokenizer;
-import java.util.List;
+import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;
@@ -113,6 +109,23 @@
* @return An ordered list of hostports for the LDAP service or null if
* the service has not been located.
*/
+ static String[] getLdapService(String domainName, Map<?,?> environment) {
+ if (environment instanceof Hashtable) {
+ return getLdapService(domainName, (Hashtable)environment);
+ }
+ return getLdapService(domainName, new Hashtable<>(environment));
+ }
+
+ /**
+ * Locates the LDAP service for a given domain.
+ * Queries DNS for a list of LDAP Service Location Records (SRV) for a
+ * given domain name.
+ *
+ * @param domainName A string domain name.
+ * @param environment The possibly null environment of the context.
+ * @return An ordered list of hostports for the LDAP service or null if
+ * the service has not been located.
+ */
static String[] getLdapService(String domainName, Hashtable<?,?> environment) {
if (domainName == null || domainName.length() == 0) {
--- a/src/java.naming/share/classes/javax/naming/directory/InitialDirContext.java Mon Nov 12 16:40:25 2018 +0100
+++ b/src/java.naming/share/classes/javax/naming/directory/InitialDirContext.java Mon Nov 12 08:33:59 2018 -0800
@@ -27,7 +27,6 @@
package javax.naming.directory;
import java.util.Hashtable;
-import javax.naming.spi.NamingManager;
import javax.naming.*;
/**
@@ -84,6 +83,36 @@
* Environment properties are discussed in the
* {@code javax.naming.InitialContext} class description.
*
+ * <p> If the {@code java.naming.provider.url} property of the supplied
+ * environment consists of a URL (or a list of URLs) using the ldap
+ * protocol the resulting {@link javax.naming.ldap.LdapContext} will use
+ * an LDAP server resolved by the configured {@link
+ * javax.naming.ldap.spi.LdapDnsProvider LdapDnsProviders}:
+ * <ol>
+ * <li>If this is the first {@code InitialDirContext} created with a
+ * {@code java.naming.provider.url} using the ldap protocol then the
+ * {@linkplain java.util.ServiceLoader ServiceLoader} mechanism is
+ * used to locate {@linkplain javax.naming.ldap.spi.LdapDnsProvider
+ * LdapDnsProvider} implementations using the system class loader.
+ * The order that providers are located is implementation specific
+ * and an implementation is free to cache the located providers.
+ * <li>The {@code lookupEndpoints} method of each provider, if instantiated,
+ * is invoked once with a combination of each of the URLs in the the
+ * {@code java.naming.provider.url} property and the environment until
+ * a provider returns non-empty or all providers have been exhausted.
+ * If none of the
+ * {@linkplain javax.naming.ldap.spi.LdapDnsProvider LdapDnsProviders}
+ * return a non-empty
+ * {@linkplain javax.naming.ldap.spi.LdapDnsProviderResult result} then
+ * the implementation will make a best-effort attempt to determine an
+ * endpoint. A
+ * {@linkplain java.util.ServiceConfigurationError ServiceConfigurationError},
+ * {@code Error} or {@code RuntimeException} thrown when loading or
+ * calling an {@linkplain javax.naming.ldap.spi.LdapDnsProvider
+ * LdapDnsProvider}, if encountered, will be propagated to the calling
+ * thread.
+ * </ol>
+ *
* <p> This constructor will not modify {@code environment}
* or save a reference to it, but may save a clone.
* Caller should not modify mutable keys and values in
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.naming/share/classes/javax/naming/ldap/spi/LdapDnsProvider.java Mon Nov 12 08:33:59 2018 -0800
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.naming.ldap.spi;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Service-provider class for DNS lookups when performing LDAP operations.
+ *
+ * <p> An LDAP DNS provider is a concrete subclass of this class that
+ * has a zero-argument constructor. LDAP DNS providers are located using the
+ * ServiceLoader facility, as specified by
+ * {@linkplain javax.naming.directory.InitialDirContext InitialDirectContext}.
+ *
+ * The
+ * {@link java.util.ServiceLoader ServiceLoader} is used to create and register
+ * implementations of {@code LdapDnsProvider}.
+ *
+ * <p> An LDAP DNS provider can be used in environments where the default
+ * DNS resolution mechanism is not sufficient to accurately pinpoint the
+ * correct LDAP servers needed to perform LDAP operations. For example, in an
+ * environment containing a mix of {@code ldap} and {@code ldaps} servers
+ * you may want the {@linkplain javax.naming.ldap.LdapContext LdapContext}
+ * to query {@code ldaps} servers only.
+ *
+ * @since 12
+ */
+public abstract class LdapDnsProvider {
+
+ // The {@code RuntimePermission("ldapDnsProvider")} is
+ // necessary to subclass and instantiate the {@code LdapDnsProvider} class.
+ private static final RuntimePermission DNSPROVIDER_PERMISSION =
+ new RuntimePermission("ldapDnsProvider");
+
+ /**
+ * Creates a new instance of {@code LdapDnsProvider}.
+ *
+ * @throws SecurityException if a security manager is present and its
+ * {@code checkPermission} method doesn't allow
+ * the {@code RuntimePermission("ldapDnsProvider")}.
+ */
+ protected LdapDnsProvider() {
+ this(checkPermission());
+ }
+
+ private LdapDnsProvider(Void unused) {
+ // nothing to do.
+ }
+
+ private static Void checkPermission() {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(DNSPROVIDER_PERMISSION);
+ }
+ return null;
+ }
+
+ /**
+ * Lookup the endpoints and domain name for the given {@link Context}
+ * {@link Context#PROVIDER_URL provider URL} and environment. The resolved
+ * endpoints and domain name are returned as an
+ * {@link LdapDnsProviderResult}.
+ *
+ * <p> An endpoint is a {@code String} representation of an LDAP URL which
+ * points to an LDAP server to be used for LDAP operations. The syntax of
+ * an LDAP URL is defined by <a href="http://www.ietf.org/rfc/rfc2255.txt">
+ * <i>RFC 2255: The LDAP URL Format</i></a>.
+ *
+ * @param url The {@link Context} {@link Context#PROVIDER_URL provider URL}
+ * @param env The {@link Context} environment.
+ *
+ * @return an {@link LdapDnsProviderResult} or empty {@code Optional}
+ * if the lookup fails.
+ *
+ * @throws NamingException if the {@code url} is not valid or an error
+ * occurred while performing the lookup.
+ * @throws NullPointerException if either {@code url} or {@code env} are
+ * {@code null}.
+ */
+ public abstract Optional<LdapDnsProviderResult> lookupEndpoints(
+ String url, Map<?,?> env) throws NamingException;
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.naming/share/classes/javax/naming/ldap/spi/LdapDnsProviderResult.java Mon Nov 12 08:33:59 2018 -0800
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.naming.ldap.spi;
+
+import java.util.List;
+
+/**
+ * The result of a DNS lookup for an LDAP URL.
+ *
+ * <p> This class is used by an {@link LdapDnsProvider} to return the result
+ * of a DNS lookup for a given LDAP URL. The result consists of a domain name
+ * and its associated ldap server endpoints.
+ *
+ * <p> A {@code null} {@code domainName} is equivalent to and represented
+ * by an empty string.
+ *
+ * @since 12
+ */
+public final class LdapDnsProviderResult {
+
+ private final String domainName;
+ private final List<String> endpoints;
+
+ /**
+ * Construct an LdapDnsProviderResult consisting of a resolved domain name
+ * and the ldap server endpoints that serve the domain.
+ *
+ * @param domainName the resolved domain name; can be null.
+ * @param endpoints the possibly empty list of resolved ldap server
+ * endpoints
+ *
+ * @throws NullPointerException if {@code endpoints} contains {@code null}
+ * elements.
+ * @throws ClassCastException if {@code endpoints} contains non-
+ * {@code String} elements.
+ */
+ public LdapDnsProviderResult(String domainName, List<String> endpoints) {
+ this.domainName = (domainName == null) ? "" : domainName;
+ this.endpoints = List.copyOf(endpoints);
+ }
+
+ /**
+ * Returns the domain name resolved from the ldap URL. This method returns
+ * the empty string if the {@code LdapDnsProviderResult} is created with a
+ * null domain name.
+ *
+ * @return the resolved domain name
+ */
+ public String getDomainName() {
+ return domainName;
+ }
+
+ /**
+ * Returns the possibly empty list of individual server endpoints resolved
+ * from the ldap URL.
+ *
+ * @return a possibly empty unmodifiable {@link List} containing the
+ * resolved ldap server endpoints
+ */
+ public List<String> getEndpoints() {
+ return endpoints;
+ }
+
+}
--- a/src/java.naming/share/classes/module-info.java Mon Nov 12 16:40:25 2018 +0100
+++ b/src/java.naming/share/classes/module-info.java Mon Nov 12 08:33:59 2018 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,10 @@
/**
* Defines the Java Naming and Directory Interface (JNDI) API.
*
+ * @provides javax.naming.ldap.spi.LdapDnsProvider
+ *
+ * @uses javax.naming.ldap.spi.LdapDnsProvider
+ *
* @moduleGraph
* @since 9
*/
@@ -37,6 +41,7 @@
exports javax.naming.event;
exports javax.naming.ldap;
exports javax.naming.spi;
+ exports javax.naming.ldap.spi;
exports com.sun.jndi.toolkit.ctx to
jdk.naming.dns;
@@ -46,6 +51,7 @@
uses javax.naming.ldap.StartTlsResponse;
uses javax.naming.spi.InitialContextFactory;
+ uses javax.naming.ldap.spi.LdapDnsProvider;
provides java.security.Provider with
sun.security.provider.certpath.ldap.JdkLDAP;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/LdapDnsProviderTest.java Mon Nov 12 08:33:59 2018 -0800
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.Permission;
+import java.util.Hashtable;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+
+/**
+ * @test
+ * @bug 8160768
+ * @summary ctx provider tests for ldap
+ * @modules java.naming/com.sun.jndi.ldap
+ * @compile dnsprovider/TestDnsProvider.java
+ * @run main/othervm LdapDnsProviderTest
+ * @run main/othervm LdapDnsProviderTest nosm
+ * @run main/othervm LdapDnsProviderTest smnodns
+ * @run main/othervm LdapDnsProviderTest smdns
+ * @run main/othervm LdapDnsProviderTest nosmbaddns
+ */
+
+class DNSSecurityManager extends SecurityManager {
+
+
+
+ /* run main/othervm LdapDnsProviderTest
+
+ * run main/othervm LdapDnsProviderTest nosm
+ * run main/othervm LdapDnsProviderTest smnodns
+ * run main/othervm LdapDnsProviderTest smdns
+ * run main/othervm LdapDnsProviderTest nosmbaddns
+ */
+
+ private boolean dnsProvider = false;
+
+ public void setAllowDnsProvider(boolean allow) {
+ dnsProvider = allow;
+ }
+
+ @Override
+ public void checkPermission(Permission p) {
+ if (p.getName().equals("ldapDnsProvider") && !dnsProvider) {
+ throw new SecurityException(p.getName());
+ }
+ }
+}
+
+class ProviderTest implements Callable<Boolean> {
+
+ private final String url;
+ private final String expected;
+ private final Hashtable<String, String> env = new Hashtable<>(11);
+
+ public ProviderTest(String url, String expected) {
+ this.url = url;
+ this.expected = expected;
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+ }
+
+ boolean shutItDown(InitialContext ctx) {
+ try {
+ if (ctx != null) ctx.close();
+ return true;
+ } catch (NamingException ex) {
+ return false;
+ }
+ }
+
+ public Boolean call() {
+ boolean passed;
+ InitialContext ctx = null;
+
+ if (url != null) {
+ env.put(Context.PROVIDER_URL, url);
+ }
+
+ try {
+ ctx = new InitialDirContext(env);
+ SearchControls scl = new SearchControls();
+ scl.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ ((InitialDirContext)ctx).search(
+ "ou=People,o=Test", "(objectClass=*)", scl);
+ throw new RuntimeException("Search should not complete");
+ } catch (NamingException e) {
+ e.printStackTrace();
+ passed = e.toString().contains(expected);
+ } finally {
+ shutItDown(ctx);
+ }
+ return passed;
+ }
+}
+
+public class LdapDnsProviderTest {
+
+ private static final String TEST_CLASSES =
+ System.getProperty("test.classes", ".");
+
+ public static void writeFile(String content, File dstFile)
+ throws IOException
+ {
+ try (FileOutputStream dst = new FileOutputStream(dstFile)) {
+ byte[] buf = content.getBytes();
+ dst.write(buf, 0, buf.length);
+ }
+ }
+
+ public static void installServiceConfigurationFile(String content) {
+ String filename = "javax.naming.ldap.spi.LdapDnsProvider";
+
+ File dstDir = new File(TEST_CLASSES, "META-INF/services");
+ if (!dstDir.exists()) {
+ if (!dstDir.mkdirs()) {
+ throw new RuntimeException(
+ "could not create META-INF/services directory " + dstDir);
+ }
+ }
+ File dstFile = new File(dstDir, filename);
+
+ try {
+ writeFile(content, dstFile);
+ } catch (IOException e) {
+ throw new RuntimeException("could not install " + dstFile, e);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length > 0 && args[0].equals("nosm")) {
+ // no security manager, serviceloader
+ installServiceConfigurationFile("dnsprovider.TestDnsProvider");
+ runTest("ldap:///dc=example,dc=com", "yupyupyup:389");
+ } else if (args.length > 0 && args[0].equals("smnodns")) {
+ // security manager & serviceloader
+ installServiceConfigurationFile("dnsprovider.TestDnsProvider");
+ // install security manager
+ System.setSecurityManager(new DNSSecurityManager());
+ runTest("ldap:///dc=example,dc=com", "ServiceConfigurationError");
+ } else if (args.length > 0 && args[0].equals("smdns")) {
+ // security manager & serviceloader
+ DNSSecurityManager sm = new DNSSecurityManager();
+ installServiceConfigurationFile("dnsprovider.TestDnsProvider");
+ // install security manager
+ System.setSecurityManager(sm);
+ sm.setAllowDnsProvider(true);
+ runTest("ldap:///dc=example,dc=com", "yupyupyup:389");
+ } else if (args.length > 0 && args[0].equals("nosmbaddns")) {
+ // no security manager, no serviceloader
+ // DefaultLdapDnsProvider
+ installServiceConfigurationFile("dnsprovider.MissingDnsProvider");
+ // no SecurityManager
+ runTest("ldap:///dc=example,dc=com", "not found");
+ } else {
+ // no security manager, no serviceloader
+ // DefaultLdapDnsProvider
+ System.err.println("TEST_CLASSES:");
+ System.err.println(TEST_CLASSES);
+ File f = new File(
+ TEST_CLASSES, "META-INF/services/javax.naming.ldap.spi.LdapDnsProvider");
+ if (f.exists()) {
+ f.delete();
+ }
+
+ // no SecurityManager
+ runTest("ldap:///dc=example,dc=com", "localhost:389");
+ runTest("ldap://localhost/dc=example,dc=com", "localhost:389");
+ runTest("ldap://localhost:111/dc=example,dc=com", "localhost:111");
+ runTest("ldaps://localhost:111/dc=example,dc=com", "localhost:111");
+ runTest("ldaps://localhost/dc=example,dc=com", "localhost:636");
+ runTest(null, "localhost:389");
+ runTest("", "ConfigurationException");
+ }
+ }
+
+ private static void runTest(String url, String expected) {
+ FutureTask<Boolean> future =
+ new FutureTask<>(
+ new ProviderTest(url, expected));
+ new Thread(future).start();
+
+ System.err.println("Testing: " + url + ", " + expected);
+ while (!future.isDone()) {
+ try {
+ if (!future.get()) {
+ System.err.println("Test failed");
+ throw new RuntimeException(
+ "Test failed, ProviderTest returned false");
+ }
+ } catch (Exception e) {
+ if (!e.toString().contains(expected)) {
+ System.err.println("Test failed");
+ throw new RuntimeException(
+ "Test failed, unexpected result");
+ }
+ }
+ }
+ System.err.println("Test passed");
+ }
+
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/dnsprovider/TestDnsProvider.java Mon Nov 12 08:33:59 2018 -0800
@@ -0,0 +1,20 @@
+package dnsprovider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.naming.ldap.spi.LdapDnsProvider;
+import javax.naming.ldap.spi.LdapDnsProviderResult;
+
+public class TestDnsProvider extends LdapDnsProvider {
+ @Override
+ public Optional<LdapDnsProviderResult> lookupEndpoints(String url,
+ Map<?, ?> env)
+ {
+ List<String> endpoints = new ArrayList<>();
+ endpoints.add("ldap://yupyupyup:389");
+ return Optional.of(
+ new LdapDnsProviderResult("test.com", endpoints));
+ }
+}
--- a/test/jdk/com/sun/security/auth/module/LdapLoginModule/CheckConfigs.policy Mon Nov 12 16:40:25 2018 +0100
+++ b/test/jdk/com/sun/security/auth/module/LdapLoginModule/CheckConfigs.policy Mon Nov 12 08:33:59 2018 -0800
@@ -6,4 +6,5 @@
//permission java.net.SocketPermission "*:636", "connect";
//permission javax.security.auth.AuthPermission "modifyPrincipals";
permission java.lang.RuntimePermission "accessClassInPackage.com.sun.jndi.ldap";
+ permission java.lang.RuntimePermission "ldapDnsProvider";
};