Merge branch 'master' into sk-pdf-error-display

This commit is contained in:
Shane Kilkelly 2016-03-22 10:56:59 +00:00
commit 45077cfba9
68 changed files with 1891 additions and 1141 deletions

View file

@ -40,6 +40,7 @@ app.js
app/js/*
test/UnitTests/js/*
test/smoke/js/*
test/acceptance/js/*
cookies.txt
requestQueueWorker.js
TpdsWorker.js

View file

@ -71,6 +71,14 @@ module.exports = (grunt) ->
dest: 'test/UnitTests/js/',
ext: '.js'
acceptance_tests:
expand: true,
flatten: false,
cwd: 'test/acceptance/coffee',
src: ['**/*.coffee'],
dest: 'test/acceptance/js/',
ext: '.js'
less:
app:
files:
@ -119,6 +127,7 @@ module.exports = (grunt) ->
clean:
app: ["app/js"]
unit_tests: ["test/UnitTests/js"]
acceptance_tests: ["test/acceptance/js"]
mochaTest:
unit:
@ -131,6 +140,12 @@ module.exports = (grunt) ->
options:
reporter: grunt.option('reporter') or 'spec'
grep: grunt.option("grep")
acceptance:
src: ["test/acceptance/js/#{grunt.option('feature') or '**'}/*.js"]
options:
timeout: 10000
reporter: grunt.option('reporter') or 'spec'
grep: grunt.option("grep")
"git-rev-parse":
version:
@ -184,6 +199,7 @@ module.exports = (grunt) ->
]
"Test tasks": [
"test:unit"
"test:acceptance"
]
"Run tasks": [
"run"
@ -290,6 +306,7 @@ module.exports = (grunt) ->
grunt.registerTask 'compile:css', 'Compile the less files to css', ['less']
grunt.registerTask 'compile:minify', 'Concat and minify the client side js', ['requirejs', "file_append"]
grunt.registerTask 'compile:unit_tests', 'Compile the unit tests', ['clean:unit_tests', 'coffee:unit_tests']
grunt.registerTask 'compile:acceptance_tests', 'Compile the acceptance tests', ['clean:acceptance_tests', 'coffee:acceptance_tests']
grunt.registerTask 'compile:smoke_tests', 'Compile the smoke tests', ['coffee:smoke_tests']
grunt.registerTask 'compile:tests', 'Compile all the tests', ['compile:smoke_tests', 'compile:unit_tests']
grunt.registerTask 'compile', 'Compiles everything need to run web-sharelatex', ['compile:server', 'compile:client', 'compile:css']
@ -297,6 +314,7 @@ module.exports = (grunt) ->
grunt.registerTask 'install', "Compile everything when installing as an npm module", ['compile']
grunt.registerTask 'test:unit', 'Run the unit tests (use --grep=<regex> or --feature=<feature> for individual tests)', ['compile:server', 'compile:modules:server', 'compile:unit_tests', 'compile:modules:unit_tests', 'mochaTest:unit'].concat(moduleUnitTestTasks)
grunt.registerTask 'test:acceptance', 'Run the acceptance tests (use --grep=<regex> or --feature=<feature> for individual tests)', ['compile:acceptance_tests', 'mochaTest:acceptance']
grunt.registerTask 'test:smoke', 'Run the smoke tests', ['compile:smoke_tests', 'mochaTest:smoke']
grunt.registerTask 'test:modules:unit', 'Run the unit tests for the modules', ['compile:modules:server', 'compile:modules:unit_tests'].concat(moduleUnitTestTasks)

View file

@ -18,18 +18,6 @@ argv = require("optimist")
.usage("Usage: $0")
.argv
Server.app.use (error, req, res, next) ->
if error?.code is 'EBADCSRFTOKEN'
logger.log err: error,url:req.url, method:req.method, user:req?.sesson?.user, "invalid csrf"
res.sendStatus(403)
return
logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear"
res.statusCode = error.status or 500
if res.statusCode == 500
res.end("Oops, something went wrong with your request, sorry. If this continues, please contact us at #{Settings.adminEmail}")
else
res.end()
if Settings.catchErrors
process.removeAllListeners "uncaughtException"
process.on "uncaughtException", (error) ->

View file

@ -44,50 +44,27 @@ module.exports = AuthenticationController =
text: req.i18n.translate("email_or_password_wrong_try_again"),
type: 'error'
getAuthToken: (req, res, next = (error) ->) ->
AuthenticationController.getLoggedInUserId req, (error, user_id) ->
return next(error) if error?
AuthenticationManager.getAuthToken user_id, (error, auth_token) ->
return next(error) if error?
res.send(auth_token)
getLoggedInUserId: (req, callback = (error, user_id) ->) ->
if req?.session?.user?._id?
callback null, req.session.user._id.toString()
else
callback null, null
getLoggedInUser: (req, options = {allow_auth_token: false}, callback = (error, user) ->) ->
if typeof(options) == "function"
callback = options
options = {allow_auth_token: false}
getLoggedInUser: (req, callback = (error, user) ->) ->
if req.session?.user?._id?
query = req.session.user._id
else if req.query?.auth_token? and options.allow_auth_token
query = { auth_token: req.query.auth_token }
else
return callback null, null
UserGetter.getUser query, callback
requireLogin: (options = {allow_auth_token: false, load_from_db: false}) ->
requireLogin: () ->
doRequest = (req, res, next = (error) ->) ->
load_from_db = options.load_from_db
if req.query?.auth_token? and options.allow_auth_token
load_from_db = true
if load_from_db
AuthenticationController.getLoggedInUser req, { allow_auth_token: options.allow_auth_token }, (error, user) ->
return next(error) if error?
return AuthenticationController._redirectToLoginOrRegisterPage(req, res) if !user?
req.user = user
return next()
if !req.session.user?
AuthenticationController._redirectToLoginOrRegisterPage(req, res)
else
if !req.session.user?
AuthenticationController._redirectToLoginOrRegisterPage(req, res)
else
req.user = req.session.user
return next()
req.user = req.session.user
return next()
return doRequest

View file

@ -35,19 +35,3 @@ module.exports = AuthenticationManager =
$unset: password: true
}, callback)
getAuthToken: (user_id, callback = (error, auth_token) ->) ->
db.users.findOne { _id: ObjectId(user_id.toString()) }, { auth_token : true }, (error, user) =>
return callback(error) if error?
return callback(new Error("user could not be found: #{user_id}")) if !user?
if user.auth_token?
callback null, user.auth_token
else
@_createSecureToken (error, auth_token) ->
db.users.update { _id: ObjectId(user_id.toString()) }, { $set : auth_token: auth_token }, (error) ->
return callback(error) if error?
callback null, auth_token
_createSecureToken: (callback = (error, token) ->) ->
crypto.randomBytes 48, (error, buffer) ->
return callback(error) if error?
callback null, buffer.toString("hex")

View file

@ -0,0 +1,73 @@
CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler")
Project = require("../../models/Project").Project
User = require("../../models/User").User
PrivilegeLevels = require("./PrivilegeLevels")
PublicAccessLevels = require("./PublicAccessLevels")
Errors = require("../Errors/Errors")
module.exports = AuthorizationManager =
# Get the privilege level that the user has for the project
# Returns:
# * privilegeLevel: "owner", "readAndWrite", of "readOnly" if the user has
# access. false if the user does not have access
# * becausePublic: true if the access level is only because the project is public.
getPrivilegeLevelForProject: (user_id, project_id, callback = (error, privilegeLevel, becausePublic) ->) ->
getPublicAccessLevel = () ->
Project.findOne { _id: project_id }, { publicAccesLevel: 1 }, (error, project) ->
return callback(error) if error?
if !project?
return callback new Errors.NotFoundError("no project found with id #{project_id}")
if project.publicAccesLevel == PublicAccessLevels.READ_ONLY
return callback null, PrivilegeLevels.READ_ONLY, true
else if project.publicAccesLevel == PublicAccessLevels.READ_AND_WRITE
return callback null, PrivilegeLevels.READ_AND_WRITE, true
else
return callback null, PrivilegeLevels.NONE, false
if !user_id?
getPublicAccessLevel()
else
CollaboratorsHandler.getMemberIdPrivilegeLevel user_id, project_id, (error, privilegeLevel) ->
return callback(error) if error?
if privilegeLevel? and privilegeLevel != PrivilegeLevels.NONE
# The user has direct access
callback null, privilegeLevel, false
else
AuthorizationManager.isUserSiteAdmin user_id, (error, isAdmin) ->
return callback(error) if error?
if isAdmin
callback null, PrivilegeLevels.OWNER, false
else
getPublicAccessLevel()
canUserReadProject: (user_id, project_id, callback = (error, canRead) ->) ->
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, (error, privilegeLevel) ->
return callback(error) if error?
return callback null, (privilegeLevel in [PrivilegeLevels.OWNER, PrivilegeLevels.READ_AND_WRITE, PrivilegeLevels.READ_ONLY])
canUserWriteProjectContent: (user_id, project_id, callback = (error, canWriteContent) ->) ->
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, (error, privilegeLevel) ->
return callback(error) if error?
return callback null, (privilegeLevel in [PrivilegeLevels.OWNER, PrivilegeLevels.READ_AND_WRITE])
canUserWriteProjectSettings: (user_id, project_id, callback = (error, canWriteSettings) ->) ->
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, (error, privilegeLevel, becausePublic) ->
return callback(error) if error?
if privilegeLevel == PrivilegeLevels.OWNER
return callback null, true
else if privilegeLevel == PrivilegeLevels.READ_AND_WRITE and !becausePublic
return callback null, true
else
return callback null, false
canUserAdminProject: (user_id, project_id, callback = (error, canAdmin) ->) ->
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, (error, privilegeLevel) ->
return callback(error) if error?
return callback null, (privilegeLevel == PrivilegeLevels.OWNER)
isUserSiteAdmin: (user_id, callback = (error, isAdmin) ->) ->
if !user_id?
return callback null, false
User.findOne { _id: user_id }, { isAdmin: 1 }, (error, user) ->
return callback(error) if error?
return callback null, (user?.isAdmin == true)

View file

@ -0,0 +1,111 @@
AuthorizationManager = require("./AuthorizationManager")
async = require "async"
logger = require "logger-sharelatex"
ObjectId = require("mongojs").ObjectId
Errors = require "../Errors/Errors"
module.exports = AuthorizationMiddlewear =
ensureUserCanReadMultipleProjects: (req, res, next) ->
project_ids = (req.query.project_ids or "").split(",")
AuthorizationMiddlewear._getUserId req, (error, user_id) ->
return next(error) if error?
# Remove the projects we have access to. Note rejectSeries doesn't use
# errors in callbacks
async.rejectSeries project_ids, (project_id, cb) ->
AuthorizationManager.canUserReadProject user_id, project_id, (error, canRead) ->
return next(error) if error?
cb(canRead)
, (unauthorized_project_ids) ->
if unauthorized_project_ids.length > 0
AuthorizationMiddlewear.redirectToRestricted req, res, next
else
next()
ensureUserCanReadProject: (req, res, next) ->
AuthorizationMiddlewear._getUserAndProjectId req, (error, user_id, project_id) ->
return next(error) if error?
AuthorizationManager.canUserReadProject user_id, project_id, (error, canRead) ->
return next(error) if error?
if canRead
logger.log {user_id, project_id}, "allowing user read access to project"
next()
else
logger.log {user_id, project_id}, "denying user read access to project"
AuthorizationMiddlewear.redirectToRestricted req, res, next
ensureUserCanWriteProjectSettings: (req, res, next) ->
AuthorizationMiddlewear._getUserAndProjectId req, (error, user_id, project_id) ->
return next(error) if error?
AuthorizationManager.canUserWriteProjectSettings user_id, project_id, (error, canWrite) ->
return next(error) if error?
if canWrite
logger.log {user_id, project_id}, "allowing user write access to project settings"
next()
else
logger.log {user_id, project_id}, "denying user write access to project settings"
AuthorizationMiddlewear.redirectToRestricted req, res, next
ensureUserCanWriteProjectContent: (req, res, next) ->
AuthorizationMiddlewear._getUserAndProjectId req, (error, user_id, project_id) ->
return next(error) if error?
AuthorizationManager.canUserWriteProjectContent user_id, project_id, (error, canWrite) ->
return next(error) if error?
if canWrite
logger.log {user_id, project_id}, "allowing user write access to project content"
next()
else
logger.log {user_id, project_id}, "denying user write access to project settings"
AuthorizationMiddlewear.redirectToRestricted req, res, next
ensureUserCanAdminProject: (req, res, next) ->
AuthorizationMiddlewear._getUserAndProjectId req, (error, user_id, project_id) ->
return next(error) if error?
AuthorizationManager.canUserAdminProject user_id, project_id, (error, canAdmin) ->
return next(error) if error?
if canAdmin
logger.log {user_id, project_id}, "allowing user admin access to project"
next()
else
logger.log {user_id, project_id}, "denying user admin access to project"
AuthorizationMiddlewear.redirectToRestricted req, res, next
ensureUserIsSiteAdmin: (req, res, next) ->
AuthorizationMiddlewear._getUserId req, (error, user_id) ->
return next(error) if error?
AuthorizationManager.isUserSiteAdmin user_id, (error, isAdmin) ->
return next(error) if error?
if isAdmin
logger.log {user_id}, "allowing user admin access to site"
next()
else
logger.log {user_id}, "denying user admin access to site"
AuthorizationMiddlewear.redirectToRestricted req, res, next
_getUserAndProjectId: (req, callback = (error, user_id, project_id) ->) ->
project_id = req.params?.project_id or req.params?.Project_id
if !project_id?
return callback(new Error("Expected project_id in request parameters"))
if !ObjectId.isValid(project_id)
return callback(new Errors.NotFoundError("invalid project_id: #{project_id}"))
AuthorizationMiddlewear._getUserId req, (error, user_id) ->
return callback(error) if error?
callback(null, user_id, project_id)
_getUserId: (req, callback = (error, user_id) ->) ->
if req.session?.user?._id?
user_id = req.session.user._id
else
user_id = null
callback null, user_id
redirectToRestricted: (req, res, next) ->
res.redirect "/restricted"
restricted : (req, res, next)->
if req.session.user?
res.render 'user/restricted',
title:'restricted'
else
logger.log "user not logged in and trying to access #{req.url}, being redirected to login"
res.redirect '/register'

View file

@ -0,0 +1,5 @@
module.exports =
NONE: false
READ_ONLY: "readOnly"
READ_AND_WRITE: "readAndWrite"
OWNER: "owner"

View file

@ -0,0 +1,4 @@
module.exports =
READ_ONLY: "readOnly"
READ_AND_WRITE: "readAndWrite"
PRIVATE: "private"

View file

@ -7,15 +7,6 @@ UserGetter = require "../User/UserGetter"
mimelib = require("mimelib")
module.exports = CollaboratorsController =
getCollaborators: (req, res, next = (error) ->) ->
ProjectGetter.getProject req.params.Project_id, { owner_ref: true, collaberator_refs: true, readOnly_refs: true}, (error, project) ->
return next(error) if error?
ProjectGetter.populateProjectWithUsers project, (error, project) ->
return next(error) if error?
CollaboratorsController._formatCollaborators project, (error, collaborators) ->
return next(error) if error?
res.send(JSON.stringify(collaborators))
addUserToProject: (req, res, next) ->
project_id = req.params.Project_id
LimitationsManager.canAddXCollaborators project_id, 1, (error, allowed) =>
@ -59,29 +50,3 @@ module.exports = CollaboratorsController =
EditorRealTimeController.emitToRoom(project_id, 'userRemovedFromProject', user_id)
callback()
_formatCollaborators: (project, callback = (error, collaborators) ->) ->
collaborators = []
pushCollaborator = (user, permissions, owner) ->
collaborators.push {
id: user._id.toString()
first_name: user.first_name
last_name: user.last_name
email: user.email
permissions: permissions
owner: owner
}
if project.owner_ref?
pushCollaborator(project.owner_ref, ["read", "write", "admin"], true)
if project.collaberator_refs? and project.collaberator_refs.length > 0
for user in project.collaberator_refs
pushCollaborator(user, ["read", "write"], false)
if project.readOnly_refs? and project.readOnly_refs.length > 0
for user in project.readOnly_refs
pushCollaborator(user, ["read"], false)
callback null, collaborators

View file

@ -1,13 +1,77 @@
UserCreator = require('../User/UserCreator')
Project = require("../../models/Project").Project
ProjectEntityHandler = require("../Project/ProjectEntityHandler")
mimelib = require("mimelib")
logger = require('logger-sharelatex')
UserGetter = require "../User/UserGetter"
ContactManager = require "../Contacts/ContactManager"
CollaboratorsEmailHandler = require "./CollaboratorsEmailHandler"
async = require "async"
PrivilegeLevels = require "../Authorization/PrivilegeLevels"
Errors = require "../Errors/Errors"
module.exports = CollaboratorsHandler =
getMemberIdsWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
Project.findOne { _id: project_id }, { owner_ref: 1, collaberator_refs: 1, readOnly_refs: 1 }, (error, project) ->
return callback(error) if error?
return callback new Errors.NotFoundError("no project found with id #{project_id}") if !project?
members = []
members.push { id: project.owner_ref.toString(), privilegeLevel: PrivilegeLevels.OWNER }
for member_id in project.readOnly_refs or []
members.push { id: member_id.toString(), privilegeLevel: PrivilegeLevels.READ_ONLY }
for member_id in project.collaberator_refs or []
members.push { id: member_id.toString(), privilegeLevel: PrivilegeLevels.READ_AND_WRITE }
return callback null, members
getMemberIds: (project_id, callback = (error, member_ids) ->) ->
CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members) ->
return callback(error) if error?
return callback null, members.map (m) -> m.id
getMembersWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
return callback(error) if error?
async.mapLimit members, 3,
(member, cb) ->
UserGetter.getUser member.id, (error, user) ->
return cb(error) if error?
return cb(null, { user: user, privilegeLevel: member.privilegeLevel })
callback
getMemberIdPrivilegeLevel: (user_id, project_id, callback = (error, privilegeLevel) ->) ->
# In future if the schema changes and getting all member ids is more expensive (multiple documents)
# then optimise this.
CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
return callback(error) if error?
for member in members
if member.id == user_id?.toString()
return callback null, member.privilegeLevel
return callback null, PrivilegeLevels.NONE
getMemberCount: (project_id, callback = (error, count) ->) ->
CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members) ->
return callback(error) if error?
return callback null, (members or []).length
getCollaboratorCount: (project_id, callback = (error, count) ->) ->
CollaboratorsHandler.getMemberCount project_id, (error, count) ->
return callback(error) if error?
return callback null, count - 1 # Don't count project owner
isUserMemberOfProject: (user_id, project_id, callback = (error, isMember, privilegeLevel) ->) ->
CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
return callback(error) if error?
for member in members
if member.id.toString() == user_id.toString()
return callback null, true, member.privilegeLevel
return callback null, false, null
getProjectsUserIsCollaboratorOf: (user_id, fields, callback = (error, readAndWriteProjects, readOnlyProjects) ->) ->
Project.find {collaberator_refs:user_id}, fields, (err, readAndWriteProjects)=>
return callback(err) if err?
Project.find {readOnly_refs:user_id}, fields, (err, readOnlyProjects)=>
return callback(err) if err?
callback(null, readAndWriteProjects, readOnlyProjects)
removeUserFromProject: (project_id, user_id, callback = (error) ->)->
logger.log user_id: user_id, project_id: project_id, "removing user"
conditions = _id:project_id
@ -38,10 +102,10 @@ module.exports = CollaboratorsHandler =
if existing_users.indexOf(user_id.toString()) > -1
return callback null # User already in Project
if privilegeLevel == 'readAndWrite'
if privilegeLevel == PrivilegeLevels.READ_AND_WRITE
level = {"collaberator_refs":user_id}
logger.log {privileges: "readAndWrite", user_id, project_id}, "adding user"
else if privilegeLevel == 'readOnly'
else if privilegeLevel == PrivilegeLevels.READ_ONLY
level = {"readOnly_refs":user_id}
logger.log {privileges: "readOnly", user_id, project_id}, "adding user"
else
@ -57,6 +121,7 @@ module.exports = CollaboratorsHandler =
Project.update { _id: project_id }, { $addToSet: level }, (error) ->
return callback(error) if error?
# Flush to TPDS in background to add files to collaborator's Dropbox
ProjectEntityHandler = require("../Project/ProjectEntityHandler")
ProjectEntityHandler.flushProjectToThirdPartyDataStore project_id, (error) ->
if error?
logger.error {err: error, project_id, user_id}, "error flushing to TPDS after adding collaborator"

View file

@ -1,11 +1,10 @@
CollaboratorsController = require('./CollaboratorsController')
SecurityManager = require('../../managers/SecurityManager')
AuthenticationController = require('../Authentication/AuthenticationController')
AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
module.exports =
apply: (webRouter, apiRouter) ->
webRouter.post '/project/:Project_id/leave', AuthenticationController.requireLogin(), CollaboratorsController.removeSelfFromProject
apiRouter.get '/project/:Project_id/collaborators', SecurityManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators
webRouter.post '/project/:Project_id/users', SecurityManager.requestIsOwner, CollaboratorsController.addUserToProject
webRouter.delete '/project/:Project_id/users/:user_id', SecurityManager.requestIsOwner, CollaboratorsController.removeUserFromProject
webRouter.post '/project/:Project_id/users', AuthorizationMiddlewear.ensureUserCanAdminProject, CollaboratorsController.addUserToProject
webRouter.delete '/project/:Project_id/users/:user_id', AuthorizationMiddlewear.ensureUserCanAdminProject, CollaboratorsController.removeUserFromProject

View file

@ -86,6 +86,9 @@ module.exports = ClsiManager =
rootResourcePathOverride = path
rootResourcePath = rootResourcePathOverride if rootResourcePathOverride?
if !rootResourcePath?
logger.warn {project_id}, "no root document found, setting to main.tex"
rootResourcePath = "main.tex"
for path, file of files
path = path.replace(/^\//, "") # Remove leading /
@ -94,19 +97,16 @@ module.exports = ClsiManager =
url: "#{Settings.apis.filestore.url}/project/#{project._id}/file/#{file._id}"
modified: file.created?.getTime()
if !rootResourcePath?
callback new Error("no root document exists")
else
callback null, {
compile:
options:
compiler: project.compiler
timeout: options.timeout
imageName: project.imageName
draft: !!options.draft
rootResourcePath: rootResourcePath
resources: resources
}
callback null, {
compile:
options:
compiler: project.compiler
timeout: options.timeout
imageName: project.imageName
draft: !!options.draft
rootResourcePath: rootResourcePath
resources: resources
}
wordCount: (project_id, file, options, callback = (error, response) ->) ->
ClsiManager._buildRequest project_id, options, (error, req) ->

View file

@ -26,7 +26,8 @@ module.exports = CompileManager =
CompileManager._checkIfRecentlyCompiled project_id, user_id, (error, recentlyCompiled) ->
return callback(error) if error?
if recentlyCompiled
return callback new Error("project was recently compiled so not continuing")
logger.warn {project_id, user_id}, "project was recently compiled so not continuing"
return callback null, "too-recently-compiled", []
CompileManager._ensureRootDocumentIsSet project_id, (error) ->
return callback(error) if error?

View file

@ -5,14 +5,18 @@ EditorRealTimeController = require "./EditorRealTimeController"
EditorController = require "./EditorController"
ProjectGetter = require('../Project/ProjectGetter')
UserGetter = require('../User/UserGetter')
AuthorizationManager = require("../Security/AuthorizationManager")
AuthorizationManager = require("../Authorization/AuthorizationManager")
ProjectEditorHandler = require('../Project/ProjectEditorHandler')
Metrics = require('../../infrastructure/Metrics')
CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler")
PrivilegeLevels = require "../Authorization/PrivilegeLevels"
module.exports = EditorHttpController =
joinProject: (req, res, next) ->
project_id = req.params.Project_id
user_id = req.query.user_id
if user_id == "anonymous-user"
user_id = null
logger.log {user_id, project_id}, "join project request"
Metrics.inc "editor.join-project"
EditorHttpController._buildJoinProjectView project_id, user_id, (error, project, privilegeLevel) ->
@ -29,17 +33,17 @@ module.exports = EditorHttpController =
ProjectGetter.getProjectWithoutDocLines project_id, (error, project) ->
return callback(error) if error?
return callback(new Error("not found")) if !project?
ProjectGetter.populateProjectWithUsers project, (error, project) ->
CollaboratorsHandler.getMembersWithPrivilegeLevels project, (error, members) ->
return callback(error) if error?
UserGetter.getUser user_id, { isAdmin: true }, (error, user) ->
return callback(error) if error?
AuthorizationManager.getPrivilegeLevelForProject project, user, (error, canAccess, privilegeLevel) ->
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, (error, privilegeLevel) ->
return callback(error) if error?
if !canAccess
if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
callback null, null, false
else
callback(null,
ProjectEditorHandler.buildProjectModelView(project),
ProjectEditorHandler.buildProjectModelView(project, members),
privilegeLevel
)

View file

@ -1,20 +1,20 @@
EditorHttpController = require('./EditorHttpController')
SecurityManager = require('../../managers/SecurityManager')
AuthenticationController = require "../Authentication/AuthenticationController"
AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
module.exports =
apply: (webRouter, apiRouter) ->
webRouter.post '/project/:Project_id/doc', SecurityManager.requestCanModifyProject, EditorHttpController.addDoc
webRouter.post '/project/:Project_id/folder', SecurityManager.requestCanModifyProject, EditorHttpController.addFolder
webRouter.post '/project/:Project_id/doc', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.addDoc
webRouter.post '/project/:Project_id/folder', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.addFolder
webRouter.post '/project/:Project_id/:entity_type/:entity_id/rename', SecurityManager.requestCanModifyProject, EditorHttpController.renameEntity
webRouter.post '/project/:Project_id/:entity_type/:entity_id/move', SecurityManager.requestCanModifyProject, EditorHttpController.moveEntity
webRouter.post '/project/:Project_id/:entity_type/:entity_id/rename', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.renameEntity
webRouter.post '/project/:Project_id/:entity_type/:entity_id/move', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.moveEntity
webRouter.delete '/project/:Project_id/file/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteFile
webRouter.delete '/project/:Project_id/doc/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteDoc
webRouter.delete '/project/:Project_id/folder/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteFolder
webRouter.delete '/project/:Project_id/file/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteFile
webRouter.delete '/project/:Project_id/doc/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteDoc
webRouter.delete '/project/:Project_id/folder/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteFolder
webRouter.post '/project/:Project_id/doc/:doc_id/restore', SecurityManager.requestCanModifyProject, EditorHttpController.restoreDoc
webRouter.post '/project/:Project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.restoreDoc
# Called by the real-time API to load up the current project state.
# This is a post request because it's more than just a getting of data. We take actions

View file

@ -1,5 +1,25 @@
Errors = require "./Errors"
logger = require "logger-sharelatex"
module.exports = ErrorController =
notFound: (req, res)->
res.statusCode = 404
res.status(404)
res.render 'general/404',
title: "page_not_found"
title: "page_not_found"
serverError: (req, res)->
res.status(500)
res.render 'general/500',
title: "Server Error"
handleError: (error, req, res, next) ->
if error?.code is 'EBADCSRFTOKEN'
logger.log err: error,url:req.url, method:req.method, user:req?.sesson?.user, "invalid csrf"
res.sendStatus(403)
return
if error instanceof Errors.NotFoundError
logger.warn {err: error, url: req.url}, "not found error"
ErrorController.notFound req, res
else
logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear"
ErrorController.serverError req, res

View file

@ -0,0 +1,9 @@
NotFoundError = (message) ->
error = new Error(message)
error.name = "NotFoundError"
error.__proto__ = NotFoundError.prototype
return error
NotFoundError.prototype.__proto__ = Error.prototype
module.exports = Errors =
NotFoundError: NotFoundError

View file

@ -5,7 +5,6 @@ projectDuplicator = require("./ProjectDuplicator")
projectCreationHandler = require("./ProjectCreationHandler")
editorController = require("../Editor/EditorController")
metrics = require('../../infrastructure/Metrics')
Project = require('../../models/Project').Project
User = require('../../models/User').User
TagsHandler = require("../Tags/TagsHandler")
SubscriptionLocator = require("../Subscription/SubscriptionLocator")
@ -13,10 +12,12 @@ NotificationsHandler = require("../Notifications/NotificationsHandler")
LimitationsManager = require("../Subscription/LimitationsManager")
_ = require("underscore")
Settings = require("settings-sharelatex")
SecurityManager = require("../../managers/SecurityManager")
AuthorizationManager = require("../Authorization/AuthorizationManager")
fs = require "fs"
InactiveProjectManager = require("../InactiveData/InactiveProjectManager")
ProjectUpdateHandler = require("./ProjectUpdateHandler")
ProjectGetter = require("./ProjectGetter")
PrivilegeLevels = require("../Authorization/PrivilegeLevels")
module.exports = ProjectController =
@ -41,6 +42,14 @@ module.exports = ProjectController =
jobs.push (callback) ->
editorController.setRootDoc project_id, req.body.rootDocId, callback
async.series jobs, (error) ->
return next(error) if error?
res.sendStatus(204)
updateProjectAdminSettings: (req, res, next) ->
project_id = req.params.Project_id
jobs = []
if req.body.publicAccessLevel?
jobs.push (callback) ->
editorController.setPublicAccessLevel project_id, req.body.publicAccessLevel, callback
@ -129,7 +138,7 @@ module.exports = ProjectController =
notifications: (cb)->
NotificationsHandler.getUserNotifications user_id, cb
projects: (cb)->
Project.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb
ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb
hasSubscription: (cb)->
LimitationsManager.userHasSubscriptionOrIsGroupMember req.session.user, cb
user: (cb) ->
@ -179,23 +188,23 @@ module.exports = ProjectController =
anonymous = false
else
anonymous = true
user_id = 'openUser'
user_id = null
project_id = req.params.Project_id
logger.log project_id:project_id, "loading editor"
async.parallel {
project: (cb)->
Project.findPopulatedById project_id, cb
ProjectGetter.getProject project_id, { name: 1, lastUpdated: 1}, cb
user: (cb)->
if user_id == 'openUser'
if !user_id?
cb null, defaultSettingsForAnonymousUser(user_id)
else
User.findById user_id, (err, user)->
logger.log project_id:project_id, user_id:user_id, "got user"
cb err, user
subscription: (cb)->
if user_id == 'openUser'
if !user_id?
return cb()
SubscriptionLocator.getUsersSubscription user_id, cb
activate: (cb)->
@ -216,8 +225,9 @@ module.exports = ProjectController =
daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000
logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor"
SecurityManager.userCanAccessProject user, project, (canAccess, privilegeLevel)->
if !canAccess
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, (error, privilegeLevel)->
return next(error) if error?
if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
return res.sendStatus 401
if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt?
@ -228,7 +238,6 @@ module.exports = ProjectController =
title: project.name
priority_title: true
bodyClasses: ["editor"]
project : project
project_id : project._id
user : {
id : user.id

View file

@ -5,6 +5,7 @@ documentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
tagsHandler = require("../Tags/TagsHandler")
async = require("async")
FileStoreHandler = require("../FileStore/FileStoreHandler")
CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler")
module.exports = ProjectDeleter =
@ -44,16 +45,10 @@ module.exports = ProjectDeleter =
(cb)->
documentUpdaterHandler.flushProjectToMongoAndDelete project_id, cb
(cb)->
tagsHandler.removeProjectFromAllTags project.owner_ref, project_id, (err)->
CollaboratorsHandler.getMemberIds project_id, (error, member_ids = []) ->
for member_id in member_ids
tagsHandler.removeProjectFromAllTags member_id, project_id, (err)->
cb() #doesn't matter if this fails or the order it happens in
(cb)->
project.collaberator_refs.forEach (collaberator_ref)->
tagsHandler.removeProjectFromAllTags collaberator_ref, project_id, ->
cb()
(cb)->
project.readOnly_refs.forEach (readOnly_ref)->
tagsHandler.removeProjectFromAllTags readOnly_ref, project_id, ->
cb()
(cb)->
Project.update {_id:project_id}, { $set: { archived: true }}, cb
], (err)->

View file

@ -4,6 +4,7 @@ Project = require('../../models/Project').Project
logger = require("logger-sharelatex")
tpdsUpdateSender = require '../ThirdPartyDataStore/TpdsUpdateSender'
_ = require("underscore")
PublicAccessLevels = require("../Authorization/PublicAccessLevels")
module.exports =
@ -49,6 +50,6 @@ module.exports =
setPublicAccessLevel : (project_id, newAccessLevel, callback = ->)->
logger.log project_id: project_id, level: newAccessLevel, "set public access level"
if project_id? && newAccessLevel? and _.include ['readOnly', 'readAndWrite', 'private'], newAccessLevel
if project_id? && newAccessLevel? and _.include [PublicAccessLevels.READ_ONLY, PublicAccessLevels.READ_AND_WRITE, PublicAccessLevels.PRIVATE], newAccessLevel
Project.update {_id:project_id},{publicAccesLevel:newAccessLevel}, (err)->
callback()

View file

@ -1,11 +1,7 @@
_ = require("underscore")
module.exports = ProjectEditorHandler =
buildProjectModelView: (project, options) ->
options ||= {}
if !options.includeUsers?
options.includeUsers = true
buildProjectModelView: (project, members) ->
result =
_id : project._id
name : project.name
@ -18,40 +14,27 @@ module.exports = ProjectEditorHandler =
spellCheckLanguage: project.spellCheckLanguage
deletedByExternalDataSource : project.deletedByExternalDataSource || false
deletedDocs: project.deletedDocs
members: []
owner = null
for member in members
if member.privilegeLevel == "owner"
owner = member.user
else
result.members.push @buildUserModelView member.user, member.privilegeLevel
if owner?
result.owner = @buildUserModelView owner, "owner"
if options.includeUsers
result.features =
collaborators: -1 # Infinite
versioning: false
dropbox:false
compileTimeout: 60
compileGroup:"standard"
templates: false
references: false
result.features = _.defaults(owner?.features or {}, {
collaborators: -1 # Infinite
versioning: false
dropbox:false
compileTimeout: 60
compileGroup:"standard"
templates: false
references: false
})
if project.owner_ref.features?
if project.owner_ref.features.collaborators?
result.features.collaborators = project.owner_ref.features.collaborators
if project.owner_ref.features.versioning?
result.features.versioning = project.owner_ref.features.versioning
if project.owner_ref.features.dropbox?
result.features.dropbox = project.owner_ref.features.dropbox
if project.owner_ref.features.compileTimeout?
result.features.compileTimeout = project.owner_ref.features.compileTimeout
if project.owner_ref.features.compileGroup?
result.features.compileGroup = project.owner_ref.features.compileGroup
if project.owner_ref.features.templates?
result.features.templates = project.owner_ref.features.templates
if project.owner_ref.features.references?
result.features.references = project.owner_ref.features.references
result.owner = @buildUserModelView project.owner_ref, "owner"
result.members = []
for ref in project.readOnly_refs
result.members.push @buildUserModelView ref, "readOnly"
for ref in project.collaberator_refs
result.members.push @buildUserModelView ref, "readAndWrite"
return result
buildUserModelView: (user, privileges) ->

View file

@ -2,11 +2,10 @@ mongojs = require("../../infrastructure/mongojs")
db = mongojs.db
ObjectId = mongojs.ObjectId
async = require "async"
Project = require("../../models/Project").Project
Errors = require("../../errors")
logger = require("logger-sharelatex")
module.exports = ProjectGetter =
EXCLUDE_DEPTH: 8
@ -51,42 +50,11 @@ module.exports = ProjectGetter =
return callback(err)
callback(null, project?[0])
populateProjectWithUsers: (project, callback=(error, project) ->) ->
# eventually this should be in a UserGetter.getUser module
getUser = (user_id, callback=(error, user) ->) ->
unless user_id instanceof ObjectId
user_id = ObjectId(user_id)
db.users.find _id: user_id, (error, users = []) ->
callback error, users[0]
jobs = []
jobs.push (callback) ->
getUser project.owner_ref, (error, user) ->
findAllUsersProjects: (user_id, fields, callback = (error, ownedProjects, readAndWriteProjects, readOnlyProjects) ->) ->
CollaboratorsHandler = require "../Collaborators/CollaboratorsHandler"
Project.find {owner_ref: user_id}, fields, (error, projects) ->
return callback(error) if error?
CollaboratorsHandler.getProjectsUserIsCollaboratorOf user_id, fields, (error, readAndWriteProjects, readOnlyProjects) ->
return callback(error) if error?
if user?
project.owner_ref = user
callback null, project
readOnly_refs = project.readOnly_refs
project.readOnly_refs = []
for readOnly_ref in readOnly_refs
do (readOnly_ref) ->
jobs.push (callback) ->
getUser readOnly_ref, (error, user) ->
return callback(error) if error?
if user?
project.readOnly_refs.push user
callback null, project
collaberator_refs = project.collaberator_refs
project.collaberator_refs = []
for collaberator_ref in collaberator_refs
do (collaberator_ref) ->
jobs.push (callback) ->
getUser collaberator_ref, (error, user) ->
return callback(error) if error?
if user?
project.collaberator_refs.push user
callback null, project
async.parallelLimit jobs, 3, (error) -> callback error, project
callback null, projects, readAndWriteProjects, readOnlyProjects

View file

@ -4,6 +4,7 @@ Errors = require "../../errors"
_ = require('underscore')
logger = require('logger-sharelatex')
async = require('async')
ProjectGetter = require "./ProjectGetter"
module.exports = ProjectLocator =
findElement: (options, _callback = (err, element, path, parentFolder)->)->
@ -131,7 +132,8 @@ module.exports = ProjectLocator =
async.waterfall jobs, callback
findUsersProjectByName: (user_id, projectName, callback)->
Project.findAllUsersProjects user_id, 'name archived', (err, projects, collabertions=[])->
ProjectGetter.findAllUsersProjects user_id, 'name archived', (err, projects, collabertions=[])->
return callback(error) if error?
projects = projects.concat(collabertions)
projectName = projectName.toLowerCase()
project = _.find projects, (project)->

View file

@ -1,7 +1,8 @@
logger = require("logger-sharelatex")
request = require("request")
settings = require("settings-sharelatex")
Project = require("../../models/Project").Project
ProjectGetter = require "../Project/ProjectGetter"
UserGetter = require "../User/UserGetter"
DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
U = require('underscore')
Async = require('async')
@ -31,11 +32,12 @@ module.exports = ReferencesHandler =
return ids
_isFullIndex: (project, callback = (err, result) ->) ->
owner = project.owner_ref
callback(null, owner.features.references == true)
UserGetter.getUser project.owner_ref, { features: true }, (err, owner) ->
return callback(err) if err?
callback(null, owner?.features?.references == true)
indexAll: (projectId, callback=(err, data)->) ->
Project.findPopulatedById projectId, (err, project) ->
ProjectGetter.getProject projectId, {rootFolder: true, owner_ref: 1}, (err, project) ->
if err
logger.err {err, projectId}, "error finding project"
return callback(err)
@ -44,7 +46,7 @@ module.exports = ReferencesHandler =
ReferencesHandler._doIndexOperation(projectId, project, docIds, callback)
index: (projectId, docIds, callback=(err, data)->) ->
Project.findPopulatedById projectId, (err, project) ->
ProjectGetter.getProject projectId, {rootFolder: true, owner_ref: 1}, (err, project) ->
if err
logger.err {err, projectId}, "error finding project"
return callback(err)

View file

@ -1,38 +0,0 @@
SecurityManager = require '../../managers/SecurityManager'
module.exports = AuthorizationManager =
getPrivilegeLevelForProject: (
project, user,
callback = (error, canAccess, privilegeLevel)->
) ->
# This is not tested because eventually this function should be brought into
# this module.
SecurityManager.userCanAccessProject user, project, (canAccess, privilegeLevel) ->
if canAccess
callback null, true, privilegeLevel
else
callback null, false
setPrivilegeLevelOnClient: (client, privilegeLevel) ->
client.set("privilege_level", privilegeLevel)
ensureClientCanViewProject: (client, callback = (error, project_id)->) ->
@ensureClientHasPrivilegeLevelForProject client, ["owner", "readAndWrite", "readOnly"], callback
ensureClientCanEditProject: (client, callback = (error, project_id)->) ->
@ensureClientHasPrivilegeLevelForProject client, ["owner", "readAndWrite"], callback
ensureClientCanAdminProject: (client, callback = (error, project_id)->) ->
@ensureClientHasPrivilegeLevelForProject client, ["owner"], callback
ensureClientHasPrivilegeLevelForProject: (client, levels, callback = (error, project_id)->) ->
client.get "privilege_level", (error, level) ->
return callback(error) if error?
if level?
client.get "project_id", (error, project_id) ->
return callback(error) if error?
if project_id?
if levels.indexOf(level) > -1
callback null, project_id

View file

@ -3,6 +3,7 @@ Project = require("../../models/Project").Project
User = require("../../models/User").User
SubscriptionLocator = require("./SubscriptionLocator")
Settings = require("settings-sharelatex")
CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler")
module.exports =
@ -13,16 +14,11 @@ module.exports =
callback null, owner.features.collaborators
else
callback null, Settings.defaultPlanCode.collaborators
currentNumberOfCollaboratorsInProject: (project_id, callback) ->
Project.findById project_id, 'collaberator_refs readOnly_refs', (error, project) ->
return callback(error) if error?
callback null, (project.collaberator_refs.length + project.readOnly_refs.length)
canAddXCollaborators: (project_id, x_collaborators, callback = (error, allowed)->) ->
@allowedNumberOfCollaboratorsInProject project_id, (error, allowed_number) =>
return callback(error) if error?
@currentNumberOfCollaboratorsInProject project_id, (error, current_number) =>
CollaboratorsHandler.getCollaboratorCount project_id, (error, current_number) =>
return callback(error) if error?
if current_number + x_collaborators <= allowed_number or allowed_number < 0
callback null, true

View file

@ -1,4 +1,4 @@
SecurityManager = require '../../managers/SecurityManager'
AuthenticationController = require '../Authentication/AuthenticationController'
SubscriptionHandler = require './SubscriptionHandler'
PlansLocator = require("./PlansLocator")
SubscriptionViewModelBuilder = require('./SubscriptionViewModelBuilder')
@ -31,7 +31,7 @@ module.exports = SubscriptionController =
#get to show the recurly.js page
paymentPage: (req, res, next) ->
SecurityManager.getCurrentUser req, (error, user) =>
AuthenticationController.getLoggedInUser req, (error, user) =>
return next(error) if error?
plan = PlansLocator.findLocalPlanInSettings(req.query.planCode)
LimitationsManager.userHasSubscription user, (err, hasSubscription)->
@ -80,7 +80,7 @@ module.exports = SubscriptionController =
userSubscriptionPage: (req, res, next) ->
SecurityManager.getCurrentUser req, (error, user) =>
AuthenticationController.getLoggedInUser req, (error, user) =>
return next(error) if error?
LimitationsManager.userHasSubscriptionOrIsGroupMember user, (err, hasSubOrIsGroupMember, subscription)->
groupLicenceInviteUrl = SubscriptionDomainHandler.getDomainLicencePage(user)
@ -109,7 +109,7 @@ module.exports = SubscriptionController =
userCustomSubscriptionPage: (req, res, next)->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
LimitationsManager.userHasSubscriptionOrIsGroupMember user, (err, hasSubOrIsGroupMember, subscription)->
res.render "subscriptions/custom_account",
title: "your_subscription"
@ -117,7 +117,7 @@ module.exports = SubscriptionController =
editBillingDetailsPage: (req, res, next) ->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
return next(error) if error?
LimitationsManager.userHasSubscription user, (err, hasSubscription)->
if !hasSubscription
@ -138,7 +138,7 @@ module.exports = SubscriptionController =
id : user.id
createSubscription: (req, res, next)->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
return callback(error) if error?
recurly_token_id = req.body.recurly_token_id
subscriptionDetails = req.body.subscriptionDetails
@ -150,14 +150,14 @@ module.exports = SubscriptionController =
res.sendStatus 201
successful_subscription: (req, res)->
SecurityManager.getCurrentUser req, (error, user) =>
AuthenticationController.getLoggedInUser req, (error, user) =>
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel user, (error, subscription) ->
res.render "subscriptions/successful_subscription",
title: "thank_you"
subscription:subscription
cancelSubscription: (req, res, next) ->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
logger.log user_id:user._id, "canceling subscription"
return next(error) if error?
SubscriptionHandler.cancelSubscription user, (err)->
@ -166,7 +166,7 @@ module.exports = SubscriptionController =
res.redirect "/user/subscription"
updateSubscription: (req, res)->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
return next(error) if error?
planCode = req.body.plan_code
logger.log planCode: planCode, user_id:user._id, "updating subscription"
@ -176,7 +176,7 @@ module.exports = SubscriptionController =
res.redirect "/user/subscription"
reactivateSubscription: (req, res)->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
logger.log user_id:user._id, "reactivating subscription"
return next(error) if error?
SubscriptionHandler.reactivateSubscription user, (err)->
@ -195,7 +195,7 @@ module.exports = SubscriptionController =
res.sendStatus 200
renderUpgradeToAnnualPlanPage: (req, res)->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)->
planCode = subscription?.planCode.toLowerCase()
if planCode?.indexOf("annual") != -1
@ -212,7 +212,7 @@ module.exports = SubscriptionController =
planName: planName
processUpgradeToAnnualPlan: (req, res)->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
{planName} = req.body
coupon_code = Settings.coupon_codes.upgradeToAnnualPromo[planName]
annualPlanName = "#{planName}-annual"
@ -225,7 +225,7 @@ module.exports = SubscriptionController =
res.sendStatus 200
extendTrial: (req, res)->
SecurityManager.getCurrentUser req, (error, user) ->
AuthenticationController.getLoggedInUser req, (error, user) ->
LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)->
SubscriptionHandler.extendTrial subscription, 14, (err)->
if err?

View file

@ -5,6 +5,7 @@ Project = require('../../models/Project').Project
keys = require('../../infrastructure/Keys')
metrics = require("../../infrastructure/Metrics")
request = require("request")
CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
buildPath = (user_id, project_name, filePath)->
projectPath = path.join(project_name, "/", filePath)
@ -122,9 +123,11 @@ module.exports = TpdsUpdateSender =
TpdsUpdateSender._enqueue "poll-dropbox:#{user_id}", "standardHttpRequest", options, callback
getProjectsUsersIds = (project_id, callback = (err, owner_id, allUserIds)->)->
Project.findById project_id, "_id owner_ref readOnly_refs collaberator_refs", (err, project)->
allUserIds = [].concat(project.collaberator_refs).concat(project.readOnly_refs).concat(project.owner_ref)
callback err, project.owner_ref, allUserIds
Project.findById project_id, "_id owner_ref", (err, project) ->
return callback(err) if err?
CollaboratorsHandler.getMemberIds project_id, (err, member_ids) ->
return callback(err) if err?
callback err, project?.owner_ref, member_ids
mergeProjectNameAndPath = (project_name, path)->
if(path.indexOf('/') == 0)

View file

@ -25,33 +25,31 @@ module.exports = ArchiveManager =
error += chunk
unzip.on "error", (err) ->
logger.error {err, source, destination}, "unzip failed"
logger.error {err, source}, "unzip failed"
if err.code == "ENOENT"
logger.error "unzip command not found. Please check the unzip command is installed"
callback(err)
unzip.on "exit", () ->
unzip.on "close", (exitCode) ->
if error?
error = new Error(error)
logger.error err:error, source: source, destination: destination, "error checking zip size"
logger.error err:error, source: source, "error checking zip size"
lines = output.split("\n")
lastLine = lines[lines.length - 2]?.trim()
totalSizeInBytes = lastLine?.split(" ")?[0]
totalSizeInBytes = parseInt(totalSizeInBytes)
totalSizeInBytesAsInt = parseInt(totalSizeInBytes)
if !totalSizeInBytes? or isNaN(totalSizeInBytes)
logger.err source:source, "error getting bytes of zip"
return callback(new Error("something went wrong"))
if !totalSizeInBytesAsInt? or isNaN(totalSizeInBytesAsInt)
logger.err source:source, totalSizeInBytes:totalSizeInBytes, totalSizeInBytesAsInt:totalSizeInBytesAsInt, lastLine:lastLine, exitCode:exitCode, "error getting bytes of zip"
return callback(new Error("error getting bytes of zip"))
isTooLarge = totalSizeInBytes > (ONE_MEG * 300)
callback(error, isTooLarge)
extractZipArchive: (source, destination, _callback = (err) ->) ->
callback = (args...) ->
@ -87,7 +85,7 @@ module.exports = ArchiveManager =
logger.error "unzip command not found. Please check the unzip command is installed"
callback(err)
unzip.on "exit", () ->
unzip.on "close", () ->
timer.done()
if error?
error = new Error(error)

View file

@ -1,4 +1,4 @@
SecurityManager = require('../../managers/SecurityManager')
AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
AuthenticationController = require('../Authentication/AuthenticationController')
ProjectUploadController = require "./ProjectUploadController"
RateLimiterMiddlewear = require('../Security/RateLimiterMiddlewear')
@ -16,6 +16,7 @@ module.exports =
maxRequests: 200
timeInterval: 60 * 30
}),
SecurityManager.requestCanModifyProject,
AuthenticationController.requireLogin(),
AuthorizationMiddlewear.ensureUserCanWriteProjectContent,
ProjectUploadController.uploadFile

View file

@ -6,10 +6,6 @@ sanitize = require('sanitizer')
module.exports = UserController =
getLoggedInUsersPersonalInfo: (req, res, next = (error) ->) ->
# this is funcky as hell, we don't use the current session to get the user
# we use the auth token, actually destroying session from the chat api request
if req.query?.auth_token?
req.session?.destroy()
logger.log user: req.user, "reciving request for getting logged in users personal info"
return next(new Error("User is not logged in")) if !req.user?
UserGetter.getUser req.user._id, {

View file

@ -30,6 +30,8 @@ OldAssetProxy = require("./OldAssetProxy")
translations = require("translations-sharelatex").setup(Settings.i18n)
Modules = require "./Modules"
ErrorController = require "../Features/Errors/ErrorController"
metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongojs/node_modules/mongodb"), logger)
metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongoose/node_modules/mongodb"), logger)
@ -136,6 +138,8 @@ app.use(webRouter)
router = new Router(webRouter, apiRouter)
app.use ErrorController.handleError
module.exports =
app: app
server: server

View file

@ -1,194 +0,0 @@
logger = require('logger-sharelatex')
crypto = require 'crypto'
Assert = require 'assert'
Settings = require 'settings-sharelatex'
User = require('../models/User').User
Project = require('../models/Project').Project
ErrorController = require("../Features/Errors/ErrorController")
AuthenticationController = require("../Features/Authentication/AuthenticationController")
_ = require('underscore')
metrics = require('../infrastructure/Metrics')
querystring = require('querystring')
async = require "async"
module.exports = SecurityManager =
restricted : (req, res, next)->
if req.session.user?
res.render 'user/restricted',
title:'restricted'
else
logger.log "user not logged in and trying to access #{req.url}, being redirected to login"
res.redirect '/register'
getCurrentUser: (req, callback) ->
if req.session.user?
User.findById req.session.user._id, callback
else
callback null, null
requestCanAccessMultipleProjects: (req, res, next) ->
project_ids = req.query.project_ids?.split(",")
jobs = []
for project_id in project_ids or []
do (project_id) ->
jobs.push (callback) ->
# This is a bit hacky - better to have an abstracted method
# that we can pass project_id to, but this whole file needs
# a serious refactor ATM.
req.params.Project_id = project_id
SecurityManager.requestCanAccessProject req, res, (error) ->
delete req.params.Project_id
callback(error)
async.series jobs, next
requestCanAccessProject : (req, res, next)->
doRequest = (req, res, next) ->
getRequestUserAndProject req, res, {allow_auth_token: options?.allow_auth_token}, (err, user, project)->
if !project? or project.archived
return ErrorController.notFound(req, res, next)
userCanAccessProject user, project, (canAccess, permissionLevel)->
if canAccess
next()
else if user?
logger.log "user_id: #{user._id} email: #{user.email} trying to access restricted page #{req.path}"
res.redirect('/restricted')
else
logger.log "user not logged in and trying to access #{req.url}, being redirected to login"
AuthenticationController._redirectToLoginOrRegisterPage(req, res)
if arguments.length > 1
options =
allow_auth_token: false
doRequest.apply(this, arguments)
else
options = req
return doRequest
requestCanModifyProject : (req, res, next)->
getRequestUserAndProject req, res, {}, (err, user, project)=>
userCanModifyProject user, project, (canModify)->
if canModify
next()
else
logger.log "user_id: #{user?._id} email: #{user?.email} can not modify project redirecting to restricted page"
res.redirect('/restricted')
userCanModifyProject : userCanModifyProject = (user, project, callback)->
if !user? or !project?
callback false
else if userIsOwner user, project
callback true
else if userIsCollaberator user, project
callback true
else if project.publicAccesLevel == "readAndWrite"
callback true
else if user.isAdmin
callback true
else
callback false
requestIsOwner : (req, res, next)->
getRequestUserAndProject req, res, {}, (err, user, project)->
if !user?
return res.redirect('/restricted')
else if userIsOwner user, project || user.isAdmin
next()
else
logger.log user_id: user?._id, email: user?.email, "user is not owner of project redirecting to restricted page"
res.redirect('/restricted')
requestIsAdmin : isAdmin = (req, res, next)->
logger.log "checking if user is admin"
user = req.session.user
if(user? && user.isAdmin)
logger.log user: user, "User is admin"
next()
else
res.redirect('/restricted')
logger.log user:user, "is not admin redirecting to restricted page"
userCanAccessProject : userCanAccessProject = (user, project, callback)=>
if !user?
user = {_id:'anonymous-user'}
if !project?
callback false
logger.log user:user, project:project, "Checking if can access"
if userIsOwner user, project
callback true, "owner"
else if userIsCollaberator user, project
callback true, "readAndWrite"
else if userIsReadOnly user, project
callback true, "readOnly"
else if user.isAdmin
logger.log user:user, project:project, "user is admin and can access project"
callback true, "owner"
else if project.publicAccesLevel == "readAndWrite"
logger.log user:user, project:project, "project is a public read and write project"
callback true, "readAndWrite"
else if project.publicAccesLevel == "readOnly"
logger.log user:user, project:project, "project is a public read only project"
callback true, "readOnly"
else
metrics.inc "security.denied"
logger.log user:user, project:project, "Security denied - user can not enter project"
callback false
userIsOwner : userIsOwner = (user, project)->
if !user?
return false
else
userId = user._id+''
ownerRef = getProjectIdFromRef(project.owner_ref)
if userId == ownerRef
true
else
false
userIsCollaberator : userIsCollaberator = (user, project)->
if !user?
return false
else
userId = user._id+''
result = false
_.each project.collaberator_refs, (colabRef)->
colabRef = getProjectIdFromRef(colabRef)
if colabRef == userId
result = true
return result
userIsReadOnly : userIsReadOnly = (user, project)->
if !user?
return false
else
userId = user._id+''
result = false
_.each project.readOnly_refs, (readOnlyRef)->
readOnlyRef = getProjectIdFromRef(readOnlyRef)
if readOnlyRef == userId
result = true
return result
getRequestUserAndProject = (req, res, options, callback)->
project_id = req.params.Project_id
if !project_id?
logger.log project_id:project_id, options:options, url:req?.url, "no project_id trying to getRequestUserAndProject"
return res.send 422
Project.findById project_id, 'name owner_ref readOnly_refs collaberator_refs publicAccesLevel archived', (err, project)=>
if err?
logger.err err:err, "error getting project for security check"
return callback err
AuthenticationController.getLoggedInUser req, options, (err, user)=>
if err?
logger.err err:err, "error getting last logged in user for security check"
callback err, user, project
getProjectIdFromRef = (ref)->
if !ref?
return null
else if ref._id?
return ref._id+''
else
return ref+''

View file

@ -43,33 +43,6 @@ ProjectSchema.statics.getProject = (project_or_id, fields, callback)->
return callback(new Errors.NotFoundError(e.message))
this.findById project_or_id, fields, callback
ProjectSchema.statics.findPopulatedById = (project_id, callback)->
logger.log project_id:project_id, "findPopulatedById"
this.find(_id: project_id )
.populate('collaberator_refs')
.populate('readOnly_refs')
.populate('owner_ref')
.exec (err, projects)->
if err?
logger.err err:err, project_id:project_id, "something went wrong looking for project findPopulatedById"
callback(err)
else if !projects? || projects.length == 0
logger.err project_id:project_id, "something went wrong looking for project findPopulatedById, no project could be found"
callback "not found"
else
logger.log project_id:project_id, "finished findPopulatedById"
callback(null, projects[0])
ProjectSchema.statics.findAllUsersProjects = (user_id, requiredFields, callback)->
this.find {owner_ref:user_id}, requiredFields, (err, projects)=>
this.find {collaberator_refs:user_id}, requiredFields, (err, collabertions)=>
this.find {readOnly_refs:user_id}, requiredFields, (err, readOnlyProjects)=>
callback(err, projects, collabertions, readOnlyProjects)
applyToAllFilesRecursivly = ProjectSchema.statics.applyToAllFilesRecursivly = (folder, fun)->
_.each folder.fileRefs, (file)->
fun(file)

View file

@ -0,0 +1,23 @@
mongoose = require 'mongoose'
Settings = require 'settings-sharelatex'
Schema = mongoose.Schema
ObjectId = Schema.ObjectId
ProjectInviteSchema = new Schema
project_id: ObjectId
from_user_id: ObjectId
privilegeLevel: String
# For existing users
to_user_id: ObjectId
# For non-existant users
hashed_token: String
email: String
conn = mongoose.createConnection(Settings.mongo.url, server: poolSize: Settings.mongo.poolSize || 10)
ProjectInvite = conn.model('ProjectInvite', ProjectInviteSchema)
mongoose.model 'ProjectInvite', ProjectInviteSchema
exports.ProjectInvite = ProjectInvite
exports.ProjectInviteSchema = ProjectInviteSchema

View file

@ -55,32 +55,6 @@ UserSchema = new Schema
# has this set to true, despite never having had a free trial
hadFreeTrial: {type: Boolean, default: false}
UserSchema.statics.getAllIds = (callback)->
this.find {}, ["first_name"], callback
UserSchema.statics.findReadOnlyProjects = (user_id, callback)->
@find({'projects.readOnly_refs':user_id}).populate('projects.readOnly_refs').run (err, users)->
projects = []
_.each users, (user)->
_.each user.projects, (project)->
_.each project.readOnly_refs, (subUser)->
if(subUser._id == user_id)
projects.push(project)
callback(projects)
UserSchema.statics.findCollaborationProjects = (user_id, callback)->
@find({'projects.collaberator_refs':user_id}).populate('projects.collaberator_refs').run (err, users)->
projects = []
_.each users, (user)->
_.each user.projects, (project)->
_.each project.collaberator_refs, (subUser)->
if(subUser._id == user_id)
projects.push(project)
callback(projects)
conn = mongoose.createConnection(Settings.mongo.url, server: poolSize: 10)
User = conn.model('User', UserSchema)

View file

@ -3,8 +3,6 @@ ErrorController = require('./Features/Errors/ErrorController')
ProjectController = require("./Features/Project/ProjectController")
ProjectApiController = require("./Features/Project/ProjectApiController")
SpellingController = require('./Features/Spelling/SpellingController')
SecurityManager = require('./managers/SecurityManager')
AuthorizationManager = require('./Features/Security/AuthorizationManager')
EditorController = require("./Features/Editor/EditorController")
EditorRouter = require("./Features/Editor/EditorRouter")
Settings = require('settings-sharelatex')
@ -39,6 +37,7 @@ RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter')
InactiveProjectController = require("./Features/InactiveData/InactiveProjectController")
ContactRouter = require("./Features/Contacts/ContactRouter")
ReferencesController = require('./Features/References/ReferencesController')
AuthorizationMiddlewear = require('./Features/Authorization/AuthorizationMiddlewear')
logger = require("logger-sharelatex")
_ = require("underscore")
@ -54,7 +53,7 @@ module.exports = class Router
webRouter.post '/login', AuthenticationController.login
webRouter.get '/logout', UserController.logout
webRouter.get '/restricted', SecurityManager.restricted
webRouter.get '/restricted', AuthorizationMiddlewear.restricted
# Left as a placeholder for implementing a public register page
webRouter.get '/register', UserPagesController.registerPage
@ -88,8 +87,7 @@ module.exports = class Router
webRouter.delete '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe
webRouter.delete '/user', AuthenticationController.requireLogin(), UserController.deleteUser
webRouter.get '/user/auth_token', AuthenticationController.requireLogin(), AuthenticationController.getAuthToken
webRouter.get '/user/personal_info', AuthenticationController.requireLogin(allow_auth_token: true), UserInfoController.getLoggedInUsersPersonalInfo
webRouter.get '/user/personal_info', AuthenticationController.requireLogin(), UserInfoController.getLoggedInUsersPersonalInfo
apiRouter.get '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo
webRouter.get '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage
@ -100,12 +98,13 @@ module.exports = class Router
params: ["Project_id"]
maxRequests: 10
timeInterval: 60
}), SecurityManager.requestCanAccessProject, ProjectController.loadEditor
webRouter.get '/Project/:Project_id/file/:File_id', SecurityManager.requestCanAccessProject, FileStoreController.getFile
webRouter.post '/project/:Project_id/settings', SecurityManager.requestCanModifyProject, ProjectController.updateProjectSettings
}), AuthorizationMiddlewear.ensureUserCanReadProject, ProjectController.loadEditor
webRouter.get '/Project/:Project_id/file/:File_id', AuthorizationMiddlewear.ensureUserCanReadProject, FileStoreController.getFile
webRouter.post '/project/:Project_id/settings', AuthorizationMiddlewear.ensureUserCanWriteProjectSettings, ProjectController.updateProjectSettings
webRouter.post '/project/:Project_id/settings/admin', AuthorizationMiddlewear.ensureUserCanAdminProject, ProjectController.updateProjectAdminSettings
webRouter.post '/project/:Project_id/compile', SecurityManager.requestCanAccessProject, CompileController.compile
webRouter.get '/Project/:Project_id/output/output.pdf', SecurityManager.requestCanAccessProject, CompileController.downloadPdf
webRouter.post '/project/:Project_id/compile', AuthorizationMiddlewear.ensureUserCanReadProject, CompileController.compile
webRouter.get '/Project/:Project_id/output/output.pdf', AuthorizationMiddlewear.ensureUserCanReadProject, CompileController.downloadPdf
webRouter.get /^\/project\/([^\/]*)\/output\/(.*)$/,
((req, res, next) ->
params =
@ -113,24 +112,24 @@ module.exports = class Router
"file": req.params[1]
req.params = params
next()
), SecurityManager.requestCanAccessProject, CompileController.getFileFromClsi
webRouter.delete "/project/:Project_id/output", SecurityManager.requestCanAccessProject, CompileController.deleteAuxFiles
webRouter.get "/project/:Project_id/sync/code", SecurityManager.requestCanAccessProject, CompileController.proxySync
webRouter.get "/project/:Project_id/sync/pdf", SecurityManager.requestCanAccessProject, CompileController.proxySync
webRouter.get "/project/:Project_id/wordcount", SecurityManager.requestCanAccessProject, CompileController.wordCount
), AuthorizationMiddlewear.ensureUserCanReadProject, CompileController.getFileFromClsi
webRouter.delete "/project/:Project_id/output", AuthorizationMiddlewear.ensureUserCanReadProject, CompileController.deleteAuxFiles
webRouter.get "/project/:Project_id/sync/code", AuthorizationMiddlewear.ensureUserCanReadProject, CompileController.proxySync
webRouter.get "/project/:Project_id/sync/pdf", AuthorizationMiddlewear.ensureUserCanReadProject, CompileController.proxySync
webRouter.get "/project/:Project_id/wordcount", AuthorizationMiddlewear.ensureUserCanReadProject, CompileController.wordCount
webRouter.delete '/Project/:Project_id', SecurityManager.requestIsOwner, ProjectController.deleteProject
webRouter.post '/Project/:Project_id/restore', SecurityManager.requestIsOwner, ProjectController.restoreProject
webRouter.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, ProjectController.cloneProject
webRouter.delete '/Project/:Project_id', AuthorizationMiddlewear.ensureUserCanAdminProject, ProjectController.deleteProject
webRouter.post '/Project/:Project_id/restore', AuthorizationMiddlewear.ensureUserCanAdminProject, ProjectController.restoreProject
webRouter.post '/Project/:Project_id/clone', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectController.cloneProject
webRouter.post '/project/:Project_id/rename', SecurityManager.requestIsOwner, ProjectController.renameProject
webRouter.post '/project/:Project_id/rename', AuthorizationMiddlewear.ensureUserCanAdminProject, ProjectController.renameProject
webRouter.get "/project/:Project_id/updates", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
webRouter.get "/project/:Project_id/doc/:doc_id/diff", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
webRouter.get "/project/:Project_id/updates", AuthorizationMiddlewear.ensureUserCanReadProject, TrackChangesController.proxyToTrackChangesApi
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, TrackChangesController.proxyToTrackChangesApi
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, TrackChangesController.proxyToTrackChangesApi
webRouter.get '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
webRouter.get '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
webRouter.post '/tag', AuthenticationController.requireLogin(), TagsController.createTag
@ -174,26 +173,26 @@ module.exports = class Router
webRouter.post "/spelling/check", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
webRouter.post "/spelling/learn", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
webRouter.get "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.getMessages
webRouter.post "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.sendMessage
webRouter.get "/project/:Project_id/messages", AuthorizationMiddlewear.ensureUserCanReadProject, ChatController.getMessages
webRouter.post "/project/:Project_id/messages", AuthorizationMiddlewear.ensureUserCanReadProject, ChatController.sendMessage
webRouter.get /learn(\/.*)?/, WikiController.getPage
webRouter.post "/project/:Project_id/references/index", SecurityManager.requestCanAccessProject, ReferencesController.index
webRouter.post "/project/:Project_id/references/indexAll", SecurityManager.requestCanAccessProject, ReferencesController.indexAll
webRouter.post "/project/:Project_id/references/index", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.index
webRouter.post "/project/:Project_id/references/indexAll", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.indexAll
#Admin Stuff
webRouter.get '/admin', SecurityManager.requestIsAdmin, AdminController.index
webRouter.get '/admin/user', SecurityManager.requestIsAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon
webRouter.get '/admin/register', SecurityManager.requestIsAdmin, AdminController.registerNewUser
webRouter.post '/admin/register', SecurityManager.requestIsAdmin, UserController.register
webRouter.post '/admin/closeEditor', SecurityManager.requestIsAdmin, AdminController.closeEditor
webRouter.post '/admin/dissconectAllUsers', SecurityManager.requestIsAdmin, AdminController.dissconectAllUsers
webRouter.post '/admin/syncUserToSubscription', SecurityManager.requestIsAdmin, AdminController.syncUserToSubscription
webRouter.post '/admin/flushProjectToTpds', SecurityManager.requestIsAdmin, AdminController.flushProjectToTpds
webRouter.post '/admin/pollDropboxForUser', SecurityManager.requestIsAdmin, AdminController.pollDropboxForUser
webRouter.post '/admin/messages', SecurityManager.requestIsAdmin, AdminController.createMessage
webRouter.post '/admin/messages/clear', SecurityManager.requestIsAdmin, AdminController.clearMessages
webRouter.get '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index
webRouter.get '/admin/user', AuthorizationMiddlewear.ensureUserIsSiteAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon
webRouter.get '/admin/register', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.registerNewUser
webRouter.post '/admin/register', AuthorizationMiddlewear.ensureUserIsSiteAdmin, UserController.register
webRouter.post '/admin/closeEditor', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.closeEditor
webRouter.post '/admin/dissconectAllUsers', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.dissconectAllUsers
webRouter.post '/admin/syncUserToSubscription', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.syncUserToSubscription
webRouter.post '/admin/flushProjectToTpds', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.flushProjectToTpds
webRouter.post '/admin/pollDropboxForUser', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.pollDropboxForUser
webRouter.post '/admin/messages', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.createMessage
webRouter.post '/admin/messages/clear', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.clearMessages
apiRouter.get '/perfTest', (req,res)->
res.send("hello")
@ -205,7 +204,7 @@ module.exports = class Router
webRouter.get '/health_check', HealthCheckController.check
webRouter.get '/health_check/redis', HealthCheckController.checkRedis
apiRouter.get "/status/compiler/:Project_id", SecurityManager.requestCanAccessProject, (req, res) ->
apiRouter.get "/status/compiler/:Project_id", AuthorizationMiddlewear.ensureUserCanReadProject, (req, res) ->
sendRes = _.once (statusCode, message)->
res.writeHead statusCode
res.end message
@ -222,9 +221,9 @@ module.exports = class Router
headers: req.headers
})
apiRouter.get '/oops-express', (req, res, next) -> next(new Error("Test error"))
apiRouter.get '/oops-internal', (req, res, next) -> throw new Error("Test error")
apiRouter.get '/oops-mongo', (req, res, next) ->
webRouter.get '/oops-express', (req, res, next) -> next(new Error("Test error"))
webRouter.get '/oops-internal', (req, res, next) -> throw new Error("Test error")
webRouter.get '/oops-mongo', (req, res, next) ->
require("./models/Project").Project.findOne {}, () ->
throw new Error("Test error")
@ -233,7 +232,7 @@ module.exports = class Router
res.send()
webRouter.post '/error/client', (req, res, next) ->
logger.error err: req.body.error, meta: req.body.meta, "client side error"
logger.warn err: req.body.error, meta: req.body.meta, "client side error"
res.sendStatus(204)
webRouter.get '*', ErrorController.notFound

View file

@ -0,0 +1,21 @@
doctype html
html(itemscope, itemtype='http://schema.org/Product')
head
title Something went wrong
link(rel="icon", href="/favicon.ico")
link(rel='stylesheet', href='/stylesheets/style.css')
link(href="//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css",rel="stylesheet")
body
.content
.container
.row
.col-md-8.col-md-offset-2.text-center
.page-header
h2 Oh dear, something went wrong.
p: img(src="/img/lion-sad-128.png", alt="Sad Lion")
p
| Something went wrong with your request, sorry. Our staff are probably looking into this, but if it continues, please contact us at #{settings.adminEmail}
p
a(href="/")
i.fa.fa-arrow-circle-o-left
| Take me home

View file

@ -259,7 +259,7 @@ module.exports =
# Should we allow access to any page without logging in? This includes
# public projects, /learn, /templates, about pages, etc.
allowPublicAccess: false
allowPublicAccess: if process.env["SHARELATEX_ALLOW_PUBLIC_ACCESS"] == 'true' then true else false
# Maximum size of text documents in the real-time editing system.
max_doc_length: 2 * 1024 * 1024 # 2mb

View file

@ -44,7 +44,8 @@
"redback": "0.4.0",
"redis": "0.10.1",
"redis-sharelatex": "0.0.9",
"request": "2.34.0",
"request": "^2.69.0",
"requests": "^0.1.7",
"rimraf": "2.2.6",
"sanitizer": "0.1.1",
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0",

View file

@ -10,5 +10,10 @@ define [
saveProjectSettings: (data) ->
data._csrf = window.csrfToken
ide.$http.post "/project/#{ide.project_id}/settings", data
saveProjectAdminSettings: (data) ->
data._csrf = window.csrfToken
ide.$http.post "/project/#{ide.project_id}/settings/admin", data
}
]

View file

@ -143,7 +143,7 @@ define [
$scope.makePublic = () ->
$scope.project.publicAccesLevel = $scope.inputs.privileges
settings.saveProjectSettings({publicAccessLevel: $scope.inputs.privileges})
settings.saveProjectAdminSettings({publicAccessLevel: $scope.inputs.privileges})
$modalInstance.close()
$scope.cancel = () ->
@ -153,7 +153,7 @@ define [
App.controller "MakePrivateModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
$scope.makePrivate = () ->
$scope.project.publicAccesLevel = "private"
settings.saveProjectSettings({publicAccessLevel: "private"})
settings.saveProjectAdminSettings({publicAccessLevel: "private"})
$modalInstance.close()
$scope.cancel = () ->

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -173,7 +173,7 @@ describe "AuthenticationController", ->
beforeEach ->
@req.session =
user: @user
@AuthenticationController.getLoggedInUser(@req, {}, @callback)
@AuthenticationController.getLoggedInUser(@req, @callback)
it "should look up the user in the database", ->
@UserGetter.getUser
@ -183,105 +183,37 @@ describe "AuthenticationController", ->
it "should return the user", ->
@callback.calledWith(null, @user).should.equal true
describe "with an auth token, but without auth_token_allowed set to true", ->
beforeEach ->
@req.query =
auth_token: "auth-token"
@AuthenticationController.getLoggedInUser(@req, {}, @callback)
it "should not look up the user in the database", ->
@UserGetter.getUser.called.should.equal false
it "should return null in the callback", ->
@callback.calledWith(null, null).should.equal true
describe "with an auth token and auth_token_allowed set to true", ->
beforeEach ->
@req.query =
auth_token: "auth-token"
@AuthenticationController.getLoggedInUser(@req, {allow_auth_token: true}, @callback)
it "should look up the user in the database", ->
@UserGetter.getUser
.calledWith(auth_token: @req.query.auth_token)
.should.equal true
it "should return the user", ->
@callback.calledWith(null, @user).should.equal true
describe "requireLogin", ->
beforeEach ->
@user =
_id: "user-id-123"
email: "user@sharelatex.com"
@middleware = @AuthenticationController.requireLogin()
describe "when loading from the database", ->
describe "when the user is logged in", ->
beforeEach ->
@middleware = @AuthenticationController.requireLogin(@options = { allow_auth_token: true, load_from_db: true })
describe "when the user is logged in", ->
beforeEach ->
@AuthenticationController.getLoggedInUser = sinon.stub().callsArgWith(2, null, @user)
@middleware(@req, @res, @next)
it "should call getLoggedInUser with the passed options", ->
@AuthenticationController.getLoggedInUser.calledWith(@req, { allow_auth_token: true }).should.equal true
it "should set the user property on the request", ->
@req.user.should.deep.equal @user
it "should call the next method in the chain", ->
@next.called.should.equal true
describe "when the user is not logged in", ->
beforeEach ->
@AuthenticationController._redirectToLoginOrRegisterPage = sinon.stub()
@AuthenticationController.getLoggedInUser = sinon.stub().callsArgWith(2, null, null)
@middleware(@req, @res, @next)
it "should redirect to the register page", ->
@AuthenticationController._redirectToLoginOrRegisterPage.calledWith(@req, @res).should.equal true
describe "when not loading from the database", ->
beforeEach ->
@middleware = @AuthenticationController.requireLogin(@options = { load_from_db: false })
describe "when the user is logged in", ->
beforeEach ->
@req.session =
user: @user = {
_id: "user-id-123"
email: "user@sharelatex.com"
}
@middleware(@req, @res, @next)
it "should set the user property on the request", ->
@req.user.should.deep.equal @user
it "should call the next method in the chain", ->
@next.called.should.equal true
describe "when the user is not logged in", ->
beforeEach ->
@req.session = {}
@AuthenticationController._redirectToLoginOrRegisterPage = sinon.stub()
@req.query = {}
@middleware(@req, @res, @next)
it "should redirect to the register or login page", ->
@AuthenticationController._redirectToLoginOrRegisterPage.calledWith(@req, @res).should.equal true
describe "when not loading from the database but an auth_token is provided", ->
beforeEach ->
@AuthenticationController.getLoggedInUser = sinon.stub().callsArgWith(2, null, @user)
@middleware = @AuthenticationController.requireLogin(@options = { load_from_db: false, allow_auth_token: true })
@req.query = auth_token: @auth_token = "auth-token-provided"
@req.session =
user: @user = {
_id: "user-id-123"
email: "user@sharelatex.com"
}
@middleware(@req, @res, @next)
it "should try to load the user from the database anyway", ->
@AuthenticationController.getLoggedInUser
.calledWith(@req, {allow_auth_token: true})
.should.equal true
it "should set the user property on the request", ->
@req.user.should.deep.equal @user
it "should call the next method in the chain", ->
@next.called.should.equal true
describe "when the user is not logged in", ->
beforeEach ->
@req.session = {}
@AuthenticationController._redirectToLoginOrRegisterPage = sinon.stub()
@req.query = {}
@middleware(@req, @res, @next)
it "should redirect to the register or login page", ->
@AuthenticationController._redirectToLoginOrRegisterPage.calledWith(@req, @res).should.equal true
describe "requireGlobalLogin", ->
beforeEach ->

View file

@ -95,49 +95,3 @@ describe "AuthenticationManager", ->
it "should call the callback", ->
@callback.called.should.equal true
describe "getAuthToken", ->
beforeEach ->
@auth_token = "auth-token"
describe "when the user has an auth token set", ->
beforeEach ->
@db.users.findOne = sinon.stub().callsArgWith(2, null, auth_token: @auth_token)
@AuthenticationManager.getAuthToken(@user_id, @callback)
it "should look up the auth token in the db", ->
@db.users.findOne
.calledWith({
_id: ObjectId(@user_id.toString())
}, {
auth_token: true
})
.should.equal true
it "should return the auth token", ->
@callback.calledWith(null, @auth_token).should.equal true
describe "when the user does not have an auth token set", ->
beforeEach ->
@db.users.findOne = sinon.stub().callsArgWith(2, null, auth_token: null)
@db.users.update = sinon.stub().callsArgWith(2, null)
@AuthenticationManager._createSecureToken = sinon.stub().callsArgWith(0, null, @auth_token)
@AuthenticationManager.getAuthToken(@user_id, @callback)
it "should generate a new auth token", ->
@AuthenticationManager._createSecureToken.called.should.equal true
it "should set the auth token on the user document in the db", ->
@db.users.update
.calledWith({
_id: ObjectId(@user_id.toString())
}, {
$set: auth_token: @auth_token
})
.should.equal true
it "should return the auth token", ->
@callback.calledWith(null, @auth_token).should.equal true

View file

@ -0,0 +1,385 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Authorization/AuthorizationManager.js"
SandboxedModule = require('sandboxed-module')
Errors = require "../../../../app/js/Features/Errors/Errors.js"
describe "AuthorizationManager", ->
beforeEach ->
@AuthorizationManager = SandboxedModule.require modulePath, requires:
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {}
"../../models/Project": Project: @Project = {}
"../../models/User": User: @User = {}
"../Errors/Errors": Errors
@user_id = "user-id-1"
@project_id = "project-id-1"
@callback = sinon.stub()
describe "getPrivilegeLevelForProject", ->
beforeEach ->
@Project.findOne = sinon.stub()
@AuthorizationManager.isUserSiteAdmin = sinon.stub()
@CollaboratorsHandler.getMemberIdPrivilegeLevel = sinon.stub()
describe "with a private project", ->
beforeEach ->
@Project.findOne
.withArgs({ _id: @project_id }, { publicAccesLevel: 1 })
.yields(null, { publicAccesLevel: "private" })
describe "with a user_id with a privilege level", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin.withArgs(@user_id).yields(null, false)
@CollaboratorsHandler.getMemberIdPrivilegeLevel
.withArgs(@user_id, @project_id)
.yields(null, "readOnly")
@AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @callback
it "should return the user's privilege level", ->
@callback.calledWith(null, "readOnly", false).should.equal true
describe "with a user_id with no privilege level", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin.withArgs(@user_id).yields(null, false)
@CollaboratorsHandler.getMemberIdPrivilegeLevel
.withArgs(@user_id, @project_id)
.yields(null, false)
@AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @callback
it "should return false", ->
@callback.calledWith(null, false, false).should.equal true
describe "with a user_id who is an admin", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin.withArgs(@user_id).yields(null, true)
@CollaboratorsHandler.getMemberIdPrivilegeLevel
.withArgs(@user_id, @project_id)
.yields(null, false)
@AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @callback
it "should return the user as an owner", ->
@callback.calledWith(null, "owner", false).should.equal true
describe "with no user (anonymous)", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject null, @project_id, @callback
it "should not call CollaboratorsHandler.getMemberIdPrivilegeLevel", ->
@CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal false
it "should not call AuthorizationManager.isUserSiteAdmin", ->
@AuthorizationManager.isUserSiteAdmin.called.should.equal false
it "should return false", ->
@callback.calledWith(null, false, false).should.equal true
describe "with a public project", ->
beforeEach ->
@Project.findOne
.withArgs({ _id: @project_id }, { publicAccesLevel: 1 })
.yields(null, { publicAccesLevel: "readAndWrite" })
describe "with a user_id with a privilege level", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin.withArgs(@user_id).yields(null, false)
@CollaboratorsHandler.getMemberIdPrivilegeLevel
.withArgs(@user_id, @project_id)
.yields(null, "readOnly")
@AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @callback
it "should return the user's privilege level", ->
@callback.calledWith(null, "readOnly", false).should.equal true
describe "with a user_id with no privilege level", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin.withArgs(@user_id).yields(null, false)
@CollaboratorsHandler.getMemberIdPrivilegeLevel
.withArgs(@user_id, @project_id)
.yields(null, false)
@AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @callback
it "should return the public privilege level", ->
@callback.calledWith(null, "readAndWrite", true).should.equal true
describe "with a user_id who is an admin", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin.withArgs(@user_id).yields(null, true)
@CollaboratorsHandler.getMemberIdPrivilegeLevel
.withArgs(@user_id, @project_id)
.yields(null, false)
@AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @callback
it "should return the user as an owner", ->
@callback.calledWith(null, "owner", false).should.equal true
describe "with no user (anonymous)", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject null, @project_id, @callback
it "should not call CollaboratorsHandler.getMemberIdPrivilegeLevel", ->
@CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal false
it "should not call AuthorizationManager.isUserSiteAdmin", ->
@AuthorizationManager.isUserSiteAdmin.called.should.equal false
it "should return the public privilege level", ->
@callback.calledWith(null, "readAndWrite", true).should.equal true
describe "when the project doesn't exist", ->
beforeEach ->
@Project.findOne
.withArgs({ _id: @project_id }, { publicAccesLevel: 1 })
.yields(null, null)
it "should return a NotFoundError", ->
@AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, (error) ->
error.should.be.instanceof Errors.NotFoundError
describe "canUserReadProject", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject = sinon.stub()
describe "when user is owner", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "owner", false)
it "should return true", (done) ->
@AuthorizationManager.canUserReadProject @user_id, @project_id, (error, canRead) ->
expect(canRead).to.equal true
done()
describe "when user has read-write access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readAndWrite", false)
it "should return true", (done) ->
@AuthorizationManager.canUserReadProject @user_id, @project_id, (error, canRead) ->
expect(canRead).to.equal true
done()
describe "when user has read-only access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readOnly", false)
it "should return true", (done) ->
@AuthorizationManager.canUserReadProject @user_id, @project_id, (error, canRead) ->
expect(canRead).to.equal true
done()
describe "when user has no access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, false, false)
it "should return false", (done) ->
@AuthorizationManager.canUserReadProject @user_id, @project_id, (error, canRead) ->
expect(canRead).to.equal false
done()
describe "canUserWriteProjectContent", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject = sinon.stub()
describe "when user is owner", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "owner", false)
it "should return true", (done) ->
@AuthorizationManager.canUserWriteProjectContent @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal true
done()
describe "when user has read-write access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readAndWrite", false)
it "should return true", (done) ->
@AuthorizationManager.canUserWriteProjectContent @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal true
done()
describe "when user has read-only access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readOnly", false)
it "should return false", (done) ->
@AuthorizationManager.canUserWriteProjectContent @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal false
done()
describe "when user has no access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, false, false)
it "should return false", (done) ->
@AuthorizationManager.canUserWriteProjectContent @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal false
done()
describe "canUserWriteProjectSettings", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject = sinon.stub()
describe "when user is owner", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "owner", false)
it "should return true", (done) ->
@AuthorizationManager.canUserWriteProjectSettings @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal true
done()
describe "when user has read-write access as a collaborator", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readAndWrite", false)
it "should return true", (done) ->
@AuthorizationManager.canUserWriteProjectSettings @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal true
done()
describe "when user has read-write access as the public", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readAndWrite", true)
it "should return false", (done) ->
@AuthorizationManager.canUserWriteProjectSettings @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal false
done()
describe "when user has read-only access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readOnly", false)
it "should return false", (done) ->
@AuthorizationManager.canUserWriteProjectSettings @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal false
done()
describe "when user has no access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, false, false)
it "should return false", (done) ->
@AuthorizationManager.canUserWriteProjectSettings @user_id, @project_id, (error, canWrite) ->
expect(canWrite).to.equal false
done()
describe "canUserAdminProject", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject = sinon.stub()
describe "when user is owner", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "owner", false)
it "should return true", (done) ->
@AuthorizationManager.canUserAdminProject @user_id, @project_id, (error, canAdmin) ->
expect(canAdmin).to.equal true
done()
describe "when user has read-write access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readAndWrite", false)
it "should return false", (done) ->
@AuthorizationManager.canUserAdminProject @user_id, @project_id, (error, canAdmin) ->
expect(canAdmin).to.equal false
done()
describe "when user has read-only access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, "readOnly", false)
it "should return false", (done) ->
@AuthorizationManager.canUserAdminProject @user_id, @project_id, (error, canAdmin) ->
expect(canAdmin).to.equal false
done()
describe "when user has no access", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject
.withArgs(@user_id, @project_id)
.yields(null, false, false)
it "should return false", (done) ->
@AuthorizationManager.canUserAdminProject @user_id, @project_id, (error, canAdmin) ->
expect(canAdmin).to.equal false
done()
describe "isUserSiteAdmin", ->
beforeEach ->
@User.findOne = sinon.stub()
describe "when user is admin", ->
beforeEach ->
@User.findOne
.withArgs({ _id: @user_id }, { isAdmin: 1 })
.yields(null, { isAdmin: true })
it "should return true", (done) ->
@AuthorizationManager.isUserSiteAdmin @user_id, (error, isAdmin) ->
expect(isAdmin).to.equal true
done()
describe "when user is not admin", ->
beforeEach ->
@User.findOne
.withArgs({ _id: @user_id }, { isAdmin: 1 })
.yields(null, { isAdmin: false })
it "should return false", (done) ->
@AuthorizationManager.isUserSiteAdmin @user_id, (error, isAdmin) ->
expect(isAdmin).to.equal false
done()
describe "when user is not found", ->
beforeEach ->
@User.findOne
.withArgs({ _id: @user_id }, { isAdmin: 1 })
.yields(null, null)
it "should return false", (done) ->
@AuthorizationManager.isUserSiteAdmin @user_id, (error, isAdmin) ->
expect(isAdmin).to.equal false
done()
describe "when no user is passed", ->
it "should return false", (done) ->
@AuthorizationManager.isUserSiteAdmin null, (error, isAdmin) =>
@User.findOne.called.should.equal false
expect(isAdmin).to.equal false
done()

View file

@ -0,0 +1,237 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Authorization/AuthorizationMiddlewear.js"
SandboxedModule = require('sandboxed-module')
Errors = require "../../../../app/js/Features/Errors/Errors.js"
describe "AuthorizationMiddlewear", ->
beforeEach ->
@AuthorizationMiddlewear = SandboxedModule.require modulePath, requires:
"./AuthorizationManager": @AuthorizationManager = {}
"logger-sharelatex": {log: () ->}
"mongojs": ObjectId: @ObjectId = {}
"../Errors/Errors": Errors
@user_id = "user-id-123"
@project_id = "project-id-123"
@req = {}
@res = {}
@ObjectId.isValid = sinon.stub()
@ObjectId.isValid.withArgs(@project_id).returns true
@next = sinon.stub()
METHODS_TO_TEST = {
"ensureUserCanReadProject": "canUserReadProject"
"ensureUserCanWriteProjectSettings": "canUserWriteProjectSettings"
"ensureUserCanWriteProjectContent": "canUserWriteProjectContent"
"ensureUserCanAdminProject": "canUserAdminProject"
}
for middlewearMethod, managerMethod of METHODS_TO_TEST
do (middlewearMethod, managerMethod) ->
describe middlewearMethod, ->
beforeEach ->
@req.params =
project_id: @project_id
@AuthorizationManager[managerMethod] = sinon.stub()
@AuthorizationMiddlewear.redirectToRestricted = sinon.stub()
describe "with missing project_id", ->
beforeEach ->
@req.params = {}
it "should return an error to next", ->
@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
@next.calledWith(new Error()).should.equal true
describe "with logged in user", ->
beforeEach ->
@req.session =
user: _id: @user_id
describe "when user has permission", ->
beforeEach ->
@AuthorizationManager[managerMethod]
.withArgs(@user_id, @project_id)
.yields(null, true)
it "should return next", ->
@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
@next.called.should.equal true
describe "when user doesn't have permission", ->
beforeEach ->
@AuthorizationManager[managerMethod]
.withArgs(@user_id, @project_id)
.yields(null, false)
it "should redirect to redirectToRestricted", ->
@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
@next.called.should.equal false
@AuthorizationMiddlewear.redirectToRestricted
.calledWith(@req, @res, @next)
.should.equal true
describe "with anonymous user", ->
describe "when user has permission", ->
beforeEach ->
@AuthorizationManager[managerMethod]
.withArgs(null, @project_id)
.yields(null, true)
it "should return next", ->
@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
@next.called.should.equal true
describe "when user doesn't have permission", ->
beforeEach ->
@AuthorizationManager[managerMethod]
.withArgs(null, @project_id)
.yields(null, false)
it "should redirect to redirectToRestricted", ->
@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
@next.called.should.equal false
@AuthorizationMiddlewear.redirectToRestricted
.calledWith(@req, @res, @next)
.should.equal true
describe "with malformed project id", ->
beforeEach ->
@req.params =
project_id: "blah"
@ObjectId.isValid = sinon.stub().returns false
it "should return a not found error", (done) ->
@AuthorizationMiddlewear[middlewearMethod] @req, @res, (error) ->
error.should.be.instanceof Errors.NotFoundError
done()
describe "ensureUserIsSiteAdmin", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin = sinon.stub()
@AuthorizationMiddlewear.redirectToRestricted = sinon.stub()
describe "with logged in user", ->
beforeEach ->
@req.session =
user: _id: @user_id
describe "when user has permission", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin
.withArgs(@user_id)
.yields(null, true)
it "should return next", ->
@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
@next.called.should.equal true
describe "when user doesn't have permission", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin
.withArgs(@user_id)
.yields(null, false)
it "should redirect to redirectToRestricted", ->
@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
@next.called.should.equal false
@AuthorizationMiddlewear.redirectToRestricted
.calledWith(@req, @res, @next)
.should.equal true
describe "with anonymous user", ->
describe "when user has permission", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin
.withArgs(null)
.yields(null, true)
it "should return next", ->
@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
@next.called.should.equal true
describe "when user doesn't have permission", ->
beforeEach ->
@AuthorizationManager.isUserSiteAdmin
.withArgs(null)
.yields(null, false)
it "should redirect to redirectToRestricted", ->
@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
@next.called.should.equal false
@AuthorizationMiddlewear.redirectToRestricted
.calledWith(@req, @res, @next)
.should.equal true
describe "ensureUserCanReadMultipleProjects", ->
beforeEach ->
@AuthorizationManager.canUserReadProject = sinon.stub()
@AuthorizationMiddlewear.redirectToRestricted = sinon.stub()
@req.query =
project_ids: "project1,project2"
describe "with logged in user", ->
beforeEach ->
@req.session =
user: _id: @user_id
describe "when user has permission to access all projects", ->
beforeEach ->
@AuthorizationManager.canUserReadProject
.withArgs(@user_id, "project1")
.yields(null, true)
@AuthorizationManager.canUserReadProject
.withArgs(@user_id, "project2")
.yields(null, true)
it "should return next", ->
@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
@next.called.should.equal true
describe "when user doesn't have permission to access one of the projects", ->
beforeEach ->
@AuthorizationManager.canUserReadProject
.withArgs(@user_id, "project1")
.yields(null, true)
@AuthorizationManager.canUserReadProject
.withArgs(@user_id, "project2")
.yields(null, false)
it "should redirect to redirectToRestricted", ->
@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
@next.called.should.equal false
@AuthorizationMiddlewear.redirectToRestricted
.calledWith(@req, @res, @next)
.should.equal true
describe "with anonymous user", ->
describe "when user has permission", ->
describe "when user has permission to access all projects", ->
beforeEach ->
@AuthorizationManager.canUserReadProject
.withArgs(null, "project1")
.yields(null, true)
@AuthorizationManager.canUserReadProject
.withArgs(null, "project2")
.yields(null, true)
it "should return next", ->
@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
@next.called.should.equal true
describe "when user doesn't have permission to access one of the projects", ->
beforeEach ->
@AuthorizationManager.canUserReadProject
.withArgs(null, "project1")
.yields(null, true)
@AuthorizationManager.canUserReadProject
.withArgs(null, "project2")
.yields(null, false)
it "should redirect to redirectToRestricted", ->
@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
@next.called.should.equal false
@AuthorizationMiddlewear.redirectToRestricted
.calledWith(@req, @res, @next)
.should.equal true

View file

@ -24,35 +24,6 @@ describe "CollaboratorsController", ->
@project_id = "project-id-123"
@callback = sinon.stub()
describe "getCollaborators", ->
beforeEach ->
@project =
_id: @project_id = "project-id-123"
@collaborators = ["array of collaborators"]
@req.params = Project_id: @project_id
@ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project)
@ProjectGetter.populateProjectWithUsers = sinon.stub().callsArgWith(1, null, @project)
@CollaboratorsController._formatCollaborators = sinon.stub().callsArgWith(1, null, @collaborators)
@CollaboratorsController.getCollaborators(@req, @res)
it "should get the project", ->
@ProjectGetter.getProject
.calledWith(@project_id, { owner_ref: true, collaberator_refs: true, readOnly_refs: true })
.should.equal true
it "should populate the users in the project", ->
@ProjectGetter.populateProjectWithUsers
.calledWith(@project)
.should.equal true
it "should format the collaborators", ->
@CollaboratorsController._formatCollaborators
.calledWith(@project)
.should.equal true
it "should return the formatted collaborators", ->
@res.body.should.equal JSON.stringify(@collaborators)
describe "addUserToProject", ->
beforeEach ->
@req.params =
@ -179,85 +150,3 @@ describe "CollaboratorsController", ->
it "should return a success code", ->
@res.sendStatus.calledWith(204).should.equal true
describe "_formatCollaborators", ->
beforeEach ->
@owner =
_id: ObjectId()
first_name: "Lenny"
last_name: "Lion"
email: "test@sharelatex.com"
hashed_password: "password" # should not be included
describe "formatting the owner", ->
beforeEach ->
@project =
owner_ref: @owner
collaberator_refs: []
@CollaboratorsController._formatCollaborators(@project, @callback)
it "should return the owner with read, write and admin permissions", ->
@formattedOwner = @callback.args[0][1][0]
expect(@formattedOwner).to.deep.equal {
id: @owner._id.toString()
first_name: @owner.first_name
last_name: @owner.last_name
email: @owner.email
permissions: ["read", "write", "admin"]
owner: true
}
describe "formatting a collaborator with write access", ->
beforeEach ->
@collaborator =
_id: ObjectId()
first_name: "Douglas"
last_name: "Adams"
email: "doug@sharelatex.com"
hashed_password: "password" # should not be included
@project =
owner_ref: @owner
collaberator_refs: [ @collaborator ]
@CollaboratorsController._formatCollaborators(@project, @callback)
it "should return the collaborator with read and write permissions", ->
@formattedCollaborator = @callback.args[0][1][1]
expect(@formattedCollaborator).to.deep.equal {
id: @collaborator._id.toString()
first_name: @collaborator.first_name
last_name: @collaborator.last_name
email: @collaborator.email
permissions: ["read", "write"]
owner: false
}
describe "formatting a collaborator with read only access", ->
beforeEach ->
@collaborator =
_id: ObjectId()
first_name: "Douglas"
last_name: "Adams"
email: "doug@sharelatex.com"
hashed_password: "password" # should not be included
@project =
owner_ref: @owner
collaberator_refs: []
readOnly_refs: [ @collaborator ]
@CollaboratorsController._formatCollaborators(@project, @callback)
it "should return the collaborator with read permissions", ->
@formattedCollaborator = @callback.args[0][1][1]
expect(@formattedCollaborator).to.deep.equal {
id: @collaborator._id.toString()
first_name: @collaborator.first_name
last_name: @collaborator.last_name
email: @collaborator.email
permissions: ["read"]
owner: false
}

View file

@ -5,6 +5,7 @@ path = require('path')
sinon = require('sinon')
modulePath = path.join __dirname, "../../../../app/js/Features/Collaborators/CollaboratorsHandler"
expect = require("chai").expect
Errors = require "../../../../app/js/Features/Errors/Errors.js"
describe "CollaboratorsHandler", ->
beforeEach ->
@ -16,12 +17,145 @@ describe "CollaboratorsHandler", ->
"../../models/Project": Project: @Project = {}
"../Project/ProjectEntityHandler": @ProjectEntityHandler = {}
"./CollaboratorsEmailHandler": @CollaboratorsEmailHandler = {}
"../Errors/Errors": Errors
@project_id = "mock-project-id"
@user_id = "mock-user-id"
@adding_user_id = "adding-user-id"
@email = "joe@sharelatex.com"
@callback = sinon.stub()
describe "getMemberIdsWithPrivilegeLevels", ->
describe "with project", ->
beforeEach ->
@Project.findOne = sinon.stub()
@Project.findOne.withArgs({_id: @project_id}, {owner_ref: 1, collaberator_refs: 1, readOnly_refs: 1}).yields(null, @project = {
owner_ref: [ "owner-ref" ]
readOnly_refs: [ "read-only-ref-1", "read-only-ref-2" ]
collaberator_refs: [ "read-write-ref-1", "read-write-ref-2" ]
})
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels @project_id, @callback
it "should return an array of member ids with their privilege levels", ->
@callback
.calledWith(null, [
{ id: "owner-ref", privilegeLevel: "owner" }
{ id: "read-only-ref-1", privilegeLevel: "readOnly" }
{ id: "read-only-ref-2", privilegeLevel: "readOnly" }
{ id: "read-write-ref-1", privilegeLevel: "readAndWrite" }
{ id: "read-write-ref-2", privilegeLevel: "readAndWrite" }
])
.should.equal true
describe "with a missing project", ->
beforeEach ->
@Project.findOne = sinon.stub().yields(null, null)
it "should return a NotFoundError", (done) ->
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels @project_id, (error) ->
error.should.be.instanceof Errors.NotFoundError
done()
describe "getMemberIds", ->
beforeEach ->
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels
.withArgs(@project_id)
.yields(null, [{id: "member-id-1"}, {id: "member-id-2"}])
@CollaboratorHandler.getMemberIds @project_id, @callback
it "should return the ids", ->
@callback
.calledWith(null, ["member-id-1", "member-id-2"])
.should.equal true
describe "getMembersWithPrivilegeLevels", ->
beforeEach ->
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels.withArgs(@project_id).yields(null, [
{ id: "read-only-ref-1", privilegeLevel: "readOnly" }
{ id: "read-only-ref-2", privilegeLevel: "readOnly" }
{ id: "read-write-ref-1", privilegeLevel: "readAndWrite" }
{ id: "read-write-ref-2", privilegeLevel: "readAndWrite" }
])
@UserGetter.getUser = sinon.stub()
@UserGetter.getUser.withArgs("read-only-ref-1").yields(null, { _id: "read-only-ref-1" })
@UserGetter.getUser.withArgs("read-only-ref-2").yields(null, { _id: "read-only-ref-2" })
@UserGetter.getUser.withArgs("read-write-ref-1").yields(null, { _id: "read-write-ref-1" })
@UserGetter.getUser.withArgs("read-write-ref-2").yields(null, { _id: "read-write-ref-2" })
@CollaboratorHandler.getMembersWithPrivilegeLevels @project_id, @callback
it "should return an array of members with their privilege levels", ->
@callback
.calledWith(undefined, [
{ user: { _id: "read-only-ref-1" }, privilegeLevel: "readOnly" }
{ user: { _id: "read-only-ref-2" }, privilegeLevel: "readOnly" }
{ user: { _id: "read-write-ref-1" }, privilegeLevel: "readAndWrite" }
{ user: { _id: "read-write-ref-2" }, privilegeLevel: "readAndWrite" }
])
.should.equal true
describe "getMemberIdPrivilegeLevel", ->
beforeEach ->
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels
.withArgs(@project_id)
.yields(null, [
{id: "member-id-1", privilegeLevel: "readAndWrite"}
{id: "member-id-2", privilegeLevel: "readOnly"}
])
it "should return the privilege level if it exists", (done) ->
@CollaboratorHandler.getMemberIdPrivilegeLevel "member-id-2", @project_id, (error, level) ->
expect(level).to.equal "readOnly"
done()
it "should return false if the member has no privilege level", (done) ->
@CollaboratorHandler.getMemberIdPrivilegeLevel "member-id-3", @project_id, (error, level) ->
expect(level).to.equal false
done()
describe "isUserMemberOfProject", ->
beforeEach ->
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
describe "when user is a member of the project", ->
beforeEach ->
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels.withArgs(@project_id).yields(null, [
{ id: "not-the-user", privilegeLevel: "readOnly" }
{ id: @user_id, privilegeLevel: "readAndWrite" }
])
@CollaboratorHandler.isUserMemberOfProject @user_id, @project_id, @callback
it "should return true and the privilegeLevel", ->
@callback
.calledWith(null, true, "readAndWrite")
.should.equal true
describe "when user is not a member of the project", ->
beforeEach ->
@CollaboratorHandler.getMemberIdsWithPrivilegeLevels.withArgs(@project_id).yields(null, [
{ id: "not-the-user", privilegeLevel: "readOnly" }
])
@CollaboratorHandler.isUserMemberOfProject @user_id, @project_id, @callback
it "should return false", ->
@callback
.calledWith(null, false, null)
.should.equal true
describe "getProjectsUserIsCollaboratorOf", ->
beforeEach ->
@fields = "mock fields"
@Project.find = sinon.stub()
@Project.find.withArgs({collaberator_refs:@user_id}, @fields).yields(null, ["mock-read-write-project-1", "mock-read-write-project-2"])
@Project.find.withArgs({readOnly_refs:@user_id}, @fields).yields(null, ["mock-read-only-project-1", "mock-read-only-project-2"])
@CollaboratorHandler.getProjectsUserIsCollaboratorOf @user_id, @fields, @callback
it "should call the callback with the projects", ->
@callback
.calledWith(null, ["mock-read-write-project-1", "mock-read-write-project-2"], ["mock-read-only-project-1", "mock-read-only-project-2"])
.should.equal true
describe "removeUserFromProject", ->
beforeEach ->

View file

@ -19,7 +19,7 @@ describe "ClsiManager", ->
url: "https://clsipremium.example.com"
"../../models/Project": Project: @Project = {}
"../Project/ProjectEntityHandler": @ProjectEntityHandler = {}
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }
"request": @request = {}
@project_id = "project-id"
@callback = sinon.stub()
@ -218,9 +218,9 @@ describe "ClsiManager", ->
@project.rootDoc_id = "not-valid"
@ClsiManager._buildRequest @project, null, (@error, @request) =>
done()
it "should return an error", ->
expect(@error).to.exist
it "should set to main.tex", ->
@request.compile.rootResourcePath.should.equal "main.tex"
describe "with the draft option", ->
it "should add the draft option into the request", (done) ->

View file

@ -27,7 +27,7 @@ describe "CompileManager", ->
Timer: class Timer
done: sinon.stub()
inc: sinon.stub()
"logger-sharelatex": @logger = { log: sinon.stub() }
"logger-sharelatex": @logger = { log: sinon.stub(), warn: sinon.stub() }
@project_id = "mock-project-id-123"
@user_id = "mock-user-id-123"
@callback = sinon.stub()
@ -90,15 +90,12 @@ describe "CompileManager", ->
.should.equal true
describe "when the project has been recently compiled", ->
beforeEach ->
it "should return", (done)->
@CompileManager._checkIfAutoCompileLimitHasBeenHit = (_, cb)-> cb(null, true)
@CompileManager._checkIfRecentlyCompiled = sinon.stub().callsArgWith(2, null, true)
@CompileManager.compile @project_id, @user_id, {}, @callback
it "should return the callback with an error", ->
@callback
.calledWith(new Error("project was recently compiled so not continuing"))
.should.equal true
@CompileManager.compile @project_id, @user_id, {}, (err, status)->
status.should.equal "too-recently-compiled"
done()
describe "should check the rate limit", ->
it "should return", (done)->

View file

@ -10,12 +10,13 @@ describe "EditorHttpController", ->
'../Project/ProjectDeleter' : @ProjectDeleter = {}
'../Project/ProjectGetter' : @ProjectGetter = {}
'../User/UserGetter' : @UserGetter = {}
"../Security/AuthorizationManager": @AuthorizationManager = {}
"../Authorization/AuthorizationManager": @AuthorizationManager = {}
'../Project/ProjectEditorHandler': @ProjectEditorHandler = {}
"./EditorRealTimeController": @EditorRealTimeController = {}
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
"./EditorController": @EditorController = {}
'../../infrastructure/Metrics': @Metrics = {inc: sinon.stub()}
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {}
@project_id = "mock-project-id"
@doc_id = "mock-doc-id"
@ -76,6 +77,17 @@ describe "EditorHttpController", ->
@ProjectDeleter.unmarkAsDeletedByExternalSource
.calledWith(@project_id)
.should.equal true
describe "with an anonymous user", ->
beforeEach ->
@req.query =
user_id: "anonymous-user"
@EditorHttpController.joinProject @req, @res
it "should pass the user id as null", ->
@EditorHttpController._buildJoinProjectView
.calledWith(@project_id, null)
.should.equal true
describe "_buildJoinProjectView", ->
beforeEach ->
@ -85,19 +97,20 @@ describe "EditorHttpController", ->
@user =
_id: @user_id = "user-id"
projects: {}
@members = ["members", "mock"]
@projectModelView =
_id: @project_id
owner:{_id:"something"}
view: true
@ProjectEditorHandler.buildProjectModelView = sinon.stub().returns(@projectModelView)
@ProjectGetter.getProjectWithoutDocLines = sinon.stub().callsArgWith(1, null, @project)
@ProjectGetter.populateProjectWithUsers = sinon.stub().callsArgWith(1, null, @project)
@CollaboratorsHandler.getMembersWithPrivilegeLevels = sinon.stub().callsArgWith(1, null, @members)
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
describe "when authorized", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject =
sinon.stub().callsArgWith(2, null, true, "owner")
sinon.stub().callsArgWith(2, null, "owner")
@EditorHttpController._buildJoinProjectView(@project_id, @user_id, @callback)
it "should find the project without doc lines", ->
@ -105,8 +118,8 @@ describe "EditorHttpController", ->
.calledWith(@project_id)
.should.equal true
it "should populate the user references in the project", ->
@ProjectGetter.populateProjectWithUsers
it "should get the list of users in the project", ->
@CollaboratorsHandler.getMembersWithPrivilegeLevels
.calledWith(@project)
.should.equal true
@ -117,7 +130,7 @@ describe "EditorHttpController", ->
it "should check the privilege level", ->
@AuthorizationManager.getPrivilegeLevelForProject
.calledWith(@project, @user)
.calledWith(@user_id, @project_id)
.should.equal true
it "should return the project model view, privilege level and protocol version", ->
@ -126,7 +139,7 @@ describe "EditorHttpController", ->
describe "when not authorized", ->
beforeEach ->
@AuthorizationManager.getPrivilegeLevelForProject =
sinon.stub().callsArgWith(2, null, false, null)
sinon.stub().callsArgWith(2, null, null)
@EditorHttpController._buildJoinProjectView(@project_id, @user_id, @callback)
it "should return false in the callback", ->

View file

@ -35,13 +35,10 @@ describe "ProjectController", ->
getAllTags: sinon.stub()
@NotificationsHandler =
getUserNotifications: sinon.stub()
@ProjectModel =
findAllUsersProjects: sinon.stub()
findPopulatedById: sinon.stub()
@UserModel =
findById: sinon.stub()
@SecurityManager =
userCanAccessProject:sinon.stub()
@AuthorizationManager =
getPrivilegeLevelForProject:sinon.stub()
@EditorController =
renameProject:sinon.stub()
@InactiveProjectManager =
@ -50,6 +47,9 @@ describe "ProjectController", ->
markAsOpened: sinon.stub()
@ReferencesSearchHandler =
indexProjectReferences: sinon.stub()
@ProjectGetter =
findAllUsersProjects: sinon.stub()
getProject: sinon.stub()
@ProjectController = SandboxedModule.require modulePath, requires:
"settings-sharelatex":@settings
"logger-sharelatex":
@ -67,12 +67,12 @@ describe "ProjectController", ->
"../Subscription/LimitationsManager": @LimitationsManager
"../Tags/TagsHandler":@TagsHandler
"../Notifications/NotificationsHandler":@NotificationsHandler
'../../models/Project': Project:@ProjectModel
"../../models/User":User:@UserModel
"../../managers/SecurityManager":@SecurityManager
"../Authorization/AuthorizationManager":@AuthorizationManager
"../InactiveData/InactiveProjectManager":@InactiveProjectManager
"./ProjectUpdateHandler":@ProjectUpdateHandler
"../ReferencesSearch/ReferencesSearchHandler": @ReferencesSearchHandler
"./ProjectGetter": @ProjectGetter
@user =
_id:"!£123213kjljkl"
@ -128,18 +128,6 @@ describe "ProjectController", ->
done()
@ProjectController.updateProjectSettings @req, @res
it "should update the public access level", (done) ->
@EditorController.setPublicAccessLevel = sinon.stub().callsArg(2)
@req.body =
publicAccessLevel: @publicAccessLevel = "readonly"
@res.sendStatus = (code) =>
@EditorController.setPublicAccessLevel
.calledWith(@project_id, @publicAccessLevel)
.should.equal true
code.should.equal 204
done()
@ProjectController.updateProjectSettings @req, @res
it "should update the root doc", (done) ->
@EditorController.setRootDoc = sinon.stub().callsArg(2)
@req.body =
@ -151,6 +139,19 @@ describe "ProjectController", ->
code.should.equal 204
done()
@ProjectController.updateProjectSettings @req, @res
describe "updateProjectAdminSettings", ->
it "should update the public access level", (done) ->
@EditorController.setPublicAccessLevel = sinon.stub().callsArg(2)
@req.body =
publicAccessLevel: @publicAccessLevel = "readonly"
@res.sendStatus = (code) =>
@EditorController.setPublicAccessLevel
.calledWith(@project_id, @publicAccessLevel)
.should.equal true
code.should.equal 204
done()
@ProjectController.updateProjectAdminSettings @req, @res
describe "deleteProject", ->
it "should tell the project deleter to archive when forever=false", (done)->
@ -224,7 +225,7 @@ describe "ProjectController", ->
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, false)
@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
@ProjectModel.findAllUsersProjects.callsArgWith(2, null, @projects, @collabertions, @readOnly)
@ProjectGetter.findAllUsersProjects.callsArgWith(2, null, @projects, @collabertions, @readOnly)
it "should render the project/list page", (done)->
@res.render = (pageName, opts)=>
@ -297,10 +298,10 @@ describe "ProjectController", ->
fontSize:"massive"
theme:"sexy"
email: "bob@bob.com"
@ProjectModel.findPopulatedById.callsArgWith 1, null, @project
@ProjectGetter.getProject.callsArgWith 2, null, @project
@UserModel.findById.callsArgWith(1, null, @user)
@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null, {})
@SecurityManager.userCanAccessProject.callsArgWith 2, true, "owner"
@AuthorizationManager.getPrivilegeLevelForProject.callsArgWith 2, null, "owner"
@ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub()
@InactiveProjectManager.reactivateProjectIfRequired.callsArgWith(1)
@ProjectUpdateHandler.markAsOpened.callsArgWith(1)
@ -312,12 +313,6 @@ describe "ProjectController", ->
done()
@ProjectController.loadEditor @req, @res
it "should add the project onto the opts", (done)->
@res.render = (pageName, opts)=>
opts.project.should.equal @project
done()
@ProjectController.loadEditor @req, @res
it "should add user", (done)->
@res.render = (pageName, opts)=>
opts.user.email.should.equal @user.email
@ -339,7 +334,7 @@ describe "ProjectController", ->
@ProjectController.loadEditor @req, @res
it "should not render the page if the project can not be accessed", (done)->
@SecurityManager.userCanAccessProject = sinon.stub().callsArgWith 2, false
@AuthorizationManager.getPrivilegeLevelForProject = sinon.stub().callsArgWith 2, null, null
@res.sendStatus = (resCode, opts)=>
resCode.should.equal 401
done()

View file

@ -33,6 +33,7 @@ describe 'ProjectDeleter', ->
'../DocumentUpdater/DocumentUpdaterHandler': @documentUpdaterHandler
"../Tags/TagsHandler":@TagsHandler
"../FileStore/FileStoreHandler": @FileStoreHandler = {}
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {}
"./ProjectGetter": @ProjectGetter
'logger-sharelatex':
log:->
@ -92,6 +93,8 @@ describe 'ProjectDeleter', ->
describe "archiveProject", ->
beforeEach ->
@CollaboratorsHandler.getMemberIds = sinon.stub()
@CollaboratorsHandler.getMemberIds.withArgs(@project_id).yields(null, ["member-id-1", "member-id-2"])
@ProjectGetter.getProject.callsArgWith(2, null, @project)
@Project.update.callsArgWith(2)
@ -111,12 +114,8 @@ describe 'ProjectDeleter', ->
it "should removeProjectFromAllTags", (done)->
@deleter.archiveProject @project_id, =>
@TagsHandler.removeProjectFromAllTags.calledWith(@project.owner_ref, @project_id).should.equal true
@TagsHandler.removeProjectFromAllTags.calledWith(@project.collaberator_refs[0], @project_id).should.equal true
@TagsHandler.removeProjectFromAllTags.calledWith(@project.collaberator_refs[1], @project_id).should.equal true
@TagsHandler.removeProjectFromAllTags.calledWith(@project.readOnly_refs[0], @project_id).should.equal true
@TagsHandler.removeProjectFromAllTags.calledWith(@project.readOnly_refs[1], @project_id).should.equal true
@TagsHandler.removeProjectFromAllTags.calledWith("member-id-1", @project_id).should.equal true
@TagsHandler.removeProjectFromAllTags.calledWith("member-id-2", @project_id).should.equal true
done()
describe "restoreProject", ->

View file

@ -38,33 +38,41 @@ describe "ProjectEditorHandler", ->
folders : []
}]
}]
owner_ref :
_id: "owner-id"
first_name : "Owner"
last_name : "ShareLaTeX"
email : "owner@sharelatex.com"
readOnly_refs: [{
_id: "read-only-id"
first_name : "Read"
last_name : "Only"
email : "read-only@sharelatex.com"
}]
collaberator_refs: [{
_id: "read-write-id"
first_name : "Read"
last_name : "Write"
email : "read-write@sharelatex.com"
}]
deletedDocs: [{
_id: "deleted-doc-id"
name: "main.tex"
}]
@members = [{
user: @owner = {
_id: "owner-id"
first_name : "Owner"
last_name : "ShareLaTeX"
email : "owner@sharelatex.com"
},
privilegeLevel: "owner"
},{
user: {
_id: "read-only-id"
first_name : "Read"
last_name : "Only"
email : "read-only@sharelatex.com"
},
privilegeLevel: "readOnly"
},{
user: {
_id: "read-write-id"
first_name : "Read"
last_name : "Write"
email : "read-write@sharelatex.com"
},
privilegeLevel: "readAndWrite"
}]
@handler = SandboxedModule.require modulePath
describe "buildProjectModelView", ->
describe "with owner and members included", ->
beforeEach ->
@result = @handler.buildProjectModelView @project
@result = @handler.buildProjectModelView @project, @members
it "should include the id", ->
should.exist @result._id
@ -140,41 +148,30 @@ describe "ProjectEditorHandler", ->
it "should set the deletedByExternalDataSource flag to false when it is not there", ->
delete @project.deletedByExternalDataSource
result = @handler.buildProjectModelView @project
result = @handler.buildProjectModelView @project, @members
result.deletedByExternalDataSource.should.equal false
it "should set the deletedByExternalDataSource flag to false when it is false", ->
result = @handler.buildProjectModelView @project
result = @handler.buildProjectModelView @project, @members
result.deletedByExternalDataSource.should.equal false
it "should set the deletedByExternalDataSource flag to true when it is true", ->
@project.deletedByExternalDataSource = true
result = @handler.buildProjectModelView @project
result = @handler.buildProjectModelView @project, @members
result.deletedByExternalDataSource.should.equal true
describe "features", ->
beforeEach ->
@project.owner_ref.features =
@owner.features =
versioning: true
collaborators: 3
compileGroup:"priority"
compileTimeout: 96
@result = @handler.buildProjectModelView @project
@result = @handler.buildProjectModelView @project, @members
it "should copy the owner features to the project", ->
@result.features.versioning.should.equal @project.owner_ref.features.versioning
@result.features.collaborators.should.equal @project.owner_ref.features.collaborators
@result.features.compileGroup.should.equal @project.owner_ref.features.compileGroup
@result.features.compileTimeout.should.equal @project.owner_ref.features.compileTimeout
@result.features.versioning.should.equal @owner.features.versioning
@result.features.collaborators.should.equal @owner.features.collaborators
@result.features.compileGroup.should.equal @owner.features.compileGroup
@result.features.compileTimeout.should.equal @owner.features.compileTimeout
describe "without owners and members", ->
beforeEach ->
@result = @handler.buildProjectModelView @project, includeUsers: false
it "should not include the owner", ->
should.not.exist @result.owner
it "should not include the members", ->
should.not.exist @result.members

View file

@ -16,6 +16,8 @@ describe "ProjectGetter", ->
projects: {}
users: {}
ObjectId: ObjectId
"../../models/Project": Project: @Project = {}
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {}
"logger-sharelatex":
err:->
log:->
@ -134,56 +136,16 @@ describe "ProjectGetter", ->
@db.projects.find = sinon.stub().callsArgWith(2, null, [@project])
it "should call find with the project id when string id is passed", (done)->
@ProjectGetter.getProject @project_id, (err, project)=>
@db.projects.find.calledWith(_id: ObjectId(@project_id)).should.equal true
assert.deepEqual @project, project
done()
it "should call find with the project id when object id is passed", (done)->
@ProjectGetter.getProject ObjectId(@project_id), (err, project)=>
@db.projects.find.calledWith(_id: ObjectId(@project_id)).should.equal true
assert.deepEqual @project, project
done()
it "should call the db when a mongoose objectid is used", (done)->
mongooseID = require('mongoose').Types.ObjectId(@project_id)
@ProjectGetter.getProject mongooseID, (err, project)=>
@db.projects.find.calledWith(_id: ObjectId(@project_id)).should.equal true
assert.deepEqual @project, project
done()
describe "populateProjectWithUsers", ->
describe "findAllUsersProjects", ->
beforeEach ->
@users = []
@user_lookup = {}
for i in [0..4]
@users[i] = _id: ObjectId.createPk()
@user_lookup[@users[i]._id.toString()] = @users[i]
@project =
_id: ObjectId.createPk()
owner_ref: @users[0]._id
readOnly_refs: [@users[1]._id, @users[2]._id]
collaberator_refs: [@users[3]._id, @users[4]._id]
@db.users.find = (query, callback) =>
callback null, [@user_lookup[query._id.toString()]]
sinon.spy @db.users, "find"
@ProjectGetter.populateProjectWithUsers @project, (err, project)=>
@callback err, project
it "should look up each user", ->
for user in @users
@db.users.find.calledWith(_id: user._id).should.equal true
it "should set the owner_ref to the owner", ->
@project.owner_ref.should.equal @users[0]
it "should set the readOnly_refs to the read only users", ->
expect(@project.readOnly_refs).to.deep.equal [@users[1], @users[2]]
it "should set the collaberator_refs to the collaborators", ->
expect(@project.collaberator_refs).to.deep.equal [@users[3], @users[4]]
it "should call the callback", ->
assert.deepEqual @callback.args[0][1], @project
@fields = {"mock": "fields"}
@Project.find = sinon.stub()
@Project.find.withArgs({owner_ref: @user_id}, @fields).yields(null, ["mock-owned-projects"])
@CollaboratorsHandler.getProjectsUserIsCollaboratorOf = sinon.stub()
@CollaboratorsHandler.getProjectsUserIsCollaboratorOf.withArgs(@user_id, @fields).yields(null, ["mock-rw-projects"], ["mock-ro-projects"])
@ProjectGetter.findAllUsersProjects @user_id, @fields, @callback
it "should call the callback with all the projects", ->
@callback
.calledWith(null, ["mock-owned-projects"], ["mock-rw-projects"], ["mock-ro-projects"])
.should.equal true

View file

@ -30,7 +30,7 @@ project.rootFolder[0] = rootFolder
project.rootDoc_id = rootDoc._id
describe 'ProjectLocatorTests', ->
describe 'ProjectLocator', ->
beforeEach ->
Project.getProject = (project_id, fields, callback)=>
@ -301,7 +301,7 @@ describe 'ProjectLocatorTests', ->
user_id = "123jojoidns"
stubbedProject = {name:"findThis"}
projects = [{name:"notThis"}, {name:"wellll"}, stubbedProject, {name:"Noooo"}]
Project.findAllUsersProjects = sinon.stub().callsArgWith(2, null, projects)
@ProjectGetter.findAllUsersProjects = sinon.stub().callsArgWith(2, null, projects)
@locator.findUsersProjectByName user_id, stubbedProject.name.toLowerCase(), (err, project)->
project.should.equal stubbedProject
done()
@ -310,7 +310,7 @@ describe 'ProjectLocatorTests', ->
user_id = "123jojoidns"
stubbedProject = {name:"findThis", _id:12331321}
projects = [{name:"notThis"}, {name:"wellll"}, {name:"findThis",archived:true}, stubbedProject, {name:"findThis",archived:true}, {name:"Noooo"}]
Project.findAllUsersProjects = sinon.stub().callsArgWith(2, null, projects)
@ProjectGetter.findAllUsersProjects = sinon.stub().callsArgWith(2, null, projects)
@locator.findUsersProjectByName user_id, stubbedProject.name.toLowerCase(), (err, project)->
project._id.should.equal stubbedProject._id
done()
@ -319,7 +319,7 @@ describe 'ProjectLocatorTests', ->
user_id = "123jojoidns"
stubbedProject = {name:"findThis"}
projects = [{name:"notThis"}, {name:"wellll"}, {name:"Noooo"}]
Project.findAllUsersProjects = sinon.stub().callsArgWith(2, null, projects, [stubbedProject])
@ProjectGetter.findAllUsersProjects = sinon.stub().callsArgWith(2, null, projects, [stubbedProject])
@locator.findUsersProjectByName user_id, stubbedProject.name.toLowerCase(), (err, project)->
project.should.equal stubbedProject
done()

View file

@ -39,10 +39,11 @@ describe 'ReferencesHandler', ->
get: sinon.stub()
post: sinon.stub()
}
'../../models/Project': {
Project: @Project = {
findPopulatedById: sinon.stub().callsArgWith(1, null, @fakeProject)
}
'../Project/ProjectGetter': @ProjectGetter = {
getProject: sinon.stub().callsArgWith(2, null, @fakeProject)
}
'../User/UserGetter': @UserGetter = {
getUser: sinon.stub()
}
'../DocumentUpdater/DocumentUpdaterHandler': @DocumentUpdaterHandler = {
flushDocToMongo: sinon.stub().callsArgWith(2, null)
@ -70,10 +71,10 @@ describe 'ReferencesHandler', ->
@handler._findBibDocIds.callCount.should.equal 0
done()
it 'should call Project.findPopulatedById', (done) ->
it 'should call ProjectGetter.getProject', (done) ->
@call (err, data) =>
@Project.findPopulatedById.callCount.should.equal 1
@Project.findPopulatedById.calledWith(@projectId).should.equal true
@ProjectGetter.getProject.callCount.should.equal 1
@ProjectGetter.getProject.calledWith(@projectId).should.equal true
done()
it 'should not call _findBibDocIds', (done) ->
@ -109,10 +110,10 @@ describe 'ReferencesHandler', ->
expect(data).to.equal @fakeResponseData
done()
describe 'when Project.findPopulatedById produces an error', ->
describe 'when ProjectGetter.getProject produces an error', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, new Error('woops'))
@ProjectGetter.getProject.callsArgWith(2, new Error('woops'))
it 'should produce an error', (done) ->
@call (err, data) =>
@ -129,7 +130,7 @@ describe 'ReferencesHandler', ->
describe 'when _isFullIndex produces an error', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, null, @fakeProject)
@ProjectGetter.getProject.callsArgWith(2, null, @fakeProject)
@handler._isFullIndex.callsArgWith(1, new Error('woops'))
it 'should produce an error', (done) ->
@ -147,7 +148,7 @@ describe 'ReferencesHandler', ->
describe 'when flushDocToMongo produces an error', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, null, @fakeProject)
@ProjectGetter.getProject.callsArgWith(2, null, @fakeProject)
@handler._isFullIndex.callsArgWith(1, false)
@DocumentUpdaterHandler.flushDocToMongo.callsArgWith(2, new Error('woops'))
@ -167,7 +168,7 @@ describe 'ReferencesHandler', ->
describe 'when request produces an error', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, null, @fakeProject)
@ProjectGetter.getProject.callsArgWith(2, null, @fakeProject)
@handler._isFullIndex.callsArgWith(1, null, false)
@DocumentUpdaterHandler.flushDocToMongo.callsArgWith(2, null)
@request.post.callsArgWith(1, new Error('woops'))
@ -182,7 +183,7 @@ describe 'ReferencesHandler', ->
describe 'when request responds with error status', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, null, @fakeProject)
@ProjectGetter.getProject.callsArgWith(2, null, @fakeProject)
@handler._isFullIndex.callsArgWith(1, null, false)
@request.post.callsArgWith(1, null, {statusCode: 500}, null)
@ -234,10 +235,10 @@ describe 'ReferencesHandler', ->
expect(data).to.equal @fakeResponseData
done()
describe 'when Project.findPopulatedById produces an error', ->
describe 'when ProjectGetter.getProject produces an error', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, new Error('woops'))
@ProjectGetter.getProject.callsArgWith(2, new Error('woops'))
it 'should produce an error', (done) ->
@call (err, data) =>
@ -254,7 +255,7 @@ describe 'ReferencesHandler', ->
describe 'when _isFullIndex produces an error', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, null, @fakeProject)
@ProjectGetter.getProject.callsArgWith(2, null, @fakeProject)
@handler._isFullIndex.callsArgWith(1, new Error('woops'))
it 'should produce an error', (done) ->
@ -272,7 +273,7 @@ describe 'ReferencesHandler', ->
describe 'when flushDocToMongo produces an error', ->
beforeEach ->
@Project.findPopulatedById.callsArgWith(1, null, @fakeProject)
@ProjectGetter.getProject.callsArgWith(2, null, @fakeProject)
@handler._isFullIndex.callsArgWith(1, false)
@DocumentUpdaterHandler.flushDocToMongo.callsArgWith(2, new Error('woops'))
@ -312,16 +313,19 @@ describe 'ReferencesHandler', ->
beforeEach ->
@fakeProject =
owner_ref:
features:
references: false
owner_ref: @owner_ref = "owner-ref-123"
@owner =
features:
references: false
@UserGetter.getUser = sinon.stub()
@UserGetter.getUser.withArgs(@owner_ref, {features: true}).yields(null, @owner)
@call = (callback) =>
@handler._isFullIndex @fakeProject, callback
describe 'with references feature on', ->
beforeEach ->
@fakeProject.owner_ref.features.references = true
@owner.features.references = true
it 'should return true', ->
@call (err, isFullIndex) =>
@ -331,7 +335,7 @@ describe 'ReferencesHandler', ->
describe 'with references feature off', ->
beforeEach ->
@fakeProject.owner_ref.features.references = false
@owner.features.references = false
it 'should return false', ->
@call (err, isFullIndex) =>

View file

@ -1,96 +0,0 @@
SandboxedModule = require('sandboxed-module')
sinon = require('sinon')
require('chai').should()
modulePath = require('path').join __dirname, '../../../../app/js/Features/Security/AuthorizationManager'
MockClient = require "../helpers/MockClient"
describe "AuthorizationManager", ->
beforeEach ->
@client = new MockClient()
@AuthorizationManager = SandboxedModule.require modulePath, requires:
'../../managers/SecurityManager':{}
describe "ensureClientCanViewProject", ->
beforeEach ->
@client.set("project_id", "project-id")
it "should let the request through for a readOnly privilege", (done) ->
@client.set("privilege_level", "readOnly")
@AuthorizationManager.ensureClientCanViewProject @client, done
it "should let the request through for a readAndWrite privilege", (done) ->
@client.set("privilege_level", "readAndWrite")
@AuthorizationManager.ensureClientCanViewProject @client, done
it "should let the request through for a owner privilege", (done) ->
@client.set("privilege_level", "owner")
@AuthorizationManager.ensureClientCanViewProject @client, done
it "should ignore an empty privilege", ->
@AuthorizationManager.ensureClientCanViewProject @client, () ->
throw new Error("Should not be called")
describe "ensureClientCanEditProject", ->
beforeEach ->
@client.set("project_id", "project-id")
it "should ignore a readOnly privilege", ->
@client.set("privilege_level", "readOnly")
@AuthorizationManager.ensureClientCanEditProject @client, () ->
throw new Error("Should not be called")
it "should let the request through for a readAndWrite privilege", (done) ->
@client.set("privilege_level", "readAndWrite")
@AuthorizationManager.ensureClientCanEditProject @client, done
it "should let the request through for a owner privilege", (done) ->
@client.set("privilege_level", "owner")
@AuthorizationManager.ensureClientCanEditProject @client, done
it "should ignore an empty privilege", ->
@AuthorizationManager.ensureClientCanEditProject @client, () ->
throw new Error("Should not be called")
describe "ensureClientCanAdminProject", ->
beforeEach ->
@client.set("project_id", "project-id")
it "should ignore a readOnly privilege", ->
@client.set("privilege_level", "readOnly")
@AuthorizationManager.ensureClientCanAdminProject @client, () ->
throw new Error("Should not be called")
it "should ignore a readAndWrite privilege", ->
@client.set("privilege_level", "readAndWrite")
@AuthorizationManager.ensureClientCanAdminProject @client, () ->
throw new Error("Should not be called")
it "should let the request through for a owner privilege", (done) ->
@client.set("privilege_level", "owner")
@AuthorizationManager.ensureClientCanAdminProject @client, done
it "should ignore an empty privilege", ->
@AuthorizationManager.ensureClientCanAdminProject @client, () ->
throw new Error("Should not be called")
describe "ensureClientHasPrivilegeLevelForProject", ->
it "should ignore callback if privilege_level is not set", ->
@client.set("project_id", "project-id")
@AuthorizationManager.ensureClientHasPrivilegeLevelForProject @client,
["owner"], (error, project_id) ->
throw new Error("Should not be called")
it "should ignore callback if project_id is not set", ->
@client.set("privilege_level", "owner")
@AuthorizationManager.ensureClientHasPrivilegeLevelForProject @client,
["owner"], (error, project_id) ->
throw new Error("Should not be called")
it "should return the project_id", (done) ->
@client.set("privilege_level", "owner")
@client.set("project_id", "project-id-123")
@AuthorizationManager.ensureClientHasPrivilegeLevelForProject @client,
["owner"], (error, project_id) ->
project_id.should.equal "project-id-123"
done()

View file

@ -29,6 +29,7 @@ describe "LimitationsManager", ->
'../../models/User' : User: @User
'./SubscriptionLocator':@SubscriptionLocator
'settings-sharelatex' : @Settings = {}
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {}
'logger-sharelatex':log:->
describe "allowedNumberOfCollaboratorsInProject", ->
@ -51,22 +52,10 @@ describe "LimitationsManager", ->
it "should return the number of collaborators the user is allowed", ->
@callback.calledWith(null, @user.features.collaborators).should.equal true
describe "currentNumberOfCollaboratorsInProject", ->
beforeEach ->
@project.collaberator_refs = ["one", "two"]
@project.readOnly_refs = ["three"]
@callback = sinon.stub()
@LimitationsManager.currentNumberOfCollaboratorsInProject(@project_id, @callback)
it "should return the total number of collaborators", ->
@callback.calledWith(null, 3).should.equal true
describe "canAddXCollaborators", ->
beforeEach ->
sinon.stub @LimitationsManager,
"currentNumberOfCollaboratorsInProject",
(project_id, callback) => callback(null, @current_number)
@CollaboratorsHandler.getCollaboratorCount = (project_id, callback) => callback(null, @current_number)
sinon.stub @LimitationsManager,
"allowedNumberOfCollaboratorsInProject",
(project_id, callback) => callback(null, @allowed_number)

View file

@ -23,8 +23,8 @@ describe "SubscriptionController sanboxed", ->
@user = {email:"tom@yahoo.com"}
@activeRecurlySubscription = mockSubscriptions["subscription-123-active"]
@SecurityManager =
getCurrentUser: sinon.stub().callsArgWith(1, null, @user)
@AuthenticationController =
getLoggedInUser: sinon.stub().callsArgWith(1, null, @user)
@SubscriptionHandler =
createSubscription: sinon.stub().callsArgWith(3)
updateSubscription: sinon.stub().callsArgWith(3)
@ -61,7 +61,7 @@ describe "SubscriptionController sanboxed", ->
@SubscriptionDomainHandler =
getDomainLicencePage:sinon.stub()
@SubscriptionController = SandboxedModule.require modulePath, requires:
'../../managers/SecurityManager': @SecurityManager
'../Authentication/AuthenticationController': @AuthenticationController
'./SubscriptionHandler': @SubscriptionHandler
"./PlansLocator": @PlansLocator
'./SubscriptionViewModelBuilder': @SubscriptionViewModelBuilder

View file

@ -20,7 +20,10 @@ filestoreUrl = "filestore.sharelatex.com"
describe 'TpdsUpdateSender', ->
beforeEach ->
@requestQueuer = (queue, meth, opts, callback)->
project = {owner_ref:user_id,readOnly_refs:[read_only_ref_1], collaberator_refs:[collaberator_ref_1]}
project = {owner_ref:user_id}
member_ids = [collaberator_ref_1, read_only_ref_1, user_id]
@CollaboratorsHandler =
getMemberIds: sinon.stub().yields(null, member_ids)
@Project = findById:sinon.stub().callsArgWith(2, null, project)
@docstoreUrl = "docstore.sharelatex.env"
@request = sinon.stub().returns(pipe:->)
@ -38,6 +41,7 @@ describe 'TpdsUpdateSender', ->
"logger-sharelatex":{log:->}
'../../models/Project': Project:@Project
'request':@request
'../Collaborators/CollaboratorsHandler': @CollaboratorsHandler
"../../infrastructure/Metrics":
inc:->

View file

@ -39,7 +39,7 @@ describe "ArchiveManager", ->
describe "successfully", ->
beforeEach (done) ->
@ArchiveManager.extractZipArchive @source, @destination, done
@process.emit "exit"
@process.emit "close"
it "should run unzip", ->
@child.spawn.calledWithExactly("unzip", [@source, "-d", @destination]).should.equal true
@ -56,7 +56,7 @@ describe "ArchiveManager", ->
@callback(error)
done()
@process.stderr.emit "data", "Something went wrong"
@process.emit "exit"
@process.emit "close"
it "should return the callback with an error", ->
@callback.calledWithExactly(new Error("Something went wrong")).should.equal true
@ -99,35 +99,35 @@ describe "ArchiveManager", ->
isTooLarge.should.equal false
done()
@process.stdout.emit "data", @output("109042")
@process.emit "exit"
@process.emit "close"
it "should return true with large bytes", (done)->
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
isTooLarge.should.equal true
done()
@process.stdout.emit "data", @output("1090000000000000042")
@process.emit "exit"
@process.emit "close"
it "should return error on no data", (done)->
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
expect(error).to.exist
done()
@process.stdout.emit "data", ""
@process.emit "exit"
@process.emit "close"
it "should return error if it didn't get a number", (done)->
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
expect(error).to.exist
done()
@process.stdout.emit "data", @output("total_size_string")
@process.emit "exit"
@process.emit "close"
it "should return error if the is only a bit of data", (done)->
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
expect(error).to.exist
done()
@process.stdout.emit "data", " Length Date Time Name \n--------"
@process.emit "exit"
@process.emit "close"
describe "findTopLevelDirectory", ->
beforeEach ->

View file

@ -0,0 +1,306 @@
expect = require("chai").expect
async = require("async")
User = require "./helpers/User"
request = require "./helpers/request"
settings = require "settings-sharelatex"
try_read_access = (user, project_id, test, callback) ->
async.series [
(cb) ->
user.request.get "/project/#{project_id}", (error, response, body) ->
return cb(error) if error?
test(response, body)
cb()
(cb) ->
user.request.get "/project/#{project_id}/download/zip", (error, response, body) ->
return cb(error) if error?
test(response, body)
cb()
], callback
try_settings_write_access = (user, project_id, test, callback) ->
async.series [
(cb) ->
user.request.post {
uri: "/project/#{project_id}/settings"
json:
compiler: "latex"
}, (error, response, body) ->
return cb(error) if error?
test(response, body)
cb()
], callback
try_admin_access = (user, project_id, test, callback) ->
async.series [
(cb) ->
user.request.post {
uri: "/project/#{project_id}/rename"
json:
newProjectName: "new-name"
}, (error, response, body) ->
return cb(error) if error?
test(response, body)
cb()
(cb) ->
user.request.post {
uri: "/project/#{project_id}/settings/admin"
json:
publicAccessLevel: "private"
}, (error, response, body) ->
return cb(error) if error?
test(response, body)
cb()
], callback
try_content_access = (user, project_id, test, callback) ->
# The real-time service calls this end point to determine the user's
# permissions.
if user.id?
user_id = user.id
else
user_id = "anonymous-user"
request.post {
url: "/project/#{project_id}/join"
qs: {user_id}
auth:
user: settings.apis.web.user
pass: settings.apis.web.pass
sendImmediately: true
json: true
jar: false
}, (error, response, body) ->
return callback(error) if error?
test(response, body)
callback()
expect_read_access = (user, project_id, callback) ->
async.series [
(cb) ->
try_read_access(user, project_id, (response, body) ->
expect(response.statusCode).to.be.oneOf [200, 204]
, cb)
(cb) ->
try_content_access(user, project_id, (response, body) ->
expect(body.privilegeLevel).to.be.oneOf ["owner", "readAndWrite", "readOnly"]
, cb)
], callback
expect_content_write_access = (user, project_id, callback) ->
try_content_access(user, project_id, (response, body) ->
expect(body.privilegeLevel).to.be.oneOf ["owner", "readAndWrite"]
, callback)
expect_settings_write_access = (user, project_id, callback) ->
try_settings_write_access(user, project_id, (response, body) ->
expect(response.statusCode).to.be.oneOf [200, 204]
, callback)
expect_admin_access = (user, project_id, callback) ->
try_admin_access(user, project_id, (response, body) ->
expect(response.statusCode).to.be.oneOf [200, 204]
, callback)
expect_no_read_access = (user, project_id, options, callback) ->
async.series [
(cb) ->
try_read_access(user, project_id, (response, body) ->
expect(response.statusCode).to.equal 302
expect(response.headers.location).to.match new RegExp(options.redirect_to)
, cb)
(cb) ->
try_content_access(user, project_id, (response, body) ->
expect(body.privilegeLevel).to.be.equal false
, cb)
], callback
expect_no_content_write_access = (user, project_id, callback) ->
try_content_access(user, project_id, (response, body) ->
expect(body.privilegeLevel).to.be.oneOf [false, "readOnly"]
, callback)
expect_no_settings_write_access = (user, project_id, options, callback) ->
try_settings_write_access(user, project_id, (response, body) ->
expect(response.statusCode).to.equal 302
expect(response.headers.location).to.match new RegExp(options.redirect_to)
, callback)
expect_no_admin_access = (user, project_id, options, callback) ->
try_admin_access(user, project_id, (response, body) ->
expect(response.statusCode).to.equal 302
expect(response.headers.location).to.match new RegExp(options.redirect_to)
, callback)
describe "Authorization", ->
before (done) ->
@timeout(10000)
@owner = new User()
@other1 = new User()
@other2 = new User()
@anon = new User()
@site_admin = new User({email: "admin@example.com"})
async.parallel [
(cb) => @owner.login cb
(cb) => @other1.login cb
(cb) => @other2.login cb
(cb) => @anon.getCsrfToken cb
(cb) =>
@site_admin.login (err) =>
return cb(err) if error?
@site_admin.ensure_admin cb
], done
describe "private project", ->
before (done) ->
@owner.createProject "private-project", (error, project_id) =>
return done(error) if error?
@project_id = project_id
done()
it "should allow the owner read access to it", (done) ->
expect_read_access @owner, @project_id, done
it "should allow the owner write access to its content", (done) ->
expect_content_write_access @owner, @project_id, done
it "should allow the owner write access to its settings", (done) ->
expect_settings_write_access @owner, @project_id, done
it "should allow the owner admin access to it", (done) ->
expect_admin_access @owner, @project_id, done
it "should not allow another user read access to the project", (done) ->
expect_no_read_access @other1, @project_id, redirect_to: "/restricted", done
it "should not allow another user write access to its content", (done) ->
expect_no_content_write_access @other1, @project_id, done
it "should not allow another user write access to its settings", (done) ->
expect_no_settings_write_access @other1, @project_id, redirect_to: "/restricted", done
it "should not allow another user admin access to it", (done) ->
expect_no_admin_access @other1, @project_id, redirect_to: "/restricted", done
it "should not allow anonymous user read access to it", (done) ->
expect_no_read_access @anon, @project_id, redirect_to: "/restricted", done
it "should not allow anonymous user write access to its content", (done) ->
expect_no_content_write_access @anon, @project_id, done
it "should not allow anonymous user write access to its settings", (done) ->
expect_no_settings_write_access @anon, @project_id, redirect_to: "/restricted", done
it "should not allow anonymous user admin access to it", (done) ->
expect_no_admin_access @anon, @project_id, redirect_to: "/restricted", done
it "should allow site admin users read access to it", (done) ->
expect_read_access @site_admin, @project_id, done
it "should allow site admin users write access to its content", (done) ->
expect_content_write_access @site_admin, @project_id, done
it "should allow site admin users write access to its settings", (done) ->
expect_settings_write_access @site_admin, @project_id, done
it "should allow site admin users admin access to it", (done) ->
expect_admin_access @site_admin, @project_id, done
describe "shared project", ->
before (done) ->
@rw_user = @other1
@ro_user = @other2
@owner.createProject "private-project", (error, project_id) =>
return done(error) if error?
@project_id = project_id
@owner.addUserToProject @project_id, @ro_user.email, "readOnly", (error) =>
return done(error) if error?
@owner.addUserToProject @project_id, @rw_user.email, "readAndWrite", (error) =>
return done(error) if error?
done()
it "should allow the read-only user read access to it", (done) ->
expect_read_access @ro_user, @project_id, done
it "should not allow the read-only user write access to its content", (done) ->
expect_no_content_write_access @ro_user, @project_id, done
it "should not allow the read-only user write access to its settings", (done) ->
expect_no_settings_write_access @ro_user, @project_id, redirect_to: "/restricted", done
it "should not allow the read-only user admin access to it", (done) ->
expect_no_admin_access @ro_user, @project_id, redirect_to: "/restricted", done
it "should allow the read-write user read access to it", (done) ->
expect_read_access @rw_user, @project_id, done
it "should allow the read-write user write access to its content", (done) ->
expect_content_write_access @rw_user, @project_id, done
it "should allow the read-write user write access to its settings", (done) ->
expect_settings_write_access @rw_user, @project_id, done
it "should not allow the read-write user admin access to it", (done) ->
expect_no_admin_access @rw_user, @project_id, redirect_to: "/restricted", done
describe "public read-write project", ->
before (done) ->
@owner.createProject "public-rw-project", (error, project_id) =>
return done(error) if error?
@project_id = project_id
@owner.makePublic @project_id, "readAndWrite", done
it "should allow a user read access to it", (done) ->
expect_read_access @other1, @project_id, done
it "should allow a user write access to its content", (done) ->
expect_content_write_access @other1, @project_id, done
it "should not allow a user write access to its settings", (done) ->
expect_no_settings_write_access @other1, @project_id, redirect_to: "/restricted", done
it "should not allow a user admin access to it", (done) ->
expect_no_admin_access @other1, @project_id, redirect_to: "/restricted", done
it "should allow an anonymous user read access to it", (done) ->
expect_read_access @anon, @project_id, done
it "should allow an anonymous user write access to its content", (done) ->
expect_content_write_access @anon, @project_id, done
it "should not allow an anonymous user write access to its settings", (done) ->
expect_no_settings_write_access @anon, @project_id, redirect_to: "/restricted", done
it "should not allow an anonymous user admin access to it", (done) ->
expect_no_admin_access @anon, @project_id, redirect_to: "/restricted", done
describe "public read-only project", ->
before (done) ->
@owner.createProject "public-ro-project", (error, project_id) =>
return done(error) if error?
@project_id = project_id
@owner.makePublic @project_id, "readOnly", done
it "should allow a user read access to it", (done) ->
expect_read_access @other1, @project_id, done
it "should not allow a user write access to its content", (done) ->
expect_no_content_write_access @other1, @project_id, done
it "should not allow a user write access to its settings", (done) ->
expect_no_settings_write_access @other1, @project_id, redirect_to: "/restricted", done
it "should not allow a user admin access to it", (done) ->
expect_no_admin_access @other1, @project_id, redirect_to: "/restricted", done
it "should allow an anonymous user read access to it", (done) ->
expect_read_access @anon, @project_id, done
it "should not allow an anonymous user write access to its content", (done) ->
expect_no_content_write_access @anon, @project_id, done
it "should not allow an anonymous user write access to its settings", (done) ->
expect_no_settings_write_access @anon, @project_id, redirect_to: "/restricted", done
it "should not allow an anonymous user admin access to it", (done) ->
expect_no_admin_access @anon, @project_id, redirect_to: "/restricted", done

View file

@ -0,0 +1,21 @@
expect = require("chai").expect
async = require("async")
User = require "./helpers/User"
describe "Project CRUD", ->
before (done) ->
@user = new User()
@user.login done
describe "when project doesn't exist", ->
it "should return 404", (done) ->
@user.request.get "/project/aaaaaaaaaaaaaaaaaaaaaaaa", (err, res, body) ->
expect(res.statusCode).to.equal 404
done()
describe "when project has malformed id", ->
it "should return 404", (done) ->
@user.request.get "/project/blah", (err, res, body) ->
expect(res.statusCode).to.equal 404
done()

View file

@ -0,0 +1,77 @@
request = require("./request")
settings = require("settings-sharelatex")
{db, ObjectId} = require("../../../../app/js/infrastructure/mongojs")
count = 0
class User
constructor: (options = {}) ->
@email = "acceptance-test-#{count}@example.com"
@password = "acceptance-test-#{count}-password"
count++
@jar = request.jar()
@request = request.defaults({
jar: @jar
})
login: (callback = (error) ->) ->
@getCsrfToken (error) =>
return callback(error) if error?
@request.post {
url: "/register" # Register will log in, but also ensure user exists
json:
email: @email
password: @password
}, (error, response, body) =>
return callback(error) if error?
db.users.findOne {email: @email}, (error, user) =>
return callback(error) if error?
@id = user?._id?.toString()
callback()
ensure_admin: (callback = (error) ->) ->
db.users.update {_id: ObjectId(@id)}, { $set: { isAdmin: true }}, callback
createProject: (name, callback = (error, project_id) ->) ->
@request.post {
url: "/project/new",
json:
projectName: name
}, (error, response, body) ->
return callback(error) if error?
if !body?.project_id?
console.error "SOMETHING WENT WRONG CREATING PROJECT", response.statusCode, response.headers["location"], body
callback(null, body.project_id)
addUserToProject: (project_id, email, privileges, callback = (error, user) ->) ->
@request.post {
url: "/project/#{project_id}/users",
json: {email, privileges}
}, (error, response, body) ->
return callback(error) if error?
callback(null, body.user)
makePublic: (project_id, level, callback = (error) ->) ->
@request.post {
url: "/project/#{project_id}/settings/admin",
json:
publicAccessLevel: level
}, (error, response, body) ->
return callback(error) if error?
callback(null)
getCsrfToken: (callback = (error) ->) ->
@request.get {
url: "/register"
}, (err, response, body) =>
return callback(error) if error?
csrfMatches = body.match("window.csrfToken = \"(.*?)\";")
if !csrfMatches?
return callback(new Error("no csrf token found"))
@request = @request.defaults({
headers:
"x-csrf-token": csrfMatches[1]
})
callback()
module.exports = User

View file

@ -0,0 +1,5 @@
BASE_URL = "http://localhost:3000"
module.exports = require("request").defaults({
baseUrl: BASE_URL,
followRedirect: false
})