Merge pull request #3558 from murgatroid99/node_auth_plugin

Add Node Auth plugin code
pull/3547/head
Tim Emiola 9 years ago
commit 77556c36bb
  1. 3
      binding.gyp
  2. 24
      src/node/ext/call.cc
  3. 5
      src/node/ext/call.h
  4. 259
      src/node/ext/call_credentials.cc
  5. 100
      src/node/ext/call_credentials.h
  6. 8
      src/node/ext/channel.cc
  7. 106
      src/node/ext/channel_credentials.cc
  8. 23
      src/node/ext/channel_credentials.h
  9. 6
      src/node/ext/node_grpc.cc
  10. 28
      src/node/index.js
  11. 155
      src/node/interop/interop_client.js
  12. 2
      src/node/performance/perf_test.js
  13. 2
      src/node/performance/qps_test.js
  14. 348
      src/node/src/client.js
  15. 163
      src/node/src/credentials.js
  16. 2
      src/node/test/call_test.js
  17. 5
      src/node/test/channel_test.js
  18. 243
      src/node/test/credentials_test.js
  19. 2
      src/node/test/end_to_end_test.js
  20. 2
      src/node/test/health_test.js
  21. 2
      src/node/test/math_client_test.js
  22. 28
      src/node/test/surface_test.js

@ -78,9 +78,10 @@
"sources": [
"src/node/ext/byte_buffer.cc",
"src/node/ext/call.cc",
"src/node/ext/call_credentials.cc",
"src/node/ext/channel.cc",
"src/node/ext/channel_credentials.cc",
"src/node/ext/completion_queue_async_worker.cc",
"src/node/ext/credentials.cc",
"src/node/ext/node_grpc.cc",
"src/node/ext/server.cc",
"src/node/ext/server_credentials.cc",

@ -39,12 +39,14 @@
#include "grpc/support/log.h"
#include "grpc/grpc.h"
#include "grpc/grpc_security.h"
#include "grpc/support/alloc.h"
#include "grpc/support/time.h"
#include "byte_buffer.h"
#include "call.h"
#include "channel.h"
#include "completion_queue_async_worker.h"
#include "call_credentials.h"
#include "timeval.h"
using std::unique_ptr;
@ -502,6 +504,7 @@ void Call::Init(Local<Object> exports) {
Nan::SetPrototypeMethod(tpl, "cancel", Cancel);
Nan::SetPrototypeMethod(tpl, "cancelWithStatus", CancelWithStatus);
Nan::SetPrototypeMethod(tpl, "getPeer", GetPeer);
Nan::SetPrototypeMethod(tpl, "setCredentials", SetCredentials);
fun_tpl.Reset(tpl);
Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
Nan::Set(exports, Nan::New("Call").ToLocalChecked(), ctr);
@ -725,5 +728,26 @@ NAN_METHOD(Call::GetPeer) {
info.GetReturnValue().Set(peer_value);
}
NAN_METHOD(Call::SetCredentials) {
Nan::HandleScope scope;
if (!HasInstance(info.This())) {
return Nan::ThrowTypeError(
"setCredentials can only be called on Call objects");
}
if (!CallCredentials::HasInstance(info[0])) {
return Nan::ThrowTypeError(
"setCredentials' first argument must be a CallCredentials");
}
Call *call = ObjectWrap::Unwrap<Call>(info.This());
CallCredentials *creds_object = ObjectWrap::Unwrap<CallCredentials>(
Nan::To<Object>(info[0]).ToLocalChecked());
grpc_credentials *creds = creds_object->GetWrappedCredentials();
grpc_call_error error = GRPC_CALL_ERROR;
if (creds) {
error = grpc_call_set_credentials(call->wrapped_call, creds);
}
info.GetReturnValue().Set(Nan::New<Uint32>(error));
}
} // namespace node
} // namespace grpc

@ -73,6 +73,10 @@ struct Resources {
std::vector<unique_ptr<PersistentValue> > handles;
};
bool CreateMetadataArray(v8::Local<v8::Object> metadata,
grpc_metadata_array *array,
shared_ptr<Resources> resources);
class Op {
public:
virtual v8::Local<v8::Value> GetNodeValue() const = 0;
@ -122,6 +126,7 @@ class Call : public Nan::ObjectWrap {
static NAN_METHOD(Cancel);
static NAN_METHOD(CancelWithStatus);
static NAN_METHOD(GetPeer);
static NAN_METHOD(SetCredentials);
static Nan::Callback *constructor;
// Used for typechecking instances of this javascript class
static Nan::Persistent<v8::FunctionTemplate> fun_tpl;

@ -0,0 +1,259 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <node.h>
#include "grpc/grpc.h"
#include "grpc/grpc_security.h"
#include "grpc/support/log.h"
#include "call_credentials.h"
#include "call.h"
namespace grpc {
namespace node {
using Nan::Callback;
using Nan::EscapableHandleScope;
using Nan::HandleScope;
using Nan::Maybe;
using Nan::MaybeLocal;
using Nan::ObjectWrap;
using Nan::Persistent;
using Nan::Utf8String;
using v8::Exception;
using v8::External;
using v8::Function;
using v8::FunctionTemplate;
using v8::Integer;
using v8::Local;
using v8::Object;
using v8::ObjectTemplate;
using v8::Value;
Nan::Callback *CallCredentials::constructor;
Persistent<FunctionTemplate> CallCredentials::fun_tpl;
CallCredentials::CallCredentials(grpc_credentials *credentials)
: wrapped_credentials(credentials) {}
CallCredentials::~CallCredentials() {
grpc_credentials_release(wrapped_credentials);
}
void CallCredentials::Init(Local<Object> exports) {
HandleScope scope;
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New("CallCredentials").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(tpl, "compose", Compose);
fun_tpl.Reset(tpl);
Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
Nan::Set(ctr, Nan::New("createFromPlugin").ToLocalChecked(),
Nan::GetFunction(
Nan::New<FunctionTemplate>(CreateFromPlugin)).ToLocalChecked());
Nan::Set(exports, Nan::New("CallCredentials").ToLocalChecked(), ctr);
constructor = new Nan::Callback(ctr);
}
bool CallCredentials::HasInstance(Local<Value> val) {
HandleScope scope;
return Nan::New(fun_tpl)->HasInstance(val);
}
Local<Value> CallCredentials::WrapStruct(grpc_credentials *credentials) {
EscapableHandleScope scope;
const int argc = 1;
if (credentials == NULL) {
return scope.Escape(Nan::Null());
}
Local<Value> argv[argc] = {
Nan::New<External>(reinterpret_cast<void *>(credentials))};
MaybeLocal<Object> maybe_instance = Nan::NewInstance(
constructor->GetFunction(), argc, argv);
if (maybe_instance.IsEmpty()) {
return scope.Escape(Nan::Null());
} else {
return scope.Escape(maybe_instance.ToLocalChecked());
}
}
grpc_credentials *CallCredentials::GetWrappedCredentials() {
return wrapped_credentials;
}
NAN_METHOD(CallCredentials::New) {
if (info.IsConstructCall()) {
if (!info[0]->IsExternal()) {
return Nan::ThrowTypeError(
"CallCredentials can only be created with the provided functions");
}
Local<External> ext = info[0].As<External>();
grpc_credentials *creds_value =
reinterpret_cast<grpc_credentials *>(ext->Value());
CallCredentials *credentials = new CallCredentials(creds_value);
credentials->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
} else {
const int argc = 1;
Local<Value> argv[argc] = {info[0]};
MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
argc, argv);
if (maybe_instance.IsEmpty()) {
// There's probably a pending exception
return;
} else {
info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
}
}
}
NAN_METHOD(CallCredentials::Compose) {
if (!CallCredentials::HasInstance(info.This())) {
return Nan::ThrowTypeError(
"compose can only be called on CallCredentials objects");
}
if (!CallCredentials::HasInstance(info[0])) {
return Nan::ThrowTypeError(
"compose's first argument must be a CallCredentials object");
}
CallCredentials *self = ObjectWrap::Unwrap<CallCredentials>(info.This());
CallCredentials *other = ObjectWrap::Unwrap<CallCredentials>(
Nan::To<Object>(info[0]).ToLocalChecked());
grpc_credentials *creds = grpc_composite_credentials_create(
self->wrapped_credentials, other->wrapped_credentials, NULL);
info.GetReturnValue().Set(WrapStruct(creds));
}
NAN_METHOD(CallCredentials::CreateFromPlugin) {
if (!info[0]->IsFunction()) {
return Nan::ThrowTypeError(
"createFromPlugin's argument must be a function");
}
grpc_metadata_credentials_plugin plugin;
plugin_state *state = new plugin_state;
state->callback = new Nan::Callback(info[0].As<Function>());
plugin.get_metadata = plugin_get_metadata;
plugin.destroy = plugin_destroy_state;
plugin.state = reinterpret_cast<void*>(state);
grpc_credentials *creds = grpc_metadata_credentials_create_from_plugin(plugin,
NULL);
info.GetReturnValue().Set(WrapStruct(creds));
}
NAN_METHOD(PluginCallback) {
// Arguments: status code, error details, metadata
if (!info[0]->IsUint32()) {
return Nan::ThrowTypeError(
"The callback's first argument must be a status code");
}
if (!info[1]->IsString()) {
return Nan::ThrowTypeError(
"The callback's second argument must be a string");
}
if (!info[2]->IsObject()) {
return Nan::ThrowTypeError(
"The callback's third argument must be an object");
}
shared_ptr<Resources> resources(new Resources);
grpc_status_code code = static_cast<grpc_status_code>(
Nan::To<uint32_t>(info[0]).FromJust());
char *details = *Utf8String(info[1]);
grpc_metadata_array array;
if (!CreateMetadataArray(Nan::To<Object>(info[2]).ToLocalChecked(),
&array, resources)){
return Nan::ThrowError("Failed to parse metadata");
}
grpc_credentials_plugin_metadata_cb cb =
reinterpret_cast<grpc_credentials_plugin_metadata_cb>(
Nan::Get(info.Callee(),
Nan::New("cb").ToLocalChecked()
).ToLocalChecked().As<External>()->Value());
void *user_data =
Nan::Get(info.Callee(),
Nan::New("user_data").ToLocalChecked()
).ToLocalChecked().As<External>()->Value();
cb(user_data, array.metadata, array.count, code, details);
}
NAUV_WORK_CB(SendPluginCallback) {
Nan::HandleScope scope;
plugin_callback_data *data = reinterpret_cast<plugin_callback_data*>(
async->data);
// Attach cb and user_data to plugin_callback so that it can access them later
v8::Local<v8::Function> plugin_callback = Nan::GetFunction(
Nan::New<v8::FunctionTemplate>(PluginCallback)).ToLocalChecked();
Nan::Set(plugin_callback, Nan::New("cb").ToLocalChecked(),
Nan::New<v8::External>(reinterpret_cast<void*>(data->cb)));
Nan::Set(plugin_callback, Nan::New("user_data").ToLocalChecked(),
Nan::New<v8::External>(data->user_data));
const int argc = 2;
v8::Local<v8::Value> argv[argc] = {
Nan::New(data->service_url).ToLocalChecked(),
plugin_callback
};
Nan::Callback *callback = data->state->callback;
callback->Call(argc, argv);
delete data;
uv_unref((uv_handle_t *)async);
uv_close((uv_handle_t *)async, (uv_close_cb)free);
}
void plugin_get_metadata(void *state, const char *service_url,
grpc_credentials_plugin_metadata_cb cb,
void *user_data) {
uv_async_t *async = static_cast<uv_async_t*>(malloc(sizeof(uv_async_t)));
uv_async_init(uv_default_loop(),
async,
SendPluginCallback);
plugin_callback_data *data = new plugin_callback_data;
data->state = reinterpret_cast<plugin_state*>(state);
data->service_url = service_url;
data->cb = cb;
data->user_data = user_data;
async->data = data;
/* libuv says that it will coalesce calls to uv_async_send. If there is ever a
* problem with a callback not getting called, that is probably the reason */
uv_async_send(async);
}
void plugin_destroy_state(void *ptr) {
plugin_state *state = reinterpret_cast<plugin_state *>(ptr);
delete state->callback;
}
} // namespace node
} // namespace grpc

@ -0,0 +1,100 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef GRPC_NODE_CALL_CREDENTIALS_H_
#define GRPC_NODE_CALL_CREDENTIALS_H_
#include <node.h>
#include <nan.h>
#include "grpc/grpc_security.h"
namespace grpc {
namespace node {
class CallCredentials : public Nan::ObjectWrap {
public:
static void Init(v8::Local<v8::Object> exports);
static bool HasInstance(v8::Local<v8::Value> val);
/* Wrap a grpc_credentials struct in a javascript object */
static v8::Local<v8::Value> WrapStruct(grpc_credentials *credentials);
/* Returns the grpc_credentials struct that this object wraps */
grpc_credentials *GetWrappedCredentials();
private:
explicit CallCredentials(grpc_credentials *credentials);
~CallCredentials();
// Prevent copying
CallCredentials(const CallCredentials &);
CallCredentials &operator=(const CallCredentials &);
static NAN_METHOD(New);
static NAN_METHOD(CreateSsl);
static NAN_METHOD(CreateFromPlugin);
static NAN_METHOD(Compose);
static Nan::Callback *constructor;
// Used for typechecking instances of this javascript class
static Nan::Persistent<v8::FunctionTemplate> fun_tpl;
grpc_credentials *wrapped_credentials;
};
/* Auth metadata plugin functionality */
typedef struct plugin_state {
Nan::Callback *callback;
} plugin_state;
typedef struct plugin_callback_data {
plugin_state *state;
const char *service_url;
grpc_credentials_plugin_metadata_cb cb;
void *user_data;
} plugin_callback_data;
void plugin_get_metadata(void *state, const char *service_url,
grpc_credentials_plugin_metadata_cb cb,
void *user_data);
void plugin_destroy_state(void *state);
NAN_METHOD(PluginCallback);
NAUV_WORK_CB(SendPluginCallback);
} // namespace node
} // namepsace grpc
#endif // GRPC_NODE_CALL_CREDENTIALS_H_

@ -42,7 +42,7 @@
#include "call.h"
#include "channel.h"
#include "completion_queue_async_worker.h"
#include "credentials.h"
#include "channel_credentials.h"
#include "timeval.h"
namespace grpc {
@ -112,11 +112,11 @@ NAN_METHOD(Channel::New) {
// Owned by the Channel object
Utf8String host(info[0]);
grpc_credentials *creds;
if (!Credentials::HasInstance(info[1])) {
if (!ChannelCredentials::HasInstance(info[1])) {
return Nan::ThrowTypeError(
"Channel's second argument must be a credential");
"Channel's second argument must be a ChannelCredentials");
}
Credentials *creds_object = ObjectWrap::Unwrap<Credentials>(
ChannelCredentials *creds_object = ObjectWrap::Unwrap<ChannelCredentials>(
Nan::To<Object>(info[1]).ToLocalChecked());
creds = creds_object->GetWrappedCredentials();
grpc_channel_args *channel_args_ptr;

@ -36,7 +36,9 @@
#include "grpc/grpc.h"
#include "grpc/grpc_security.h"
#include "grpc/support/log.h"
#include "credentials.h"
#include "channel_credentials.h"
#include "call_credentials.h"
#include "call.h"
namespace grpc {
namespace node {
@ -60,51 +62,40 @@ using v8::Object;
using v8::ObjectTemplate;
using v8::Value;
Nan::Callback *Credentials::constructor;
Persistent<FunctionTemplate> Credentials::fun_tpl;
Nan::Callback *ChannelCredentials::constructor;
Persistent<FunctionTemplate> ChannelCredentials::fun_tpl;
Credentials::Credentials(grpc_credentials *credentials)
ChannelCredentials::ChannelCredentials(grpc_credentials *credentials)
: wrapped_credentials(credentials) {}
Credentials::~Credentials() {
ChannelCredentials::~ChannelCredentials() {
grpc_credentials_release(wrapped_credentials);
}
void Credentials::Init(Local<Object> exports) {
void ChannelCredentials::Init(Local<Object> exports) {
HandleScope scope;
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New("Credentials").ToLocalChecked());
tpl->SetClassName(Nan::New("ChannelCredentials").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(tpl, "compose", Compose);
fun_tpl.Reset(tpl);
Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
Nan::Set(ctr, Nan::New("createDefault").ToLocalChecked(),
Nan::GetFunction(
Nan::New<FunctionTemplate>(CreateDefault)).ToLocalChecked());
Nan::Set(ctr, Nan::New("createSsl").ToLocalChecked(),
Nan::GetFunction(
Nan::New<FunctionTemplate>(CreateSsl)).ToLocalChecked());
Nan::Set(ctr, Nan::New("createComposite").ToLocalChecked(),
Nan::GetFunction(
Nan::New<FunctionTemplate>(CreateComposite)).ToLocalChecked());
Nan::Set(ctr, Nan::New("createGce").ToLocalChecked(),
Nan::GetFunction(
Nan::New<FunctionTemplate>(CreateGce)).ToLocalChecked());
Nan::Set(ctr, Nan::New("createIam").ToLocalChecked(),
Nan::GetFunction(
Nan::New<FunctionTemplate>(CreateIam)).ToLocalChecked());
Nan::Set(ctr, Nan::New("createInsecure").ToLocalChecked(),
Nan::GetFunction(
Nan::New<FunctionTemplate>(CreateInsecure)).ToLocalChecked());
Nan::Set(exports, Nan::New("Credentials").ToLocalChecked(), ctr);
Nan::Set(exports, Nan::New("ChannelCredentials").ToLocalChecked(), ctr);
constructor = new Nan::Callback(ctr);
}
bool Credentials::HasInstance(Local<Value> val) {
bool ChannelCredentials::HasInstance(Local<Value> val) {
HandleScope scope;
return Nan::New(fun_tpl)->HasInstance(val);
}
Local<Value> Credentials::WrapStruct(grpc_credentials *credentials) {
Local<Value> ChannelCredentials::WrapStruct(grpc_credentials *credentials) {
EscapableHandleScope scope;
const int argc = 1;
Local<Value> argv[argc] = {
@ -118,20 +109,20 @@ Local<Value> Credentials::WrapStruct(grpc_credentials *credentials) {
}
}
grpc_credentials *Credentials::GetWrappedCredentials() {
grpc_credentials *ChannelCredentials::GetWrappedCredentials() {
return wrapped_credentials;
}
NAN_METHOD(Credentials::New) {
NAN_METHOD(ChannelCredentials::New) {
if (info.IsConstructCall()) {
if (!info[0]->IsExternal()) {
return Nan::ThrowTypeError(
"Credentials can only be created with the provided functions");
"ChannelCredentials can only be created with the provided functions");
}
Local<External> ext = info[0].As<External>();
grpc_credentials *creds_value =
reinterpret_cast<grpc_credentials *>(ext->Value());
Credentials *credentials = new Credentials(creds_value);
ChannelCredentials *credentials = new ChannelCredentials(creds_value);
credentials->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
@ -149,16 +140,7 @@ NAN_METHOD(Credentials::New) {
}
}
NAN_METHOD(Credentials::CreateDefault) {
grpc_credentials *creds = grpc_google_default_credentials_create();
if (creds == NULL) {
info.GetReturnValue().SetNull();
} else {
info.GetReturnValue().Set(WrapStruct(creds));
}
}
NAN_METHOD(Credentials::CreateSsl) {
NAN_METHOD(ChannelCredentials::CreateSsl) {
char *root_certs = NULL;
grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL};
if (::node::Buffer::HasInstance(info[0])) {
@ -188,49 +170,25 @@ NAN_METHOD(Credentials::CreateSsl) {
}
}
NAN_METHOD(Credentials::CreateComposite) {
if (!HasInstance(info[0])) {
NAN_METHOD(ChannelCredentials::Compose) {
if (!ChannelCredentials::HasInstance(info.This())) {
return Nan::ThrowTypeError(
"createComposite's first argument must be a Credentials object");
"compose can only be called on ChannelCredentials objects");
}
if (!HasInstance(info[1])) {
if (!CallCredentials::HasInstance(info[0])) {
return Nan::ThrowTypeError(
"createComposite's second argument must be a Credentials object");
"compose's first argument must be a CallCredentials object");
}
Credentials *creds1 = ObjectWrap::Unwrap<Credentials>(
ChannelCredentials *self = ObjectWrap::Unwrap<ChannelCredentials>(
info.This());
if (self->wrapped_credentials == NULL) {
return Nan::ThrowTypeError(
"Cannot compose insecure credential");
}
CallCredentials *other = ObjectWrap::Unwrap<CallCredentials>(
Nan::To<Object>(info[0]).ToLocalChecked());
Credentials *creds2 = ObjectWrap::Unwrap<Credentials>(
Nan::To<Object>(info[1]).ToLocalChecked());
grpc_credentials *creds = grpc_composite_credentials_create(
creds1->wrapped_credentials, creds2->wrapped_credentials, NULL);
if (creds == NULL) {
info.GetReturnValue().SetNull();
} else {
info.GetReturnValue().Set(WrapStruct(creds));
}
}
NAN_METHOD(Credentials::CreateGce) {
Nan::HandleScope scope;
grpc_credentials *creds = grpc_google_compute_engine_credentials_create(NULL);
if (creds == NULL) {
info.GetReturnValue().SetNull();
} else {
info.GetReturnValue().Set(WrapStruct(creds));
}
}
NAN_METHOD(Credentials::CreateIam) {
if (!info[0]->IsString()) {
return Nan::ThrowTypeError("createIam's first argument must be a string");
}
if (!info[1]->IsString()) {
return Nan::ThrowTypeError("createIam's second argument must be a string");
}
Utf8String auth_token(info[0]);
Utf8String auth_selector(info[1]);
grpc_credentials *creds =
grpc_google_iam_credentials_create(*auth_token, *auth_selector, NULL);
self->wrapped_credentials, other->GetWrappedCredentials(), NULL);
if (creds == NULL) {
info.GetReturnValue().SetNull();
} else {
@ -238,7 +196,7 @@ NAN_METHOD(Credentials::CreateIam) {
}
}
NAN_METHOD(Credentials::CreateInsecure) {
NAN_METHOD(ChannelCredentials::CreateInsecure) {
info.GetReturnValue().Set(WrapStruct(NULL));
}

@ -31,8 +31,8 @@
*
*/
#ifndef NET_GRPC_NODE_CREDENTIALS_H_
#define NET_GRPC_NODE_CREDENTIALS_H_
#ifndef NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_
#define NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_
#include <node.h>
#include <nan.h>
@ -43,7 +43,7 @@ namespace grpc {
namespace node {
/* Wrapper class for grpc_credentials structs */
class Credentials : public Nan::ObjectWrap {
class ChannelCredentials : public Nan::ObjectWrap {
public:
static void Init(v8::Local<v8::Object> exports);
static bool HasInstance(v8::Local<v8::Value> val);
@ -54,21 +54,18 @@ class Credentials : public Nan::ObjectWrap {
grpc_credentials *GetWrappedCredentials();
private:
explicit Credentials(grpc_credentials *credentials);
~Credentials();
explicit ChannelCredentials(grpc_credentials *credentials);
~ChannelCredentials();
// Prevent copying
Credentials(const Credentials &);
Credentials &operator=(const Credentials &);
ChannelCredentials(const ChannelCredentials &);
ChannelCredentials &operator=(const ChannelCredentials &);
static NAN_METHOD(New);
static NAN_METHOD(CreateDefault);
static NAN_METHOD(CreateSsl);
static NAN_METHOD(CreateComposite);
static NAN_METHOD(CreateGce);
static NAN_METHOD(CreateFake);
static NAN_METHOD(CreateIam);
static NAN_METHOD(CreateInsecure);
static NAN_METHOD(Compose);
static Nan::Callback *constructor;
// Used for typechecking instances of this javascript class
static Nan::Persistent<v8::FunctionTemplate> fun_tpl;
@ -79,4 +76,4 @@ class Credentials : public Nan::ObjectWrap {
} // namespace node
} // namespace grpc
#endif // NET_GRPC_NODE_CREDENTIALS_H_
#endif // NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_

@ -37,10 +37,11 @@
#include "grpc/grpc.h"
#include "call.h"
#include "call_credentials.h"
#include "channel.h"
#include "channel_credentials.h"
#include "server.h"
#include "completion_queue_async_worker.h"
#include "credentials.h"
#include "server_credentials.h"
using v8::Local;
@ -240,10 +241,11 @@ void init(Local<Object> exports) {
InitWriteFlags(exports);
grpc::node::Call::Init(exports);
grpc::node::CallCredentials::Init(exports);
grpc::node::Channel::Init(exports);
grpc::node::ChannelCredentials::Init(exports);
grpc::node::Server::Init(exports);
grpc::node::CompletionQueueAsyncWorker::Init(exports);
grpc::node::Credentials::Init(exports);
grpc::node::ServerCredentials::Init(exports);
}

@ -93,32 +93,6 @@ exports.load = function load(filename, format) {
return loadObject(builder.ns);
};
/**
* Get a function that a client can use to update metadata with authentication
* information from a Google Auth credential object, which comes from the
* google-auth-library.
* @param {Object} credential The credential object to use
* @return {function(Object, callback)} Metadata updater function
*/
exports.getGoogleAuthDelegate = function getGoogleAuthDelegate(credential) {
/**
* Update a metadata object with authentication information.
* @param {string} authURI The uri to authenticate to
* @param {Object} metadata Metadata object
* @param {function(Error, Object)} callback
*/
return function updateMetadata(authURI, metadata, callback) {
credential.getRequestMetadata(authURI, function(err, header) {
if (err) {
callback(err);
return;
}
metadata.add('authorization', header.Authorization);
callback(null, metadata);
});
};
};
/**
* @see module:src/server.Server
*/
@ -152,7 +126,7 @@ exports.writeFlags = grpc.writeFlags;
/**
* Credentials factories
*/
exports.Credentials = grpc.Credentials;
exports.credentials = require('./src/credentials.js');
/**
* ServerCredentials factories

@ -354,33 +354,26 @@ function customMetadata(client, done) {
* primarily for use with mocha
*/
function authTest(expected_user, scope, client, done) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
var arg = {
response_type: 'COMPRESSABLE',
response_size: 314159,
payload: {
body: zeroBuffer(271828)
},
fill_username: true,
fill_oauth_scope: true
};
client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
if (credential.createScopedRequired() && scope) {
credential = credential.createScoped(scope);
assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
assert.strictEqual(resp.payload.body.length, 314159);
assert.strictEqual(resp.username, expected_user);
if (scope) {
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
}
if (done) {
done();
}
client.$updateMetadata = grpc.getGoogleAuthDelegate(credential);
var arg = {
response_type: 'COMPRESSABLE',
response_size: 314159,
payload: {
body: zeroBuffer(271828)
},
fill_username: true,
fill_oauth_scope: true
};
client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
assert.strictEqual(resp.payload.body.length, 314159);
assert.strictEqual(resp.username, expected_user);
if (scope) {
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
}
if (done) {
done();
}
});
});
}
@ -419,25 +412,86 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) {
});
}
function perRpcAuthTest(expected_user, scope, per_rpc, client, done) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
assert.ifError(err);
var arg = {
fill_username: true,
fill_oauth_scope: true
};
if (credential.createScopedRequired() && scope) {
credential = credential.createScoped(scope);
}
var creds = grpc.credentials.createFromGoogleCredential(credential);
client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.username, expected_user);
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
if (done) {
done();
}
}, null, {credentials: creds});
});
}
function getApplicationCreds(scope, callback) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
if (err) {
callback(err);
return;
}
if (credential.createScopedRequired() && scope) {
credential = credential.createScoped(scope);
}
callback(null, grpc.credentials.createFromGoogleCredential(credential));
});
}
function getOauth2Creds(scope, callback) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
if (err) {
callback(err);
return;
}
credential = credential.createScoped(scope);
credential.getAccessToken(function(err, token) {
if (err) {
callback(err);
return;
}
var updateMd = function(service_url, callback) {
var metadata = new grpc.Metadata();
metadata.add('authorization', 'Bearer ' + token);
callback(null, metadata);
};
callback(null, grpc.credentials.createFromMetadataGenerator(updateMd));
});
});
}
/**
* Map from test case names to test functions
*/
var test_cases = {
empty_unary: emptyUnary,
large_unary: largeUnary,
client_streaming: clientStreaming,
server_streaming: serverStreaming,
ping_pong: pingPong,
empty_stream: emptyStream,
cancel_after_begin: cancelAfterBegin,
cancel_after_first_response: cancelAfterFirstResponse,
timeout_on_sleeping_server: timeoutOnSleepingServer,
custom_metadata: customMetadata,
compute_engine_creds: _.partial(authTest, COMPUTE_ENGINE_USER, null),
service_account_creds: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
jwt_token_creds: _.partial(authTest, AUTH_USER, null),
oauth2_auth_token: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false),
per_rpc_creds: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, true)
empty_unary: {run: emptyUnary},
large_unary: {run: largeUnary},
client_streaming: {run: clientStreaming},
server_streaming: {run: serverStreaming},
ping_pong: {run: pingPong},
empty_stream: {run: emptyStream},
cancel_after_begin: {run: cancelAfterBegin},
cancel_after_first_response: {run: cancelAfterFirstResponse},
timeout_on_sleeping_server: {run: timeoutOnSleepingServer},
custom_metadata: {run: customMetadata},
compute_engine_creds: {run: _.partial(authTest, COMPUTE_ENGINE_USER, null),
getCreds: _.partial(getApplicationCreds, null)},
service_account_creds: {run: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
getCreds: _.partial(getApplicationCreds, AUTH_SCOPE)},
jwt_token_creds: {run: _.partial(authTest, AUTH_USER, null),
getCreds: _.partial(getApplicationCreds, null)},
oauth2_auth_token: {run: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false),
getCreds: _.partial(getOauth2Creds, AUTH_SCOPE)},
per_rpc_creds: {run: _.partial(perRpcAuthTest, AUTH_USER, AUTH_SCOPE, true)}
};
/**
@ -463,17 +517,30 @@ function runTest(address, host_override, test_case, tls, test_ca, done) {
ca_path = process.env.SSL_CERT_FILE;
}
var ca_data = fs.readFileSync(ca_path);
creds = grpc.Credentials.createSsl(ca_data);
creds = grpc.credentials.createSsl(ca_data);
if (host_override) {
options['grpc.ssl_target_name_override'] = host_override;
options['grpc.default_authority'] = host_override;
}
} else {
creds = grpc.Credentials.createInsecure();
creds = grpc.credentials.createInsecure();
}
var client = new testProto.TestService(address, creds, options);
var test = test_cases[test_case];
test_cases[test_case](client, done);
var execute = function(err, creds) {
assert.ifError(err);
var client = new testProto.TestService(address, creds, options);
test.run(client, done);
};
if (test.getCreds) {
test.getCreds(function(err, new_creds) {
execute(err, grpc.credentials.combineChannelCredentials(
creds, new_creds));
});
} else {
execute(null, creds);
}
}
if (require.main === module) {

@ -42,7 +42,7 @@ function runTest(iterations, callback) {
var testServer = interop_server.getServer(0, false);
testServer.server.start();
var client = new testProto.TestService('localhost:' + testServer.port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
function runIterations(finish) {
var start = process.hrtime();

@ -62,7 +62,7 @@ function runTest(concurrent_calls, seconds, callback) {
var testServer = interop_server.getServer(0, false);
testServer.server.start();
var client = new testProto.TestService('localhost:' + testServer.port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
var warmup_num = 100;

@ -233,17 +233,23 @@ function getCall(channel, method, options) {
var host;
var parent;
var propagate_flags;
var credentials;
if (options) {
deadline = options.deadline;
host = options.host;
parent = _.get(options, 'parent.call');
propagate_flags = options.propagate_flags;
credentials = options.credentials;
}
if (deadline === undefined) {
deadline = Infinity;
}
return new grpc.Call(channel, method, deadline, host,
parent, propagate_flags);
var call = new grpc.Call(channel, method, deadline, host,
parent, propagate_flags);
if (credentials) {
call.setCredentials(credentials);
}
return call;
}
/**
@ -282,60 +288,53 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
emitter.getPeer = function getPeer() {
return call.getPeer();
};
this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
if (error) {
call.cancel();
callback(error);
return;
}
var client_batch = {};
var message = serialize(argument);
if (options) {
message.grpcWriteFlags = options.flags;
}
client_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
client_batch[grpc.opType.SEND_MESSAGE] = message;
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) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
var status = response.status;
var error;
var deserialized;
if (status.code === grpc.status.OK) {
if (err) {
// Got a batch error, but OK status. Something went wrong
callback(err);
return;
} else {
try {
deserialized = deserialize(response.read);
} catch (e) {
/* Change status to indicate bad server response. This will result
* in passing an error to the callback */
status = {
code: grpc.status.INTERNAL,
details: 'Failed to parse server response'
};
}
}
}
if (status.code !== grpc.status.OK) {
error = new Error(response.status.details);
error.code = status.code;
error.metadata = status.metadata;
callback(error);
var client_batch = {};
var message = serialize(argument);
if (options) {
message.grpcWriteFlags = options.flags;
}
client_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
client_batch[grpc.opType.SEND_MESSAGE] = message;
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) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
var status = response.status;
var error;
var deserialized;
if (status.code === grpc.status.OK) {
if (err) {
// Got a batch error, but OK status. Something went wrong
callback(err);
return;
} else {
callback(null, deserialized);
try {
deserialized = deserialize(response.read);
} catch (e) {
/* Change status to indicate bad server response. This will result
* in passing an error to the callback */
status = {
code: grpc.status.INTERNAL,
details: 'Failed to parse server response'
};
}
}
emitter.emit('status', status);
emitter.emit('metadata', Metadata._fromCoreRepresentation(
response.metadata));
});
}
if (status.code !== grpc.status.OK) {
error = new Error(response.status.details);
error.code = status.code;
error.metadata = status.metadata;
callback(error);
} else {
callback(null, deserialized);
}
emitter.emit('status', status);
emitter.emit('metadata', Metadata._fromCoreRepresentation(
response.metadata));
});
return emitter;
}
@ -371,62 +370,55 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
metadata = metadata.clone();
}
var stream = new ClientWritableStream(call, serialize);
this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
if (error) {
call.cancel();
callback(error);
var metadata_batch = {};
metadata_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
call.startBatch(metadata_batch, function(err, response) {
if (err) {
// The call has stopped for some reason. A non-OK status will arrive
// in the other batch.
return;
}
var metadata_batch = {};
metadata_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
call.startBatch(metadata_batch, function(err, response) {
stream.emit('metadata', Metadata._fromCoreRepresentation(
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) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
var status = response.status;
var error;
var deserialized;
if (status.code === grpc.status.OK) {
if (err) {
// The call has stopped for some reason. A non-OK status will arrive
// in the other batch.
// Got a batch error, but OK status. Something went wrong
callback(err);
return;
}
stream.emit('metadata', Metadata._fromCoreRepresentation(
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) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
var status = response.status;
var error;
var deserialized;
if (status.code === grpc.status.OK) {
if (err) {
// Got a batch error, but OK status. Something went wrong
callback(err);
return;
} else {
try {
deserialized = deserialize(response.read);
} catch (e) {
/* Change status to indicate bad server response. This will result
* in passing an error to the callback */
status = {
code: grpc.status.INTERNAL,
details: 'Failed to parse server response'
};
}
}
}
if (status.code !== grpc.status.OK) {
error = new Error(response.status.details);
error.code = status.code;
error.metadata = status.metadata;
callback(error);
} else {
callback(null, deserialized);
try {
deserialized = deserialize(response.read);
} catch (e) {
/* Change status to indicate bad server response. This will result
* in passing an error to the callback */
status = {
code: grpc.status.INTERNAL,
details: 'Failed to parse server response'
};
}
}
stream.emit('status', status);
});
}
if (status.code !== grpc.status.OK) {
error = new Error(response.status.details);
error.code = status.code;
error.metadata = status.metadata;
callback(error);
} else {
callback(null, deserialized);
}
stream.emit('status', status);
});
return stream;
}
@ -462,51 +454,44 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
metadata = metadata.clone();
}
var stream = new ClientReadableStream(call, deserialize);
this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
if (error) {
call.cancel();
stream.emit('error', error);
var start_batch = {};
var message = serialize(argument);
if (options) {
message.grpcWriteFlags = options.flags;
}
start_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
start_batch[grpc.opType.SEND_MESSAGE] = message;
start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
call.startBatch(start_batch, function(err, response) {
if (err) {
// The call has stopped for some reason. A non-OK status will arrive
// in the other batch.
return;
}
var start_batch = {};
var message = serialize(argument);
if (options) {
message.grpcWriteFlags = options.flags;
}
start_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
start_batch[grpc.opType.SEND_MESSAGE] = message;
start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
call.startBatch(start_batch, function(err, response) {
stream.emit('metadata', Metadata._fromCoreRepresentation(
response.metadata));
});
var status_batch = {};
status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(status_batch, function(err, response) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
stream.emit('status', response.status);
if (response.status.code !== grpc.status.OK) {
var error = new Error(response.status.details);
error.code = response.status.code;
error.metadata = response.status.metadata;
stream.emit('error', error);
return;
} else {
if (err) {
// The call has stopped for some reason. A non-OK status will arrive
// in the other batch.
return;
}
stream.emit('metadata', Metadata._fromCoreRepresentation(
response.metadata));
});
var status_batch = {};
status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(status_batch, function(err, response) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
stream.emit('status', response.status);
if (response.status.code !== grpc.status.OK) {
var error = new Error(response.status.details);
error.code = response.status.code;
error.metadata = response.status.metadata;
stream.emit('error', error);
// Got a batch error, but OK status. Something went wrong
stream.emit('error', err);
return;
} else {
if (err) {
// Got a batch error, but OK status. Something went wrong
stream.emit('error', err);
return;
}
}
});
}
});
return stream;
}
@ -540,45 +525,38 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
metadata = metadata.clone();
}
var stream = new ClientDuplexStream(call, serialize, deserialize);
this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
if (error) {
call.cancel();
stream.emit('error', error);
var start_batch = {};
start_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
call.startBatch(start_batch, function(err, response) {
if (err) {
// The call has stopped for some reason. A non-OK status will arrive
// in the other batch.
return;
}
var start_batch = {};
start_batch[grpc.opType.SEND_INITIAL_METADATA] =
metadata._getCoreRepresentation();
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
call.startBatch(start_batch, function(err, response) {
stream.emit('metadata', Metadata._fromCoreRepresentation(
response.metadata));
});
var status_batch = {};
status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(status_batch, function(err, response) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
stream.emit('status', response.status);
if (response.status.code !== grpc.status.OK) {
var error = new Error(response.status.details);
error.code = response.status.code;
error.metadata = response.status.metadata;
stream.emit('error', error);
return;
} else {
if (err) {
// The call has stopped for some reason. A non-OK status will arrive
// in the other batch.
// Got a batch error, but OK status. Something went wrong
stream.emit('error', err);
return;
}
stream.emit('metadata', Metadata._fromCoreRepresentation(
response.metadata));
});
var status_batch = {};
status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(status_batch, function(err, response) {
response.status.metadata = Metadata._fromCoreRepresentation(
response.status.metadata);
stream.emit('status', response.status);
if (response.status.code !== grpc.status.OK) {
var error = new Error(response.status.details);
error.code = response.status.code;
error.metadata = response.status.metadata;
stream.emit('error', error);
return;
} else {
if (err) {
// Got a batch error, but OK status. Something went wrong
stream.emit('error', err);
return;
}
}
});
}
});
return stream;
}
@ -618,15 +596,8 @@ exports.makeClientConstructor = function(methods, serviceName) {
* @param {grpc.Credentials} credentials Credentials to use to connect
* to the server
* @param {Object} options Options to pass to the underlying channel
* @param {function(string, Object, function)=} updateMetadata function to
* update the metadata for each request
*/
function Client(address, credentials, options, updateMetadata) {
if (!updateMetadata) {
updateMetadata = function(uri, metadata, callback) {
callback(null, metadata);
};
}
function Client(address, credentials, options) {
if (!options) {
options = {};
}
@ -634,11 +605,6 @@ exports.makeClientConstructor = function(methods, serviceName) {
/* Private fields use $ as a prefix instead of _ because it is an invalid
* prefix of a method name */
this.$channel = new grpc.Channel(address, credentials, options);
// Remove the optional DNS scheme, trailing port, and trailing backslash
address = address.replace(/^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$/, '$2');
this.$server_address = address;
this.$auth_uri = 'https://' + this.$server_address + '/' + serviceName;
this.$updateMetadata = updateMetadata;
}
_.each(methods, function(attrs, name) {

@ -0,0 +1,163 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* Credentials module
*
* This module contains factory methods for two different credential types:
* CallCredentials and ChannelCredentials. ChannelCredentials are things like
* SSL credentials that can be used to secure a connection, and are used to
* construct a Client object. CallCredentials genrally modify metadata, so they
* can be attached to an individual method call.
*
* CallCredentials can be composed with other CallCredentials to create
* CallCredentials. ChannelCredentials can be composed with CallCredentials
* to create ChannelCredentials. No combined credential can have more than
* one ChannelCredentials.
*
* For example, to create a client secured with SSL that uses Google
* default application credentials to authenticate:
*
* var channel_creds = credentials.createSsl(root_certs);
* (new GoogleAuth()).getApplicationDefault(function(err, credential) {
* var call_creds = credentials.createFromGoogleCredential(credential);
* var combined_creds = credentials.combineChannelCredentials(
* channel_creds, call_creds);
* var client = new Client(address, combined_creds);
* });
*
* @module
*/
'use strict';
var grpc = require('bindings')('grpc_node.node');
var CallCredentials = grpc.CallCredentials;
var ChannelCredentials = grpc.ChannelCredentials;
var Metadata = require('./metadata.js');
/**
* Create an SSL Credentials object. If using a client-side certificate, both
* the second and third arguments must be passed.
* @param {Buffer} root_certs The root certificate data
* @param {Buffer=} private_key The client certificate private key, if
* applicable
* @param {Buffer=} cert_chain The client certificate cert chain, if applicable
* @return {ChannelCredentials} The SSL Credentials object
*/
exports.createSsl = ChannelCredentials.createSsl;
/**
* Create a gRPC credentials object from a metadata generation function. This
* function gets the service URL and a callback as parameters. The error
* passed to the callback can optionally have a 'code' value attached to it,
* which corresponds to a status code that this library uses.
* @param {function(String, function(Error, Metadata))} metadata_generator The
* function that generates metadata
* @return {CallCredentials} The credentials object
*/
exports.createFromMetadataGenerator = function(metadata_generator) {
return CallCredentials.createFromPlugin(function(service_url, callback) {
metadata_generator(service_url, function(error, metadata) {
var code = grpc.status.OK;
var message = '';
if (error) {
message = error.message;
if (error.hasOwnProperty('code')) {
code = error.code;
}
}
callback(code, message, metadata._getCoreRepresentation());
});
});
};
/**
* Create a gRPC credential from a Google credential object.
* @param {Object} google_credential The Google credential object to use
* @return {CallCredentials} The resulting credentials object
*/
exports.createFromGoogleCredential = function(google_credential) {
return exports.createFromMetadataGenerator(function(service_url, callback) {
google_credential.getRequestMetadata(service_url, function(err, header) {
if (err) {
callback(err);
return;
}
var metadata = new Metadata();
metadata.add('authorization', header.Authorization);
callback(null, metadata);
});
});
};
/**
* Combine a ChannelCredentials with any number of CallCredentials into a single
* ChannelCredentials object.
* @param {ChannelCredentials} channel_credential The ChannelCredentials to
* start with
* @param {...CallCredentials} credentials The CallCredentials to compose
* @return ChannelCredentials A credentials object that combines all of the
* input credentials
*/
exports.combineChannelCredentials = function(channel_credential) {
var current = channel_credential;
for (var i = 1; i < arguments.length; i++) {
current = current.compose(arguments[i]);
}
return current;
};
/**
* Combine any number of CallCredentials into a single CallCredentials object
* @param {...CallCredentials} credentials the CallCredentials to compose
* @return CallCredentials A credentials object that combines all of the input
* credentials
*/
exports.combineCallCredentials = function() {
var current = arguments[0];
for (var i = 1; i < arguments.length; i++) {
current = current.compose(arguments[i]);
}
return current;
}
/**
* Create an insecure credentials object. This is used to create a channel that
* does not use SSL. This cannot be composed with anything.
* @return {ChannelCredentials} The insecure credentials object
*/
exports.createInsecure = ChannelCredentials.createInsecure;

@ -48,7 +48,7 @@ function getDeadline(timeout_secs) {
return deadline;
}
var insecureCreds = grpc.Credentials.createInsecure();
var insecureCreds = grpc.ChannelCredentials.createInsecure();
describe('call', function() {
var channel;

@ -56,7 +56,7 @@ function multiDone(done, count) {
}
};
}
var insecureCreds = grpc.Credentials.createInsecure();
var insecureCreds = grpc.ChannelCredentials.createInsecure();
describe('channel', function() {
describe('constructor', function() {
@ -149,12 +149,13 @@ describe('channel', function() {
afterEach(function() {
channel.close();
});
it('should time out if called alone', function(done) {
it.only('should time out if called alone', function(done) {
var old_state = channel.getConnectivityState();
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 1);
channel.watchConnectivityState(old_state, deadline, function(err, value) {
assert(err);
console.log('Callback from watchConnectivityState');
done();
});
});

@ -0,0 +1,243 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
'use strict';
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var grpc = require('..');
/**
* This is used for testing functions with multiple asynchronous calls that
* can happen in different orders. This should be passed the number of async
* function invocations that can occur last, and each of those should call this
* function's return value
* @param {function()} done The function that should be called when a test is
* complete.
* @param {number} count The number of calls to the resulting function if the
* test passes.
* @return {function()} The function that should be called at the end of each
* sequence of asynchronous functions.
*/
function multiDone(done, count) {
return function() {
count -= 1;
if (count <= 0) {
done();
}
};
}
describe('client credentials', function() {
var Client;
var server;
var port;
var client_ssl_creds;
var client_options = {};
before(function() {
var proto = grpc.load(__dirname + '/test_service.proto');
server = new grpc.Server();
server.addProtoService(proto.TestService.service, {
unary: function(call, cb) {
call.sendMetadata(call.metadata);
cb(null, {});
},
clientStream: function(stream, cb){
stream.on('data', function(data) {});
stream.on('end', function() {
stream.sendMetadata(stream.metadata);
cb(null, {});
});
},
serverStream: function(stream) {
stream.sendMetadata(stream.metadata);
stream.end();
},
bidiStream: function(stream) {
stream.on('data', function(data) {});
stream.on('end', function() {
stream.sendMetadata(stream.metadata);
stream.end();
});
}
});
var key_path = path.join(__dirname, './data/server1.key');
var pem_path = path.join(__dirname, './data/server1.pem');
var key_data = fs.readFileSync(key_path);
var pem_data = fs.readFileSync(pem_path);
var creds = grpc.ServerCredentials.createSsl(null,
[{private_key: key_data,
cert_chain: pem_data}]);
//creds = grpc.ServerCredentials.createInsecure();
port = server.bind('localhost:0', creds);
server.start();
Client = proto.TestService;
var ca_path = path.join(__dirname, '../test/data/ca.pem');
var ca_data = fs.readFileSync(ca_path);
client_ssl_creds = grpc.credentials.createSsl(ca_data);
var host_override = 'foo.test.google.fr';
client_options['grpc.ssl_target_name_override'] = host_override;
client_options['grpc.default_authority'] = host_override;
});
after(function() {
server.forceShutdown();
});
it('Should accept SSL creds for a client', function(done) {
var client = new Client('localhost:' + port, client_ssl_creds,
client_options);
client.unary({}, function(err, data) {
assert.ifError(err);
done();
});
});
it('Should update metadata with SSL creds', function(done) {
var metadataUpdater = function(service_url, callback) {
var metadata = new grpc.Metadata();
metadata.set('plugin_key', 'plugin_value');
callback(null, metadata);
};
var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
creds);
var client = new Client('localhost:' + port, combined_creds,
client_options);
var call = client.unary({}, function(err, data) {
assert.ifError(err);
});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
done();
});
});
it('Should update metadata for two simultaneous calls', function(done) {
done = multiDone(done, 2);
var metadataUpdater = function(service_url, callback) {
var metadata = new grpc.Metadata();
metadata.set('plugin_key', 'plugin_value');
callback(null, metadata);
};
var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
creds);
var client = new Client('localhost:' + port, combined_creds,
client_options);
var call = client.unary({}, function(err, data) {
assert.ifError(err);
});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
done();
});
var call2 = client.unary({}, function(err, data) {
assert.ifError(err);
});
call2.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
done();
});
});
describe('Per-rpc creds', function() {
var client;
var updater_creds;
before(function() {
client = new Client('localhost:' + port, client_ssl_creds,
client_options);
var metadataUpdater = function(service_url, callback) {
var metadata = new grpc.Metadata();
metadata.set('plugin_key', 'plugin_value');
callback(null, metadata);
};
updater_creds = grpc.credentials.createFromMetadataGenerator(
metadataUpdater);
});
it('Should update metadata on a unary call', function(done) {
var call = client.unary({}, function(err, data) {
assert.ifError(err);
}, null, {credentials: updater_creds});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
done();
});
});
it('should update metadata on a client streaming call', function(done) {
var call = client.clientStream(function(err, data) {
assert.ifError(err);
}, null, {credentials: updater_creds});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
done();
});
call.end();
});
it('should update metadata on a server streaming call', function(done) {
var call = client.serverStream({}, null, {credentials: updater_creds});
call.on('data', function() {});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
done();
});
});
it('should update metadata on a bidi streaming call', function(done) {
var call = client.bidiStream(null, {credentials: updater_creds});
call.on('data', function() {});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
done();
});
call.end();
});
it('should be able to use multiple plugin credentials', function(done) {
var altMetadataUpdater = function(service_url, callback) {
var metadata = new grpc.Metadata();
metadata.set('other_plugin_key', 'other_plugin_value');
callback(null, metadata);
};
var alt_updater_creds = grpc.credentials.createFromMetadataGenerator(
altMetadataUpdater);
var combined_updater = grpc.credentials.combineCallCredentials(
updater_creds, alt_updater_creds);
var call = client.unary({}, function(err, data) {
assert.ifError(err);
}, null, {credentials: updater_creds});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
assert.deepEqual(metadata.get('other_plugin_key'),
['other_plugin_value']);
done();
});
});
});
});

@ -57,7 +57,7 @@ function multiDone(done, count) {
};
}
var insecureCreds = grpc.Credentials.createInsecure();
var insecureCreds = grpc.ChannelCredentials.createInsecure();
describe('end-to-end', function() {
var server;

@ -54,7 +54,7 @@ describe('Health Checking', function() {
grpc.ServerCredentials.createInsecure());
healthServer.start();
healthClient = new health.Client('localhost:' + port_num,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
});
after(function() {
healthServer.forceShutdown();

@ -56,7 +56,7 @@ describe('Math client', function() {
grpc.ServerCredentials.createInsecure());
server.start();
math_client = new math.Math('localhost:' + port_num,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
done();
});
after(function() {

@ -163,7 +163,7 @@ describe('waitForClientReady', function() {
Client = surface_client.makeProtobufClientConstructor(mathService);
});
beforeEach(function() {
client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
client = new Client('localhost:' + port, grpc.credentials.createInsecure());
});
after(function() {
server.forceShutdown();
@ -217,7 +217,7 @@ describe('Echo service', function() {
});
var port = server.bind('localhost:0', server_insecure_creds);
var Client = surface_client.makeProtobufClientConstructor(echo_service);
client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
client = new Client('localhost:' + port, grpc.credentials.createInsecure());
server.start();
});
after(function() {
@ -263,7 +263,7 @@ describe('Generic client and server', function() {
server.start();
var Client = grpc.makeGenericClientConstructor(string_service_attrs);
client = new Client('localhost:' + port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
});
after(function() {
server.forceShutdown();
@ -311,7 +311,7 @@ describe('Echo metadata', function() {
});
var port = server.bind('localhost:0', server_insecure_creds);
var Client = surface_client.makeProtobufClientConstructor(test_service);
client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
client = new Client('localhost:' + port, grpc.credentials.createInsecure());
server.start();
metadata = new grpc.Metadata();
metadata.set('key', 'value');
@ -437,7 +437,7 @@ describe('Other conditions', function() {
});
port = server.bind('localhost:0', server_insecure_creds);
Client = surface_client.makeProtobufClientConstructor(test_service);
client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
client = new Client('localhost:' + port, grpc.credentials.createInsecure());
server.start();
});
after(function() {
@ -484,7 +484,7 @@ describe('Other conditions', function() {
var Client = surface_client.makeClientConstructor(test_service_attrs,
'TestService');
misbehavingClient = new Client('localhost:' + port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
});
it('should respond correctly to a unary call', function(done) {
misbehavingClient.unary(badArg, function(err, data) {
@ -711,7 +711,7 @@ describe('Call propagation', function() {
});
var port = server.bind('localhost:0', server_insecure_creds);
Client = surface_client.makeProtobufClientConstructor(test_service);
client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
client = new Client('localhost:' + port, grpc.credentials.createInsecure());
server.start();
});
after(function() {
@ -748,7 +748,7 @@ describe('Call propagation', function() {
var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
proxy.start();
var proxy_client = new Client('localhost:' + proxy_port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
var call = proxy_client.unary({}, function(err, value) {
done();
});
@ -771,7 +771,7 @@ describe('Call propagation', function() {
var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
proxy.start();
var proxy_client = new Client('localhost:' + proxy_port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
var call = proxy_client.clientStream(function(err, value) {
done();
});
@ -792,7 +792,7 @@ describe('Call propagation', function() {
var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
proxy.start();
var proxy_client = new Client('localhost:' + proxy_port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
var call = proxy_client.serverStream({});
call.on('error', function(err) {
done();
@ -813,7 +813,7 @@ describe('Call propagation', function() {
var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
proxy.start();
var proxy_client = new Client('localhost:' + proxy_port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
var call = proxy_client.bidiStream();
call.on('error', function(err) {
done();
@ -842,7 +842,7 @@ describe('Call propagation', function() {
var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
proxy.start();
var proxy_client = new Client('localhost:' + proxy_port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 1);
proxy_client.clientStream(function(err, value) {
@ -865,7 +865,7 @@ describe('Call propagation', function() {
var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
proxy.start();
var proxy_client = new Client('localhost:' + proxy_port,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 1);
var call = proxy_client.bidiStream(null, {deadline: deadline});
@ -888,7 +888,7 @@ describe('Cancelling surface client', function() {
});
var port = server.bind('localhost:0', server_insecure_creds);
var Client = surface_client.makeProtobufClientConstructor(mathService);
client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
client = new Client('localhost:' + port, grpc.credentials.createInsecure());
server.start();
});
after(function() {

Loading…
Cancel
Save