Skip to content

Commit 9ae23ce

Browse files
committed
DEVEXP-447 Better error when HTTP is used instead of HTTPS
1 parent 55dcc91 commit 9ae23ce

File tree

2 files changed

+61
-21
lines changed

2 files changed

+61
-21
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -165,26 +165,38 @@ public void setMaxDelay(int maxDelay) {
165165
this.maxDelay = maxDelay;
166166
}
167167

168-
private FailedRequest extractErrorFields(Response response) {
169-
if (response == null) return null;
170-
try {
171-
if (response.code() == STATUS_UNAUTHORIZED) {
172-
FailedRequest failure = new FailedRequest();
173-
failure.setMessageString("Unauthorized");
174-
failure.setStatusString("Failed Auth");
175-
return failure;
176-
}
177-
String responseBody = getEntity(response.body(), String.class);
178-
InputStream is = new ByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8));
179-
FailedRequest handler = FailedRequest.getFailedRequest(response.code(), response.header(HEADER_CONTENT_TYPE), is);
180-
if (handler.getMessage() == null) {
181-
handler.setMessageString(responseBody);
182-
}
183-
return handler;
184-
} finally {
185-
closeResponse(response);
186-
}
187-
}
168+
private FailedRequest extractErrorFields(Response response) {
169+
if (response == null) return null;
170+
try {
171+
if (response.code() == STATUS_UNAUTHORIZED) {
172+
FailedRequest failure = new FailedRequest();
173+
failure.setMessageString("Unauthorized");
174+
failure.setStatusString("Failed Auth");
175+
return failure;
176+
}
177+
178+
final String responseBody = getEntity(response.body(), String.class);
179+
// If HTTP is used but HTTPS is required, MarkLogic returns a text/html response that is not suitable to
180+
// return to a user. But it will contain the below error message, which is much nicer to return to the user.
181+
final String sslErrorMessage = "You have attempted to access an HTTPS server using HTTP";
182+
if (response.code() == STATUS_FORBIDDEN && responseBody != null && responseBody.contains(sslErrorMessage)) {
183+
FailedRequest failure = new FailedRequest();
184+
failure.setMessageString(sslErrorMessage + ".");
185+
failure.setStatusString("Forbidden");
186+
failure.setStatusCode(STATUS_FORBIDDEN);
187+
return failure;
188+
}
189+
190+
InputStream is = new ByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8));
191+
FailedRequest handler = FailedRequest.getFailedRequest(response.code(), response.header(HEADER_CONTENT_TYPE), is);
192+
if (handler.getMessage() == null) {
193+
handler.setMessageString(responseBody);
194+
}
195+
return handler;
196+
} finally {
197+
closeResponse(response);
198+
}
199+
}
188200

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

marklogic-client-api/src/test/java/com/marklogic/client/test/CheckSSLConnectionTest.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22

33
import com.marklogic.client.DatabaseClient;
44
import com.marklogic.client.DatabaseClientFactory;
5+
import com.marklogic.client.ForbiddenUserException;
56
import com.marklogic.client.MarkLogicIOException;
67
import com.marklogic.client.test.junit5.RequireSSLExtension;
78
import org.junit.jupiter.api.Test;
89
import org.junit.jupiter.api.extension.ExtendWith;
910

1011
import javax.net.ssl.SSLContext;
12+
import javax.net.ssl.SSLHandshakeException;
1113
import javax.net.ssl.TrustManager;
1214

1315
import static org.junit.jupiter.api.Assertions.assertEquals;
1416
import static org.junit.jupiter.api.Assertions.assertNull;
1517
import static org.junit.jupiter.api.Assertions.assertThrows;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
1619

1720
@ExtendWith(RequireSSLExtension.class)
1821
class CheckSSLConnectionTest {
@@ -58,8 +61,33 @@ void defaultSslContext() throws Exception {
5861
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY)
5962
.build();
6063

61-
assertThrows(MarkLogicIOException.class, () -> client.checkConnection(),
64+
MarkLogicIOException ex = assertThrows(MarkLogicIOException.class, () -> client.checkConnection(),
6265
"The connection should fail because the JVM's default SSL Context does not have a CA certificate that " +
6366
"corresponds to the test-only certificate that the app server is using for this test");
67+
68+
assertTrue(ex.getCause() instanceof SSLHandshakeException, "Unexpected cause: " + ex.getCause());
69+
String message = ex.getCause().getMessage();
70+
assertTrue(message.startsWith("PKIX path building failed"), "The call should have failed because the JVM's " +
71+
"default SSL context does not have a CA certificate for the app server's certificate; cause: " + message);
72+
}
73+
74+
@Test
75+
void noSslContext() {
76+
DatabaseClient client = Common.newClientBuilder().build();
77+
78+
DatabaseClient.ConnectionResult result = client.checkConnection();
79+
assertEquals("Forbidden", result.getErrorMessage(), "MarkLogic is expected to return a 403 Forbidden when the " +
80+
"user tries to access an HTTPS app server using HTTP");
81+
assertEquals(403, result.getStatusCode());
82+
83+
ForbiddenUserException ex = assertThrows(ForbiddenUserException.class,
84+
() -> client.newServerEval().javascript("fn.currentDate()").evalAs(String.class));
85+
86+
assertEquals(
87+
"Local message: User is not allowed to apply resource at eval. Server Message: You have attempted to access an HTTPS server using HTTP.",
88+
ex.getMessage(),
89+
"The user should get a clear message on why the connection failed as opposed to the previous error " +
90+
"message of 'Server (not a REST instance?)'."
91+
);
6492
}
6593
}

0 commit comments

Comments
 (0)