Skip to content

Commit

Permalink
Merge pull request #1577 from marklogic/release/6.2.2
Browse files Browse the repository at this point in the history
Merging release/6.2.2 into master
  • Loading branch information
rjrudin authored Jul 19, 2023
2 parents e8100a1 + 55d832e commit b25a122
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ To use the API in your [Maven](https://maven.apache.org/) project, include the f
<dependency>
<groupId>com.marklogic</groupId>
<artifactId>marklogic-client-api</artifactId>
<version>6.2.1</version>
<version>6.2.2</version>
</dependency>

To use the API in your [Gradle](https://gradle.org/) project, include the following in your build.gradle file:

dependencies {
implementation "com.marklogic:marklogic-client-api:6.2.1"
implementation "com.marklogic:marklogic-client-api:6.2.2"
}

Next, read [The Java API in Five Minutes](http://developer.marklogic.com/try/java/index) to get started.
Expand Down Expand Up @@ -130,7 +130,7 @@ https://developer.marklogic.com/free-developer

To obtain verified downloads signed with MarkLogic's PGP key, use maven tools or directly download
the .jar and .asc files from
[maven central](https://repo1.maven.org/maven2/com/marklogic/marklogic-client-api/5.3.0/). MarkLogic's
[maven central](https://repo1.maven.org/maven2/com/marklogic/marklogic-client-api/). MarkLogic's
pgp key ID is 48D4B86E and it is available from pgp.mit.edu by installing gnupg and running the command:

$ gpg --keyserver pgp.mit.edu --recv-key 48D4B86E
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=com.marklogic
version=6.2.1
version=6.2.2
describedName=MarkLogic Java Client API
publishUrl=file:../marklogic-java/releases

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,19 @@ public DatabaseClientBuilder withCertificateAuth(String file, String password) {
.withCertificatePassword(password);
}

/**
*
* @param sslContext
* @param trustManager
* @return
* @since 6.2.2
*/
public DatabaseClientBuilder withCertificateAuth(SSLContext sslContext, X509TrustManager trustManager) {
return withAuthType(AUTH_TYPE_CERTIFICATE)
.withSSLContext(sslContext)
.withTrustManager(trustManager);
}

public DatabaseClientBuilder withSAMLAuth(String token) {
return withAuthType(AUTH_TYPE_SAML)
.withSAMLToken(token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1238,8 +1238,8 @@ public String getCertificatePassword() {
* "kerberos", "certificate", or "saml"</li>
* <li>marklogic.client.username = must be a String; required for basic and digest authentication</li>
* <li>marklogic.client.password = must be a String; required for basic and digest authentication</li>
* <li>marklogic.client.certificate.file = must be a String; required for certificate authentication</li>
* <li>marklogic.client.certificate.password = must be a String; required for certificate authentication</li>
* <li>marklogic.client.certificate.file = must be a String; optional for certificate authentication</li>
* <li>marklogic.client.certificate.password = must be a String; optional for certificate authentication</li>
* <li>marklogic.client.cloud.apiKey = must be a String; required for cloud authentication</li>
* <li>marklogic.client.kerberos.principal = must be a String; required for Kerberos authentication</li>
* <li>marklogic.client.saml.token = must be a String; required for SAML authentication</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,19 @@ private DatabaseClientFactory.SecurityContext newCloudAuthContext() {
}

private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLInputs sslInputs) {
try {
return new DatabaseClientFactory.CertificateAuthContext(
getRequiredStringValue("certificate.file"),
getRequiredStringValue("certificate.password"),
sslInputs.getTrustManager()
);
} catch (Exception e) {
throw new RuntimeException("Unable to create CertificateAuthContext; cause " + e.getMessage(), e);
String file = getNullableStringValue("certificate.file");
String password = getNullableStringValue("certificate.password");
if (file != null && file.trim().length() > 0) {
try {
if (password != null && password.trim().length() > 0) {
return new DatabaseClientFactory.CertificateAuthContext(file, password, sslInputs.getTrustManager());
}
return new DatabaseClientFactory.CertificateAuthContext(file, sslInputs.getTrustManager());
} catch (Exception e) {
throw new RuntimeException("Unable to create CertificateAuthContext; cause " + e.getMessage(), e);
}
}
return new DatabaseClientFactory.CertificateAuthContext(sslInputs.getSslContext(), sslInputs.getTrustManager());
}

private DatabaseClientFactory.SecurityContext newKerberosAuthContext() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,12 +787,8 @@ private void sendMetadataImpl(OutputStream out) {

serializer.flush();
serializer.close();
} catch (XMLStreamException e) {
throw new MarkLogicIOException("Failed to serialize metadata", e);
} catch (TransformerFactoryConfigurationError e) {
throw new MarkLogicIOException("Failed to serialize metadata", e);
} catch (TransformerException e) {
throw new MarkLogicIOException("Failed to serialize metadata", e);
} catch (Exception e) {
throw new MarkLogicIOException("Failed to serialize metadata: cause: " + e.getMessage(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -54,6 +53,27 @@ void trustAllManager() throws Exception {
assertNull(result.getErrorMessage());
}

/**
* Demonstrates using a custom X509TrustManager that only accepts the issuer of the public certificate associated
* with the certificate template created via RequireSSLExtension.
*/
@Test
void customTrustManager() {
if (Common.USE_REVERSE_PROXY_SERVER) {
return;
}

DatabaseClient client = Common.newClientBuilder()
.withSSLProtocol("TLSv1.2")
.withTrustManager(RequireSSLExtension.newTrustManager())
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY)
.build();

DatabaseClient.ConnectionResult result = client.checkConnection();
assertEquals(0, result.getStatusCode());
assertNull(result.getErrorMessage());
}

@Test
void defaultSslContext() throws Exception {
DatabaseClient client = Common.newClientBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
import com.marklogic.client.DatabaseClient;
import com.marklogic.client.DatabaseClientBuilder;
import com.marklogic.client.DatabaseClientFactory;
import com.marklogic.client.ext.modulesloader.ssl.SimpleX509TrustManager;
import org.junit.jupiter.api.Test;

import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;

import java.security.NoSuchAlgorithmException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand Down Expand Up @@ -138,14 +143,45 @@ void kerberos() {
}

@Test
void certificate() {
void certificateValidFile() {
DatabaseClient client = Common.newClientBuilder()
.withCertificateAuth("src/test/resources/test_certificate.p12", "abc")
.build();

assertNotNull(client);
assertNotNull(client.getSecurityContext().getSSLContext(), "An SSLContext should have been created based " +
"on the test keystore.");
}

@Test
void certificateInvalidFile() {
DatabaseClientBuilder builder = Common.newClientBuilder()
.withCertificateAuth("not.found", "passwd");

Exception ex = assertThrows(Exception.class, () -> builder.buildBean());
assertTrue(ex.getMessage().contains("Unable to create CertificateAuthContext"),
"We don't yet have a real test for certificate authentication, so there's not yet a certificate store " +
"to test against; just making sure that an attempt is made to create a CertificateAuthContext");
assertEquals("Unable to create CertificateAuthContext; cause not.found (No such file or directory)",
ex.getMessage(), "Should fail because the certificate file path is not valid, and thus a keystore " +
"cannot be created from it.");
}

@Test
void certificateWithNoFile() throws NoSuchAlgorithmException {
SSLContext defaultContext = SSLContext.getDefault();
X509TrustManager trustManager = new SimpleX509TrustManager();
DatabaseClientBuilder builder = Common.newClientBuilder()
.withCertificateAuth(defaultContext, trustManager)
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.STRICT);

// Verify the SSL-related objects are the same ones passed in above.
DatabaseClientFactory.Bean bean = builder.buildBean();
assertSame(defaultContext, bean.getSecurityContext().getSSLContext());
assertSame(trustManager, bean.getSecurityContext().getTrustManager());
assertSame(DatabaseClientFactory.SSLHostnameVerifier.STRICT, bean.getSecurityContext().getSSLHostnameVerifier());

DatabaseClient client = bean.newClient();
assertNotNull(client, "The client can be instantiated because a certificate file and password aren't " +
"required. In this scenario, it's expected that a user will provide their own SSLContext to use for " +
"certificate authentication.");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@
import com.marklogic.mgmt.ManageClient;
import com.marklogic.mgmt.resource.appservers.ServerManager;
import com.marklogic.mgmt.resource.security.CertificateTemplateManager;
import com.marklogic.rest.util.Fragment;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

/**
* Use this on tests that require an app server to require SSL connections. The app server will be modified to require
* SSL connections before any test runs and will then be restored back to normal after all tests in the test class run.
Expand Down Expand Up @@ -52,6 +59,39 @@ public void afterAll(ExtensionContext context) {
new CertificateTemplateManager(manageClient).delete(TEMPLATE);
}

/**
* @return a trust manager that accepts the public certificate associated with the certificate template created
* by this class.
*/
public static X509TrustManager newTrustManager() {
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{getCertificate()};
}
};
}

private static X509Certificate getCertificate() {
CertificateTemplateManager mgr = new CertificateTemplateManager(Common.newManageClient());

Fragment response = mgr.getCertificatesForTemplate(TEMPLATE_NAME);
String cert = response.getElementValue("/msec:certificate-list/msec:certificate/msec:pem");
try {
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(cert.getBytes()));
} catch (CertificateException e) {
throw new RuntimeException("Unable to generate X509Certificate: " + e.getMessage(), e);
}
}

private void setSslCertificateTemplate(String templateName) {
new ServerManager(manageClient).save(
Common.newServerPayload()
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ It is not intended to be used to build this project.
<modelVersion>4.0.0</modelVersion>
<groupId>com.marklogic</groupId>
<artifactId>marklogic-client-api</artifactId>
<version>6.2.1</version>
<version>6.2.2</version>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
Expand Down
4 changes: 2 additions & 2 deletions test-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ plugins {
}

dependencies {
implementation "io.undertow:undertow-core:2.2.21.Final"
implementation "io.undertow:undertow-servlet:2.2.21.Final"
implementation "io.undertow:undertow-core:2.2.24.Final"
implementation "io.undertow:undertow-servlet:2.2.24.Final"
implementation "com.marklogic:ml-javaclient-util:4.4.0"
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'ch.qos.logback:logback-classic:1.3.5'
Expand Down

0 comments on commit b25a122

Please sign in to comment.