From e7d67668e9a673bd4eee987cdb33900730ce3357 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 18 Mar 2016 15:59:03 +0000 Subject: [PATCH] Improve error reporting and show 404 when project ids are malformed --- services/web/app.coffee | 12 --- .../Authorization/AuthorizationManager.coffee | 5 +- .../AuthorizationMiddlewear.coffee | 4 + .../Collaborators/CollaboratorsHandler.coffee | 3 +- .../Features/Errors/ErrorController.coffee | 23 ++++- .../app/coffee/Features/Errors/Errors.coffee | 9 ++ .../app/coffee/infrastructure/Server.coffee | 4 + services/web/app/views/general/500.jade | 16 ++++ services/web/public/img/lion-sad-128.png | Bin 0 -> 14098 bytes .../AuthorizationManagerTests.coffee | 12 +++ .../AuthorizationMiddlewearTests.coffee | 16 ++++ .../CollaboratorsHandlerTests.coffee | 48 ++++++---- .../coffee/AuthorizationTests.coffee | 83 +----------------- .../acceptance/coffee/ProjectCRUDTests.coffee | 21 +++++ .../acceptance/coffee/helpers/User.coffee | 74 ++++++++++++++++ .../acceptance/coffee/helpers/request.coffee | 5 ++ 16 files changed, 222 insertions(+), 113 deletions(-) create mode 100644 services/web/app/coffee/Features/Errors/Errors.coffee create mode 100644 services/web/app/views/general/500.jade create mode 100644 services/web/public/img/lion-sad-128.png create mode 100644 services/web/test/acceptance/coffee/ProjectCRUDTests.coffee create mode 100644 services/web/test/acceptance/coffee/helpers/User.coffee create mode 100644 services/web/test/acceptance/coffee/helpers/request.coffee diff --git a/services/web/app.coffee b/services/web/app.coffee index 3d45307e5d..8496af8f17 100644 --- a/services/web/app.coffee +++ b/services/web/app.coffee @@ -18,18 +18,6 @@ argv = require("optimist") .usage("Usage: $0") .argv -Server.app.use (error, req, res, next) -> - if error?.code is 'EBADCSRFTOKEN' - logger.log err: error,url:req.url, method:req.method, user:req?.sesson?.user, "invalid csrf" - res.sendStatus(403) - return - logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear" - res.statusCode = error.status or 500 - if res.statusCode == 500 - res.end("Oops, something went wrong with your request, sorry. If this continues, please contact us at #{Settings.adminEmail}") - else - res.end() - if Settings.catchErrors process.removeAllListeners "uncaughtException" process.on "uncaughtException", (error) -> diff --git a/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee b/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee index db49881bbf..eb6f564fea 100644 --- a/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee +++ b/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee @@ -3,6 +3,7 @@ Project = require("../../models/Project").Project User = require("../../models/User").User PrivilegeLevels = require("./PrivilegeLevels") PublicAccessLevels = require("./PublicAccessLevels") +Errors = require("../Errors/Errors") module.exports = AuthorizationManager = # Get the privilege level that the user has for the project @@ -14,8 +15,10 @@ module.exports = AuthorizationManager = getPublicAccessLevel = () -> Project.findOne { _id: project_id }, { publicAccesLevel: 1 }, (error, project) -> return callback(error) if error? + if !project? + return callback new Errors.NotFoundError("no project found with id #{project_id}") if project.publicAccesLevel == PublicAccessLevels.READ_ONLY - return callback null, PrivilegeLevels.READ_ONLY + return callback null, PrivilegeLevels.READ_ONLY, true else if project.publicAccesLevel == PublicAccessLevels.READ_AND_WRITE return callback null, PrivilegeLevels.READ_AND_WRITE, true else diff --git a/services/web/app/coffee/Features/Authorization/AuthorizationMiddlewear.coffee b/services/web/app/coffee/Features/Authorization/AuthorizationMiddlewear.coffee index 2db1632ecf..4888db0c8a 100644 --- a/services/web/app/coffee/Features/Authorization/AuthorizationMiddlewear.coffee +++ b/services/web/app/coffee/Features/Authorization/AuthorizationMiddlewear.coffee @@ -1,6 +1,8 @@ AuthorizationManager = require("./AuthorizationManager") async = require "async" logger = require "logger-sharelatex" +ObjectId = require("mongojs").ObjectId +Errors = require "../Errors/Errors" module.exports = AuthorizationMiddlewear = ensureUserCanReadMultipleProjects: (req, res, next) -> @@ -83,6 +85,8 @@ module.exports = AuthorizationMiddlewear = project_id = req.params?.project_id or req.params?.Project_id if !project_id? return callback(new Error("Expected project_id in request parameters")) + if !ObjectId.isValid(project_id) + return callback(new Errors.NotFoundError("invalid project_id: #{project_id}")) AuthorizationMiddlewear._getUserId req, (error, user_id) -> return callback(error) if error? callback(null, user_id, project_id) diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee index cb6459c85f..71737eecff 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee @@ -7,12 +7,13 @@ ContactManager = require "../Contacts/ContactManager" CollaboratorsEmailHandler = require "./CollaboratorsEmailHandler" async = require "async" PrivilegeLevels = require "../Authorization/PrivilegeLevels" +Errors = require "../Errors/Errors" module.exports = CollaboratorsHandler = getMemberIdsWithPrivilegeLevels: (project_id, callback = (error, members) ->) -> Project.findOne { _id: project_id }, { owner_ref: 1, collaberator_refs: 1, readOnly_refs: 1 }, (error, project) -> return callback(error) if error? - return callback null, null if !project? + return callback new Errors.NotFoundError("no project found with id #{project_id}") if !project? members = [] members.push { id: project.owner_ref.toString(), privilegeLevel: PrivilegeLevels.OWNER } for member_id in project.readOnly_refs or [] diff --git a/services/web/app/coffee/Features/Errors/ErrorController.coffee b/services/web/app/coffee/Features/Errors/ErrorController.coffee index d0589ba5ed..a0ce6c85c9 100644 --- a/services/web/app/coffee/Features/Errors/ErrorController.coffee +++ b/services/web/app/coffee/Features/Errors/ErrorController.coffee @@ -1,5 +1,24 @@ +Errors = require "./Errors" +logger = require "logger-sharelatex" + module.exports = ErrorController = notFound: (req, res)-> - res.statusCode = 404 + res.status(404) res.render 'general/404', - title: "page_not_found" \ No newline at end of file + title: "page_not_found" + + serverError: (req, res)-> + res.status(500) + res.render 'general/500', + title: "Server Error" + + handleError: (error, req, res, next) -> + if error?.code is 'EBADCSRFTOKEN' + logger.log err: error,url:req.url, method:req.method, user:req?.sesson?.user, "invalid csrf" + res.sendStatus(403) + return + logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear" + if error instanceof Errors.NotFoundError + ErrorController.notFound req, res + else + ErrorController.serverError req, res \ No newline at end of file diff --git a/services/web/app/coffee/Features/Errors/Errors.coffee b/services/web/app/coffee/Features/Errors/Errors.coffee new file mode 100644 index 0000000000..0bbff1f19b --- /dev/null +++ b/services/web/app/coffee/Features/Errors/Errors.coffee @@ -0,0 +1,9 @@ +NotFoundError = (message) -> + error = new Error(message) + error.name = "NotFoundError" + error.__proto__ = NotFoundError.prototype + return error +NotFoundError.prototype.__proto__ = Error.prototype + +module.exports = Errors = + NotFoundError: NotFoundError \ No newline at end of file diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index 4a035ba007..fea8752bb2 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -30,6 +30,8 @@ OldAssetProxy = require("./OldAssetProxy") translations = require("translations-sharelatex").setup(Settings.i18n) Modules = require "./Modules" +ErrorController = require "../Features/Errors/ErrorController" + metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongojs/node_modules/mongodb"), logger) metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongoose/node_modules/mongodb"), logger) @@ -136,6 +138,8 @@ app.use(webRouter) router = new Router(webRouter, apiRouter) +app.use ErrorController.handleError + module.exports = app: app server: server diff --git a/services/web/app/views/general/500.jade b/services/web/app/views/general/500.jade new file mode 100644 index 0000000000..12983b923b --- /dev/null +++ b/services/web/app/views/general/500.jade @@ -0,0 +1,16 @@ +extends ../layout + +block content + .content + .container + .row + .col-md-8.col-md-offset-2.text-center + .page-header + h2 Oh dear, something went wrong. + p: img(src="/img/lion-sad-128.png", alt="Sad Lion") + p + | Something went wrong with your request, sorry. Our staff are probably looking into this, but it continues, please contact us at #{settings.adminEmail} + p + a(href="/") + i.fa.fa-arrow-circle-o-left + | #{translate("take_me_home")} diff --git a/services/web/public/img/lion-sad-128.png b/services/web/public/img/lion-sad-128.png new file mode 100644 index 0000000000000000000000000000000000000000..e5e58c42b0e574cb98c55f4b141cad01eff6f6d4 GIT binary patch literal 14098 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 literal 0 HcmV?d00001 diff --git a/services/web/test/UnitTests/coffee/Authorization/AuthorizationManagerTests.coffee b/services/web/test/UnitTests/coffee/Authorization/AuthorizationManagerTests.coffee index b1a464829c..1e3b6b0ebe 100644 --- a/services/web/test/UnitTests/coffee/Authorization/AuthorizationManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Authorization/AuthorizationManagerTests.coffee @@ -4,6 +4,7 @@ should = chai.should() expect = chai.expect modulePath = "../../../../app/js/Features/Authorization/AuthorizationManager.js" SandboxedModule = require('sandboxed-module') +Errors = require "../../../../app/js/Features/Errors/Errors.js" describe "AuthorizationManager", -> beforeEach -> @@ -11,6 +12,7 @@ describe "AuthorizationManager", -> "../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {} "../../models/Project": Project: @Project = {} "../../models/User": User: @User = {} + "../Errors/Errors": Errors @user_id = "user-id-1" @project_id = "project-id-1" @callback = sinon.stub() @@ -91,6 +93,16 @@ describe "AuthorizationManager", -> it "should return the public privilege level", -> @callback.calledWith(null, "readAndWrite", true).should.equal true + + describe "when the project doesn't exist", -> + beforeEach -> + @Project.findOne + .withArgs({ _id: @project_id }, { publicAccesLevel: 1 }) + .yields(null, null) + + it "should return a NotFoundError", -> + @AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, (error) -> + error.should.be.instanceof Errors.NotFoundError describe "canUserReadProject", -> beforeEach -> diff --git a/services/web/test/UnitTests/coffee/Authorization/AuthorizationMiddlewearTests.coffee b/services/web/test/UnitTests/coffee/Authorization/AuthorizationMiddlewearTests.coffee index 7e1ca2d5ef..bc62e603de 100644 --- a/services/web/test/UnitTests/coffee/Authorization/AuthorizationMiddlewearTests.coffee +++ b/services/web/test/UnitTests/coffee/Authorization/AuthorizationMiddlewearTests.coffee @@ -4,16 +4,21 @@ should = chai.should() expect = chai.expect modulePath = "../../../../app/js/Features/Authorization/AuthorizationMiddlewear.js" SandboxedModule = require('sandboxed-module') +Errors = require "../../../../app/js/Features/Errors/Errors.js" describe "AuthorizationMiddlewear", -> beforeEach -> @AuthorizationMiddlewear = SandboxedModule.require modulePath, requires: "./AuthorizationManager": @AuthorizationManager = {} "logger-sharelatex": {log: () ->} + "mongojs": ObjectId: @ObjectId = {} + "../Errors/Errors": Errors @user_id = "user-id-123" @project_id = "project-id-123" @req = {} @res = {} + @ObjectId.isValid = sinon.stub() + @ObjectId.isValid.withArgs(@project_id).returns true @next = sinon.stub() METHODS_TO_TEST = { @@ -90,6 +95,17 @@ describe "AuthorizationMiddlewear", -> @AuthorizationMiddlewear.redirectToRestricted .calledWith(@req, @res, @next) .should.equal true + + describe "with malformed project id", -> + beforeEach -> + @req.params = + project_id: "blah" + @ObjectId.isValid = sinon.stub().returns false + + it "should return a not found error", (done) -> + @AuthorizationMiddlewear[middlewearMethod] @req, @res, (error) -> + error.should.be.instanceof Errors.NotFoundError + done() describe "ensureUserIsSiteAdmin", -> beforeEach -> diff --git a/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsHandlerTests.coffee b/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsHandlerTests.coffee index f659cfebe6..632b43e7f2 100644 --- a/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsHandlerTests.coffee @@ -5,6 +5,7 @@ path = require('path') sinon = require('sinon') modulePath = path.join __dirname, "../../../../app/js/Features/Collaborators/CollaboratorsHandler" expect = require("chai").expect +Errors = require "../../../../app/js/Features/Errors/Errors.js" describe "CollaboratorsHandler", -> beforeEach -> @@ -16,6 +17,7 @@ describe "CollaboratorsHandler", -> "../../models/Project": Project: @Project = {} "../Project/ProjectEntityHandler": @ProjectEntityHandler = {} "./CollaboratorsEmailHandler": @CollaboratorsEmailHandler = {} + "../Errors/Errors": Errors @project_id = "mock-project-id" @user_id = "mock-user-id" @@ -24,25 +26,35 @@ describe "CollaboratorsHandler", -> @callback = sinon.stub() describe "getMemberIdsWithPrivilegeLevels", -> - beforeEach -> - @Project.findOne = sinon.stub() - @Project.findOne.withArgs({_id: @project_id}, {owner_ref: 1, collaberator_refs: 1, readOnly_refs: 1}).yields(null, @project = { - owner_ref: [ "owner-ref" ] - readOnly_refs: [ "read-only-ref-1", "read-only-ref-2" ] - collaberator_refs: [ "read-write-ref-1", "read-write-ref-2" ] - }) - @CollaboratorHandler.getMemberIdsWithPrivilegeLevels @project_id, @callback + describe "with project", -> + beforeEach -> + @Project.findOne = sinon.stub() + @Project.findOne.withArgs({_id: @project_id}, {owner_ref: 1, collaberator_refs: 1, readOnly_refs: 1}).yields(null, @project = { + owner_ref: [ "owner-ref" ] + readOnly_refs: [ "read-only-ref-1", "read-only-ref-2" ] + collaberator_refs: [ "read-write-ref-1", "read-write-ref-2" ] + }) + @CollaboratorHandler.getMemberIdsWithPrivilegeLevels @project_id, @callback - it "should return an array of member ids with their privilege levels", -> - @callback - .calledWith(null, [ - { id: "owner-ref", privilegeLevel: "owner" } - { id: "read-only-ref-1", privilegeLevel: "readOnly" } - { id: "read-only-ref-2", privilegeLevel: "readOnly" } - { id: "read-write-ref-1", privilegeLevel: "readAndWrite" } - { id: "read-write-ref-2", privilegeLevel: "readAndWrite" } - ]) - .should.equal true + it "should return an array of member ids with their privilege levels", -> + @callback + .calledWith(null, [ + { id: "owner-ref", privilegeLevel: "owner" } + { id: "read-only-ref-1", privilegeLevel: "readOnly" } + { id: "read-only-ref-2", privilegeLevel: "readOnly" } + { id: "read-write-ref-1", privilegeLevel: "readAndWrite" } + { id: "read-write-ref-2", privilegeLevel: "readAndWrite" } + ]) + .should.equal true + + describe "with a missing project", -> + beforeEach -> + @Project.findOne = sinon.stub().yields(null, null) + + it "should return a NotFoundError", (done) -> + @CollaboratorHandler.getMemberIdsWithPrivilegeLevels @project_id, (error) -> + error.should.be.instanceof Errors.NotFoundError + done() describe "getMemberIds", -> beforeEach -> diff --git a/services/web/test/acceptance/coffee/AuthorizationTests.coffee b/services/web/test/acceptance/coffee/AuthorizationTests.coffee index 0cacffb5cf..5d54151483 100644 --- a/services/web/test/acceptance/coffee/AuthorizationTests.coffee +++ b/services/web/test/acceptance/coffee/AuthorizationTests.coffee @@ -1,83 +1,8 @@ -request = require("request") expect = require("chai").expect -async = require "async" -settings = require("settings-sharelatex") -{db} = require("../../../app/js/infrastructure/mongojs") - -count = 0 -BASE_URL = "http://localhost:3000" - -request = request.defaults({ - baseUrl: BASE_URL, - followRedirect: false -}) - -class User - constructor: (options = {}) -> - @email = "acceptance-test-#{count}@example.com" - @password = "acceptance-test-#{count}-password" - count++ - @jar = request.jar() - @request = request.defaults({ - jar: @jar - }) - - login: (callback = (error) ->) -> - @getCsrfToken (error) => - return callback(error) if error? - @request.post { - url: "/register" # Register will log in, but also ensure user exists - json: - email: @email - password: @password - }, (error, response, body) => - return callback(error) if error? - db.users.findOne {email: @email}, (error, user) => - return callback(error) if error? - @id = user?._id?.toString() - callback() - - createProject: (name, callback = (error, project_id) ->) -> - @request.post { - url: "/project/new", - json: - projectName: name - }, (error, response, body) -> - return callback(error) if error? - if !body?.project_id? - console.error "SOMETHING WENT WRONG CREATING PROJECT", response.statusCode, response.headers["location"], body - callback(null, body.project_id) - - addUserToProject: (project_id, email, privileges, callback = (error, user) ->) -> - @request.post { - url: "/project/#{project_id}/users", - json: {email, privileges} - }, (error, response, body) -> - return callback(error) if error? - callback(null, body.user) - - makePublic: (project_id, level, callback = (error) ->) -> - @request.post { - url: "/project/#{project_id}/settings/admin", - json: - publicAccessLevel: level - }, (error, response, body) -> - return callback(error) if error? - callback(null) - - getCsrfToken: (callback = (error) ->) -> - @request.get { - url: "/register" - }, (err, response, body) => - return callback(error) if error? - csrfMatches = body.match("window.csrfToken = \"(.*?)\";") - if !csrfMatches? - return callback(new Error("no csrf token found")) - @request = @request.defaults({ - headers: - "x-csrf-token": csrfMatches[1] - }) - callback() +async = require("async") +User = require "./helpers/User" +request = require "./helpers/request" +settings = require "settings-sharelatex" try_read_access = (user, project_id, test, callback) -> async.series [ diff --git a/services/web/test/acceptance/coffee/ProjectCRUDTests.coffee b/services/web/test/acceptance/coffee/ProjectCRUDTests.coffee new file mode 100644 index 0000000000..19e5f445f7 --- /dev/null +++ b/services/web/test/acceptance/coffee/ProjectCRUDTests.coffee @@ -0,0 +1,21 @@ +expect = require("chai").expect +async = require("async") +User = require "./helpers/User" + +describe "Project CRUD", -> + before (done) -> + @user = new User() + @user.login done + + describe "when project doesn't exist", -> + it "should return 404", (done) -> + @user.request.get "/project/aaaaaaaaaaaaaaaaaaaaaaaa", (err, res, body) -> + expect(res.statusCode).to.equal 404 + done() + + describe "when project has malformed id", -> + it "should return 404", (done) -> + @user.request.get "/project/blah", (err, res, body) -> + expect(res.statusCode).to.equal 404 + done() + \ No newline at end of file diff --git a/services/web/test/acceptance/coffee/helpers/User.coffee b/services/web/test/acceptance/coffee/helpers/User.coffee new file mode 100644 index 0000000000..f1c38029d7 --- /dev/null +++ b/services/web/test/acceptance/coffee/helpers/User.coffee @@ -0,0 +1,74 @@ +request = require("./request") +settings = require("settings-sharelatex") +{db} = require("../../../../app/js/infrastructure/mongojs") + +count = 0 + +class User + constructor: (options = {}) -> + @email = "acceptance-test-#{count}@example.com" + @password = "acceptance-test-#{count}-password" + count++ + @jar = request.jar() + @request = request.defaults({ + jar: @jar + }) + + login: (callback = (error) ->) -> + @getCsrfToken (error) => + return callback(error) if error? + @request.post { + url: "/register" # Register will log in, but also ensure user exists + json: + email: @email + password: @password + }, (error, response, body) => + return callback(error) if error? + db.users.findOne {email: @email}, (error, user) => + return callback(error) if error? + @id = user?._id?.toString() + callback() + + createProject: (name, callback = (error, project_id) ->) -> + @request.post { + url: "/project/new", + json: + projectName: name + }, (error, response, body) -> + return callback(error) if error? + if !body?.project_id? + console.error "SOMETHING WENT WRONG CREATING PROJECT", response.statusCode, response.headers["location"], body + callback(null, body.project_id) + + addUserToProject: (project_id, email, privileges, callback = (error, user) ->) -> + @request.post { + url: "/project/#{project_id}/users", + json: {email, privileges} + }, (error, response, body) -> + return callback(error) if error? + callback(null, body.user) + + makePublic: (project_id, level, callback = (error) ->) -> + @request.post { + url: "/project/#{project_id}/settings/admin", + json: + publicAccessLevel: level + }, (error, response, body) -> + return callback(error) if error? + callback(null) + + getCsrfToken: (callback = (error) ->) -> + @request.get { + url: "/register" + }, (err, response, body) => + return callback(error) if error? + csrfMatches = body.match("window.csrfToken = \"(.*?)\";") + if !csrfMatches? + return callback(new Error("no csrf token found")) + @request = @request.defaults({ + headers: + "x-csrf-token": csrfMatches[1] + }) + callback() + +module.exports = User \ No newline at end of file diff --git a/services/web/test/acceptance/coffee/helpers/request.coffee b/services/web/test/acceptance/coffee/helpers/request.coffee new file mode 100644 index 0000000000..879acd843a --- /dev/null +++ b/services/web/test/acceptance/coffee/helpers/request.coffee @@ -0,0 +1,5 @@ +BASE_URL = "http://localhost:3000" +module.exports = require("request").defaults({ + baseUrl: BASE_URL, + followRedirect: false +}) \ No newline at end of file