Merge branch 'master' into pr-history-labels-part-2

This commit is contained in:
Paulo Reis 2018-08-10 15:57:32 +01:00
commit a5c6f81573
12 changed files with 152 additions and 27 deletions

View file

@ -1,6 +1,5 @@
AuthenticationManager = require ("./AuthenticationManager")
LoginRateLimiter = require("../Security/LoginRateLimiter")
UserGetter = require "../User/UserGetter"
UserUpdater = require "../User/UserUpdater"
Metrics = require('metrics-sharelatex')
logger = require("logger-sharelatex")
@ -64,7 +63,10 @@ module.exports = AuthenticationController =
if user # `user` is either a user object or false
AuthenticationController.finishLogin(user, req, res, next)
else
res.json message: info
if info.redir?
res.json {redir: info.redir}
else
res.json message: info
)(req, res, next)
finishLogin: (user, req, res, next) ->
@ -81,20 +83,30 @@ module.exports = AuthenticationController =
doPassportLogin: (req, username, password, done) ->
email = username.toLowerCase()
LoginRateLimiter.processLoginRequest email, (err, isAllowed)->
return done(err) if err?
if !isAllowed
logger.log email:email, "too many login requests"
return done(null, null, {text: req.i18n.translate("to_many_login_requests_2_mins"), type: 'error'})
AuthenticationManager.authenticate email: email, password, (error, user) ->
return done(error) if error?
if user?
# async actions
return done(null, user)
else
AuthenticationController._recordFailedLogin()
logger.log email: email, "failed log in"
return done(null, false, {text: req.i18n.translate("email_or_password_wrong_try_again"), type: 'error'})
Modules = require "../../infrastructure/Modules"
Modules.hooks.fire 'preDoPassportLogin', email, (err, infoList) ->
return next(err) if err?
info = infoList.find((i) => i?)
if info?
return done(null, false, info)
LoginRateLimiter.processLoginRequest email, (err, isAllowed)->
return done(err) if err?
if !isAllowed
logger.log email:email, "too many login requests"
return done(null, null, {text: req.i18n.translate("to_many_login_requests_2_mins"), type: 'error'})
AuthenticationManager.authenticate email: email, password, (error, user) ->
return done(error) if error?
if user?
# async actions
return done(null, user)
else
AuthenticationController._recordFailedLogin()
logger.log email: email, "failed log in"
return done(
null,
false,
{text: req.i18n.translate("email_or_password_wrong_try_again"), type: 'error'}
)
_loginAsyncHandlers: (req, user) ->
UserHandler.setupLoginData(user, ()->)

View file

@ -347,7 +347,6 @@ module.exports = ProjectController =
useV2History: !!project.overleaf?.history?.display
richTextEnabled: Features.hasFeature('rich-text')
showTestControls: req.query?.tc == 'true' || user.isAdmin
showPublishModal: req.query?.pm == 'true'
timer.done()
_buildProjectList: (allProjects, v1Projects = [])->

View file

@ -55,7 +55,8 @@ module.exports = ProjectCreationHandler =
if Settings.apis?.project_history?.displayHistoryForNewProjects
project.overleaf.history.display = true
if Settings.currentImageName?
project.imageName = Settings.currentImageName
# avoid clobbering any imageName already set in attributes (e.g. importedImageName)
project.imageName ?= Settings.currentImageName
project.rootFolder[0] = rootFolder
User.findById owner_id, "ace.spellCheckLanguage", (err, user)->
project.spellCheckLanguage = user.ace.spellCheckLanguage

View file

@ -68,7 +68,7 @@ Modules.loadViewIncludes app
app.use bodyParser.urlencoded({ extended: true, limit: "2mb"})
# Make sure we can process the max doc length plus some overhead for JSON encoding
app.use bodyParser.json({limit: Settings.max_doc_length + 16 * 1024}) # 16kb overhead
app.use bodyParser.json({limit: Settings.max_doc_length + 64 * 1024}) # 64kb overhead
app.use multer(dest: Settings.path.uploadFolder)
app.use methodOverride()

View file

@ -93,7 +93,7 @@ script(type="text/ng-template", id="historyLabelTooltipTpl")
i.fa.fa-tag
|  {{ $ctrl.labelText }}
p.history-label-tooltip-owner #{translate("history_label_created_by")} {{ $ctrl.labelOwnerName }}
time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }}
time.history-label-tooltip-datetime {{ $ctrl.labelCreationDateTime | formatDate }}
script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate")

View file

@ -129,7 +129,6 @@ script(type="text/ng-template", id="historyEntriesListTpl")
users="$ctrl.users"
on-select="$ctrl.onEntrySelect({ selectedEntry: selectedEntry })"
on-label-delete="$ctrl.onLabelDelete({ label: label })"
ng-show="!$ctrl.isLoading"
)
.loading(ng-show="$ctrl.isLoading")
i.fa.fa-spin.fa-refresh

View file

@ -162,7 +162,7 @@ define [
cursorPosition = @editor.getCursorPosition()
end = change.end
{lineUpToCursor, commandFragment} = Helpers.getContext(@editor, end)
if lineUpToCursor.match(/.*%.*/)
if (i = lineUpToCursor.indexOf('%') > -1 and lineUpToCursor[i-1] != '\\')
return
lastCharIsBackslash = lineUpToCursor.slice(-1) == "\\"
lastTwoChars = lineUpToCursor.slice(-2)

View file

@ -6,13 +6,17 @@ define [
App.controller "HistoryListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) ->
$scope.hoveringOverListSelectors = false
projectUsers = $scope.project.members.concat $scope.project.owner
$scope.projectUsers = []
$scope.$watch "project.members", (newVal) ->
if newVal?
$scope.projectUsers = newVal.concat $scope.project.owner
# This method (and maybe the one below) will be removed soon. User details data will be
# injected into the history API responses, so we won't need to fetch user data from other
# local data structures.
_getUserById = (id) ->
_.find projectUsers, (user) ->
_.find $scope.projectUsers, (user) ->
curUserId = user?._id or user?.id
curUserId == id

View file

@ -7,8 +7,13 @@ define [
$scope.hoveringOverListSelectors = false
$scope.listConfig =
showOnlyLabelled: false
$scope.projectUsers = $scope.project.members.concat $scope.project.owner
$scope.projectUsers = []
$scope.$watch "project.members", (newVal) ->
if newVal?
$scope.projectUsers = newVal.concat $scope.project.owner
$scope.loadMore = () =>
ide.historyManager.fetchNextBatchOfUpdates()

View file

@ -0,0 +1,85 @@
const mongojs = require('../app/js/infrastructure/mongojs')
const { db } = mongojs
const async = require('async')
const minilist = require('minimist')
const newTimeout = 240
const oldTimeoutLimits = {$gt: 60, $lt: 240}
const updateUser = function (user, callback) {
console.log(`Updating user ${user._id}`)
const update = {
$set: {
'features.compileTimeout': newTimeout
}
}
db.users.update({
_id: user._id,
'features.compileTimeout': oldTimeoutLimits
}, update, callback)
}
const updateUsers = (users, callback) =>
async.eachLimit(users, ASYNC_LIMIT, updateUser, function (error) {
if (error) {
callback(error)
return
}
counter += users.length
console.log(`${counter} users updated`)
if (DO_ALL) {
return loopForUsers(callback)
} else {
console.log('*** run again to continue updating ***')
return callback()
}
})
var loopForUsers = callback =>
db.users.find(
{ 'features.compileTimeout': oldTimeoutLimits },
{ 'features.compileTimeout': 1 }
).limit(FETCH_LIMIT, function (error, users) {
if (error) {
callback(error)
return
}
if (users.length === 0) {
console.log(`DONE (${counter} users updated)`)
return callback()
}
updateUsers(users, callback)
})
var counter = 0
var run = () =>
loopForUsers(function (error) {
if (error) { throw error }
process.exit()
})
let FETCH_LIMIT, ASYNC_LIMIT, DO_ALL
var setup = function () {
let args = minilist(process.argv.slice(2))
// --fetch N get N users each time
FETCH_LIMIT = (args.fetch) ? args.fetch : 100
// --async M run M updates in parallel
ASYNC_LIMIT = (args.async) ? args.async : 10
// --all means run to completion
if (args.all) {
if (args.fetch) {
console.error('error: do not use --fetch with --all')
process.exit(1)
} else {
DO_ALL = true
// if we are updating for all users then ignore the fetch limit.
FETCH_LIMIT = 0
// A limit() value of 0 (i.e. .limit(0)) is equivalent to setting
// no limit.
// https://docs.mongodb.com/manual/reference/method/cursor.limit
}
}
}
setup()
run()

View file

@ -15,7 +15,6 @@ describe "AuthenticationController", ->
tk.freeze(Date.now())
@AuthenticationController = SandboxedModule.require modulePath, requires:
"./AuthenticationManager": @AuthenticationManager = {}
"../User/UserGetter" : @UserGetter = {}
"../User/UserUpdater" : @UserUpdater = {}
"metrics-sharelatex": @Metrics = { inc: sinon.stub() }
"../Security/LoginRateLimiter": @LoginRateLimiter = { processLoginRequest:sinon.stub(), recordSuccessfulLogin:sinon.stub() }
@ -29,6 +28,7 @@ describe "AuthenticationController", ->
trackSession: sinon.stub()
untrackSession: sinon.stub()
revokeAllUserSessions: sinon.stub().callsArgWith(1, null)
"../../infrastructure/Modules": @Modules = {hooks: {fire: sinon.stub().callsArgWith(2, null, [])}}
@user =
_id: ObjectId()
email: @email = "USER@example.com"
@ -214,6 +214,7 @@ describe "AuthenticationController", ->
beforeEach ->
@AuthenticationController._recordFailedLogin = sinon.stub()
@AuthenticationController._recordSuccessfulLogin = sinon.stub()
@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, [])
# @AuthenticationController.establishUserSession = sinon.stub().callsArg(2)
@req.body =
email: @email
@ -222,6 +223,17 @@ describe "AuthenticationController", ->
postLoginRedirect: "/path/to/redir/to"
@cb = sinon.stub()
describe "when the preDoPassportLogin hooks produce an info object", ->
beforeEach ->
@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, [null, {redir: '/somewhere'}, null])
it "should stop early and call done with this info object", (done) ->
@AuthenticationController.doPassportLogin(@req, @req.body.email, @req.body.password, @cb)
@cb.callCount.should.equal 1
@cb.calledWith(null, false, {redir: '/somewhere'}).should.equal true
@LoginRateLimiter.processLoginRequest.callCount.should.equal 0
done()
describe "when the users rate limit", ->
beforeEach ->

View file

@ -111,7 +111,7 @@ describe 'ProjectCreationHandler', ->
project.spellCheckLanguage.should.equal "de"
done()
it "should set the imageName to currentImageName if set", (done) ->
it "should set the imageName to currentImageName if set and no imageName attribute", (done) ->
@Settings.currentImageName = "mock-image-name"
@handler.createBlankProject ownerId, projectName, (err, project)=>
project.imageName.should.equal @Settings.currentImageName
@ -123,6 +123,14 @@ describe 'ProjectCreationHandler', ->
expect(project.imageName).to.not.exist
done()
it "should set the imageName to the attribute value if set and not overwrite it with the currentImageName", (done) ->
@Settings.currentImageName = "mock-image-name"
attributes =
imageName: "attribute-image-name"
@handler.createBlankProject ownerId, projectName, attributes, (err, project)=>
project.imageName.should.equal attributes.imageName
done()
it "should not set the overleaf.history.display if not configured in settings", (done) ->
@Settings.apis.project_history.displayHistoryForNewProjects = false
@handler.createBlankProject ownerId, projectName, (err, project)=>