|
|
@@ -0,0 +1,180 @@
|
|
|
+const SandboxedModule = require('sandboxed-module')
|
|
|
+const sinon = require('sinon')
|
|
|
+const { expect } = require('chai')
|
|
|
+const { ObjectId } = require('mongodb')
|
|
|
+const MockRequest = require('../helpers/MockRequest')
|
|
|
+const MockResponse = require('../helpers/MockResponse')
|
|
|
+const PrivilegeLevels = require('../../../../app/src/Features/Authorization/PrivilegeLevels')
|
|
|
+
|
|
|
+const MODULE_PATH =
|
|
|
+ '../../../../app/src/Features/TokenAccess/TokenAccessController'
|
|
|
+
|
|
|
+describe('TokenAccessController', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ this.user = { _id: ObjectId() }
|
|
|
+ this.project = {
|
|
|
+ _id: ObjectId(),
|
|
|
+ tokenAccessReadAndWrite_refs: [],
|
|
|
+ tokenAccessReadOnly_refs: [],
|
|
|
+ }
|
|
|
+ this.req = new MockRequest()
|
|
|
+ this.res = new MockResponse()
|
|
|
+
|
|
|
+ this.Settings = {}
|
|
|
+ this.TokenAccessHandler = {
|
|
|
+ TOKEN_TYPES: {
|
|
|
+ READ_ONLY: 'readOnly',
|
|
|
+ READ_AND_WRITE: 'readAndWrite',
|
|
|
+ },
|
|
|
+ isReadAndWriteToken: sinon.stub().returns(true),
|
|
|
+ isReadOnlyToken: sinon.stub().returns(true),
|
|
|
+ tokenAccessEnabledForProject: sinon.stub().returns(true),
|
|
|
+ promises: {
|
|
|
+ addReadAndWriteUserToProject: sinon.stub().resolves(),
|
|
|
+ addReadOnlyUserToProject: sinon.stub().resolves(),
|
|
|
+ getProjectByToken: sinon.stub().resolves(this.project),
|
|
|
+ getV1DocPublishedInfo: sinon.stub().resolves({ allow: true }),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ this.SessionManager = {
|
|
|
+ getLoggedInUserId: sinon.stub().returns(this.user._id),
|
|
|
+ }
|
|
|
+
|
|
|
+ this.AuthenticationController = {}
|
|
|
+
|
|
|
+ this.AuthorizationManager = {
|
|
|
+ promises: {
|
|
|
+ getPrivilegeLevelForProject: sinon
|
|
|
+ .stub()
|
|
|
+ .resolves(PrivilegeLevels.NONE),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ this.AuthorizationMiddleware = {}
|
|
|
+
|
|
|
+ this.ProjectAuditLogHandler = {
|
|
|
+ promises: {
|
|
|
+ addEntry: sinon.stub().resolves(),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ this.TokenAccessController = SandboxedModule.require(MODULE_PATH, {
|
|
|
+ requires: {
|
|
|
+ '@overleaf/settings': this.Settings,
|
|
|
+ './TokenAccessHandler': this.TokenAccessHandler,
|
|
|
+ '../Authentication/AuthenticationController':
|
|
|
+ this.AuthenticationController,
|
|
|
+ '../Authentication/SessionManager': this.SessionManager,
|
|
|
+ '../Authorization/AuthorizationManager': this.AuthorizationManager,
|
|
|
+ '../Authorization/AuthorizationMiddleware':
|
|
|
+ this.AuthorizationMiddleware,
|
|
|
+ '../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('grantTokenAccessReadAndWrite', function () {
|
|
|
+ describe('normal case', function () {
|
|
|
+ beforeEach(function (done) {
|
|
|
+ this.req.params = { token: this.token }
|
|
|
+ this.req.body = { confirmedByUser: true }
|
|
|
+ this.res.callback = done
|
|
|
+ this.TokenAccessController.grantTokenAccessReadAndWrite(
|
|
|
+ this.req,
|
|
|
+ this.res,
|
|
|
+ done
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ it('grants read and write access', function () {
|
|
|
+ expect(
|
|
|
+ this.TokenAccessHandler.promises.addReadAndWriteUserToProject
|
|
|
+ ).to.have.been.calledWith(this.user._id, this.project._id)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('writes a project audit log', function () {
|
|
|
+ expect(
|
|
|
+ this.ProjectAuditLogHandler.promises.addEntry
|
|
|
+ ).to.have.been.calledWith(
|
|
|
+ this.project._id,
|
|
|
+ 'join-via-token',
|
|
|
+ this.user._id,
|
|
|
+ this.req.ip,
|
|
|
+ { privileges: 'readAndWrite' }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the access was already granted', function () {
|
|
|
+ beforeEach(function (done) {
|
|
|
+ this.project.tokenAccessReadAndWrite_refs.push(this.user._id)
|
|
|
+ this.req.params = { token: this.token }
|
|
|
+ this.req.body = { confirmedByUser: true }
|
|
|
+ this.res.callback = done
|
|
|
+ this.TokenAccessController.grantTokenAccessReadAndWrite(
|
|
|
+ this.req,
|
|
|
+ this.res,
|
|
|
+ done
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ it("doesn't write a project audit log", function () {
|
|
|
+ expect(this.ProjectAuditLogHandler.promises.addEntry).to.not.have.been
|
|
|
+ .called
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('grantTokenAccessReadOnly', function () {
|
|
|
+ describe('normal case', function () {
|
|
|
+ beforeEach(function (done) {
|
|
|
+ this.req.params = { token: this.token }
|
|
|
+ this.req.body = { confirmedByUser: true }
|
|
|
+ this.res.callback = done
|
|
|
+ this.TokenAccessController.grantTokenAccessReadOnly(
|
|
|
+ this.req,
|
|
|
+ this.res,
|
|
|
+ done
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ it('grants read-only access', function () {
|
|
|
+ expect(
|
|
|
+ this.TokenAccessHandler.promises.addReadOnlyUserToProject
|
|
|
+ ).to.have.been.calledWith(this.user._id, this.project._id)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('writes a project audit log', function () {
|
|
|
+ expect(
|
|
|
+ this.ProjectAuditLogHandler.promises.addEntry
|
|
|
+ ).to.have.been.calledWith(
|
|
|
+ this.project._id,
|
|
|
+ 'join-via-token',
|
|
|
+ this.user._id,
|
|
|
+ this.req.ip,
|
|
|
+ { privileges: 'readOnly' }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the access was already granted', function () {
|
|
|
+ beforeEach(function (done) {
|
|
|
+ this.project.tokenAccessReadOnly_refs.push(this.user._id)
|
|
|
+ this.req.params = { token: this.token }
|
|
|
+ this.req.body = { confirmedByUser: true }
|
|
|
+ this.res.callback = done
|
|
|
+ this.TokenAccessController.grantTokenAccessReadOnly(
|
|
|
+ this.req,
|
|
|
+ this.res,
|
|
|
+ done
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ it("doesn't write a project audit log", function () {
|
|
|
+ expect(this.ProjectAuditLogHandler.promises.addEntry).to.not.have.been
|
|
|
+ .called
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|