[client] KUDU-3472 Java client API to import JWT

This patch adds one new method into AsyncKuduClient and KuduClient API
to set a JWT (JSON Web Token) to be used for authentication.  The newly
introduced API matches the one introduced in the C++ Kudu client.

Change-Id: Ic43bd1219f4613e9cac11762726a5d6d2cdc764b
Reviewed-on: http://gerrit.cloudera.org:8080/19836
Tested-by: Alexey Serbin <alexey@apache.org>
Reviewed-by: Alexey Serbin <alexey@apache.org>
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
index 2e8a24e..68aa1b2 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
@@ -80,6 +80,7 @@
 import org.apache.kudu.master.Master.TSInfoPB;
 import org.apache.kudu.master.Master.TableIdentifierPB;
 import org.apache.kudu.master.Master.TabletLocationsPB;
+import org.apache.kudu.security.Token;
 import org.apache.kudu.security.Token.SignedTokenPB;
 import org.apache.kudu.util.AsyncUtil;
 import org.apache.kudu.util.NetUtil;
@@ -1178,6 +1179,24 @@
   }
 
   /**
+   * Set JWT (JSON Web Token) to authenticate the client to a server.
+   * <p>
+   * @note If {@link #importAuthenticationCredentials(byte[] authnData)} and
+   * this method are called on the same object, the JWT provided with this call
+   * overrides the corresponding JWT that comes as a part of the imported
+   * authentication credentials (if present).
+   *
+   * @param jwt The JSON web token to set.
+   */
+  @InterfaceStability.Unstable
+  public void jwt(String jwt) {
+    Token.JwtRawPB jwtPB = Token.JwtRawPB.newBuilder()
+        .setJwtData(ByteString.copyFromUtf8(jwt))
+        .build();
+    securityContext.setJsonWebToken(jwtPB);
+  }
+
+  /**
    * Get the timeout used for operations on sessions and scanners.
    * @return a timeout in milliseconds
    */
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java
index 1536574..ed2f90e 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java
@@ -22,6 +22,7 @@
 import java.util.concurrent.Executor;
 
 import com.google.common.base.Preconditions;
+import com.google.protobuf.ByteString;
 import com.stumbleupon.async.Deferred;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
@@ -30,6 +31,7 @@
 
 import org.apache.kudu.Schema;
 import org.apache.kudu.master.Master.TableIdentifierPB;
+import org.apache.kudu.security.Token;
 
 /**
  * A synchronous and thread-safe client for Kudu.
@@ -467,6 +469,21 @@
   }
 
   /**
+   * Set JWT (JSON Web Token) to authenticate the client to a server.
+   * <p>
+   * @note If {@link #importAuthenticationCredentials(byte[] authnData)} and
+   * this method are called on the same object, the JWT provided with this call
+   * overrides the corresponding JWT that comes as a part of the imported
+   * authentication credentials (if present).
+   *
+   * @param jwt The JSON web token to set.
+   */
+  @InterfaceStability.Unstable
+  public void jwt(String jwt) {
+    asyncClient.jwt(jwt);
+  }
+
+  /**
    * Get the timeout used for operations on sessions and scanners.
    * @return a timeout in milliseconds
    */
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/SecurityContext.java b/java/kudu-client/src/main/java/org/apache/kudu/client/SecurityContext.java
index 8597e0c..b02c020 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/SecurityContext.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/SecurityContext.java
@@ -278,6 +278,9 @@
     if (authnToken != null) {
       pb.setAuthnToken(authnToken);
     }
+    if (jsonWebToken != null) {
+      pb.setJwt(jsonWebToken);
+    }
     pb.addAllCaCertDers(trustedCertDers);
     return pb.build().toByteArray();
   }
@@ -315,10 +318,9 @@
       }
 
       LOG.debug("Importing authentication credentials with {} authn token, " +
-                "JWT={} , " +
-                "{} cert(s), and realUser={}",
+                "with {} JWT, {} cert(s), and realUser={}",
                 pb.hasAuthnToken() ? "one" : "no",
-                pb.hasJwt() ? pb.getJwt() : "<none>",
+                pb.hasJwt() ? "one" : "no",
                 pb.getCaCertDersCount(),
                 pb.hasRealUser() ? pb.getRealUser() : "<none>");
       if (pb.hasAuthnToken()) {
@@ -327,7 +329,11 @@
       trustCertificates(pb.getCaCertDersList());
 
       if (pb.hasJwt()) {
-        jsonWebToken = pb.getJwt();
+        // Don't overwrite the JWT in the context if it's already set.
+        if (!jsonWebToken.hasJwtData() ||
+            (jsonWebToken.hasJwtData() && jsonWebToken.getJwtData().isEmpty())) {
+          jsonWebToken = pb.getJwt();
+        }
       }
 
       if (pb.hasRealUser()) {
diff --git a/java/kudu-test-utils/src/test/java/org/apache/kudu/test/TestMiniKuduCluster.java b/java/kudu-test-utils/src/test/java/org/apache/kudu/test/TestMiniKuduCluster.java
index ddfbe6d..c31efb3 100644
--- a/java/kudu-test-utils/src/test/java/org/apache/kudu/test/TestMiniKuduCluster.java
+++ b/java/kudu-test-utils/src/test/java/org/apache/kudu/test/TestMiniKuduCluster.java
@@ -119,31 +119,18 @@
 
   @Test(timeout = 50000)
   public void testJwt() throws Exception {
-    try {
-      MiniKuduClusterBuilder clusterBuilder = new MiniKuduCluster.MiniKuduClusterBuilder()
-              .numMasterServers(NUM_MASTERS)
-              .numTabletServers(0)
-              .enableClientJwt()
-              .addJwks("account-id", true);
-
-      harness = new KuduTestHarness(clusterBuilder);
-      harness.before();
-      harness.startAllMasterServers();
-
-      String jwt = harness.createJwtFor("account-id", "subject", true);
+    try (MiniKuduCluster cluster = new MiniKuduCluster.MiniKuduClusterBuilder()
+                                                     .numMasterServers(NUM_MASTERS)
+                                                     .numTabletServers(0)
+                                                     .enableClientJwt()
+                                                     .addJwks("account-id", true)
+                                                     .build();
+        KuduClient client = new KuduClientBuilder(cluster.getMasterAddressesAsString()).build()) {
+      String jwt = cluster.createJwtFor("account-id", "subject", true);
       assertNotNull(jwt);
-      AuthenticationCredentialsPB credentials = AuthenticationCredentialsPB.newBuilder()
-              .setJwt(JwtRawPB.newBuilder()
-                      .setJwtData(ByteString.copyFromUtf8(jwt))
-                      .build())
-              .build();
 
-      AsyncKuduClient c = harness.getAsyncClient();
-      c.importAuthenticationCredentials(credentials.toByteArray());
-      c.getTablesList();
-
-    } catch (Exception e) {
-      throw new RuntimeException(e);
+      client.jwt(jwt);
+      client.getTablesList();
     }
   }