Skip to content

Commit 529bb36

Browse files
vietjtsegismont
authored andcommitted
Let the client run fine when the scram dependency is not available.
Motivation: The scram dependency is optional and the module descriptor declares it as static, however in practice when the scram dependency is not available the client cannot be used because the ScramAuthentication is unavailable due to a classloading error. The client should be resilient to this common case and continue to work when scram is not available. Changes: Introduce an indirection level, the scram interactions are now handled by a ScramSession and the ScramAuthentication is a factory for sessions. When the scram dependency is not available the ScramAuthentication instance is null, the InitCommandCodec can check this field and throw an exception that will close the connection (as per the protocol requirements). Result: The client runs fine without the scram dependency on the class/module path.
1 parent 813f9e5 commit 529bb36

File tree

7 files changed

+186
-23
lines changed

7 files changed

+186
-23
lines changed

vertx-pg-client/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,21 @@
196196
</environmentVariables>
197197
</configuration>
198198
</execution>
199+
<execution>
200+
<id>missing-scram-test</id>
201+
<goals>
202+
<goal>integration-test</goal>
203+
</goals>
204+
<phase>integration-test</phase>
205+
<configuration>
206+
<includes>
207+
<include>io/vertx/pgclient/it/MissingScramTest.java</include>
208+
</includes>
209+
<classpathDependencyExcludes>
210+
<classpathDependencyExclude>com.ongres.scram:scram-client</classpathDependencyExclude>
211+
</classpathDependencyExcludes>
212+
</configuration>
213+
</execution>
199214
</executions>
200215
</plugin>
201216
</plugins>

vertx-pg-client/src/main/asciidoc/index.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ dependencies {
254254
}
255255
----
256256

257+
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.
258+
257259
include::queries.adoc[leveloffset=1]
258260

259261
== Returning clauses
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.vertx.pgclient.impl.auth.scram;
2+
3+
import com.ongres.scram.client.ScramClient;
4+
import io.vertx.core.impl.logging.Logger;
5+
import io.vertx.core.impl.logging.LoggerFactory;
6+
7+
public class ScramAuthentication {
8+
9+
private static final Logger logger = LoggerFactory.getLogger(ScramAuthentication.class);
10+
11+
public static ScramAuthentication INSTANCE;
12+
13+
static {
14+
ScramAuthentication instance;
15+
try {
16+
ScramClient.MechanismsBuildStage builder = ScramClient.builder();
17+
logger.debug("Scram authentication is available " + builder);
18+
instance = new ScramAuthentication();
19+
} catch (Throwable notFound) {
20+
instance = null;
21+
}
22+
INSTANCE = instance;
23+
}
24+
25+
private ScramAuthentication() {
26+
}
27+
28+
public ScramSession session(String username, char[] password) {
29+
return new ScramSessionImpl(username, password);
30+
}
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (C) 2017 Julien Viet
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package io.vertx.pgclient.impl.auth.scram;
19+
20+
import io.netty.buffer.ByteBuf;
21+
import io.netty.channel.ChannelHandlerContext;
22+
import io.vertx.pgclient.impl.codec.ScramClientInitialMessage;
23+
24+
public interface ScramSession {
25+
26+
/*
27+
* The client selects one of the supported mechanisms from the list,
28+
* and sends a SASLInitialResponse message to the server.
29+
* The message includes the name of the selected mechanism, and
30+
* an optional Initial Client Response, if the selected mechanism uses that.
31+
*/
32+
ScramClientInitialMessage createInitialSaslMessage(ByteBuf in, ChannelHandlerContext ctx);
33+
34+
/*
35+
* One or more server-challenge and client-response message will follow.
36+
* Each server-challenge is sent in an AuthenticationSASLContinue message,
37+
* followed by a response from client in an SASLResponse message.
38+
* The particulars of the messages are mechanism specific.
39+
*/
40+
String receiveServerFirstMessage(ByteBuf in);
41+
42+
/*
43+
* Finally, when the authentication exchange is completed successfully,
44+
* the server sends an AuthenticationSASLFinal message, followed immediately by an AuthenticationOk message.
45+
* The AuthenticationSASLFinal contains additional server-to-client data,
46+
* whose content is particular to the selected authentication mechanism.
47+
* If the authentication mechanism doesn't use additional data that's sent at completion,
48+
* the AuthenticationSASLFinal message is not sent
49+
*/
50+
void checkServerFinalMessage(ByteBuf in);
51+
52+
}

vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java renamed to vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSessionImpl.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,36 @@
1515
*
1616
*/
1717

18-
package io.vertx.pgclient.impl.util;
19-
20-
import java.nio.charset.StandardCharsets;
21-
import java.security.cert.Certificate;
22-
import java.security.cert.CertificateEncodingException;
23-
import java.security.cert.X509Certificate;
24-
import java.util.ArrayList;
25-
import java.util.List;
26-
27-
import javax.net.ssl.SSLException;
28-
import javax.net.ssl.SSLSession;
18+
package io.vertx.pgclient.impl.auth.scram;
2919

3020
import com.ongres.scram.client.ScramClient;
3121
import com.ongres.scram.common.StringPreparation;
3222
import com.ongres.scram.common.exception.ScramInvalidServerSignatureException;
3323
import com.ongres.scram.common.exception.ScramParseException;
3424
import com.ongres.scram.common.exception.ScramServerErrorException;
3525
import com.ongres.scram.common.util.TlsServerEndpoint;
36-
3726
import io.netty.buffer.ByteBuf;
3827
import io.netty.channel.ChannelHandlerContext;
3928
import io.netty.handler.ssl.SslHandler;
4029
import io.vertx.pgclient.impl.codec.ScramClientInitialMessage;
30+
import io.vertx.pgclient.impl.util.Util;
31+
32+
import javax.net.ssl.SSLException;
33+
import javax.net.ssl.SSLSession;
34+
import java.nio.charset.StandardCharsets;
35+
import java.security.cert.Certificate;
36+
import java.security.cert.CertificateEncodingException;
37+
import java.security.cert.X509Certificate;
38+
import java.util.ArrayList;
39+
import java.util.List;
4140

42-
public class ScramAuthentication {
41+
public class ScramSessionImpl implements ScramSession {
4342

4443
private final String username;
4544
private final char[] password;
4645
private ScramClient scramClient;
4746

48-
public ScramAuthentication(String username, char[] password) {
47+
public ScramSessionImpl(String username, char[] password) {
4948
this.username = username;
5049
this.password = password;
5150
}

vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,24 @@
1616
*/
1717
package io.vertx.pgclient.impl.codec;
1818

19-
import java.nio.charset.Charset;
20-
import java.nio.charset.StandardCharsets;
21-
2219
import io.netty.buffer.ByteBuf;
20+
import io.vertx.core.VertxException;
2321
import io.vertx.pgclient.impl.PgDatabaseMetadata;
2422
import io.vertx.pgclient.impl.PgSocketConnection;
25-
import io.vertx.pgclient.impl.util.ScramAuthentication;
23+
import io.vertx.pgclient.impl.auth.scram.ScramAuthentication;
24+
import io.vertx.pgclient.impl.auth.scram.ScramSession;
2625
import io.vertx.sqlclient.impl.Connection;
2726
import io.vertx.sqlclient.impl.command.CommandResponse;
2827
import io.vertx.sqlclient.impl.command.InitCommand;
2928

29+
import java.nio.charset.Charset;
30+
import java.nio.charset.StandardCharsets;
31+
3032
class InitCommandCodec extends PgCommandCodec<Connection, InitCommand> {
3133

3234
private PgEncoder encoder;
3335
private String encoding;
34-
private ScramAuthentication scramAuthentication;
36+
private ScramSession scramSession;
3537

3638
InitCommandCodec(InitCommand cmd) {
3739
super(cmd);
@@ -57,21 +59,26 @@ public void handleAuthenticationClearTextPassword() {
5759

5860
@Override
5961
void handleAuthenticationSasl(ByteBuf in) {
60-
scramAuthentication = new ScramAuthentication(cmd.username(), cmd.password().toCharArray());
62+
ScramAuthentication scramAuth = ScramAuthentication.INSTANCE;
63+
if (scramAuth == null) {
64+
// This will close the connection
65+
throw new VertxException("Scram authentication not supported, missing com.ongres.scram:scram-client on the class/module path");
66+
}
67+
scramSession = scramAuth.session(cmd.username(), cmd.password().toCharArray());
6168
encoder.writeScramClientInitialMessage(
62-
scramAuthentication.createInitialSaslMessage(in, encoder.channelHandlerContext()));
69+
scramSession.createInitialSaslMessage(in, encoder.channelHandlerContext()));
6370
encoder.flush();
6471
}
6572

6673
@Override
6774
void handleAuthenticationSaslContinue(ByteBuf in) {
68-
encoder.writeScramClientFinalMessage(new ScramClientFinalMessage(scramAuthentication.receiveServerFirstMessage(in)));
75+
encoder.writeScramClientFinalMessage(new ScramClientFinalMessage(scramSession.receiveServerFirstMessage(in)));
6976
encoder.flush();
7077
}
7178

7279
@Override
7380
void handleAuthenticationSaslFinal(ByteBuf in) {
74-
scramAuthentication.checkServerFinalMessage(in);
81+
scramSession.checkServerFinalMessage(in);
7582
}
7683

7784
@Override
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.vertx.pgclient.it;
2+
3+
import io.vertx.core.Vertx;
4+
import io.vertx.ext.unit.Async;
5+
import io.vertx.ext.unit.TestContext;
6+
import io.vertx.ext.unit.junit.VertxUnitRunner;
7+
import io.vertx.pgclient.PgConnectOptions;
8+
import io.vertx.pgclient.PgConnection;
9+
import io.vertx.pgclient.junit.ContainerPgRule;
10+
import org.junit.After;
11+
import org.junit.Before;
12+
import org.junit.ClassRule;
13+
import org.junit.Test;
14+
import org.junit.runner.RunWith;
15+
16+
import static org.junit.Assume.assumeTrue;
17+
18+
@RunWith(VertxUnitRunner.class)
19+
public class MissingScramTest {
20+
21+
@ClassRule
22+
public static ContainerPgRule rule = new ContainerPgRule();
23+
24+
private Vertx vertx;
25+
26+
private PgConnectOptions options;
27+
28+
@Before
29+
public void setup() throws Exception {
30+
vertx = Vertx.vertx();
31+
options = rule.options();
32+
}
33+
34+
private PgConnectOptions options() {
35+
return new PgConnectOptions(options);
36+
}
37+
38+
@Test
39+
public void testSaslConnectionFails(TestContext ctx) throws InterruptedException {
40+
assumeTrue(ContainerPgRule.isAtLeastPg10());
41+
Async async = ctx.async();
42+
PgConnectOptions options = new PgConnectOptions(options());
43+
options.setUser("saslscram");
44+
options.setPassword("saslscrampwd");
45+
46+
PgConnection.connect(vertx, options).onComplete(
47+
ctx.asyncAssertFailure(ar -> {
48+
async.complete();
49+
})
50+
);
51+
}
52+
53+
@After
54+
public void teardown(TestContext ctx) {
55+
vertx.close().onComplete(ctx.asyncAssertSuccess());
56+
}
57+
}

0 commit comments

Comments
 (0)