From 3962104f14efac69941b0519dad76aa33b863f1e Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Tue, 30 Jul 2013 17:12:09 -0700 Subject: [PATCH 1/5] auth: draft jwt support using gapitoken --- lib/auth/jwtclient.js | 85 +++++++++++++++++++++++++++++++++++++++++++ lib/googleapis.js | 1 + package.json | 3 +- tests/test.jwt.js | 71 ++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 lib/auth/jwtclient.js create mode 100644 tests/test.jwt.js diff --git a/lib/auth/jwtclient.js b/lib/auth/jwtclient.js new file mode 100644 index 00000000000..48b1a958344 --- /dev/null +++ b/lib/auth/jwtclient.js @@ -0,0 +1,85 @@ +/** + * Copyright 2013 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var Auth2Client = require('./oauth2client.js'); +var util = require('util'); +var GAPI = require('gapitoken'); + +/** + * @constructor + * JWT service account credentials. + * + * Retrieve access token using gapitoken. + * + * @param {string=} email service account email address. + * @param {string=} keyFile path to private key file. + * @param {array=} scopes list of requested scopes. + * + */ +function JWT(email, keyFile, scopes) { + JWT.super_.call(this); + this.email = email; + this.keyFile = keyFile; + this.scopes = scopes; + this.GAPI = GAPI; +} + +/** + * Inherit from Auth2Client. + */ +util.inherits(JWT, Auth2Client); + +/** + * Get the initial access token using gapitoken. + * @param {function=} opt_callback Optional callback. + */ +JWT.prototype.authorize = function(opt_callback) { + var that = this; + that.gapi = that.GAPI({ + iss: that.email, + scope: that.scopes.join(' '), + keyFile: that.keyFile + }, function(err) { + if (err) { + opt_callback && opt_callback(err, null); + } else { + that.refreshToken_(null, function(err, result) { + if (!err) { + that.credentials = result; + that.credentials.refresh_token = 'jwt-placeholder'; + } + opt_callback && opt_callback(err, result); + }); + } + }); +}; + +/** + * @private + * Refreshes the access token. + * @param {object=} ignored_ + * @param {function=} opt_callback Optional callback. + */ +JWT.prototype.refreshToken_ = function(ignored_, opt_callback) { + this.gapi.getToken(function(err, token) { + opt_callback && opt_callback(err, token); + }); +}; + +/** + * Export Compute. + */ +module.exports = JWT; diff --git a/lib/googleapis.js b/lib/googleapis.js index 77d2c485ec2..451c0bb15a3 100644 --- a/lib/googleapis.js +++ b/lib/googleapis.js @@ -254,6 +254,7 @@ googleapis.OAuth2Client = require('./auth/oauth2client.js'); */ googleapis.auth = { Compute: require('./auth/computeclient.js'), + JWT: require('./auth/jwtclient.js'), OAuth2Client: googleapis.OAuth2Client }; diff --git a/package.json b/package.json index 5d96ce07cc3..e0926ccf9a0 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ ], "dependencies" : { "request": "2.14.0", - "async": "0.2.6" + "async": "0.2.6", + "gapitoken": "0.0.3" }, "devDependencies" : { "mocha": "1.8.1", diff --git a/tests/test.jwt.js b/tests/test.jwt.js new file mode 100644 index 00000000000..1b53b4ead5e --- /dev/null +++ b/tests/test.jwt.js @@ -0,0 +1,71 @@ +/** + * Copyright 2013 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var assert = require('assert'); + +var googleapis = require('../lib/googleapis.js'); + +describe('JWT auth client', function() { + it('should get an initial access token', function(done) { + var jwt = new googleapis.auth.JWT( + 'foo@serviceaccount.com', + '/path/to/key.pem', + ['http://bar', 'http://foo']); + jwt.GAPI = function(opts, callback) { + assert.equal('foo@serviceaccount.com', opts.iss); + assert.equal('/path/to/key.pem', opts.keyFile); + assert.equal('http://bar http://foo', opts.scope); + setTimeout(function() { + callback(null); + }, 0); + return { + getToken: function(opt_callback) { + opt_callback(null, { + 'access_token': 'initial-access-token', + 'token_type': 'Bearer' + }); + } + } + }; + jwt.authorize(function() { + assert.equal('initial-access-token', jwt.credentials.access_token); + assert.equal('jwt-placeholder', jwt.credentials.refresh_token); + done(); + }); + }); + it('should refresh token when request fails', function(done) { + var jwt = new googleapis.auth.JWT(); + jwt.credentials = { + access_token: 'initial-access-token', + refresh_token: 'jwt-placeholder' + }; + jwt.transporter = { + request: function(opts, opt_callback) { + opt_callback([{code: 401}], null); + } + }; + jwt.refreshToken_ = function(token, callback) { + callback(null, { + 'access_token': 'another-access-token', + 'token_type': 'Bearer' + }); + }; + jwt.request({}, function() { + assert.equal('another-access-token', jwt.credentials.access_token); + done(); + }); + }); +}); From 2ba96be5d8ecd7483f6f2107e9168b2f61cb7bea Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Tue, 30 Jul 2013 17:38:10 -0700 Subject: [PATCH 2/5] auth/jwt: fix gapitoken calling convention --- lib/auth/jwtclient.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/auth/jwtclient.js b/lib/auth/jwtclient.js index 48b1a958344..9e7e2415613 100644 --- a/lib/auth/jwtclient.js +++ b/lib/auth/jwtclient.js @@ -48,7 +48,7 @@ util.inherits(JWT, Auth2Client); */ JWT.prototype.authorize = function(opt_callback) { var that = this; - that.gapi = that.GAPI({ + that.gapi = new that.GAPI({ iss: that.email, scope: that.scopes.join(' '), keyFile: that.keyFile @@ -74,8 +74,13 @@ JWT.prototype.authorize = function(opt_callback) { * @param {function=} opt_callback Optional callback. */ JWT.prototype.refreshToken_ = function(ignored_, opt_callback) { - this.gapi.getToken(function(err, token) { - opt_callback && opt_callback(err, token); + var that = this; + that.gapi.getToken(function(err, token) { + opt_callback && opt_callback(err, { + access_token: token, + token_type: 'Bearer', + expires_in: that.gapi.token_expires + }); }); }; From 14ed5bc72c699d6c959397ea269f5261cc231cfd Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Wed, 31 Jul 2013 02:31:41 -0700 Subject: [PATCH 3/5] auth/jwt: fix tests --- tests/test.jwt.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test.jwt.js b/tests/test.jwt.js index 1b53b4ead5e..f28a6d48275 100644 --- a/tests/test.jwt.js +++ b/tests/test.jwt.js @@ -33,10 +33,7 @@ describe('JWT auth client', function() { }, 0); return { getToken: function(opt_callback) { - opt_callback(null, { - 'access_token': 'initial-access-token', - 'token_type': 'Bearer' - }); + opt_callback(null, 'initial-access-token'); } } }; @@ -47,7 +44,10 @@ describe('JWT auth client', function() { }); }); it('should refresh token when request fails', function(done) { - var jwt = new googleapis.auth.JWT(); + var jwt = new googleapis.auth.JWT( + 'foo@serviceaccount.com', + '/path/to/key.pem', + ['http://bar', 'http://foo']); jwt.credentials = { access_token: 'initial-access-token', refresh_token: 'jwt-placeholder' From 9beb567e835ba5f875c912f311c1f7182314f209 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Wed, 14 Aug 2013 14:15:36 -0700 Subject: [PATCH 4/5] package.json: add missing colon --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7785bd7e73e..340a53bf2e1 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ ], "dependencies" : { "request": "~2.25.0", - "async": "0.2.6" + "async": "0.2.6", "gapitoken": "0.0.3" }, "devDependencies" : { From 8cd32650588414c7e1d14ecad8fb3b70849b46f9 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Wed, 14 Aug 2013 14:39:54 -0700 Subject: [PATCH 5/5] tests: fix jwt tests --- tests/test.jwt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.jwt.js b/tests/test.jwt.js index f28a6d48275..49699c13b3b 100644 --- a/tests/test.jwt.js +++ b/tests/test.jwt.js @@ -54,7 +54,7 @@ describe('JWT auth client', function() { }; jwt.transporter = { request: function(opts, opt_callback) { - opt_callback([{code: 401}], null); + opt_callback(null, null, {statusCode: 401}); } }; jwt.refreshToken_ = function(token, callback) {