From e882b4da725a68cfae85b36abcadebe10611dfa2 Mon Sep 17 00:00:00 2001 From: Ryan Birmingham Date: Thu, 2 Apr 2020 19:02:12 -0400 Subject: [PATCH 1/4] fix slide update handler --- handlers/dataHandlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/dataHandlers.js b/handlers/dataHandlers.js index 3921233..4292f3b 100644 --- a/handlers/dataHandlers.js +++ b/handlers/dataHandlers.js @@ -176,7 +176,7 @@ Slide.update = function(req, res, next) { var newVals = { $set: JSON.parse(req.body), }; - mongoUpdate('slide', query).then((x) => { + mongoUpdate('slide', query, newVals).then((x) => { req.data = x; next(); }).catch((e) => next(e)); From 76c45db7740ac08a5b4c39260a29a197eb79b76a Mon Sep 17 00:00:00 2001 From: Ryan Birmingham Date: Thu, 2 Apr 2020 21:09:05 -0400 Subject: [PATCH 2/4] Stricter Updates and Deletes (#16) * test method on slides * forgot we kept oid compatibility * expand to some other editing routes * don't show connection info * try removing old query * Try deleting query bit by bit * bypass all in zero case too * delete slide uses query * deleting or editing 0 maybe shgould be a 400 * fix style --- caracal.js | 16 +++++++++ handlers/authHandlers.js | 52 +++++++++++++++++++----------- handlers/dataHandlers.js | 3 +- handlers/filterFunction.js | 23 +++++++++++++ test/functional/slide_lifecycle.js | 4 +-- 5 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 handlers/filterFunction.js diff --git a/caracal.js b/caracal.js index 046fb19..b2d65c8 100644 --- a/caracal.js +++ b/caracal.js @@ -60,8 +60,12 @@ app.use('/data/Slide/find', auth.filterHandler('data', 'userFilter', 'filter')); app.use('/data/Slide/post', permissionHandler(['Admin', 'Editor'])); app.use('/data/Slide/post', dataHandlers.Slide.add); app.use('/data/Slide/delete', permissionHandler(['Admin', 'Editor'])); +app.use('/data/Slide/delete', dataHandlers.Slide.find); +app.use('/data/Slide/delete', auth.editHandler('data', 'userFilter', 'filter')); app.use('/data/Slide/delete', dataHandlers.Slide.delete); app.use('/data/Slide/update', permissionHandler(['Admin', 'Editor'])); +app.use('/data/Slide/update', dataHandlers.Slide.find); +app.use('/data/Slide/update', auth.editHandler('data', 'userFilter', 'filter')); app.use('/data/Slide/update', dataHandlers.Slide.update); // mark app.use('/data/Mark/find', dataHandlers.Mark.find); @@ -70,8 +74,12 @@ app.use('/data/Mark/multi', dataHandlers.Mark.multi); app.use('/data/Mark/types', dataHandlers.Mark.types); app.use('/data/Mark/post', permissionHandler(['Admin', 'Editor'])); app.use('/data/Mark/post', dataHandlers.Mark.add); +app.use('/data/Mark/delete', dataHandlers.Mark.find); +app.use('/data/Mark/delete', auth.editHandler('data')); app.use('/data/Mark/delete', permissionHandler(['Admin', 'Editor'])); app.use('/data/Mark/delete', dataHandlers.Mark.delete); +app.use('/data/Mark/update', dataHandlers.Mark.find); +app.use('/data/Mark/update', auth.editHandler('data')); app.use('/data/Mark/update', permissionHandler(['Admin', 'Editor'])); app.use('/data/Mark/update', dataHandlers.Mark.update); // template @@ -87,16 +95,24 @@ app.use('/data/Heatmap/find', dataHandlers.Heatmap.find); app.use('/data/Heatmap/types', dataHandlers.Heatmap.types); app.use('/data/Heatmap/post', permissionHandler(['Admin', 'Editor'])); app.use('/data/Heatmap/post', dataHandlers.Heatmap.add); +app.use('/data/Heatmap/delete', dataHandlers.Heatmap.find); +app.use('/data/Heatmap/delete', auth.editHandler('data')); app.use('/data/Heatmap/delete', permissionHandler(['Admin', 'Editor'])); app.use('/data/Heatmap/delete', dataHandlers.Heatmap.delete); +app.use('/data/Heatmap/update', dataHandlers.Heatmap.find); +app.use('/data/Heatmap/update', auth.editHandler('data')); app.use('/data/Heatmap/update', permissionHandler(['Admin', 'Editor'])); app.use('/data/Heatmap/update', dataHandlers.Heatmap.update); // heatmapEdit app.use('/data/HeatmapEdit/find', dataHandlers.HeatmapEdit.find); app.use('/data/HeatmapEdit/post', permissionHandler(['Admin', 'Editor'])); app.use('/data/HeatmapEdit/post', dataHandlers.HeatmapEdit.add); +app.use('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.find); +app.use('/data/HeatmapEdit/delete', auth.editHandler('data')); app.use('/data/HeatmapEdit/delete', permissionHandler(['Admin', 'Editor'])); app.use('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.delete); +app.use('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.find); +app.use('/data/HeatmapEdit/update', auth.editHandler('data')); app.use('/data/HeatmapEdit/update', permissionHandler(['Admin', 'Editor'])); app.use('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.update); // log diff --git a/handlers/authHandlers.js b/handlers/authHandlers.js index 57423ea..39dc5e7 100644 --- a/handlers/authHandlers.js +++ b/handlers/authHandlers.js @@ -3,6 +3,7 @@ var jwt = require('jsonwebtoken'); const jwksClient = require('jwks-rsa'); var atob = require('atob'); var fs = require('fs'); +var filterFunction = require('./filterFunction.js'); var JWK_URL = process.env.JWK_URL; var DISABLE_SEC = process.env.DISABLE_SEC || false; @@ -193,29 +194,41 @@ function loginHandler(checkKey) { // use filter handler AFTER data handler function filterHandler(dataField, filterField, attrField) { return function(req, res, next) { - // do nothing if sec disabled, or if filter contains "**" - // all docs with no set attrField are passed too - var filter = req[filterField]; - // make filter an array - if (!Array.isArray(filter)) { - filter = [filter]; + req[dataField] = filterFunction(req[filterField], req[dataField], attrField, '**'); + next(); + }; +} + +// use edit handler AFTER a find route to populate data, but BEFORE the edit itself +function editHandler(dataField, filterField, attrField) { + return function(req, res, next) { + if (filterField && attrField) { + req[dataField] = filterFunction(req[filterField], req[dataField], attrField, '**'); } - if (filter.indexOf('**') == -1) { - // filter data in dataField if attrField in filterField - var data = req[dataField]; - // is data an array? - if (Array.isArray(data)) { - // remove ones where does not match - req[dataField] = data.filter((x) => (!x[attrField] || filter.indexOf(x[attrField]) >= 0) ); - } else { - if (!data[attrField] || filter.indexOf(data[attrField]) >= 0) { - req[dataField] = data; - } else { - req[dataField] = {}; + if (!Array.isArray(req[dataField])) { + req[dataField] = [req[dataField]]; + } + // edit routes should operate on one object + if (req[dataField].length == 1) { + // DESTROY query + for (let n in req.query) { + if (req.query.hasOwnProperty(n)) { + delete req.query[n]; } } + req.query = {_id: req[dataField][0]._id['$oid']}; + next(); + } else if (req[dataField].length == 0) { + let errorMessage = {}; + errorMessage.error = 'Nothing applicable to change.'; + errorMessage.statusCode = 400; + next(errorMessage); + } else { + let errorMessage = {}; + errorMessage.error = 'At most one document may be changed at once.'; + errorMessage.statusCode = 400; + next(errorMessage); } - next(); }; } @@ -224,6 +237,7 @@ auth.jwkTokenTrade = jwkTokenTrade; auth.tokenTrade = tokenTrade; auth.filterHandler = filterHandler; auth.loginHandler = loginHandler; +auth.editHandler = editHandler; auth.CLIENT = CLIENT; auth.PRIKEY = PRIKEY; auth.PUBKEY = PUBKEY; diff --git a/handlers/dataHandlers.js b/handlers/dataHandlers.js index 4292f3b..38e06be 100644 --- a/handlers/dataHandlers.js +++ b/handlers/dataHandlers.js @@ -103,6 +103,7 @@ function mongoDelete(collection, query) { if (err) { rej(err); } + delete result.connection; res(result); db.close(); }); @@ -130,6 +131,7 @@ function mongoUpdate(collection, query, newVals) { if (err) { rej(err); } + delete result.connection; res(result); db.close(); }); @@ -142,7 +144,6 @@ function mongoUpdate(collection, query, newVals) { }); } - var Slide = {}; Slide.find = function(req, res, next) { // slide, specimen, study, location diff --git a/handlers/filterFunction.js b/handlers/filterFunction.js new file mode 100644 index 0000000..c33da96 --- /dev/null +++ b/handlers/filterFunction.js @@ -0,0 +1,23 @@ +function filterFunction(filter, data, attr, wildcard) { + // make filter an array + if (!Array.isArray(filter)) { + filter = [filter]; + } + if (filter.indexOf(wildcard) == -1) { + // is data an array? + if (Array.isArray(data)) { + // remove ones where does not match + data = data.filter((x) => (!x[attr] || filter.indexOf(x[attr]) >= 0) ); + } else { + if (!data[attr] || filter.indexOf(data[attr]) >= 0) { + data = data; + } else { + data = {}; + } + } + } + return data; +} + + +module.exports = filterFunction; diff --git a/test/functional/slide_lifecycle.js b/test/functional/slide_lifecycle.js index 90c936b..e269b43 100644 --- a/test/functional/slide_lifecycle.js +++ b/test/functional/slide_lifecycle.js @@ -48,11 +48,9 @@ describe('Slide Lifecycle Step 2', function() { describe('Slide Lifecycle Step 3', function() { it('Deletes a Slide', function(done) { this.timeout(5000); - var slideData = {'name': 'TEST', 'specimen': '', 'study': '', 'location': '/images/sample.svs', 'mpp': 0.499}; chai.request(server) - .post(deleteurl) + .delete(deleteurl + '?name=TEST') .set('Content-Type', 'application/json; charset=utf-8') - .send(slideData) .end(function(err, res) { (res).should.have.status(200); (res.body).should.be.a('object'); From 749c0f729e0531c077c85313c8cbbcd6fabd86d2 Mon Sep 17 00:00:00 2001 From: Ryan Birmingham Date: Fri, 3 Apr 2020 13:44:04 -0400 Subject: [PATCH 3/4] picky verbs --- caracal.js | 155 +++++++++++++++++++++++++++-------------------------- 1 file changed, 79 insertions(+), 76 deletions(-) diff --git a/caracal.js b/caracal.js index b2d65c8..75d4aa2 100644 --- a/caracal.js +++ b/caracal.js @@ -55,93 +55,96 @@ app.use('/loader/', loaderHandler); // data, mongo app.use('/data', auth.loginHandler(auth.PUBKEY)); // slide -app.use('/data/Slide/find', dataHandlers.Slide.find); -app.use('/data/Slide/find', auth.filterHandler('data', 'userFilter', 'filter')); -app.use('/data/Slide/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Slide/post', dataHandlers.Slide.add); -app.use('/data/Slide/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Slide/delete', dataHandlers.Slide.find); -app.use('/data/Slide/delete', auth.editHandler('data', 'userFilter', 'filter')); -app.use('/data/Slide/delete', dataHandlers.Slide.delete); -app.use('/data/Slide/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Slide/update', dataHandlers.Slide.find); -app.use('/data/Slide/update', auth.editHandler('data', 'userFilter', 'filter')); -app.use('/data/Slide/update', dataHandlers.Slide.update); +app.get('/data/Slide/find', dataHandlers.Slide.find); +app.get('/data/Slide/find', auth.filterHandler('data', 'userFilter', 'filter')); +app.post('/data/Slide/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Slide/post', dataHandlers.Slide.add); +app.delete('/data/Slide/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Slide/delete', dataHandlers.Slide.find); +app.delete('/data/Slide/delete', auth.editHandler('data', 'userFilter', 'filter')); +app.delete('/data/Slide/delete', dataHandlers.Slide.delete); +app.post('/data/Slide/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Slide/update', dataHandlers.Slide.find); +app.post('/data/Slide/update', auth.editHandler('data', 'userFilter', 'filter')); +app.post('/data/Slide/update', dataHandlers.Slide.update); // mark -app.use('/data/Mark/find', dataHandlers.Mark.find); -app.use('/data/Mark/spatial', dataHandlers.Mark.spatial); -app.use('/data/Mark/multi', dataHandlers.Mark.multi); -app.use('/data/Mark/types', dataHandlers.Mark.types); -app.use('/data/Mark/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Mark/post', dataHandlers.Mark.add); -app.use('/data/Mark/delete', dataHandlers.Mark.find); -app.use('/data/Mark/delete', auth.editHandler('data')); -app.use('/data/Mark/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Mark/delete', dataHandlers.Mark.delete); -app.use('/data/Mark/update', dataHandlers.Mark.find); -app.use('/data/Mark/update', auth.editHandler('data')); -app.use('/data/Mark/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Mark/update', dataHandlers.Mark.update); +app.get('/data/Mark/find', dataHandlers.Mark.find); +app.get('/data/Mark/spatial', dataHandlers.Mark.spatial); +app.get('/data/Mark/multi', dataHandlers.Mark.multi); +app.get('/data/Mark/types', dataHandlers.Mark.types); +app.post('/data/Mark/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Mark/post', dataHandlers.Mark.add); +app.delete('/data/Mark/delete', dataHandlers.Mark.find); +app.delete('/data/Mark/delete', auth.editHandler('data')); +app.delete('/data/Mark/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Mark/delete', dataHandlers.Mark.delete); +app.post('/data/Mark/update', dataHandlers.Mark.find); +app.post('/data/Mark/update', auth.editHandler('data')); +app.post('/data/Mark/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Mark/update', dataHandlers.Mark.update); // template -app.use('/data/Template/find', dataHandlers.Template.find); -app.use('/data/Template/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Template/post', dataHandlers.Template.add); -app.use('/data/Template/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Template/delete', dataHandlers.Template.delete); -app.use('/data/Template/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Template/update', dataHandlers.Template.update); +app.get('/data/Template/find', dataHandlers.Template.find); +app.post('/data/Template/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Template/post', dataHandlers.Template.add); +app.delete('/data/Template/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Template/delete', dataHandlers.Template.delete); +app.post('/data/Template/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Template/update', dataHandlers.Template.update); // heatmap -app.use('/data/Heatmap/find', dataHandlers.Heatmap.find); -app.use('/data/Heatmap/types', dataHandlers.Heatmap.types); -app.use('/data/Heatmap/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Heatmap/post', dataHandlers.Heatmap.add); -app.use('/data/Heatmap/delete', dataHandlers.Heatmap.find); -app.use('/data/Heatmap/delete', auth.editHandler('data')); -app.use('/data/Heatmap/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Heatmap/delete', dataHandlers.Heatmap.delete); -app.use('/data/Heatmap/update', dataHandlers.Heatmap.find); -app.use('/data/Heatmap/update', auth.editHandler('data')); -app.use('/data/Heatmap/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Heatmap/update', dataHandlers.Heatmap.update); +app.get('/data/Heatmap/find', dataHandlers.Heatmap.find); +app.get('/data/Heatmap/types', dataHandlers.Heatmap.types); +app.post('/data/Heatmap/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Heatmap/post', dataHandlers.Heatmap.add); +app.delete('/data/Heatmap/delete', dataHandlers.Heatmap.find); +app.delete('/data/Heatmap/delete', auth.editHandler('data')); +app.delete('/data/Heatmap/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Heatmap/delete', dataHandlers.Heatmap.delete); +app.post('/data/Heatmap/update', dataHandlers.Heatmap.find); +app.post('/data/Heatmap/update', auth.editHandler('data')); +app.post('/data/Heatmap/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Heatmap/update', dataHandlers.Heatmap.update); // heatmapEdit -app.use('/data/HeatmapEdit/find', dataHandlers.HeatmapEdit.find); -app.use('/data/HeatmapEdit/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/HeatmapEdit/post', dataHandlers.HeatmapEdit.add); -app.use('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.find); -app.use('/data/HeatmapEdit/delete', auth.editHandler('data')); -app.use('/data/HeatmapEdit/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.delete); -app.use('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.find); -app.use('/data/HeatmapEdit/update', auth.editHandler('data')); -app.use('/data/HeatmapEdit/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.update); +app.get('/data/HeatmapEdit/find', dataHandlers.HeatmapEdit.find); +app.post('/data/HeatmapEdit/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/HeatmapEdit/post', dataHandlers.HeatmapEdit.add); +app.delete('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.find); +app.delete('/data/HeatmapEdit/delete', auth.editHandler('data')); +app.delete('/data/HeatmapEdit/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.delete); +app.post('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.find); +app.post('/data/HeatmapEdit/update', auth.editHandler('data')); +app.post('/data/HeatmapEdit/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.update); // log -app.use('/data/Log/find', dataHandlers.Log.find); +app.get('/data/Log/find', dataHandlers.Log.find); // anyone can add a log -app.use('/data/Log/post', dataHandlers.Log.add); -app.use('/data/Log/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Log/delete', dataHandlers.Log.delete); -app.use('/data/Log/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Log/update', dataHandlers.Log.update); +app.post('/data/Log/post', dataHandlers.Log.add); +app.delete('/data/Log/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Log/delete', dataHandlers.Log.delete); +app.post('/data/Log/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Log/update', dataHandlers.Log.update); // config -app.use('/data/Configuration/find', dataHandlers.Config.find); -app.use('/data/Configuration/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Configuration/post', dataHandlers.Config.add); -app.use('/data/Configuration/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Configuration/delete', dataHandlers.Config.delete); -app.use('/data/Configuration/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Configuration/update', dataHandlers.Config.update); +app.get('/data/Configuration/find', dataHandlers.Config.find); +app.post('/data/Configuration/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Configuration/post', dataHandlers.Config.add); +app.delete('/data/Configuration/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Configuration/delete', dataHandlers.Config.delete); +app.post('/data/Configuration/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Configuration/update', dataHandlers.Config.update); // user -app.use('/data/User/find', dataHandlers.User.find); -app.use('/data/User/post', permissionHandler(['Admin'])); -app.use('/data/User/post', dataHandlers.User.add); -app.use('/data/User/delete', permissionHandler(['Admin'])); -app.use('/data/user/delete', dataHandlers.User.delete); -app.use('/data/User/update', permissionHandler(['Admin'])); -app.use('/data/User/update', dataHandlers.User.update); +app.get('/data/User/find', dataHandlers.User.find); +app.post('/data/User/post', permissionHandler(['Admin'])); +app.post('/data/User/post', dataHandlers.User.add); +app.delete('/data/User/delete', permissionHandler(['Admin'])); +app.delete('/data/user/delete', dataHandlers.User.delete); +app.post('/data/User/update', permissionHandler(['Admin'])); +app.post('/data/User/update', dataHandlers.User.update); // render mongo returns/data app.use('/data', function(req, res, next) { + if (!req.data) { + res.status(404).json({'error': 'nothing found'}); + } res.json(req.data); }); From 55147f2064c0efb21cec7eddf056ae4acdeb9ec1 Mon Sep 17 00:00:00 2001 From: Ryan Birmingham Date: Fri, 3 Apr 2020 13:53:08 -0400 Subject: [PATCH 4/4] fix test case --- caracal.js | 2 +- test/functional/slide_lifecycle.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/caracal.js b/caracal.js index 75d4aa2..c71e293 100644 --- a/caracal.js +++ b/caracal.js @@ -143,7 +143,7 @@ app.post('/data/User/update', dataHandlers.User.update); // render mongo returns/data app.use('/data', function(req, res, next) { if (!req.data) { - res.status(404).json({'error': 'nothing found'}); + res.status(404).json({}); } res.json(req.data); }); diff --git a/test/functional/slide_lifecycle.js b/test/functional/slide_lifecycle.js index e269b43..6403d46 100644 --- a/test/functional/slide_lifecycle.js +++ b/test/functional/slide_lifecycle.js @@ -32,11 +32,9 @@ describe('Slide Lifecycle Step 1', function() { describe('Slide Lifecycle Step 2', function() { it('Finds the Slide', function(done) { this.timeout(5000); - var slideData = {'name': 'TEST', 'specimen': '', 'study': '', 'location': '/images/sample.svs', 'mpp': 0.499}; chai.request(server) - .post(findurl) + .get(findurl + '?name=TEST') .set('Content-Type', 'application/json; charset=utf-8') - .send(slideData) .end(function(err, res) { (res).should.have.status(200); (res.body).should.be.an('array');