This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch 3.2.x in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit dc220e3bf1924e6b9602f66c8ee5c768115ba55d Author: Darren Coleman <[email protected]> AuthorDate: Mon Sep 18 12:21:36 2023 +0100 Updates to LDAP tests and usage docs (#5310) Fixes #5309 --- .../ROOT/pages/reference/extensions/ldap.adoc | 136 +-------------------- extensions/ldap/runtime/src/main/doc/usage.adoc | 134 +------------------- .../quarkus/component/ldap/it/LdapResource.java | 48 +++++--- .../camel/quarkus/component/ldap/it/LdapTest.java | 28 ++++- 4 files changed, 60 insertions(+), 286 deletions(-) diff --git a/docs/modules/ROOT/pages/reference/extensions/ldap.adoc b/docs/modules/ROOT/pages/reference/extensions/ldap.adoc index f0880d6d3d..8194a85386 100644 --- a/docs/modules/ROOT/pages/reference/extensions/ldap.adoc +++ b/docs/modules/ROOT/pages/reference/extensions/ldap.adoc @@ -46,146 +46,16 @@ endif::[] [id="extensions-ldap-usage"] == Usage -[id="extensions-ldap-usage-dircontext"] -=== DirContext - -The URI, `ldap:ldapserver`, references a bean with the ID `ldapserver`. A CDI producer method may be used to instantiate a `DirContext` object as follows: - -[source,java] ----- -public class LdapServerProducer { - - @Produces - @Dependent - @Named("ldapserver") - public DirContext createLdapServer() throws Exception { - Hashtable<String, String> env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - env.put(Context.PROVIDER_URL, "ldap://localhost:10389"); - env.put(Context.SECURITY_AUTHENTICATION, "none"); - - return new InitialDirContext(env); - } -} ----- - -The preceding example creates a regular Sun based LDAP `DirContext` that connects anonymously to a locally hosted LDAP server. The use of the `@Named` annotation binds the `DirContext` into the Camel registry automatically. - -[id="extensions-ldap-usage-configuring-ssl"] -=== Configuring SSL - -When connecting to an LDAP server over SSL/TLS, you may encounter situations where the default trust manager used by the JVM is unable to verify the certificate. This can happen, for example, when the server uses a self-signed certificate or when the certificate is issued by a non-trusted CA. In such cases, you may need to provide a custom trust manager implementation that can verify the server's certificate. - -The following code shows an implementation of a custom socket factory that can be used to create SSL/TLS sockets. The class name of the custom SSL socket factory is then specified in the `java.naming.ldap.factory.socket` property of the environment hashtable used to create the LDAP context. - -[source,java] ----- -public class CustomSSLSocketFactory extends SSLSocketFactory { - - private SSLSocketFactory delegate; - - public CustomSSLSocketFactory() throws Exception { - String trustStoreFilename = ConfigProvider.getConfig().getValue("ldap.trustStore", String.class); - String trustStorePassword = ConfigProvider.getConfig().getValue("ldap.trustStorePassword", String.class); - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream in = new FileInputStream(trustStoreFilename)) { - keyStore.load(in, trustStorePassword.toCharArray()); - } - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(keyStore); - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, tmf.getTrustManagers(), null); - delegate = ctx.getSocketFactory(); - - } - - public static SocketFactory getDefault() { - try { - return new CustomSSLSocketFactory(); - } catch (Exception ex) { - ex.printStackTrace(); - return null; - } - } - - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return delegate.createSocket(s, host, port, autoClose); - } - - @Override - public String[] getDefaultCipherSuites() { - return delegate.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - return delegate.createSocket(host, port); - } - - @Override - public Socket createSocket(InetAddress address, int port) throws IOException { - return delegate.createSocket(address, port); - } - - @Override - public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) - throws IOException, UnknownHostException { - return delegate.createSocket(host, port, localAddress, localPort); - } - - @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) - throws IOException { - return delegate.createSocket(address, port, localAddress, localPort); - } -} ----- - -The constructor uses the `ConfigProvider` to read the `ldap.trustStore` and `ldap.trustStorePassword` configuration properties, which could be specified in the `application.properties` file as follows: - -[source,properties] ----- -ldap.trustStore=/path/to/truststore.jks -ldap.trustStorePassword=secret ----- - -Finally, alter the `LdapServerProducer.createLdapServer()` method so that the `PROVIDER_URL` entry uses the `ldaps` protocol instead of `ldap`, and add the `CustomSSLSocketFactory` entry: - -[source,java] ----- -public class LdapServerProducer { - - @Produces - @Dependent - @Named("ldapserver") - public DirContext createLdapServer() throws Exception { - Hashtable<String, String> env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - env.put(Context.PROVIDER_URL, "ldaps://" + InetAddress.getLocalHost().getCanonicalHostName() + ":10636"); - env.put(Context.SECURITY_AUTHENTICATION, "none"); - env.put("java.naming.ldap.factory.socket", CustomSSLSocketFactory.class.getName()); - - return new InitialDirContext(env); - } -} ----- - [id="extensions-ldap-usage-using-ssl-in-native-mode"] -==== Using SSL in Native Mode +=== Using SSL in Native Mode -When using a custom `SSLSocketFactory` in native mode, you need to register the class for reflection otherwise the class will not be made available on the classpath. Add the `@RegisterForReflection` annotation above the class definition, as follows: +When using a custom `SSLSocketFactory` in native mode, such as the one in the xref:{cq-camel-components}::ldap-component.adoc#_configuring_ssl[Configuring SSL] section, you need to register the class for reflection otherwise the class will not be made available on the classpath. Add the `@RegisterForReflection` annotation above the class definition, as follows: [source,java] ---- @RegisterForReflection public class CustomSSLSocketFactory extends SSLSocketFactory { - // The class definition is the same as above. + // The class definition is the same as in the above link. } ---- diff --git a/extensions/ldap/runtime/src/main/doc/usage.adoc b/extensions/ldap/runtime/src/main/doc/usage.adoc index ffbc96e7c5..dbb52638a8 100644 --- a/extensions/ldap/runtime/src/main/doc/usage.adoc +++ b/extensions/ldap/runtime/src/main/doc/usage.adoc @@ -1,139 +1,11 @@ -=== DirContext +=== Using SSL in Native Mode -The URI, `ldap:ldapserver`, references a bean with the ID `ldapserver`. A CDI producer method may be used to instantiate a `DirContext` object as follows: - -[source,java] ----- -public class LdapServerProducer { - - @Produces - @Dependent - @Named("ldapserver") - public DirContext createLdapServer() throws Exception { - Hashtable<String, String> env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - env.put(Context.PROVIDER_URL, "ldap://localhost:10389"); - env.put(Context.SECURITY_AUTHENTICATION, "none"); - - return new InitialDirContext(env); - } -} ----- - -The preceding example creates a regular Sun based LDAP `DirContext` that connects anonymously to a locally hosted LDAP server. The use of the `@Named` annotation binds the `DirContext` into the Camel registry automatically. - -=== Configuring SSL - -When connecting to an LDAP server over SSL/TLS, you may encounter situations where the default trust manager used by the JVM is unable to verify the certificate. This can happen, for example, when the server uses a self-signed certificate or when the certificate is issued by a non-trusted CA. In such cases, you may need to provide a custom trust manager implementation that can verify the server's certificate. - -The following code shows an implementation of a custom socket factory that can be used to create SSL/TLS sockets. The class name of the custom SSL socket factory is then specified in the `java.naming.ldap.factory.socket` property of the environment hashtable used to create the LDAP context. - -[source,java] ----- -public class CustomSSLSocketFactory extends SSLSocketFactory { - - private SSLSocketFactory delegate; - - public CustomSSLSocketFactory() throws Exception { - String trustStoreFilename = ConfigProvider.getConfig().getValue("ldap.trustStore", String.class); - String trustStorePassword = ConfigProvider.getConfig().getValue("ldap.trustStorePassword", String.class); - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream in = new FileInputStream(trustStoreFilename)) { - keyStore.load(in, trustStorePassword.toCharArray()); - } - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(keyStore); - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, tmf.getTrustManagers(), null); - delegate = ctx.getSocketFactory(); - - } - - public static SocketFactory getDefault() { - try { - return new CustomSSLSocketFactory(); - } catch (Exception ex) { - ex.printStackTrace(); - return null; - } - } - - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return delegate.createSocket(s, host, port, autoClose); - } - - @Override - public String[] getDefaultCipherSuites() { - return delegate.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - return delegate.createSocket(host, port); - } - - @Override - public Socket createSocket(InetAddress address, int port) throws IOException { - return delegate.createSocket(address, port); - } - - @Override - public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) - throws IOException, UnknownHostException { - return delegate.createSocket(host, port, localAddress, localPort); - } - - @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) - throws IOException { - return delegate.createSocket(address, port, localAddress, localPort); - } -} ----- - -The constructor uses the `ConfigProvider` to read the `ldap.trustStore` and `ldap.trustStorePassword` configuration properties, which could be specified in the `application.properties` file as follows: - -[source,properties] ----- -ldap.trustStore=/path/to/truststore.jks -ldap.trustStorePassword=secret ----- - -Finally, alter the `LdapServerProducer.createLdapServer()` method so that the `PROVIDER_URL` entry uses the `ldaps` protocol instead of `ldap`, and add the `CustomSSLSocketFactory` entry: - -[source,java] ----- -public class LdapServerProducer { - - @Produces - @Dependent - @Named("ldapserver") - public DirContext createLdapServer() throws Exception { - Hashtable<String, String> env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - env.put(Context.PROVIDER_URL, "ldaps://" + InetAddress.getLocalHost().getCanonicalHostName() + ":10636"); - env.put(Context.SECURITY_AUTHENTICATION, "none"); - env.put("java.naming.ldap.factory.socket", CustomSSLSocketFactory.class.getName()); - - return new InitialDirContext(env); - } -} ----- - -==== Using SSL in Native Mode - -When using a custom `SSLSocketFactory` in native mode, you need to register the class for reflection otherwise the class will not be made available on the classpath. Add the `@RegisterForReflection` annotation above the class definition, as follows: +When using a custom `SSLSocketFactory` in native mode, such as the one in the xref:{cq-camel-components}::ldap-component.adoc#_configuring_ssl[Configuring SSL] section, you need to register the class for reflection otherwise the class will not be made available on the classpath. Add the `@RegisterForReflection` annotation above the class definition, as follows: [source,java] ---- @RegisterForReflection public class CustomSSLSocketFactory extends SSLSocketFactory { - // The class definition is the same as above. + // The class definition is the same as in the above link. } ---- diff --git a/integration-tests/ldap/src/main/java/org/apache/camel/quarkus/component/ldap/it/LdapResource.java b/integration-tests/ldap/src/main/java/org/apache/camel/quarkus/component/ldap/it/LdapResource.java index 746524857b..73828a6c02 100644 --- a/integration-tests/ldap/src/main/java/org/apache/camel/quarkus/component/ldap/it/LdapResource.java +++ b/integration-tests/ldap/src/main/java/org/apache/camel/quarkus/component/ldap/it/LdapResource.java @@ -30,7 +30,9 @@ import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchResult; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; @@ -50,6 +52,12 @@ public class LdapResource { @Inject CamelContext camelContext; + private String ldapHost; + private String ldapPort; + private boolean useSSL; + private String trustStoreFilename; + private String trustStorePassword; + /** * Extracts the LDAP connection parameters passed from the test and creates a * {@link javax.naming.directory.DirContext} from them. @@ -62,43 +70,43 @@ public class LdapResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void configure(Map<String, String> options) throws Exception { - String host = options.get("host"); - String port = options.get("port"); - boolean useSSL = Boolean.valueOf(options.get("ssl")); - String trustStoreFilename = options.get("trustStore"); - String trustStorePassword = options.get("trustStorePassword"); - - DirContext dirContext = createLdapContext(host, port, useSSL, trustStoreFilename, trustStorePassword); - camelContext.getRegistry().bind("ldapserver", dirContext); + ldapHost = options.get("host"); + ldapPort = options.get("port"); + useSSL = Boolean.valueOf(options.get("ssl")); + trustStoreFilename = options.get("trustStore"); + trustStorePassword = options.get("trustStorePassword"); } @Path("/search") @GET @Produces(MediaType.APPLICATION_JSON) - public Response search(@QueryParam("q") String filter) throws Exception { - ProducerTemplate producer = camelContext.createProducerTemplate(); - List<SearchResult> results = producer.requestBody("direct:start", filter, List.class); - return Response.ok(convertSearchResults(results)).build(); + public Response search(@QueryParam("q") String q) throws Exception { + return Response.ok(searchByUid(q)).build(); } @Path("/safeSearch") @GET @Produces(MediaType.APPLICATION_JSON) - public Response safeSearch(@QueryParam("q") String unsafeFilter) throws Exception { - String filter = String.format("(ou=%s)", LdapHelper.escapeFilter(unsafeFilter)); + public Response safeSearch(@QueryParam("q") String q) throws Exception { + return Response.ok(searchByUid(LdapHelper.escapeFilter(q))).build(); + } + + @SuppressWarnings("unchecked") + private List<Map<String, String>> searchByUid(String uid) throws Exception { + String filter = String.format("(uid=%s)", uid); ProducerTemplate producer = camelContext.createProducerTemplate(); List<SearchResult> results = producer.requestBody("direct:start", filter, List.class); - - return Response.ok(convertSearchResults(results)).build(); + return convertSearchResults(results); } - private DirContext createLdapContext(String host, String port, boolean useSSL, String trustStoreFilename, - String trustStorePassword) - throws Exception { + @Produces + @Dependent + @Named("ldapserver") + public DirContext createLdapContext() throws Exception { String scheme = useSSL ? "ldaps" : "ldap"; Hashtable<String, Object> env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - env.put(Context.PROVIDER_URL, String.format("%s://%s:%s", scheme, host, port)); + env.put(Context.PROVIDER_URL, String.format("%s://%s:%s", scheme, ldapHost, ldapPort)); env.put(Context.SECURITY_AUTHENTICATION, "none"); if (useSSL) { diff --git a/integration-tests/ldap/src/test/java/org/apache/camel/quarkus/component/ldap/it/LdapTest.java b/integration-tests/ldap/src/test/java/org/apache/camel/quarkus/component/ldap/it/LdapTest.java index 672a437f82..962ec3b7c7 100644 --- a/integration-tests/ldap/src/test/java/org/apache/camel/quarkus/component/ldap/it/LdapTest.java +++ b/integration-tests/ldap/src/test/java/org/apache/camel/quarkus/component/ldap/it/LdapTest.java @@ -24,6 +24,7 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; @@ -110,7 +111,7 @@ class LdapTest { TypeRef<List<Map<String, Object>>> typeRef = new TypeRef<>() { }; List<Map<String, Object>> results = RestAssured.given() - .queryParam("q", "(uid=tcruise)") + .queryParam("q", "tcruise") .get("/ldap/search") .then() .statusCode(200) @@ -132,14 +133,37 @@ class LdapTest { TypeRef<List<Map<String, Object>>> typeRef = new TypeRef<>() { }; + + // Verfiy that calling the unsafe endpoint with a wildcard returns multiple results. List<Map<String, Object>> results = RestAssured.given() .queryParam("q", "test*") - .get("/ldap/safeSearch") + .get("/ldap/search") .then() .statusCode(200) .extract().as(typeRef); + assertEquals(3, results.size()); + assertEquals(List.of("test1", "test2", "testNoOU"), + results.stream().map(r -> r.get("uid")).collect(Collectors.toList())); + // Verify that the same query passed to the safeSearch returns no matching results. + results = RestAssured.given() + .queryParam("q", "test*") + .get("/ldap/safeSearch") + .then() + .statusCode(200) + .extract().as(typeRef); assertEquals(0, results.size()); + + // Verify that non-escaped queries also work with escaped search + results = RestAssured.given() + .queryParam("q", "test1") + .get("/ldap/safeSearch") + .then() + .statusCode(200) + .extract().as(typeRef); + assertEquals(1, results.size()); + assertEquals("test1", results.get(0).get("ou")); + } /**
