Skip to content

Commit d4644da

Browse files
authored
Introduce credentials provider (#3224)
References: 1. #1602 and related PRs. Current PR is probably better than handling in JedisFactory 2. redis/redis-py#2261 - main reason of this PR 3. redis/lettuce#1774 4. #632 --- * Introduce credentials provider * use volatile * Test in Sentineled mode * Support CharSequence in DefaultRedisCredentials * Added doc for prepare() and cleanUp() * Test the provider interface * Added example * Removed deprecations
1 parent 1d898f1 commit d4644da

15 files changed

+439
-92
lines changed

src/main/java/redis/clients/jedis/CommandArguments.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ public ProtocolCommand getCommand() {
2828
}
2929

3030
public CommandArguments add(Object arg) {
31-
if (arg instanceof Rawable) {
31+
if (arg == null) {
32+
throw new IllegalArgumentException("null is not a valid argument.");
33+
} else if (arg instanceof Rawable) {
3234
args.add((Rawable) arg);
3335
} else if (arg instanceof byte[]) {
3436
args.add(RawableFactory.from((byte[]) arg));
@@ -37,9 +39,6 @@ public CommandArguments add(Object arg) {
3739
} else if (arg instanceof Boolean) {
3840
args.add(RawableFactory.from(Integer.toString((Boolean) arg ? 1 : 0)));
3941
} else {
40-
if (arg == null) {
41-
throw new IllegalArgumentException("null is not a valid argument.");
42-
}
4342
args.add(RawableFactory.from(String.valueOf(arg)));
4443
}
4544
return this;

src/main/java/redis/clients/jedis/Connection.java

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import java.io.IOException;
55
import java.net.Socket;
66
import java.net.SocketException;
7+
import java.nio.ByteBuffer;
8+
import java.nio.CharBuffer;
79
import java.util.ArrayList;
10+
import java.util.Arrays;
811
import java.util.List;
12+
import java.util.function.Supplier;
913

1014
import redis.clients.jedis.args.Rawable;
1115
import redis.clients.jedis.commands.ProtocolCommand;
@@ -336,15 +340,16 @@ public List<Object> getMany(final int count) {
336340
private void initializeFromClientConfig(JedisClientConfig config) {
337341
try {
338342
connect();
339-
String password = config.getPassword();
340-
if (password != null) {
341-
String user = config.getUser();
342-
if (user != null) {
343-
auth(user, password);
344-
} else {
345-
auth(password);
346-
}
343+
344+
Supplier<RedisCredentials> credentialsProvider = config.getCredentialsProvider();
345+
if (credentialsProvider instanceof RedisCredentialsProvider) {
346+
((RedisCredentialsProvider) credentialsProvider).prepare();
347+
auth(credentialsProvider);
348+
((RedisCredentialsProvider) credentialsProvider).cleanUp();
349+
} else {
350+
auth(credentialsProvider);
347351
}
352+
348353
int dbIndex = config.getDatabase();
349354
if (dbIndex > 0) {
350355
select(dbIndex);
@@ -354,27 +359,41 @@ private void initializeFromClientConfig(JedisClientConfig config) {
354359
// TODO: need to figure out something without encoding
355360
clientSetname(clientName);
356361
}
362+
357363
} catch (JedisException je) {
358364
try {
359365
if (isConnected()) {
360366
quit();
361367
}
362368
disconnect();
363369
} catch (Exception e) {
364-
//
370+
// the first exception 'je' will be thrown
365371
}
366372
throw je;
367373
}
368374
}
369375

370-
private String auth(final String password) {
371-
sendCommand(Protocol.Command.AUTH, password);
372-
return getStatusCodeReply();
373-
}
376+
private void auth(final Supplier<RedisCredentials> credentialsProvider) {
377+
RedisCredentials credentials = credentialsProvider.get();
378+
if (credentials == null || credentials.getPassword() == null) return;
374379

375-
private String auth(final String user, final String password) {
376-
sendCommand(Protocol.Command.AUTH, user, password);
377-
return getStatusCodeReply();
380+
// Source: https://stackoverflow.com/a/9670279/4021802
381+
ByteBuffer passBuf = Protocol.CHARSET.encode(CharBuffer.wrap(credentials.getPassword()));
382+
byte[] rawPass = Arrays.copyOfRange(passBuf.array(), passBuf.position(), passBuf.limit());
383+
Arrays.fill(passBuf.array(), (byte) 0); // clear sensitive data
384+
385+
if (credentials.getUser() != null) {
386+
sendCommand(Protocol.Command.AUTH, SafeEncoder.encode(credentials.getUser()), rawPass);
387+
} else {
388+
sendCommand(Protocol.Command.AUTH, rawPass);
389+
}
390+
391+
Arrays.fill(rawPass, (byte) 0); // clear sensitive data
392+
393+
// clearing 'char[] credentials.getPassword()' should be
394+
// handled in RedisCredentialsProvider.cleanUp()
395+
396+
getStatusCodeReply(); // OK
378397
}
379398

380399
public String select(final int index) {

src/main/java/redis/clients/jedis/ConnectionFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public ConnectionFactory(final JedisSocketFactory jedisSocketFactory, final Jedi
3535
this.jedisSocketFactory = jedisSocketFactory;
3636
}
3737

38+
/**
39+
* @deprecated Use {@link RedisCredentialsProvider} through
40+
* {@link JedisClientConfig#getCredentialsProvider()}.
41+
*/
42+
@Deprecated
3843
public void setPassword(final String password) {
3944
this.clientConfig.updatePassword(password);
4045
}

src/main/java/redis/clients/jedis/DefaultJedisClientConfig.java

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package redis.clients.jedis;
22

3-
import java.util.Objects;
3+
import java.util.function.Supplier;
44
import javax.net.ssl.HostnameVerifier;
55
import javax.net.ssl.SSLParameters;
66
import javax.net.ssl.SSLSocketFactory;
@@ -11,8 +11,7 @@ public final class DefaultJedisClientConfig implements JedisClientConfig {
1111
private final int socketTimeoutMillis;
1212
private final int blockingSocketTimeoutMillis;
1313

14-
private final String user;
15-
private volatile String password;
14+
private volatile Supplier<RedisCredentials> credentialsProvider;
1615
private final int database;
1716
private final String clientName;
1817

@@ -24,14 +23,13 @@ public final class DefaultJedisClientConfig implements JedisClientConfig {
2423
private final HostAndPortMapper hostAndPortMapper;
2524

2625
private DefaultJedisClientConfig(int connectionTimeoutMillis, int soTimeoutMillis,
27-
int blockingSocketTimeoutMillis, String user, String password, int database, String clientName,
28-
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
26+
int blockingSocketTimeoutMillis, Supplier<RedisCredentials> credentialsProvider, int database,
27+
String clientName, boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
2928
HostnameVerifier hostnameVerifier, HostAndPortMapper hostAndPortMapper) {
3029
this.connectionTimeoutMillis = connectionTimeoutMillis;
3130
this.socketTimeoutMillis = soTimeoutMillis;
3231
this.blockingSocketTimeoutMillis = blockingSocketTimeoutMillis;
33-
this.user = user;
34-
this.password = password;
32+
this.credentialsProvider = credentialsProvider;
3533
this.database = database;
3634
this.clientName = clientName;
3735
this.ssl = ssl;
@@ -58,19 +56,25 @@ public int getBlockingSocketTimeoutMillis() {
5856

5957
@Override
6058
public String getUser() {
61-
return user;
59+
return credentialsProvider.get().getUser();
6260
}
6361

6462
@Override
6563
public String getPassword() {
66-
return password;
64+
char[] password = credentialsProvider.get().getPassword();
65+
return password == null ? null : new String(password);
6766
}
6867

6968
@Override
69+
public Supplier<RedisCredentials> getCredentialsProvider() {
70+
return credentialsProvider;
71+
}
72+
73+
@Override
74+
@Deprecated
7075
public synchronized void updatePassword(String password) {
71-
if (!Objects.equals(this.password, password)) {
72-
this.password = password;
73-
}
76+
((DefaultRedisCredentialsProvider) this.credentialsProvider)
77+
.setCredentials(new DefaultRedisCredentials(getUser(), password));
7478
}
7579

7680
@Override
@@ -120,6 +124,7 @@ public static class Builder {
120124

121125
private String user = null;
122126
private String password = null;
127+
private Supplier<RedisCredentials> credentialsProvider;
123128
private int database = Protocol.DEFAULT_DATABASE;
124129
private String clientName = null;
125130

@@ -134,9 +139,14 @@ private Builder() {
134139
}
135140

136141
public DefaultJedisClientConfig build() {
142+
if (credentialsProvider == null) {
143+
credentialsProvider = new DefaultRedisCredentialsProvider(
144+
new DefaultRedisCredentials(user, password));
145+
}
146+
137147
return new DefaultJedisClientConfig(connectionTimeoutMillis, socketTimeoutMillis,
138-
blockingSocketTimeoutMillis, user, password, database, clientName, ssl, sslSocketFactory,
139-
sslParameters, hostnameVerifier, hostAndPortMapper);
148+
blockingSocketTimeoutMillis, credentialsProvider, database, clientName, ssl,
149+
sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMapper);
140150
}
141151

142152
public Builder timeoutMillis(int timeoutMillis) {
@@ -170,6 +180,16 @@ public Builder password(String password) {
170180
return this;
171181
}
172182

183+
public Builder credentials(RedisCredentials credentials) {
184+
this.credentialsProvider = new DefaultRedisCredentialsProvider(credentials);
185+
return this;
186+
}
187+
188+
public Builder credentialsProvider(Supplier<RedisCredentials> credentials) {
189+
this.credentialsProvider = credentials;
190+
return this;
191+
}
192+
173193
public Builder database(int database) {
174194
this.database = database;
175195
return this;
@@ -210,16 +230,18 @@ public static DefaultJedisClientConfig create(int connectionTimeoutMillis, int s
210230
int blockingSocketTimeoutMillis, String user, String password, int database, String clientName,
211231
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
212232
HostnameVerifier hostnameVerifier, HostAndPortMapper hostAndPortMapper) {
213-
return new DefaultJedisClientConfig(connectionTimeoutMillis, soTimeoutMillis,
214-
blockingSocketTimeoutMillis, user, password, database, clientName, ssl,
215-
sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMapper);
233+
return new DefaultJedisClientConfig(
234+
connectionTimeoutMillis, soTimeoutMillis, blockingSocketTimeoutMillis,
235+
new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(user, password)),
236+
database, clientName, ssl, sslSocketFactory, sslParameters,
237+
hostnameVerifier, hostAndPortMapper);
216238
}
217239

218240
public static DefaultJedisClientConfig copyConfig(JedisClientConfig copy) {
219241
return new DefaultJedisClientConfig(copy.getConnectionTimeoutMillis(),
220-
copy.getSocketTimeoutMillis(), copy.getBlockingSocketTimeoutMillis(), copy.getUser(),
221-
copy.getPassword(), copy.getDatabase(), copy.getClientName(), copy.isSsl(),
222-
copy.getSslSocketFactory(), copy.getSslParameters(), copy.getHostnameVerifier(),
223-
copy.getHostAndPortMapper());
242+
copy.getSocketTimeoutMillis(), copy.getBlockingSocketTimeoutMillis(),
243+
copy.getCredentialsProvider(), copy.getDatabase(), copy.getClientName(),
244+
copy.isSsl(), copy.getSslSocketFactory(), copy.getSslParameters(),
245+
copy.getHostnameVerifier(), copy.getHostAndPortMapper());
224246
}
225247
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package redis.clients.jedis;
2+
3+
public final class DefaultRedisCredentials implements RedisCredentials {
4+
5+
private final String user;
6+
private final char[] password;
7+
8+
public DefaultRedisCredentials(String user, char[] password) {
9+
this.user = user;
10+
this.password = password;
11+
}
12+
13+
public DefaultRedisCredentials(String user, CharSequence password) {
14+
this.user = user;
15+
this.password = password == null ? null
16+
: password instanceof String ? ((String) password).toCharArray()
17+
: toCharArray(password);
18+
}
19+
20+
@Override
21+
public String getUser() {
22+
return user;
23+
}
24+
25+
@Override
26+
public char[] getPassword() {
27+
return password;
28+
}
29+
30+
private static char[] toCharArray(CharSequence seq) {
31+
final int len = seq.length();
32+
char[] arr = new char[len];
33+
for (int i = 0; i < len; i++) {
34+
arr[i] = seq.charAt(i);
35+
}
36+
return arr;
37+
}
38+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package redis.clients.jedis;
2+
3+
public final class DefaultRedisCredentialsProvider implements RedisCredentialsProvider {
4+
5+
private volatile RedisCredentials credentials;
6+
7+
public DefaultRedisCredentialsProvider(RedisCredentials credentials) {
8+
this.credentials = credentials;
9+
}
10+
11+
public void setCredentials(RedisCredentials credentials) {
12+
this.credentials = credentials;
13+
}
14+
15+
@Override
16+
public RedisCredentials get() {
17+
return this.credentials;
18+
}
19+
}

src/main/java/redis/clients/jedis/JedisClientConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package redis.clients.jedis;
22

3+
import java.util.function.Supplier;
34
import javax.net.ssl.HostnameVerifier;
45
import javax.net.ssl.SSLParameters;
56
import javax.net.ssl.SSLSocketFactory;
@@ -39,9 +40,15 @@ default String getPassword() {
3940
return null;
4041
}
4142

43+
@Deprecated
4244
default void updatePassword(String password) {
4345
}
4446

47+
default Supplier<RedisCredentials> getCredentialsProvider() {
48+
return new DefaultRedisCredentialsProvider(
49+
new DefaultRedisCredentials(getUser(), getPassword()));
50+
}
51+
4552
default int getDatabase() {
4653
return Protocol.DEFAULT_DATABASE;
4754
}

src/main/java/redis/clients/jedis/JedisFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ void setHostAndPort(final HostAndPort hostAndPort) {
142142
((DefaultJedisSocketFactory) jedisSocketFactory).updateHostAndPort(hostAndPort);
143143
}
144144

145+
/**
146+
* @deprecated Use {@link RedisCredentialsProvider} through
147+
* {@link JedisClientConfig#getCredentialsProvider()}.
148+
*/
149+
@Deprecated
145150
public void setPassword(final String password) {
146151
this.clientConfig.updatePassword(password);
147152
}

src/main/java/redis/clients/jedis/Protocol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public final class Protocol {
4949
private static final String NOPERM_PREFIX = "NOPERM";
5050

5151
private Protocol() {
52-
// this prevent the class from instantiation
52+
throw new InstantiationError("Must not instantiate this class");
5353
}
5454

5555
public static void sendCommand(final RedisOutputStream os, CommandArguments args) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package redis.clients.jedis;
2+
3+
public interface RedisCredentials {
4+
5+
/**
6+
* @return Redis ACL user
7+
*/
8+
default String getUser() {
9+
return null;
10+
}
11+
12+
default char[] getPassword() {
13+
return null;
14+
}
15+
}

0 commit comments

Comments
 (0)