Skip to content

Commit fa9e6a5

Browse files
committed
JVMCBC-1706 (followup) Prevent switching authenticator types
Motivation ---------- Prevent edge cases where the new authenticator doesn't support features required by old connections authenticated with the previous authenticator. For example, if we allowed switching from Password to Certificate, HTTP requests on old password-auth connections would be missing the auth header, since CertificateAuthenticator doesn't set HTTP headers. Modifications ------------- Add the authenticator type check. Update the API reference documentation. Change-Id: I2ab3c4c6fb1a7765384f9827408aadf67f05aa25 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/239130 Reviewed-by: David Nault <[email protected]> Tested-by: Build Bot <[email protected]>
1 parent 6c1cdca commit fa9e6a5

9 files changed

Lines changed: 82 additions & 11 deletions

File tree

core-io/src/main/java/com/couchbase/client/core/Core.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,12 +1027,24 @@ public Authenticator authenticator() {
10271027

10281028
@Override
10291029
public void authenticator(Authenticator newAuthenticator) {
1030+
requireSameClass(authenticator.wrapped(), newAuthenticator);
1031+
10301032
this.authenticator.setDelegate(newAuthenticator);
10311033
if (newAuthenticator.getSingleStepSaslAuthParameters() != null) {
10321034
authenticationRefreshTriggers.publish(AuthenticationRefreshTrigger.INSTANCE);
10331035
}
10341036
}
10351037

1038+
static void requireSameClass(Authenticator currentAuth, Authenticator newAuth) {
1039+
Class<?> currentClass = currentAuth.getClass();
1040+
Class<?> newClass = newAuth.getClass();
1041+
if (!newClass.equals(currentClass)) {
1042+
throw InvalidArgumentException.fromMessage (
1043+
"Switching authenticator types is not supported; cannot switch from " + currentClass + " to " + newClass
1044+
);
1045+
}
1046+
}
1047+
10361048
@Stability.Internal
10371049
public AppTelemetryCollector appTelemetryCollector() { return appTelemetry.collector; }
10381050

core-io/src/main/java/com/couchbase/client/core/CoreProtostellar.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import java.util.concurrent.CompletableFuture;
7070
import java.util.concurrent.ConcurrentHashMap;
7171

72+
import static com.couchbase.client.core.Core.requireSameClass;
7273
import static com.couchbase.client.core.api.CoreCouchbaseOps.checkConnectionStringScheme;
7374
import static com.couchbase.client.core.logging.RedactableArgument.redactSystem;
7475
import static com.couchbase.client.core.util.CbCollections.mapOf;
@@ -228,6 +229,8 @@ public Authenticator authenticator() {
228229

229230
@Override
230231
public void authenticator(Authenticator newAuthenticator) {
232+
requireSameClass(authenticator.wrapped(), newAuthenticator);
233+
231234
this.authenticator.setDelegate(newAuthenticator);
232235
}
233236

core-io/src/main/java/com/couchbase/client/core/env/DelegatingAuthenticator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void setDelegate(Authenticator delegate) {
5858
setDelegate(delegate);
5959
}
6060

61-
protected Authenticator wrapped() {
61+
public Authenticator wrapped() {
6262
return delegate;
6363
}
6464

java-client/src/main/java/com/couchbase/client/java/AsyncCluster.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ String clusterToStringHelper(Class clusterClass) {
265265
));
266266
}
267267

268+
/**
269+
* @see Cluster#authenticator(Authenticator)
270+
*/
268271
public void authenticator(Authenticator newAuthenticator) {
269272
couchbaseOps.authenticator(newAuthenticator);
270273
}

java-client/src/main/java/com/couchbase/client/java/Cluster.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
2525
import com.couchbase.client.core.diagnostics.PingResult;
2626
import com.couchbase.client.core.env.Authenticator;
27+
import com.couchbase.client.core.env.JwtAuthenticator;
2728
import com.couchbase.client.core.env.OwnedOrExternal;
2829
import com.couchbase.client.core.env.PasswordAuthenticator;
2930
import com.couchbase.client.core.env.SeedNode;
3031
import com.couchbase.client.core.error.CouchbaseException;
32+
import com.couchbase.client.core.error.InvalidArgumentException;
3133
import com.couchbase.client.core.error.TimeoutException;
3234
import com.couchbase.client.core.error.context.ReducedQueryErrorContext;
3335
import com.couchbase.client.core.util.ConnectionString;
@@ -317,6 +319,26 @@ public ReactiveCluster reactive() {
317319
return reactiveCluster;
318320
}
319321

322+
/**
323+
* Sets the authenticator used by the client.
324+
* <p>
325+
* Use this method to update a client certificate or JSON Web Token (JWT)
326+
* before it expires.
327+
* <p>
328+
* The new authenticator must be of the same class as the existing authenticator.
329+
* <p>
330+
* If the new authenticator is a {@link JwtAuthenticator}, the new credential is
331+
* immediately applied to all existing server connections.
332+
* <p>
333+
* <b>CAVEAT:</b> For all other types of authenticators, the new credential takes effect
334+
* at an unspecified time in the future. Existing connections might continue
335+
* to use the old credential indefinitely, while newly created connections
336+
* are authenticated with the new credential. Consequently, changing to a credential
337+
* with different privileges is strongly discouraged.
338+
*
339+
* @param newAuthenticator The authenticator bearing the updated user credential.
340+
* @throws InvalidArgumentException If the given authenticator is a different type than the existing authenticator.
341+
*/
320342
public void authenticator(Authenticator newAuthenticator) {
321343
asyncCluster.authenticator(newAuthenticator);
322344
}

java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ private ReactiveCluster(
188188
this.reactor = asyncCluster.environment();
189189
}
190190

191+
/**
192+
* @see Cluster#authenticator(Authenticator)
193+
*/
191194
public void authenticator(Authenticator newAuthenticator) {
192195
asyncCluster.authenticator(newAuthenticator);
193196
}

kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.couchbase.client.core.diagnostics.HealthPinger
2727
import com.couchbase.client.core.env.Authenticator
2828
import com.couchbase.client.core.env.CertificateAuthenticator
2929
import com.couchbase.client.core.env.ConnectionStringPropertyLoader
30+
import com.couchbase.client.core.env.JwtAuthenticator
3031
import com.couchbase.client.core.env.PasswordAuthenticator
3132
import com.couchbase.client.core.error.UnambiguousTimeoutException
3233
import com.couchbase.client.core.service.ServiceType
@@ -123,6 +124,26 @@ public class Cluster internal constructor(
123124
private val couchbaseOps = CoreCouchbaseOps.create(env, initialAuthenticator, connectionString)
124125
private val searchOps = couchbaseOps.searchOps(null)
125126

127+
/**
128+
* Sets the authenticator used by the client.
129+
*
130+
* Use this method to update a client certificate or JSON Web Token (JWT)
131+
* before it expires.
132+
*
133+
* The new authenticator must be of the same class as the existing authenticator.
134+
*
135+
* If the new authenticator is a [JwtAuthenticator], the new credential is
136+
* immediately applied to all existing server connections.
137+
*
138+
* **CAVEAT:** For all other types of authenticators, the new credential takes effect
139+
* at an unspecified time in the future. Existing connections might continue
140+
* to use the old credential indefinitely, while newly created connections
141+
* are authenticated with the new credential. Consequently, changing to a credential
142+
* with different privileges is strongly discouraged.
143+
*
144+
* @throws com.couchbase.client.core.error.InvalidArgumentException
145+
* If the given authenticator is a different type than the existing authenticator.
146+
*/
126147
public var authenticator: Authenticator
127148
@Deprecated("Property 'authenticator' is write-only.", level = DeprecationLevel.ERROR)
128149
get() = throw UnsupportedOperationException()

scala-client/src/main/scala/com/couchbase/client/scala/AsyncClusterBase.scala

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,7 @@ trait AsyncClusterBase { this: AsyncCluster =>
106106
new AsyncBucket(bucketName, couchbaseOps, environment)
107107
}
108108

109-
/** Sets a new authenticator, that will be used for any future connections created to Couchbase services.
110-
*
111-
* Note that existing connections will not be terminated.
112-
*
113-
* This method is thread-safe.
114-
*/
109+
/** @see [[ClusterBase.authenticator]] */
115110
def authenticator(authenticator: Authenticator): Try[Unit] = {
116111
Try(couchbaseOps.authenticator(authenticator))
117112
}

scala-client/src/main/scala/com/couchbase/client/scala/ClusterBase.scala

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.couchbase.client.scala
1919
import com.couchbase.client.core.annotation.Stability
2020
import com.couchbase.client.core.annotation.Stability.Uncommitted
2121
import com.couchbase.client.core.diagnostics._
22-
import com.couchbase.client.core.env.{Authenticator, PasswordAuthenticator}
22+
import com.couchbase.client.core.env.{Authenticator, JwtAuthenticator, PasswordAuthenticator}
2323
import com.couchbase.client.core.transaction.CoreTransactionsReactive
2424
import com.couchbase.client.core.util.ConnectionString
2525
import com.couchbase.client.core.util.ConnectionStringUtil.asConnectionString
@@ -82,11 +82,23 @@ trait ClusterBase { this: Cluster =>
8282
new Bucket(async.bucket(bucketName))
8383
}
8484

85-
/** Sets a new authenticator, that will be used for any future connections created to Couchbase services.
85+
/** Sets the authenticator used by the client.
8686
*
87-
* Note that existing connections will not be terminated.
87+
* Use this method to update a client certificate or JSON Web Token (JWT)
88+
* before it expires.
8889
*
89-
* This method is thread-safe.
90+
* The new authenticator must be of the same class as the existing authenticator.
91+
*
92+
* If the new authenticator is a [[JwtAuthenticator]], the new credential is
93+
* immediately applied to all existing server connections.
94+
*
95+
* <b>CAVEAT:</b> For all other types of authenticators, the new credential takes effect
96+
* at an unspecified time in the future. Existing connections might continue
97+
* to use the old credential indefinitely, while newly created connections
98+
* are authenticated with the new credential. Consequently, changing to a credential
99+
* with different privileges is strongly discouraged.
100+
*
101+
* @param authenticator The authenticator bearing the updated user credential.
90102
*/
91103
def authenticator(authenticator: Authenticator): Try[Unit] = {
92104
async.authenticator(authenticator)

0 commit comments

Comments
 (0)