From 2c147f575c01b2b920b1de7bea00cd85a0431118 Mon Sep 17 00:00:00 2001 From: Simon Detheridge Date: Fri, 18 Oct 2019 10:32:19 +0100 Subject: [PATCH] Merge pull request #2206 from overleaf/sk-restricted-users-redux Update the joinProject api to include isRestrictedUser flag GitOrigin-RevId: 38988b5c886e5355edc2edcd834ae6e334fc9f10 --- .../Authorization/AuthorizationManager.js | 6 + .../Collaborators/CollaboratorsHandler.js | 28 ++ .../Features/Editor/EditorHttpController.js | 263 +++++++++--------- .../src/Features/Project/ProjectController.js | 7 +- .../test/acceptance/src/TokenAccessTests.js | 23 ++ .../AuthorizationManagerTests.js | 24 ++ .../src/Editor/EditorHttpControllerTests.js | 186 ++++++++----- .../src/Project/ProjectControllerTests.js | 29 +- 8 files changed, 345 insertions(+), 221 deletions(-) diff --git a/services/web/app/src/Features/Authorization/AuthorizationManager.js b/services/web/app/src/Features/Authorization/AuthorizationManager.js index a9b20100a4..ee6e168b90 100644 --- a/services/web/app/src/Features/Authorization/AuthorizationManager.js +++ b/services/web/app/src/Features/Authorization/AuthorizationManager.js @@ -9,6 +9,12 @@ const { ObjectId } = require('mongojs') const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler') module.exports = AuthorizationManager = { + isRestrictedUser(userId, privilegeLevel, isTokenMember) { + return ( + privilegeLevel === PrivilegeLevels.READ_ONLY && (isTokenMember || !userId) + ) + }, + getPublicAccessLevel(projectId, callback) { if (!ObjectId.isValid(projectId)) { return callback(new Error('invalid project id')) diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsHandler.js b/services/web/app/src/Features/Collaborators/CollaboratorsHandler.js index 2007e3273d..2f8904a271 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsHandler.js +++ b/services/web/app/src/Features/Collaborators/CollaboratorsHandler.js @@ -10,11 +10,13 @@ const CollaboratorsGetter = require('./CollaboratorsGetter') const Errors = require('../Errors/Errors') module.exports = { + userIsTokenMember: callbackify(userIsTokenMember), removeUserFromProject: callbackify(removeUserFromProject), removeUserFromAllProjects: callbackify(removeUserFromAllProjects), addUserIdToProject: callbackify(addUserIdToProject), transferProjects: callbackify(transferProjects), promises: { + userIsTokenMember, removeUserFromProject, removeUserFromAllProjects, addUserIdToProject, @@ -191,6 +193,32 @@ async function setCollaboratorPrivilegeLevel( } } +async function userIsTokenMember(userId, projectId) { + if (!userId) { + return false + } + try { + const project = await Project.findOne( + { + _id: projectId, + $or: [ + { tokenAccessReadOnly_refs: userId }, + { tokenAccessReadAndWrite_refs: userId } + ] + }, + { + _id: 1 + } + ) + return project != null + } catch (err) { + throw new OError({ + message: 'problem while checking if user is token member', + info: { userId, projectId } + }).withCause(err) + } +} + async function _flushProjects(projectIds) { for (const projectId of projectIds) { await ProjectEntityHandler.promises.flushProjectToThirdPartyDataStore( diff --git a/services/web/app/src/Features/Editor/EditorHttpController.js b/services/web/app/src/Features/Editor/EditorHttpController.js index 78e830c440..47bb405420 100644 --- a/services/web/app/src/Features/Editor/EditorHttpController.js +++ b/services/web/app/src/Features/Editor/EditorHttpController.js @@ -1,30 +1,14 @@ -/* 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: - * 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 - */ let EditorHttpController -const ProjectEntityUpdateHandler = require('../Project/ProjectEntityUpdateHandler') const ProjectDeleter = require('../Project/ProjectDeleter') const logger = require('logger-sharelatex') -const EditorRealTimeController = require('./EditorRealTimeController') const EditorController = require('./EditorController') const ProjectGetter = require('../Project/ProjectGetter') -const UserGetter = require('../User/UserGetter') const AuthorizationManager = require('../Authorization/AuthorizationManager') const ProjectEditorHandler = require('../Project/ProjectEditorHandler') const Metrics = require('metrics-sharelatex') const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter') const CollaboratorsInviteHandler = require('../Collaborators/CollaboratorsInviteHandler') +const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler') const PrivilegeLevels = require('../Authorization/PrivilegeLevels') const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler') const AuthenticationController = require('../Authentication/AuthenticationController') @@ -32,63 +16,67 @@ const Errors = require('../Errors/Errors') module.exports = EditorHttpController = { joinProject(req, res, next) { - const project_id = req.params.Project_id - let { user_id } = req.query - if (user_id === 'anonymous-user') { - user_id = null + const projectId = req.params.Project_id + let userId = req.query.user_id + if (userId === 'anonymous-user') { + userId = null } - logger.log({ user_id, project_id }, 'join project request') + logger.log({ userId, projectId }, 'join project request') Metrics.inc('editor.join-project') - return EditorHttpController._buildJoinProjectView( - req, - project_id, - user_id, - function(error, project, privilegeLevel) { - if (error != null) { - return next(error) - } - // Hide access tokens if this is not the project owner - TokenAccessHandler.protectTokens(project, privilegeLevel) - res.json({ - project, - privilegeLevel - }) - // Only show the 'renamed or deleted' message once - if (project != null ? project.deletedByExternalDataSource : undefined) { - return ProjectDeleter.unmarkAsDeletedByExternalSource(project_id) - } + EditorHttpController._buildJoinProjectView(req, projectId, userId, function( + error, + project, + privilegeLevel, + isRestrictedUser + ) { + if (error) { + return next(error) } - ) + // Hide access tokens if this is not the project owner + TokenAccessHandler.protectTokens(project, privilegeLevel) + if (isRestrictedUser) { + project.owner = { _id: project.owner._id } + } + res.json({ + project, + privilegeLevel, + isRestrictedUser + }) + // Only show the 'renamed or deleted' message once + if (project != null ? project.deletedByExternalDataSource : undefined) { + return ProjectDeleter.unmarkAsDeletedByExternalSource(projectId) + } + }) }, - _buildJoinProjectView(req, project_id, user_id, callback) { + _buildJoinProjectView(req, projectId, userId, callback) { if (callback == null) { - callback = function(error, project, privilegeLevel) {} + callback = function() {} } - logger.log({ project_id, user_id }, 'building the joinProject view') - return ProjectGetter.getProjectWithoutDocLines(project_id, function( + logger.log({ projectId, userId }, 'building the joinProject view') + ProjectGetter.getProjectWithoutDocLines(projectId, function( error, project ) { - if (error != null) { + if (error) { return callback(error) } if (project == null) { return callback(new Errors.NotFoundError('project not found')) } - return CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels( - project_id, + CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels( + projectId, function(error, members) { - if (error != null) { + if (error) { return callback(error) } - const token = TokenAccessHandler.getRequestToken(req, project_id) - return AuthorizationManager.getPrivilegeLevelForProject( - user_id, - project_id, + const token = TokenAccessHandler.getRequestToken(req, projectId) + AuthorizationManager.getPrivilegeLevelForProject( + userId, + projectId, token, function(error, privilegeLevel) { - if (error != null) { + if (error) { return callback(error) } if ( @@ -96,38 +84,53 @@ module.exports = EditorHttpController = { privilegeLevel === PrivilegeLevels.NONE ) { logger.log( - { project_id, user_id, privilegeLevel }, + { projectId, userId, privilegeLevel }, 'not an acceptable privilege level, returning null' ) return callback(null, null, false) } - return CollaboratorsInviteHandler.getAllInvites( - project_id, - function(error, invites) { - if (error != null) { - return callback(error) - } - logger.log( - { - project_id, - user_id, - memberCount: members.length, - inviteCount: invites.length, - privilegeLevel - }, - 'returning project model view' - ) - return callback( - null, - ProjectEditorHandler.buildProjectModelView( - project, - members, - invites - ), - privilegeLevel - ) + CollaboratorsInviteHandler.getAllInvites(projectId, function( + error, + invites + ) { + if (error) { + return callback(error) } - ) + logger.log( + { + projectId, + userId, + memberCount: members.length, + inviteCount: invites.length, + privilegeLevel + }, + 'returning project model view' + ) + CollaboratorsHandler.userIsTokenMember( + userId, + projectId, + (err, isTokenMember) => { + if (err) { + return callback(err) + } + const isRestrictedUser = AuthorizationManager.isRestrictedUser( + userId, + privilegeLevel, + isTokenMember + ) + callback( + null, + ProjectEditorHandler.buildProjectModelView( + project, + members, + invites + ), + privilegeLevel, + isRestrictedUser + ) + } + ) + }) } ) } @@ -140,24 +143,24 @@ module.exports = EditorHttpController = { }, addDoc(req, res, next) { - const project_id = req.params.Project_id + const projectId = req.params.Project_id const { name } = req.body - const { parent_folder_id } = req.body - const user_id = AuthenticationController.getLoggedInUserId(req) + const parentFolderId = req.body.parent_folder_id + const userId = AuthenticationController.getLoggedInUserId(req) logger.log( - { project_id, name, parent_folder_id }, + { projectId, name, parentFolderId }, 'getting request to add doc to project' ) if (!EditorHttpController._nameIsAcceptableLength(name)) { return res.sendStatus(400) } - return EditorController.addDoc( - project_id, - parent_folder_id, + EditorController.addDoc( + projectId, + parentFolderId, name, [], 'editor', - user_id, + userId, function(error, doc) { if (error && error.message === 'project_has_to_many_files') { return res @@ -173,15 +176,15 @@ module.exports = EditorHttpController = { }, addFolder(req, res, next) { - const project_id = req.params.Project_id + const projectId = req.params.Project_id const { name } = req.body - const { parent_folder_id } = req.body + const parentFolderId = req.body.parent_folder_id if (!EditorHttpController._nameIsAcceptableLength(name)) { return res.sendStatus(400) } - return EditorController.addFolder( - project_id, - parent_folder_id, + EditorController.addFolder( + projectId, + parentFolderId, name, 'editor', function(error, doc) { @@ -201,22 +204,22 @@ module.exports = EditorHttpController = { }, renameEntity(req, res, next) { - const project_id = req.params.Project_id - const { entity_id } = req.params - const { entity_type } = req.params + const projectId = req.params.Project_id + const entityId = req.params.entity_id + const entityType = req.params.entity_type const { name } = req.body if (!EditorHttpController._nameIsAcceptableLength(name)) { return res.sendStatus(400) } - const user_id = AuthenticationController.getLoggedInUserId(req) - return EditorController.renameEntity( - project_id, - entity_id, - entity_type, + const userId = AuthenticationController.getLoggedInUserId(req) + EditorController.renameEntity( + projectId, + entityId, + entityType, name, - user_id, + userId, function(error) { - if (error != null) { + if (error) { return next(error) } return res.sendStatus(204) @@ -225,19 +228,19 @@ module.exports = EditorHttpController = { }, moveEntity(req, res, next) { - const project_id = req.params.Project_id - const { entity_id } = req.params - const { entity_type } = req.params - const { folder_id } = req.body - const user_id = AuthenticationController.getLoggedInUserId(req) - return EditorController.moveEntity( - project_id, - entity_id, - folder_id, - entity_type, - user_id, + const projectId = req.params.Project_id + const entityId = req.params.entity_id + const entityType = req.params.entity_type + const folderId = req.body.folder_id + const userId = AuthenticationController.getLoggedInUserId(req) + EditorController.moveEntity( + projectId, + entityId, + folderId, + entityType, + userId, function(error) { - if (error != null) { + if (error) { return next(error) } return res.sendStatus(204) @@ -247,35 +250,35 @@ module.exports = EditorHttpController = { deleteDoc(req, res, next) { req.params.entity_type = 'doc' - return EditorHttpController.deleteEntity(req, res, next) + EditorHttpController.deleteEntity(req, res, next) }, deleteFile(req, res, next) { req.params.entity_type = 'file' - return EditorHttpController.deleteEntity(req, res, next) + EditorHttpController.deleteEntity(req, res, next) }, deleteFolder(req, res, next) { req.params.entity_type = 'folder' - return EditorHttpController.deleteEntity(req, res, next) + EditorHttpController.deleteEntity(req, res, next) }, deleteEntity(req, res, next) { - const project_id = req.params.Project_id - const { entity_id } = req.params - const { entity_type } = req.params - const user_id = AuthenticationController.getLoggedInUserId(req) - return EditorController.deleteEntity( - project_id, - entity_id, - entity_type, + const projectId = req.params.Project_id + const entityId = req.params.entity_id + const entityType = req.params.entity_type + const userId = AuthenticationController.getLoggedInUserId(req) + EditorController.deleteEntity( + projectId, + entityId, + entityType, 'editor', - user_id, + userId, function(error) { - if (error != null) { + if (error) { return next(error) } - return res.sendStatus(204) + res.sendStatus(204) } ) } diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index e8998f3d39..abddcbe0c1 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -723,8 +723,11 @@ const ProjectController = { anonymous, anonymousAccessToken: req._anonymousAccessToken, isTokenMember, - isRestrictedTokenMember: - privilegeLevel === 'readOnly' && (anonymous || isTokenMember), + isRestrictedTokenMember: AuthorizationManager.isRestrictedUser( + userId, + privilegeLevel, + isTokenMember + ), languages: Settings.languages, editorThemes: THEME_LIST, maxDocLength: Settings.max_doc_length, diff --git a/services/web/test/acceptance/src/TokenAccessTests.js b/services/web/test/acceptance/src/TokenAccessTests.js index 2f18866f15..9691721214 100644 --- a/services/web/test/acceptance/src/TokenAccessTests.js +++ b/services/web/test/acceptance/src/TokenAccessTests.js @@ -237,6 +237,13 @@ describe('TokenAccess', function() { this.projectId, (response, body) => { expect(body.privilegeLevel).to.equal('readOnly') + expect(body.isRestrictedUser).to.equal(true) + expect(body.project.owner).to.have.keys('_id') + expect(body.project.owner).to.not.have.any.keys( + 'email', + 'first_name', + 'last_name' + ) }, cb ) @@ -344,6 +351,13 @@ describe('TokenAccess', function() { this.tokens.readOnly, (response, body) => { expect(body.privilegeLevel).to.equal('readOnly') + expect(body.isRestrictedUser).to.equal(true) + expect(body.project.owner).to.have.keys('_id') + expect(body.project.owner).to.not.have.any.keys( + 'email', + 'first_name', + 'last_name' + ) }, cb ) @@ -453,6 +467,15 @@ describe('TokenAccess', function() { this.projectId, (response, body) => { expect(body.privilegeLevel).to.equal('readAndWrite') + expect(body.isRestrictedUser).to.equal(false) + expect(body.project.owner).to.have.all.keys( + '_id', + 'email', + 'first_name', + 'last_name', + 'privileges', + 'signUpDate' + ) }, cb ) diff --git a/services/web/test/unit/src/Authorization/AuthorizationManagerTests.js b/services/web/test/unit/src/Authorization/AuthorizationManagerTests.js index 8c197c20df..7055d2f58e 100644 --- a/services/web/test/unit/src/Authorization/AuthorizationManagerTests.js +++ b/services/web/test/unit/src/Authorization/AuthorizationManagerTests.js @@ -45,6 +45,30 @@ describe('AuthorizationManager', function() { return (this.callback = sinon.stub()) }) + describe('isRestrictedUser', function() { + it('should produce the correct values', function() { + const notRestrictedScenarios = [ + [null, 'readAndWrite', false], + ['id', 'readAndWrite', true], + ['id', 'readOnly', false] + ] + const restrictedScenarios = [ + [null, 'readOnly', false], + ['id', 'readOnly', true] + ] + for (var notRestrictedArgs of notRestrictedScenarios) { + expect( + this.AuthorizationManager.isRestrictedUser(...notRestrictedArgs) + ).to.equal(false) + } + for (var restrictedArgs of restrictedScenarios) { + expect( + this.AuthorizationManager.isRestrictedUser(...restrictedArgs) + ).to.equal(true) + } + }) + }) + describe('getPrivilegeLevelForProject', function() { beforeEach(function() { this.ProjectGetter.getProject = sinon.stub() diff --git a/services/web/test/unit/src/Editor/EditorHttpControllerTests.js b/services/web/test/unit/src/Editor/EditorHttpControllerTests.js index 3bf90f2bf7..5915f5252c 100644 --- a/services/web/test/unit/src/Editor/EditorHttpControllerTests.js +++ b/services/web/test/unit/src/Editor/EditorHttpControllerTests.js @@ -1,14 +1,3 @@ -/* eslint-disable - max-len, - no-return-assign, -*/ -// 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 SandboxedModule = require('sandboxed-module') const sinon = require('sinon') require('chai').should() @@ -25,13 +14,10 @@ describe('EditorHttpController', function() { console: console }, requires: { - '../Project/ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}), '../Project/ProjectDeleter': (this.ProjectDeleter = {}), '../Project/ProjectGetter': (this.ProjectGetter = {}), - '../User/UserGetter': (this.UserGetter = {}), '../Authorization/AuthorizationManager': (this.AuthorizationManager = {}), '../Project/ProjectEditorHandler': (this.ProjectEditorHandler = {}), - './EditorRealTimeController': (this.EditorRealTimeController = {}), 'logger-sharelatex': (this.logger = { log: sinon.stub(), error: sinon.stub() @@ -39,6 +25,7 @@ describe('EditorHttpController', function() { './EditorController': (this.EditorController = {}), 'metrics-sharelatex': (this.Metrics = { inc: sinon.stub() }), '../Collaborators/CollaboratorsGetter': (this.CollaboratorsGetter = {}), + '../Collaborators/CollaboratorsHandler': (this.CollaboratorsHandler = {}), '../Collaborators/CollaboratorsInviteHandler': (this.CollaboratorsInviteHandler = {}), '../TokenAccess/TokenAccessHandler': (this.TokenAccessHandler = {}), '../Authentication/AuthenticationController': (this.AuthenticationController = {}), @@ -64,7 +51,7 @@ describe('EditorHttpController', function() { this.TokenAccessHandler.getRequestToken = sinon .stub() .returns((this.token = null)) - return (this.TokenAccessHandler.protectTokens = sinon.stub()) + this.TokenAccessHandler.protectTokens = sinon.stub() }) describe('joinProject', function() { @@ -72,71 +59,112 @@ describe('EditorHttpController', function() { this.req.params = { Project_id: this.project_id } this.req.query = { user_id: this.user_id } this.projectView = { - _id: this.project_id + _id: this.project_id, + owner: { + _id: 'owner', + email: 'owner@example.com', + other_property: true + } + } + this.reducedProjectView = { + _id: this.project_id, + owner: { _id: 'owner' } } this.EditorHttpController._buildJoinProjectView = sinon .stub() - .callsArgWith(3, null, this.projectView, 'owner') - return (this.ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub()) + .callsArgWith(3, null, this.projectView, 'owner', false) + this.ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub() }) describe('successfully', function() { beforeEach(function() { - return this.EditorHttpController.joinProject(this.req, this.res) + this.AuthorizationManager.isRestrictedUser = sinon.stub().returns(false) + this.EditorHttpController.joinProject(this.req, this.res) }) it('should get the project view', function() { - return this.EditorHttpController._buildJoinProjectView + this.EditorHttpController._buildJoinProjectView .calledWith(this.req, this.project_id, this.user_id) .should.equal(true) }) it('should return the project and privilege level', function() { - return this.res.json + this.res.json .calledWith({ project: this.projectView, - privilegeLevel: 'owner' + privilegeLevel: 'owner', + isRestrictedUser: false }) .should.equal(true) }) it('should not try to unmark the project as deleted', function() { - return this.ProjectDeleter.unmarkAsDeletedByExternalSource.called.should.equal( + this.ProjectDeleter.unmarkAsDeletedByExternalSource.called.should.equal( false ) }) it('should send an inc metric', function() { - return this.Metrics.inc - .calledWith('editor.join-project') - .should.equal(true) + this.Metrics.inc.calledWith('editor.join-project').should.equal(true) }) }) describe('when the project is marked as deleted', function() { beforeEach(function() { this.projectView.deletedByExternalDataSource = true - return this.EditorHttpController.joinProject(this.req, this.res) + this.EditorHttpController.joinProject(this.req, this.res) }) it('should unmark the project as deleted', function() { - return this.ProjectDeleter.unmarkAsDeletedByExternalSource + this.ProjectDeleter.unmarkAsDeletedByExternalSource .calledWith(this.project_id) .should.equal(true) }) }) + describe('with an restricted user', function() { + beforeEach(function() { + this.EditorHttpController._buildJoinProjectView = sinon + .stub() + .callsArgWith(3, null, this.projectView, 'readOnly', true) + this.EditorHttpController.joinProject(this.req, this.res) + }) + + it('should mark the user as restricted, and hide details of owner', function() { + this.res.json + .calledWith({ + project: this.reducedProjectView, + privilegeLevel: 'readOnly', + isRestrictedUser: true + }) + .should.equal(true) + }) + }) + describe('with an anonymous user', function() { beforeEach(function() { this.req.query = { user_id: 'anonymous-user' } - return this.EditorHttpController.joinProject(this.req, this.res) + this.EditorHttpController._buildJoinProjectView = sinon + .stub() + .callsArgWith(3, null, this.projectView, 'readOnly', true) + this.EditorHttpController.joinProject(this.req, this.res) }) it('should pass the user id as null', function() { - return this.EditorHttpController._buildJoinProjectView + this.EditorHttpController._buildJoinProjectView .calledWith(this.req, this.project_id, null) .should.equal(true) }) + + it('should mark the user as restricted', function() { + this.res.json + .calledWith({ + project: this.reducedProjectView, + privilegeLevel: 'readOnly', + isRestrictedUser: true + }) + .should.equal(true) + }) }) }) @@ -180,18 +208,19 @@ describe('EditorHttpController', function() { this.CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels = sinon .stub() .callsArgWith(1, null, this.members) + this.CollaboratorsHandler.userIsTokenMember = sinon + .stub() + .callsArgWith(2, null, false) + this.AuthorizationManager.isRestrictedUser = sinon.stub().returns(false) this.CollaboratorsInviteHandler.getAllInvites = sinon .stub() .callsArgWith(1, null, this.invites) - return (this.UserGetter.getUser = sinon - .stub() - .callsArgWith(2, null, this.user)) }) describe('when project is not found', function() { beforeEach(function() { this.ProjectGetter.getProjectWithoutDocLines.yields(null, null) - return this.EditorHttpController._buildJoinProjectView( + this.EditorHttpController._buildJoinProjectView( this.req, this.project_id, this.user_id, @@ -211,7 +240,7 @@ describe('EditorHttpController', function() { this.AuthorizationManager.getPrivilegeLevelForProject = sinon .stub() .callsArgWith(3, null, 'owner') - return this.EditorHttpController._buildJoinProjectView( + this.EditorHttpController._buildJoinProjectView( this.req, this.project_id, this.user_id, @@ -220,32 +249,57 @@ describe('EditorHttpController', function() { }) it('should find the project without doc lines', function() { - return this.ProjectGetter.getProjectWithoutDocLines + this.ProjectGetter.getProjectWithoutDocLines .calledWith(this.project_id) .should.equal(true) }) it('should get the list of users in the project', function() { - return this.CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels + this.CollaboratorsGetter.getInvitedMembersWithPrivilegeLevels .calledWith(this.project_id) .should.equal(true) }) it('should check the privilege level', function() { - return this.AuthorizationManager.getPrivilegeLevelForProject + this.AuthorizationManager.getPrivilegeLevelForProject .calledWith(this.user_id, this.project_id, this.token) .should.equal(true) }) + it('should check if user is restricted', function() { + this.AuthorizationManager.isRestrictedUser.called.should.equal(true) + }) + it('should include the invites', function() { - return this.CollaboratorsInviteHandler.getAllInvites + this.CollaboratorsInviteHandler.getAllInvites .calledWith(this.project._id) .should.equal(true) }) it('should return the project model view, privilege level and protocol version', function() { - return this.callback - .calledWith(null, this.projectModelView, 'owner') + this.callback + .calledWith(null, this.projectModelView, 'owner', false) + .should.equal(true) + }) + }) + + describe('when user is restricted', function() { + beforeEach(function() { + this.AuthorizationManager.getPrivilegeLevelForProject = sinon + .stub() + .callsArgWith(3, null, 'readOnly') + this.AuthorizationManager.isRestrictedUser.returns(true) + this.EditorHttpController._buildJoinProjectView( + this.req, + this.project_id, + this.user_id, + this.callback + ) + }) + + it('should set the isRestrictedUser flag', function() { + this.callback + .calledWith(null, this.projectModelView, 'readOnly', true) .should.equal(true) }) }) @@ -255,7 +309,7 @@ describe('EditorHttpController', function() { this.AuthorizationManager.getPrivilegeLevelForProject = sinon .stub() .callsArgWith(3, null, null) - return this.EditorHttpController._buildJoinProjectView( + this.EditorHttpController._buildJoinProjectView( this.req, this.project_id, this.user_id, @@ -264,7 +318,7 @@ describe('EditorHttpController', function() { }) it('should return false in the callback', function() { - return this.callback.calledWith(null, null, false).should.equal(true) + this.callback.calledWith(null, null, false).should.equal(true) }) }) }) @@ -277,18 +331,18 @@ describe('EditorHttpController', function() { name: (this.name = 'doc-name'), parent_folder_id: this.parent_folder_id } - return (this.EditorController.addDoc = sinon + this.EditorController.addDoc = sinon .stub() - .callsArgWith(6, null, this.doc)) + .callsArgWith(6, null, this.doc) }) describe('successfully', function() { beforeEach(function() { - return this.EditorHttpController.addDoc(this.req, this.res) + this.EditorHttpController.addDoc(this.req, this.res) }) it('should call EditorController.addDoc', function() { - return this.EditorController.addDoc + this.EditorController.addDoc .calledWith( this.project_id, this.parent_folder_id, @@ -301,7 +355,7 @@ describe('EditorHttpController', function() { }) it('should send the doc back as JSON', function() { - return this.res.json.calledWith(this.doc).should.equal(true) + this.res.json.calledWith(this.doc).should.equal(true) }) }) @@ -339,18 +393,18 @@ describe('EditorHttpController', function() { name: (this.name = 'folder-name'), parent_folder_id: this.parent_folder_id } - return (this.EditorController.addFolder = sinon + this.EditorController.addFolder = sinon .stub() - .callsArgWith(4, null, this.folder)) + .callsArgWith(4, null, this.folder) }) describe('successfully', function() { beforeEach(function() { - return this.EditorHttpController.addFolder(this.req, this.res) + this.EditorHttpController.addFolder(this.req, this.res) }) it('should call EditorController.addFolder', function() { - return this.EditorController.addFolder + this.EditorController.addFolder .calledWith( this.project_id, this.parent_folder_id, @@ -361,7 +415,7 @@ describe('EditorHttpController', function() { }) it('should send the folder back as JSON', function() { - return this.res.json.calledWith(this.folder).should.equal(true) + this.res.json.calledWith(this.folder).should.equal(true) }) }) @@ -417,11 +471,11 @@ describe('EditorHttpController', function() { } this.req.body = { name: (this.name = 'new-name') } this.EditorController.renameEntity = sinon.stub().callsArg(5) - return this.EditorHttpController.renameEntity(this.req, this.res) + this.EditorHttpController.renameEntity(this.req, this.res) }) it('should call EditorController.renameEntity', function() { - return this.EditorController.renameEntity + this.EditorController.renameEntity .calledWith( this.project_id, this.entity_id, @@ -433,7 +487,7 @@ describe('EditorHttpController', function() { }) it('should send back a success response', function() { - return this.res.sendStatus.calledWith(204).should.equal(true) + this.res.sendStatus.calledWith(204).should.equal(true) }) }) @@ -449,11 +503,11 @@ describe('EditorHttpController', function() { 'EDMUBEEBKBXUUUZERMNSXFFWIBHGSDAWGMRIQWJBXGWSBVWSIKLFPRBYSJEKMFHTRZBHVKJSRGKTBHMJRXPHORFHAKRNPZGGYIOTEDMUBEEBKBXUUUZERMNSXFFWIBHGSDAWGMRIQWJBXGWSBVWSIKLFPRBYSJEKMFHTRZBHVKJSRGKTBHMJRXPHORFHAKRNPZGGYIOT') } this.EditorController.renameEntity = sinon.stub().callsArg(4) - return this.EditorHttpController.renameEntity(this.req, this.res) + this.EditorHttpController.renameEntity(this.req, this.res) }) it('should send back a bad request status code', function() { - return this.res.sendStatus.calledWith(400).should.equal(true) + this.res.sendStatus.calledWith(400).should.equal(true) }) }) @@ -466,11 +520,11 @@ describe('EditorHttpController', function() { } this.req.body = { name: (this.name = '') } this.EditorController.renameEntity = sinon.stub().callsArg(4) - return this.EditorHttpController.renameEntity(this.req, this.res) + this.EditorHttpController.renameEntity(this.req, this.res) }) it('should send back a bad request status code', function() { - return this.res.sendStatus.calledWith(400).should.equal(true) + this.res.sendStatus.calledWith(400).should.equal(true) }) }) @@ -483,11 +537,11 @@ describe('EditorHttpController', function() { } this.req.body = { folder_id: (this.folder_id = 'folder-id-123') } this.EditorController.moveEntity = sinon.stub().callsArg(5) - return this.EditorHttpController.moveEntity(this.req, this.res) + this.EditorHttpController.moveEntity(this.req, this.res) }) it('should call EditorController.moveEntity', function() { - return this.EditorController.moveEntity + this.EditorController.moveEntity .calledWith( this.project_id, this.entity_id, @@ -499,7 +553,7 @@ describe('EditorHttpController', function() { }) it('should send back a success response', function() { - return this.res.sendStatus.calledWith(204).should.equal(true) + this.res.sendStatus.calledWith(204).should.equal(true) }) }) @@ -511,11 +565,11 @@ describe('EditorHttpController', function() { entity_type: (this.entity_type = 'entity-type') } this.EditorController.deleteEntity = sinon.stub().callsArg(5) - return this.EditorHttpController.deleteEntity(this.req, this.res) + this.EditorHttpController.deleteEntity(this.req, this.res) }) it('should call EditorController.deleteEntity', function() { - return this.EditorController.deleteEntity + this.EditorController.deleteEntity .calledWith( this.project_id, this.entity_id, @@ -527,7 +581,7 @@ describe('EditorHttpController', function() { }) it('should send back a success response', function() { - return this.res.sendStatus.calledWith(204).should.equal(true) + this.res.sendStatus.calledWith(204).should.equal(true) }) }) }) diff --git a/services/web/test/unit/src/Project/ProjectControllerTests.js b/services/web/test/unit/src/Project/ProjectControllerTests.js index 1e54b65128..1a2dcd8b1f 100644 --- a/services/web/test/unit/src/Project/ProjectControllerTests.js +++ b/services/web/test/unit/src/Project/ProjectControllerTests.js @@ -59,7 +59,10 @@ describe('ProjectController', function() { this.TagsHandler = { getAllTags: sinon.stub() } this.NotificationsHandler = { getUserNotifications: sinon.stub() } this.UserModel = { findById: sinon.stub() } - this.AuthorizationManager = { getPrivilegeLevelForProject: sinon.stub() } + this.AuthorizationManager = { + getPrivilegeLevelForProject: sinon.stub(), + isRestrictedUser: sinon.stub().returns(false) + } this.EditorController = { renameProject: sinon.stub() } this.InactiveProjectManager = { reactivateProjectIfRequired: sinon.stub() } this.ProjectUpdateHandler = { markAsOpened: sinon.stub() } @@ -804,13 +807,8 @@ describe('ProjectController', function() { return this.ProjectController.loadEditor(this.req, this.res) }) - it('should set isRestrictedTokenMember to true when the user is accessing project via read-only token', function(done) { - this.CollaboratorsGetter.userIsTokenMember.callsArgWith(2, null, true) - this.AuthorizationManager.getPrivilegeLevelForProject.callsArgWith( - 3, - null, - 'readOnly' - ) + it('should set isRestrictedTokenMember when appropriate', function(done) { + this.AuthorizationManager.isRestrictedUser.returns(true) this.res.render = (pageName, opts) => { opts.isRestrictedTokenMember.should.exist opts.isRestrictedTokenMember.should.equal(true) @@ -819,21 +817,6 @@ describe('ProjectController', function() { return this.ProjectController.loadEditor(this.req, this.res) }) - it('should set isRestrictedTokenMember to true when anonymous read-only token access', function(done) { - this.CollaboratorsGetter.userIsTokenMember.callsArgWith(2, null, null) - this.AuthenticationController.isUserLoggedIn = sinon.stub().returns(false) - this.AuthorizationManager.getPrivilegeLevelForProject.callsArgWith( - 3, - null, - 'readOnly' - ) - this.res.render = (pageName, opts) => { - opts.isRestrictedTokenMember.should.exist - opts.isRestrictedTokenMember.should.equal(true) - return done() - } - return this.ProjectController.loadEditor(this.req, this.res) - }) it('should render the closed page if the editor is closed', function(done) { this.settings.editorIsOpen = false this.res.render = (pageName, opts) => {