Skip to content

Commit a2bc0ba

Browse files
committed
JAVA-1792: Add AuthProvider callback to handle missing challenge from server
1 parent e3d1168 commit a2bc0ba

File tree

7 files changed

+43
-59
lines changed

7 files changed

+43
-59
lines changed

changelog/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### 4.0.0-alpha4 (in progress)
66

7+
- [improvement] JAVA-1792: Add AuthProvider callback to handle missing challenge from server
78
- [improvement] JAVA-1775: Assume default packages for built-in policies
89
- [improvement] JAVA-1774: Standardize policy locations
910
- [improvement] JAVA-1798: Allow passing the default LBP filter as a session builder argument

core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.datastax.oss.driver.api.core.auth;
1717

18+
import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy;
1819
import com.datastax.oss.driver.internal.core.auth.PlainTextAuthProvider;
1920
import java.net.SocketAddress;
2021

@@ -35,4 +36,22 @@ public interface AuthProvider {
3536
*/
3637
Authenticator newAuthenticator(SocketAddress host, String serverAuthenticator)
3738
throws AuthenticationException;
39+
40+
/**
41+
* What to do if the server does not send back an authentication challenge (in other words, lets
42+
* the client connect without any form of authentication).
43+
*
44+
* <p>This is suspicious because having authentication enabled on the client but not on the server
45+
* is probably a configuration mistake.
46+
*
47+
* <p>Provider implementations are free to handle this however they want; typical approaches are:
48+
*
49+
* <ul>
50+
* <li>ignoring;
51+
* <li>logging a warning;
52+
* <li>throwing an {@link AuthenticationException} to abort the connection (but note that it
53+
* will be retried according to the {@link ReconnectionPolicy}).
54+
* </ul>
55+
*/
56+
void onMissingChallenge(SocketAddress host) throws AuthenticationException;
3857
}

core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ public enum DefaultDriverOption implements DriverOption {
109109
AUTH_PROVIDER_CLASS("protocol.auth-provider.class", false),
110110
AUTH_PROVIDER_USER_NAME("protocol.auth-provider.username", false),
111111
AUTH_PROVIDER_PASSWORD("protocol.auth-provider.password", false),
112-
AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH("protocol.auth-provider.warn-if-no-server-auth", false),
113112

114113
SSL_ENGINE_FACTORY_CLASS("ssl-engine-factory.class", false),
115114
SSL_CIPHER_SUITES("ssl-engine-factory.cipher-suites", false),

core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.net.SocketAddress;
2626
import java.nio.ByteBuffer;
2727
import net.jcip.annotations.ThreadSafe;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
2830

2931
/**
3032
* A simple authentication provider that supports SASL authentication using the PLAIN mechanism for
@@ -48,10 +50,14 @@
4850
@ThreadSafe
4951
public class PlainTextAuthProvider implements AuthProvider {
5052

53+
private static final Logger LOG = LoggerFactory.getLogger(PlainTextAuthProvider.class);
54+
55+
private final String logPrefix;
5156
private final DriverConfigProfile config;
5257

5358
/** Builds a new instance. */
5459
public PlainTextAuthProvider(DriverContext context) {
60+
this.logPrefix = context.sessionName();
5561
this.config = context.config().getDefaultProfile();
5662
}
5763

@@ -62,6 +68,15 @@ public Authenticator newAuthenticator(SocketAddress host, String serverAuthentic
6268
return new PlainTextAuthenticator(username, password);
6369
}
6470

71+
@Override
72+
public void onMissingChallenge(SocketAddress host) {
73+
LOG.warn(
74+
"[{}] {} did not send an authentication challenge; "
75+
+ "This is suspicious because the driver expects authentication",
76+
logPrefix,
77+
host);
78+
}
79+
6580
private static class PlainTextAuthenticator implements SyncAuthenticator {
6681

6782
private final ByteBuffer initialToken;

core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ class ProtocolInitHandler extends ConnectInitHandler {
6161

6262
private final InternalDriverContext context;
6363
private final long timeoutMillis;
64-
private final boolean warnIfNoServerAuth;
6564
private final ProtocolVersion initialProtocolVersion;
6665
private final DriverChannelOptions options;
6766
// might be null if this is the first channel to this cluster
@@ -83,8 +82,6 @@ class ProtocolInitHandler extends ConnectInitHandler {
8382

8483
this.timeoutMillis =
8584
defaultConfig.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis();
86-
this.warnIfNoServerAuth =
87-
defaultConfig.getBoolean(DefaultDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH);
8885
this.initialProtocolVersion = protocolVersion;
8986
this.expectedClusterName = expectedClusterName;
9087
this.options = options;
@@ -168,15 +165,9 @@ void onResponse(Message response) {
168165
ProtocolUtils.opcodeString(response.opcode));
169166
try {
170167
if (step == Step.STARTUP && response instanceof Ready) {
171-
if (warnIfNoServerAuth && context.authProvider().isPresent()) {
172-
LOG.warn(
173-
"[{}] {} did not send an authentication challenge; "
174-
+ "This is suspicious because the driver expects authentication "
175-
+ "(configured auth provider = {})",
176-
logPrefix,
177-
channel.remoteAddress(),
178-
context.authProvider().get().getClass().getName());
179-
}
168+
context
169+
.authProvider()
170+
.ifPresent(provider -> provider.onMissingChallenge(channel.remoteAddress()));
180171
step = Step.GET_CLUSTER_NAME;
181172
send();
182173
} else if (step == Step.STARTUP && response instanceof Authenticate) {

core/src/main/resources/reference.conf

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,6 @@ datastax-java-driver {
7474
# Sample configuration for the plain-text provider:
7575
// username = cassandra
7676
// password = cassandra
77-
78-
# Whether to log a warning if an authentication provider is configured on the driver side, but
79-
# the server does not issue an authentication challenge (i.e. lets the driver connect without
80-
# any form of authentication).
81-
#
82-
# This is intended as a help to detect server configuration issues. The warning will be logged
83-
# for every faulty node, and each time a new connection is created.
84-
#
85-
# This option can be changed at runtime, the new value will be used for new connections created
86-
# after the change.
87-
warn-if-no-server-auth = true
8877
}
8978

9079
# The compressor to use for protocol frames. If it is not qualified, the driver assumes that it

core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@
1616
package com.datastax.oss.driver.internal.core.channel;
1717

1818
import static com.datastax.oss.driver.Assertions.assertThat;
19-
import static org.mockito.Mockito.atLeast;
2019

21-
import ch.qos.logback.classic.Level;
22-
import ch.qos.logback.classic.Logger;
23-
import ch.qos.logback.classic.spi.ILoggingEvent;
24-
import ch.qos.logback.core.Appender;
2520
import com.datastax.oss.driver.api.core.CqlIdentifier;
2621
import com.datastax.oss.driver.api.core.DefaultProtocolVersion;
2722
import com.datastax.oss.driver.api.core.InvalidKeyspaceException;
@@ -56,16 +51,11 @@
5651
import java.util.List;
5752
import java.util.Optional;
5853
import java.util.concurrent.TimeUnit;
59-
import org.assertj.core.api.filter.Filters;
60-
import org.junit.After;
6154
import org.junit.Before;
6255
import org.junit.Test;
63-
import org.mockito.ArgumentCaptor;
64-
import org.mockito.Captor;
6556
import org.mockito.Mock;
6657
import org.mockito.Mockito;
6758
import org.mockito.MockitoAnnotations;
68-
import org.slf4j.LoggerFactory;
6959

7060
public class ProtocolInitHandlerTest extends ChannelHandlerTestBase {
7161

@@ -75,13 +65,10 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase {
7565
@Mock private DriverConfig driverConfig;
7666
@Mock private DriverConfigProfile defaultConfigProfile;
7767
@Mock private Compressor<ByteBuf> compressor;
78-
@Mock private Appender<ILoggingEvent> appender;
79-
@Captor private ArgumentCaptor<ILoggingEvent> loggingEventCaptor;
8068

8169
private ProtocolVersionRegistry protocolVersionRegistry =
8270
new CassandraProtocolVersionRegistry("test");
8371
private HeartbeatHandler heartbeatHandler;
84-
private Logger logger;
8572

8673
@Before
8774
@Override
@@ -96,10 +83,6 @@ public void setup() {
9683
Mockito.when(
9784
defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_HEARTBEAT_INTERVAL))
9885
.thenReturn(Duration.ofSeconds(30));
99-
Mockito.when(
100-
defaultConfigProfile.getBoolean(
101-
DefaultDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH))
102-
.thenReturn(true);
10386
Mockito.when(internalDriverContext.protocolVersionRegistry())
10487
.thenReturn(protocolVersionRegistry);
10588
Mockito.when(internalDriverContext.compressor()).thenReturn(compressor);
@@ -119,14 +102,6 @@ public void setup() {
119102
"test"));
120103

121104
heartbeatHandler = new HeartbeatHandler(defaultConfigProfile);
122-
123-
logger = (Logger) LoggerFactory.getLogger(ProtocolInitHandler.class);
124-
logger.addAppender(appender);
125-
}
126-
127-
@After
128-
public void teardown() {
129-
logger.detachAppender(appender);
130105
}
131106

132107
@Test
@@ -321,7 +296,7 @@ public void should_initialize_with_authentication() {
321296
}
322297

323298
@Test
324-
public void should_warn_if_auth_configured_but_server_does_not_send_challenge() {
299+
public void should_invoke_auth_provider_when_server_does_not_send_challenge() {
325300
channel
326301
.pipeline()
327302
.addLast(
@@ -341,16 +316,11 @@ public void should_warn_if_auth_configured_but_server_does_not_send_challenge()
341316
Frame requestFrame = readOutboundFrame();
342317
assertThat(requestFrame.message).isInstanceOf(Startup.class);
343318

344-
// Simulate a READY response, a warning should be logged
319+
// Simulate a READY response, the provider should be notified
345320
writeInboundFrame(buildInboundFrame(requestFrame, new Ready()));
346-
Mockito.verify(appender, atLeast(1)).doAppend(loggingEventCaptor.capture());
347-
Iterable<ILoggingEvent> warnLogs =
348-
Filters.filter(loggingEventCaptor.getAllValues()).with("level", Level.WARN).get();
349-
assertThat(warnLogs).hasSize(1);
350-
assertThat(warnLogs.iterator().next().getFormattedMessage())
351-
.contains("did not send an authentication challenge");
352-
353-
// Apart from the warning, init should proceed normally
321+
Mockito.verify(authProvider).onMissingChallenge(channel.remoteAddress());
322+
323+
// Since our mock does nothing, init should proceed normally
354324
requestFrame = readOutboundFrame();
355325
assertThat(requestFrame.message).isInstanceOf(Query.class);
356326
writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("someClusterName"));

0 commit comments

Comments
 (0)