mirror of https://github.com/grpc/grpc-node.git
Merge pull request #219 from kjin/google-creds
grpc-js: add google credentials implementation
This commit is contained in:
commit
cdd1620853
|
|
@ -12,8 +12,8 @@ export class CallCredentialsFilter extends BaseFilter implements Filter {
|
|||
}
|
||||
|
||||
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||
// TODO(murgatroid99): pass real options to generateMetadata
|
||||
let credsMetadata = this.credentials.generateMetadata({});
|
||||
// TODO(kjin): pass real service URL to generateMetadata
|
||||
let credsMetadata = this.credentials.generateMetadata({ service_url: '' });
|
||||
let resultMetadata = await metadata;
|
||||
resultMetadata.merge(await credsMetadata);
|
||||
return resultMetadata;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import {map, reduce} from 'lodash';
|
|||
|
||||
import {Metadata} from './metadata';
|
||||
|
||||
export type CallMetadataOptions = { service_url: string; };
|
||||
|
||||
export type CallMetadataGenerator =
|
||||
(options: {}, cb: (err: Error|null, metadata?: Metadata) => void) =>
|
||||
(options: CallMetadataOptions, cb: (err: Error|null, metadata?: Metadata) => void) =>
|
||||
void;
|
||||
|
||||
/**
|
||||
|
|
@ -15,7 +17,7 @@ export interface CallCredentials {
|
|||
* Asynchronously generates a new Metadata object.
|
||||
* @param options Options used in generating the Metadata object.
|
||||
*/
|
||||
generateMetadata(options: {}): Promise<Metadata>;
|
||||
generateMetadata(options: CallMetadataOptions): Promise<Metadata>;
|
||||
/**
|
||||
* Creates a new CallCredentials object from properties of both this and
|
||||
* another CallCredentials object. This object's metadata generator will be
|
||||
|
|
@ -28,7 +30,7 @@ export interface CallCredentials {
|
|||
class ComposedCallCredentials implements CallCredentials {
|
||||
constructor(private creds: CallCredentials[]) {}
|
||||
|
||||
async generateMetadata(options: {}): Promise<Metadata> {
|
||||
async generateMetadata(options: CallMetadataOptions): Promise<Metadata> {
|
||||
let base: Metadata = new Metadata();
|
||||
let generated: Metadata[] = await Promise.all(
|
||||
map(this.creds, (cred) => cred.generateMetadata(options)));
|
||||
|
|
@ -46,7 +48,7 @@ class ComposedCallCredentials implements CallCredentials {
|
|||
class SingleCallCredentials implements CallCredentials {
|
||||
constructor(private metadataGenerator: CallMetadataGenerator) {}
|
||||
|
||||
generateMetadata(options: {}): Promise<Metadata> {
|
||||
generateMetadata(options: CallMetadataOptions): Promise<Metadata> {
|
||||
return new Promise<Metadata>((resolve, reject) => {
|
||||
this.metadataGenerator(options, (err, metadata) => {
|
||||
if (metadata !== undefined) {
|
||||
|
|
@ -64,7 +66,7 @@ class SingleCallCredentials implements CallCredentials {
|
|||
}
|
||||
|
||||
class EmptyCallCredentials implements CallCredentials {
|
||||
generateMetadata(options: {}): Promise<Metadata> {
|
||||
generateMetadata(options: CallMetadataOptions): Promise<Metadata> {
|
||||
return Promise.resolve(new Metadata());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -271,9 +271,6 @@ export class Http2CallStream extends Duplex implements CallStream {
|
|||
let code: Status;
|
||||
let details = '';
|
||||
switch (errorCode) {
|
||||
case http2.constants.NGHTTP2_NO_ERROR:
|
||||
code = Status.OK;
|
||||
break;
|
||||
case http2.constants.NGHTTP2_REFUSED_STREAM:
|
||||
code = Status.UNAVAILABLE;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ export interface UnaryCallback<ResponseType> {
|
|||
(err: ServiceError|null, value?: ResponseType): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic gRPC client. Primarily useful as a base class for all generated
|
||||
* clients.
|
||||
*/
|
||||
export class Client {
|
||||
private readonly [kChannel]: Channel;
|
||||
constructor(
|
||||
|
|
|
|||
|
|
@ -5,31 +5,73 @@ import { Client } from './client';
|
|||
import { Status} from './constants';
|
||||
import { makeClientConstructor, loadPackageDefinition } from './make-client';
|
||||
import { Metadata } from './metadata';
|
||||
import { IncomingHttpHeaders } from 'http';
|
||||
|
||||
const notImplementedFn = () => { throw new Error('Not implemented'); };
|
||||
export interface OAuth2Client {
|
||||
getRequestMetadata: (url: string, callback: (err: Error|null, headers?: { Authorization: string }) => void) => void;
|
||||
}
|
||||
|
||||
/**** Client Credentials ****/
|
||||
|
||||
// Using assign only copies enumerable properties, which is what we want
|
||||
export const credentials = Object.assign({
|
||||
/**
|
||||
* Create a gRPC credential from a Google credential object.
|
||||
* @param googleCredentials The authentication client to use.
|
||||
* @return The resulting CallCredentials object.
|
||||
*/
|
||||
createFromGoogleCredential: (googleCredentials: OAuth2Client): CallCredentials => {
|
||||
return CallCredentials.createFromMetadataGenerator((options, callback) => {
|
||||
googleCredentials.getRequestMetadata(options.service_url, (err, headers) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
const metadata = new Metadata();
|
||||
metadata.add('authorization', headers!.Authorization);
|
||||
callback(null, metadata);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine a ChannelCredentials with any number of CallCredentials into a
|
||||
* single ChannelCredentials object.
|
||||
* @param channelCredentials The ChannelCredentials object.
|
||||
* @param callCredentials Any number of CallCredentials objects.
|
||||
* @return The resulting ChannelCredentials object.
|
||||
*/
|
||||
combineChannelCredentials: (
|
||||
channelCredentials: ChannelCredentials,
|
||||
...callCredentials: CallCredentials[]): ChannelCredentials => {
|
||||
return callCredentials.reduce((acc, other) => acc.compose(other), channelCredentials);
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine any number of CallCredentials into a single CallCredentials object.
|
||||
* @param first The first CallCredentials object.
|
||||
* @param additional Any number of additional CallCredentials objects.
|
||||
* @return The resulting CallCredentials object.
|
||||
*/
|
||||
combineCallCredentials: (
|
||||
first: CallCredentials,
|
||||
...additional: CallCredentials[]): CallCredentials => {
|
||||
return additional.reduce((acc, other) => acc.compose(other), first);
|
||||
}
|
||||
}, ChannelCredentials, CallCredentials);
|
||||
|
||||
/**** Metadata ****/
|
||||
|
||||
// Metadata
|
||||
export { Metadata };
|
||||
|
||||
// Client credentials
|
||||
|
||||
export const credentials = {
|
||||
createSsl: ChannelCredentials.createSsl,
|
||||
createFromMetadataGenerator: CallCredentials.createFromMetadataGenerator,
|
||||
createFromGoogleCredential: notImplementedFn /*TODO*/,
|
||||
combineChannelCredentials: (first: ChannelCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first),
|
||||
combineCallCredentials: (first: CallCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first),
|
||||
createInsecure: ChannelCredentials.createInsecure
|
||||
};
|
||||
|
||||
// Constants
|
||||
/**** Constants ****/
|
||||
|
||||
export {
|
||||
Status as status
|
||||
// TODO: Other constants as well
|
||||
};
|
||||
|
||||
// Client
|
||||
/**** Client ****/
|
||||
|
||||
export {
|
||||
Client,
|
||||
|
|
@ -37,4 +79,9 @@ export {
|
|||
makeClientConstructor,
|
||||
makeClientConstructor as makeGenericClientConstructor
|
||||
};
|
||||
|
||||
/**
|
||||
* Close a Client object.
|
||||
* @param client The client to close.
|
||||
*/
|
||||
export const closeClient = (client: Client) => client.close();
|
||||
|
|
|
|||
|
|
@ -130,7 +130,12 @@ export type GrpcObject = {
|
|||
[index: string]: GrpcObject | ServiceClientConstructor;
|
||||
};
|
||||
|
||||
export function loadPackageDefinition(packageDef: PackageDefinition) {
|
||||
/**
|
||||
* Load a gRPC package definition as a gRPC object hierarchy.
|
||||
* @param packageDef The package definition object.
|
||||
* @return The resulting gRPC object.
|
||||
*/
|
||||
export function loadPackageDefinition(packageDef: PackageDefinition): GrpcObject {
|
||||
const result: GrpcObject = {};
|
||||
for (const serviceFqn in packageDef) {
|
||||
const service = packageDef[serviceFqn];
|
||||
|
|
|
|||
|
|
@ -5,18 +5,6 @@ import {Metadata} from '../src/metadata';
|
|||
|
||||
// Metadata generators
|
||||
|
||||
function makeGenerator(props: Array<string>): CallMetadataGenerator {
|
||||
return (options: {[propName: string]: string}, cb) => {
|
||||
const metadata: Metadata = new Metadata();
|
||||
props.forEach((prop) => {
|
||||
if (options[prop]) {
|
||||
metadata.add(prop, options[prop]);
|
||||
}
|
||||
});
|
||||
cb(null, metadata);
|
||||
};
|
||||
}
|
||||
|
||||
function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator {
|
||||
return (options, cb) => {
|
||||
const metadata = new Metadata();
|
||||
|
|
@ -25,7 +13,11 @@ function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator {
|
|||
};
|
||||
}
|
||||
|
||||
const generateFromName: CallMetadataGenerator = makeGenerator(['name']);
|
||||
const generateFromServiceURL: CallMetadataGenerator = (options, cb) => {
|
||||
const metadata: Metadata = new Metadata();
|
||||
metadata.add('service_url', options.service_url);
|
||||
cb(null, metadata);
|
||||
};
|
||||
const generateWithError: CallMetadataGenerator = (options, cb) =>
|
||||
cb(new Error());
|
||||
|
||||
|
|
@ -35,16 +27,16 @@ describe('CallCredentials', () => {
|
|||
describe('createFromMetadataGenerator', () => {
|
||||
it('should accept a metadata generator', () => {
|
||||
assert.doesNotThrow(
|
||||
() => CallCredentials.createFromMetadataGenerator(generateFromName));
|
||||
() => CallCredentials.createFromMetadataGenerator(generateFromServiceURL));
|
||||
});
|
||||
});
|
||||
|
||||
describe('compose', () => {
|
||||
it('should accept a CallCredentials object and return a new object', () => {
|
||||
const callCredentials1 =
|
||||
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||
CallCredentials.createFromMetadataGenerator(generateFromServiceURL);
|
||||
const callCredentials2 =
|
||||
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||
CallCredentials.createFromMetadataGenerator(generateFromServiceURL);
|
||||
const combinedCredentials = callCredentials1.compose(callCredentials2);
|
||||
assert.notEqual(combinedCredentials, callCredentials1);
|
||||
assert.notEqual(combinedCredentials, callCredentials2);
|
||||
|
|
@ -52,9 +44,9 @@ describe('CallCredentials', () => {
|
|||
|
||||
it('should be chainable', () => {
|
||||
const callCredentials1 =
|
||||
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||
CallCredentials.createFromMetadataGenerator(generateFromServiceURL);
|
||||
const callCredentials2 =
|
||||
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||
CallCredentials.createFromMetadataGenerator(generateFromServiceURL);
|
||||
assert.doesNotThrow(() => {
|
||||
callCredentials1.compose(callCredentials2)
|
||||
.compose(callCredentials2)
|
||||
|
|
@ -67,14 +59,14 @@ describe('CallCredentials', () => {
|
|||
it('should call the function passed to createFromMetadataGenerator',
|
||||
async () => {
|
||||
const callCredentials =
|
||||
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||
CallCredentials.createFromMetadataGenerator(generateFromServiceURL);
|
||||
let metadata: Metadata;
|
||||
try {
|
||||
metadata = await callCredentials.generateMetadata({name: 'foo'});
|
||||
metadata = await callCredentials.generateMetadata({service_url: 'foo'});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
assert.deepEqual(metadata.get('name'), ['foo']);
|
||||
assert.deepEqual(metadata.get('service_url'), ['foo']);
|
||||
});
|
||||
|
||||
it('should emit an error if the associated metadataGenerator does',
|
||||
|
|
@ -83,7 +75,7 @@ describe('CallCredentials', () => {
|
|||
CallCredentials.createFromMetadataGenerator(generateWithError);
|
||||
let metadata: Metadata|null = null;
|
||||
try {
|
||||
metadata = await callCredentials.generateMetadata({});
|
||||
metadata = await callCredentials.generateMetadata({service_url: ''});
|
||||
} catch (err) {
|
||||
assert.ok(err instanceof Error);
|
||||
}
|
||||
|
|
@ -115,13 +107,12 @@ describe('CallCredentials', () => {
|
|||
expected: ['150', '200', '50', '100']
|
||||
}
|
||||
];
|
||||
const options = {};
|
||||
// Try each test case and make sure the msElapsed field is as expected
|
||||
await Promise.all(testCases.map(async (testCase) => {
|
||||
const {credentials, expected} = testCase;
|
||||
let metadata: Metadata;
|
||||
try {
|
||||
metadata = await credentials.generateMetadata(options);
|
||||
metadata = await credentials.generateMetadata({service_url: ''});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ describe('CallStream', () => {
|
|||
assert2.afterMustCallsSatisfied(done);
|
||||
});
|
||||
|
||||
it('should end a call with an error if a stream was closed', (done) => {
|
||||
describe('should end a call with an error if a stream was closed', () => {
|
||||
const c = http2.constants;
|
||||
const s = Status;
|
||||
const errorCodeMapping = {
|
||||
|
|
@ -121,21 +121,31 @@ describe('CallStream', () => {
|
|||
[c.NGHTTP2_ENHANCE_YOUR_CALM]: s.RESOURCE_EXHAUSTED,
|
||||
[c.NGHTTP2_INADEQUATE_SECURITY]: s.PERMISSION_DENIED
|
||||
};
|
||||
forOwn(errorCodeMapping, (value: Status | null, key) => {
|
||||
const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory);
|
||||
const http2Stream = new ClientHttp2StreamMock({
|
||||
payload: Buffer.alloc(0),
|
||||
frameLengths: []
|
||||
const keys = Object.keys(errorCodeMapping).map(key => Number(key));
|
||||
keys.forEach((key) => {
|
||||
const value = errorCodeMapping[key];
|
||||
// A null value indicates: behavior isn't specified, so skip this test.
|
||||
let maybeSkip = (fn: typeof it) => value ? fn : fn.skip;
|
||||
maybeSkip(it)(`for error code ${key}`, () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory);
|
||||
const http2Stream = new ClientHttp2StreamMock({
|
||||
payload: Buffer.alloc(0),
|
||||
frameLengths: []
|
||||
});
|
||||
callStream.attachHttp2Stream(http2Stream);
|
||||
callStream.once('status', (status) => {
|
||||
try {
|
||||
assert.strictEqual(status.code, value);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
http2Stream.emit('close', Number(key));
|
||||
});
|
||||
});
|
||||
callStream.attachHttp2Stream(http2Stream);
|
||||
if (value !== null) {
|
||||
callStream.once('status', assert2.mustCall((status) => {
|
||||
assert.strictEqual(status.code, value);
|
||||
}));
|
||||
}
|
||||
http2Stream.emit('streamClosed', Number(key));
|
||||
});
|
||||
assert2.afterMustCallsSatisfied(done);
|
||||
});
|
||||
|
||||
it('should have functioning getters', (done) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue