mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-08 07:52:06 +00:00
Merge branch 'master' into pr-bulk-actions
This commit is contained in:
commit
e532f543a3
20 changed files with 931 additions and 63 deletions
|
@ -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) ->
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
@ -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')
|
|
@ -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')
|
|
@ -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) ->)->
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
45
services/web/app/views/sudo_mode/sudo_mode_prompt.pug
Normal file
45
services/web/app/views/sudo_mode/sudo_mode_prompt.pug
Normal file
|
@ -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')}
|
|
@ -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)
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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", ->
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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 ->
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue