Merge branch 'master' into node_auth_integration
diff --git a/src/node/index.js b/src/node/index.js
index fe1fb1d..f7cbdf0 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -74,6 +74,36 @@
 }
 
 /**
+ * Get a function that a client can use to update metadata with authentication
+ * information from a Google Auth credential object.
+ * @param {Object} credential The credential object to use
+ * @return {function(Object, callback)} Metadata updater function
+ */
+function getGoogleAuthDelegate(credential) {
+  /**
+   * Update a metadata object with authentication information.
+   * @param {Object} metadata Metadata object
+   * @param {function(Error, Object)} callback
+   */
+  return function updateMetadata(metadata, callback) {
+    metadata = _.clone(metadata);
+    if (metadata.Authorization) {
+      metadata.Authorization = _.clone(metadata.Authorization);
+    } else {
+      metadata.Authorization = [];
+    }
+    credential.getAccessToken(function(err, token) {
+      if (err) {
+        callback(err);
+        return;
+      }
+      metadata.Authorization.push('Bearer ' + token);
+      callback(null, metadata);
+    });
+  };
+}
+
+/**
  * See docs for loadObject
  */
 exports.loadObject = loadObject;
@@ -106,3 +136,5 @@
  * ServerCredentials factories
  */
 exports.ServerCredentials = grpc.ServerCredentials;
+
+exports.getGoogleAuthDelegate = getGoogleAuthDelegate;
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index d00724b..fc2fdf4 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -35,9 +35,14 @@
 var path = require('path');
 var grpc = require('..');
 var testProto = grpc.load(__dirname + '/test.proto').grpc.testing;
+var GoogleAuth = require('googleauth');
 
 var assert = require('assert');
 
+var AUTH_SCOPE = 'https://www.googleapis.com/auth/xapi.zoo';
+var AUTH_SCOPE_RESPONSE = 'xapi.zoo';
+var AUTH_USER = '155450119199-3psnrh1sdr3d8cpj1v46naggf81mhdnk@developer.gserviceaccount.com';
+
 /**
  * Create a buffer filled with size zeroes
  * @param {number} size The length of the buffer
@@ -256,6 +261,45 @@
 }
 
 /**
+ * Run one of the authentication tests.
+ * @param {Client} client The client to test against
+ * @param {function} done Callback to call when the test is completed. Included
+ *     primarily for use with mocha
+ */
+function authTest(client, done) {
+  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
+    assert.ifError(err);
+    if (credential.createScopedRequired()) {
+      credential = credential.createScoped(AUTH_SCOPE);
+    }
+    client.updateMetadata = grpc.getGoogleAuthDelegate(credential);
+    var arg = {
+      response_type: testProto.PayloadType.COMPRESSABLE,
+      response_size: 314159,
+      payload: {
+        body: zeroBuffer(271828)
+      },
+      fill_username: true,
+      fill_oauth_scope: true
+    };
+    var call = client.unaryCall(arg, function(err, resp) {
+      assert.ifError(err);
+      assert.strictEqual(resp.payload.type, testProto.PayloadType.COMPRESSABLE);
+      assert.strictEqual(resp.payload.body.limit - resp.payload.body.offset,
+                         314159);
+      assert.strictEqual(resp.username, AUTH_USER);
+      assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
+    });
+    call.on('status', function(status) {
+      assert.strictEqual(status.code, grpc.status.OK);
+      if (done) {
+        done();
+      }
+    });
+  });
+}
+
+/**
  * Map from test case names to test functions
  */
 var test_cases = {
@@ -266,7 +310,9 @@
   ping_pong: pingPong,
   empty_stream: emptyStream,
   cancel_after_begin: cancelAfterBegin,
-  cancel_after_first_response: cancelAfterFirstResponse
+  cancel_after_first_response: cancelAfterFirstResponse,
+  compute_engine_creds: authTest,
+  service_account_creds: authTest
 };
 
 /**
@@ -280,11 +326,16 @@
  * @param {function} done Callback to call when the test is completed. Included
  *     primarily for use with mocha
  */
-function runTest(address, host_override, test_case, tls, done) {
+function runTest(address, host_override, test_case, tls, test_ca, done) {
   // TODO(mlumish): enable TLS functionality
   var options = {};
   if (tls) {
-    var ca_path = path.join(__dirname, '../test/data/ca.pem');
+    var ca_path;
+    if (test_ca) {
+      ca_path = path.join(__dirname, '../test/data/ca.pem');
+    } else {
+      ca_path = process.env.SSL_CERT_FILE;
+    }
     var ca_data = fs.readFileSync(ca_path);
     var creds = grpc.Credentials.createSsl(ca_data);
     options.credentials = creds;
@@ -304,7 +355,10 @@
              'use_tls', 'use_test_ca']
   });
   runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override,
-          argv.test_case, argv.use_tls === 'true');
+          argv.test_case, argv.use_tls === 'true', argv.use_test_ca === 'true',
+          function () {
+            console.log('OK:', argv.test_case);
+          });
 }
 
 /**
diff --git a/src/node/interop/messages.proto b/src/node/interop/messages.proto
index eb65264..65a8140 100644
--- a/src/node/interop/messages.proto
+++ b/src/node/interop/messages.proto
@@ -66,6 +66,12 @@
 
   // Optional input payload sent along with the request.
   optional Payload payload = 3;
+
+  // Whether SimpleResponse should include username.
+  optional bool fill_username = 4;
+
+  // Whether SimpleResponse should include OAuth scope.
+  optional bool fill_oauth_scope = 5;
 }
 
 // Unary response, as configured by the request.
@@ -74,7 +80,9 @@
   optional Payload payload = 1;
   // The user the request came from, for verifying authentication was
   // successful when the client expected it.
-  optional int64 effective_gaia_user_id = 2;
+  optional string username = 2;
+  // OAuth scope.
+  optional string oauth_scope = 3;
 }
 
 // Client-streaming request.
diff --git a/src/node/package.json b/src/node/package.json
index 8f81014..821641c 100644
--- a/src/node/package.json
+++ b/src/node/package.json
@@ -14,7 +14,8 @@
   },
   "devDependencies": {
     "mocha": "~1.21.0",
-    "minimist": "^1.1.0"
+    "minimist": "^1.1.0",
+    "googleauth": "google/google-auth-library-nodejs"
   },
   "main": "index.js"
 }
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 81fa65e..19c3144 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -224,25 +224,32 @@
     emitter.cancel = function cancel() {
       call.cancel();
     };
-    var client_batch = {};
-    client_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
-    client_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
-    client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
-    client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-    client_batch[grpc.opType.RECV_MESSAGE] = true;
-    client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-    call.startBatch(client_batch, function(err, response) {
-      if (err) {
-        callback(err);
+    this.updateMetadata(metadata, function(error, metadata) {
+      if (error) {
+        call.cancel();
+        callback(error);
         return;
       }
-      if (response.status.code != grpc.status.OK) {
-        callback(response.status);
-        return;
-      }
-      emitter.emit('status', response.status);
-      emitter.emit('metadata', response.metadata);
-      callback(null, deserialize(response.read));
+      var client_batch = {};
+      client_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      client_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
+      client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
+      client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+      client_batch[grpc.opType.RECV_MESSAGE] = true;
+      client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+      call.startBatch(client_batch, function(err, response) {
+        if (err) {
+          callback(err);
+          return;
+        }
+        if (response.status.code != grpc.status.OK) {
+          callback(response.status);
+          return;
+        }
+        emitter.emit('status', response.status);
+        emitter.emit('metadata', response.metadata);
+        callback(null, deserialize(response.read));
+      });
     });
     return emitter;
   }
@@ -279,30 +286,37 @@
       metadata = {};
     }
     var stream = new ClientWritableStream(call, serialize);
-    var metadata_batch = {};
-    metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
-    metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-    call.startBatch(metadata_batch, function(err, response) {
-      if (err) {
-        callback(err);
+    this.updateMetadata(metadata, function(error, metadata) {
+      if (error) {
+        call.cancel();
+        callback(error);
         return;
       }
-      stream.emit('metadata', response.metadata);
-    });
-    var client_batch = {};
-    client_batch[grpc.opType.RECV_MESSAGE] = true;
-    client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-    call.startBatch(client_batch, function(err, response) {
-      if (err) {
-        callback(err);
-        return;
-      }
-      if (response.status.code != grpc.status.OK) {
-        callback(response.status);
-        return;
-      }
-      stream.emit('status', response.status);
-      callback(null, deserialize(response.read));
+      var metadata_batch = {};
+      metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+      call.startBatch(metadata_batch, function(err, response) {
+        if (err) {
+          callback(err);
+          return;
+        }
+        stream.emit('metadata', response.metadata);
+      });
+      var client_batch = {};
+      client_batch[grpc.opType.RECV_MESSAGE] = true;
+      client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+      call.startBatch(client_batch, function(err, response) {
+        if (err) {
+          callback(err);
+          return;
+        }
+        if (response.status.code != grpc.status.OK) {
+          callback(response.status);
+          return;
+        }
+        stream.emit('status', response.status);
+        callback(null, deserialize(response.read));
+      });
     });
     return stream;
   }
@@ -339,24 +353,31 @@
       metadata = {};
     }
     var stream = new ClientReadableStream(call, deserialize);
-    var start_batch = {};
-    start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
-    start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-    start_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
-    start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
-    call.startBatch(start_batch, function(err, response) {
-      if (err) {
-        throw err;
+    this.updateMetadata(metadata, function(error, metadata) {
+      if (error) {
+        call.cancel();
+        stream.emit('error', error);
+        return;
       }
-      stream.emit('metadata', response.metadata);
-    });
-    var status_batch = {};
-    status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-    call.startBatch(status_batch, function(err, response) {
-      if (err) {
-        throw err;
-      }
-      stream.emit('status', response.status);
+      var start_batch = {};
+      start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+      start_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
+      start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
+      call.startBatch(start_batch, function(err, response) {
+        if (err) {
+          throw err;
+        }
+        stream.emit('metadata', response.metadata);
+      });
+      var status_batch = {};
+      status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+      call.startBatch(status_batch, function(err, response) {
+        if (err) {
+          throw err;
+        }
+        stream.emit('status', response.status);
+      });
     });
     return stream;
   }
@@ -391,22 +412,29 @@
       metadata = {};
     }
     var stream = new ClientDuplexStream(call, serialize, deserialize);
-    var start_batch = {};
-    start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
-    start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-    call.startBatch(start_batch, function(err, response) {
-      if (err) {
-        throw err;
+    this.updateMetadata(metadata, function(error, metadata) {
+      if (error) {
+        call.cancel();
+        stream.emit('error', error);
+        return;
       }
-      stream.emit('metadata', response.metadata);
-    });
-    var status_batch = {};
-    status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-    call.startBatch(status_batch, function(err, response) {
-      if (err) {
-        throw err;
-      }
-      stream.emit('status', response.status);
+      var start_batch = {};
+      start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
+      start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+      call.startBatch(start_batch, function(err, response) {
+        if (err) {
+          throw err;
+        }
+        stream.emit('metadata', response.metadata);
+      });
+      var status_batch = {};
+      status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+      call.startBatch(status_batch, function(err, response) {
+        if (err) {
+          throw err;
+        }
+        stream.emit('status', response.status);
+      });
     });
     return stream;
   }
@@ -438,8 +466,17 @@
    * @constructor
    * @param {string} address The address of the server to connect to
    * @param {Object} options Options to pass to the underlying channel
+   * @param {function(Object, function)=} updateMetadata function to update the
+   *     metadata for each request
    */
-  function Client(address, options) {
+  function Client(address, options, updateMetadata) {
+    if (updateMetadata) {
+      this.updateMetadata = updateMetadata;
+    } else {
+      this.updateMetadata = function(metadata, callback) {
+        callback(null, metadata);
+      };
+    }
     this.channel = new grpc.Channel(address, options);
   }
 
@@ -458,11 +495,13 @@
         method_type = 'unary';
       }
     }
-    Client.prototype[decapitalize(method.name)] =
-        requester_makers[method_type](
-            prefix + capitalize(method.name),
-            common.serializeCls(method.resolvedRequestType.build()),
-            common.deserializeCls(method.resolvedResponseType.build()));
+    var serialize = common.serializeCls(method.resolvedRequestType.build());
+    var deserialize = common.deserializeCls(
+        method.resolvedResponseType.build());
+    Client.prototype[decapitalize(method.name)] = requester_makers[method_type](
+        prefix + capitalize(method.name), serialize, deserialize);
+    Client.prototype[decapitalize(method.name)].serialize = serialize;
+    Client.prototype[decapitalize(method.name)].deserialize = deserialize;
   });
 
   Client.service = service;
diff --git a/src/node/test/interop_sanity_test.js b/src/node/test/interop_sanity_test.js
index 16def1f..d1bdd16 100644
--- a/src/node/test/interop_sanity_test.js
+++ b/src/node/test/interop_sanity_test.js
@@ -53,30 +53,35 @@
   });
   // This depends on not using a binary stream
   it('should pass empty_unary', function(done) {
-    interop_client.runTest(port, name_override, 'empty_unary', true, done);
+    interop_client.runTest(port, name_override, 'empty_unary', true, true,
+                           done);
   });
   // This fails due to an unknown bug
   it('should pass large_unary', function(done) {
-    interop_client.runTest(port, name_override, 'large_unary', true, done);
+    interop_client.runTest(port, name_override, 'large_unary', true, true,
+                           done);
   });
   it('should pass client_streaming', function(done) {
-    interop_client.runTest(port, name_override, 'client_streaming', true, done);
+    interop_client.runTest(port, name_override, 'client_streaming', true, true,
+                           done);
   });
   it('should pass server_streaming', function(done) {
-    interop_client.runTest(port, name_override, 'server_streaming', true, done);
+    interop_client.runTest(port, name_override, 'server_streaming', true, true,
+                           done);
   });
   it('should pass ping_pong', function(done) {
-    interop_client.runTest(port, name_override, 'ping_pong', true, done);
+    interop_client.runTest(port, name_override, 'ping_pong', true, true, done);
   });
   it('should pass empty_stream', function(done) {
-    interop_client.runTest(port, name_override, 'empty_stream', true, done);
+    interop_client.runTest(port, name_override, 'empty_stream', true, true,
+                           done);
   });
   it('should pass cancel_after_begin', function(done) {
     interop_client.runTest(port, name_override, 'cancel_after_begin', true,
-                           done);
+                           true, done);
   });
   it('should pass cancel_after_first_response', function(done) {
     interop_client.runTest(port, name_override, 'cancel_after_first_response',
-                           true, done);
+                           true, true, done);
   });
 });
diff --git a/tools/dockerfile/grpc_node/Dockerfile b/tools/dockerfile/grpc_node/Dockerfile
index ce582d2..59aa8cf 100644
--- a/tools/dockerfile/grpc_node/Dockerfile
+++ b/tools/dockerfile/grpc_node/Dockerfile
@@ -11,4 +11,11 @@
 
 RUN cd /var/local/git/grpc/src/node && npm install && node-gyp rebuild
 
+# Add a cacerts directory containing the Google root pem file, allowing the
+# ruby client to access the production test instance
+ADD cacerts cacerts
+
+# Add a service_account directory containing the auth creds file
+ADD service_account service_account
+
 CMD ["/usr/bin/nodejs", "/var/local/git/grpc/src/node/interop/interop_server.js", "--use_tls=true", "--port=8040"]
\ No newline at end of file
diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh
index 198327f..41a1d20 100755
--- a/tools/gce_setup/grpc_docker.sh
+++ b/tools/gce_setup/grpc_docker.sh
@@ -1044,11 +1044,39 @@
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_interop_gen_node_cmd() {
   local cmd_prefix="sudo docker run grpc/node";
-  local test_script="/usr/bin/nodejs /var/local/git/grpc/src/node/interop/interop_client.js --use_tls=true";
+  local test_script="/usr/bin/nodejs /var/local/git/grpc/src/node/interop/interop_client.js --use_tls=true --use_test_ca=true";
   local the_cmd="$cmd_prefix $test_script $@";
   echo $the_cmd
 }
 
+# constructs the full dockerized node gce=>prod interop test cmd.
+#
+# call-seq:
+#   flags= .... # generic flags to include the command
+#   cmd=$($grpc_gen_test_cmd $flags)
+grpc_cloud_prod_gen_node_cmd() {
+  local cmd_prefix="sudo docker run grpc/node";
+  local test_script="/usr/bin/nodejs /var/local/git/grpc/src/node/interop/interop_client.js --use_tls=true";
+  local gfe_flags=$(_grpc_prod_gfe_flags);
+  local the_cmd="$cmd_prefix $test_script $gfe_flags $@";
+  echo $the_cmd
+}
+
+# constructs the full dockerized node service_account auth interop test cmd.
+#
+# call-seq:
+#   flags= .... # generic flags to include the command
+#   cmd=$($grpc_gen_test_cmd $flags)
+grpc_cloud_prod_auth_service_account_creds_gen_node_cmd() {
+  local cmd_prefix="sudo docker run grpc/node";
+  local test_script="/usr/bin/nodejs /var/local/git/grpc/src/node/interop/interop_client.js --use_tls=true";
+  local gfe_flags=$(_grpc_prod_gfe_flags);
+  local env_prefix="SSL_CERT_FILE=/cacerts/roots.pem"
+  env_prefix+=" GOOGLE_APPLICATION_CREDENTIALS=/service_account/stubbyCloudTestingTest-7dd63462c60c.json"
+  local the_cmd="$env_prefix $cmd_prefix $test_script $gfe_flags $@";
+  echo $the_cmd
+}
+
 # constructs the full dockerized cpp interop test cmd.
 #
 # call-seq:
diff --git a/tools/gce_setup/shared_startup_funcs.sh b/tools/gce_setup/shared_startup_funcs.sh
index fe00e0c..143e039 100755
--- a/tools/gce_setup/shared_startup_funcs.sh
+++ b/tools/gce_setup/shared_startup_funcs.sh
@@ -416,6 +416,10 @@
     grpc_docker_sync_roots_pem $dockerfile_dir/cacerts || return 1;
     grpc_docker_sync_service_account $dockerfile_dir/service_account || return 1;
   }
+  [[ $image_label == "grpc/node" ]] && {
+    grpc_docker_sync_roots_pem $dockerfile_dir/cacerts || return 1;
+    grpc_docker_sync_service_account $dockerfile_dir/service_account || return 1;
+  }
   [[ $image_label == "grpc/cxx" ]] && {
     grpc_docker_sync_roots_pem $dockerfile_dir/cacerts || return 1;
     grpc_docker_sync_service_account $dockerfile_dir/service_account || return 1;