8160768: Add capability to custom resolve host/domain names within the default JNDI LDAP provider

Reviewed-by: alanb, dfuchs, chegar, mchung, vtewari
This commit is contained in:
Rob McKenna 2018-11-12 08:33:59 -08:00
parent b33edbf1ec
commit d3ccf8a0ca
11 changed files with 773 additions and 49 deletions

View File

@ -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);
}
}
}

View File

@ -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 java.util.Enumeration;
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 @@ final public class LdapCtxFactory implements ObjectFactory, InitialContextFactor
}
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);
} else {
ctx = new LdapCtx(dn, host, port, env, ldapUrl.useSsl());
// Record the URL that created the context
((LdapCtx)ctx).setProviderUrl(url);
if (lastException != null) {
throw lastException;
}
// 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 @@ final public class LdapCtxFactory implements ObjectFactory, InitialContextFactor
* 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;
}
/**

View File

@ -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;
}
}

View File

@ -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.*;
@ -103,6 +99,23 @@ class ServiceLocator {
return (domain.length() != 0) ? domain.toString() : null;
}
/**
* 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, 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

View File

@ -27,7 +27,6 @@
package javax.naming.directory;
import java.util.Hashtable;
import javax.naming.spi.NamingManager;
import javax.naming.*;
/**
@ -84,6 +83,36 @@ public class InitialDirContext extends InitialContext implements DirContext {
* 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

View File

@ -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&nbsp;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;
}

View File

@ -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;
}
}

View File

@ -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 @@ module java.naming {
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 @@ module java.naming {
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;

View File

@ -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");
}
}

View File

@ -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));
}
}

View File

@ -6,4 +6,5 @@ grant codeBase "file:${test.classes}/*" {
//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";
};