Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 50 additions & 13 deletions src/main/java/io/nats/client/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ public class Options {
private final long reconnectBufferSize;
private final char[] username;
private final char[] password;
private final char[] token;
private final Supplier<char[]> tokenSupplier;
private final String inboxPrefix;
private boolean useOldRequestStyle;
private final int bufferSize;
Expand Down Expand Up @@ -700,6 +700,27 @@ public Thread newThread(Runnable r) {
}
}

static class DefaultTokenSupplier implements Supplier<char[]> {
final char[] token;

public DefaultTokenSupplier() {
token = null;
}

public DefaultTokenSupplier(char[] token) {
this.token = token;
}

public DefaultTokenSupplier(String token) {
this.token = token == null ? null : token.toCharArray();
}

@Override
public char[] get() {
return token;
}
}

/**
* Set old request style.
* @param value true to use the old request style
Expand Down Expand Up @@ -758,7 +779,7 @@ public static class Builder {
private long reconnectBufferSize = DEFAULT_RECONNECT_BUF_SIZE;
private char[] username = null;
private char[] password = null;
private char[] token = null;
private Supplier<char[]> tokenSupplier = new DefaultTokenSupplier();
private boolean useOldRequestStyle = false;
private int bufferSize = DEFAULT_BUFFER_SIZE;
private boolean trackAdvancedStats = false;
Expand Down Expand Up @@ -855,7 +876,7 @@ public Builder properties(Properties props) {

charArrayProperty(props, PROP_USERNAME, ca -> this.username = ca);
charArrayProperty(props, PROP_PASSWORD, ca -> this.password = ca);
charArrayProperty(props, PROP_TOKEN, ca -> this.token = ca);
charArrayProperty(props, PROP_TOKEN, ca -> this.tokenSupplier = new DefaultTokenSupplier(ca));

booleanProperty(props, PROP_SECURE, b -> this.useDefaultTls = b);
booleanProperty(props, PROP_OPENTLS, b -> this.useTrustAllTls = b);
Expand Down Expand Up @@ -1477,27 +1498,39 @@ public Builder userInfo(char[] userName, char[] password) {

/**
* Set the token for token-based authentication.
* If a token is provided in a server URI it overrides this value.
* If a token is provided in a server URI, it overrides this value.
*
* @param token The token
* @return the Builder for chaining
* @deprecated use the char[] version instead for better security
*/
@Deprecated
public Builder token(String token) {
this.token = token.toCharArray();
this.tokenSupplier = new DefaultTokenSupplier(token);
return this;
}

/**
* Set the token for token-based authentication.
* If a token is provided in a server URI it overrides this value.
* If a token is provided in a server URI, it overrides this value.
*
* @param token The token
* @return the Builder for chaining
*/
public Builder token(char[] token) {
this.token = token;
this.tokenSupplier = new DefaultTokenSupplier(token);
return this;
}

/**
* Set the token supplier for token-based authentication.
* If a token is provided in a server URI, it overrides this value.
*
* @param tokenSupplier The tokenSupplier
* @return the Builder for chaining
*/
public Builder tokenSupplier(Supplier<char[]> tokenSupplier) {
this.tokenSupplier = tokenSupplier == null ? new DefaultTokenSupplier() : tokenSupplier;
return this;
}

Expand Down Expand Up @@ -1784,7 +1817,7 @@ public Options build() throws IllegalStateException {
// ----------------------------------------------------------------------------------------------------
// BUILD IMPL
// ----------------------------------------------------------------------------------------------------
if (this.username != null && this.token != null) {
if (this.username != null && tokenSupplier.get() != null) {
throw new IllegalStateException("Options can't have token and username");
}

Expand Down Expand Up @@ -1958,7 +1991,7 @@ public Builder(Options o) {
this.reconnectBufferSize = o.reconnectBufferSize;
this.username = o.username;
this.password = o.password;
this.token = o.token;
this.tokenSupplier = o.tokenSupplier;
this.useOldRequestStyle = o.useOldRequestStyle;
this.maxControlLine = o.maxControlLine;
this.bufferSize = o.bufferSize;
Expand Down Expand Up @@ -2026,7 +2059,7 @@ private Options(Builder b) {
this.reconnectBufferSize = b.reconnectBufferSize;
this.username = b.username;
this.password = b.password;
this.token = b.token;
this.tokenSupplier = b.tokenSupplier;
this.useOldRequestStyle = b.useOldRequestStyle;
this.maxControlLine = b.maxControlLine;
this.bufferSize = b.bufferSize;
Expand Down Expand Up @@ -2456,14 +2489,15 @@ public char[] getPasswordChars() {
*/
@Deprecated
public String getToken() {
char[] token = tokenSupplier.get();
return token == null ? null : new String(token);
}

/**
* @return the token to be used for token-based authentication, see {@link Builder#token(String) token()} in the builder doc
*/
public char[] getTokenChars() {
return token;
return tokenSupplier.get();
}

/**
Expand Down Expand Up @@ -2649,8 +2683,11 @@ else if (this.password != null) {
if (uriToken != null) {
appendOption(connectString, Options.OPTION_AUTH_TOKEN, uriToken, true, true);
}
else if (this.token != null) {
appendOption(connectString, Options.OPTION_AUTH_TOKEN, this.token, true);
else {
char[] token = this.tokenSupplier.get();
if (token != null) {
appendOption(connectString, Options.OPTION_AUTH_TOKEN, token, true);
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/test/java/io/nats/client/OptionsTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.nats.client.utils.CloseOnUpgradeAttempt;
import io.nats.client.utils.CoverageServerPool;
import io.nats.client.utils.ResourceUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.net.ssl.SSLContext;
Expand All @@ -33,6 +34,8 @@
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import static io.nats.client.Options.*;
import static io.nats.client.support.Encoding.base64UrlEncodeToString;
Expand Down Expand Up @@ -856,6 +859,21 @@ public void testTokenInURL() {
assertFalse(connectString.contains("\"pass\":"));
}

@Test
public void testTokenSupplier() {
String serverURI = "nats://localhost:2222";
AtomicInteger counter = new AtomicInteger(0);
Supplier<char[]> tokenSupplier = () -> {
counter.incrementAndGet();
return "short-lived-token".toCharArray();
};
Options o = new Options.Builder().tokenSupplier(tokenSupplier).build();

String connectString = o.buildProtocolConnectOptionsString(serverURI, true, null).toString();
assertTrue(connectString.contains("\"auth_token\":\"short-lived-token\""));
assertEquals(1, counter.get());
}

@Test
public void testThrowOnNoOpts() {
assertThrows(IllegalArgumentException.class, () -> new Options.Builder((Options) null));
Expand Down
Loading