diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index 70cd0a845b..3fbad520c9 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -196,7 +196,11 @@ module.exports = AuthenticationController = _setRedirectInSession: (req, value) -> if !value? value = if Object.keys(req.query).length > 0 then "#{req.path}?#{querystring.stringify(req.query)}" else "#{req.path}" - if req.session? && !value.match(new RegExp('^\/(socket.io|js|stylesheets|img)\/.*$')) + if ( + req.session? && + !value.match(new RegExp('^\/(socket.io|js|stylesheets|img)\/.*$')) && + !value.match(new RegExp('^.*\.(png|jpeg|svg)$')) + ) req.session.postLoginRedirect = value _getRedirectFromSession: (req) -> diff --git a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee index c19c5e02ea..18e69e274d 100644 --- a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee @@ -2,6 +2,8 @@ Settings = require "settings-sharelatex" request = require('request') RedisWrapper = require("../../infrastructure/RedisWrapper") rclient = RedisWrapper.client("clsi_cookie") +if Settings.redis.clsi_cookie_secondary? + rclient_secondary = RedisWrapper.client("clsi_cookie_secondary") Cookie = require('cookie') logger = require "logger-sharelatex" @@ -17,10 +19,10 @@ module.exports = ClsiCookieManager = rclient.get buildKey(project_id), (err, serverId)-> if err? return callback(err) - if serverId? - return callback(null, serverId) - else + if !serverId? or serverId == "" return ClsiCookieManager._populateServerIdViaRequest project_id, callback + else + return callback(null, serverId) _populateServerIdViaRequest :(project_id, callback = (err, serverId)->)-> @@ -42,11 +44,16 @@ module.exports = ClsiCookieManager = if !clsiCookiesEnabled return callback() serverId = ClsiCookieManager._parseServerIdFromResponse(response) + if rclient_secondary? + @_setServerIdInRedis rclient_secondary, project_id, serverId + @_setServerIdInRedis rclient, project_id, serverId, (err) -> + callback(err, serverId) + + _setServerIdInRedis: (rclient, project_id, serverId, callback = (err) ->) -> multi = rclient.multi() multi.set buildKey(project_id), serverId multi.expire buildKey(project_id), Settings.clsiCookie.ttl - multi.exec (err)-> - callback(err, serverId) + multi.exec callback clearServerId: (project_id, callback = (err)->)-> if !clsiCookiesEnabled diff --git a/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee b/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee index 10545cca76..92efe7bfcb 100644 --- a/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee +++ b/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee @@ -20,30 +20,33 @@ module.exports = FileStoreHandler = logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "tried to upload symlink, not contining" return callback(new Error("can not upload symlink")) + _cb = callback + callback = (err) -> + callback = -> # avoid double callbacks + _cb(err) + logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk" readStream = fs.createReadStream(fsPath) - opts = - method: "post" - uri: FileStoreHandler._buildUrl(project_id, file_id) - timeout:fiveMinsInMs - writeStream = request(opts) - readStream.pipe writeStream - - writeStream.on 'response', (response) -> - if response.statusCode not in [200, 201] - err = new Error("non-ok response from filestore for upload: #{response.statusCode}") - logger.err {err, statusCode: response.statusCode}, "error uploading to filestore" - callback(err) - else - callback(null) - readStream.on "error", (err)-> logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk" callback err - - writeStream.on "error", (err)-> - logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk" - callback err + readStream.on "open", () -> + opts = + method: "post" + uri: FileStoreHandler._buildUrl(project_id, file_id) + timeout:fiveMinsInMs + writeStream = request(opts) + writeStream.on "error", (err)-> + logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk" + callback err + writeStream.on 'response', (response) -> + if response.statusCode not in [200, 201] + err = new Error("non-ok response from filestore for upload: #{response.statusCode}") + logger.err {err, statusCode: response.statusCode}, "error uploading to filestore" + callback(err) + else + callback(null) + readStream.pipe writeStream getFileStream: (project_id, file_id, query, callback)-> logger.log project_id:project_id, file_id:file_id, query:query, "getting file stream from file store" diff --git a/services/web/app/coffee/Features/SudoMode/SudoModeController.coffee b/services/web/app/coffee/Features/SudoMode/SudoModeController.coffee new file mode 100644 index 0000000000..da31522a6c --- /dev/null +++ b/services/web/app/coffee/Features/SudoMode/SudoModeController.coffee @@ -0,0 +1,63 @@ +logger = require 'logger-sharelatex' +SudoModeHandler = require './SudoModeHandler' +AuthenticationController = require '../Authentication/AuthenticationController' +AuthenticationManager = require '../Authentication/AuthenticationManager' +ObjectId = require('../../infrastructure/Mongoose').mongo.ObjectId +UserGetter = require '../User/UserGetter' + + +module.exports = SudoModeController = + + sudoModePrompt: (req, res, next) -> + if req.externalAuthenticationSystemUsed() + logger.log {userId}, "[SudoMode] using external auth, redirecting" + return res.redirect('/project') + userId = AuthenticationController.getLoggedInUserId(req) + logger.log {userId}, "[SudoMode] rendering sudo mode password page" + SudoModeHandler.isSudoModeActive userId, (err, isActive) -> + if err? + logger.err {err, userId}, "[SudoMode] error checking if sudo mode is active" + return next(err) + if isActive + logger.log {userId}, "[SudoMode] sudo mode already active, redirecting" + return res.redirect('/project') + res.render 'sudo_mode/sudo_mode_prompt', title: 'confirm_password_to_continue' + + submitPassword: (req, res, next) -> + userId = AuthenticationController.getLoggedInUserId(req) + redir = AuthenticationController._getRedirectFromSession(req) || "/project" + password = req.body.password + if !password + logger.log {userId}, "[SudoMode] no password supplied, failed authentication" + return next(new Error('no password supplied')) + logger.log {userId, redir}, "[SudoMode] checking user password" + UserGetter.getUser ObjectId(userId), {email: 1}, (err, userRecord) -> + if err? + logger.err {err, userId}, "[SudoMode] error getting user" + return next(err) + if !userRecord? + err = new Error('user not found') + logger.err {err, userId}, "[SudoMode] user not found" + return next(err) + AuthenticationManager.authenticate email: userRecord.email, password, (err, user) -> + if err? + logger.err {err, userId}, "[SudoMode] error authenticating user" + return next(err) + if user? + logger.log {userId}, "[SudoMode] authenticated user, activating sudo mode" + SudoModeHandler.activateSudoMode userId, (err) -> + if err? + logger.err {err, userId}, "[SudoMode] error activating sudo mode" + return next(err) + return res.json { + redir: redir + } + else + logger.log {userId}, "[SudoMode] authentication failed for user" + return res.json { + message: { + text: req.i18n.translate("invalid_password"), + type: 'error' + } + } + diff --git a/services/web/app/coffee/Features/SudoMode/SudoModeHandler.coffee b/services/web/app/coffee/Features/SudoMode/SudoModeHandler.coffee new file mode 100644 index 0000000000..004b63ccc2 --- /dev/null +++ b/services/web/app/coffee/Features/SudoMode/SudoModeHandler.coffee @@ -0,0 +1,33 @@ +RedisWrapper = require('../../infrastructure/RedisWrapper') +rclient = RedisWrapper.client('sudomode') +logger = require('logger-sharelatex') + + +TIMEOUT_IN_SECONDS = 60 * 60 + + +module.exports = SudoModeHandler = + + _buildKey: (userId) -> + "SudoMode:{#{userId}}" + + activateSudoMode: (userId, callback=(err)->) -> + if !userId? + return callback(new Error('[SudoMode] user must be supplied')) + duration = TIMEOUT_IN_SECONDS + logger.log {userId, duration}, "[SudoMode] activating sudo mode for user" + rclient.set SudoModeHandler._buildKey(userId), '1', 'EX', duration, callback + + clearSudoMode: (userId, callback=(err)->) -> + if !userId? + return callback(new Error('[SudoMode] user must be supplied')) + logger.log {userId}, "[SudoMode] clearing sudo mode for user" + rclient.del SudoModeHandler._buildKey(userId), callback + + isSudoModeActive: (userId, callback=(err, isActive)->) -> + if !userId? + return callback(new Error('[SudoMode] user must be supplied')) + rclient.get SudoModeHandler._buildKey(userId), (err, result) -> + if err? + return callback(err) + callback(null, result == '1') diff --git a/services/web/app/coffee/Features/SudoMode/SudoModeMiddlewear.coffee b/services/web/app/coffee/Features/SudoMode/SudoModeMiddlewear.coffee new file mode 100644 index 0000000000..479d67eee0 --- /dev/null +++ b/services/web/app/coffee/Features/SudoMode/SudoModeMiddlewear.coffee @@ -0,0 +1,24 @@ +logger = require 'logger-sharelatex' +SudoModeHandler = require './SudoModeHandler' +AuthenticationController = require '../Authentication/AuthenticationController' + + +module.exports = SudoModeMiddlewear = + + protectPage: (req, res, next) -> + if req.externalAuthenticationSystemUsed() + logger.log {userId}, "[SudoMode] using external auth, skipping sudo-mode check" + return next() + userId = AuthenticationController.getLoggedInUserId(req) + logger.log {userId}, "[SudoMode] protecting endpoint, checking if sudo mode is active" + SudoModeHandler.isSudoModeActive userId, (err, isActive) -> + if err? + logger.err {err, userId}, "[SudoMode] error checking if sudo mode is active" + return next(err) + if isActive + logger.log {userId}, "[SudoMode] sudo mode active, continuing" + return next() + else + logger.log {userId}, "[SudoMode] sudo mode not active, redirecting" + AuthenticationController._setRedirectInSession(req) + return res.redirect('/confirm-password') diff --git a/services/web/app/coffee/Features/User/UserController.coffee b/services/web/app/coffee/Features/User/UserController.coffee index 50b02fc918..65000985e1 100644 --- a/services/web/app/coffee/Features/User/UserController.coffee +++ b/services/web/app/coffee/Features/User/UserController.coffee @@ -11,6 +11,7 @@ AuthenticationManager = require("../Authentication/AuthenticationManager") AuthenticationController = require('../Authentication/AuthenticationController') UserSessionsManager = require("./UserSessionsManager") UserUpdater = require("./UserUpdater") +SudoModeHandler = require('../SudoMode/SudoModeHandler') settings = require "settings-sharelatex" module.exports = UserController = @@ -118,6 +119,7 @@ module.exports = UserController = if err logger.err err: err, 'error destorying session' UserSessionsManager.untrackSession(user, sessionId) + SudoModeHandler.clearSudoMode(user._id) res.redirect '/login' register : (req, res, next = (error) ->)-> diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index ba753cc86e..100fd28bd6 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -84,6 +84,11 @@ module.exports = (app, webRouter, apiRouter)-> webRouter.use addSetContentDisposition apiRouter.use addSetContentDisposition + webRouter.use (req, res, next)-> + req.externalAuthenticationSystemUsed = res.locals.externalAuthenticationSystemUsed = -> + Settings.ldap? or Settings.saml? + next() + webRouter.use (req, res, next)-> cdnBlocked = req.query.nocdn == 'true' or req.session.cdnBlocked @@ -222,11 +227,6 @@ module.exports = (app, webRouter, apiRouter)-> res.locals.formatPrice = SubscriptionFormatters.formatPrice next() - webRouter.use (req, res, next)-> - res.locals.externalAuthenticationSystemUsed = -> - Settings.ldap? or Settings.saml? - next() - webRouter.use (req, res, next)-> currentUser = AuthenticationController.getSessionUser(req) if currentUser? diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index fa799a2dff..549cc50e81 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -39,6 +39,8 @@ ContactRouter = require("./Features/Contacts/ContactRouter") ReferencesController = require('./Features/References/ReferencesController') AuthorizationMiddlewear = require('./Features/Authorization/AuthorizationMiddlewear') BetaProgramController = require('./Features/BetaProgram/BetaProgramController') +SudoModeController = require('./Features/SudoMode/SudoModeController') +SudoModeMiddlewear = require('./Features/SudoMode/SudoModeMiddlewear') AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter') AnnouncementsController = require("./Features/Announcements/AnnouncementsController") @@ -86,11 +88,17 @@ module.exports = class Router webRouter.get '/user/activate', UserPagesController.activateAccountPage AuthenticationController.addEndpointToLoginWhitelist '/user/activate' - webRouter.get '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage + webRouter.get '/user/settings', + AuthenticationController.requireLogin(), + SudoModeMiddlewear.protectPage, + UserPagesController.settingsPage webRouter.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings webRouter.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword - webRouter.get '/user/sessions', AuthenticationController.requireLogin(), UserPagesController.sessionsPage + webRouter.get '/user/sessions', + AuthenticationController.requireLogin(), + SudoModeMiddlewear.protectPage, + UserPagesController.sessionsPage webRouter.post '/user/sessions/clear', AuthenticationController.requireLogin(), UserController.clearSessions webRouter.delete '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe @@ -239,6 +247,9 @@ module.exports = class Router webRouter.get "/beta/participate", AuthenticationController.requireLogin(), BetaProgramController.optInPage webRouter.post "/beta/opt-in", AuthenticationController.requireLogin(), BetaProgramController.optIn webRouter.post "/beta/opt-out", AuthenticationController.requireLogin(), BetaProgramController.optOut + webRouter.get "/confirm-password", AuthenticationController.requireLogin(), SudoModeController.sudoModePrompt + webRouter.post "/confirm-password", AuthenticationController.requireLogin(), SudoModeController.submitPassword + #Admin Stuff webRouter.get '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index diff --git a/services/web/app/views/sudo_mode/sudo_mode_prompt.pug b/services/web/app/views/sudo_mode/sudo_mode_prompt.pug new file mode 100644 index 0000000000..27ccc41eb6 --- /dev/null +++ b/services/web/app/views/sudo_mode/sudo_mode_prompt.pug @@ -0,0 +1,45 @@ +extends ../layout + +block content + .content.content-alt + .container + .row + .col-md-8.col-md-offset-2.col-lg-6.col-lg-offset-3 + .card + .page-header.text-centered + h2 #{translate('confirm_password_to_continue')} + div + .container-fluid + .row + .col-md-12 + form(async-form="confirmPassword", name="confirmPassword", + action='/confirm-password', method="POST", ng-cloak) + input(name='_csrf', type='hidden', value=csrfToken) + form-messages(for="confirmPassword") + .form-group + label + | #{translate('password')} + input.form-control( + type='password', + name='password', + required, + placeholder='********', + ng-model="password" + ) + span.small.text-primary( + ng-show="confirmPassword.password.$invalid && confirmPassword.password.$dirty" + ) + | #{translate("required")} + .actions + button.btn-primary.btn( + style="width: 100%", + type='submit', + ng-disabled="confirmPassword.inflight" + ) + span #{translate("confirm")} + + .row.row-spaced-small + .col-md-12 + p.text-centered + small + | #{translate('confirm_password_footer')} diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee index 4d293dcd4b..e759b9c236 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee @@ -33,7 +33,7 @@ define [ @rejectChangeIds([ change_id ]) @$scope.$on "change:bulk-accept", (e, change_ids) => - @acceptChangeIds(change_ids) + @acceptChangeIds(change_ids) @$scope.$on "change:bulk-reject", (e, change_ids) => @rejectChangeIds(change_ids) diff --git a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee index f99e8543b1..a014b28098 100644 --- a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee @@ -555,6 +555,14 @@ describe "AuthenticationController", -> @AuthenticationController._setRedirectInSession(@req, '/somewhere/specific') expect(@req.session.postLoginRedirect).to.equal "/somewhere/specific" + describe 'with a png', -> + beforeEach -> + @req = {session: {}} + + it 'should not set the redirect', -> + @AuthenticationController._setRedirectInSession(@req, '/something.png') + expect(@req.session.postLoginRedirect).to.equal undefined + describe 'with a js path', -> beforeEach -> diff --git a/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee b/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee index c54f405bb9..ae0e9eb20e 100644 --- a/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee @@ -34,9 +34,8 @@ describe "ClsiCookieManager", -> ttl:Math.random() key: "coooookie" @requires = - "../../infrastructure/RedisWrapper": - client: => - @redis + "../../infrastructure/RedisWrapper": @RedisWrapper = + client: => @redis "settings-sharelatex": @settings "request": @request @@ -61,6 +60,13 @@ describe "ClsiCookieManager", -> @ClsiCookieManager._populateServerIdViaRequest.calledWith(@project_id).should.equal true done() + it "should _populateServerIdViaRequest if no key is blank", (done)-> + @ClsiCookieManager._populateServerIdViaRequest = sinon.stub().callsArgWith(1) + @redis.get.callsArgWith(1, null, "") + @ClsiCookieManager._getServerId @project_id, (err, serverId)=> + @ClsiCookieManager._populateServerIdViaRequest.calledWith(@project_id).should.equal true + done() + describe "_populateServerIdViaRequest", -> @@ -106,6 +112,24 @@ describe "ClsiCookieManager", -> @ClsiCookieManager.setServerId @project_id, @response, (err, serverId)=> @redisMulti.exec.called.should.equal false done() + + it "should also set in the secondary if secondary redis is enabled", (done) -> + @redisSecondaryMulti = + set:sinon.stub() + expire:sinon.stub() + exec:sinon.stub() + @redis_secondary = + multi: => @redisSecondaryMulti + @settings.redis.clsi_cookie_secondary = {} + @RedisWrapper.client = sinon.stub() + @RedisWrapper.client.withArgs("clsi_cookie").returns(@redis) + @RedisWrapper.client.withArgs("clsi_cookie_secondary").returns(@redis_secondary) + @ClsiCookieManager = SandboxedModule.require modulePath, requires:@requires + @ClsiCookieManager._parseServerIdFromResponse = sinon.stub().returns("clsi-8") + @ClsiCookieManager.setServerId @project_id, @response, (err, serverId)=> + @redisSecondaryMulti.set.calledWith("clsiserver:#{@project_id}", "clsi-8").should.equal true + @redisSecondaryMulti.expire.calledWith("clsiserver:#{@project_id}", @settings.clsiCookie.ttl).should.equal true + done() describe "getCookieJar", -> diff --git a/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee b/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee index 01990787e1..73f32f3a60 100644 --- a/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee @@ -40,8 +40,8 @@ describe "FileStoreHandler", -> it "should create read stream", (done)-> @fs.createReadStream.returns pipe:-> - on: (type, cb)-> - if type == "end" + on: (type, cb)-> + if type == "open" cb() @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, => @fs.createReadStream.calledWith(@fsPath).should.equal true @@ -51,7 +51,7 @@ describe "FileStoreHandler", -> @request.returns(@writeStream) @fs.createReadStream.returns on: (type, cb)-> - if type == "end" + if type == "open" cb() pipe:(o)=> @writeStream.should.equal o @@ -62,7 +62,7 @@ describe "FileStoreHandler", -> @fs.createReadStream.returns pipe:-> on: (type, cb)-> - if type == "end" + if type == "open" cb() @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, => @request.args[0][0].method.should.equal "post" @@ -73,7 +73,7 @@ describe "FileStoreHandler", -> @fs.createReadStream.returns pipe:-> on: (type, cb)-> - if type == "end" + if type == "open" cb() @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, => @handler._buildUrl.calledWith(@project_id, @file_id).should.equal true @@ -83,7 +83,7 @@ describe "FileStoreHandler", -> @fs.createReadStream.returns pipe:-> on: (type, cb)-> - if type == "end" + if type == "open" cb() @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err) => expect(err).to.not.exist @@ -113,7 +113,7 @@ describe "FileStoreHandler", -> @fs.createReadStream.returns pipe:-> on: (type, cb)-> - if type == "end" + if type == "open" cb() @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err) => expect(err).to.exist diff --git a/services/web/test/UnitTests/coffee/SudoMode/SudoModeControllerTests.coffee b/services/web/test/UnitTests/coffee/SudoMode/SudoModeControllerTests.coffee new file mode 100644 index 0000000000..c1de2e278c --- /dev/null +++ b/services/web/test/UnitTests/coffee/SudoMode/SudoModeControllerTests.coffee @@ -0,0 +1,297 @@ +SandboxedModule = require('sandboxed-module') +sinon = require 'sinon' +should = require("chai").should() +expect = require('chai').expect +MockRequest = require "../helpers/MockRequest" +MockResponse = require "../helpers/MockResponse" +modulePath = '../../../../app/js/Features/SudoMode/SudoModeController' + +describe 'SudoModeController', -> + beforeEach -> + @user = + _id: 'abcd' + email: 'user@example.com' + @UserGetter = + getUser: sinon.stub().callsArgWith(2, null, @user) + @SudoModeHandler = + isSudoModeActive: sinon.stub() + activateSudoMode: sinon.stub() + @AuthenticationController = + getLoggedInUserId: sinon.stub().returns(@user._id) + _getRediretFromSession: sinon.stub() + @AuthenticationManager = + authenticate: sinon.stub() + @UserGetter = + getUser: sinon.stub() + @SudoModeController = SandboxedModule.require modulePath, requires: + 'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()} + './SudoModeHandler': @SudoModeHandler + '../Authentication/AuthenticationController': @AuthenticationController + '../Authentication/AuthenticationManager': @AuthenticationManager + '../../infrastructure/Mongoose': {mongo: {ObjectId: () -> 'some_object_id'}} + '../User/UserGetter': @UserGetter + + describe 'sudoModePrompt', -> + beforeEach -> + @SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, null, false) + @req = {externalAuthenticationSystemUsed: sinon.stub().returns(false)} + @res = {redirect: sinon.stub(), render: sinon.stub()} + @next = sinon.stub() + + it 'should get the logged in user id', -> + @SudoModeController.sudoModePrompt(@req, @res, @next) + @AuthenticationController.getLoggedInUserId.callCount.should.equal 1 + @AuthenticationController.getLoggedInUserId.calledWith(@req).should.equal true + + it 'should check if sudo-mode is active', -> + @SudoModeController.sudoModePrompt(@req, @res, @next) + @SudoModeHandler.isSudoModeActive.callCount.should.equal 1 + @SudoModeHandler.isSudoModeActive.calledWith(@user._id).should.equal true + + it 'should redirect when sudo-mode is active', -> + @SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, null, true) + @SudoModeController.sudoModePrompt(@req, @res, @next) + @res.redirect.callCount.should.equal 1 + @res.redirect.calledWith('/project').should.equal true + + it 'should render the sudo_mode_prompt page when sudo mode is not active', -> + @SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, null, false) + @SudoModeController.sudoModePrompt(@req, @res, @next) + @res.render.callCount.should.equal 1 + @res.render.calledWith('sudo_mode/sudo_mode_prompt').should.equal true + + describe 'when isSudoModeActive produces an error', -> + beforeEach -> + @SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, new Error('woops')) + @next = sinon.stub() + + it 'should call next with an error', -> + @SudoModeController.sudoModePrompt(@req, @res, @next) + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + + it 'should not render page', -> + @SudoModeController.sudoModePrompt(@req, @res, @next) + @res.render.callCount.should.equal 0 + + describe 'when external auth system is used', -> + beforeEach -> + @req.externalAuthenticationSystemUsed = sinon.stub().returns(true) + + it 'should redirect', -> + @SudoModeController.sudoModePrompt(@req, @res, @next) + @res.redirect.callCount.should.equal 1 + @res.redirect.calledWith('/project').should.equal true + + it 'should not check if sudo mode is active', -> + @SudoModeController.sudoModePrompt(@req, @res, @next) + @SudoModeHandler.isSudoModeActive.callCount.should.equal 0 + + it 'should not render page', -> + @SudoModeController.sudoModePrompt(@req, @res, @next) + @res.render.callCount.should.equal 0 + + describe 'submitPassword', -> + beforeEach -> + @AuthenticationController._getRedirectFromSession = sinon.stub().returns '/somewhere' + @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user) + @AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, null, @user) + @SudoModeHandler.activateSudoMode = sinon.stub().callsArgWith(1, null) + @password = 'a_terrible_secret' + @req = {body: {password: @password}} + @res = {json: sinon.stub()} + @next = sinon.stub() + + describe 'when all goes well', -> + beforeEach -> + + it 'should get the logged in user id', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationController.getLoggedInUserId.callCount.should.equal 1 + @AuthenticationController.getLoggedInUserId.calledWith(@req).should.equal true + + it 'should get redirect from session', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationController._getRedirectFromSession.callCount.should.equal 1 + @AuthenticationController._getRedirectFromSession.calledWith(@req).should.equal true + + it 'should get the user from storage', -> + @SudoModeController.submitPassword(@req, @res, @next) + @UserGetter.getUser.callCount.should.equal 1 + @UserGetter.getUser.calledWith('some_object_id', {email: 1}).should.equal true + + it 'should try to authenticate the user with the password', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationManager.authenticate.callCount.should.equal 1 + @AuthenticationManager.authenticate.calledWith({email: @user.email}, @password).should.equal true + + it 'should activate sudo mode', -> + @SudoModeController.submitPassword(@req, @res, @next) + @SudoModeHandler.activateSudoMode.callCount.should.equal 1 + @SudoModeHandler.activateSudoMode.calledWith(@user._id).should.equal true + + it 'should send back a json response', -> + @SudoModeController.submitPassword(@req, @res, @next) + @res.json.callCount.should.equal 1 + @res.json.calledWith({redir: '/somewhere'}).should.equal true + + it 'should not call next', -> + @SudoModeController.submitPassword(@req, @res, @next) + @next.callCount.should.equal 0 + + describe 'when no password is supplied', -> + beforeEach -> + @req.body.password = '' + @next = sinon.stub() + + it 'should return next with an error', -> + @SudoModeController.submitPassword(@req, @res, @next) + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + + it 'should not get the user from storage', -> + @SudoModeController.submitPassword(@req, @res, @next) + @UserGetter.getUser.callCount.should.equal 0 + + it 'should not try to authenticate the user with the password', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationManager.authenticate.callCount.should.equal 0 + + it 'should not activate sudo mode', -> + @SudoModeController.submitPassword(@req, @res, @next) + @SudoModeHandler.activateSudoMode.callCount.should.equal 0 + + it 'should not send back a json response', -> + @SudoModeController.submitPassword(@req, @res, @next) + @res.json.callCount.should.equal 0 + + describe 'when getUser produces an error', -> + beforeEach -> + @UserGetter.getUser = sinon.stub().callsArgWith(2, new Error('woops')) + @next = sinon.stub() + + it 'should return next with an error', -> + @SudoModeController.submitPassword(@req, @res, @next) + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + + it 'should get the user from storage', -> + @SudoModeController.submitPassword(@req, @res, @next) + @UserGetter.getUser.callCount.should.equal 1 + @UserGetter.getUser.calledWith('some_object_id', {email: 1}).should.equal true + + it 'should not try to authenticate the user with the password', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationManager.authenticate.callCount.should.equal 0 + + it 'should not activate sudo mode', -> + @SudoModeController.submitPassword(@req, @res, @next) + @SudoModeHandler.activateSudoMode.callCount.should.equal 0 + + it 'should not send back a json response', -> + @SudoModeController.submitPassword(@req, @res, @next) + @res.json.callCount.should.equal 0 + + describe 'when getUser does not find a user', -> + beforeEach -> + @UserGetter.getUser = sinon.stub().callsArgWith(2, null, null) + @next = sinon.stub() + + it 'should return next with an error', -> + @SudoModeController.submitPassword(@req, @res, @next) + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + + it 'should get the user from storage', -> + @SudoModeController.submitPassword(@req, @res, @next) + @UserGetter.getUser.callCount.should.equal 1 + @UserGetter.getUser.calledWith('some_object_id', {email: 1}).should.equal true + + it 'should not try to authenticate the user with the password', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationManager.authenticate.callCount.should.equal 0 + + it 'should not activate sudo mode', -> + @SudoModeController.submitPassword(@req, @res, @next) + @SudoModeHandler.activateSudoMode.callCount.should.equal 0 + + it 'should not send back a json response', -> + @SudoModeController.submitPassword(@req, @res, @next) + @res.json.callCount.should.equal 0 + + describe 'when authentication fails', -> + beforeEach -> + @AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, null, null) + @res.json = sinon.stub() + @req.i18n = {translate: sinon.stub()} + + it 'should send back a failure message', -> + @SudoModeController.submitPassword(@req, @res, @next) + @res.json.callCount.should.equal 1 + expect(@res.json.lastCall.args[0]).to.have.keys ['message'] + expect(@res.json.lastCall.args[0].message).to.have.keys ['text', 'type'] + @req.i18n.translate.callCount.should.equal 1 + @req.i18n.translate.calledWith('invalid_password') + + it 'should get the user from storage', -> + @SudoModeController.submitPassword(@req, @res, @next) + @UserGetter.getUser.callCount.should.equal 1 + @UserGetter.getUser.calledWith('some_object_id', {email: 1}).should.equal true + + it 'should try to authenticate the user with the password', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationManager.authenticate.callCount.should.equal 1 + @AuthenticationManager.authenticate.calledWith({email: @user.email}, @password).should.equal true + + it 'should not activate sudo mode', -> + @SudoModeController.submitPassword(@req, @res, @next) + @SudoModeHandler.activateSudoMode.callCount.should.equal 0 + + describe 'when authentication produces an error', -> + beforeEach -> + @AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, new Error('woops')) + @next = sinon.stub() + + it 'should return next with an error', -> + @SudoModeController.submitPassword(@req, @res, @next) + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + + it 'should get the user from storage', -> + @SudoModeController.submitPassword(@req, @res, @next) + @UserGetter.getUser.callCount.should.equal 1 + @UserGetter.getUser.calledWith('some_object_id', {email: 1}).should.equal true + + it 'should try to authenticate the user with the password', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationManager.authenticate.callCount.should.equal 1 + @AuthenticationManager.authenticate.calledWith({email: @user.email}, @password).should.equal true + + it 'should not activate sudo mode', -> + @SudoModeController.submitPassword(@req, @res, @next) + @SudoModeHandler.activateSudoMode.callCount.should.equal 0 + + describe 'when sudo mode activation produces an error', -> + beforeEach -> + @SudoModeHandler.activateSudoMode = sinon.stub().callsArgWith(1, new Error('woops')) + @next = sinon.stub() + + it 'should return next with an error', -> + @SudoModeController.submitPassword(@req, @res, @next) + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + + it 'should get the user from storage', -> + @SudoModeController.submitPassword(@req, @res, @next) + @UserGetter.getUser.callCount.should.equal 1 + @UserGetter.getUser.calledWith('some_object_id', {email: 1}).should.equal true + + it 'should try to authenticate the user with the password', -> + @SudoModeController.submitPassword(@req, @res, @next) + @AuthenticationManager.authenticate.callCount.should.equal 1 + @AuthenticationManager.authenticate.calledWith({email: @user.email}, @password).should.equal true + + it 'should have tried to activate sudo mode', -> + @SudoModeController.submitPassword(@req, @res, @next) + @SudoModeHandler.activateSudoMode.callCount.should.equal 1 + @SudoModeHandler.activateSudoMode.calledWith(@user._id).should.equal true diff --git a/services/web/test/UnitTests/coffee/SudoMode/SudoModeHandlerTests.coffee b/services/web/test/UnitTests/coffee/SudoMode/SudoModeHandlerTests.coffee new file mode 100644 index 0000000000..e7f3ccd150 --- /dev/null +++ b/services/web/test/UnitTests/coffee/SudoMode/SudoModeHandlerTests.coffee @@ -0,0 +1,188 @@ +SandboxedModule = require('sandboxed-module') +assert = require('assert') +require('chai').should() +expect = require('chai').expect +sinon = require('sinon') +modulePath = require('path').join __dirname, '../../../../app/js/Features/SudoMode/SudoModeHandler' + + +describe 'SudoModeHandler', -> + beforeEach -> + @userId = 'some_user_id' + @rclient = {get: sinon.stub(), set: sinon.stub(), del: sinon.stub()} + @RedisWrapper = + client: () => @rclient + @SudoModeHandler = SandboxedModule.require modulePath, requires: + '../../infrastructure/RedisWrapper': @RedisWrapper + 'logger-sharelatex': @logger = {log: sinon.stub(), err: sinon.stub()} + + describe '_buildKey', -> + + it 'should build a properly formed key', -> + expect(@SudoModeHandler._buildKey('123')).to.equal 'SudoMode:{123}' + + describe 'activateSudoMode', -> + beforeEach -> + @call = (cb) => + @SudoModeHandler.activateSudoMode @userId, cb + + describe 'when all goes well', -> + beforeEach -> + @rclient.set = sinon.stub().callsArgWith(4, null) + + + it 'should not produce an error', (done) -> + @call (err) => + expect(err).to.equal null + done() + + it 'should set a value in redis', (done) -> + @call (err) => + expect(@rclient.set.callCount).to.equal 1 + expect(@rclient.set.calledWith( + 'SudoMode:{some_user_id}', '1', 'EX', 60*60 + )).to.equal true + done() + + describe 'when user id is not supplied', -> + beforeEach -> + @call = (cb) => + @SudoModeHandler.activateSudoMode null, cb + + it 'should produce an error', (done) -> + @call (err) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() + + it 'should not set value in redis', (done) -> + @call (err) => + expect(@rclient.set.callCount).to.equal 0 + done() + + describe 'when rclient.set produces an error', -> + beforeEach -> + @rclient.set = sinon.stub().callsArgWith(4, new Error('woops')) + + it 'should produce an error', (done) -> + @call (err) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() + + describe 'clearSudoMode', -> + beforeEach -> + @rclient.del = sinon.stub().callsArgWith(1, null) + @call = (cb) => + @SudoModeHandler.clearSudoMode @userId, cb + + it 'should not produce an error', (done) -> + @call (err) => + expect(err).to.equal null + done() + + it 'should delete key from redis', (done) -> + @call (err) => + expect(@rclient.del.callCount).to.equal 1 + expect(@rclient.del.calledWith( + 'SudoMode:{some_user_id}' + )).to.equal true + done() + + describe 'when rclient.del produces an error', -> + beforeEach -> + @rclient.del = sinon.stub().callsArgWith(1, new Error('woops')) + + it 'should produce an error', (done) -> + @call (err) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() + + describe 'when user id is not supplied', -> + beforeEach -> + @call = (cb) => + @SudoModeHandler.clearSudoMode null, cb + + it 'should produce an error', (done) -> + @call (err) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() + + it 'should not delete value in redis', (done) -> + @call (err) => + expect(@rclient.del.callCount).to.equal 0 + done() + + describe 'isSudoModeActive', -> + beforeEach -> + @call = (cb) => + @SudoModeHandler.isSudoModeActive @userId, cb + + describe 'when sudo-mode is active for that user', -> + beforeEach -> + @rclient.get = sinon.stub().callsArgWith(1, null, '1') + + it 'should not produce an error', (done) -> + @call (err, isActive) => + expect(err).to.equal null + done() + + it 'should get the value from redis', (done) -> + @call (err, isActive) => + expect(@rclient.get.callCount).to.equal 1 + expect(@rclient.get.calledWith('SudoMode:{some_user_id}')).to.equal true + done() + + it 'should produce a true result', (done) -> + @call (err, isActive) => + expect(isActive).to.equal true + done() + + describe 'when sudo-mode is not active for that user', -> + beforeEach -> + @rclient.get = sinon.stub().callsArgWith(1, null, null) + + it 'should not produce an error', (done) -> + @call (err, isActive) => + expect(err).to.equal null + done() + + it 'should get the value from redis', (done) -> + @call (err, isActive) => + expect(@rclient.get.callCount).to.equal 1 + expect(@rclient.get.calledWith('SudoMode:{some_user_id}')).to.equal true + done() + + it 'should produce a false result', (done) -> + @call (err, isActive) => + expect(isActive).to.equal false + done() + + describe 'when rclient.get produces an error', -> + beforeEach -> + @rclient.get = sinon.stub().callsArgWith(1, new Error('woops')) + + it 'should produce an error', (done) -> + @call (err, isActive) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + expect(isActive).to.be.oneOf [null, undefined] + done() + + describe 'when user id is not supplied', -> + beforeEach -> + @call = (cb) => + @SudoModeHandler.isSudoModeActive null, cb + + it 'should produce an error', (done) -> + @call (err) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() + + it 'should not get value in redis', (done) -> + @call (err) => + expect(@rclient.get.callCount).to.equal 0 + done() diff --git a/services/web/test/UnitTests/coffee/SudoMode/SudoModeMiddlewearTests.coffee b/services/web/test/UnitTests/coffee/SudoMode/SudoModeMiddlewearTests.coffee new file mode 100644 index 0000000000..00e8d0ae57 --- /dev/null +++ b/services/web/test/UnitTests/coffee/SudoMode/SudoModeMiddlewearTests.coffee @@ -0,0 +1,123 @@ +SandboxedModule = require('sandboxed-module') +assert = require('assert') +require('chai').should() +expect = require('chai').expect +sinon = require('sinon') +modulePath = require('path').join __dirname, '../../../../app/js/Features/SudoMode/SudoModeMiddlewear' + + +describe 'SudoModeMiddlewear', -> + beforeEach -> + @userId = 'some_user_id' + @SudoModeHandler = + isSudoModeActive: sinon.stub() + @AuthenticationController = + getLoggedInUserId: sinon.stub().returns(@userId) + _setRedirectInSession: sinon.stub() + @SudoModeMiddlewear = SandboxedModule.require modulePath, requires: + './SudoModeHandler': @SudoModeHandler + '../Authentication/AuthenticationController': @AuthenticationController + 'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()} + + describe 'protectPage', -> + beforeEach -> + @externalAuth = false + @call = (cb) => + @req = {externalAuthenticationSystemUsed: sinon.stub().returns(@externalAuth)} + @res = {redirect: sinon.stub()} + @next = sinon.stub() + @SudoModeMiddlewear.protectPage @req, @res, @next + cb() + + describe 'when sudo mode is active', -> + beforeEach -> + @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId) + @SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, null, true) + + it 'should get the current user id', (done) -> + @call () => + @AuthenticationController.getLoggedInUserId.callCount.should.equal 1 + done() + + it 'should check if sudo-mode is active', (done) -> + @call () => + @SudoModeHandler.isSudoModeActive.callCount.should.equal 1 + @SudoModeHandler.isSudoModeActive.calledWith(@userId).should.equal true + done() + + it 'should call next', (done) -> + @call () => + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.equal undefined + done() + + describe 'when sudo mode is not active', -> + beforeEach -> + @AuthenticationController._setRedirectInSession = sinon.stub() + @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId) + @SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, null, false) + + it 'should get the current user id', (done) -> + @call () => + @AuthenticationController.getLoggedInUserId.callCount.should.equal 1 + done() + + it 'should check if sudo-mode is active', (done) -> + @call () => + @SudoModeHandler.isSudoModeActive.callCount.should.equal 1 + @SudoModeHandler.isSudoModeActive.calledWith(@userId).should.equal true + done() + + it 'should set redirect in session', (done) -> + @call () => + @AuthenticationController._setRedirectInSession.callCount.should.equal 1 + @AuthenticationController._setRedirectInSession.calledWith(@req).should.equal true + done() + + it 'should redirect to the password-prompt page', (done) -> + @call () => + @res.redirect.callCount.should.equal 1 + @res.redirect.calledWith('/confirm-password').should.equal true + done() + + describe 'when isSudoModeActive produces an error', -> + beforeEach -> + @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId) + @SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, new Error('woops')) + + it 'should get the current user id', (done) -> + @call () => + @AuthenticationController.getLoggedInUserId.callCount.should.equal 1 + done() + + it 'should check if sudo-mode is active', (done) -> + @call () => + @SudoModeHandler.isSudoModeActive.callCount.should.equal 1 + @SudoModeHandler.isSudoModeActive.calledWith(@userId).should.equal true + done() + + it 'should call next with an error', (done) -> + @call () => + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + done() + + describe 'when external auth is being used', -> + beforeEach -> + @externalAuth = true + + it 'should immediately return next with no args', (done) -> + @call () => + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.not.exist + done() + + it 'should not get the current user id', (done) -> + @call () => + @AuthenticationController.getLoggedInUserId.callCount.should.equal 0 + done() + + it 'should not check if sudo-mode is active', (done) -> + @call () => + @SudoModeHandler.isSudoModeActive.callCount.should.equal 0 + done() diff --git a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee index ecb33495c4..a71fe4bb91 100644 --- a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee @@ -60,6 +60,8 @@ describe "UserController", -> trackSession: sinon.stub() untrackSession: sinon.stub() revokeAllUserSessions: sinon.stub().callsArgWith(2, null) + @SudoModeHandler = + clearSudoMode: sinon.stub() @UserController = SandboxedModule.require modulePath, requires: "./UserLocator": @UserLocator "./UserDeleter": @UserDeleter @@ -73,6 +75,7 @@ describe "UserController", -> "../Subscription/SubscriptionDomainHandler":@SubscriptionDomainHandler "./UserHandler":@UserHandler "./UserSessionsManager": @UserSessionsManager + "../SudoMode/SudoModeHandler": @SudoModeHandler "settings-sharelatex": @settings "logger-sharelatex": log:-> @@ -302,6 +305,17 @@ describe "UserController", -> @UserController.logout @req, @res + it 'should clear sudo-mode', (done) -> + @req.session.destroy = sinon.stub().callsArgWith(0) + @SudoModeHandler.clearSudoMode = sinon.stub() + @res.redirect = (url)=> + url.should.equal "/login" + @SudoModeHandler.clearSudoMode.callCount.should.equal 1 + @SudoModeHandler.clearSudoMode.calledWith(@user._id).should.equal true + done() + + @UserController.logout @req, @res + describe "register", -> beforeEach -> diff --git a/services/web/test/acceptance/coffee/SessionTests.coffee b/services/web/test/acceptance/coffee/SessionTests.coffee index 56783b5b85..07a431a229 100644 --- a/services/web/test/acceptance/coffee/SessionTests.coffee +++ b/services/web/test/acceptance/coffee/SessionTests.coffee @@ -34,9 +34,9 @@ describe "Sessions", -> expect(sessions[0].slice(0, 5)).to.equal 'sess:' next() - # should be able to access settings page + # should be able to access project list page , (next) => - @user1.getUserSettingsPage (err, statusCode) => + @user1.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 200 next() @@ -94,15 +94,15 @@ describe "Sessions", -> expect(sessions[1].slice(0, 5)).to.equal 'sess:' next() - # both should be able to access settings page + # both should be able to access project list page , (next) => - @user1.getUserSettingsPage (err, statusCode) => + @user1.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 200 next() , (next) => - @user2.getUserSettingsPage (err, statusCode) => + @user2.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 200 next() @@ -117,16 +117,16 @@ describe "Sessions", -> expect(sessions.length).to.equal 1 next() - # first session should not have access to settings page + # first session should not have access to project list page , (next) => - @user1.getUserSettingsPage (err, statusCode) => + @user1.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 302 next() # second session should still have access to settings , (next) => - @user2.getUserSettingsPage (err, statusCode) => + @user2.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 200 next() @@ -141,9 +141,9 @@ describe "Sessions", -> expect(sessions.length).to.equal 0 next() - # second session should not have access to settings page + # second session should not have access to project list page , (next) => - @user2.getUserSettingsPage (err, statusCode) => + @user2.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 302 next() @@ -216,22 +216,22 @@ describe "Sessions", -> expect(sessions.length).to.equal 1 next() - # users one and three should not be able to access settings page + # users one and three should not be able to access project list page , (next) => - @user1.getUserSettingsPage (err, statusCode) => + @user1.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 302 next() , (next) => - @user3.getUserSettingsPage (err, statusCode) => + @user3.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 302 next() - # user two should still be logged in, and able to access settings page + # user two should still be logged in, and able to access project list page , (next) => - @user2.getUserSettingsPage (err, statusCode) => + @user2.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 200 next() @@ -305,6 +305,19 @@ describe "Sessions", -> expect(sessions[1].slice(0, 5)).to.equal 'sess:' next() + # enter sudo-mode + , (next) => + @user2.getCsrfToken (err) => + expect(err).to.be.oneOf [null, undefined] + @user2.request.post { + uri: '/confirm-password', + json: + password: @user2.password + }, (err, response, body) => + expect(err).to.be.oneOf [null, undefined] + expect(response.statusCode).to.equal 200 + next() + # check the sessions page , (next) => @user2.request.get { @@ -328,22 +341,22 @@ describe "Sessions", -> expect(sessions.length).to.equal 1 next() - # users one and three should not be able to access settings page + # users one and three should not be able to access project list page , (next) => - @user1.getUserSettingsPage (err, statusCode) => + @user1.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 302 next() , (next) => - @user3.getUserSettingsPage (err, statusCode) => + @user3.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 302 next() - # user two should still be logged in, and able to access settings page + # user two should still be logged in, and able to access project list page , (next) => - @user2.getUserSettingsPage (err, statusCode) => + @user2.getProjectListPage (err, statusCode) => expect(err).to.equal null expect(statusCode).to.equal 200 next() diff --git a/services/web/test/acceptance/coffee/helpers/User.coffee b/services/web/test/acceptance/coffee/helpers/User.coffee index eecde65322..da03cb9917 100644 --- a/services/web/test/acceptance/coffee/helpers/User.coffee +++ b/services/web/test/acceptance/coffee/helpers/User.coffee @@ -134,6 +134,15 @@ class User return callback(error) if error? callback(null, response.statusCode) + getProjectListPage: (callback=(error, statusCode)->) -> + @getCsrfToken (error) => + return callback(error) if error? + @request.get { + url: "/project" + }, (error, response, body) => + return callback(error) if error? + callback(null, response.statusCode) + module.exports = User