| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685 |
- /* eslint-disable
- node/handle-callback-err,
- 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 SandboxedModule = require('sandboxed-module')
- const path = require('path')
- const sinon = require('sinon')
- const modulePath = path.join(
- __dirname,
- '../../../../app/src/Features/TokenAccess/TokenAccessHandler'
- )
- const { expect } = require('chai')
- const { ObjectId } = require('mongodb')
- describe('TokenAccessHandler', function () {
- beforeEach(function () {
- this.token = 'abcdefabcdef'
- this.projectId = ObjectId()
- this.project = {
- _id: this.projectId,
- publicAccesLevel: 'tokenBased',
- owner_ref: ObjectId(),
- }
- this.userId = ObjectId()
- this.req = {}
- return (this.TokenAccessHandler = SandboxedModule.require(modulePath, {
- requires: {
- mongodb: { ObjectId },
- '../../models/Project': { Project: (this.Project = {}) },
- '@overleaf/settings': (this.settings = {}),
- '../V1/V1Api': (this.V1Api = {
- request: sinon.stub(),
- }),
- crypto: (this.Crypto = require('crypto')),
- '../Analytics/AnalyticsManager': (this.Analytics = {
- recordEventForUser: sinon.stub(),
- }),
- },
- }))
- })
- describe('getTokenType', function () {
- it('should determine tokens correctly', function () {
- const specs = {
- abcdefabcdef: 'readOnly',
- aaaaaabbbbbb: 'readOnly',
- '54325aaaaaa': 'readAndWrite',
- '54325aaaaaabbbbbb': 'readAndWrite',
- '': null,
- abc123def: null,
- }
- for (const token of Object.keys(specs)) {
- expect(this.TokenAccessHandler.getTokenType(token)).to.equal(
- specs[token]
- )
- }
- })
- })
- describe('getProjectByReadOnlyToken', function () {
- beforeEach(function () {
- this.token = 'abcdefabcdef'
- this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
- })
- it('should get the project', function (done) {
- this.TokenAccessHandler.getProjectByReadOnlyToken(
- this.token,
- (err, project) => {
- expect(err).to.not.exist
- expect(project).to.exist
- expect(this.Project.findOne.callCount).to.equal(1)
- done()
- }
- )
- })
- })
- describe('getProjectByReadAndWriteToken', function () {
- beforeEach(function () {
- sinon.spy(this.Crypto, 'timingSafeEqual')
- this.token = '1234abcdefabcdef'
- this.project.tokens = {
- readAndWrite: this.token,
- readAndWritePrefix: '1234',
- }
- this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
- })
- afterEach(function () {
- this.Crypto.timingSafeEqual.restore()
- })
- it('should get the project and do timing-safe comparison', function (done) {
- this.TokenAccessHandler.getProjectByReadAndWriteToken(
- this.token,
- (err, project) => {
- expect(err).to.not.exist
- expect(project).to.exist
- expect(this.Crypto.timingSafeEqual.callCount).to.equal(1)
- expect(
- this.Crypto.timingSafeEqual.calledWith(Buffer.from(this.token))
- ).to.equal(true)
- expect(this.Project.findOne.callCount).to.equal(1)
- done()
- }
- )
- })
- })
- describe('addReadOnlyUserToProject', function () {
- beforeEach(function () {
- return (this.Project.updateOne = sinon.stub().callsArgWith(2, null))
- })
- it('should call Project.updateOne', function (done) {
- return this.TokenAccessHandler.addReadOnlyUserToProject(
- this.userId,
- this.projectId,
- err => {
- expect(this.Project.updateOne.callCount).to.equal(1)
- expect(
- this.Project.updateOne.calledWith({
- _id: this.projectId,
- })
- ).to.equal(true)
- expect(
- this.Project.updateOne.lastCall.args[1].$addToSet
- ).to.have.keys('tokenAccessReadOnly_refs')
- sinon.assert.calledWith(
- this.Analytics.recordEventForUser,
- this.userId,
- 'project-joined',
- { mode: 'read-only' }
- )
- return done()
- }
- )
- })
- it('should not produce an error', function (done) {
- return this.TokenAccessHandler.addReadOnlyUserToProject(
- this.userId,
- this.projectId,
- err => {
- expect(err).to.not.exist
- return done()
- }
- )
- })
- describe('when Project.updateOne produces an error', function () {
- beforeEach(function () {
- return (this.Project.updateOne = sinon
- .stub()
- .callsArgWith(2, new Error('woops')))
- })
- it('should produce an error', function (done) {
- return this.TokenAccessHandler.addReadOnlyUserToProject(
- this.userId,
- this.projectId,
- err => {
- expect(err).to.exist
- return done()
- }
- )
- })
- })
- })
- describe('addReadAndWriteUserToProject', function () {
- beforeEach(function () {
- return (this.Project.updateOne = sinon.stub().callsArgWith(2, null))
- })
- it('should call Project.updateOne', function (done) {
- return this.TokenAccessHandler.addReadAndWriteUserToProject(
- this.userId,
- this.projectId,
- err => {
- expect(this.Project.updateOne.callCount).to.equal(1)
- expect(
- this.Project.updateOne.calledWith({
- _id: this.projectId,
- })
- ).to.equal(true)
- expect(
- this.Project.updateOne.lastCall.args[1].$addToSet
- ).to.have.keys('tokenAccessReadAndWrite_refs')
- sinon.assert.calledWith(
- this.Analytics.recordEventForUser,
- this.userId,
- 'project-joined',
- { mode: 'read-write' }
- )
- return done()
- }
- )
- })
- it('should not produce an error', function (done) {
- return this.TokenAccessHandler.addReadAndWriteUserToProject(
- this.userId,
- this.projectId,
- err => {
- expect(err).to.not.exist
- return done()
- }
- )
- })
- describe('when Project.updateOne produces an error', function () {
- beforeEach(function () {
- return (this.Project.updateOne = sinon
- .stub()
- .callsArgWith(2, new Error('woops')))
- })
- it('should produce an error', function (done) {
- return this.TokenAccessHandler.addReadAndWriteUserToProject(
- this.userId,
- this.projectId,
- err => {
- expect(err).to.exist
- return done()
- }
- )
- })
- })
- })
- describe('grantSessionTokenAccess', function () {
- beforeEach(function () {
- return (this.req = { session: {}, headers: {} })
- })
- it('should add the token to the session', function (done) {
- this.TokenAccessHandler.grantSessionTokenAccess(
- this.req,
- this.projectId,
- this.token
- )
- expect(
- this.req.session.anonTokenAccess[this.projectId.toString()]
- ).to.equal(this.token)
- return done()
- })
- })
- describe('validateTokenForAnonymousAccess', function () {
- describe('when a read-only project is found', function () {
- beforeEach(function () {
- this.TokenAccessHandler.getTokenType = sinon.stub().returns('readOnly')
- this.TokenAccessHandler.getProjectByToken = sinon
- .stub()
- .callsArgWith(2, null, this.project)
- })
- it('should try to find projects with both kinds of token', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, allowed) => {
- expect(
- this.TokenAccessHandler.getProjectByToken.callCount
- ).to.equal(1)
- return done()
- }
- )
- })
- it('should allow read-only access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(err).to.not.exist
- expect(rw).to.equal(false)
- expect(ro).to.equal(true)
- return done()
- }
- )
- })
- })
- describe('when a read-and-write project is found', function () {
- beforeEach(function () {
- this.TokenAccessHandler.getTokenType = sinon
- .stub()
- .returns('readAndWrite')
- this.TokenAccessHandler.getProjectByToken = sinon
- .stub()
- .callsArgWith(2, null, this.project)
- })
- describe('when Anonymous token access is not enabled', function (done) {
- beforeEach(function () {
- this.TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = false
- })
- it('should try to find projects with both kinds of token', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(
- this.TokenAccessHandler.getProjectByToken.callCount
- ).to.equal(1)
- return done()
- }
- )
- })
- it('should not allow read-and-write access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(err).to.not.exist
- expect(rw).to.equal(false)
- expect(ro).to.equal(false)
- return done()
- }
- )
- })
- })
- describe('when anonymous token access is enabled', function (done) {
- beforeEach(function () {
- this.TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = true
- })
- it('should try to find projects with both kinds of token', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(
- this.TokenAccessHandler.getProjectByToken.callCount
- ).to.equal(1)
- return done()
- }
- )
- })
- it('should allow read-and-write access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(err).to.not.exist
- expect(rw).to.equal(true)
- expect(ro).to.equal(false)
- return done()
- }
- )
- })
- })
- })
- describe('when no project is found', function () {
- beforeEach(function () {
- this.TokenAccessHandler.getProjectByToken = sinon
- .stub()
- .callsArgWith(2, null, null, null)
- })
- it('should try to find projects with both kinds of token', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, allowed) => {
- expect(
- this.TokenAccessHandler.getProjectByToken.callCount
- ).to.equal(1)
- return done()
- }
- )
- })
- it('should not allow any access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(err).to.not.exist
- expect(rw).to.equal(false)
- expect(ro).to.equal(false)
- return done()
- }
- )
- })
- })
- describe('when findProject produces an error', function () {
- beforeEach(function () {
- this.TokenAccessHandler.getProjectByToken = sinon
- .stub()
- .callsArgWith(2, new Error('woops'))
- })
- it('should try to find projects with both kinds of token', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, allowed) => {
- expect(
- this.TokenAccessHandler.getProjectByToken.callCount
- ).to.equal(1)
- return done()
- }
- )
- })
- it('should produce an error and not allow access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(err).to.exist
- expect(err).to.be.instanceof(Error)
- expect(rw).to.equal(undefined)
- expect(ro).to.equal(undefined)
- return done()
- }
- )
- })
- })
- describe('when project is not set to token-based access', function () {
- beforeEach(function () {
- return (this.project.publicAccesLevel = 'private')
- })
- describe('for read-and-write project', function () {
- beforeEach(function () {
- this.TokenAccessHandler.getTokenType = sinon
- .stub()
- .returns('readAndWrite')
- this.TokenAccessHandler.getProjectByToken = sinon
- .stub()
- .callsArgWith(2, null, this.project)
- })
- it('should not allow any access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(err).to.not.exist
- expect(rw).to.equal(false)
- expect(ro).to.equal(false)
- return done()
- }
- )
- })
- })
- describe('for read-only project', function () {
- beforeEach(function () {
- this.TokenAccessHandler.getTokenType = sinon
- .stub()
- .returns('readOnly')
- this.TokenAccessHandler.getProjectByToken = sinon
- .stub()
- .callsArgWith(2, null, this.project)
- })
- it('should not allow any access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- this.token,
- (err, rw, ro) => {
- expect(err).to.not.exist
- expect(rw).to.equal(false)
- expect(ro).to.equal(false)
- return done()
- }
- )
- })
- })
- describe('with nothing', function () {
- beforeEach(function () {
- this.TokenAccessHandler.getProjectByToken = sinon
- .stub()
- .callsArgWith(1, null, null, null)
- })
- it('should not allow any access', function (done) {
- return this.TokenAccessHandler.validateTokenForAnonymousAccess(
- this.projectId,
- null,
- (err, rw, ro) => {
- expect(err).to.not.exist
- expect(rw).to.equal(false)
- expect(ro).to.equal(false)
- return done()
- }
- )
- })
- })
- })
- })
- describe('protectTokens', function () {
- beforeEach(function () {
- return (this.project = {
- tokens: {
- readAndWrite: 'rw',
- readOnly: 'ro',
- readAndWritePrefix: 'pre',
- },
- })
- })
- it('should hide write token from read-only user', function () {
- this.TokenAccessHandler.protectTokens(this.project, 'readOnly')
- expect(this.project.tokens.readAndWrite).to.equal('')
- expect(this.project.tokens.readAndWritePrefix).to.equal('')
- return expect(this.project.tokens.readOnly).to.equal('ro')
- })
- it('should hide read token from read-write user', function () {
- this.TokenAccessHandler.protectTokens(this.project, 'readAndWrite')
- expect(this.project.tokens.readAndWrite).to.equal('rw')
- return expect(this.project.tokens.readOnly).to.equal('')
- })
- it('should leave tokens in place for owner', function () {
- this.TokenAccessHandler.protectTokens(this.project, 'owner')
- expect(this.project.tokens.readAndWrite).to.equal('rw')
- return expect(this.project.tokens.readOnly).to.equal('ro')
- })
- })
- describe('getDocPublishedInfo', function () {
- beforeEach(function () {
- return (this.callback = sinon.stub())
- })
- describe('when v1 api not set', function () {
- beforeEach(function () {
- this.settings.apis = { v1: undefined }
- return this.TokenAccessHandler.getV1DocPublishedInfo(
- this.token,
- this.callback
- )
- })
- it('should not check access and return default info', function () {
- expect(this.V1Api.request.called).to.equal(false)
- return expect(
- this.callback.calledWith(null, {
- allow: true,
- })
- ).to.equal(true)
- })
- })
- describe('when v1 api is set', function () {
- beforeEach(function () {
- return (this.settings.apis = { v1: { url: 'v1Url' } })
- })
- describe('on V1Api.request success', function () {
- beforeEach(function () {
- this.V1Api.request = sinon
- .stub()
- .callsArgWith(1, null, null, 'mock-data')
- return this.TokenAccessHandler.getV1DocPublishedInfo(
- this.token,
- this.callback
- )
- })
- it('should return response body', function () {
- expect(
- this.V1Api.request.calledWith({
- url: `/api/v1/sharelatex/docs/${this.token}/is_published`,
- })
- ).to.equal(true)
- return expect(this.callback.calledWith(null, 'mock-data')).to.equal(
- true
- )
- })
- })
- describe('on V1Api.request error', function () {
- beforeEach(function () {
- this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
- return this.TokenAccessHandler.getV1DocPublishedInfo(
- this.token,
- this.callback
- )
- })
- it('should callback with error', function () {
- return expect(this.callback.calledWith('error')).to.equal(true)
- })
- })
- })
- })
- describe('getV1DocInfo', function () {
- beforeEach(function () {
- return (this.callback = sinon.stub())
- })
- describe('when v1 api not set', function () {
- beforeEach(function () {
- return this.TokenAccessHandler.getV1DocInfo(
- this.token,
- this.v2UserId,
- this.callback
- )
- })
- it('should not check access and return default info', function () {
- expect(this.V1Api.request.called).to.equal(false)
- return expect(
- this.callback.calledWith(null, {
- exists: true,
- exported: false,
- })
- ).to.equal(true)
- })
- })
- describe('when v1 api is set', function () {
- beforeEach(function () {
- return (this.settings.apis = { v1: 'v1' })
- })
- describe('on V1Api.request success', function () {
- beforeEach(function () {
- this.V1Api.request = sinon
- .stub()
- .callsArgWith(1, null, null, 'mock-data')
- return this.TokenAccessHandler.getV1DocInfo(
- this.token,
- this.v2UserId,
- this.callback
- )
- })
- it('should return response body', function () {
- expect(
- this.V1Api.request.calledWith({
- url: `/api/v1/sharelatex/docs/${this.token}/info`,
- })
- ).to.equal(true)
- return expect(this.callback.calledWith(null, 'mock-data')).to.equal(
- true
- )
- })
- })
- describe('on V1Api.request error', function () {
- beforeEach(function () {
- this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
- return this.TokenAccessHandler.getV1DocInfo(
- this.token,
- this.v2UserId,
- this.callback
- )
- })
- it('should callback with error', function () {
- return expect(this.callback.calledWith('error')).to.equal(true)
- })
- })
- })
- })
- })
|