Skip to content

Commit 7e74450

Browse files
authored
JAVA-2953: Promote ProgrammaticPlainTextAuthProvider to the public API and add credentials hot-reload (#1564)
1 parent f08db2e commit 7e74450

File tree

11 files changed

+330
-108
lines changed

11 files changed

+330
-108
lines changed

changelog/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### 4.13.0 (in progress)
66

7+
- [improvement] JAVA-2953: Promote ProgrammaticPlainTextAuthProvider to the public API and add
8+
credentials hot-reload
79
- [improvement] JAVA-2951: Accept multiple node state listeners, schema change listeners and request trackers
810

911
Merged from 4.12.x:

core/src/main/java/com/datastax/dse/driver/internal/core/auth/DseProgrammaticPlainTextAuthProvider.java

Lines changed: 0 additions & 32 deletions
This file was deleted.

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
* Common infrastructure for plain text auth providers.
3737
*
3838
* <p>This can be reused to write an implementation that retrieves the credentials from another
39-
* source than the configuration.
39+
* source than the configuration. The driver offers one built-in implementation: {@link
40+
* ProgrammaticPlainTextAuthProvider}.
4041
*/
4142
@ThreadSafe
4243
public abstract class PlainTextAuthProviderBase implements AuthProvider {
@@ -58,6 +59,9 @@ protected PlainTextAuthProviderBase(@NonNull String logPrefix) {
5859
* Retrieves the credentials from the underlying source.
5960
*
6061
* <p>This is invoked every time the driver opens a new connection.
62+
*
63+
* @param endPoint The endpoint being contacted.
64+
* @param serverAuthenticator The authenticator class sent by the endpoint.
6165
*/
6266
@NonNull
6367
protected abstract Credentials getCredentials(
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright DataStax, Inc.
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+
package com.datastax.oss.driver.api.core.auth;
17+
18+
import com.datastax.oss.driver.api.core.metadata.EndPoint;
19+
import com.datastax.oss.driver.api.core.session.SessionBuilder;
20+
import com.datastax.oss.driver.internal.core.util.Strings;
21+
import edu.umd.cs.findbugs.annotations.NonNull;
22+
import java.util.Objects;
23+
import net.jcip.annotations.ThreadSafe;
24+
25+
/**
26+
* A simple plaintext {@link AuthProvider} that receives the credentials programmatically instead of
27+
* pulling them from the configuration.
28+
*
29+
* <p>To use this class, create an instance with the appropriate credentials to use and pass it to
30+
* your session builder:
31+
*
32+
* <pre>
33+
* AuthProvider authProvider = new ProgrammaticPlainTextAuthProvider("...", "...");
34+
* CqlSession session =
35+
* CqlSession.builder()
36+
* .addContactEndPoints(...)
37+
* .withAuthProvider(authProvider)
38+
* .build();
39+
* </pre>
40+
*
41+
* <p>It also offers the possibility of changing the credentials at runtime. The new credentials
42+
* will be used for all connections initiated after the change.
43+
*
44+
* <p>Implementation Note: this implementation is not particularly suited for highly-sensitive
45+
* applications: it stores the credentials to use as private fields, and even if the fields are char
46+
* arrays rather than strings to make it difficult to dump their contents, they are never cleared
47+
* until the provider itself is garbage-collected, which typically only happens when the session is
48+
* closed.
49+
*
50+
* @see SessionBuilder#withAuthProvider(AuthProvider)
51+
* @see SessionBuilder#withAuthCredentials(String, String)
52+
* @see SessionBuilder#withAuthCredentials(String, String, String)
53+
*/
54+
@ThreadSafe
55+
public class ProgrammaticPlainTextAuthProvider extends PlainTextAuthProviderBase {
56+
57+
private volatile char[] username;
58+
private volatile char[] password;
59+
private volatile char[] authorizationId;
60+
61+
/** Builds an instance for simple username/password authentication. */
62+
public ProgrammaticPlainTextAuthProvider(@NonNull String username, @NonNull String password) {
63+
this(username, password, "");
64+
}
65+
66+
/**
67+
* Builds an instance for username/password authentication, and proxy authentication with the
68+
* given authorizationId.
69+
*
70+
* <p>This feature is only available with Datastax Enterprise. If the target server is Apache
71+
* Cassandra, use {@link #ProgrammaticPlainTextAuthProvider(String, String)} instead, or set the
72+
* authorizationId to an empty string.
73+
*/
74+
public ProgrammaticPlainTextAuthProvider(
75+
@NonNull String username, @NonNull String password, @NonNull String authorizationId) {
76+
// This will typically be built before the session so we don't know the log prefix yet. Pass an
77+
// empty string, it's only used in one log message.
78+
super("");
79+
this.username = Strings.requireNotEmpty(username, "username").toCharArray();
80+
this.password = Strings.requireNotEmpty(password, "password").toCharArray();
81+
this.authorizationId =
82+
Objects.requireNonNull(authorizationId, "authorizationId cannot be null").toCharArray();
83+
}
84+
85+
/**
86+
* Changes the username.
87+
*
88+
* <p>The new credentials will be used for all connections initiated after this method was called.
89+
*
90+
* @param username the new name.
91+
*/
92+
public void setUsername(@NonNull String username) {
93+
this.username = Strings.requireNotEmpty(username, "username").toCharArray();
94+
}
95+
96+
/**
97+
* Changes the password.
98+
*
99+
* <p>The new credentials will be used for all connections initiated after this method was called.
100+
*
101+
* @param password the new password.
102+
*/
103+
public void setPassword(@NonNull String password) {
104+
this.password = Strings.requireNotEmpty(password, "password").toCharArray();
105+
}
106+
107+
/**
108+
* Changes the authorization id.
109+
*
110+
* <p>The new credentials will be used for all connections initiated after this method was called.
111+
*
112+
* <p>This feature is only available with Datastax Enterprise. If the target server is Apache
113+
* Cassandra, this method should not be used.
114+
*
115+
* @param authorizationId the new authorization id.
116+
*/
117+
public void setAuthorizationId(@NonNull String authorizationId) {
118+
this.authorizationId =
119+
Objects.requireNonNull(authorizationId, "authorizationId cannot be null").toCharArray();
120+
}
121+
122+
/**
123+
* {@inheritDoc}
124+
*
125+
* <p>This implementation disregards the endpoint being connected to as well as the authenticator
126+
* class sent by the server, and always returns the same credentials.
127+
*/
128+
@NonNull
129+
@Override
130+
protected Credentials getCredentials(
131+
@NonNull EndPoint endPoint, @NonNull String serverAuthenticator) {
132+
return new Credentials(username.clone(), password.clone(), authorizationId.clone());
133+
}
134+
}

core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.datastax.oss.driver.api.core.CqlSession;
2020
import com.datastax.oss.driver.api.core.auth.AuthProvider;
2121
import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase;
22+
import com.datastax.oss.driver.api.core.auth.ProgrammaticPlainTextAuthProvider;
2223
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
2324
import com.datastax.oss.driver.api.core.config.DriverConfig;
2425
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
@@ -37,7 +38,6 @@
3738
import com.datastax.oss.driver.api.core.type.codec.registry.MutableCodecRegistry;
3839
import com.datastax.oss.driver.api.core.uuid.Uuids;
3940
import com.datastax.oss.driver.internal.core.ContactPoints;
40-
import com.datastax.oss.driver.internal.core.auth.ProgrammaticPlainTextAuthProvider;
4141
import com.datastax.oss.driver.internal.core.config.cloud.CloudConfig;
4242
import com.datastax.oss.driver.internal.core.config.cloud.CloudConfigFactory;
4343
import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader;

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@
4646
* }
4747
* </pre>
4848
*
49-
* See {@code reference.conf} (in the manual or core driver JAR) for more details.
49+
* The authentication provider cannot be changed at runtime; however, the credentials can be changed
50+
* at runtime: the new ones will be used for new connection attempts once the configuration gets
51+
* {@linkplain com.datastax.oss.driver.api.core.config.DriverConfigLoader#reload() reloaded}.
52+
*
53+
* <p>See {@code reference.conf} (in the manual or core driver JAR) for more details.
5054
*/
5155
@ThreadSafe
5256
public class PlainTextAuthProvider extends PlainTextAuthProviderBase {

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

Lines changed: 0 additions & 66 deletions
This file was deleted.

core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
1919
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet;
2020
import java.util.Locale;
21+
import java.util.Objects;
2122

2223
public class Strings {
2324

@@ -255,6 +256,21 @@ public static boolean isLongLiteral(String str) {
255256
return true;
256257
}
257258

259+
/**
260+
* Checks whether the given text is not null and not empty.
261+
*
262+
* @param text The text to check.
263+
* @param name The name of the argument.
264+
* @return The text (for method chaining).
265+
*/
266+
public static String requireNotEmpty(String text, String name) {
267+
Objects.requireNonNull(text, name + " cannot be null");
268+
if (text.isEmpty()) {
269+
throw new IllegalArgumentException(name + " cannot be empty");
270+
}
271+
return text;
272+
}
273+
258274
private Strings() {}
259275

260276
private static final ImmutableSet<String> RESERVED_KEYWORDS =

0 commit comments

Comments
 (0)