diff --git a/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee b/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee index e7e4938f79..e2068bdb21 100644 --- a/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee +++ b/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee @@ -1,6 +1,4 @@ -ProjectGetter = require '../Project/ProjectGetter' Project = require('../../models/Project').Project -AuthenticationController = require '../Authentication/AuthenticationController' PublicAccessLevels = require '../Authorization/PublicAccessLevels' ObjectId = require("mongojs").ObjectId diff --git a/services/web/test/UnitTests/coffee/TokenAccess/TokenAccessHandlerTests.coffee b/services/web/test/UnitTests/coffee/TokenAccess/TokenAccessHandlerTests.coffee new file mode 100644 index 0000000000..87bbc69ef0 --- /dev/null +++ b/services/web/test/UnitTests/coffee/TokenAccess/TokenAccessHandlerTests.coffee @@ -0,0 +1,252 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../../app/js/Features/TokenAccess/TokenAccessHandler" +expect = require("chai").expect +ObjectId = require("mongojs").ObjectId + +describe "TokenAccessHandler", -> + + beforeEach -> + @token = 'sometokenthing' + @projectId = ObjectId() + @project = + _id: @projectId + publicAccesLevel: 'tokenBased' + @userId = ObjectId() + @req = {} + @TokenAccessHandler = SandboxedModule.require modulePath, requires: + '../../models/Project': {Project: @Project = {}} + + + describe 'findProjectWithReadOnlyToken', -> + beforeEach -> + @Project.findOne = sinon.stub().callsArgWith(2, null, @project) + + it 'should call Project.findOne', (done) -> + @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) => + expect(@Project.findOne.callCount).to.equal 1 + expect(@Project.findOne.calledWith({ + 'tokens.readOnly': @token, + 'publicAccesLevel': 'tokenBased' + })).to.equal true + done() + + it 'should produce a project object with no error', (done) -> + @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) => + expect(err).to.not.exist + expect(project).to.exist + expect(project).to.deep.equal @project + done() + + describe 'when Project.findOne produces an error', -> + beforeEach -> + @Project.findOne = sinon.stub().callsArgWith(2, new Error('woops')) + + it 'should produce an error', (done) -> + @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) => + expect(err).to.exist + expect(project).to.not.exist + expect(err).to.be.instanceof Error + done() + + describe 'findProjectWithReadAndWriteToken', -> + beforeEach -> + @Project.findOne = sinon.stub().callsArgWith(2, null, @project) + + it 'should call Project.findOne', (done) -> + @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project) => + expect(@Project.findOne.callCount).to.equal 1 + expect(@Project.findOne.calledWith({ + 'tokens.readAndWrite': @token, + 'publicAccesLevel': 'tokenBased' + })).to.equal true + done() + + it 'should produce a project object with no error', (done) -> + @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project) => + expect(err).to.not.exist + expect(project).to.exist + expect(project).to.deep.equal @project + done() + + describe 'when Project.findOne produces an error', -> + beforeEach -> + @Project.findOne = sinon.stub().callsArgWith(2, new Error('woops')) + + it 'should produce an error', (done) -> + @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project) => + expect(err).to.exist + expect(project).to.not.exist + expect(err).to.be.instanceof Error + done() + + + describe 'addReadOnlyUserToProject', -> + beforeEach -> + @Project.update = sinon.stub().callsArgWith(2, null) + + it 'should call Project.update', (done) -> + @TokenAccessHandler.addReadOnlyUserToProject @userId, @projectId, (err) => + expect(@Project.update.callCount).to.equal 1 + expect(@Project.update.calledWith({ + _id: @projectId + })).to.equal true + expect(@Project.update.lastCall.args[1]['$addToSet']) + .to.have.keys 'tokenAccessReadOnly_refs' + done() + + it 'should not produce an error', (done) -> + @TokenAccessHandler.addReadOnlyUserToProject @userId, @projectId, (err) => + expect(err).to.not.exist + done() + + describe 'when Project.update produces an error', -> + beforeEach -> + @Project.update = sinon.stub().callsArgWith(2, new Error('woops')) + + it 'should produce an error', (done) -> + @TokenAccessHandler.addReadOnlyUserToProject @userId, @projectId, (err) => + expect(err).to.exist + done() + + + describe 'addReadAndWriteUserToProject', -> + beforeEach -> + @Project.update = sinon.stub().callsArgWith(2, null) + + it 'should call Project.update', (done) -> + @TokenAccessHandler.addReadAndWriteUserToProject @userId, @projectId, (err) => + expect(@Project.update.callCount).to.equal 1 + expect(@Project.update.calledWith({ + _id: @projectId + })).to.equal true + expect(@Project.update.lastCall.args[1]['$addToSet']) + .to.have.keys 'tokenAccessReadAndWrite_refs' + done() + + it 'should not produce an error', (done) -> + @TokenAccessHandler.addReadAndWriteUserToProject @userId, @projectId, (err) => + expect(err).to.not.exist + done() + + describe 'when Project.update produces an error', -> + beforeEach -> + @Project.update = sinon.stub().callsArgWith(2, new Error('woops')) + + it 'should produce an error', (done) -> + @TokenAccessHandler.addReadAndWriteUserToProject @userId, @projectId, (err) => + expect(err).to.exist + done() + + + describe 'grantSessionReadOnlyTokenAccess', -> + beforeEach -> + @req = {session: {}, headers: {}} + + it 'should add the token to the session', (done) -> + @TokenAccessHandler.grantSessionReadOnlyTokenAccess(@req, @projectId, @token) + expect(@req.session.anonReadOnlyTokenAccess[@projectId.toString()]) + .to.equal @token + done() + + + describe 'requestHasReadOnlyTokenAccess', -> + beforeEach -> + @req = {session: {}, headers: {}} + @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub() + .callsArgWith(1, null, @project) + + describe 'with header', -> + beforeEach -> + @req.headers['x-sl-anon-token'] = @token + + it 'should call findProjectWithReadOnlyToken', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount) + .to.equal 1 + done() + + it 'should allow access', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(err).to.not.exist + expect(allowed).to.equal true + done() + + describe 'with session', -> + beforeEach -> + @req.session.anonReadOnlyTokenAccess = {} + @req.session.anonReadOnlyTokenAccess[@projectId.toString()] = @token + + describe 'when no project is found', -> + beforeEach -> + @req.headers['x-sl-anon-token'] = @token + @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub() + .callsArgWith(1, null, null) + + it 'should call findProjectWithReadOnlyToken', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount) + .to.equal 1 + done() + + it 'should not allow access', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(err).to.not.exist + expect(allowed).to.equal false + done() + + describe 'when no findProject produces an error', -> + beforeEach -> + @req.headers['x-sl-anon-token'] = @token + @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub() + .callsArgWith(1, new Error('woops')) + + it 'should call findProjectWithReadOnlyToken', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount) + .to.equal 1 + done() + + it 'should produce an error and not allow access', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(err).to.exist + expect(err).to.be.instanceof Error + expect(allowed).to.equal undefined + done() + + describe 'when project is not set to token-based access', -> + beforeEach -> + @req.headers['x-sl-anon-token'] = @token + @project.publicAccesLevel = 'private' + @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub() + .callsArgWith(1, null, @project) + + it 'should call findProjectWithReadOnlyToken', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount) + .to.equal 1 + done() + + it 'should not allow access', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(err).to.not.exist + expect(allowed).to.equal false + done() + + describe 'with nothing', -> + beforeEach -> + + it 'should not call findProjectWithReadOnlyToken', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount) + .to.equal 0 + done() + + it 'should not allow access', (done) -> + @TokenAccessHandler.requestHasReadOnlyTokenAccess @req, @projectId, (err, allowed) => + expect(err).to.not.exist + expect(allowed).to.equal false + done()