mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-04 22:59:01 +02:00
Merge branch 'master' into templatessearch
This commit is contained in:
@@ -40,6 +40,7 @@ app.js
|
||||
app/js/*
|
||||
test/UnitTests/js/*
|
||||
test/smoke/js/*
|
||||
test/acceptance/js/*
|
||||
cookies.txt
|
||||
requestQueueWorker.js
|
||||
TpdsWorker.js
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports =
|
||||
NONE: false
|
||||
READ_ONLY: "readOnly"
|
||||
READ_AND_WRITE: "readAndWrite"
|
||||
OWNER: "owner"
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports =
|
||||
READ_ONLY: "readOnly"
|
||||
READ_AND_WRITE: "readAndWrite"
|
||||
PRIVATE: "private"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -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()
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,12 +80,12 @@ 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)
|
||||
if subscription?.customAccount
|
||||
logger.log user: user, "redirecting to plans"
|
||||
logger.log user: user, "redirecting to custom account page"
|
||||
res.redirect "/user/subscription/custom_account"
|
||||
else if groupLicenceInviteUrl? and !hasSubOrIsGroupMember
|
||||
logger.log user:user, "redirecting to group subscription invite page"
|
||||
@@ -109,15 +109,19 @@ module.exports = SubscriptionController =
|
||||
|
||||
|
||||
userCustomSubscriptionPage: (req, res, next)->
|
||||
SecurityManager.getCurrentUser req, (error, user) ->
|
||||
AuthenticationController.getLoggedInUser req, (error, user) ->
|
||||
LimitationsManager.userHasSubscriptionOrIsGroupMember user, (err, hasSubOrIsGroupMember, subscription)->
|
||||
if !subscription?
|
||||
err = new Error("subscription null for custom account, user:#{user?._id}")
|
||||
logger.warn err:err, "subscription is null for custom accounts page"
|
||||
return next(err)
|
||||
res.render "subscriptions/custom_account",
|
||||
title: "your_subscription"
|
||||
subscription: subscription
|
||||
|
||||
|
||||
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 +142,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 +154,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 +170,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 +180,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 +199,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 +216,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 +229,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?
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -64,6 +64,12 @@ module.exports = (app, webRouter, apiRouter)->
|
||||
Settings.siteUrl.substring(Settings.siteUrl.indexOf("//")+2)
|
||||
next()
|
||||
|
||||
webRouter.use (req, res, next)->
|
||||
res.locals.getUserEmail = ->
|
||||
email = req?.session?.user?.email or ""
|
||||
return email
|
||||
next()
|
||||
|
||||
webRouter.use (req, res, next)->
|
||||
res.locals.formatProjectPublicAccessLevel = (privilegeLevel)->
|
||||
formatedPrivileges = private:"Private", readOnly:"Public: Read Only", readAndWrite:"Public: Read and Write"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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+''
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,30 @@
|
||||
script(type='text/ng-template', id='supportModalTemplate')
|
||||
.modal-header
|
||||
button.close(
|
||||
type="button"
|
||||
data-dismiss="modal"
|
||||
ng-click="close()"
|
||||
) ×
|
||||
h3 Contact Us
|
||||
.modal-body.contact-us-modal
|
||||
span(ng-show="sent == false")
|
||||
label
|
||||
| Subject
|
||||
.form-group
|
||||
input.field.text.medium.span8.form-control(ng-model="form.subject", maxlength='255', tabindex='1', onkeyup='')
|
||||
label.desc
|
||||
| Email
|
||||
.form-group
|
||||
input.field.text.medium.span8.form-control(ng-model="form.email", ng-init="form.email = '#{getUserEmail()}'", type='email', spellcheck='false', value='', maxlength='255', tabindex='2')
|
||||
label#title12.desc
|
||||
| Project URL (optional)
|
||||
.form-group
|
||||
input.field.text.medium.span8.form-control(ng-model="form.project_url", tabindex='3', onkeyup='')
|
||||
label.desc
|
||||
| Message
|
||||
.form-group
|
||||
textarea.field.text.medium.span8.form-control(ng-model="form.message",type='text', value='', maxlength='255', tabindex='4', onkeyup='')
|
||||
.form-group.text-center
|
||||
input.btn-success.btn.btn-lg(type='submit', ng-disabled="sending", ng-click="contactUs()" value='Get in Touch')
|
||||
span(ng-show="sent")
|
||||
p Request Sent, Thank you.
|
||||
@@ -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
|
||||
@@ -25,6 +25,7 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
each subdomainDetails in settings.i18n.subdomainLang
|
||||
link(rel="alternate", href=subdomainDetails.url+currentUrl, hreflang=subdomainDetails.lngCode)
|
||||
|
||||
|
||||
meta(itemprop="name", content="ShareLaTeX, the Online LaTeX Editor")
|
||||
|
||||
-if (typeof(meta) == "undefined")
|
||||
@@ -60,8 +61,8 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
sixpackDomain: '#{settings.sixpack.domain}'
|
||||
};
|
||||
window.systemMessages = !{JSON.stringify(systemMessages).replace(/\//g, '\\/')};
|
||||
window.ab = {}
|
||||
window.user_id = '#{getLoggedInUserId()}'
|
||||
window.ab = {};
|
||||
window.user_id = '#{getLoggedInUserId()}';
|
||||
|
||||
- if (typeof(settings.algolia) != "undefined")
|
||||
script.
|
||||
@@ -80,6 +81,7 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
}
|
||||
|
||||
body
|
||||
|
||||
- if(typeof(suppressSystemMessages) == "undefined")
|
||||
.system-messages(
|
||||
ng-cloak
|
||||
@@ -118,21 +120,14 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
"paths" : {
|
||||
"moment": "libs/moment-2.7.0"
|
||||
}
|
||||
};
|
||||
};
|
||||
script(
|
||||
data-main=jsPath+'main.js',
|
||||
baseurl=jsPath,
|
||||
src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
|
||||
)
|
||||
|
||||
- if (typeof(tenderUrl) != "undefined")
|
||||
script(src="https://#{tenderUrl}/tender_widget.js" )
|
||||
script(type="text/javascript").
|
||||
Tender = {
|
||||
hideToggle: true,
|
||||
widgetToggles: $(".js-tender-widget"),
|
||||
category: "questions"
|
||||
};
|
||||
include contact-us-modal
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -70,23 +70,23 @@ aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected'
|
||||
ng-repeat="entity in rootFolder.children | orderBy:[orderByFoldersFirst, 'name']"
|
||||
)
|
||||
|
||||
div(ng-show="deletedDocs.length > 0 && ui.view == 'track-changes'")
|
||||
h3 #{translate("deleted_files")}
|
||||
ul.list-unstyled.file-tree-list.deleted-docs
|
||||
li(
|
||||
ng-class="{ 'selected': entity.selected }",
|
||||
ng-repeat="entity in deletedDocs | orderBy:'name'",
|
||||
ng-controller="FileTreeEntityController"
|
||||
)
|
||||
.entity
|
||||
.entity-name(
|
||||
ng-click="select($event)"
|
||||
)
|
||||
//- Just a spacer to align with folders
|
||||
i.fa.fa-fw.toggle
|
||||
i.fa.fa-fw.fa-file
|
||||
li(ng-show="deletedDocs.length > 0 && ui.view == 'track-changes'")
|
||||
h3 #{translate("deleted_files")}
|
||||
li(
|
||||
ng-class="{ 'selected': entity.selected }",
|
||||
ng-repeat="entity in deletedDocs | orderBy:'name'",
|
||||
ng-controller="FileTreeEntityController",
|
||||
ng-show="ui.view == 'track-changes'"
|
||||
)
|
||||
.entity
|
||||
.entity-name(
|
||||
ng-click="select($event)"
|
||||
)
|
||||
//- Just a spacer to align with folders
|
||||
i.fa.fa-fw.toggle
|
||||
i.fa.fa-fw.fa-file
|
||||
|
||||
span {{ entity.name }}
|
||||
span {{ entity.name }}
|
||||
|
||||
script(type='text/ng-template', id='entityListItemTemplate')
|
||||
li(
|
||||
|
||||
@@ -32,7 +32,7 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
a.log-btn(
|
||||
href
|
||||
ng-click="toggleLogs()"
|
||||
ng-class="{ 'active': (pdf.view == 'logs' || pdf.failure) && !pdf.error && !pdf.timeout && !pdf.uncompiled }"
|
||||
ng-class="{ 'active': shouldShowLogs == true }"
|
||||
tooltip="#{translate('logs_and_output_files')}"
|
||||
tooltip-placement="bottom"
|
||||
)
|
||||
@@ -74,95 +74,10 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
)
|
||||
i.split-screen
|
||||
i.split-screen
|
||||
// end of toolbar
|
||||
|
||||
.pdf-viewer(ng-show="pdf.url && pdf.view == 'pdf' && !pdf.failure && !pdf.timeout && !pdf.error")
|
||||
div(
|
||||
pdfng
|
||||
ng-if="settings.pdfViewer == 'pdfjs'"
|
||||
pdf-src="pdf.url"
|
||||
key="{{ project_id }}"
|
||||
resize-on="layout:main:resize,layout:pdf:resize"
|
||||
highlights="pdf.highlights"
|
||||
position="pdf.position"
|
||||
dbl-click-callback="syncToCode"
|
||||
)
|
||||
|
||||
iframe(
|
||||
ng-src="{{ pdf.url }}"
|
||||
ng-if="settings.pdfViewer == 'native'"
|
||||
)
|
||||
|
||||
.pdf-uncompiled(ng-show="pdf.uncompiled && !pdf.compiling")
|
||||
|
|
||||
i.fa.fa-level-up.fa-flip-horizontal.fa-2x
|
||||
| #{translate('click_here_to_preview_pdf')}
|
||||
|
||||
.pdf-errors(ng-show="pdf.timedout || pdf.error")
|
||||
.alert.alert-danger(ng-show="pdf.error")
|
||||
strong #{translate("server_error")}
|
||||
span #{translate("somthing_went_wrong_compiling")}
|
||||
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.timedout")
|
||||
p
|
||||
strong #{translate("timedout")}.
|
||||
span #{translate("proj_timed_out_reason")}
|
||||
p
|
||||
a.text-info(href="https://www.sharelatex.com/learn/Debugging_Compilation_timeout_errors", target="_blank")
|
||||
| #{translate("learn_how_to_make_documents_compile_quickly")}
|
||||
|
||||
.alert.alert-success(ng-show="pdf.timedout && !hasPremiumCompile")
|
||||
p(ng-if="project.owner._id == user.id")
|
||||
strong #{translate("upgrade_for_faster_compiles")}
|
||||
p(ng-if="project.owner._id != user.id")
|
||||
strong #{translate("ask_proj_owner_to_upgrade_for_faster_compiles")}
|
||||
p #{translate("free_accounts_have_timeout_upgrade_to_increase")}
|
||||
p Plus:
|
||||
p
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("unlimited_projects")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("full_doc_history")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_dropbox")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_github")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
|#{translate("compile_larger_projects")}
|
||||
|
||||
p(ng-controller="FreeTrialModalController", ng-if="project.owner._id == user.id")
|
||||
a.btn.btn-success.row-spaced-small(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
sixpack-convert="track_changes_feature_info"
|
||||
ng-click="startFreeTrial('compile-timeout')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
.pdf-errors(ng-show="pdf.projectTooLarge")
|
||||
.alert.alert-danger
|
||||
strong #{translate("project_too_large")}
|
||||
span #{translate("project_too_large_please_reduce")}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.pdf-logs(ng-show="(pdf.view == 'logs' || pdf.failure) && !pdf.error && !pdf.timeout && !pdf.uncompiled")
|
||||
// logs view
|
||||
.pdf-logs(ng-show="shouldShowLogs")
|
||||
.alert.alert-success(ng-show="pdf.logEntries.all.length == 0")
|
||||
| #{translate("no_errors_good_job")}
|
||||
|
||||
@@ -214,6 +129,90 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
span(ng-show="pdf.showRawLog") #{translate("hide_raw_logs")}
|
||||
|
||||
pre(ng-bind="pdf.rawLog", ng-show="pdf.showRawLog")
|
||||
|
||||
|
||||
// non-log views (pdf and errors)
|
||||
div(ng-show="!shouldShowLogs", ng-switch on="pdf.view")
|
||||
.pdf-uncompiled(ng-switch-when="uncompiled" ng-show="!pdf.compiling")
|
||||
|
|
||||
i.fa.fa-level-up.fa-flip-horizontal.fa-2x
|
||||
| #{translate('click_here_to_preview_pdf')}
|
||||
|
||||
.pdf-viewer(ng-switch-when="pdf")
|
||||
div(
|
||||
pdfng
|
||||
ng-if="settings.pdfViewer == 'pdfjs'"
|
||||
pdf-src="pdf.url"
|
||||
key="{{ project_id }}"
|
||||
resize-on="layout:main:resize,layout:pdf:resize"
|
||||
highlights="pdf.highlights"
|
||||
position="pdf.position"
|
||||
dbl-click-callback="syncToCode"
|
||||
)
|
||||
iframe(
|
||||
ng-src="{{ pdf.url }}"
|
||||
ng-if="settings.pdfViewer == 'native'"
|
||||
)
|
||||
|
||||
.pdf-errors(ng-switch-when="errors")
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.error || pdf.renderingError")
|
||||
strong #{translate("server_error")}
|
||||
span #{translate("somthing_went_wrong_compiling")}
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.clsiMaintenance")
|
||||
strong #{translate("server_error")}
|
||||
span #{translate("clsi_maintenance")}
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.tooRecentlyCompiled")
|
||||
strong #{translate("server_error")}
|
||||
span #{translate("too_recently_compiled")}
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.timedout")
|
||||
p
|
||||
strong #{translate("timedout")}.
|
||||
span #{translate("proj_timed_out_reason")}
|
||||
p
|
||||
a.text-info(href="https://www.sharelatex.com/learn/Debugging_Compilation_timeout_errors", target="_blank")
|
||||
| #{translate("learn_how_to_make_documents_compile_quickly")}
|
||||
|
||||
.alert.alert-success(ng-show="pdf.timedout && !hasPremiumCompile")
|
||||
p(ng-if="project.owner._id == user.id")
|
||||
strong #{translate("upgrade_for_faster_compiles")}
|
||||
p(ng-if="project.owner._id != user.id")
|
||||
strong #{translate("ask_proj_owner_to_upgrade_for_faster_compiles")}
|
||||
p #{translate("free_accounts_have_timeout_upgrade_to_increase")}
|
||||
p Plus:
|
||||
p
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("unlimited_projects")}
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("full_doc_history")}
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_dropbox")}
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_github")}
|
||||
li
|
||||
i.fa.fa-check
|
||||
|#{translate("compile_larger_projects")}
|
||||
p(ng-controller="FreeTrialModalController", ng-if="project.owner._id == user.id")
|
||||
a.btn.btn-success.row-spaced-small(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
sixpack-convert="track_changes_feature_info"
|
||||
ng-click="startFreeTrial('compile-timeout')"
|
||||
) #{translate("start_free_trial")}
|
||||
// // // //
|
||||
|
||||
|
||||
|
||||
script(type='text/ng-template', id='clearCacheModalTemplate')
|
||||
.modal-header
|
||||
|
||||
@@ -183,7 +183,9 @@ script(type='text/ng-template', id='deleteProjectsModalTemplate')
|
||||
data-dismiss="modal"
|
||||
ng-click="cancel()"
|
||||
) ×
|
||||
h3 {{action}} #{translate("projects")}
|
||||
h3(ng-if="action == 'delete'") #{translate("delete_projects")}
|
||||
h3(ng-if="action == 'leave'") #{translate("leave_projects")}
|
||||
h3(ng-if="action == 'delete-and-leave'") #{translate("delete_and_leave_projects")}
|
||||
.modal-body
|
||||
div(ng-show="projectsToDelete.length > 0")
|
||||
p #{translate("about_to_delete_projects")}
|
||||
@@ -201,7 +203,7 @@ script(type='text/ng-template', id='deleteProjectsModalTemplate')
|
||||
) #{translate("cancel")}
|
||||
button.btn.btn-danger(
|
||||
ng-click="delete()"
|
||||
) {{action}}
|
||||
) #{translate("confirm")}
|
||||
|
||||
script(type="text/ng-template", id="uploadProjectModalTemplate")
|
||||
.modal-header
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
.btn-toolbar(ng-show="filter != 'archived'")
|
||||
.btn-group(ng-hide="selectedProjects.length < 1")
|
||||
a.btn.btn-default(
|
||||
href='#',
|
||||
href,
|
||||
tooltip="#{translate('download')}",
|
||||
tooltip-placement="bottom",
|
||||
tooltip-append-to-body="true",
|
||||
@@ -32,7 +32,7 @@
|
||||
)
|
||||
i.fa.fa-cloud-download
|
||||
a.btn.btn-default(
|
||||
href='#',
|
||||
href,
|
||||
tooltip="#{translate('delete')}",
|
||||
tooltip-placement="bottom",
|
||||
tooltip-append-to-body="true",
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
.btn-group.dropdown(ng-hide="selectedProjects.length < 1", dropdown)
|
||||
a.btn.btn-default.dropdown-toggle(
|
||||
href="#",
|
||||
href,
|
||||
data-toggle="dropdown",
|
||||
dropdown-toggle,
|
||||
tooltip="#{translate('add_to_folders')}",
|
||||
@@ -72,11 +72,11 @@
|
||||
| {{tag.name}}
|
||||
li.divider
|
||||
li
|
||||
a(href="#", ng-click="openNewTagModal()", stop-propagation="click") #{translate("create_new_folder")}
|
||||
a(href, ng-click="openNewTagModal()", stop-propagation="click") #{translate("create_new_folder")}
|
||||
|
||||
.btn-group(ng-hide="selectedProjects.length != 1", dropdown).dropdown
|
||||
a.btn.btn-default.dropdown-toggle(
|
||||
href='#',
|
||||
href,
|
||||
data-toggle="dropdown",
|
||||
dropdown-toggle
|
||||
) #{translate("more")}
|
||||
@@ -84,19 +84,19 @@
|
||||
ul.dropdown-menu.dropdown-menu-right(role="menu")
|
||||
li(ng-show="getFirstSelectedProject().accessLevel == 'owner'")
|
||||
a(
|
||||
href='#',
|
||||
href,
|
||||
ng-click="openRenameProjectModal()"
|
||||
) #{translate("rename")}
|
||||
li
|
||||
a(
|
||||
href='#',
|
||||
href,
|
||||
ng-click="openCloneProjectModal()"
|
||||
) #{translate("make_copy")}
|
||||
|
||||
.btn-toolbar(ng-show="filter == 'archived'")
|
||||
.btn-group(ng-hide="selectedProjects.length < 1")
|
||||
a.btn.btn-default(
|
||||
href='#',
|
||||
href,
|
||||
data-original-title="Restore",
|
||||
data-toggle="tooltip",
|
||||
data-placement="bottom",
|
||||
@@ -105,7 +105,7 @@
|
||||
|
||||
.btn-group(ng-hide="selectedProjects.length < 1")
|
||||
a.btn.btn-danger(
|
||||
href='#',
|
||||
href,
|
||||
data-original-title="Delete Forever",
|
||||
data-toggle="tooltip",
|
||||
data-placement="bottom",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
+3
-5
@@ -1,9 +1,9 @@
|
||||
define [
|
||||
"ide/editor/directives/aceEditor/auto-complete/SuggestionManager"
|
||||
"ide/editor/directives/aceEditor/auto-complete/Snippets"
|
||||
"ide/editor/directives/aceEditor/auto-complete/SnippetManager"
|
||||
"ace/ace"
|
||||
"ace/ext-language_tools"
|
||||
], (SuggestionManager, Snippets) ->
|
||||
], (SuggestionManager, SnippetManager) ->
|
||||
Range = ace.require("ace/range").Range
|
||||
|
||||
getLastCommandFragment = (lineUpToCursor) ->
|
||||
@@ -38,9 +38,7 @@ define [
|
||||
enableLiveAutocompletion: false
|
||||
})
|
||||
|
||||
SnippetCompleter =
|
||||
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||
callback null, Snippets
|
||||
SnippetCompleter = new SnippetManager()
|
||||
|
||||
references = @$scope.$root._references
|
||||
ReferencesCompleter =
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
define () ->
|
||||
environments = [
|
||||
"abstract",
|
||||
"align", "align*",
|
||||
"equation", "equation*",
|
||||
"gather", "gather*",
|
||||
"multline", "multline*",
|
||||
"split",
|
||||
"verbatim"
|
||||
]
|
||||
|
||||
staticSnippets = for env in environments
|
||||
{
|
||||
caption: "\\begin{#{env}}..."
|
||||
snippet: """
|
||||
\\begin{#{env}}
|
||||
\t$1
|
||||
\\end{#{env}}
|
||||
"""
|
||||
meta: "env"
|
||||
}
|
||||
|
||||
staticSnippets = staticSnippets.concat [{
|
||||
caption: "\\begin{array}..."
|
||||
snippet: """
|
||||
\\begin{array}{${1:cc}}
|
||||
\t$2 & $3 \\\\\\\\
|
||||
\t$4 & $5
|
||||
\\end{array}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{figure}..."
|
||||
snippet: """
|
||||
\\begin{figure}
|
||||
\t\\centering
|
||||
\t\\includegraphics{$1}
|
||||
\t\\caption{${2:Caption}}
|
||||
\t\\label{${3:fig:my_label}}
|
||||
\\end{figure}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{tabular}..."
|
||||
snippet: """
|
||||
\\begin{tabular}{${1:c|c}}
|
||||
\t$2 & $3 \\\\\\\\
|
||||
\t$4 & $5
|
||||
\\end{tabular}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{table}..."
|
||||
snippet: """
|
||||
\\begin{table}[$1]
|
||||
\t\\centering
|
||||
\t\\begin{tabular}{${2:c|c}}
|
||||
\t\t$3 & $4 \\\\\\\\
|
||||
\t\t$5 & $6
|
||||
\t\\end{tabular}
|
||||
\t\\caption{${7:Caption}}
|
||||
\t\\label{${8:tab:my_label}}
|
||||
\\end{table}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{list}..."
|
||||
snippet: """
|
||||
\\begin{list}
|
||||
\t\\item $1
|
||||
\\end{list}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{enumerate}..."
|
||||
snippet: """
|
||||
\\begin{enumerate}
|
||||
\t\\item $1
|
||||
\\end{enumerate}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{itemize}..."
|
||||
snippet: """
|
||||
\\begin{itemize}
|
||||
\t\\item $1
|
||||
\\end{itemize}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{frame}..."
|
||||
snippet: """
|
||||
\\begin{frame}{${1:Frame Title}}
|
||||
\t$2
|
||||
\\end{frame}
|
||||
"""
|
||||
meta: "env"
|
||||
}]
|
||||
|
||||
|
||||
parseCustomEnvironments = (text) ->
|
||||
re = /^\\newenvironment{(\w+)}.*$/gm
|
||||
result = []
|
||||
iterations = 0
|
||||
while match = re.exec(text)
|
||||
result.push {name: match[1], whitespace: null}
|
||||
iterations += 1
|
||||
if iterations >= 1000
|
||||
return result
|
||||
return result
|
||||
|
||||
|
||||
parseBeginCommands = (text) ->
|
||||
re = /^\\begin{(\w+)}.*\n([\t ]*).*$/gm
|
||||
result = []
|
||||
iterations = 0
|
||||
while match = re.exec(text)
|
||||
result.push {name: match[1], whitespace: match[2]}
|
||||
iterations += 1
|
||||
if iterations >= 1000
|
||||
return result
|
||||
return result
|
||||
|
||||
class SnippetManager
|
||||
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||
docText = session.getValue()
|
||||
customEnvironments = parseCustomEnvironments(docText)
|
||||
beginCommands = parseBeginCommands(docText)
|
||||
parsedItemsMap = {}
|
||||
for environment in customEnvironments
|
||||
parsedItemsMap[environment.name] = environment
|
||||
for command in beginCommands
|
||||
parsedItemsMap[command.name] = command
|
||||
parsedItems = _.values(parsedItemsMap)
|
||||
snippets = staticSnippets.concat(
|
||||
parsedItems.map (item) ->
|
||||
{
|
||||
caption: "\\begin{#{item.name}}..."
|
||||
snippet: """
|
||||
\\begin{#{item.name}}
|
||||
#{item.whitespace || ''}$0
|
||||
\\end{#{item.name}}
|
||||
"""
|
||||
meta: "env"
|
||||
}
|
||||
).concat(
|
||||
# arguably these `end` commands shouldn't be here, as they're not snippets
|
||||
# but this is where we have access to the `begin` environment names
|
||||
# *shrug*
|
||||
parsedItems.map (item) ->
|
||||
{
|
||||
caption: "\\end{#{item.name}}"
|
||||
value: "\\end{#{item.name}}"
|
||||
meta: "env"
|
||||
}
|
||||
)
|
||||
callback null, snippets
|
||||
|
||||
return SnippetManager
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
define () ->
|
||||
environments = [
|
||||
"abstract",
|
||||
"align", "align*",
|
||||
"equation", "equation*",
|
||||
"gather", "gather*",
|
||||
"multline", "multline*",
|
||||
"split",
|
||||
"verbatim"
|
||||
]
|
||||
|
||||
snippets = for env in environments
|
||||
{
|
||||
caption: "\\begin{#{env}}..."
|
||||
snippet: """
|
||||
\\begin{#{env}}
|
||||
\t$1
|
||||
\\end{#{env}}
|
||||
"""
|
||||
meta: "env"
|
||||
}
|
||||
|
||||
snippets = snippets.concat [{
|
||||
caption: "\\begin{array}..."
|
||||
snippet: """
|
||||
\\begin{array}{${1:cc}}
|
||||
\t$2 & $3 \\\\\\\\
|
||||
\t$4 & $5
|
||||
\\end{array}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{figure}..."
|
||||
snippet: """
|
||||
\\begin{figure}
|
||||
\t\\centering
|
||||
\t\\includegraphics{$1}
|
||||
\t\\caption{${2:Caption}}
|
||||
\t\\label{${3:fig:my_label}}
|
||||
\\end{figure}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{tabular}..."
|
||||
snippet: """
|
||||
\\begin{tabular}{${1:c|c}}
|
||||
\t$2 & $3 \\\\\\\\
|
||||
\t$4 & $5
|
||||
\\end{tabular}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{table}..."
|
||||
snippet: """
|
||||
\\begin{table}[$1]
|
||||
\t\\centering
|
||||
\t\\begin{tabular}{${2:c|c}}
|
||||
\t\t$3 & $4 \\\\\\\\
|
||||
\t\t$5 & $6
|
||||
\t\\end{tabular}
|
||||
\t\\caption{${7:Caption}}
|
||||
\t\\label{${8:tab:my_label}}
|
||||
\\end{table}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{list}..."
|
||||
snippet: """
|
||||
\\begin{list}
|
||||
\t\\item $1
|
||||
\\end{list}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{enumerate}..."
|
||||
snippet: """
|
||||
\\begin{enumerate}
|
||||
\t\\item $1
|
||||
\\end{enumerate}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{itemize}..."
|
||||
snippet: """
|
||||
\\begin{itemize}
|
||||
\t\\item $1
|
||||
\\end{itemize}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{frame}..."
|
||||
snippet: """
|
||||
\\begin{frame}{${1:Frame Title}}
|
||||
\t$2
|
||||
\\end{frame}
|
||||
"""
|
||||
meta: "env"
|
||||
}]
|
||||
|
||||
return snippets
|
||||
+1
-2
@@ -28,7 +28,7 @@ define [], () ->
|
||||
|
||||
# Ignore single letter commands since auto complete is moot then.
|
||||
commandRegex: /\\([a-zA-Z][a-zA-Z]+)/
|
||||
|
||||
|
||||
nextCommand: () ->
|
||||
i = @doc.search(@commandRegex)
|
||||
if i == -1
|
||||
@@ -123,4 +123,3 @@ define [], () ->
|
||||
completionBeforeCursor: completionBeforeCursor
|
||||
completionAfterCursor: completionAfterCursor
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,13 @@ define [
|
||||
"libs/bib-log-parser"
|
||||
], (App, LogParser, BibLogParser) ->
|
||||
App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, localStorage) ->
|
||||
|
||||
autoCompile = true
|
||||
|
||||
# pdf.view = uncompiled | pdf | errors
|
||||
$scope.pdf.view = if $scope?.pdf?.url then 'pdf' else 'uncompiled'
|
||||
$scope.shouldShowLogs = false
|
||||
|
||||
$scope.$on "project:joined", () ->
|
||||
return if !autoCompile
|
||||
autoCompile = false
|
||||
@@ -12,7 +18,8 @@ define [
|
||||
$scope.hasPremiumCompile = $scope.project.features.compileGroup == "priority"
|
||||
|
||||
$scope.$on "pdf:error:display", () ->
|
||||
$scope.pdf.error = true
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.renderingError = true
|
||||
|
||||
$scope.draft = localStorage("draft:#{$scope.project_id}") or false
|
||||
$scope.$watch "draft", (new_value, old_value) ->
|
||||
@@ -37,17 +44,32 @@ define [
|
||||
$scope.pdf.uncompiled = false
|
||||
$scope.pdf.projectTooLarge = false
|
||||
$scope.pdf.url = null
|
||||
$scope.pdf.clsiMaintenance = false
|
||||
$scope.pdf.tooRecentlyCompiled = false
|
||||
|
||||
if response.status == "timedout"
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.timedout = true
|
||||
else if response.status == "autocompile-backoff"
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.uncompiled = true
|
||||
else if response.status == "project-too-large"
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.projectTooLarge = true
|
||||
else if response.status == "failure"
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.failure = true
|
||||
$scope.shouldShowLogs = true
|
||||
fetchLogs()
|
||||
else if response.status == 'clsi-maintenance'
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.clsiMaintenance = true
|
||||
else if response.status == "too-recently-compiled"
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.tooRecentlyCompiled = true
|
||||
else if response.status == "success"
|
||||
$scope.pdf.view = 'pdf'
|
||||
$scope.shouldShowLogs = false
|
||||
# define the base url
|
||||
$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}"
|
||||
# add a query string parameter for the compile group
|
||||
@@ -116,7 +138,7 @@ define [
|
||||
entries.warnings = entries.warnings.concat(biberLogEntries.warnings)
|
||||
proceed()
|
||||
.error (e) ->
|
||||
console.error ">> error", e
|
||||
# it's not an error for the output.blg file to not be present
|
||||
proceed()
|
||||
# # # #
|
||||
.error () ->
|
||||
@@ -158,6 +180,7 @@ define [
|
||||
.error () ->
|
||||
$scope.pdf.compiling = false
|
||||
$scope.pdf.error = true
|
||||
$scope.pdf.view = 'errors'
|
||||
|
||||
# This needs to be public.
|
||||
ide.$scope.recompile = $scope.recompile
|
||||
@@ -171,13 +194,11 @@ define [
|
||||
}
|
||||
|
||||
$scope.toggleLogs = () ->
|
||||
if !$scope.pdf.view? or $scope.pdf.view == "pdf"
|
||||
$scope.pdf.view = "logs"
|
||||
else
|
||||
$scope.pdf.view = "pdf"
|
||||
$scope.shouldShowLogs = !$scope.shouldShowLogs
|
||||
|
||||
$scope.showPdf = () ->
|
||||
$scope.pdf.view = "pdf"
|
||||
$scope.shouldShowLogs = false
|
||||
|
||||
$scope.toggleRawLog = () ->
|
||||
$scope.pdf.showRawLog = !$scope.pdf.showRawLog
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
]
|
||||
@@ -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 = () ->
|
||||
|
||||
@@ -12,6 +12,7 @@ define [
|
||||
"libs/angular-cookies"
|
||||
"libs/passfield"
|
||||
"libs/sixpack"
|
||||
"libs/groove"
|
||||
"libs/angular-sixpack"
|
||||
"libs/ng-tags-input-3.0.0"
|
||||
], () ->
|
||||
|
||||
@@ -15,7 +15,7 @@ define [
|
||||
"main/annual-upgrade"
|
||||
"main/register-users"
|
||||
"main/subscription/group-subscription-invite-controller"
|
||||
"main/universties-site"
|
||||
"main/contact-us"
|
||||
"analytics/AbTestingManager"
|
||||
"directives/asyncForm"
|
||||
"directives/stopPropagation"
|
||||
@@ -30,3 +30,6 @@ define [
|
||||
"__MAIN_CLIENTSIDE_INCLUDES__"
|
||||
], () ->
|
||||
angular.bootstrap(document.body, ["SharelatexApp"])
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
|
||||
|
||||
App.controller 'ContactModal', ($scope, $modal) ->
|
||||
$scope.contactUsModal = () ->
|
||||
modalInstance = $modal.open(
|
||||
templateUrl: "supportModalTemplate"
|
||||
controller: "SupportModalController"
|
||||
)
|
||||
|
||||
App.controller 'SupportModalController', ($scope, $modalInstance) ->
|
||||
$scope.form = {}
|
||||
$scope.sent = false
|
||||
$scope.sending = false
|
||||
$scope.contactUs = ->
|
||||
if !$scope.form.email?
|
||||
console.log "email not set"
|
||||
return
|
||||
$scope.sending = true
|
||||
ticketNumber = Math.floor((1 + Math.random()) * 0x10000).toString(32)
|
||||
params =
|
||||
email: $scope.form.email
|
||||
message: $scope.form.message or ""
|
||||
subject: $scope.form.subject + " - [#{ticketNumber}]"
|
||||
about : $scope.form.project_url or ""
|
||||
labels: "support"
|
||||
|
||||
Groove.createTicket params, (err, json)->
|
||||
$scope.sent = true
|
||||
$scope.$apply()
|
||||
|
||||
$scope.close = () ->
|
||||
$modalInstance.close()
|
||||
|
||||
|
||||
App.controller 'UniverstiesContactController', ($scope, $modal) ->
|
||||
|
||||
$scope.form = {}
|
||||
$scope.sent = false
|
||||
$scope.sending = false
|
||||
$scope.contactUs = ->
|
||||
if !$scope.form.email?
|
||||
console.log "email not set"
|
||||
return
|
||||
$scope.sending = true
|
||||
ticketNumber = Math.floor((1 + Math.random()) * 0x10000).toString(32)
|
||||
params =
|
||||
name: $scope.form.name || $scope.form.email
|
||||
email: $scope.form.email
|
||||
labels: "#{$scope.form.source} accounts"
|
||||
message: "Please contact me with more details"
|
||||
subject: $scope.form.subject + " - [#{ticketNumber}]"
|
||||
about : "#{$scope.form.position || ''} #{$scope.form.university || ''}"
|
||||
|
||||
Groove.createTicket params, (err, json)->
|
||||
$scope.sent = true
|
||||
$scope.$apply()
|
||||
@@ -65,11 +65,11 @@ define [
|
||||
$scope.projectsToLeave = projects.filter (project) -> project.accessLevel != "owner"
|
||||
|
||||
if $scope.projectsToLeave.length > 0 and $scope.projectsToDelete.length > 0
|
||||
$scope.action = "Delete & Leave"
|
||||
$scope.action = "delete-and-leave"
|
||||
else if $scope.projectsToLeave.length == 0 and $scope.projectsToDelete.length > 0
|
||||
$scope.action = "Delete"
|
||||
$scope.action = "delete"
|
||||
else
|
||||
$scope.action = "Leave"
|
||||
$scope.action = "leave"
|
||||
|
||||
$scope.delete = () ->
|
||||
$modalInstance.close()
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
|
||||
App.controller 'UniverstiesContactController', ($scope, $modal) ->
|
||||
|
||||
$scope.form = {}
|
||||
$scope.sent = false
|
||||
$scope.sending = false
|
||||
$scope.contactUs = ->
|
||||
if !$scope.form.email?
|
||||
console.log "email not set"
|
||||
return
|
||||
$scope.sending = true
|
||||
ticketNumber = Math.floor((1 + Math.random()) * 0x10000).toString(32)
|
||||
params =
|
||||
name: $scope.form.name || $scope.form.email
|
||||
email: $scope.form.email
|
||||
labels: $scope.form.source
|
||||
message: "Please contact me with more details"
|
||||
subject: $scope.form.subject + " - [#{ticketNumber}]"
|
||||
about : "#{$scope.form.position || ''} #{$scope.form.university || ''}"
|
||||
|
||||
Groove.createTicket params, (err, json)->
|
||||
$scope.sent = true
|
||||
$scope.$apply()
|
||||
@@ -16,6 +16,7 @@ define [
|
||||
_csrf : window.csrfToken
|
||||
|
||||
$scope.showForm = ->
|
||||
GrooveWidget.toggle()
|
||||
$scope.formVisable = true
|
||||
|
||||
$scope.getPercentComplete = ->
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,84 @@
|
||||
!function(window) {
|
||||
|
||||
window.Groove = {
|
||||
|
||||
init: function(options) {
|
||||
this._options = options;
|
||||
if (typeof grooveOnReady != 'undefined') {grooveOnReady();}
|
||||
},
|
||||
|
||||
createTicket: function(params, callback) {
|
||||
var postData = serialize({
|
||||
"ticket[enduser_name]": params["name"],
|
||||
"ticket[enduser_email]": params["email"],
|
||||
"ticket[title]": params["subject"],
|
||||
"ticket[enduser_about]": params["about"],
|
||||
"ticket[label_string]": params["labels"],
|
||||
"ticket[comments_attributes][0][body]": params["message"]
|
||||
});
|
||||
|
||||
sendRequest(this._options.widget_ticket_url, function(req) {
|
||||
if (callback) {callback(req);}
|
||||
}, postData);
|
||||
}
|
||||
};
|
||||
|
||||
// http://www.quirksmode.org/js/xmlhttp.html
|
||||
function sendRequest(url, callback, postData) {
|
||||
var req = createXMLHTTPObject();
|
||||
if (!req) return;
|
||||
var method = (postData) ? "POST" : "GET";
|
||||
req.open(method, url, true);
|
||||
if (postData){
|
||||
try {
|
||||
req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
|
||||
}
|
||||
catch(e) {
|
||||
req.contentType = 'application/x-www-form-urlencoded';
|
||||
};
|
||||
};
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState != 4) return;
|
||||
callback(req);
|
||||
}
|
||||
if (req.readyState == 4) return;
|
||||
req.send(postData);
|
||||
}
|
||||
|
||||
var XMLHttpFactories = [
|
||||
function () {return new XDomainRequest()},
|
||||
function () {return new XMLHttpRequest()},
|
||||
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
|
||||
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
|
||||
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
|
||||
];
|
||||
|
||||
function createXMLHTTPObject() {
|
||||
var xmlhttp = false;
|
||||
for (var i = 0; i < XMLHttpFactories.length; i++) {
|
||||
try {
|
||||
xmlhttp = XMLHttpFactories[i]();
|
||||
}
|
||||
catch (e) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return xmlhttp;
|
||||
}
|
||||
|
||||
function serialize(obj) {
|
||||
var str = [];
|
||||
for(var p in obj) {
|
||||
if (obj[p]) {
|
||||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
|
||||
}
|
||||
}
|
||||
return str.join("&");
|
||||
}
|
||||
|
||||
if (typeof grooveOnLoad != 'undefined') {grooveOnLoad();}
|
||||
}(window);
|
||||
|
||||
Groove.init({"widget_ticket_url":"https://sharelatex-accounts.groovehq.com/widgets/f5ad3b09-7d99-431b-8af5-c5725e3760ce/ticket.json"});
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.contact-us-modal {
|
||||
|
||||
textarea {
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
@@ -71,3 +71,4 @@
|
||||
@import "app/templates.less";
|
||||
@import "app/wiki.less";
|
||||
@import "app/translations.less";
|
||||
@import "app/contact-us.less";
|
||||
|
||||
+23
-91
@@ -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 ->
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,14 +61,16 @@ describe "SubscriptionController sanboxed", ->
|
||||
@SubscriptionDomainHandler =
|
||||
getDomainLicencePage:sinon.stub()
|
||||
@SubscriptionController = SandboxedModule.require modulePath, requires:
|
||||
'../../managers/SecurityManager': @SecurityManager
|
||||
'../Authentication/AuthenticationController': @AuthenticationController
|
||||
'./SubscriptionHandler': @SubscriptionHandler
|
||||
"./PlansLocator": @PlansLocator
|
||||
'./SubscriptionViewModelBuilder': @SubscriptionViewModelBuilder
|
||||
"./LimitationsManager": @LimitationsManager
|
||||
"../../infrastructure/GeoIpLookup":@GeoIpLookup
|
||||
'./RecurlyWrapper': @RecurlyWrapper
|
||||
"logger-sharelatex": log:->
|
||||
"logger-sharelatex":
|
||||
log:->
|
||||
warn:->
|
||||
"settings-sharelatex": @settings
|
||||
"./SubscriptionDomainHandler":@SubscriptionDomainHandler
|
||||
|
||||
@@ -273,7 +275,7 @@ describe "SubscriptionController sanboxed", ->
|
||||
describe "userCustomSubscriptionPage", ->
|
||||
beforeEach (done) ->
|
||||
@res.callback = done
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true)
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true, {})
|
||||
@SubscriptionController.userCustomSubscriptionPage @req, @res
|
||||
|
||||
it "should render the page", (done)->
|
||||
|
||||
@@ -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:->
|
||||
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
BASE_URL = "http://localhost:3000"
|
||||
module.exports = require("request").defaults({
|
||||
baseUrl: BASE_URL,
|
||||
followRedirect: false
|
||||
})
|
||||
Reference in New Issue
Block a user