Comparing version 0.2.36 to 0.2.38
184
lib/index.js
@@ -1,11 +0,11 @@ | ||
var request = require("request"); | ||
var EventEmitter = require("events").EventEmitter; | ||
var oauth = require("./middleware/oauth"); | ||
var urls = require("url"); | ||
var config = require("./internal/config"); | ||
var Q = require("q"); | ||
var _ = require("underscore"); | ||
var colors = require("colors"); | ||
var lt = require("localtunnel").client; | ||
var fs = require("fs"); | ||
var config = require("./internal/config"); | ||
var registration = require("./internal/registration"); | ||
var defLogger = require("./internal/logger"); | ||
var oauth = require("./middleware/oauth"); | ||
var webhookOAuth = require("./middleware/webhook-oauth"); | ||
@@ -22,8 +22,10 @@ function Plugin(app, logger) { | ||
self.name = self.descriptor.get("name"); | ||
_.extend(self, registration); | ||
self.on("remote_plugin_installed", function (key, settings) { | ||
// self.logger.warn("DEBUG: plugin installed:", key, JSON.stringify(settings)); | ||
self.settings.hset(settings.clientKey, settings); | ||
}); | ||
if(self.app.get("env") === "development") { | ||
if (self.app.get("env") === "development") { | ||
self.logger.info("Watching atlassian-plugin.xml for changes."); | ||
@@ -67,5 +69,2 @@ fs.watchFile("atlassian-plugin.xml", function (curr, prev) { | ||
var installed = self.descriptor.webhooks("remote_plugin_installed")[0]; | ||
var installedUrl = installed && basePath + installed.get("url"); | ||
// auto-register routes for each webhook in the descriptor | ||
@@ -78,18 +77,3 @@ self.descriptor.webhooks().forEach(function (webhook) { | ||
// auth middleware | ||
function (req, res, next) { | ||
var path = urls.parse(req.url).pathname; | ||
// self.logger.warn("DEBUG: webhook auth:", JSON.stringify(installed), path, JSON.stringify(req.body)); | ||
if (!installed || path !== installedUrl) { | ||
// self.logger.warn("DEBUG: Authenticating webhook", webhookUrl); | ||
self.authenticate()(req, res, next); | ||
} | ||
else { | ||
// @todo for the "installed" webhook, we need to handshake back with the host at a known or | ||
// discovered url to retrieve its public key and verify the signature | ||
// @see https://bitbucket.org/atlassian/node-ap3/issue/9/in-production-mode-whitelist-only-jiracom | ||
// @see https://bitbucket.org/atlassian/node-ap3/issue/10/verify-host-during-remote_plugin_installed | ||
// self.logger.warn("DEBUG: Allowing webhook", webhookUrl); | ||
next(); | ||
} | ||
}, | ||
webhookOAuth(self, basePath), | ||
// request handler | ||
@@ -113,152 +97,12 @@ function (req, res) { | ||
proto.authenticate = function () { | ||
return oauth(this); | ||
proto.authenticate = function (publicKey) { | ||
return oauth(this, publicKey); | ||
}; | ||
// returns the best available http client given a context request | ||
proto.httpClient = function (req) { | ||
return (req.context && req.context.http) || require("request"); | ||
return req.context && req.context.http; | ||
}; | ||
proto.register = function (isReregistration) { | ||
var self = this; | ||
var hosts = this.config.hosts(); | ||
if (hosts && hosts.length > 0) { | ||
self._registrations = {}; | ||
if(!isReregistration){ | ||
self.logger.info("Registering plugin..."); | ||
process.once("SIGINT", function () { | ||
function exit() { process.exit(1); } | ||
self.deregister().then(exit, exit); | ||
}); | ||
} | ||
return Q.allResolved(hosts.map(_.bind(register, self))).then( | ||
function (promises) { | ||
promises.forEach(function (promise) { | ||
var host = promise.valueOf(); | ||
if (host) { | ||
self.once("remote_plugin_installed", function (key, settings) { | ||
if (stripCredentials(host) === settings.baseUrl) { | ||
self.logger.info("Registered with " + settings.baseUrl + "."); | ||
self._registrations[host] = settings.clientKey; | ||
// @todo unsubscribe this webhook listener | ||
} | ||
}); | ||
} | ||
}); | ||
}, | ||
function () { | ||
self.logger.error.apply(self.logger, arguments); | ||
} | ||
); | ||
} | ||
return Q.resolve(); | ||
}; | ||
function register(host) { | ||
var self = this; | ||
var dfd = Q.defer(); | ||
request.post({ | ||
uri: host + "/rest/remotable-plugins/latest/installer", | ||
form: {url: self.config.localBaseUrl() + "/atlassian-plugin.xml"}, | ||
jar: false | ||
}, function (err, res, body) { | ||
if ((err && err.code !== "ECONNREFUSED") || res && res.statusCode !== 200) { | ||
var args = ["Failed to register with host server:"]; | ||
if (err) args.push(err); | ||
if (body) args.push(body); | ||
self.logger.error.apply(self.logger, args); | ||
dfd.resolve(); | ||
} | ||
else if (res && res.statusCode === 200) { | ||
dfd.resolve(host); | ||
} | ||
else { | ||
dfd.resolve(); | ||
} | ||
}); | ||
return dfd.promise; | ||
} | ||
proto.deregister = function () { | ||
var self = this; | ||
if (self._registrations) { | ||
var hosts = _.keys(self._registrations); | ||
if (hosts && hosts.length > 0) { | ||
console.log(); | ||
self.logger.info("Deregistering plugin..."); | ||
return Q.allResolved(hosts.map(_.bind(deregister, self))).then( | ||
function (promises) { | ||
promises.forEach(function (promise) { | ||
var host = promise.valueOf(); | ||
if (host) { | ||
// @todo is there an uninstall webhook we can listen for for parity with register? | ||
var bareHost = stripCredentials(host); | ||
delete self._registrations[bareHost]; | ||
self.logger.info("Deregistered with host " + bareHost + "."); | ||
} | ||
}); | ||
}, | ||
function () { | ||
self.logger.error(arguments.join(' ')) | ||
} | ||
); | ||
} | ||
} | ||
return Q.resolve(); | ||
}; | ||
function deregister(host) { | ||
var self = this; | ||
var dfd = Q.defer(); | ||
request.del({ | ||
uri: host + "/rest/remotable-plugins/latest/uninstaller/" + this.key, | ||
jar: false | ||
}, function (err, res) { | ||
if ((err && err.code !== "ECONNREFUSED") || res && res.statusCode !== 204) { | ||
var args = ["Failed to deregister with host server"]; | ||
if (err) args.push(":" + err); | ||
self.logger.error.apply(self.logger, args); | ||
dfd.resolve(); | ||
} | ||
else if (res && res.statusCode === 204) { | ||
if (host.key) { | ||
function resolve() { dfd.resolve(host); } | ||
self.settings.hdel(host.key).then(resolve, resolve); | ||
} | ||
else { | ||
dfd.resolve(host); | ||
} | ||
} | ||
else { | ||
dfd.resolve(); | ||
} | ||
}); | ||
return dfd.promise; | ||
} | ||
function Logger() { | ||
var logger = {}; | ||
var ops = {"info": "white", "warn": "yellow", "error": "red"}; | ||
_.keys(ops).forEach(function (op) { | ||
logger[op] = function () { | ||
var args = [].slice.call(arguments); | ||
console[op].apply(console, args.map(function (arg) { | ||
// @todo stringify objects with util.inspect and then apply styles to the resulting string | ||
return _.isObject(arg) ? arg : new String(arg)[ops[op]].bold; | ||
})); | ||
}; | ||
}); | ||
return logger; | ||
} | ||
function stripCredentials(url) { | ||
url = urls.parse(url); | ||
delete url.auth; | ||
return urls.format(url); | ||
} | ||
module.exports = function (app, logger) { | ||
if (!logger) logger = Logger(); | ||
return new Plugin(app, logger); | ||
return new Plugin(app, logger || defLogger); | ||
}; |
@@ -54,2 +54,11 @@ var _ = require("underscore"); | ||
return crypto.createHash("sha1").update(this.privateKey()).digest("base64"); | ||
}, | ||
whitelist: function () { | ||
var list = get(modeValues, "whitelist", "AP3_HOST_WHITELIST"); | ||
if (!list) list = mode === "production" ? "*.jira.com" : "*"; | ||
if (_.isString(list)) list = [list]; | ||
return list.map(function (glob) { | ||
return new RegExp(glob.replace(/\./g, "\\.").replace(/\*/g, "[^.]*")); | ||
}); | ||
} | ||
@@ -56,0 +65,0 @@ |
@@ -12,5 +12,2 @@ // modified from https://bitbucket.org/knecht_andreas/atlassian-oauth-validator | ||
var signatureBase = createSignatureBase(options.method, options.url, normParams); | ||
// if (options.logger) { | ||
// options.logger.warn("DEBUG: oauth.verify:", JSON.stringify(options), normParams, signatureBase); | ||
// } | ||
verifier.update(signatureBase); | ||
@@ -17,0 +14,0 @@ if (verifier.verify(ensurePem("PUBLIC", options.publicKey), options.signature, "base64")) { |
@@ -6,3 +6,3 @@ // modified from https://bitbucket.org/knecht_andreas/atlassian-oauth-validator | ||
module.exports = function (plugin) { | ||
module.exports = function (plugin, optionalPublicKey) { | ||
@@ -120,16 +120,20 @@ var maxTimestampAge = 5 * 60 * 1000; | ||
plugin.settings.hget(clientKey).then( | ||
function (consumer) { | ||
if (!consumer) { | ||
send(401, "OAuth consumer " + clientKey + " not approved to make requests."); | ||
if (optionalPublicKey) { | ||
verify(optionalPublicKey); | ||
} | ||
else { | ||
plugin.settings.hget(clientKey).then( | ||
function (consumer) { | ||
if (!consumer) { | ||
send(401, "OAuth consumer " + clientKey + " not approved to make requests."); | ||
} | ||
else { | ||
verify(consumer.publicKey); | ||
} | ||
}, | ||
function (err) { | ||
send(401, "OAuth request not authenticated due to consumer lookup failure: " + err) | ||
} | ||
else { | ||
// plugin.logger.warn("DEBUG: hget consumer:", clientKey, JSON.stringify(consumer)); | ||
verify(consumer.publicKey); | ||
} | ||
}, | ||
function (err) { | ||
send(401, "OAuth request not authenticated due to consumer lookup failure: " + err) | ||
} | ||
); | ||
); | ||
} | ||
@@ -136,0 +140,0 @@ }; |
{ | ||
"name": "ap3", | ||
"version": "0.2.36", | ||
"version": "0.2.38", | ||
"description": "Atlassian Plugins 3 library for Express", | ||
@@ -5,0 +5,0 @@ "dependencies": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36433
20
1007