Merge pull request #2195 from overleaf/em-collab-permissions

Move collaborators code to async/await

GitOrigin-RevId: 55b5dd8154d024e2cee738208c45a8139870b92b
This commit is contained in:
Timothée Alby 2019-10-07 15:30:51 +07:00 committed by sharelatex
parent b050de1645
commit 5f107374a6
31 changed files with 1335 additions and 1908 deletions

View file

@ -1,5 +1,5 @@
let AuthorizationManager
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const ProjectGetter = require('../Project/ProjectGetter')
const { User } = require('../../models/User')
const PrivilegeLevels = require('./PrivilegeLevels')
@ -55,7 +55,7 @@ module.exports = AuthorizationManager = {
// User is present, get their privilege level from database
getPrivilegeLevelForProjectWithUser(userId, projectId, token, callback) {
CollaboratorsHandler.getMemberIdPrivilegeLevel(userId, projectId, function(
CollaboratorsGetter.getMemberIdPrivilegeLevel(userId, projectId, function(
error,
privilegeLevel
) {

View file

@ -1,87 +1,56 @@
const async = require('async')
const OError = require('@overleaf/o-error')
const CollaboratorsHandler = require('./CollaboratorsHandler')
const CollaboratorsGetter = require('./CollaboratorsGetter')
const AuthenticationController = require('../Authentication/AuthenticationController')
const EditorRealTimeController = require('../Editor/EditorRealTimeController')
const TagsHandler = require('../Tags/TagsHandler')
const logger = require('logger-sharelatex')
const { expressify } = require('../../util/promises')
const CollaboratorsController = {
removeUserFromProject(req, res, next) {
const projectId = req.params.Project_id
const userId = req.params.user_id
CollaboratorsController._removeUserIdFromProject(
projectId,
userId,
function(error) {
if (error) {
return next(error)
}
EditorRealTimeController.emitToRoom(
projectId,
'project:membership:changed',
{ members: true }
)
res.sendStatus(204)
}
)
},
removeSelfFromProject(req, res, next) {
const projectId = req.params.Project_id
const userId = AuthenticationController.getLoggedInUserId(req)
CollaboratorsController._removeUserIdFromProject(
projectId,
userId,
function(error) {
if (error) {
return next(error)
}
res.sendStatus(204)
}
)
},
_removeUserIdFromProject(projectId, userId, callback) {
async.series(
[
cb => {
CollaboratorsHandler.removeUserFromProject(projectId, userId, cb)
},
cb => {
EditorRealTimeController.emitToRoom(
projectId,
'userRemovedFromProject',
userId
)
cb()
},
cb => {
TagsHandler.removeProjectFromAllTags(userId, projectId, cb)
}
],
function(error) {
if (error) {
return callback(error)
}
callback()
}
)
},
getAllMembers(req, res, next) {
const projectId = req.params.Project_id
logger.log({ projectId }, 'getting all active members for project')
CollaboratorsHandler.getAllInvitedMembers(projectId, function(
err,
members
) {
if (err) {
logger.warn({ projectId }, 'error getting members for project')
return next(err)
}
res.json({ members })
})
}
module.exports = {
removeUserFromProject: expressify(removeUserFromProject),
removeSelfFromProject: expressify(removeSelfFromProject),
getAllMembers: expressify(getAllMembers)
}
module.exports = CollaboratorsController
async function removeUserFromProject(req, res, next) {
const projectId = req.params.Project_id
const userId = req.params.user_id
await _removeUserIdFromProject(projectId, userId)
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {
members: true
})
res.sendStatus(204)
}
async function removeSelfFromProject(req, res, next) {
const projectId = req.params.Project_id
const userId = AuthenticationController.getLoggedInUserId(req)
await _removeUserIdFromProject(projectId, userId)
res.sendStatus(204)
}
async function getAllMembers(req, res, next) {
const projectId = req.params.Project_id
logger.log({ projectId }, 'getting all active members for project')
let members
try {
members = await CollaboratorsGetter.promises.getAllInvitedMembers(projectId)
} catch (err) {
throw new OError({
message: 'error getting members for project',
info: { projectId }
}).withCause(err)
}
res.json({ members })
}
async function _removeUserIdFromProject(projectId, userId) {
await CollaboratorsHandler.promises.removeUserFromProject(projectId, userId)
EditorRealTimeController.emitToRoom(
projectId,
'userRemovedFromProject',
userId
)
await TagsHandler.promises.removeProjectFromAllTags(userId, projectId)
}

View file

@ -0,0 +1,272 @@
const { callbackify } = require('util')
const pLimit = require('p-limit')
const { ObjectId } = require('mongojs')
const OError = require('@overleaf/o-error')
const { Project } = require('../../models/Project')
const UserGetter = require('../User/UserGetter')
const ProjectGetter = require('../Project/ProjectGetter')
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
const Errors = require('../Errors/Errors')
const ProjectEditorHandler = require('../Project/ProjectEditorHandler')
const Sources = require('../Authorization/Sources')
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
module.exports = {
getMemberIdsWithPrivilegeLevels: callbackify(getMemberIdsWithPrivilegeLevels),
getMemberIds: callbackify(getMemberIds),
getInvitedMemberIds: callbackify(getInvitedMemberIds),
getInvitedMembersWithPrivilegeLevels: callbackify(
getInvitedMembersWithPrivilegeLevels
),
getInvitedMembersWithPrivilegeLevelsFromFields: callbackify(
getInvitedMembersWithPrivilegeLevelsFromFields
),
getMemberIdPrivilegeLevel: callbackify(getMemberIdPrivilegeLevel),
getInvitedCollaboratorCount: callbackify(getInvitedCollaboratorCount),
getProjectsUserIsMemberOf: callbackify(getProjectsUserIsMemberOf),
isUserInvitedMemberOfProject: callbackify(isUserInvitedMemberOfProject),
userIsTokenMember: callbackify(userIsTokenMember),
getAllInvitedMembers: callbackify(getAllInvitedMembers),
promises: {
getMemberIdsWithPrivilegeLevels,
getMemberIds,
getInvitedMemberIds,
getInvitedMembersWithPrivilegeLevels,
getInvitedMembersWithPrivilegeLevelsFromFields,
getMemberIdPrivilegeLevel,
getInvitedCollaboratorCount,
getProjectsUserIsMemberOf,
isUserInvitedMemberOfProject,
userIsTokenMember,
getAllInvitedMembers
}
}
async function getMemberIdsWithPrivilegeLevels(projectId) {
const project = await ProjectGetter.promises.getProject(projectId, {
owner_ref: 1,
collaberator_refs: 1,
readOnly_refs: 1,
tokenAccessReadOnly_refs: 1,
tokenAccessReadAndWrite_refs: 1,
publicAccesLevel: 1
})
if (!project) {
throw new Errors.NotFoundError(`no project found with id ${projectId}`)
}
const memberIds = _getMemberIdsWithPrivilegeLevelsFromFields(
project.owner_ref,
project.collaberator_refs,
project.readOnly_refs,
project.tokenAccessReadAndWrite_refs,
project.tokenAccessReadOnly_refs,
project.publicAccesLevel
)
return memberIds
}
async function getMemberIds(projectId) {
const members = await getMemberIdsWithPrivilegeLevels(projectId)
return members.map(m => m.id)
}
async function getInvitedMemberIds(projectId) {
const members = await getMemberIdsWithPrivilegeLevels(projectId)
return members.filter(m => m.source !== Sources.TOKEN).map(m => m.id)
}
async function getInvitedMembersWithPrivilegeLevels(projectId) {
let members = await getMemberIdsWithPrivilegeLevels(projectId)
members = members.filter(m => m.source !== Sources.TOKEN)
return _loadMembers(members)
}
async function getInvitedMembersWithPrivilegeLevelsFromFields(
ownerId,
collaboratorIds,
readOnlyIds
) {
let members = _getMemberIdsWithPrivilegeLevelsFromFields(
ownerId,
collaboratorIds,
readOnlyIds,
[],
[],
null
)
return _loadMembers(members)
}
async function getMemberIdPrivilegeLevel(userId, projectId) {
// In future if the schema changes and getting all member ids is more expensive (multiple documents)
// then optimise this.
if (userId == null) {
return PrivilegeLevels.NONE
}
const members = await getMemberIdsWithPrivilegeLevels(projectId)
for (const member of members) {
if (member.id === userId.toString()) {
return member.privilegeLevel
}
}
return PrivilegeLevels.NONE
}
async function getInvitedCollaboratorCount(projectId) {
const count = await _getInvitedMemberCount(projectId)
return count - 1 // Don't count project owner
}
async function isUserInvitedMemberOfProject(userId, projectId) {
const members = await getMemberIdsWithPrivilegeLevels(projectId)
for (const member of members) {
if (
member.id.toString() === userId.toString() &&
member.source !== Sources.TOKEN
) {
return true
}
}
return false
}
async function getProjectsUserIsMemberOf(userId, fields) {
const limit = pLimit(2)
const [
readAndWrite,
readOnly,
tokenReadAndWrite,
tokenReadOnly
] = await Promise.all([
limit(() => Project.find({ collaberator_refs: userId }, fields).exec()),
limit(() => Project.find({ readOnly_refs: userId }, fields).exec()),
limit(() =>
Project.find(
{
tokenAccessReadAndWrite_refs: userId,
publicAccesLevel: PublicAccessLevels.TOKEN_BASED
},
fields
).exec()
),
limit(() =>
Project.find(
{
tokenAccessReadOnly_refs: userId,
publicAccesLevel: PublicAccessLevels.TOKEN_BASED
},
fields
).exec()
)
])
return { readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly }
}
async function getAllInvitedMembers(projectId) {
try {
const rawMembers = await getInvitedMembersWithPrivilegeLevels(projectId)
const { members } = ProjectEditorHandler.buildOwnerAndMembersViews(
rawMembers
)
return members
} catch (err) {
throw new OError({
message: 'error getting members for project',
info: { projectId }
}).withCause(err)
}
}
async function userIsTokenMember(userId, projectId) {
userId = ObjectId(userId.toString())
projectId = ObjectId(projectId.toString())
const project = await Project.findOne(
{
_id: projectId,
$or: [
{ tokenAccessReadOnly_refs: userId },
{ tokenAccessReadAndWrite_refs: userId }
]
},
{
_id: 1
}
).exec()
return project != null
}
async function _getInvitedMemberCount(projectId) {
const members = await getMemberIdsWithPrivilegeLevels(projectId)
return members.filter(m => m.source !== Sources.TOKEN).length
}
function _getMemberIdsWithPrivilegeLevelsFromFields(
ownerId,
collaboratorIds,
readOnlyIds,
tokenAccessIds,
tokenAccessReadOnlyIds,
publicAccessLevel
) {
const members = []
members.push({
id: ownerId.toString(),
privilegeLevel: PrivilegeLevels.OWNER,
source: Sources.OWNER
})
for (const memberId of collaboratorIds || []) {
members.push({
id: memberId.toString(),
privilegeLevel: PrivilegeLevels.READ_AND_WRITE,
source: Sources.INVITE
})
}
for (const memberId of readOnlyIds || []) {
members.push({
id: memberId.toString(),
privilegeLevel: PrivilegeLevels.READ_ONLY,
source: Sources.INVITE
})
}
if (publicAccessLevel === PublicAccessLevels.TOKEN_BASED) {
for (const memberId of tokenAccessIds || []) {
members.push({
id: memberId.toString(),
privilegeLevel: PrivilegeLevels.READ_AND_WRITE,
source: Sources.TOKEN
})
}
for (const memberId of tokenAccessReadOnlyIds || []) {
members.push({
id: memberId.toString(),
privilegeLevel: PrivilegeLevels.READ_ONLY,
source: Sources.TOKEN
})
}
}
return members
}
async function _loadMembers(members) {
const limit = pLimit(3)
const results = await Promise.all(
members.map(member =>
limit(async () => {
const user = await UserGetter.promises.getUser(member.id, {
_id: 1,
email: 1,
features: 1,
first_name: 1,
last_name: 1,
signUpDate: 1
})
if (user != null) {
return { user, privilegeLevel: member.privilegeLevel }
} else {
return null
}
})
)
)
return results.filter(r => r != null)
}

View file

@ -1,672 +1,159 @@
/* eslint-disable
camelcase,
handle-callback-err,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const UserCreator = require('../User/UserCreator')
const { callbackify } = require('util')
const OError = require('@overleaf/o-error')
const { Project } = require('../../models/Project')
const ProjectGetter = require('../Project/ProjectGetter')
const logger = require('logger-sharelatex')
const UserGetter = require('../User/UserGetter')
const ContactManager = require('../Contacts/ContactManager')
const CollaboratorsEmailHandler = require('./CollaboratorsEmailHandler')
const async = require('async')
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
const Errors = require('../Errors/Errors')
const EmailHelper = require('../Helpers/EmailHelper')
const ProjectEditorHandler = require('../Project/ProjectEditorHandler')
const Sources = require('../Authorization/Sources')
const { ObjectId } = require('mongojs')
const { promisifyAll } = require('../../util/promises')
const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
const CollaboratorsGetter = require('./CollaboratorsGetter')
const CollaboratorsHandler = {
getMemberIdsWithPrivilegeLevels(project_id, callback) {
if (callback == null) {
callback = function(error, members) {}
}
const projection = {
owner_ref: 1,
collaberator_refs: 1,
readOnly_refs: 1,
tokenAccessReadOnly_refs: 1,
tokenAccessReadAndWrite_refs: 1,
publicAccesLevel: 1
}
ProjectGetter.getProject(project_id, projection, (error, project) => {
if (error) {
return callback(error)
}
if (!project) {
return callback(
new Errors.NotFoundError(`no project found with id ${project_id}`)
)
}
callback(
null,
CollaboratorsHandler.getMemberIdsWithPrivilegeLevelsFromFields(
project.owner_ref,
project.collaberator_refs,
project.readOnly_refs,
project.tokenAccessReadAndWrite_refs,
project.tokenAccessReadOnly_refs,
project.publicAccesLevel
)
)
})
},
getMemberIdsWithPrivilegeLevelsFromFields(
ownerId,
collaboratorIds,
readOnlyIds,
tokenAccessIds,
tokenAccessReadOnlyIds,
publicAccessLevel
) {
let member_id
const members = []
members.push({
id: ownerId.toString(),
privilegeLevel: PrivilegeLevels.OWNER,
source: Sources.OWNER
})
for (member_id of Array.from(collaboratorIds || [])) {
members.push({
id: member_id.toString(),
privilegeLevel: PrivilegeLevels.READ_AND_WRITE,
source: Sources.INVITE
})
}
for (member_id of Array.from(readOnlyIds || [])) {
members.push({
id: member_id.toString(),
privilegeLevel: PrivilegeLevels.READ_ONLY,
source: Sources.INVITE
})
}
if (publicAccessLevel === PublicAccessLevels.TOKEN_BASED) {
for (member_id of Array.from(tokenAccessIds || [])) {
members.push({
id: member_id.toString(),
privilegeLevel: PrivilegeLevels.READ_AND_WRITE,
source: Sources.TOKEN
})
}
for (member_id of Array.from(tokenAccessReadOnlyIds || [])) {
members.push({
id: member_id.toString(),
privilegeLevel: PrivilegeLevels.READ_ONLY,
source: Sources.TOKEN
})
}
}
return members
},
getMemberIds(project_id, callback) {
if (callback == null) {
callback = function(error, member_ids) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (error != null) {
return callback(error)
}
return callback(null, members.map(m => m.id))
}
)
},
getInvitedMemberIds(project_id, callback) {
if (callback == null) {
callback = function(error, member_ids) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (error != null) {
return callback(error)
}
return callback(
null,
members.filter(m => m.source !== Sources.TOKEN).map(m => m.id)
)
}
)
},
USER_PROJECTION: {
_id: 1,
email: 1,
features: 1,
first_name: 1,
last_name: 1,
signUpDate: 1
},
_loadMembers(members, callback) {
if (callback == null) {
callback = function(error, users) {}
}
const result = []
return async.mapLimit(
members,
3,
(member, cb) =>
UserGetter.getUser(
member.id,
CollaboratorsHandler.USER_PROJECTION,
function(error, user) {
if (error != null) {
return cb(error)
}
if (user != null) {
result.push({ user, privilegeLevel: member.privilegeLevel })
}
return cb()
}
),
function(error) {
if (error != null) {
return callback(error)
}
return callback(null, result)
}
)
},
getMembersWithPrivilegeLevels(project_id, callback) {
if (callback == null) {
callback = function(error, members) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (members == null) {
members = []
}
if (error != null) {
return callback(error)
}
return CollaboratorsHandler._loadMembers(members, (error, users) =>
callback(error, users)
)
}
)
},
getInvitedMembersWithPrivilegeLevels(project_id, callback) {
if (callback == null) {
callback = function(error, members) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (members == null) {
members = []
}
if (error != null) {
return callback(error)
}
members = members.filter(m => m.source !== Sources.TOKEN)
return CollaboratorsHandler._loadMembers(members, (error, users) =>
callback(error, users)
)
}
)
},
getInvitedMembersWithPrivilegeLevelsFromFields(
ownerId,
collaboratorIds,
readOnlyIds,
callback
) {
let members = CollaboratorsHandler.getMemberIdsWithPrivilegeLevelsFromFields(
ownerId,
collaboratorIds,
readOnlyIds,
[],
[],
null
)
return CollaboratorsHandler._loadMembers(members, (error, users) =>
callback(error, users)
)
},
getTokenMembersWithPrivilegeLevels(project_id, callback) {
if (callback == null) {
callback = function(error, members) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (members == null) {
members = []
}
if (error != null) {
return callback(error)
}
members = members.filter(m => m.source === Sources.TOKEN)
return CollaboratorsHandler._loadMembers(members, (error, users) =>
callback(error, users)
)
}
)
},
getMemberIdPrivilegeLevel(user_id, project_id, callback) {
// In future if the schema changes and getting all member ids is more expensive (multiple documents)
// then optimise this.
if (callback == null) {
callback = function(error, privilegeLevel) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (members == null) {
members = []
}
if (error != null) {
return callback(error)
}
for (let member of Array.from(members)) {
if (
member.id === (user_id != null ? user_id.toString() : undefined)
) {
return callback(null, member.privilegeLevel)
}
}
return callback(null, PrivilegeLevels.NONE)
}
)
},
getInvitedMemberCount(project_id, callback) {
if (callback == null) {
callback = function(error, count) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (error != null) {
return callback(error)
}
return callback(
null,
(members || []).filter(m => m.source !== Sources.TOKEN).length
)
}
)
},
getInvitedCollaboratorCount(project_id, callback) {
if (callback == null) {
callback = function(error, count) {}
}
return CollaboratorsHandler.getInvitedMemberCount(project_id, function(
error,
count
) {
if (error != null) {
return callback(error)
}
return callback(null, count - 1)
})
}, // Don't count project owner
isUserInvitedMemberOfProject(user_id, project_id, callback) {
if (callback == null) {
callback = function(error, isMember, privilegeLevel) {}
}
return CollaboratorsHandler.getMemberIdsWithPrivilegeLevels(
project_id,
function(error, members) {
if (members == null) {
members = []
}
if (error != null) {
return callback(error)
}
for (let member of Array.from(members)) {
if (
member.id.toString() === user_id.toString() &&
member.source !== Sources.TOKEN
) {
return callback(null, true, member.privilegeLevel)
}
}
return callback(null, false, null)
}
)
},
getProjectsUserIsMemberOf(user_id, fields, callback) {
if (callback == null) {
callback = function(error, results) {}
}
return async.mapLimit(
[
{ collaberator_refs: user_id },
{ readOnly_refs: user_id },
{
tokenAccessReadAndWrite_refs: user_id,
publicAccesLevel: PublicAccessLevels.TOKEN_BASED
},
{
tokenAccessReadOnly_refs: user_id,
publicAccesLevel: PublicAccessLevels.TOKEN_BASED
}
],
2,
(query, cb) => Project.find(query, fields, cb),
function(error, results) {
if (error != null) {
return callback(error)
}
const projects = {
readAndWrite: results[0],
readOnly: results[1],
tokenReadAndWrite: results[2],
tokenReadOnly: results[3]
}
return callback(null, projects)
}
)
},
removeUserFromProject(project_id, user_id, callback) {
if (callback == null) {
callback = function(error) {}
}
logger.log({ user_id, project_id }, 'removing user')
const conditions = { _id: project_id }
const update = { $pull: {} }
update['$pull'] = {
collaberator_refs: user_id,
readOnly_refs: user_id,
tokenAccessReadOnly_refs: user_id,
tokenAccessReadAndWrite_refs: user_id
}
return Project.update(conditions, update, function(err) {
if (err != null) {
logger.warn({ err }, 'problem removing user from project collaberators')
}
return callback(err)
})
},
removeUserFromAllProjets(user_id, callback) {
if (callback == null) {
callback = function(error) {}
}
return CollaboratorsHandler.getProjectsUserIsMemberOf(
user_id,
{ _id: 1 },
function(
error,
{ readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly }
) {
if (error != null) {
return callback(error)
}
const allProjects = readAndWrite
.concat(readOnly)
.concat(tokenReadAndWrite)
.concat(tokenReadOnly)
const jobs = []
for (let project of Array.from(allProjects)) {
;(project =>
jobs.push(function(cb) {
if (project == null) {
return cb()
}
return CollaboratorsHandler.removeUserFromProject(
project._id,
user_id,
cb
)
}))(project)
}
return async.series(jobs, callback)
}
)
},
addUserIdToProject(
project_id,
adding_user_id,
user_id,
privilegeLevel,
callback
) {
if (callback == null) {
callback = function(error) {}
}
return ProjectGetter.getProject(
project_id,
{ collaberator_refs: 1, readOnly_refs: 1 },
function(error, project) {
let level
if (error != null) {
return callback(error)
}
let existing_users = project.collaberator_refs || []
existing_users = existing_users.concat(project.readOnly_refs || [])
existing_users = existing_users.map(u => u.toString())
if (existing_users.indexOf(user_id.toString()) > -1) {
return callback(null) // User already in Project
}
if (privilegeLevel === PrivilegeLevels.READ_AND_WRITE) {
level = { collaberator_refs: user_id }
logger.log(
{ privileges: 'readAndWrite', user_id, project_id },
'adding user'
)
} else if (privilegeLevel === PrivilegeLevels.READ_ONLY) {
level = { readOnly_refs: user_id }
logger.log(
{ privileges: 'readOnly', user_id, project_id },
'adding user'
)
} else {
return callback(
new Error(`unknown privilegeLevel: ${privilegeLevel}`)
)
}
if (adding_user_id) {
ContactManager.addContact(adding_user_id, user_id)
}
return Project.update(
{ _id: project_id },
{ $addToSet: level },
function(error) {
if (error != null) {
return callback(error)
}
// Flush to TPDS in background to add files to collaborator's Dropbox
const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
ProjectEntityHandler.flushProjectToThirdPartyDataStore(
project_id,
function(error) {
if (error != null) {
return logger.error(
{ err: error, project_id, user_id },
'error flushing to TPDS after adding collaborator'
)
}
}
)
return callback()
}
)
}
)
},
getAllInvitedMembers(projectId, callback) {
if (callback == null) {
callback = function(err, members) {}
}
logger.log({ projectId }, 'fetching all members')
return CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels(
projectId,
function(error, rawMembers) {
if (error != null) {
logger.warn({ projectId, error }, 'error getting members for project')
return callback(error)
}
const {
owner,
members
} = ProjectEditorHandler.buildOwnerAndMembersViews(rawMembers)
return callback(null, members)
}
)
},
userIsTokenMember(userId, projectId, callback) {
if (callback == null) {
callback = function(err, isTokenMember) {}
}
userId = ObjectId(userId.toString())
projectId = ObjectId(projectId.toString())
return Project.findOne(
{
_id: projectId,
$or: [
{ tokenAccessReadOnly_refs: userId },
{ tokenAccessReadAndWrite_refs: userId }
]
},
{
_id: 1
},
function(err, project) {
if (err != null) {
return callback(err)
}
return callback(null, project != null)
}
)
},
transferProjects(from_user_id, to_user_id, callback) {
if (callback == null) {
callback = function(err, projects) {}
}
const MEMBER_KEYS = ['collaberator_refs', 'readOnly_refs']
// Find all the projects this user is part of so we can flush them to TPDS
let query = {
$or: [{ owner_ref: from_user_id }].concat(
MEMBER_KEYS.map(function(key) {
const q = {}
q[key] = from_user_id
return q
})
) // [{ collaberator_refs: from_user_id }, ...]
}
return Project.find(query, { _id: 1 }, function(error, projects) {
if (projects == null) {
projects = []
}
if (error != null) {
return callback(error)
}
const project_ids = projects.map(p => p._id)
logger.log(
{ project_ids, from_user_id, to_user_id },
'transferring projects'
)
const update_jobs = []
update_jobs.push(cb =>
Project.update(
{ owner_ref: from_user_id },
{ $set: { owner_ref: to_user_id } },
{ multi: true },
cb
)
)
for (let key of Array.from(MEMBER_KEYS)) {
;(key =>
update_jobs.push(function(cb) {
query = {}
const addNewUserUpdate = { $addToSet: {} }
const removeOldUserUpdate = { $pull: {} }
query[key] = from_user_id
removeOldUserUpdate.$pull[key] = from_user_id
addNewUserUpdate.$addToSet[key] = to_user_id
// Mongo won't let us pull and addToSet in the same query, so do it in
// two. Note we need to add first, since the query is based on the old user.
return Project.update(
query,
addNewUserUpdate,
{ multi: true },
function(error) {
if (error != null) {
return cb(error)
}
return Project.update(
query,
removeOldUserUpdate,
{ multi: true },
cb
)
}
)
}))(key)
}
// Flush each project to TPDS to add files to new user's Dropbox
const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
const flush_jobs = []
for (let project_id of Array.from(project_ids)) {
;(project_id =>
flush_jobs.push(cb =>
ProjectEntityHandler.flushProjectToThirdPartyDataStore(
project_id,
cb
)
))(project_id)
}
// Flush in background, no need to block on this
async.series(flush_jobs, function(error) {
if (error != null) {
return logger.err(
{ err: error, project_ids, from_user_id, to_user_id },
'error flushing tranferred projects to TPDS'
)
}
})
return async.series(update_jobs, function(err) {
logger.log('flushed transferred projects to TPDS')
return callback(err)
})
})
module.exports = {
removeUserFromProject: callbackify(removeUserFromProject),
removeUserFromAllProjects: callbackify(removeUserFromAllProjects),
addUserIdToProject: callbackify(addUserIdToProject),
transferProjects: callbackify(transferProjects),
promises: {
removeUserFromProject,
removeUserFromAllProjects,
addUserIdToProject,
transferProjects
}
}
CollaboratorsHandler.promises = promisifyAll(CollaboratorsHandler, {
without: ['getMemberIdsWithPrivilegeLevelsFromFields']
})
module.exports = CollaboratorsHandler
async function removeUserFromProject(projectId, userId) {
try {
await Project.update(
{ _id: projectId },
{
$pull: {
collaberator_refs: userId,
readOnly_refs: userId,
tokenAccessReadOnly_refs: userId,
tokenAccessReadAndWrite_refs: userId
}
}
).exec()
} catch (err) {
throw new OError({
message: 'problem removing user from project collaborators',
info: { projectId, userId }
}).withCause(err)
}
}
async function removeUserFromAllProjects(userId) {
const {
readAndWrite,
readOnly,
tokenReadAndWrite,
tokenReadOnly
} = await CollaboratorsGetter.promises.getProjectsUserIsMemberOf(userId, {
_id: 1
})
const allProjects = readAndWrite
.concat(readOnly)
.concat(tokenReadAndWrite)
.concat(tokenReadOnly)
for (const project of allProjects) {
await removeUserFromProject(project._id, userId)
}
}
async function addUserIdToProject(
projectId,
addingUserId,
userId,
privilegeLevel
) {
const project = await ProjectGetter.promises.getProject(projectId, {
collaberator_refs: 1,
readOnly_refs: 1
})
let level
let existingUsers = project.collaberator_refs || []
existingUsers = existingUsers.concat(project.readOnly_refs || [])
existingUsers = existingUsers.map(u => u.toString())
if (existingUsers.includes(userId.toString())) {
return // User already in Project
}
if (privilegeLevel === PrivilegeLevels.READ_AND_WRITE) {
level = { collaberator_refs: userId }
logger.log({ privileges: 'readAndWrite', userId, projectId }, 'adding user')
} else if (privilegeLevel === PrivilegeLevels.READ_ONLY) {
level = { readOnly_refs: userId }
logger.log({ privileges: 'readOnly', userId, projectId }, 'adding user')
} else {
throw new Error(`unknown privilegeLevel: ${privilegeLevel}`)
}
if (addingUserId) {
ContactManager.addContact(addingUserId, userId)
}
await Project.update({ _id: projectId }, { $addToSet: level }).exec()
// Flush to TPDS in background to add files to collaborator's Dropbox
ProjectEntityHandler.promises
.flushProjectToThirdPartyDataStore(projectId)
.catch(err => {
logger.error(
{ err, projectId, userId },
'error flushing to TPDS after adding collaborator'
)
})
}
async function transferProjects(fromUserId, toUserId) {
// Find all the projects this user is part of so we can flush them to TPDS
const projects = await Project.find(
{
$or: [
{ owner_ref: fromUserId },
{ collaberator_refs: fromUserId },
{ readOnly_refs: fromUserId }
]
},
{ _id: 1 }
).exec()
const projectIds = projects.map(p => p._id)
logger.log({ projectIds, fromUserId, toUserId }, 'transferring projects')
await Project.update(
{ owner_ref: fromUserId },
{ $set: { owner_ref: toUserId } },
{ multi: true }
).exec()
await Project.update(
{ collaberator_refs: fromUserId },
{
$addToSet: { collaberator_refs: toUserId },
$pull: { collaberator_refs: fromUserId }
},
{ multi: true }
).exec()
await Project.update(
{ readOnly_refs: fromUserId },
{
$addToSet: { readOnly_refs: toUserId },
$pull: { readOnly_refs: fromUserId }
},
{ multi: true }
).exec()
// Flush in background, no need to block on this
_flushProjects(projectIds).catch(err => {
logger.err(
{ err, projectIds, fromUserId, toUserId },
'error flushing tranferred projects to TPDS'
)
})
}
async function _flushProjects(projectIds) {
for (const projectId of projectIds) {
await ProjectEntityHandler.promises.flushProjectToThirdPartyDataStore(
projectId
)
}
}

View file

@ -16,7 +16,7 @@ let CollaboratorsInviteController
const ProjectGetter = require('../Project/ProjectGetter')
const LimitationsManager = require('../Subscription/LimitationsManager')
const UserGetter = require('../User/UserGetter')
const CollaboratorsHandler = require('./CollaboratorsHandler')
const CollaboratorsGetter = require('./CollaboratorsGetter')
const CollaboratorsInviteHandler = require('./CollaboratorsInviteHandler')
const logger = require('logger-sharelatex')
const Settings = require('settings-sharelatex')
@ -257,10 +257,10 @@ module.exports = CollaboratorsInviteController = {
}
// check if the user is already a member of the project
const currentUser = AuthenticationController.getSessionUser(req)
return CollaboratorsHandler.isUserInvitedMemberOfProject(
return CollaboratorsGetter.isUserInvitedMemberOfProject(
currentUser._id,
projectId,
function(err, isMember, _privilegeLevel) {
function(err, isMember) {
if (err != null) {
logger.warn(
{ err, projectId },

View file

@ -23,7 +23,7 @@ const UserGetter = require('../User/UserGetter')
const AuthorizationManager = require('../Authorization/AuthorizationManager')
const ProjectEditorHandler = require('../Project/ProjectEditorHandler')
const Metrics = require('metrics-sharelatex')
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const CollaboratorsInviteHandler = require('../Collaborators/CollaboratorsInviteHandler')
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
@ -76,7 +76,7 @@ module.exports = EditorHttpController = {
if (project == null) {
return callback(new Errors.NotFoundError('project not found'))
}
return CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels(
return CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels(
project_id,
function(error, members) {
if (error != null) {

View file

@ -28,7 +28,7 @@ const AuthenticationController = require('../Authentication/AuthenticationContro
const PackageVersions = require('../../infrastructure/PackageVersions')
const Sources = require('../Authorization/Sources')
const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const Modules = require('../../infrastructure/Modules')
const ProjectEntityHandler = require('./ProjectEntityHandler')
const UserGetter = require('../User/UserGetter')
@ -616,7 +616,7 @@ const ProjectController = {
if (userId == null) {
return cb()
}
CollaboratorsHandler.userIsTokenMember(userId, projectId, cb)
CollaboratorsGetter.userIsTokenMember(userId, projectId, cb)
},
brandVariation: [
'project',

View file

@ -23,6 +23,7 @@ const async = require('async')
const ProjectHelper = require('./ProjectHelper')
const ProjectDetailsHandler = require('./ProjectDetailsHandler')
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const DocstoreManager = require('../Docstore/DocstoreManager')
const moment = require('moment')
@ -73,7 +74,7 @@ const ProjectDeleter = {
if (err != null) {
return callback(err)
}
return CollaboratorsHandler.removeUserFromAllProjets(
return CollaboratorsHandler.removeUserFromAllProjects(
user_id,
callback
)
@ -249,8 +250,7 @@ async function deleteProject(project_id, options = {}) {
)
await flushProjectToMongoAndDelete(project_id)
const getMemberIds = promisify(CollaboratorsHandler.getMemberIds)
let member_ids = await getMemberIds(project_id)
let member_ids = await CollaboratorsGetter.promises.getMemberIds(project_id)
// fire these jobs in the background
Array.from(member_ids).forEach(member_id =>

View file

@ -181,7 +181,7 @@ const ProjectGetter = {
}
}
}
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
return Project.find({ owner_ref: user_id }, fields, function(
error,
ownedProjects
@ -189,7 +189,7 @@ const ProjectGetter = {
if (error != null) {
return callback(error)
}
return CollaboratorsHandler.getProjectsUserIsMemberOf(
return CollaboratorsGetter.getProjectsUserIsMemberOf(
user_id,
fields,
function(error, projects) {

View file

@ -18,7 +18,7 @@ const ProjectGetter = require('../Project/ProjectGetter')
const UserGetter = require('../User/UserGetter')
const SubscriptionLocator = require('./SubscriptionLocator')
const Settings = require('settings-sharelatex')
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const CollaboratorsInvitesHandler = require('../Collaborators/CollaboratorsInviteHandler')
const V1SubscriptionManager = require('./V1SubscriptionManager')
@ -62,7 +62,7 @@ module.exports = LimitationsManager = {
if (error != null) {
return callback(error)
}
return CollaboratorsHandler.getInvitedCollaboratorCount(
return CollaboratorsGetter.getInvitedCollaboratorCount(
project_id,
(error, current_number) => {
if (error != null) {

View file

@ -12,13 +12,13 @@
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let TagsHandler
const settings = require('settings-sharelatex')
const request = require('request')
const logger = require('logger-sharelatex')
const { promisifyAll } = require('../../util/promises')
const TIMEOUT = 1000
module.exports = TagsHandler = {
const TagsHandler = {
getAllTags(user_id, callback) {
this._requestTags(user_id, (err, allTags) => {
if (allTags == null) {
@ -205,3 +205,6 @@ module.exports = TagsHandler = {
)
}
}
TagsHandler.promises = promisifyAll(TagsHandler)
module.exports = TagsHandler

View file

@ -21,7 +21,7 @@ const ProjectGetter = require('../Project/ProjectGetter')
const keys = require('../../infrastructure/Keys')
const metrics = require('metrics-sharelatex')
const request = require('request')
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const { promisifyAll } = require('../../util/promises')
const buildPath = function(user_id, project_name, filePath) {
@ -298,7 +298,7 @@ var getProjectsUsersIds = function(project_id, callback) {
if (err != null) {
return callback(err)
}
return CollaboratorsHandler.getInvitedMemberIds(project_id, function(
return CollaboratorsGetter.getInvitedMemberIds(project_id, function(
err,
member_ids
) {

View file

@ -1,5 +1,5 @@
const { Project } = require('../../models/Project')
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
const UserGetter = require('../User/UserGetter')
@ -133,7 +133,7 @@ const TokenAccessHandler = {
},
_userIsMember(userId, projectId, callback) {
CollaboratorsHandler.isUserInvitedMemberOfProject(
CollaboratorsGetter.isUserInvitedMemberOfProject(
userId,
projectId,
callback

View file

@ -1,4 +1,4 @@
const expressify = require('../../util/expressify')
const { expressify } = require('../../util/promises')
const async = require('async')
const UserMembershipAuthorization = require('./UserMembershipAuthorization')
const AuthenticationController = require('../Authentication/AuthenticationController')

View file

@ -1,5 +0,0 @@
module.exports = function expressify(fn) {
return (req, res, next) => {
fn(req, res, next).catch(next)
}
}

View file

@ -1,6 +1,9 @@
const { promisify } = require('util')
module.exports = { promisifyAll }
module.exports = {
promisifyAll,
expressify
}
/**
* Promisify all functions in a module.
@ -29,3 +32,14 @@ function promisifyAll(module, opts = {}) {
}
return promises
}
/**
* Transform an async function into an Express middleware
*
* Any error will be passed to the error middlewares via `next()`
*/
function expressify(fn) {
return (req, res, next) => {
fn(req, res, next).catch(next)
}
}

View file

@ -15069,12 +15069,11 @@
"dev": true
},
"p-limit": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz",
"integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==",
"dev": true,
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
"integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
"requires": {
"p-try": "^1.0.0"
"p-try": "^2.0.0"
}
},
"p-locate": {
@ -15084,6 +15083,23 @@
"dev": true,
"requires": {
"p-limit": "^1.1.0"
},
"dependencies": {
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
"dev": true,
"requires": {
"p-try": "^1.0.0"
}
},
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
"dev": true
}
}
},
"p-map": {
@ -15130,10 +15146,9 @@
}
},
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
"dev": true
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"pac-proxy-agent": {
"version": "2.0.2",

View file

@ -86,6 +86,7 @@
"nvd3": "^1.8.6",
"oauth2-server": "^3.0.1",
"optimist": "0.6.1",
"p-limit": "^2.2.1",
"passport": "^0.3.2",
"passport-google-oauth20": "^1.0.0",
"passport-ldapauth": "^0.6.0",

View file

@ -8,6 +8,9 @@ chai.use(require('sinon-chai'))
// Load promise support for chai
chai.use(require('chai-as-promised'))
// Do not truncate assertion errors
chai.config.truncateThreshold = 0
// add support for promises in sinon
require('sinon-as-promised')
// add support for mongoose in sinon

View file

@ -27,7 +27,7 @@ describe('AuthorizationManager', function() {
console: console
},
requires: {
'../Collaborators/CollaboratorsHandler': (this.CollaboratorsHandler = {}),
'../Collaborators/CollaboratorsGetter': (this.CollaboratorsGetter = {}),
'../Project/ProjectGetter': (this.ProjectGetter = {}),
'../../models/User': {
User: (this.User = {})
@ -49,7 +49,7 @@ describe('AuthorizationManager', function() {
beforeEach(function() {
this.ProjectGetter.getProject = sinon.stub()
this.AuthorizationManager.isUserSiteAdmin = sinon.stub()
return (this.CollaboratorsHandler.getMemberIdPrivilegeLevel = sinon.stub())
return (this.CollaboratorsGetter.getMemberIdPrivilegeLevel = sinon.stub())
})
describe('with a token-based project', function() {
@ -64,7 +64,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, false)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, 'readOnly')
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -87,7 +87,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, false)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, false)
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -110,7 +110,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, true)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, false)
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -143,8 +143,8 @@ describe('AuthorizationManager', function() {
)
})
it('should not call CollaboratorsHandler.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal(
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
false
)
})
@ -184,8 +184,8 @@ describe('AuthorizationManager', function() {
)
})
it('should not call CollaboratorsHandler.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal(
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
false
)
})
@ -224,8 +224,8 @@ describe('AuthorizationManager', function() {
)
})
it('should not call CollaboratorsHandler.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal(
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
false
)
})
@ -264,8 +264,8 @@ describe('AuthorizationManager', function() {
)
})
it('should not call CollaboratorsHandler.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal(
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
false
)
})
@ -303,7 +303,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, false)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, 'readOnly')
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -326,7 +326,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, false)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, false)
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -349,7 +349,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, true)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, false)
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -377,8 +377,8 @@ describe('AuthorizationManager', function() {
)
})
it('should not call CollaboratorsHandler.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal(
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
false
)
})
@ -409,7 +409,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, false)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, 'readOnly')
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -432,7 +432,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, false)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, false)
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -455,7 +455,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, true)
this.CollaboratorsHandler.getMemberIdPrivilegeLevel
this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, false)
return this.AuthorizationManager.getPrivilegeLevelForProject(
@ -483,8 +483,8 @@ describe('AuthorizationManager', function() {
)
})
it('should not call CollaboratorsHandler.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsHandler.getMemberIdPrivilegeLevel.called.should.equal(
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
false
)
})
@ -525,7 +525,7 @@ describe('AuthorizationManager', function() {
this.AuthorizationManager.isUserSiteAdmin
.withArgs(this.user_id)
.yields(null, false)
return this.CollaboratorsHandler.getMemberIdPrivilegeLevel
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel
.withArgs(this.user_id, this.project_id)
.yields(null, 'readOnly')
})

View file

@ -1,177 +1,174 @@
/* eslint-disable
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const sinon = require('sinon')
const chai = require('chai')
const should = chai.should()
const { expect } = chai
const modulePath =
'../../../../app/src/Features/Collaborators/CollaboratorsController.js'
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const events = require('events')
const MockRequest = require('../helpers/MockRequest')
const MockResponse = require('../helpers/MockResponse')
const { ObjectId } = require('mongojs')
const MODULE_PATH =
'../../../../app/src/Features/Collaborators/CollaboratorsController.js'
describe('CollaboratorsController', function() {
beforeEach(function() {
this.CollaboratorsController = SandboxedModule.require(modulePath, {
this.res = new MockResponse()
this.req = new MockRequest()
this.user_id = 'user-id-123'
this.project_id = 'project-id-123'
this.callback = sinon.stub()
this.CollaboratorsHandler = {
promises: {
removeUserFromProject: sinon.stub().resolves()
}
}
this.CollaboratorsGetter = {
promises: {
getAllInvitedMembers: sinon.stub()
}
}
this.EditorRealTimeController = {
emitToRoom: sinon.stub()
}
this.TagsHandler = {
promises: {
removeProjectFromAllTags: sinon.stub().resolves()
}
}
this.AuthenticationController = {
getLoggedInUserId: sinon.stub().returns(this.user_id)
}
this.logger = {
err: sinon.stub(),
warn: sinon.stub(),
log: sinon.stub()
}
this.CollaboratorsController = SandboxedModule.require(MODULE_PATH, {
globals: {
console: console
},
requires: {
'./CollaboratorsHandler': (this.CollaboratorsHandler = {}),
'../Editor/EditorRealTimeController': (this.EditorRealTimeController = {}),
'../Tags/TagsHandler': (this.TagsHandler = {}),
'../Authentication/AuthenticationController': (this.AuthenticationController = {}),
'logger-sharelatex': (this.logger = {
err: sinon.stub(),
warn: sinon.stub(),
log: sinon.stub()
})
'./CollaboratorsHandler': this.CollaboratorsHandler,
'./CollaboratorsGetter': this.CollaboratorsGetter,
'../Editor/EditorRealTimeController': this.EditorRealTimeController,
'../Tags/TagsHandler': this.TagsHandler,
'../Authentication/AuthenticationController': this
.AuthenticationController,
'logger-sharelatex': this.logger
}
})
this.res = new MockResponse()
this.req = new MockRequest()
this.project_id = 'project-id-123'
return (this.callback = sinon.stub())
})
describe('removeUserFromProject', function() {
beforeEach(function() {
beforeEach(function(done) {
this.req.params = {
Project_id: (this.project_id = 'project-id-123'),
user_id: (this.user_id = 'user-id-123')
Project_id: this.project_id,
user_id: this.user_id
}
this.res.sendStatus = sinon.stub()
this.CollaboratorsHandler.removeUserFromProject = sinon.stub().callsArg(2)
this.EditorRealTimeController.emitToRoom = sinon.stub()
this.TagsHandler.removeProjectFromAllTags = sinon.stub().callsArg(2)
return this.CollaboratorsController.removeUserFromProject(
this.req,
this.res
)
this.res.sendStatus = sinon.spy(() => {
done()
})
this.CollaboratorsController.removeUserFromProject(this.req, this.res)
})
it('should from the user from the project', function() {
return this.CollaboratorsHandler.removeUserFromProject
.calledWith(this.project_id, this.user_id)
.should.equal(true)
expect(
this.CollaboratorsHandler.promises.removeUserFromProject
).to.have.been.calledWith(this.project_id, this.user_id)
})
it('should emit a userRemovedFromProject event to the proejct', function() {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'userRemovedFromProject', this.user_id)
.should.equal(true)
expect(this.EditorRealTimeController.emitToRoom).to.have.been.calledWith(
this.project_id,
'userRemovedFromProject',
this.user_id
)
})
it('should send the back a success response', function() {
return this.res.sendStatus.calledWith(204).should.equal(true)
this.res.sendStatus.calledWith(204).should.equal(true)
})
it('should have called emitToRoom', function() {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:membership:changed')
.should.equal(true)
expect(this.EditorRealTimeController.emitToRoom).to.have.been.calledWith(
this.project_id,
'project:membership:changed'
)
})
})
describe('removeSelfFromProject', function() {
beforeEach(function() {
this.user_id = 'user-id-123'
this.AuthenticationController.getLoggedInUserId = sinon
.stub()
.returns(this.user_id)
beforeEach(function(done) {
this.req.params = { Project_id: this.project_id }
this.res.sendStatus = sinon.stub()
this.CollaboratorsHandler.removeUserFromProject = sinon.stub().callsArg(2)
this.EditorRealTimeController.emitToRoom = sinon.stub()
this.TagsHandler.removeProjectFromAllTags = sinon.stub().callsArg(2)
return this.CollaboratorsController.removeSelfFromProject(
this.req,
this.res
)
this.res.sendStatus = sinon.spy(() => {
done()
})
this.CollaboratorsController.removeSelfFromProject(this.req, this.res)
})
it('should remove the logged in user from the project', function() {
return this.CollaboratorsHandler.removeUserFromProject
.calledWith(this.project_id, this.user_id)
.should.equal(true)
expect(
this.CollaboratorsHandler.promises.removeUserFromProject
).to.have.been.calledWith(this.project_id, this.user_id)
})
it('should emit a userRemovedFromProject event to the proejct', function() {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'userRemovedFromProject', this.user_id)
.should.equal(true)
})
it('should remove the project from all tags', function() {
sinon.assert.calledWith(
this.TagsHandler.removeProjectFromAllTags,
this.user_id,
this.project_id
expect(this.EditorRealTimeController.emitToRoom).to.have.been.calledWith(
this.project_id,
'userRemovedFromProject',
this.user_id
)
})
it('should remove the project from all tags', function() {
expect(
this.TagsHandler.promises.removeProjectFromAllTags
).to.have.been.calledWith(this.user_id, this.project_id)
})
it('should return a success code', function() {
return this.res.sendStatus.calledWith(204).should.equal(true)
this.res.sendStatus.calledWith(204).should.equal(true)
})
})
describe('getAllMembers', function() {
beforeEach(function() {
this.AuthenticationController.getLoggedInUserId = sinon
.stub()
.returns((this.user_id = 'user-id-123'))
beforeEach(function(done) {
this.req.params = { Project_id: this.project_id }
this.res.json = sinon.stub()
this.res.json = sinon.spy(() => {
done()
})
this.next = sinon.stub()
this.members = [{ a: 1 }]
this.CollaboratorsHandler.getAllInvitedMembers = sinon
.stub()
.callsArgWith(1, null, this.members)
return this.CollaboratorsController.getAllMembers(
this.req,
this.res,
this.next
this.CollaboratorsGetter.promises.getAllInvitedMembers.resolves(
this.members
)
this.CollaboratorsController.getAllMembers(this.req, this.res, this.next)
})
it('should not produce an error', function() {
return this.next.callCount.should.equal(0)
this.next.callCount.should.equal(0)
})
it('should produce a json response', function() {
this.res.json.callCount.should.equal(1)
return this.res.json
.calledWith({ members: this.members })
.should.equal(true)
this.res.json.calledWith({ members: this.members }).should.equal(true)
})
it('should call CollaboratorsHandler.getAllMembers', function() {
return this.CollaboratorsHandler.getAllInvitedMembers.callCount.should.equal(
1
)
it('should call CollaboratorsGetter.getAllInvitedMembers', function() {
expect(this.CollaboratorsGetter.promises.getAllInvitedMembers).to.have
.been.calledOnce
})
describe('when CollaboratorsHandler.getAllInvitedMembers produces an error', function() {
beforeEach(function() {
describe('when CollaboratorsGetter.getAllInvitedMembers produces an error', function() {
beforeEach(function(done) {
this.res.json = sinon.stub()
this.next = sinon.stub()
this.CollaboratorsHandler.getAllInvitedMembers = sinon
.stub()
.callsArgWith(1, new Error('woops'))
return this.CollaboratorsController.getAllMembers(
this.next = sinon.spy(() => {
done()
})
this.CollaboratorsGetter.promises.getAllInvitedMembers.rejects(
new Error('woops')
)
this.CollaboratorsController.getAllMembers(
this.req,
this.res,
this.next
@ -179,12 +176,14 @@ describe('CollaboratorsController', function() {
})
it('should produce an error', function() {
this.next.callCount.should.equal(1)
return this.next.firstCall.args[0].should.be.instanceof(Error)
expect(this.next).to.have.been.calledOnce
expect(this.next).to.have.been.calledWithMatch(
sinon.match.instanceOf(Error)
)
})
it('should not produce a json response', function() {
return this.res.json.callCount.should.equal(0)
this.res.json.callCount.should.equal(0)
})
})
})

View file

@ -0,0 +1,358 @@
const Path = require('path')
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { expect } = require('chai')
const { ObjectId } = require('mongojs')
const { Project } = require('../helpers/models/Project')
const Errors = require('../../../../app/src/Features/Errors/Errors')
const MODULE_PATH = Path.join(
__dirname,
'../../../../app/src/Features/Collaborators/CollaboratorsGetter'
)
describe('CollaboratorsGetter', function() {
beforeEach(function() {
this.userId = 'mock-user-id'
this.ownerRef = ObjectId()
this.readOnlyRef1 = ObjectId()
this.readOnlyRef2 = ObjectId()
this.readWriteRef1 = ObjectId()
this.readWriteRef2 = ObjectId()
this.readOnlyTokenRef = ObjectId()
this.readWriteTokenRef = ObjectId()
this.nonMemberRef = ObjectId()
this.project = {
_id: ObjectId(),
owner_ref: [this.ownerRef],
readOnly_refs: [this.readOnlyRef1, this.readOnlyRef2],
collaberator_refs: [this.readWriteRef1, this.readWriteRef2],
tokenAccessReadAndWrite_refs: [this.readWriteTokenRef],
tokenAccessReadOnly_refs: [this.readOnlyTokenRef],
publicAccesLevel: 'tokenBased'
}
this.UserGetter = {
promises: {
getUser: sinon.stub().resolves(null)
}
}
this.ProjectMock = sinon.mock(Project)
this.ProjectGetter = {
promises: {
getProject: sinon.stub().resolves(this.project)
}
}
this.ProjectEditorHandler = {
buildOwnerAndMembersViews: sinon.stub()
}
this.CollaboratorsGetter = SandboxedModule.require(MODULE_PATH, {
globals: {
console: console
},
requires: {
'../User/UserGetter': this.UserGetter,
'../../models/Project': { Project },
'../Project/ProjectGetter': this.ProjectGetter,
'../Errors/Errors': Errors,
'../Project/ProjectEditorHandler': this.ProjectEditorHandler
}
})
})
afterEach(function() {
this.ProjectMock.verify()
})
describe('getMemberIdsWithPrivilegeLevels', function() {
describe('with project', function() {
it('should return an array of member ids with their privilege levels', async function() {
const result = await this.CollaboratorsGetter.promises.getMemberIdsWithPrivilegeLevels(
this.project._id
)
expect(result).to.have.deep.members([
{
id: this.ownerRef.toString(),
privilegeLevel: 'owner',
source: 'owner'
},
{
id: this.readWriteRef1.toString(),
privilegeLevel: 'readAndWrite',
source: 'invite'
},
{
id: this.readWriteRef2.toString(),
privilegeLevel: 'readAndWrite',
source: 'invite'
},
{
id: this.readOnlyRef1.toString(),
privilegeLevel: 'readOnly',
source: 'invite'
},
{
id: this.readOnlyRef2.toString(),
privilegeLevel: 'readOnly',
source: 'invite'
},
{
id: this.readOnlyTokenRef.toString(),
privilegeLevel: 'readOnly',
source: 'token'
},
{
id: this.readWriteTokenRef.toString(),
privilegeLevel: 'readAndWrite',
source: 'token'
}
])
})
})
describe('with a missing project', function() {
beforeEach(function() {
this.ProjectGetter.promises.getProject.resolves(null)
})
it('should return a NotFoundError', async function() {
await expect(
this.CollaboratorsGetter.promises.getMemberIdsWithPrivilegeLevels(
this.project._id
)
).to.be.rejectedWith(Errors.NotFoundError)
})
})
})
describe('getMemberIds', function() {
it('should return the ids', async function() {
const memberIds = await this.CollaboratorsGetter.promises.getMemberIds(
this.project._id
)
expect(memberIds).to.have.members([
this.ownerRef.toString(),
this.readOnlyRef1.toString(),
this.readOnlyRef2.toString(),
this.readWriteRef1.toString(),
this.readWriteRef2.toString(),
this.readWriteTokenRef.toString(),
this.readOnlyTokenRef.toString()
])
})
})
describe('getInvitedMemberIds', function() {
it('should return the invited ids', async function() {
const memberIds = await this.CollaboratorsGetter.promises.getInvitedMemberIds(
this.project._id
)
expect(memberIds).to.have.members([
this.ownerRef.toString(),
this.readOnlyRef1.toString(),
this.readOnlyRef2.toString(),
this.readWriteRef1.toString(),
this.readWriteRef2.toString()
])
})
})
describe('getInvitedMembersWithPrivilegeLevels', function() {
beforeEach(function() {
this.UserGetter.promises.getUser
.withArgs(this.readOnlyRef1.toString())
.resolves({ _id: this.readOnlyRef1 })
this.UserGetter.promises.getUser
.withArgs(this.readOnlyTokenRef.toString())
.resolves({ _id: this.readOnlyTokenRef })
this.UserGetter.promises.getUser
.withArgs(this.readWriteRef2.toString())
.resolves({ _id: this.readWriteRef2 })
this.UserGetter.promises.getUser
.withArgs(this.readWriteTokenRef.toString())
.resolves({ _id: this.readWriteTokenRef })
})
it('should return an array of invited members with their privilege levels', async function() {
const result = await this.CollaboratorsGetter.promises.getInvitedMembersWithPrivilegeLevels(
this.project._id
)
expect(result).to.have.deep.members([
{ user: { _id: this.readOnlyRef1 }, privilegeLevel: 'readOnly' },
{ user: { _id: this.readWriteRef2 }, privilegeLevel: 'readAndWrite' }
])
})
})
describe('getMemberIdPrivilegeLevel', function() {
it('should return the privilege level if it exists', async function() {
const level = await this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
this.readOnlyRef1,
this.project._id
)
expect(level).to.equal('readOnly')
})
it('should return false if the member has no privilege level', async function() {
const level = await this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
this.nonMemberRef,
this.project._id
)
expect(level).to.equal(false)
})
})
describe('isUserInvitedMemberOfProject', function() {
describe('when user is a member of the project', function() {
it('should return true and the privilegeLevel', async function() {
const isMember = await this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
this.readOnlyRef1
)
expect(isMember).to.equal(true)
})
})
describe('when user is not a member of the project', function() {
it('should return false', async function() {
const isMember = await this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
this.nonMemberRef
)
expect(isMember).to.equal(false)
})
})
})
describe('getProjectsUserIsMemberOf', function() {
beforeEach(function() {
this.fields = 'mock fields'
this.ProjectMock.expects('find')
.withArgs({ collaberator_refs: this.userId }, this.fields)
.chain('exec')
.resolves(['mock-read-write-project-1', 'mock-read-write-project-2'])
this.ProjectMock.expects('find')
.withArgs({ readOnly_refs: this.userId }, this.fields)
.chain('exec')
.resolves(['mock-read-only-project-1', 'mock-read-only-project-2'])
this.ProjectMock.expects('find')
.withArgs(
{
tokenAccessReadAndWrite_refs: this.userId,
publicAccesLevel: 'tokenBased'
},
this.fields
)
.chain('exec')
.resolves([
'mock-token-read-write-project-1',
'mock-token-read-write-project-2'
])
this.ProjectMock.expects('find')
.withArgs(
{
tokenAccessReadOnly_refs: this.userId,
publicAccesLevel: 'tokenBased'
},
this.fields
)
.chain('exec')
.resolves([
'mock-token-read-only-project-1',
'mock-token-read-only-project-2'
])
})
it('should call the callback with the projects', async function() {
const projects = await this.CollaboratorsGetter.promises.getProjectsUserIsMemberOf(
this.userId,
this.fields
)
expect(projects).to.deep.equal({
readAndWrite: [
'mock-read-write-project-1',
'mock-read-write-project-2'
],
readOnly: ['mock-read-only-project-1', 'mock-read-only-project-2'],
tokenReadAndWrite: [
'mock-token-read-write-project-1',
'mock-token-read-write-project-2'
],
tokenReadOnly: [
'mock-token-read-only-project-1',
'mock-token-read-only-project-2'
]
})
})
})
describe('getAllInvitedMembers', function() {
beforeEach(async function() {
this.owningUser = {
_id: this.ownerRef,
email: 'owner@example.com',
features: { a: 1 }
}
this.readWriteUser = {
_id: this.readWriteRef1,
email: 'readwrite@example.com'
}
this.members = [
{ user: this.owningUser, privilegeLevel: 'owner' },
{ user: this.readWriteUser, privilegeLevel: 'readAndWrite' }
]
this.views = {
owner: this.owningUser,
ownerFeatures: this.owningUser.features,
members: [
{ _id: this.readWriteUser._id, email: this.readWriteUser.email }
]
}
this.UserGetter.promises.getUser
.withArgs(this.owningUser._id.toString())
.resolves(this.owningUser)
this.UserGetter.promises.getUser
.withArgs(this.readWriteUser._id.toString())
.resolves(this.readWriteUser)
this.ProjectEditorHandler.buildOwnerAndMembersViews.returns(this.views)
this.result = await this.CollaboratorsGetter.promises.getAllInvitedMembers(
this.project._id
)
})
it('should produce a list of members', function() {
expect(this.result).to.deep.equal(this.views.members)
})
it('should call ProjectEditorHandler.buildOwnerAndMembersViews', function() {
expect(this.ProjectEditorHandler.buildOwnerAndMembersViews).to.have.been
.calledOnce
expect(
this.ProjectEditorHandler.buildOwnerAndMembersViews
).to.have.been.calledWith(this.members)
})
})
describe('userIsTokenMember', function() {
it('should return true when the project is found', async function() {
this.ProjectMock.expects('findOne')
.chain('exec')
.resolves(this.project)
const isMember = await this.CollaboratorsGetter.promises.userIsTokenMember(
this.userId,
this.project._id
)
expect(isMember).to.be.true
})
it('should return false when the project is not found', async function() {
this.ProjectMock.expects('findOne')
.chain('exec')
.resolves(null)
const isMember = await this.CollaboratorsGetter.promises.userIsTokenMember(
this.userId,
this.project._id
)
expect(isMember).to.be.false
})
})
})

View file

@ -51,7 +51,7 @@ describe('CollaboratorsInviteController', function() {
'../Project/ProjectGetter': (this.ProjectGetter = {}),
'../Subscription/LimitationsManager': this.LimitationsManager,
'../User/UserGetter': this.UserGetter,
'./CollaboratorsHandler': (this.CollaboratorsHandler = {}),
'./CollaboratorsGetter': (this.CollaboratorsGetter = {}),
'./CollaboratorsInviteHandler': (this.CollaboratorsInviteHandler = {}),
'logger-sharelatex': (this.logger = {
err: sinon.stub(),
@ -571,9 +571,9 @@ describe('CollaboratorsInviteController', function() {
email: 'john@example.com'
}
this.CollaboratorsHandler.isUserInvitedMemberOfProject = sinon
this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
.stub()
.callsArgWith(2, null, false, null)
.callsArgWith(2, null, false)
this.CollaboratorsInviteHandler.getInviteByToken = sinon
.stub()
.callsArgWith(2, null, this.invite)
@ -606,11 +606,11 @@ describe('CollaboratorsInviteController', function() {
return this.next.callCount.should.equal(0)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -641,9 +641,9 @@ describe('CollaboratorsInviteController', function() {
describe('when user is already a member of the project', function() {
beforeEach(function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject = sinon
this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
.stub()
.callsArgWith(2, null, true, null)
.callsArgWith(2, null, true)
return this.CollaboratorsInviteController.viewInvite(
this.req,
this.res,
@ -662,11 +662,11 @@ describe('CollaboratorsInviteController', function() {
return this.next.callCount.should.equal(0)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -688,7 +688,7 @@ describe('CollaboratorsInviteController', function() {
describe('when isUserInvitedMemberOfProject produces an error', function() {
beforeEach(function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject = sinon
this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
.stub()
.callsArgWith(2, new Error('woops'))
return this.CollaboratorsInviteController.viewInvite(
@ -703,11 +703,11 @@ describe('CollaboratorsInviteController', function() {
return expect(this.next.firstCall.args[0]).to.be.instanceof(Error)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -746,11 +746,11 @@ describe('CollaboratorsInviteController', function() {
return this.next.calledWith(this.err).should.equal(true)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -759,7 +759,7 @@ describe('CollaboratorsInviteController', function() {
this.CollaboratorsInviteHandler.getInviteByToken.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -798,11 +798,11 @@ describe('CollaboratorsInviteController', function() {
return this.next.callCount.should.equal(0)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -811,7 +811,7 @@ describe('CollaboratorsInviteController', function() {
this.CollaboratorsInviteHandler.getInviteByToken.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -840,11 +840,11 @@ describe('CollaboratorsInviteController', function() {
return expect(this.next.firstCall.args[0]).to.be.instanceof(Error)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -888,11 +888,11 @@ describe('CollaboratorsInviteController', function() {
return this.next.callCount.should.equal(0)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -930,11 +930,11 @@ describe('CollaboratorsInviteController', function() {
return expect(this.next.firstCall.args[0]).to.be.instanceof(Error)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})
@ -978,11 +978,11 @@ describe('CollaboratorsInviteController', function() {
return this.next.callCount.should.equal(0)
})
it('should call CollaboratorsHandler.isUserInvitedMemberOfProject', function() {
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount.should.equal(
it('should call CollaboratorsGetter.isUserInvitedMemberOfProject', function() {
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount.should.equal(
1
)
return this.CollaboratorsHandler.isUserInvitedMemberOfProject
return this.CollaboratorsGetter.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.should.equal(true)
})

View file

@ -38,7 +38,7 @@ describe('EditorHttpController', function() {
}),
'./EditorController': (this.EditorController = {}),
'metrics-sharelatex': (this.Metrics = { inc: sinon.stub() }),
'../Collaborators/CollaboratorsHandler': (this.CollaboratorsHandler = {}),
'../Collaborators/CollaboratorsGetter': (this.CollaboratorsGetter = {}),
'../Collaborators/CollaboratorsInviteHandler': (this.CollaboratorsInviteHandler = {}),
'../TokenAccess/TokenAccessHandler': (this.TokenAccessHandler = {}),
'../Authentication/AuthenticationController': (this.AuthenticationController = {}),
@ -177,12 +177,9 @@ describe('EditorHttpController', function() {
this.ProjectGetter.getProjectWithoutDocLines = sinon
.stub()
.callsArgWith(1, null, this.project)
this.CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels = sinon
this.CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels = sinon
.stub()
.callsArgWith(1, null, this.members)
this.CollaboratorsHandler.getTokenMembersWithPrivilegeLevels = sinon
.stub()
.callsArgWith(1, null, this.tokenMembers)
this.CollaboratorsInviteHandler.getAllInvites = sinon
.stub()
.callsArgWith(1, null, this.invites)
@ -229,7 +226,7 @@ describe('EditorHttpController', function() {
})
it('should get the list of users in the project', function() {
return this.CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels
return this.CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels
.calledWith(this.project_id)
.should.equal(true)
})

View file

@ -87,7 +87,7 @@ describe('ProjectController', function() {
getRequestToken: sinon.stub().returns(this.token),
protectTokens: sinon.stub()
}
this.CollaboratorsHandler = {
this.CollaboratorsGetter = {
userIsTokenMember: sinon.stub().callsArgWith(2, null, false)
}
this.ProjectEntityHandler = {}
@ -152,7 +152,7 @@ describe('ProjectController', function() {
.AuthenticationController,
'../Analytics/AnalyticsManager': this.AnalyticsManager,
'../TokenAccess/TokenAccessHandler': this.TokenAccessHandler,
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
'../../infrastructure/Modules': this.Modules,
'./ProjectEntityHandler': this.ProjectEntityHandler,
'../Errors/Errors': Errors,
@ -805,7 +805,7 @@ describe('ProjectController', function() {
})
it('should set isRestrictedTokenMember to true when the user is accessing project via read-only token', function(done) {
this.CollaboratorsHandler.userIsTokenMember.callsArgWith(2, null, true)
this.CollaboratorsGetter.userIsTokenMember.callsArgWith(2, null, true)
this.AuthorizationManager.getPrivilegeLevelForProject.callsArgWith(
3,
null,
@ -820,7 +820,7 @@ describe('ProjectController', function() {
})
it('should set isRestrictedTokenMember to true when anonymous read-only token access', function(done) {
this.CollaboratorsHandler.userIsTokenMember.callsArgWith(2, null, null)
this.CollaboratorsGetter.userIsTokenMember.callsArgWith(2, null, null)
this.AuthenticationController.isUserLoggedIn = sinon.stub().returns(false)
this.AuthorizationManager.getPrivilegeLevelForProject.callsArgWith(
3,

View file

@ -104,11 +104,15 @@ describe('ProjectDeleter', function() {
removeProjectFromAllTags: sinon.stub().callsArgWith(2)
}
this.CollaboratorsHandler = {
removeUserFromAllProjets: sinon.stub().yields(),
getMemberIds: sinon
.stub()
.withArgs(this.project_id)
.yields(null, ['member-id-1', 'member-id-2'])
removeUserFromAllProjects: sinon.stub().yields()
}
this.CollaboratorsGetter = {
promises: {
getMemberIds: sinon
.stub()
.withArgs(this.project_id)
.resolves(['member-id-1', 'member-id-2'])
}
}
this.logger = {
@ -151,6 +155,7 @@ describe('ProjectDeleter', function() {
'../Tags/TagsHandler': this.TagsHandler,
'../FileStore/FileStoreHandler': (this.FileStoreHandler = {}),
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
'../Docstore/DocstoreManager': this.DocstoreManager,
'./ProjectDetailsHandler': this.ProjectDetailsHandler,
'../../infrastructure/mongojs': { db: this.db },
@ -239,11 +244,11 @@ describe('ProjectDeleter', function() {
it('should remove all the projects the user is a collaborator of', function(done) {
this.ProjectDeleter.deleteUsersProjects(this.user._id, () => {
sinon.assert.calledWith(
this.CollaboratorsHandler.removeUserFromAllProjets,
this.CollaboratorsHandler.removeUserFromAllProjects,
this.user._id
)
sinon.assert.calledOnce(
this.CollaboratorsHandler.removeUserFromAllProjets
this.CollaboratorsHandler.removeUserFromAllProjects
)
done()
})

View file

@ -48,7 +48,7 @@ describe('ProjectGetter', function() {
'../../models/DeletedProject': {
DeletedProject: this.DeletedProject
},
'../Collaborators/CollaboratorsHandler': (this.CollaboratorsHandler = {}),
'../Collaborators/CollaboratorsGetter': (this.CollaboratorsGetter = {}),
'../../infrastructure/LockManager': (this.LockManager = {
runWithLock: sinon.spy((namespace, id, runner, callback) =>
runner(callback)
@ -314,8 +314,8 @@ describe('ProjectGetter', function() {
this.Project.find
.withArgs({ owner_ref: this.user_id }, this.fields)
.yields(null, ['mock-owned-projects'])
this.CollaboratorsHandler.getProjectsUserIsMemberOf = sinon.stub()
this.CollaboratorsHandler.getProjectsUserIsMemberOf
this.CollaboratorsGetter.getProjectsUserIsMemberOf = sinon.stub()
this.CollaboratorsGetter.getProjectsUserIsMemberOf
.withArgs(this.user_id, this.fields)
.yields(null, {
readAndWrite: ['mock-rw-projects'],

View file

@ -58,7 +58,7 @@ describe('LimitationsManager', function() {
'../User/UserGetter': this.UserGetter,
'./SubscriptionLocator': this.SubscriptionLocator,
'settings-sharelatex': (this.Settings = {}),
'../Collaborators/CollaboratorsHandler': (this.CollaboratorsHandler = {}),
'../Collaborators/CollaboratorsGetter': (this.CollaboratorsGetter = {}),
'../Collaborators/CollaboratorsInviteHandler': (this.CollaboratorsInviteHandler = {}),
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
'logger-sharelatex': {
@ -150,7 +150,7 @@ describe('LimitationsManager', function() {
this.current_number = 1
this.allowed_number = 2
this.invite_count = 0
this.CollaboratorsHandler.getInvitedCollaboratorCount = (
this.CollaboratorsGetter.getInvitedCollaboratorCount = (
project_id,
callback
) => callback(null, this.current_number)
@ -183,7 +183,7 @@ describe('LimitationsManager', function() {
this.current_number = 1
this.allowed_number = 4
this.invite_count = 1
this.CollaboratorsHandler.getInvitedCollaboratorCount = (
this.CollaboratorsGetter.getInvitedCollaboratorCount = (
project_id,
callback
) => callback(null, this.current_number)
@ -216,7 +216,7 @@ describe('LimitationsManager', function() {
this.current_number = 1
this.allowed_number = 2
this.invite_count = 0
this.CollaboratorsHandler.getInvitedCollaboratorCount = (
this.CollaboratorsGetter.getInvitedCollaboratorCount = (
project_id,
callback
) => callback(null, this.current_number)
@ -249,7 +249,7 @@ describe('LimitationsManager', function() {
this.current_number = 3
this.allowed_number = 2
this.invite_count = 0
this.CollaboratorsHandler.getInvitedCollaboratorCount = (
this.CollaboratorsGetter.getInvitedCollaboratorCount = (
project_id,
callback
) => callback(null, this.current_number)
@ -282,7 +282,7 @@ describe('LimitationsManager', function() {
this.current_number = 100
this.allowed_number = -1
this.invite_count = 0
this.CollaboratorsHandler.getInvitedCollaboratorCount = (
this.CollaboratorsGetter.getInvitedCollaboratorCount = (
project_id,
callback
) => callback(null, this.current_number)
@ -315,7 +315,7 @@ describe('LimitationsManager', function() {
this.current_number = 0
this.allowed_number = 2
this.invite_count = 2
this.CollaboratorsHandler.getInvitedCollaboratorCount = (
this.CollaboratorsGetter.getInvitedCollaboratorCount = (
project_id,
callback
) => callback(null, this.current_number)
@ -348,7 +348,7 @@ describe('LimitationsManager', function() {
this.current_number = 1
this.allowed_number = 2
this.invite_count = 1
this.CollaboratorsHandler.getInvitedCollaboratorCount = (
this.CollaboratorsGetter.getInvitedCollaboratorCount = (
project_id,
callback
) => callback(null, this.current_number)

View file

@ -39,7 +39,7 @@ describe('TpdsUpdateSender', function() {
this.requestQueuer = function(queue, meth, opts, callback) {}
const project = { owner_ref: user_id }
const member_ids = [collaberator_ref_1, read_only_ref_1, user_id]
this.CollaboratorsHandler = {
this.CollaboratorsGetter = {
getInvitedMemberIds: sinon.stub().yields(null, member_ids)
}
this.ProjectGetter = {
@ -69,7 +69,7 @@ describe('TpdsUpdateSender', function() {
'logger-sharelatex': { log() {} },
'../Project/ProjectGetter': this.ProjectGetter,
request: this.request,
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
'metrics-sharelatex': {
inc() {}
}

View file

@ -40,7 +40,7 @@ describe('TokenAccessHandler', function() {
requires: {
'../../models/Project': { Project: (this.Project = {}) },
'settings-sharelatex': (this.settings = {}),
'../Collaborators/CollaboratorsHandler': (this.CollaboratorsHandler = {}),
'../Collaborators/CollaboratorsGetter': (this.CollaboratorsGetter = {}),
'../User/UserGetter': (this.UserGetter = {}),
'../V1/V1Api': (this.V1Api = {
request: sinon.stub()
@ -301,7 +301,7 @@ describe('TokenAccessHandler', function() {
describe('when user does have higher access', function() {
beforeEach(function() {
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
return (this.CollaboratorsHandler.isUserInvitedMemberOfProject = sinon
return (this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
.stub()
.callsArgWith(2, null, true))
})
@ -328,10 +328,10 @@ describe('TokenAccessHandler', function() {
this.userId,
(err, project) => {
expect(
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount
).to.equal(1)
expect(
this.CollaboratorsHandler.isUserInvitedMemberOfProject.calledWith(
this.CollaboratorsGetter.isUserInvitedMemberOfProject.calledWith(
this.userId,
this.project._id
)
@ -358,7 +358,7 @@ describe('TokenAccessHandler', function() {
describe('when user does not have higher access', function() {
beforeEach(function() {
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
return (this.CollaboratorsHandler.isUserInvitedMemberOfProject = sinon
return (this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
.stub()
.callsArgWith(2, null, false))
})
@ -385,10 +385,10 @@ describe('TokenAccessHandler', function() {
this.userId,
(err, project) => {
expect(
this.CollaboratorsHandler.isUserInvitedMemberOfProject.callCount
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount
).to.equal(1)
expect(
this.CollaboratorsHandler.isUserInvitedMemberOfProject.calledWith(
this.CollaboratorsGetter.isUserInvitedMemberOfProject.calledWith(
this.userId,
this.project._id
)
@ -435,7 +435,7 @@ describe('TokenAccessHandler', function() {
describe('when isUserInvitedMemberOfProject produces an error', function() {
beforeEach(function() {
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
return (this.CollaboratorsHandler.isUserInvitedMemberOfProject = sinon
return (this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
.stub()
.callsArgWith(2, new Error('woops')))
})