diff --git a/vertx-pg-client/pom.xml b/vertx-pg-client/pom.xml
index 44c659b627..18f6828544 100644
--- a/vertx-pg-client/pom.xml
+++ b/vertx-pg-client/pom.xml
@@ -161,6 +161,21 @@
+
+ missing-scram-test
+
+ integration-test
+
+ integration-test
+
+
+ io/vertx/pgclient/it/MissingScramTest.java
+
+
+ com.ongres.scram:scram-client
+
+
+
diff --git a/vertx-pg-client/src/main/asciidoc/index.adoc b/vertx-pg-client/src/main/asciidoc/index.adoc
index 0dddca21b4..2ade01339a 100644
--- a/vertx-pg-client/src/main/asciidoc/index.adoc
+++ b/vertx-pg-client/src/main/asciidoc/index.adoc
@@ -254,6 +254,8 @@ dependencies {
}
----
+When the database requires a scram authentication and the scram client jar is not on the class/module path, the connection will be closed by the client.
+
include::queries.adoc[leveloffset=1]
== Returning clauses
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramAuthentication.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramAuthentication.java
new file mode 100644
index 0000000000..658977c4f5
--- /dev/null
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramAuthentication.java
@@ -0,0 +1,31 @@
+package io.vertx.pgclient.impl.auth.scram;
+
+import com.ongres.scram.client.ScramClient;
+import io.vertx.core.internal.logging.Logger;
+import io.vertx.core.internal.logging.LoggerFactory;
+
+public class ScramAuthentication {
+
+ private static final Logger logger = LoggerFactory.getLogger(ScramAuthentication.class);
+
+ public static ScramAuthentication INSTANCE;
+
+ static {
+ ScramAuthentication instance;
+ try {
+ ScramClient.MechanismsBuildStage builder = ScramClient.builder();
+ logger.debug("Scram authentication is available " + builder);
+ instance = new ScramAuthentication();
+ } catch (Throwable notFound) {
+ instance = null;
+ }
+ INSTANCE = instance;
+ }
+
+ private ScramAuthentication() {
+ }
+
+ public ScramSession session(String username, char[] password) {
+ return new ScramSessionImpl(username, password);
+ }
+}
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSession.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSession.java
new file mode 100644
index 0000000000..1cc05c5ec2
--- /dev/null
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSession.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 Julien Viet
+ *
+ * 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 io.vertx.pgclient.impl.auth.scram;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.vertx.pgclient.impl.codec.ScramClientInitialMessage;
+
+public interface ScramSession {
+
+ /*
+ * The client selects one of the supported mechanisms from the list,
+ * and sends a SASLInitialResponse message to the server.
+ * The message includes the name of the selected mechanism, and
+ * an optional Initial Client Response, if the selected mechanism uses that.
+ */
+ ScramClientInitialMessage createInitialSaslMessage(ByteBuf in, ChannelHandlerContext ctx);
+
+ /*
+ * One or more server-challenge and client-response message will follow.
+ * Each server-challenge is sent in an AuthenticationSASLContinue message,
+ * followed by a response from client in an SASLResponse message.
+ * The particulars of the messages are mechanism specific.
+ */
+ String receiveServerFirstMessage(ByteBuf in);
+
+ /*
+ * Finally, when the authentication exchange is completed successfully,
+ * the server sends an AuthenticationSASLFinal message, followed immediately by an AuthenticationOk message.
+ * The AuthenticationSASLFinal contains additional server-to-client data,
+ * whose content is particular to the selected authentication mechanism.
+ * If the authentication mechanism doesn't use additional data that's sent at completion,
+ * the AuthenticationSASLFinal message is not sent
+ */
+ void checkServerFinalMessage(ByteBuf in);
+
+}
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSessionImpl.java
similarity index 96%
rename from vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java
rename to vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSessionImpl.java
index b1d8d69689..66fb7e797f 100644
--- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSessionImpl.java
@@ -15,17 +15,7 @@
*
*/
-package io.vertx.pgclient.impl.util;
-
-import java.nio.charset.StandardCharsets;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
+package io.vertx.pgclient.impl.auth.scram;
import com.ongres.scram.client.ScramClient;
import com.ongres.scram.common.StringPreparation;
@@ -33,19 +23,28 @@
import com.ongres.scram.common.exception.ScramParseException;
import com.ongres.scram.common.exception.ScramServerErrorException;
import com.ongres.scram.common.util.TlsServerEndpoint;
-
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.ssl.SslHandler;
import io.vertx.pgclient.impl.codec.ScramClientInitialMessage;
+import io.vertx.pgclient.impl.util.Util;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
-public class ScramAuthentication {
+public class ScramSessionImpl implements ScramSession {
private final String username;
private final char[] password;
private ScramClient scramClient;
- public ScramAuthentication(String username, char[] password) {
+ public ScramSessionImpl(String username, char[] password) {
this.username = username;
this.password = password;
}
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java
index 2fa0332cb7..a92505d1cb 100644
--- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java
@@ -20,9 +20,11 @@
import java.nio.charset.StandardCharsets;
import io.netty.buffer.ByteBuf;
+import io.vertx.core.VertxException;
import io.vertx.pgclient.impl.PgDatabaseMetadata;
import io.vertx.pgclient.impl.PgSocketConnection;
-import io.vertx.pgclient.impl.util.ScramAuthentication;
+import io.vertx.pgclient.impl.auth.scram.ScramAuthentication;
+import io.vertx.pgclient.impl.auth.scram.ScramSession;
import io.vertx.sqlclient.internal.Connection;
import io.vertx.sqlclient.internal.command.CommandResponse;
import io.vertx.sqlclient.internal.command.InitCommand;
@@ -31,7 +33,7 @@ class InitCommandCodec extends PgCommandCodec {
private PgEncoder encoder;
private String encoding;
- private ScramAuthentication scramAuthentication;
+ private ScramSession scramSession;
InitCommandCodec(InitCommand cmd) {
super(cmd);
@@ -57,21 +59,26 @@ public void handleAuthenticationClearTextPassword() {
@Override
void handleAuthenticationSasl(ByteBuf in) {
- scramAuthentication = new ScramAuthentication(cmd.username(), cmd.password().toCharArray());
+ ScramAuthentication scramAuth = ScramAuthentication.INSTANCE;
+ if (scramAuth == null) {
+ // This will close the connection
+ throw new VertxException("Scram authentication not supported, missing com.ongres.scram:scram-client on the class/module path");
+ }
+ scramSession = scramAuth.session(cmd.username(), cmd.password().toCharArray());
encoder.writeScramClientInitialMessage(
- scramAuthentication.createInitialSaslMessage(in, encoder.channelHandlerContext()));
+ scramSession.createInitialSaslMessage(in, encoder.channelHandlerContext()));
encoder.flush();
}
@Override
void handleAuthenticationSaslContinue(ByteBuf in) {
- encoder.writeScramClientFinalMessage(new ScramClientFinalMessage(scramAuthentication.receiveServerFirstMessage(in)));
+ encoder.writeScramClientFinalMessage(new ScramClientFinalMessage(scramSession.receiveServerFirstMessage(in)));
encoder.flush();
}
@Override
void handleAuthenticationSaslFinal(ByteBuf in) {
- scramAuthentication.checkServerFinalMessage(in);
+ scramSession.checkServerFinalMessage(in);
}
@Override
diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/it/MissingScramTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/it/MissingScramTest.java
new file mode 100644
index 0000000000..f938a07163
--- /dev/null
+++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/it/MissingScramTest.java
@@ -0,0 +1,57 @@
+package io.vertx.pgclient.it;
+
+import io.vertx.core.Vertx;
+import io.vertx.ext.unit.Async;
+import io.vertx.ext.unit.TestContext;
+import io.vertx.ext.unit.junit.VertxUnitRunner;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.pgclient.PgConnection;
+import io.vertx.pgclient.junit.ContainerPgRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assume.assumeTrue;
+
+@RunWith(VertxUnitRunner.class)
+public class MissingScramTest {
+
+ @ClassRule
+ public static ContainerPgRule rule = new ContainerPgRule();
+
+ private Vertx vertx;
+
+ private PgConnectOptions options;
+
+ @Before
+ public void setup() throws Exception {
+ vertx = Vertx.vertx();
+ options = rule.options();
+ }
+
+ private PgConnectOptions options() {
+ return new PgConnectOptions(options);
+ }
+
+ @Test
+ public void testSaslConnectionFails(TestContext ctx) throws InterruptedException {
+ assumeTrue(ContainerPgRule.isAtLeastPg10());
+ Async async = ctx.async();
+ PgConnectOptions options = new PgConnectOptions(options());
+ options.setUser("saslscram");
+ options.setPassword("saslscrampwd");
+
+ PgConnection.connect(vertx, options).onComplete(
+ ctx.asyncAssertFailure(ar -> {
+ async.complete();
+ })
+ );
+ }
+
+ @After
+ public void teardown(TestContext ctx) {
+ vertx.close().onComplete(ctx.asyncAssertSuccess());
+ }
+}