Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions lib/common/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

'use strict';

var fs = require('fs');
var GAPIToken = require('gapitoken');
var req = require('request');
var pkg = require('../../package.json');
Expand Down Expand Up @@ -53,12 +54,9 @@ Token.prototype.isExpired = function() {
* @param {Object} opts Options.
*/
function Connection(opts) {
var credentials = opts.keyFilename && require(opts.keyFilename) || {};
// client email for the service account
this.email = credentials.client_email;
// contains the contents of a pem file
this.privateKey = credentials.private_key;
this.opts = opts || {};

this.credentials = null;
this.scopes = opts.scopes || [];
this.token = null; // existing access token, if exists

Expand All @@ -68,7 +66,6 @@ function Connection(opts) {

/**
* Retrieves a token to authorize the requests.

* @param {Function} callback Callback.
*/
Connection.prototype.connect = function(callback) {
Expand All @@ -92,9 +89,9 @@ Connection.prototype.connect = function(callback) {
* @param {Function} callback Callback function.
*/
Connection.prototype.fetchToken = function(callback) {
if (!this.email || !this.privateKey) {
// We should be on GCE, try to retrieve token from
// the metadata from server.
var that = this;
if (!this.opts.keyFilename) {
// We should be on GCE, try to retrieve token from the metadata server.
req({
method: 'get',
uri: METADATA_TOKEN_URL,
Expand All @@ -112,9 +109,24 @@ Connection.prototype.fetchToken = function(callback) {
});
return;
}
if (!this.credentials) {
// read key file once and cache the contents.
fs.readFile(this.opts.keyFilename, function(err, data) {
if (err) {
return callback(err);
}
that.credentials = JSON.parse(data);
that.fetchServiceAccountToken_(callback);
});
return;
}
this.fetchServiceAccountToken_(callback);
};

Connection.prototype.fetchServiceAccountToken_ = function(callback) {
var gapi = new GAPIToken({
iss: this.email,
key: this.privateKey,
iss: this.credentials.client_email,
key: this.credentials.private_key,
scope: this.scopes.join(' ')
}, function(err) {
if (err) {
Expand Down
130 changes: 78 additions & 52 deletions test/common/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,69 +20,95 @@

var assert = require('assert');
var async = require('async');
var conn = require('../../lib/common/connection.js');
var fs = require('fs');
var path = require('path');

var connection = require('../../lib/common/connection.js');

describe('Connection', function() {
var tokenNeverExpires = new conn.Token('token', new Date(3000, 0, 0));
var tokenExpired = new conn.Token('token', new Date(2011, 0, 0));
it('should fetch a new token if token expires', function(done) {
var c = new conn.Connection({
email: 'x@provider',
privateKey: '/some/path',
scopes: ['scope1', 'scope2']
var conn;

beforeEach(function() {
conn = new connection.Connection({
keyFilename: path.join(__dirname, '../testdata/privateKeyFile.json')
});
c.token = tokenExpired;
c.fetchToken = function() {
done();
};
c.requester = function(opts, callback) {
callback(null);
};
c.req({ uri: 'https://someuri' }, function(){});
});

it('should make other requests wait while connecting', function(done) {
var numTokenFetches = 0;
var c = new conn.Connection({
email: 'x@provider',
privateKey: '/some/path',
scopes: ['scope1', 'scope2']
});
c.fetchToken = function(cb) {
numTokenFetches++;
setTimeout(function() {
cb(null, tokenNeverExpires);
}, 100);
};
c.requester = function(opts, callback) {
it('should use a private key json file', function(done) {
var privateKeyFileJson = require('../testdata/privateKeyFile.json');
conn.fetchServiceAccountToken_ = function(callback) {
callback(null);
};

async.parallel([
function(done) { c.req({ uri: 'https://someuri' }, done); },
function(done) { c.req({ uri: 'https://someuri' }, done); },
function(done) { c.req({ uri: 'https://someuri' }, done); }
], function(err) {
assert.equal(err, null);
assert.equal(numTokenFetches, 1);
assert.equal(c.token, tokenNeverExpires);
conn.fetchToken(function(err) {
assert.ifError(err);
assert.deepEqual(conn.credentials, privateKeyFileJson);
done();
});
});

it('should fetch a new token if token is invalid', function(done) {
var c = new conn.Connection({
email: 'x@provider',
privateKey: '/some/path',
scopes: ['scope1', 'scope2']
describe('Token', function() {
var tokenNeverExpires = new connection.Token('token', new Date(3000, 0, 0));
var tokenExpired = new connection.Token('token', new Date(2011, 0, 0));

it('should fetch a new token if token expires', function(done) {
var c = new connection.Connection({
email: 'x@provider',
privateKey: '/some/path',
scopes: ['scope1', 'scope2']
});
c.token = tokenExpired;
c.fetchToken = function() {
done();
};
c.requester = function(opts, callback) {
callback(null);
};
c.req({ uri: 'https://someuri' }, function(){});
});

it('should make other requests wait while connecting', function(done) {
var numTokenFetches = 0;
var c = new connection.Connection({
email: 'x@provider',
privateKey: '/some/path',
scopes: ['scope1', 'scope2']
});
c.fetchToken = function(cb) {
numTokenFetches++;
setTimeout(function() {
cb(null, tokenNeverExpires);
}, 100);
};
c.requester = function(opts, callback) {
callback(null);
};

async.parallel([
function(done) { c.req({ uri: 'https://someuri' }, done); },
function(done) { c.req({ uri: 'https://someuri' }, done); },
function(done) { c.req({ uri: 'https://someuri' }, done); }
], function(err) {
assert.equal(err, null);
assert.equal(numTokenFetches, 1);
assert.equal(c.token, tokenNeverExpires);
done();
});
});

it('should fetch a new token if token is invalid', function(done) {
var c = new connection.Connection({
email: 'x@provider',
privateKey: '/some/path',
scopes: ['scope1', 'scope2']
});
c.token = new connection.Token();
c.fetchToken = function() {
done();
};
c.requester = function(opts, callback) {
callback(null);
};
c.req({ uri: 'https://someuri' }, function(){});
});
c.token = new conn.Token();
c.fetchToken = function() {
done();
};
c.requester = function(opts, callback) {
callback(null);
};
c.req({ uri: 'https://someuri' }, function(){});
});
});
7 changes: 7 additions & 0 deletions test/testdata/privateKeyFile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"private_key_id": "7",
"private_key": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----\n",
"client_email": "firstpart@secondpart.com",
"client_id": "8",
"type": "service_account"
}