diff --git a/src/main/java/io/nats/client/Options.java b/src/main/java/io/nats/client/Options.java index 9c97fc5d9..3fe9c5d49 100644 --- a/src/main/java/io/nats/client/Options.java +++ b/src/main/java/io/nats/client/Options.java @@ -2399,51 +2399,50 @@ public CharBuffer buildProtocolConnectOptionsString(String serverURI, boolean in appendOption(connectString, Options.OPTION_SIG, encodedSig, true, true); appendOption(connectString, Options.OPTION_JWT, jwt, true, true); } - else { - String uriUser = null; - String uriPass = null; - String uriToken = null; - - // Values from URI override options - try { - URI uri = this.createURIForServer(serverURI); - String userInfo = uri.getRawUserInfo(); - if (userInfo != null) { - int at = userInfo.indexOf(":"); - if (at == -1) { - uriToken = uriDecode(userInfo); - } - else { - uriUser = uriDecode(userInfo.substring(0, at)); - uriPass = uriDecode(userInfo.substring(at + 1)); - } + + String uriUser = null; + String uriPass = null; + String uriToken = null; + + // Values from URI override options + try { + URI uri = this.createURIForServer(serverURI); + String userInfo = uri.getRawUserInfo(); + if (userInfo != null) { + int at = userInfo.indexOf(":"); + if (at == -1) { + uriToken = uriDecode(userInfo); + } + else { + uriUser = uriDecode(userInfo.substring(0, at)); + uriPass = uriDecode(userInfo.substring(at + 1)); } } - catch (URISyntaxException e) { - // the createURIForServer call is the one that potentially throws this - // uriUser, uriPass and uriToken will already be null - } + } + catch (URISyntaxException e) { + // the createURIForServer call is the one that potentially throws this + // uriUser, uriPass and uriToken will already be null + } - if (uriUser != null) { - appendOption(connectString, Options.OPTION_USER, uriUser, true, true); - } - else if (this.username != null) { - appendOption(connectString, Options.OPTION_USER, this.username, true, true); - } + if (uriUser != null) { + appendOption(connectString, Options.OPTION_USER, uriUser, true, true); + } + else if (this.username != null) { + appendOption(connectString, Options.OPTION_USER, this.username, true, true); + } - if (uriPass != null) { - appendOption(connectString, Options.OPTION_PASSWORD, uriPass, true, true); - } - else if (this.password != null) { - appendOption(connectString, Options.OPTION_PASSWORD, this.password, true, true); - } + if (uriPass != null) { + appendOption(connectString, Options.OPTION_PASSWORD, uriPass, true, true); + } + else if (this.password != null) { + appendOption(connectString, Options.OPTION_PASSWORD, this.password, true, true); + } - 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, true); - } + 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, true); } } diff --git a/src/test/java/io/nats/client/AuthHandlerForTesting.java b/src/test/java/io/nats/client/AuthHandlerForTesting.java index 708c5f704..01a5840ff 100644 --- a/src/test/java/io/nats/client/AuthHandlerForTesting.java +++ b/src/test/java/io/nats/client/AuthHandlerForTesting.java @@ -18,13 +18,21 @@ public class AuthHandlerForTesting implements AuthHandler { private final NKey nkey; + private final char[] jwt; public AuthHandlerForTesting(NKey nkey) { this.nkey = nkey; + this.jwt = null; + } + + public AuthHandlerForTesting(NKey nkey, char[] jwt) { + this.nkey = nkey; + this.jwt = jwt; } public AuthHandlerForTesting() throws Exception { this.nkey = NKey.createUser(null); + this.jwt = null; } public NKey getNKey() { @@ -48,6 +56,6 @@ public byte[] sign(byte[] nonce) { } public char[] getJWT() { - return null; + return this.jwt; } } \ No newline at end of file diff --git a/src/test/java/io/nats/client/OptionsTests.java b/src/test/java/io/nats/client/OptionsTests.java index 7592d3d9c..37f64498c 100644 --- a/src/test/java/io/nats/client/OptionsTests.java +++ b/src/test/java/io/nats/client/OptionsTests.java @@ -694,6 +694,51 @@ public void testNKeyConnectOptions() throws Exception { assertEquals(expectedWithAuth, o.buildProtocolConnectOptionsString("nats://localhost:4222", true, nonce).toString(), "auth connect options"); } + // Test for auth handler from nkey, option JWT and user info + @Test + public void testNKeyJWTAndUserInfoOptions() throws Exception { + // "jwt" is encoded from: + // Header: {"alg":"HS256"} + // Payload: {"jti":"","iat":2000000000,"iss":"","name":"user_jwt","sub":"","nats":{"pub":{"deny":[">"]}, + // "sub":{"deny":[">"]},"subs":-1,"data":-1,"payload":-1,"type":"user","version":2}} + String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIiLCJpYXQiOjIwMDAwMDAwMDAsImlzcyI6IiIsIm5hbWUiOiJ1c2VyX2p3" + + "dCIsInN1YiI6IiIsIm5hdHMiOnsicHViIjp7ImRlbnkiOlsiPiJdfSwic3ViIjp7ImRlbnkiOlsiPiJdfSwic3VicyI6LTEsImRh" + + "dGEiOi0xLCJwYXlsb2FkIjotMSwidHlwZSI6InVzZXIiLCJ2ZXJzaW9uIjoyfX0"; + NKey nkey = NKey.createUser(null); + String username = "username"; + String password = "password"; + AuthHandlerForTesting th = new AuthHandlerForTesting(nkey, jwt.toCharArray()); + byte[] nonce = "abcdefg".getBytes(StandardCharsets.UTF_8); + String sig = Base64.getUrlEncoder().withoutPadding().encodeToString(th.sign(nonce)); + + // Assert that no auth and user info is given + Options options = new Options.Builder().authHandler(th) + .userInfo(username.toCharArray(), password.toCharArray()).build(); + String expectedWithoutAuth = "{\"lang\":\"java\",\"version\":\"" + Nats.CLIENT_VERSION + "\"" + + ",\"protocol\":1,\"verbose\":false,\"pedantic\":false,\"tls_required\":false,\"echo\":true," + + "\"headers\":true,\"no_responders\":true}"; + String actualWithoutAuth = options + .buildProtocolConnectOptionsString("nats://localhost:4222", false, nonce).toString(); + assertEquals(expectedWithoutAuth, actualWithoutAuth); + + // Assert that auth and user info is given via options + String expectedWithAuth = "{\"lang\":\"java\",\"version\":\"" + Nats.CLIENT_VERSION + "\"" + + ",\"protocol\":1,\"verbose\":false,\"pedantic\":false,\"tls_required\":false,\"echo\":true," + + "\"headers\":true,\"no_responders\":true,\"nkey\":\"" + new String(th.getID()) + "\",\"sig\":\"" + + sig + "\",\"jwt\":\"" + jwt + "\",\"user\":\"" + username + "\",\"pass\":\"" + password + "\"}"; + String actualWithAuthInOptions = options + .buildProtocolConnectOptionsString("nats://localhost:4222", true, nonce).toString(); + assertEquals(expectedWithAuth, actualWithAuthInOptions); + + // Assert that auth is given via options and user info is given via server URI + Options optionsWithoutUserInfo = new Options.Builder().authHandler(th).build(); + String serverUriWithAuth = "nats://" + username + ":" + password + "@localhost:4222"; + String actualWithAuthInServerUri = optionsWithoutUserInfo + .buildProtocolConnectOptionsString(serverUriWithAuth, true, nonce).toString(); + assertEquals(expectedWithAuth, actualWithAuthInServerUri); + } + + @Test public void testDefaultDataPort() { Options o = new Options.Builder().socketWriteTimeout(null).build();