mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-30 01:55:34 -05:00
Merge pull request #2202 from overleaf/em-collab-set-permissions
Endpoint for setting a collaborator's permissions GitOrigin-RevId: eb4d4dcc476908f5a42fefd7b81ef6fcc000be5b
This commit is contained in:
parent
17babc034f
commit
45e5808a35
12 changed files with 444 additions and 31 deletions
|
@ -1,16 +1,19 @@
|
|||
const OError = require('@overleaf/o-error')
|
||||
const HttpErrors = require('@overleaf/o-error/http')
|
||||
const CollaboratorsHandler = require('./CollaboratorsHandler')
|
||||
const CollaboratorsGetter = require('./CollaboratorsGetter')
|
||||
const AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
const EditorRealTimeController = require('../Editor/EditorRealTimeController')
|
||||
const TagsHandler = require('../Tags/TagsHandler')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const logger = require('logger-sharelatex')
|
||||
const { expressify } = require('../../util/promises')
|
||||
|
||||
module.exports = {
|
||||
removeUserFromProject: expressify(removeUserFromProject),
|
||||
removeSelfFromProject: expressify(removeSelfFromProject),
|
||||
getAllMembers: expressify(getAllMembers)
|
||||
getAllMembers: expressify(getAllMembers),
|
||||
setCollaboratorInfo: expressify(setCollaboratorInfo)
|
||||
}
|
||||
|
||||
async function removeUserFromProject(req, res, next) {
|
||||
|
@ -45,6 +48,26 @@ async function getAllMembers(req, res, next) {
|
|||
res.json({ members })
|
||||
}
|
||||
|
||||
async function setCollaboratorInfo(req, res, next) {
|
||||
const projectId = req.params.Project_id
|
||||
const userId = req.params.user_id
|
||||
const { privilegeLevel } = req.body
|
||||
try {
|
||||
await CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
|
||||
projectId,
|
||||
userId,
|
||||
privilegeLevel
|
||||
)
|
||||
} catch (err) {
|
||||
if (err instanceof Errors.NotFoundError) {
|
||||
throw new HttpErrors.NotFoundError({})
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
res.sendStatus(204)
|
||||
}
|
||||
|
||||
async function _removeUserIdFromProject(projectId, userId) {
|
||||
await CollaboratorsHandler.promises.removeUserFromProject(projectId, userId)
|
||||
EditorRealTimeController.emitToRoom(
|
||||
|
|
|
@ -7,6 +7,7 @@ const ContactManager = require('../Contacts/ContactManager')
|
|||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
|
||||
const CollaboratorsGetter = require('./CollaboratorsGetter')
|
||||
const Errors = require('../Errors/Errors')
|
||||
|
||||
module.exports = {
|
||||
removeUserFromProject: callbackify(removeUserFromProject),
|
||||
|
@ -17,7 +18,8 @@ module.exports = {
|
|||
removeUserFromProject,
|
||||
removeUserFromAllProjects,
|
||||
addUserIdToProject,
|
||||
transferProjects
|
||||
transferProjects,
|
||||
setCollaboratorPrivilegeLevel
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,6 +152,45 @@ async function transferProjects(fromUserId, toUserId) {
|
|||
})
|
||||
}
|
||||
|
||||
async function setCollaboratorPrivilegeLevel(
|
||||
projectId,
|
||||
userId,
|
||||
privilegeLevel
|
||||
) {
|
||||
// Make sure we're only updating the project if the user is already a
|
||||
// collaborator
|
||||
const query = {
|
||||
_id: projectId,
|
||||
$or: [{ collaberator_refs: userId }, { readOnly_refs: userId }]
|
||||
}
|
||||
let update
|
||||
switch (privilegeLevel) {
|
||||
case PrivilegeLevels.READ_AND_WRITE: {
|
||||
update = {
|
||||
$pull: { readOnly_refs: userId },
|
||||
$addToSet: { collaberator_refs: userId }
|
||||
}
|
||||
break
|
||||
}
|
||||
case PrivilegeLevels.READ_ONLY: {
|
||||
update = {
|
||||
$pull: { collaberator_refs: userId },
|
||||
$addToSet: { readOnly_refs: userId }
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw new OError({
|
||||
message: `unknown privilege level: ${privilegeLevel}`
|
||||
})
|
||||
}
|
||||
}
|
||||
const mongoResponse = await Project.updateOne(query, update).exec()
|
||||
if (mongoResponse.n === 0) {
|
||||
throw new Errors.NotFoundError('project or collaborator not found')
|
||||
}
|
||||
}
|
||||
|
||||
async function _flushProjects(projectIds) {
|
||||
for (const projectId of projectIds) {
|
||||
await ProjectEntityHandler.promises.flushProjectToThirdPartyDataStore(
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
/* eslint-disable
|
||||
max-len,
|
||||
*/
|
||||
// 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 CollaboratorsController = require('./CollaboratorsController')
|
||||
const AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
const AuthorizationMiddleware = require('../Authorization/AuthorizationMiddleware')
|
||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
const CollaboratorsInviteController = require('./CollaboratorsInviteController')
|
||||
const RateLimiterMiddleware = require('../Security/RateLimiterMiddleware')
|
||||
const CaptchaMiddleware = require('../Captcha/CaptchaMiddleware')
|
||||
const { Joi, validate } = require('../../infrastructure/Validation')
|
||||
|
||||
module.exports = {
|
||||
apply(webRouter, apiRouter) {
|
||||
|
@ -23,6 +15,24 @@ module.exports = {
|
|||
CollaboratorsController.removeSelfFromProject
|
||||
)
|
||||
|
||||
webRouter.put(
|
||||
'/project/:Project_id/users/:user_id',
|
||||
AuthenticationController.requireLogin(),
|
||||
validate({
|
||||
params: Joi.object({
|
||||
Project_id: Joi.objectId(),
|
||||
user_id: Joi.objectId()
|
||||
}),
|
||||
body: Joi.object({
|
||||
privilegeLevel: Joi.string()
|
||||
.valid(PrivilegeLevels.READ_ONLY, PrivilegeLevels.READ_AND_WRITE)
|
||||
.required()
|
||||
})
|
||||
}),
|
||||
AuthorizationMiddleware.ensureUserCanAdminProject,
|
||||
CollaboratorsController.setCollaboratorInfo
|
||||
)
|
||||
|
||||
webRouter.delete(
|
||||
'/project/:Project_id/users/:user_id',
|
||||
AuthenticationController.requireLogin(),
|
||||
|
@ -91,7 +101,7 @@ module.exports = {
|
|||
CollaboratorsInviteController.viewInvite
|
||||
)
|
||||
|
||||
return webRouter.post(
|
||||
webRouter.post(
|
||||
'/project/:Project_id/invite/token/:token/accept',
|
||||
AuthenticationController.requireLogin(),
|
||||
CollaboratorsInviteController.acceptInvite
|
||||
|
|
|
@ -5,6 +5,7 @@ const logger = require('logger-sharelatex')
|
|||
const metrics = require('metrics-sharelatex')
|
||||
const crawlerLogger = require('./CrawlerLogger')
|
||||
const expressLocals = require('./ExpressLocals')
|
||||
const Validation = require('./Validation')
|
||||
const Router = require('../router')
|
||||
const helmet = require('helmet')
|
||||
const UserSessionsRedis = require('../Features/User/UserSessionsRedis')
|
||||
|
@ -242,6 +243,7 @@ const enableApiRouter =
|
|||
if (enableApiRouter || notDefined(enableApiRouter)) {
|
||||
logger.info('providing api router')
|
||||
app.use(privateApiRouter)
|
||||
app.use(Validation.errorMiddleware)
|
||||
app.use(HttpErrorController.handleError)
|
||||
app.use(ErrorController.handleApiError)
|
||||
}
|
||||
|
@ -250,10 +252,14 @@ const enableWebRouter =
|
|||
Settings.web != null ? Settings.web.enableWebRouter : undefined
|
||||
if (enableWebRouter || notDefined(enableWebRouter)) {
|
||||
logger.info('providing web router')
|
||||
|
||||
app.use(publicApiRouter) // public API goes with web router for public access
|
||||
app.use(Validation.errorMiddleware)
|
||||
app.use(HttpErrorController.handleError)
|
||||
app.use(ErrorController.handleApiError)
|
||||
|
||||
app.use(webRouter)
|
||||
app.use(Validation.errorMiddleware)
|
||||
app.use(HttpErrorController.handleError)
|
||||
app.use(ErrorController.handleError)
|
||||
}
|
||||
|
|
14
services/web/app/src/infrastructure/Validation.js
Normal file
14
services/web/app/src/infrastructure/Validation.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
const { Joi: CelebrateJoi, celebrate, errors } = require('celebrate')
|
||||
const JoiObjectId = require('joi-mongodb-objectid')
|
||||
|
||||
const Joi = CelebrateJoi.extend(JoiObjectId)
|
||||
const errorMiddleware = errors()
|
||||
|
||||
module.exports = { Joi, validate, errorMiddleware }
|
||||
|
||||
/**
|
||||
* Validation middleware
|
||||
*/
|
||||
function validate(schema) {
|
||||
return celebrate(schema, { allowUnknown: true })
|
||||
}
|
84
services/web/package-lock.json
generated
84
services/web/package-lock.json
generated
|
@ -1864,6 +1864,40 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@hapi/address": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.2.tgz",
|
||||
"integrity": "sha512-O4QDrx+JoGKZc6aN64L04vqa7e41tIiLU+OvKdcYaEMP97UttL0f9GIi9/0A4WAMx0uBd6SidDIhktZhgOcN8Q=="
|
||||
},
|
||||
"@hapi/bourne": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz",
|
||||
"integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="
|
||||
},
|
||||
"@hapi/hoek": {
|
||||
"version": "8.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.2.5.tgz",
|
||||
"integrity": "sha512-rmGFzok1zR3xZKd5m3ihWdqafXFxvPHoQ/78+AG5URKbEbJiwBBfRgzbu+07W5f3+07JRshw6QqGbVmCp8ntig=="
|
||||
},
|
||||
"@hapi/joi": {
|
||||
"version": "15.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz",
|
||||
"integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==",
|
||||
"requires": {
|
||||
"@hapi/address": "2.x.x",
|
||||
"@hapi/bourne": "1.x.x",
|
||||
"@hapi/hoek": "8.x.x",
|
||||
"@hapi/topo": "3.x.x"
|
||||
}
|
||||
},
|
||||
"@hapi/topo": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.4.tgz",
|
||||
"integrity": "sha512-aVWQTOI9wBD6zawmOr6f+tdEIxQC8JXfQVLTjgGe8YEStAWGn/GNNVTobKJhbWKveQj2RyYF3oYbO9SC8/eOCA==",
|
||||
"requires": {
|
||||
"@hapi/hoek": "8.x.x"
|
||||
}
|
||||
},
|
||||
"@overleaf/o-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-2.1.0.tgz",
|
||||
|
@ -4169,6 +4203,22 @@
|
|||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
|
||||
},
|
||||
"celebrate": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/celebrate/-/celebrate-10.0.1.tgz",
|
||||
"integrity": "sha512-Eke/caDOlcLjwk8WN4+bX9WyiiIgJ+zjbsh368FAtEj74bNC3Y+gVfFF8efmP9iYVnFSkLy31+6bKCQm/Zza+g==",
|
||||
"requires": {
|
||||
"@hapi/joi": "15.x.x",
|
||||
"escape-html": "1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
}
|
||||
}
|
||||
},
|
||||
"center-align": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
|
||||
|
@ -4197,6 +4247,12 @@
|
|||
"check-error": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"chaid": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/chaid/-/chaid-1.0.2.tgz",
|
||||
"integrity": "sha1-9Y6UNgUoq9qkas8LOlF0oG4Vd1A=",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
|
@ -10675,6 +10731,34 @@
|
|||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
|
||||
"integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w=="
|
||||
},
|
||||
"joi-mongodb-objectid": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/joi-mongodb-objectid/-/joi-mongodb-objectid-0.1.0.tgz",
|
||||
"integrity": "sha512-5N86VRXOd8TZ2nEvlg/EvIeF/StFrB2VAI9iD46di2B2eR7dU2kLhsYTwiAOQMFozIg64qfWs/eEed52w9YpBw==",
|
||||
"requires": {
|
||||
"bson": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bson": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-4.0.2.tgz",
|
||||
"integrity": "sha512-rBdCxMBCg2aR420e1oKUejjcuPZLTibA7zEhWAlliFWEwzuBCC9Dkp5r7VFFIQB2t1WVsvTbohry575mc7Xw5A==",
|
||||
"requires": {
|
||||
"buffer": "^5.1.0",
|
||||
"long": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
|
||||
"integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jquery": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-1.11.1.tgz",
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"bcrypt": "^3.0.4",
|
||||
"body-parser": "^1.13.1",
|
||||
"bufferedstream": "1.6.0",
|
||||
"celebrate": "^10.0.1",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"codemirror": "^5.33.0",
|
||||
"connect-redis": "^3.1.0",
|
||||
|
@ -63,6 +64,7 @@
|
|||
"helmet": "^3.8.1",
|
||||
"http-proxy": "^1.8.1",
|
||||
"is-utf8": "^0.2.1",
|
||||
"joi-mongodb-objectid": "^0.1.0",
|
||||
"jquery": "^1.11.1",
|
||||
"json2csv": "^4.3.3",
|
||||
"jsonwebtoken": "^8.0.1",
|
||||
|
@ -125,6 +127,7 @@
|
|||
"babel-plugin-angularjs-annotate": "^0.10.0",
|
||||
"chai": "3.5.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chaid": "^1.0.2",
|
||||
"clean-css-cli": "^4.2.1",
|
||||
"es6-promise": "^4.0.5",
|
||||
"eslint": "^4.18.1",
|
||||
|
|
1
services/web/test/acceptance/bootstrap.js
vendored
1
services/web/test/acceptance/bootstrap.js
vendored
|
@ -1,2 +1,3 @@
|
|||
const chai = require('chai')
|
||||
chai.use(require('chai-as-promised'))
|
||||
chai.use(require('chaid'))
|
||||
|
|
102
services/web/test/acceptance/src/SharingTests.js
Normal file
102
services/web/test/acceptance/src/SharingTests.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
const { expect } = require('chai')
|
||||
const User = require('./helpers/User').promises
|
||||
|
||||
describe('Sharing', function() {
|
||||
beforeEach(async function() {
|
||||
this.ownerSession = new User()
|
||||
this.collaboratorSession = new User()
|
||||
this.strangerSession = new User()
|
||||
await this.ownerSession.login()
|
||||
await this.collaboratorSession.login()
|
||||
await this.strangerSession.login()
|
||||
this.owner = await this.ownerSession.get()
|
||||
this.collaborator = await this.collaboratorSession.get()
|
||||
this.stranger = await this.strangerSession.get()
|
||||
this.projectId = await this.ownerSession.createProject('Test project')
|
||||
})
|
||||
|
||||
describe('with read-only collaborator', function() {
|
||||
beforeEach(async function() {
|
||||
await this.ownerSession.addUserToProject(
|
||||
this.projectId,
|
||||
this.collaborator,
|
||||
'readOnly'
|
||||
)
|
||||
})
|
||||
|
||||
it('sets the privilege level to read-write', async function() {
|
||||
await this.ownerSession.setCollaboratorInfo(
|
||||
this.projectId,
|
||||
this.collaborator._id,
|
||||
{ privilegeLevel: 'readAndWrite' }
|
||||
)
|
||||
const project = await this.ownerSession.getProject(this.projectId)
|
||||
expect(project.collaberator_refs).to.be.unordered.ids([
|
||||
this.collaborator._id
|
||||
])
|
||||
expect(project.readOnly_refs).to.deep.equal([])
|
||||
})
|
||||
|
||||
it('treats setting the privilege to read-only as a noop', async function() {
|
||||
await this.ownerSession.setCollaboratorInfo(
|
||||
this.projectId,
|
||||
this.collaborator._id,
|
||||
{ privilegeLevel: 'readOnly' }
|
||||
)
|
||||
const project = await this.ownerSession.getProject(this.projectId)
|
||||
expect(project.collaberator_refs).to.deep.equal([])
|
||||
expect(project.readOnly_refs).to.be.unordered.ids([this.collaborator._id])
|
||||
})
|
||||
|
||||
it('prevents non-owners to set the privilege level', async function() {
|
||||
await expect(
|
||||
this.collaboratorSession.setCollaboratorInfo(
|
||||
this.projectId,
|
||||
this.collaborator._id,
|
||||
{ privilegeLevel: 'readAndWrite' }
|
||||
)
|
||||
).to.be.rejectedWith('Unexpected status code: 403')
|
||||
})
|
||||
|
||||
it('validates the privilege level', async function() {
|
||||
await expect(
|
||||
this.collaboratorSession.setCollaboratorInfo(
|
||||
this.projectId,
|
||||
this.collaborator._id,
|
||||
{ privilegeLevel: 'superpowers' }
|
||||
)
|
||||
).to.be.rejectedWith('Unexpected status code: 400')
|
||||
})
|
||||
|
||||
it('returns 404 if the user is not already a collaborator', async function() {
|
||||
await expect(
|
||||
this.ownerSession.setCollaboratorInfo(
|
||||
this.projectId,
|
||||
this.stranger._id,
|
||||
{ privilegeLevel: 'readOnly' }
|
||||
)
|
||||
).to.be.rejectedWith('Unexpected status code: 404')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with read-write collaborator', function() {
|
||||
beforeEach(async function() {
|
||||
await this.ownerSession.addUserToProject(
|
||||
this.projectId,
|
||||
this.collaborator,
|
||||
'readAndWrite'
|
||||
)
|
||||
})
|
||||
|
||||
it('sets the privilege level to read-only', async function() {
|
||||
await this.ownerSession.setCollaboratorInfo(
|
||||
this.projectId,
|
||||
this.collaborator._id,
|
||||
{ privilegeLevel: 'readOnly' }
|
||||
)
|
||||
const project = await this.ownerSession.getProject(this.projectId)
|
||||
expect(project.collaberator_refs).to.deep.equal([])
|
||||
expect(project.readOnly_refs).to.be.unordered.ids([this.collaborator._id])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -678,6 +678,31 @@ class User {
|
|||
callback
|
||||
)
|
||||
}
|
||||
|
||||
setCollaboratorInfo(projectId, userId, info, callback) {
|
||||
this.getCsrfToken(err => {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
this.request.put(
|
||||
{
|
||||
url: `/project/${projectId.toString()}/users/${userId.toString()}`,
|
||||
json: info
|
||||
},
|
||||
(err, response) => {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
if (response.statusCode !== 204) {
|
||||
return callback(
|
||||
new Error(`Unexpected status code: ${response.statusCode}`)
|
||||
)
|
||||
}
|
||||
callback()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
User.promises = class extends User {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { ObjectId } = require('mongodb')
|
||||
const HttpErrors = require('@overleaf/o-error/http')
|
||||
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
||||
const MockRequest = require('../helpers/MockRequest')
|
||||
const MockResponse = require('../helpers/MockResponse')
|
||||
|
||||
|
@ -12,13 +15,14 @@ describe('CollaboratorsController', function() {
|
|||
this.res = new MockResponse()
|
||||
this.req = new MockRequest()
|
||||
|
||||
this.user_id = 'user-id-123'
|
||||
this.project_id = 'project-id-123'
|
||||
this.userId = ObjectId()
|
||||
this.projectId = ObjectId()
|
||||
this.callback = sinon.stub()
|
||||
|
||||
this.CollaboratorsHandler = {
|
||||
promises: {
|
||||
removeUserFromProject: sinon.stub().resolves()
|
||||
removeUserFromProject: sinon.stub().resolves(),
|
||||
setCollaboratorPrivilegeLevel: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
this.CollaboratorsGetter = {
|
||||
|
@ -35,7 +39,7 @@ describe('CollaboratorsController', function() {
|
|||
}
|
||||
}
|
||||
this.AuthenticationController = {
|
||||
getLoggedInUserId: sinon.stub().returns(this.user_id)
|
||||
getLoggedInUserId: sinon.stub().returns(this.userId)
|
||||
}
|
||||
this.logger = {
|
||||
err: sinon.stub(),
|
||||
|
@ -54,6 +58,8 @@ describe('CollaboratorsController', function() {
|
|||
'../Tags/TagsHandler': this.TagsHandler,
|
||||
'../Authentication/AuthenticationController': this
|
||||
.AuthenticationController,
|
||||
'../Errors/Errors': Errors,
|
||||
'@overleaf/o-error/http': HttpErrors,
|
||||
'logger-sharelatex': this.logger
|
||||
}
|
||||
})
|
||||
|
@ -62,8 +68,8 @@ describe('CollaboratorsController', function() {
|
|||
describe('removeUserFromProject', function() {
|
||||
beforeEach(function(done) {
|
||||
this.req.params = {
|
||||
Project_id: this.project_id,
|
||||
user_id: this.user_id
|
||||
Project_id: this.projectId,
|
||||
user_id: this.userId
|
||||
}
|
||||
this.res.sendStatus = sinon.spy(() => {
|
||||
done()
|
||||
|
@ -74,14 +80,14 @@ describe('CollaboratorsController', function() {
|
|||
it('should from the user from the project', function() {
|
||||
expect(
|
||||
this.CollaboratorsHandler.promises.removeUserFromProject
|
||||
).to.have.been.calledWith(this.project_id, this.user_id)
|
||||
).to.have.been.calledWith(this.projectId, this.userId)
|
||||
})
|
||||
|
||||
it('should emit a userRemovedFromProject event to the proejct', function() {
|
||||
expect(this.EditorRealTimeController.emitToRoom).to.have.been.calledWith(
|
||||
this.project_id,
|
||||
this.projectId,
|
||||
'userRemovedFromProject',
|
||||
this.user_id
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -91,7 +97,7 @@ describe('CollaboratorsController', function() {
|
|||
|
||||
it('should have called emitToRoom', function() {
|
||||
expect(this.EditorRealTimeController.emitToRoom).to.have.been.calledWith(
|
||||
this.project_id,
|
||||
this.projectId,
|
||||
'project:membership:changed'
|
||||
)
|
||||
})
|
||||
|
@ -99,7 +105,7 @@ describe('CollaboratorsController', function() {
|
|||
|
||||
describe('removeSelfFromProject', function() {
|
||||
beforeEach(function(done) {
|
||||
this.req.params = { Project_id: this.project_id }
|
||||
this.req.params = { Project_id: this.projectId }
|
||||
this.res.sendStatus = sinon.spy(() => {
|
||||
done()
|
||||
})
|
||||
|
@ -109,21 +115,21 @@ describe('CollaboratorsController', function() {
|
|||
it('should remove the logged in user from the project', function() {
|
||||
expect(
|
||||
this.CollaboratorsHandler.promises.removeUserFromProject
|
||||
).to.have.been.calledWith(this.project_id, this.user_id)
|
||||
).to.have.been.calledWith(this.projectId, this.userId)
|
||||
})
|
||||
|
||||
it('should emit a userRemovedFromProject event to the proejct', function() {
|
||||
expect(this.EditorRealTimeController.emitToRoom).to.have.been.calledWith(
|
||||
this.project_id,
|
||||
this.projectId,
|
||||
'userRemovedFromProject',
|
||||
this.user_id
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('should remove the project from all tags', function() {
|
||||
expect(
|
||||
this.TagsHandler.promises.removeProjectFromAllTags
|
||||
).to.have.been.calledWith(this.user_id, this.project_id)
|
||||
).to.have.been.calledWith(this.userId, this.projectId)
|
||||
})
|
||||
|
||||
it('should return a success code', function() {
|
||||
|
@ -133,7 +139,7 @@ describe('CollaboratorsController', function() {
|
|||
|
||||
describe('getAllMembers', function() {
|
||||
beforeEach(function(done) {
|
||||
this.req.params = { Project_id: this.project_id }
|
||||
this.req.params = { Project_id: this.projectId }
|
||||
this.res.json = sinon.spy(() => {
|
||||
done()
|
||||
})
|
||||
|
@ -187,4 +193,39 @@ describe('CollaboratorsController', function() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('setCollaboratorInfo', function() {
|
||||
beforeEach(function() {
|
||||
this.req.params = {
|
||||
Project_id: this.projectId,
|
||||
user_id: this.userId
|
||||
}
|
||||
this.req.body = { privilegeLevel: 'readOnly' }
|
||||
})
|
||||
|
||||
it('should set the collaborator privilege level', function(done) {
|
||||
this.res.sendStatus = status => {
|
||||
expect(status).to.equal(204)
|
||||
expect(
|
||||
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
|
||||
).to.have.been.calledWith(this.projectId, this.userId, 'readOnly')
|
||||
done()
|
||||
}
|
||||
this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
|
||||
})
|
||||
|
||||
it('should return a 404 when the project or collaborator is not found', function(done) {
|
||||
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel.rejects(
|
||||
new Errors.NotFoundError()
|
||||
)
|
||||
this.CollaboratorsController.setCollaboratorInfo(
|
||||
this.req,
|
||||
this.res,
|
||||
err => {
|
||||
expect(err).to.be.instanceof(HttpErrors.NotFoundError)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -21,8 +21,8 @@ describe('CollaboratorsHandler', function() {
|
|||
warn: sinon.stub(),
|
||||
err: sinon.stub()
|
||||
}
|
||||
this.userId = 'mock-user-id'
|
||||
this.addingUserId = 'adding-user-id'
|
||||
this.userId = ObjectId()
|
||||
this.addingUserId = ObjectId()
|
||||
this.project = {
|
||||
_id: ObjectId()
|
||||
}
|
||||
|
@ -346,4 +346,67 @@ describe('CollaboratorsHandler', function() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('setCollaboratorPrivilegeLevel', function() {
|
||||
it('sets a collaborator to read-only', async function() {
|
||||
this.ProjectMock.expects('updateOne')
|
||||
.withArgs(
|
||||
{
|
||||
_id: this.projectId,
|
||||
$or: [
|
||||
{ collaberator_refs: this.userId },
|
||||
{ readOnly_refs: this.userId }
|
||||
]
|
||||
},
|
||||
{
|
||||
$pull: { collaberator_refs: this.userId },
|
||||
$addToSet: { readOnly_refs: this.userId }
|
||||
}
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves({ n: 1 })
|
||||
await this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
|
||||
this.projectId,
|
||||
this.userId,
|
||||
'readOnly'
|
||||
)
|
||||
})
|
||||
|
||||
it('sets a collaborator to read-write', async function() {
|
||||
this.ProjectMock.expects('updateOne')
|
||||
.withArgs(
|
||||
{
|
||||
_id: this.projectId,
|
||||
$or: [
|
||||
{ collaberator_refs: this.userId },
|
||||
{ readOnly_refs: this.userId }
|
||||
]
|
||||
},
|
||||
{
|
||||
$addToSet: { collaberator_refs: this.userId },
|
||||
$pull: { readOnly_refs: this.userId }
|
||||
}
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves({ n: 1 })
|
||||
await this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
|
||||
this.projectId,
|
||||
this.userId,
|
||||
'readAndWrite'
|
||||
)
|
||||
})
|
||||
|
||||
it('throws a NotFoundError if the project or collaborator does not exist', async function() {
|
||||
this.ProjectMock.expects('updateOne')
|
||||
.chain('exec')
|
||||
.resolves({ n: 0 })
|
||||
await expect(
|
||||
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
|
||||
this.projectId,
|
||||
this.userId,
|
||||
'readAndWrite'
|
||||
)
|
||||
).to.be.rejectedWith(Errors.NotFoundError)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue