adding auth checks on trigger create and delete
diff --git a/actions/changes.js b/actions/changes.js
index 19bc087..0fa5597 100644
--- a/actions/changes.js
+++ b/actions/changes.js
@@ -36,7 +36,7 @@
if (lifecycleEvent === 'CREATE') {
// handle any invalid parameters here
- for(var prop in msg) {
+ for (var prop in msg) {
if (!(prop in validProperties)) {
var eMsg = 'cloudant trigger feed: invalid property not supported: ' + prop;
console.log(eMsg,'[error:]', whisk.error(eMsg));
@@ -69,62 +69,55 @@
// auth key for trigger
var apiKey = msg.authKey;
- var auth = apiKey.split(':');
var input = {};
- input["accounturl"] = "https://" + host;
- input["host"] = host;
- input["port"] = port;
- input["protocol"] = protocol;
- input["dbname"] = dbname;
- input["user"] = user;
- input["pass"] = pass;
- input["apikey"] = apiKey;
- input["maxTriggers"] = maxTriggers;
- input["callback"] = {};
- input["callback"]["action"] = {};
- input["callback"]["action"]["name"] = trigger;
+ input.accounturl = "https://" + host;
+ input.host = host;
+ input.port = port;
+ input.protocol = protocol;
+ input.dbname = dbname;
+ input.user = user;
+ input.pass = pass;
+ input.apikey = apiKey;
+ input.maxTriggers = maxTriggers;
+ input.callback = {};
+ input.callback.action = {};
+ input.callback.action.name = trigger;
- return cloudantHelper(provider_endpoint, 'put', replaceNameTrigger, auth, input);
+ return cloudantHelper(provider_endpoint, 'put', replaceNameTrigger, input);
} else if (lifecycleEvent === 'DELETE') {
- return cloudantHelper(provider_endpoint, 'delete', replaceNameTrigger);
+
+ var jsonOptions = {};
+ jsonOptions.apikey = msg.authKey;
+
+ return cloudantHelper(provider_endpoint, 'delete', replaceNameTrigger, jsonOptions);
} else {
return whisk.error('operation is neither CREATE or DELETE');
}
}
-function cloudantHelper(endpoint, verb, name, auth, input) {
- var uri = 'http://' + endpoint + '/cloudanttriggers/' + name;
- var options = {
- method : verb,
- uri : uri
- };
-
- if(auth){
- options.auth = {
- user : auth[0],
- pass : auth[1]
- };
- }
-
- if (verb === 'put') {
- options.json = input;
- }
-
+function cloudantHelper(endpoint, verb, name, input) {
+ var url = 'http://' + endpoint + '/cloudanttriggers/' + name;
var promise = new Promise(function(resolve, reject) {
- request(options, function(error, response, body) {
- console.log('cloudant trigger feed: done http request', '[error:]', error);
- if (!error && response.statusCode === 200) {
- console.log(body);
- resolve();
- } else {
- if (response) {
- console.log('response code:', response.statusCode);
- } else {
- console.log('no response');
- }
- reject(error);
- }
- });
+ request({
+ method : verb,
+ url : url,
+ json: input
+ }, function(error, response, body) {
+ console.log('cloudant trigger feed: done http request');
+ if (!error && response.statusCode === 200) {
+ console.log(body);
+ resolve();
+ }
+ else {
+ if (response) {
+ console.log('response code:', response.statusCode);
+ reject(body);
+ } else {
+ console.log('no response');
+ reject(error);
+ }
+ }
+ });
});
return promise;
diff --git a/provider/app.js b/provider/app.js
index 7f135b3..9a25a47 100644
--- a/provider/app.js
+++ b/provider/app.js
@@ -92,10 +92,9 @@
}
}
- ///
var nanoDb = createTriggerDb();
if (nanoDb === null) {
- logger.error(tid, 'init', 'found an error creating database: ', err);
+ logger.error(tid, 'init', 'found an error creating database: ');
} else {
logger.info(tid, 'init', 'trigger storage database details: ', nanoDb);
diff --git a/provider/lib/delete.js b/provider/lib/delete.js
index b1c68eb..a7d89a7 100644
--- a/provider/lib/delete.js
+++ b/provider/lib/delete.js
@@ -1,15 +1,55 @@
+var request = require('request');
+
module.exports = function(tid, logger, utils) {
- // Test Endpoint
- this.endPoint = '/cloudanttriggers/:id';
+ // Test Endpoint
+ this.endPoint = '/cloudanttriggers/:id';
- // Delete Logic
- this.delete = function (req, res) {
+ // Delete Logic
+ this.delete = function (req, res) {
- var method = 'DELETE /cloudanttriggers';
- logger.info(tid, method);
- utils.deleteTriggerFromDB(req.params.id, res);
+ var method = 'DELETE /cloudanttriggers';
+ logger.info(tid, method);
- };
+ var id = req.params.id;
+ var args = typeof req.body === 'object' ? req.body : JSON.parse(req.body);
+
+ //Check that user has access rights to delete a trigger
+ var triggerObj = utils.parseQName(id, ':');
+ var host = 'https://' + utils.routerHost +':'+ 443;
+ var triggerURL = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
+ var auth = args.apikey.split(':');
+
+ request({
+ method: 'get',
+ url: triggerURL,
+ auth: {
+ user: auth[0],
+ pass: auth[1]
+ }
+ }, function(error, response, body) {
+ //delete from database if user is authenticated (200) or if trigger has already been deleted (404)
+ if (!error && (response.statusCode === 200 || response.statusCode === 404)) {
+ utils.deleteTriggerFromDB(id, res);
+ }
+ else {
+ var errorMsg = 'Cloudant data trigger ' + id + ' cannot be deleted.';
+ logger.error(tid, method, errorMsg, error);
+ if (error) {
+ res.status(400).json({
+ message: errorMsg,
+ error: error.message
+ });
+ }
+ else {
+ var info = JSON.parse(body);
+ res.status(response.statusCode).json({
+ message: errorMsg,
+ error: info.error
+ });
+ }
+ }
+ });
+ };
};
diff --git a/provider/lib/update.js b/provider/lib/update.js
index a467d29..28c9907 100644
--- a/provider/lib/update.js
+++ b/provider/lib/update.js
@@ -1,46 +1,81 @@
+var request = require('request');
+
module.exports = function(tid, logger, utils) {
- // Test Endpoint
- this.endPoint = '/cloudanttriggers/:id';
+ // Test Endpoint
+ this.endPoint = '/cloudanttriggers/:id';
- // Update Logic
- this.update = function (req, res) {
+ // Update Logic
+ this.update = function (req, res) {
- var method = 'PUT /cloudanttriggers';
+ var method = 'PUT /cloudanttriggers';
- logger.info(tid, method);
- var args = typeof req.body === 'object' ? req.body : JSON.parse(req.body);
- if(args.maxTriggers > utils.triggersLimit) {
- // TODO: update error code to indicate that content provided is not correct
- logger.warn(tid, method, 'maxTriggers > ' + utils.triggersLimit + ' is not allowed');
- res.status(400).json({
- error: 'maxTriggers > ' + utils.triggersLimit + ' is not allowed'
+ logger.info(tid, method);
+ var args = typeof req.body === 'object' ? req.body : JSON.parse(req.body);
+ if (args.maxTriggers > utils.triggersLimit) {
+ logger.warn(tid, method, 'maxTriggers > ' + utils.triggersLimit + ' is not allowed');
+ res.status(400).json({
+ error: 'maxTriggers > ' + utils.triggersLimit + ' is not allowed'
+ });
+ return;
+ } else if (!args.callback || !args.callback.action || !args.callback.action.name) {
+ logger.warn(tid, method, 'Your callback is unknown for cloudant trigger:', args.callback);
+ res.status(400).json({
+ error: 'Your callback is unknown for cloudant trigger.'
+ });
+ return;
+ }
+ //Check that user has access rights to create a trigger
+ var triggerName = args.callback.action.name;
+ var triggerObj = utils.parseQName(triggerName);
+ var host = 'https://' + utils.routerHost +':'+ 443;
+ var triggerURL = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
+ var auth = args.apikey.split(':');
+
+ logger.info(tid, method, 'Checking if user has access rights to create a trigger');
+ request({
+ method: 'get',
+ url: triggerURL,
+ auth: {
+ user: auth[0],
+ pass: auth[1]
+ }
+ }, function(error, response, body) {
+ if (error || response.statusCode >= 400) {
+ var errorMsg = 'Trigger authentication request failed.';
+ logger.error(tid, method, errorMsg, error);
+ if (error) {
+ res.status(400).json({
+ message: errorMsg,
+ error: error.message
+ });
+ }
+ else {
+ var info = JSON.parse(body);
+ res.status(response.statusCode).json({
+ message: errorMsg,
+ error: info.error
+ });
+ }
+ }
+ else {
+ var id = req.params.id;
+ var trigger = utils.initTrigger(args, id);
+ // 10 is number of retries to create a trigger.
+ utils.createTrigger(trigger, 10)
+ .then(function (newTrigger) {
+ logger.info(tid, method, 'Trigger was added and database is confirmed.', newTrigger);
+ utils.addTriggerToDB(newTrigger, res);
+ }, function (err) {
+ logger.error(tid, method, 'Trigger could not be created.', err);
+ utils.deleteTrigger(id);
+ res.status(400).json({
+ message: 'Trigger could not be created.',
+ error: err
+ });
+ });
+ }
});
- return;
- } else if (!args.callback || !args.callback.action || !args.callback.action.name) {
- // TODO: update error code to indicate that content provided is not correct
- logger.warn(tid, method, 'Your callback is unknown for cloudant trigger:', args.callback);
- res.status(400).json({
- error: 'You callback is unknown for cloudant trigger.'
- });
- return;
- }
- var id = req.params.id;
- var trigger = utils.initTrigger(args, id);
- // 10 is number of retries to create a trigger.
- var promise = utils.createTrigger(trigger, utils.retryCount);
- promise.then(function(newTrigger) {
- logger.info(tid, method, "Trigger was added and database is confirmed.", newTrigger);
- utils.addTriggerToDB(newTrigger, res);
- }, function(err) {
- logger.error(tid, method, "Trigger could not be created.", err);
- utils.deleteTrigger(id);
- res.status(400).json({
- message: "Trigger could not be created.",
- error: err
- });
- });
-
- };
+ };
};
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index 7a25a57..db82187 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -45,116 +45,116 @@
// Add a trigger: listen for changes and dispatch.
this.createTrigger = function(dataTrigger, retryCount) {
- var method = 'createTrigger';
+ var method = 'createTrigger';
- // Cleanup connection when trigger is deleted.
- var sinceToUse = dataTrigger.since ? dataTrigger.since : "now";
- var nanoConnection;
- var dbURL;
- var dbProtocol = 'https'; // unless specified protocol will default to https
- if (dataTrigger.protocol) {
- dbProtocol = dataTrigger.protocol;
- }
+ // Cleanup connection when trigger is deleted.
+ var sinceToUse = dataTrigger.since ? dataTrigger.since : "now";
+ var nanoConnection;
+ var dbURL;
+ var dbProtocol = 'https'; // unless specified protocol will default to https
+ if (dataTrigger.protocol) {
+ dbProtocol = dataTrigger.protocol;
+ }
- // unless specified host will default to accounturl without the https:// in front
- var dbHost;
- if (dataTrigger.host) {
- dbHost = dataTrigger.host;
- } else {
- dbHost = dataTrigger.accounturl;
- dbHost = dbHost.replace('https://','');
- }
+ // unless specified host will default to accounturl without the https:// in front
+ var dbHost;
+ if (dataTrigger.host) {
+ dbHost = dataTrigger.host;
+ } else {
+ dbHost = dataTrigger.accounturl;
+ dbHost = dbHost.replace('https://','');
+ }
- // both couch and cloudant should have their URLs in the username:password@host format
- dbURL = dbProtocol + '://' + dataTrigger.user + ':' + dataTrigger.pass + '@' + dbHost;
+ // both couch and cloudant should have their URLs in the username:password@host format
+ dbURL = dbProtocol + '://' + dataTrigger.user + ':' + dataTrigger.pass + '@' + dbHost;
- // add port if specified
- if (dataTrigger.port) {
- dbURL = dbURL + ':' + dataTrigger.port;
- }
+ // add port if specified
+ if (dataTrigger.port) {
+ dbURL = dbURL + ':' + dataTrigger.port;
+ }
- logger.info(tid, method,'found trigger url: ', dbURL);
- nanoConnection = require('nano')(dbURL);
+ logger.info(tid, method,'found trigger url: ', dbURL);
+ nanoConnection = require('nano')(dbURL);
- try {
+ try {
- var triggeredDB = nanoConnection.use(dataTrigger.dbname);
+ var triggeredDB = nanoConnection.use(dataTrigger.dbname);
- // Listen for changes on this database.
- // always set the include doc setting to false
- var feed = triggeredDB.follow({since: sinceToUse, include_docs: false});
+ // Listen for changes on this database.
+ // always set the include doc setting to false
+ var feed = triggeredDB.follow({since: sinceToUse, include_docs: false});
- dataTrigger.feed = feed;
- that.triggers[dataTrigger.id] = dataTrigger;
+ dataTrigger.feed = feed;
+ that.triggers[dataTrigger.id] = dataTrigger;
- feed.on('change', function (change) {
- var triggerHandle = that.triggers[dataTrigger.id];
+ feed.on('change', function (change) {
+ var triggerHandle = that.triggers[dataTrigger.id];
- logger.info(tid, method, 'Got change from', dataTrigger.dbname, change, triggerHandle);
- logger.info(tid, method, 'Found triggerHandle', triggerHandle);
+ logger.info(tid, method, 'Got change from', dataTrigger.dbname, change, triggerHandle);
+ logger.info(tid, method, 'Found triggerHandle', triggerHandle);
- if (triggerHandle && triggerHandle.retriesLeft > 0) {
+ if (triggerHandle && triggerHandle.retriesLeft > 0) {
- logger.info(tid, method, 'triggers left:', triggerHandle.triggersLeft);
- logger.info(tid, method, 'retries left:', triggerHandle.retriesLeft);
+ logger.info(tid, method, 'triggers left:', triggerHandle.triggersLeft);
+ logger.info(tid, method, 'retries left:', triggerHandle.retriesLeft);
- if (triggerHandle.triggersLeft === -1) {
- logger.info(tid, method, 'found a trigger fire limit set to -1. setting it to fire infinitely many times');
- that.unlimitedTriggerFires = true;
- } else {
- that.unlimitedTriggerFires = false;
- }
+ if (triggerHandle.triggersLeft === -1) {
+ logger.info(tid, method, 'found a trigger fire limit set to -1. setting it to fire infinitely many times');
+ that.unlimitedTriggerFires = true;
+ } else {
+ that.unlimitedTriggerFires = false;
+ }
- if(that.unlimitedTriggerFires || triggerHandle.triggersLeft > 0) {
- try {
- logger.info(tid, method, 'found a valid trigger. lets fire this trigger', triggerHandle);
- that.fireTrigger(dataTrigger.id, change);
- } catch (e) {
- logger.error(tid, method, 'Exception occurred in callback', e);
- }
- }
- }
- });
+ if (that.unlimitedTriggerFires || triggerHandle.triggersLeft > 0) {
+ try {
+ logger.info(tid, method, 'found a valid trigger. lets fire this trigger', triggerHandle);
+ that.fireTrigger(dataTrigger.id, change);
+ } catch (e) {
+ logger.error(tid, method, 'Exception occurred in callback', e);
+ }
+ }
+ }
+ });
- feed.follow();
+ feed.follow();
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve, reject) {
- feed.on('error', function (err) {
- logger.error(tid, method,'Error occurred for trigger', dataTrigger.id, '(db ' + dataTrigger.dbname + '):', err);
- // revive the feed if an error occured for now
- // the user should be in charge of removing the feeds
- logger.info(tid, "attempting to recreate trigger", dataTrigger.id);
- that.deleteTrigger(dataTrigger.id);
- dataTrigger.since = "now";
- if (retryCount > 0) {
- var addTriggerPromise = that.createTrigger(dataTrigger, (retryCount - 1));
- addTriggerPromise.then(function(trigger) {
- logger.error(tid, method, "Retry Count:", (retryCount - 1));
- resolve(trigger);
- }, function(err) {
- reject(err);
- });
- } else {
- logger.error(tid, method, "Trigger's feed produced too many errors. Deleting the trigger", dataTrigger.id, '(db ' + dataTrigger.dbname + ')');
- reject({
- error: err,
- message: "Trigger's feed produced too many errors. Deleting the trigger " + dataTrigger.id
- });
- }
- });
+ feed.on('error', function (err) {
+ logger.error(tid, method,'Error occurred for trigger', dataTrigger.id, '(db ' + dataTrigger.dbname + '):', err);
+ // revive the feed if an error occured for now
+ // the user should be in charge of removing the feeds
+ logger.info(tid, "attempting to recreate trigger", dataTrigger.id);
+ that.deleteTrigger(dataTrigger.id);
+ dataTrigger.since = "now";
+ if (retryCount > 0) {
+ that.createTrigger(dataTrigger, (retryCount - 1))
+ .then(function(trigger) {
+ logger.error(tid, method, "Retry Count:", (retryCount - 1));
+ resolve(trigger);
+ }, function(err) {
+ reject(err);
+ });
+ } else {
+ logger.error(tid, method, "Trigger's feed produced too many errors. Deleting the trigger", dataTrigger.id, '(db ' + dataTrigger.dbname + ')');
+ reject({
+ error: err,
+ message: "Trigger's feed produced too many errors. Deleting the trigger " + dataTrigger.id
+ });
+ }
+ });
- feed.on('confirm', function (dbObj) {
- logger.info(tid, method, 'Added cloudant data trigger', dataTrigger.id, 'listening for changes in database', dataTrigger.dbname);
- resolve(dataTrigger);
- });
+ feed.on('confirm', function (dbObj) {
+ logger.info(tid, method, 'Added cloudant data trigger', dataTrigger.id, 'listening for changes in database', dataTrigger.dbname);
+ resolve(dataTrigger);
+ });
});
- } catch (err) {
- logger.info('caught an exception: ' + err);
- return Promise.reject(err);
- }
+ } catch (err) {
+ logger.info('caught an exception: ' + err);
+ return Promise.reject(err);
+ }
};
@@ -333,8 +333,8 @@
logger.info(tid, method, 'fireTrigger: form =', form);
logger.info(tid, method, 'for trigger', id, 'invoking action', triggerName, 'with db update', JSON.stringify(form));
- var host = 'https://'+routerHost+':'+443;
- var uri = host+'/api/v1/namespaces/' + triggerObj.namespace +'/triggers/'+triggerObj.name;
+ var host = 'https://' + routerHost + ':' + 443;
+ var uri = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
var auth = apikey.split(':');
logger.info(tid, method, uri, auth, form);
@@ -386,9 +386,9 @@
});
};
- this.parseQName = function (qname) {
+ this.parseQName = function (qname, separator) {
var parsed = {};
- var delimiter = '/';
+ var delimiter = separator || '/';
var defaultNamespace = '_';
if (qname && qname.charAt(0) === delimiter) {
var parts = qname.split(delimiter);