Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 MarkLogic Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.marklogic.client.extra.okhttpclient;

import com.marklogic.client.DatabaseClientFactory;
import com.marklogic.client.impl.okhttp.OkHttpUtil;
import okhttp3.OkHttpClient;

/**
* Exposes the mechanism for constructing an {@code OkHttpClient.Builder} in the same fashion as when a
* {@code DatabaseClient} is constructed. Primarily intended for reuse in the ml-app-deployer library. If the
* Java Client moves to a different HTTP client library, this will no longer work.
*
* @since 6.1.0
*/
public interface OkHttpClientBuilderFactory {

static OkHttpClient.Builder newOkHttpClientBuilder(String host, int port, DatabaseClientFactory.SecurityContext securityContext) {
return OkHttpUtil.newOkHttpClientBuilder(host, port, securityContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,67 +188,24 @@ private FailedRequest extractErrorFields(Response response) {

@Override
public void connect(String host, int port, String basePath, String database, SecurityContext securityContext){

if (host == null)
throw new IllegalArgumentException("No host provided");
if (securityContext == null)
throw new IllegalArgumentException("No security context provided");

OkHttpClient.Builder clientBuilder = OkHttpUtil.newClientBuilder();
AuthenticationConfigurer authenticationConfigurer = null;

// As of 6.1.0, kerberos/saml/certificate are still coded within this class to avoid potential breaks from
// refactoring. Once the tests for these auth methods are running properly, the code for each can be
// safely refactored.
if (securityContext instanceof BasicAuthContext) {
authenticationConfigurer = new BasicAuthenticationConfigurer();
checkFirstRequest = false;
} else if (securityContext instanceof DigestAuthContext) {
authenticationConfigurer = new DigestAuthenticationConfigurer();
checkFirstRequest = true;
} else if (securityContext instanceof KerberosAuthContext) {
configureKerberosAuth((KerberosAuthContext) securityContext, host, clientBuilder);
checkFirstRequest = false;
} else if (securityContext instanceof CertificateAuthContext) {
checkFirstRequest = false;
} else if (securityContext instanceof SAMLAuthContext) {
configureSAMLAuth((SAMLAuthContext) securityContext, clientBuilder);
checkFirstRequest = false;
} else if (securityContext instanceof MarkLogicCloudAuthContext) {
authenticationConfigurer = new MarkLogicCloudAuthenticationConfigurer(host, port);
checkFirstRequest = false;
}
else {
throw new IllegalArgumentException("Unsupported security context: " + securityContext.getClass());
}
this.checkFirstRequest = securityContext instanceof DigestAuthContext;
this.database = database;
this.baseUri = HttpUrlBuilder.newBaseUrl(host, port, basePath, securityContext.getSSLContext());

if (authenticationConfigurer != null) {
authenticationConfigurer.configureAuthentication(clientBuilder, securityContext);
}

SSLContext sslContext = securityContext.getSSLContext();
X509TrustManager trustManager = securityContext.getTrustManager();
OkHttpClient.Builder clientBuilder = OkHttpUtil.newOkHttpClientBuilder(host, port, securityContext);

SSLHostnameVerifier sslVerifier = null;
if (sslContext != null || securityContext instanceof CertificateAuthContext) {
sslVerifier = securityContext.getSSLHostnameVerifier() != null ?
securityContext.getSSLHostnameVerifier() :
SSLHostnameVerifier.COMMON;
Properties props = System.getProperties();
if (props.containsKey(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) {
configureOkHttpLogging(clientBuilder, props);
}
this.configureDelayAndRetry(props);

OkHttpUtil.configureSocketFactory(clientBuilder, sslContext, trustManager);
OkHttpUtil.configureHostnameVerifier(clientBuilder, sslVerifier);

this.database = database;
this.baseUri = HttpUrlBuilder.newBaseUrl(host, port, basePath, sslContext);

Properties props = System.getProperties();
if (props.containsKey(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) {
configureOkHttpLogging(clientBuilder, props);
}
this.configureDelayAndRetry(props);

this.client = clientBuilder.build();
this.client = clientBuilder.build();
}

/**
Expand Down Expand Up @@ -297,26 +254,6 @@ private void configureDelayAndRetry(Properties props) {
}
}

private void configureKerberosAuth(KerberosAuthContext keberosAuthContext, String host, OkHttpClient.Builder clientBuilder) {
Map<String, String> kerberosOptions = keberosAuthContext.getKrbOptions();
Interceptor interceptor = new HTTPKerberosAuthInterceptor(host, kerberosOptions);
clientBuilder.addInterceptor(interceptor);
}

private void configureSAMLAuth(SAMLAuthContext samlAuthContext, OkHttpClient.Builder clientBuilder) {
Interceptor interceptor;
String authorizationTokenValue = samlAuthContext.getToken();
if(authorizationTokenValue != null && authorizationTokenValue.length() > 0) {
interceptor = new HTTPSamlAuthInterceptor(authorizationTokenValue);
} else if(samlAuthContext.getAuthorizer()!=null) {
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorizer());
} else if(samlAuthContext.getRenewer()!=null) {
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorization(),samlAuthContext.getRenewer());
} else
throw new IllegalArgumentException("Either a call back or renewer expected.");
clientBuilder.addInterceptor(interceptor);
}

@Override
public DatabaseClient getDatabaseClient() {
return databaseClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.marklogic.client.impl.okhttp;

import com.marklogic.client.DatabaseClientFactory;
import com.marklogic.client.impl.HTTPKerberosAuthInterceptor;
import com.marklogic.client.impl.HTTPSamlAuthInterceptor;
import okhttp3.ConnectionPool;
import okhttp3.CookieJar;
import okhttp3.Dns;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;

import javax.net.SocketFactory;
import javax.net.ssl.*;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
Expand All @@ -17,6 +24,7 @@
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
Expand All @@ -28,11 +36,53 @@ public abstract class OkHttpUtil {

final private static ConnectionPool connectionPool = new ConnectionPool();

public static OkHttpClient.Builder newOkHttpClientBuilder(String host, int port, DatabaseClientFactory.SecurityContext securityContext) {
OkHttpClient.Builder clientBuilder = OkHttpUtil.newClientBuilder();
AuthenticationConfigurer authenticationConfigurer = null;

// As of 6.1.0, kerberos/saml/certificate are still coded within this class to avoid potential breaks from
// refactoring. Once the tests for these auth methods are running properly, the code for each can be
// safely refactored.
if (securityContext instanceof DatabaseClientFactory.BasicAuthContext) {
authenticationConfigurer = new BasicAuthenticationConfigurer();
} else if (securityContext instanceof DatabaseClientFactory.DigestAuthContext) {
authenticationConfigurer = new DigestAuthenticationConfigurer();
} else if (securityContext instanceof DatabaseClientFactory.KerberosAuthContext) {
configureKerberosAuth((DatabaseClientFactory.KerberosAuthContext) securityContext, host, clientBuilder);
} else if (securityContext instanceof DatabaseClientFactory.CertificateAuthContext) {
} else if (securityContext instanceof DatabaseClientFactory.SAMLAuthContext) {
configureSAMLAuth((DatabaseClientFactory.SAMLAuthContext) securityContext, clientBuilder);
} else if (securityContext instanceof DatabaseClientFactory.MarkLogicCloudAuthContext) {
authenticationConfigurer = new MarkLogicCloudAuthenticationConfigurer(host, port);
} else {
throw new IllegalArgumentException("Unsupported security context: " + securityContext.getClass());
}

if (authenticationConfigurer != null) {
authenticationConfigurer.configureAuthentication(clientBuilder, securityContext);
}

SSLContext sslContext = securityContext.getSSLContext();
X509TrustManager trustManager = securityContext.getTrustManager();

DatabaseClientFactory.SSLHostnameVerifier sslVerifier = null;
if (sslContext != null || securityContext instanceof DatabaseClientFactory.CertificateAuthContext) {
sslVerifier = securityContext.getSSLHostnameVerifier() != null ?
securityContext.getSSLHostnameVerifier() :
DatabaseClientFactory.SSLHostnameVerifier.COMMON;
}

OkHttpUtil.configureSocketFactory(clientBuilder, sslContext, trustManager);
OkHttpUtil.configureHostnameVerifier(clientBuilder, sslVerifier);

return clientBuilder;
}

/**
* @return an OkHttpClient.Builder initialized with a sensible set of defaults that can then have authentication
* configured
*/
public static OkHttpClient.Builder newClientBuilder() {
static OkHttpClient.Builder newClientBuilder() {
return new OkHttpClient.Builder()
.followRedirects(false)
.followSslRedirects(false)
Expand All @@ -47,13 +97,33 @@ public static OkHttpClient.Builder newClientBuilder() {
.dns(new DnsImpl());
}

private static void configureKerberosAuth(DatabaseClientFactory.KerberosAuthContext keberosAuthContext, String host, OkHttpClient.Builder clientBuilder) {
Map<String, String> kerberosOptions = keberosAuthContext.getKrbOptions();
Interceptor interceptor = new HTTPKerberosAuthInterceptor(host, kerberosOptions);
clientBuilder.addInterceptor(interceptor);
}

private static void configureSAMLAuth(DatabaseClientFactory.SAMLAuthContext samlAuthContext, OkHttpClient.Builder clientBuilder) {
Interceptor interceptor;
String authorizationTokenValue = samlAuthContext.getToken();
if (authorizationTokenValue != null && authorizationTokenValue.length() > 0) {
interceptor = new HTTPSamlAuthInterceptor(authorizationTokenValue);
} else if (samlAuthContext.getAuthorizer() != null) {
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorizer());
} else if (samlAuthContext.getRenewer() != null) {
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorization(), samlAuthContext.getRenewer());
} else
throw new IllegalArgumentException("Either a call back or renewer expected.");
clientBuilder.addInterceptor(interceptor);
}

/**
* Configure the hostname verifier for the given OkHttpClient.Builder based on the given SSLHostnameVerifier.
*
* @param clientBuilder
* @param sslVerifier
*/
public static void configureHostnameVerifier(OkHttpClient.Builder clientBuilder, DatabaseClientFactory.SSLHostnameVerifier sslVerifier) {
private static void configureHostnameVerifier(OkHttpClient.Builder clientBuilder, DatabaseClientFactory.SSLHostnameVerifier sslVerifier) {
HostnameVerifier hostnameVerifier = null;
if (DatabaseClientFactory.SSLHostnameVerifier.ANY.equals(sslVerifier)) {
hostnameVerifier = (hostname, session) -> true;
Expand All @@ -75,7 +145,7 @@ public static void configureHostnameVerifier(OkHttpClient.Builder clientBuilder,
* @param sslContext
* @param trustManager
*/
public static void configureSocketFactory(OkHttpClient.Builder clientBuilder, SSLContext sslContext, X509TrustManager trustManager) {
private static void configureSocketFactory(OkHttpClient.Builder clientBuilder, SSLContext sslContext, X509TrustManager trustManager) {
/**
* Per https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory- ,
* OkHttp requires a TrustManager to be specified so that it can build a clean certificate chain. If trustManager
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.marklogic.client.extra.okhttpclient;

import com.marklogic.client.DatabaseClientFactory;
import com.marklogic.client.test.Common;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertNotNull;

public class OkHttpClientBuilderFactoryTest {

@Test
void smokeTest() {
DatabaseClientFactory.Bean bean = Common.newClientBuilder().buildBean();
OkHttpClient.Builder builder = OkHttpClientBuilderFactory.newOkHttpClientBuilder(
bean.getHost(), bean.getPort(), bean.getSecurityContext());
assertNotNull(builder);

OkHttpClient client = builder.build();
assertNotNull(client, "This is simply verifying that the public/extra method doesn't throw an error. It's " +
"expected to reuse the same approach that constructing a DatabaseClient does. And it's expected that " +
"with ml-app-deployer 4.5.0 depending on this method for any calls to MarkLogic, tests will fail in that " +
"project if there's ever an issue with this method");
}
}