From 01fa26d7494b43327bb08efb370149829f447fd9 Mon Sep 17 00:00:00 2001 From: David Banham Date: Wed, 8 Mar 2017 15:45:18 +1100 Subject: [PATCH 1/2] Pass config variables rather than reading file from disc. This allows the calling application to choose how it handles configuration. Particularly useful for applications following the 12 factor methodology. --- .gitignore | 2 +- example_testing_config.json | 23 ++++++++++++++ lib/application.js | 61 ++++++++++++------------------------- lib/logger.js | 3 +- lib/oauth/oauth.js | 3 +- test/accountingtests.js | 54 ++++++++++---------------------- 6 files changed, 61 insertions(+), 85 deletions(-) create mode 100644 example_testing_config.json diff --git a/.gitignore b/.gitignore index 89703139..af228d90 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ .nyc_output/ .DS_Store .vscode -*config.json \ No newline at end of file +testing_config.json diff --git a/example_testing_config.json b/example_testing_config.json new file mode 100644 index 00000000..be9ce195 --- /dev/null +++ b/example_testing_config.json @@ -0,0 +1,23 @@ +{ + "APPTYPE": "PARTNER", + "partner": { + "AuthorizeCallbackUrl": null, + "ConsumerKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "PrivateKeyPath": "/some/path/to/privatekey.pem", + "UserAgent": "Tester (PARTNER) - Application for testing Xero", + "XeroPassword": "XXXXXXXXXX", + "XeroUsername": "foo@example.com" + }, + "private": { + "ConsumerKey": "AAAAAAAAAAAAAAAAAA", + "ConsumerSecret": "BBBBBBBBBBBBBBBBBBBB", + "PrivateKeyPath": "/some/path/to/privatekey.pem", + "UserAgent": "Tester (PRIVATE) - Application for testing Xero" + }, + "public": { + "AuthorizeCallbackUrl": "https://example.com/xerocallback", + "ConsumerKey": "AAAAAAAAAAAAAAAAAA", + "ConsumerSecret": "BBBBBBBBBBBBBBBBBBBB", + "UserAgent": "Tester (PUBLIC) - Application for testing Xero" + } +} diff --git a/lib/application.js b/lib/application.js index 8d157e24..1f629b92 100644 --- a/lib/application.js +++ b/lib/application.js @@ -2,7 +2,6 @@ var _ = require('lodash'), logger = require('./logger'), OAuth = require('./oauth/oauth').OAuth, OAuthEcho = require('./oauth/oauth').OAuthEcho, - fs = require('fs'), extend = require('./misc/extend'), dateformat = require('dateformat'), querystring = require('querystring'), @@ -421,50 +420,28 @@ _.extend(Application.prototype, { } }) -populateOptions = function(configFilePath) { - if (!configFilePath) { - //look for the config file in the user's home directory - var homedir, config; - - homedir = process.env.HOME || process.env.USERPROFILE; - if (homedir) { - configFilePath = homedir + '/.xero/config.json'; - } else { - var err = 'Couldn\'t find config.json in your home dir [' + homedir + '/.xero]. Exiting...'; - console.error(err); - throw err; - } - } - +populateOptions = function(config) { var options = {}; - try { - logger.debug('configFilePath: ' + configFilePath); - - config = require(configFilePath); - options["consumerKey"] = config.ConsumerKey; - options["consumerSecret"] = config.ConsumerSecret; - options["privateKeyPath"] = config.PrivateKeyPath; - options["authorizeCallbackUrl"] = config.AuthorizeCallbackUrl; - options["userAgent"] = config.UserAgent || "Xero - Node.js SDK"; - options["runscopeBucketId"] = config.RunscopeBucketId; - } catch (e) { - var err = 'Couldn\'t read config.json from [' + configFilePath + ']. Exiting...'; - console.error(err); - } + options["consumerKey"] = config.ConsumerKey; + options["consumerSecret"] = config.ConsumerSecret; + options["privateKey"] = config.PrivateKey; + options["authorizeCallbackUrl"] = config.AuthorizeCallbackUrl; + options["userAgent"] = config.UserAgent || "Xero - Node.js SDK"; + options["runscopeBucketId"] = config.RunscopeBucketId; return options; } var PrivateApplication = Application.extend({ - constructor: function(configFilePath, options) { + constructor: function(config) { logger.debug('PrivateApplication::constructor'); - var options = _.merge(populateOptions(configFilePath), options); - Application.call(this, _.extend({}, options, { type: 'private' })); + config = populateOptions(config); + Application.call(this, _.extend({}, config, { type: 'private' })); }, init: function() { Application.prototype.init.apply(this, arguments); - var rsaPrivateKey = fs.readFileSync(this.options.privateKeyPath, "utf8"); + var rsaPrivateKey = this.options.privateKey; this.oa = new OAuth( null, null, @@ -533,10 +510,10 @@ var RequireAuthorizationApplication = Application.extend({ var PublicApplication = RequireAuthorizationApplication.extend({ - constructor: function(configFilePath, args) { + constructor: function(config) { logger.debug('PublicApplication::constructor'); - var params = _.merge(populateOptions(configFilePath), args); - RequireAuthorizationApplication.call(this, _.extend({}, params, { type: 'public' })); + config = populateOptions(config); + RequireAuthorizationApplication.call(this, _.extend({}, config, { type: 'public' })); }, init: function() { RequireAuthorizationApplication.prototype.init.apply(this, arguments); @@ -554,15 +531,15 @@ var PublicApplication = RequireAuthorizationApplication.extend({ }); var PartnerApplication = RequireAuthorizationApplication.extend({ - constructor: function(configFilePath, options) { + constructor: function(config) { logger.debug('PartnerApplication::constructor'); - var options = _.merge(populateOptions(configFilePath), options); - RequireAuthorizationApplication.call(this, _.extend({}, options, { type: 'partner' })); + config = populateOptions(config); + RequireAuthorizationApplication.call(this, _.extend({}, config, { type: 'partner' })); }, init: function() { RequireAuthorizationApplication.prototype.init.apply(this, arguments); - var rsaPrivateKey = fs.readFileSync(this.options.privateKeyPath, "utf8"); + var rsaPrivateKey = this.options.privateKey; this.oa = new OAuth( this.options.baseUrl + this.options.requestTokenUrl, this.options.baseUrl + this.options.accessTokenUrl, @@ -574,7 +551,7 @@ var PartnerApplication = RequireAuthorizationApplication.extend({ null, { 'User-Agent': this.options.userAgent } ); //use SSL certificate - var keyCert = fs.readFileSync(this.options.privateKeyPath); + var keyCert = this.options.privateKey; this.oa._createClient = function(port, hostname, method, path, headers, sslEnabled) { var options = { host: hostname, diff --git a/lib/logger.js b/lib/logger.js index 42c7b68f..fd128da2 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,5 +1,4 @@ var log4js = require('log4js'), - fs = require('fs'), path = require('path'), dateFormat = require('dateformat'), util = require('util'), @@ -95,4 +94,4 @@ Logger.prototype = { }; module.exports = new Logger() // default instance; -module.exports.Logger = Logger; \ No newline at end of file +module.exports.Logger = Logger; diff --git a/lib/oauth/oauth.js b/lib/oauth/oauth.js index 81c05314..7d7417d8 100644 --- a/lib/oauth/oauth.js +++ b/lib/oauth/oauth.js @@ -3,7 +3,6 @@ var crypto = require('crypto'), http = require('http'), https = require('https'), URL = require('url'), - fs = require('fs'), querystring = require('querystring'), OAuthUtils = require('./_utils'), _ = require('lodash'); @@ -585,4 +584,4 @@ exports.OAuth.prototype.authHeader = function(url, oauth_token, oauth_token_secr var orderedParameters = this._prepareParameters(oauth_token, oauth_token_secret, method, url, {}); return this._buildAuthorizationHeaders(orderedParameters); -}; \ No newline at end of file +}; diff --git a/test/accountingtests.js b/test/accountingtests.js index dfb69607..6364af1a 100644 --- a/test/accountingtests.js +++ b/test/accountingtests.js @@ -5,36 +5,32 @@ var chai = require('chai'), xero = require('..'), util = require('util'), Browser = require('zombie'), - uuid = require('uuid'); + uuid = require('uuid'), + fs = require('fs'), + metaConfig = require('../testing_config.json'); process.on('uncaughtException', function(err) { console.log('uncaught', err) }) var currentApp; -var organisationCountry = ""; +var organisationCountry = ''; -var APPTYPE = "PRIVATE"; -var privateConfigFile = "../private_app_config.json"; -var publicConfigFile = "../public_app_config.json"; -var partnerConfigFile = "../partner_app_config.json"; -var configFile = ""; +var APPTYPE = metaConfig.APPTYPE; +var config = metaConfig[APPTYPE.toLowerCase()]; -before('init instance and set options', function(done) { - //This constructor looks in ~/.xero/config.json for settings +if (config.PrivateKeyPath && !config.PrivateKey) config.PrivateKey = fs.readFileSync(config.PrivateKeyPath); +before('init instance and set options', function(done) { switch (APPTYPE) { case "PRIVATE": - configFile = privateConfigFile; - currentApp = new xero.PrivateApplication(configFile); + currentApp = new xero.PrivateApplication(config); break; case "PUBLIC": - configFile = publicConfigFile; - currentApp = new xero.PublicApplication(publicConfigFile, { runscopeBucketId: "ei635hnc0fem" }); + currentApp = new xero.PublicApplication(config); break; case "PARTNER": - configFile = partnerConfigFile; - currentApp = new xero.PartnerApplication(partnerConfigFile, { authorizedCallbackUrl: "" }); + currentApp = new xero.PartnerApplication(config); break; default: throw "No App Type Set!!" @@ -100,28 +96,10 @@ describe('get access for public or partner application', function() { }); describe('submits form', function() { - var options = {}; - - before(function(done) { - - if (APPTYPE === "PRIVATE") { - this.skip(); - } - - try { - console.log('configFile: ' + configFile); - - var config = require(configFile); - options["XeroUsername"] = config.XeroUsername; - options["XeroPassword"] = config.XeroPassword; - done(); - } catch (e) { - var err = 'Couldn\'t read config.json from [' + configFile + ']. Exiting...'; - console.log(err); - throw e; - } - - }); + var options = { + XeroUsername: config.XeroUsername, + XeroPassword: config.XeroPassword + }; it('should login', function(done) { browser @@ -1450,4 +1428,4 @@ function wrapError(err) { return err; else if (err.statusCode) return new Error(err.statusCode + ': ' + err.exception.Message); -} \ No newline at end of file +} From 35554a0201a0408d12a8178d7a547a91c287c9ff Mon Sep 17 00:00:00 2001 From: David Banham Date: Wed, 8 Mar 2017 15:58:43 +1100 Subject: [PATCH 2/2] Just use camelCase rather than PascalCase in passed config --- example_testing_config.json | 28 ++++++++++++++-------------- lib/application.js | 16 ---------------- test/accountingtests.js | 6 +++--- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/example_testing_config.json b/example_testing_config.json index be9ce195..2c0e81b3 100644 --- a/example_testing_config.json +++ b/example_testing_config.json @@ -1,23 +1,23 @@ { "APPTYPE": "PARTNER", "partner": { - "AuthorizeCallbackUrl": null, - "ConsumerKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - "PrivateKeyPath": "/some/path/to/privatekey.pem", - "UserAgent": "Tester (PARTNER) - Application for testing Xero", - "XeroPassword": "XXXXXXXXXX", - "XeroUsername": "foo@example.com" + "authorizeCallbackUrl": null, + "consumerKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "privateKeyPath": "/some/path/to/privatekey.pem", + "userAgent": "Tester (PARTNER) - Application for testing Xero", + "xeroPassword": "XXXXXXXXXX", + "xeroUsername": "foo@example.com" }, "private": { - "ConsumerKey": "AAAAAAAAAAAAAAAAAA", - "ConsumerSecret": "BBBBBBBBBBBBBBBBBBBB", - "PrivateKeyPath": "/some/path/to/privatekey.pem", - "UserAgent": "Tester (PRIVATE) - Application for testing Xero" + "consumerKey": "AAAAAAAAAAAAAAAAAA", + "consumerSecret": "BBBBBBBBBBBBBBBBBBBB", + "privateKeyPath": "/some/path/to/privatekey.pem", + "userAgent": "Tester (PRIVATE) - Application for testing Xero" }, "public": { - "AuthorizeCallbackUrl": "https://example.com/xerocallback", - "ConsumerKey": "AAAAAAAAAAAAAAAAAA", - "ConsumerSecret": "BBBBBBBBBBBBBBBBBBBB", - "UserAgent": "Tester (PUBLIC) - Application for testing Xero" + "authorizeCallbackUrl": "https://example.com/xerocallback", + "consumerKey": "AAAAAAAAAAAAAAAAAA", + "consumerSecret": "BBBBBBBBBBBBBBBBBBBB", + "userAgent": "Tester (PUBLIC) - Application for testing Xero" } } diff --git a/lib/application.js b/lib/application.js index 1f629b92..4d85dd72 100644 --- a/lib/application.js +++ b/lib/application.js @@ -420,23 +420,9 @@ _.extend(Application.prototype, { } }) -populateOptions = function(config) { - var options = {}; - - options["consumerKey"] = config.ConsumerKey; - options["consumerSecret"] = config.ConsumerSecret; - options["privateKey"] = config.PrivateKey; - options["authorizeCallbackUrl"] = config.AuthorizeCallbackUrl; - options["userAgent"] = config.UserAgent || "Xero - Node.js SDK"; - options["runscopeBucketId"] = config.RunscopeBucketId; - - return options; -} - var PrivateApplication = Application.extend({ constructor: function(config) { logger.debug('PrivateApplication::constructor'); - config = populateOptions(config); Application.call(this, _.extend({}, config, { type: 'private' })); }, init: function() { @@ -512,7 +498,6 @@ var RequireAuthorizationApplication = Application.extend({ var PublicApplication = RequireAuthorizationApplication.extend({ constructor: function(config) { logger.debug('PublicApplication::constructor'); - config = populateOptions(config); RequireAuthorizationApplication.call(this, _.extend({}, config, { type: 'public' })); }, init: function() { @@ -533,7 +518,6 @@ var PublicApplication = RequireAuthorizationApplication.extend({ var PartnerApplication = RequireAuthorizationApplication.extend({ constructor: function(config) { logger.debug('PartnerApplication::constructor'); - config = populateOptions(config); RequireAuthorizationApplication.call(this, _.extend({}, config, { type: 'partner' })); }, diff --git a/test/accountingtests.js b/test/accountingtests.js index 6364af1a..d78d253b 100644 --- a/test/accountingtests.js +++ b/test/accountingtests.js @@ -19,7 +19,7 @@ var organisationCountry = ''; var APPTYPE = metaConfig.APPTYPE; var config = metaConfig[APPTYPE.toLowerCase()]; -if (config.PrivateKeyPath && !config.PrivateKey) config.PrivateKey = fs.readFileSync(config.PrivateKeyPath); +if (config.privateKeyPath && !config.privateKey) config.privateKey = fs.readFileSync(config.privateKeyPath); before('init instance and set options', function(done) { switch (APPTYPE) { @@ -97,8 +97,8 @@ describe('get access for public or partner application', function() { describe('submits form', function() { var options = { - XeroUsername: config.XeroUsername, - XeroPassword: config.XeroPassword + XeroUsername: config.xeroUsername, + XeroPassword: config.xeroPassword }; it('should login', function(done) {