Merge pull request #17380 from overleaf/dp-mongoose-callback-toke-access-handler

Promisify TokenAccessHandler and TokenAccessHandlerTests

GitOrigin-RevId: 835081e78977456a59b7e16043fd6dcbdbce3ade
This commit is contained in:
David 2024-03-06 11:18:29 +00:00 committed by Copybot
parent 887a404fdd
commit 43007539a0
2 changed files with 392 additions and 476 deletions

View file

@ -7,7 +7,7 @@ const Settings = require('@overleaf/settings')
const logger = require('@overleaf/logger')
const V1Api = require('../V1/V1Api')
const crypto = require('crypto')
const { promisifyAll } = require('@overleaf/promise-utils')
const { callbackifyAll } = require('@overleaf/promise-utils')
const Analytics = require('../Analytics/AnalyticsManager')
const READ_AND_WRITE_TOKEN_PATTERN = '([0-9]+[a-z]{6,12})'
@ -23,27 +23,22 @@ const TokenAccessHandler = {
Settings.allowAnonymousReadAndWriteSharing === true,
READ_AND_WRITE_TOKEN_PATTERN,
READ_AND_WRITE_TOKEN_REGEX: new RegExp(`^${READ_AND_WRITE_TOKEN_PATTERN}$`),
READ_AND_WRITE_URL_REGEX: new RegExp(`^/${READ_AND_WRITE_TOKEN_PATTERN}$`),
READ_ONLY_TOKEN_PATTERN,
READ_ONLY_TOKEN_REGEX: new RegExp(`^${READ_ONLY_TOKEN_PATTERN}$`),
READ_ONLY_URL_REGEX: new RegExp(`^/read/${READ_ONLY_TOKEN_PATTERN}$`),
makeReadAndWriteTokenUrl(token) {
_makeReadAndWriteTokenUrl(token) {
return `/${token}`
},
makeReadOnlyTokenUrl(token) {
_makeReadOnlyTokenUrl(token) {
return `/read/${token}`
},
makeTokenUrl(token) {
const tokenType = TokenAccessHandler.getTokenType(token)
if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE) {
return TokenAccessHandler.makeReadAndWriteTokenUrl(token)
return TokenAccessHandler._makeReadAndWriteTokenUrl(token)
} else if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_ONLY) {
return TokenAccessHandler.makeReadOnlyTokenUrl(token)
return TokenAccessHandler._makeReadOnlyTokenUrl(token)
} else {
throw new Error('invalid token type')
}
@ -85,24 +80,22 @@ const TokenAccessHandler = {
return project.publicAccesLevel === PublicAccessLevels.TOKEN_BASED
},
_projectFindOne(query, callback) {
Project.findOne(
query,
{
_id: 1,
tokens: 1,
publicAccesLevel: 1,
owner_ref: 1,
name: 1,
tokenAccessReadOnly_refs: 1,
tokenAccessReadAndWrite_refs: 1,
},
callback
)
async _projectFindOne(query) {
return await Project.findOne(query, {
_id: 1,
tokens: 1,
publicAccesLevel: 1,
owner_ref: 1,
name: 1,
tokenAccessReadOnly_refs: 1,
tokenAccessReadAndWrite_refs: 1,
}).exec()
},
getProjectByReadOnlyToken(token, callback) {
TokenAccessHandler._projectFindOne({ 'tokens.readOnly': token }, callback)
async getProjectByReadOnlyToken(token) {
return await TokenAccessHandler._projectFindOne({
'tokens.readOnly': token,
})
},
_extractNumericPrefix(token) {
@ -113,92 +106,83 @@ const TokenAccessHandler = {
return token.match(/^\d+(\w+)/)
},
getProjectByReadAndWriteToken(token, callback) {
async getProjectByReadAndWriteToken(token) {
const numericPrefixMatch = TokenAccessHandler._extractNumericPrefix(token)
if (!numericPrefixMatch) {
return callback(null, null)
return null
}
const numerics = numericPrefixMatch[1]
TokenAccessHandler._projectFindOne(
{
'tokens.readAndWritePrefix': numerics,
},
function (err, project) {
if (err != null) {
return callback(err)
}
if (project == null) {
return callback(null, null)
}
try {
if (
!crypto.timingSafeEqual(
Buffer.from(token),
Buffer.from(project.tokens.readAndWrite)
)
) {
logger.err(
{ projectId: project._id },
'read-and-write token match on numeric section, but not on full token'
)
callback(null, null)
} else {
callback(null, project)
}
} catch (error) {
err = error
logger.err(
{ projectId: project._id, cryptoErr: err },
'error comparing tokens'
)
callback(null, null)
}
}
)
},
getProjectByToken(tokenType, token, callback) {
if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_ONLY) {
TokenAccessHandler.getProjectByReadOnlyToken(token, callback)
} else if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE) {
TokenAccessHandler.getProjectByReadAndWriteToken(token, callback)
} else {
callback(new Error('invalid token type'))
const project = await TokenAccessHandler._projectFindOne({
'tokens.readAndWritePrefix': numerics,
})
if (project == null) {
return null
}
try {
if (
!crypto.timingSafeEqual(
Buffer.from(token),
Buffer.from(project.tokens.readAndWrite)
)
) {
logger.err(
{ projectId: project._id },
'read-and-write token match on numeric section, but not on full token'
)
return null
} else {
return project
}
} catch (error) {
logger.err({ projectId: project._id, error }, 'error comparing tokens')
return null
}
},
addReadOnlyUserToProject(userId, projectId, callback) {
async getProjectByToken(tokenType, token) {
if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_ONLY) {
return await TokenAccessHandler.getProjectByReadOnlyToken(token)
} else if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE) {
return await TokenAccessHandler.getProjectByReadAndWriteToken(token)
}
throw new Error('invalid token type')
},
async addReadOnlyUserToProject(userId, projectId) {
userId = new ObjectId(userId.toString())
projectId = new ObjectId(projectId.toString())
Analytics.recordEventForUser(userId, 'project-joined', {
mode: 'read-only',
})
Project.updateOne(
return await Project.updateOne(
{
_id: projectId,
},
{
$addToSet: { tokenAccessReadOnly_refs: userId },
},
callback
)
}
).exec()
},
addReadAndWriteUserToProject(userId, projectId, callback) {
async addReadAndWriteUserToProject(userId, projectId) {
userId = new ObjectId(userId.toString())
projectId = new ObjectId(projectId.toString())
Analytics.recordEventForUser(userId, 'project-joined', {
mode: 'read-write',
})
Project.updateOne(
return await Project.updateOne(
{
_id: projectId,
},
{
$addToSet: { tokenAccessReadAndWrite_refs: userId },
},
callback
)
}
).exec()
},
grantSessionTokenAccess(req, projectId, token) {
@ -219,65 +203,58 @@ const TokenAccessHandler = {
return token
},
validateTokenForAnonymousAccess(projectId, token, callback) {
async validateTokenForAnonymousAccess(projectId, token, callback) {
if (!token) {
return callback(null, false, false)
return { isValidReadAndWrite: false, isValidReadOnly: false }
}
const tokenType = TokenAccessHandler.getTokenType(token)
if (!tokenType) {
return callback(new Error('invalid token type'))
throw new Error('invalid token type')
}
TokenAccessHandler.getProjectByToken(tokenType, token, (err, project) => {
if (err) {
return callback(err)
}
if (
!project ||
!TokenAccessHandler.tokenAccessEnabledForProject(project) ||
project._id.toString() !== projectId.toString()
) {
return callback(null, false, false)
}
// TODO: think about cleaning up this interface and its usage in AuthorizationManager
callback(
null,
const project = await TokenAccessHandler.getProjectByToken(tokenType, token)
if (
!project ||
!TokenAccessHandler.tokenAccessEnabledForProject(project) ||
project._id.toString() !== projectId.toString()
) {
return { isValidReadAndWrite: false, isValidReadOnly: false }
}
// TODO: think about cleaning up this interface and its usage in AuthorizationManager
return {
isValidReadAndWrite:
tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE &&
TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED,
tokenType === TokenAccessHandler.TOKEN_TYPES.READ_ONLY
)
})
TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED,
isValidReadOnly: tokenType === TokenAccessHandler.TOKEN_TYPES.READ_ONLY,
}
},
getV1DocPublishedInfo(token, callback) {
async getV1DocPublishedInfo(token) {
// default to allowing access
if (!Settings.apis.v1 || !Settings.apis.v1.url) {
return callback(null, { allow: true })
return { allow: true }
}
V1Api.request(
{ url: `/api/v1/overleaf/docs/${token}/is_published` },
function (err, response, body) {
if (err != null) {
return callback(err)
}
callback(null, body)
}
)
const { body } = await V1Api.promises.request({
url: `/api/v1/overleaf/docs/${token}/is_published`,
})
return body
},
getV1DocInfo(token, v2UserId, callback) {
async getV1DocInfo(token, v2UserId) {
if (!Settings.apis || !Settings.apis.v1) {
return callback(null, {
return {
exists: true,
exported: false,
})
}
const v1Url = `/api/v1/overleaf/docs/${token}/info`
V1Api.request({ url: v1Url }, function (err, response, body) {
if (err != null) {
return callback(err)
}
callback(null, body)
})
}
const v1Url = `/api/v1/overleaf/docs/${token}/info`
const { body } = await V1Api.promises.request({ url: v1Url })
return body
},
createTokenHashPrefix(token) {
@ -337,19 +314,33 @@ const TokenAccessHandler = {
},
}
TokenAccessHandler.promises = promisifyAll(TokenAccessHandler, {
without: [
'getTokenType',
'tokenAccessEnabledForProject',
'_extractNumericPrefix',
'_extractStringSuffix',
'_projectFindOne',
'grantSessionTokenAccess',
'getRequestToken',
],
multiResult: {
validateTokenForAnonymousAccess: ['isValidReadAndWrite', 'isValidReadOnly'],
},
})
module.exports = TokenAccessHandler
module.exports = {
...TokenAccessHandler,
...callbackifyAll(TokenAccessHandler, {
multiResult: {
validateTokenForAnonymousAccess: [
'isValidReadAndWrite',
'isValidReadOnly',
],
},
without: [
'makeTokenUrl',
'getTokenType',
'isReadOnlyToken',
'isReadAndWriteToken',
'isValidToken',
'tokenAccessEnabledForProject',
'grantSessionTokenAccess',
'getRequestToken',
'createTokenHashPrefix',
'normalizeTokenHashPrefix',
'checkTokenHashPrefix',
'_makeReadAndWriteTokenUrl',
'_makeReadOnlyTokenUrl',
'_projectFindOne',
'_extractNumericPrefix',
'_extractStringSuffix',
],
}),
promises: TokenAccessHandler,
}

View file

@ -26,7 +26,9 @@ describe('TokenAccessHandler', function () {
'@overleaf/metrics': (this.Metrics = { inc: sinon.stub() }),
'@overleaf/settings': (this.settings = {}),
'../V1/V1Api': (this.V1Api = {
request: sinon.stub(),
promises: {
request: sinon.stub(),
},
}),
crypto: (this.Crypto = require('crypto')),
'../Analytics/AnalyticsManager': (this.Analytics = {
@ -57,19 +59,18 @@ describe('TokenAccessHandler', function () {
describe('getProjectByReadOnlyToken', function () {
beforeEach(function () {
this.token = 'abcdefabcdef'
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
this.Project.findOne = sinon.stub().returns({
exec: sinon.stub().resolves(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()
}
)
it('should get the project', async function () {
const project =
await this.TokenAccessHandler.promises.getProjectByReadOnlyToken(
this.token
)
expect(project).to.exist
expect(this.Project.findOne.callCount).to.equal(1)
})
})
@ -81,69 +82,55 @@ describe('TokenAccessHandler', function () {
readAndWrite: this.token,
readAndWritePrefix: '1234',
}
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
this.Project.findOne = sinon.stub().returns({
exec: sinon.stub().resolves(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()
}
)
it('should get the project and do timing-safe comparison', async function () {
const project =
await this.TokenAccessHandler.promises.getProjectByReadAndWriteToken(
this.token
)
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)
})
})
describe('addReadOnlyUserToProject', function () {
beforeEach(function () {
this.Project.updateOne = sinon.stub().callsArgWith(2, null)
this.Project.updateOne = sinon.stub().returns({
exec: sinon.stub().resolves(null),
})
})
it('should call Project.updateOne', function (done) {
this.TokenAccessHandler.addReadOnlyUserToProject(
it('should call Project.updateOne', async function () {
await this.TokenAccessHandler.promises.addReadOnlyUserToProject(
this.userId,
this.projectId,
err => {
expect(err).to.not.exist
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' }
)
done()
}
this.projectId
)
})
it('should not produce an error', function (done) {
this.TokenAccessHandler.addReadOnlyUserToProject(
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,
this.projectId,
err => {
expect(err).to.not.exist
done()
}
'project-joined',
{ mode: 'read-only' }
)
})
@ -151,61 +138,47 @@ describe('TokenAccessHandler', function () {
beforeEach(function () {
this.Project.updateOne = sinon
.stub()
.callsArgWith(2, new Error('woops'))
.returns({ exec: sinon.stub().rejects(new Error('woops')) })
})
it('should produce an error', function (done) {
this.TokenAccessHandler.addReadOnlyUserToProject(
this.userId,
this.projectId,
err => {
expect(err).to.exist
done()
}
)
it('should be rejected', async function () {
await expect(
this.TokenAccessHandler.promises.addReadOnlyUserToProject(
this.userId,
this.projectId
)
).to.be.rejected
})
})
})
describe('addReadAndWriteUserToProject', function () {
beforeEach(function () {
this.Project.updateOne = sinon.stub().callsArgWith(2, null)
this.Project.updateOne = sinon
.stub()
.returns({ exec: sinon.stub().resolves(null) })
})
it('should call Project.updateOne', function (done) {
this.TokenAccessHandler.addReadAndWriteUserToProject(
it('should call Project.updateOne', async function () {
await this.TokenAccessHandler.promises.addReadAndWriteUserToProject(
this.userId,
this.projectId,
err => {
expect(err).to.not.exist
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' }
)
done()
}
this.projectId
)
})
it('should not produce an error', function (done) {
this.TokenAccessHandler.addReadAndWriteUserToProject(
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,
this.projectId,
err => {
expect(err).to.not.exist
done()
}
'project-joined',
{ mode: 'read-write' }
)
})
@ -213,18 +186,16 @@ describe('TokenAccessHandler', function () {
beforeEach(function () {
this.Project.updateOne = sinon
.stub()
.callsArgWith(2, new Error('woops'))
.returns({ exec: sinon.stub().rejects(new Error('woops')) })
})
it('should produce an error', function (done) {
this.TokenAccessHandler.addReadAndWriteUserToProject(
this.userId,
this.projectId,
err => {
expect(err).to.exist
done()
}
)
it('should produce an error', async function () {
await expect(
this.TokenAccessHandler.promises.addReadAndWriteUserToProject(
this.userId,
this.projectId
)
).to.be.rejected
})
})
})
@ -234,8 +205,8 @@ describe('TokenAccessHandler', function () {
this.req = { session: {}, headers: {} }
})
it('should add the token to the session', function (done) {
this.TokenAccessHandler.grantSessionTokenAccess(
it('should add the token to the session', function () {
this.TokenAccessHandler.promises.grantSessionTokenAccess(
this.req,
this.projectId,
this.token
@ -243,7 +214,6 @@ describe('TokenAccessHandler', function () {
expect(
this.req.session.anonTokenAccess[this.projectId.toString()]
).to.equal(this.token)
done()
})
})
@ -251,185 +221,158 @@ describe('TokenAccessHandler', function () {
describe('when a read-only project is found', function () {
beforeEach(function () {
this.TokenAccessHandler.getTokenType = sinon.stub().returns('readOnly')
this.TokenAccessHandler.getProjectByToken = sinon
this.TokenAccessHandler.promises.getProjectByToken = sinon
.stub()
.callsArgWith(2, null, this.project)
.resolves(this.project)
})
it('should try to find projects with both kinds of token', function (done) {
this.TokenAccessHandler.validateTokenForAnonymousAccess(
it('should try to find projects with both kinds of token', async function () {
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token,
(err, allowed) => {
expect(err).to.not.exist
expect(
this.TokenAccessHandler.getProjectByToken.callCount
).to.equal(1)
done()
}
this.token
)
expect(
this.TokenAccessHandler.promises.getProjectByToken.callCount
).to.equal(1)
})
it('should allow read-only access', function (done) {
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)
done()
}
)
it('should allow read-only access', async function () {
const { isValidReadAndWrite, isValidReadOnly } =
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
expect(isValidReadAndWrite).to.equal(false)
expect(isValidReadOnly).to.equal(true)
})
})
describe('when a read-and-write project is found', function () {
beforeEach(function () {
this.TokenAccessHandler.getTokenType = sinon
this.TokenAccessHandler.promises.getTokenType = sinon
.stub()
.returns('readAndWrite')
this.TokenAccessHandler.getProjectByToken = sinon
this.TokenAccessHandler.promises.getProjectByToken = sinon
.stub()
.callsArgWith(2, null, this.project)
.resolves(this.project)
})
describe('when Anonymous token access is not enabled', function (done) {
describe('when Anonymous token access is not enabled', function () {
beforeEach(function () {
this.TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = false
})
it('should try to find projects with both kinds of token', function (done) {
this.TokenAccessHandler.validateTokenForAnonymousAccess(
it('should try to find projects with both kinds of token', async function () {
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token,
(err, rw, ro) => {
expect(err).to.not.exist
expect(
this.TokenAccessHandler.getProjectByToken.callCount
).to.equal(1)
done()
}
this.token
)
expect(
this.TokenAccessHandler.promises.getProjectByToken.callCount
).to.equal(1)
})
it('should not allow read-and-write access', function (done) {
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)
done()
}
)
it('should not allow read-and-write access', async function () {
const { isValidReadAndWrite, isValidReadOnly } =
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
expect(isValidReadAndWrite).to.equal(false)
expect(isValidReadOnly).to.equal(false)
})
})
describe('when anonymous token access is enabled', function (done) {
describe('when anonymous token access is enabled', function () {
beforeEach(function () {
this.TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = true
this.TokenAccessHandler.promises.ANONYMOUS_READ_AND_WRITE_ENABLED = true
})
it('should try to find projects with both kinds of token', function (done) {
this.TokenAccessHandler.validateTokenForAnonymousAccess(
it('should try to find projects with both kinds of token', async function () {
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token,
(err, rw, ro) => {
expect(err).to.not.exist
expect(
this.TokenAccessHandler.getProjectByToken.callCount
).to.equal(1)
done()
}
this.token
)
expect(
this.TokenAccessHandler.promises.getProjectByToken.callCount
).to.equal(1)
})
it('should allow read-and-write access', function (done) {
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)
done()
}
)
it('should allow read-and-write access', async function () {
const { isValidReadAndWrite, isValidReadOnly } =
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
expect(isValidReadAndWrite).to.equal(true)
expect(isValidReadOnly).to.equal(false)
})
})
})
describe('when no project is found', function () {
beforeEach(function () {
this.TokenAccessHandler.getProjectByToken = sinon
this.TokenAccessHandler.promises.getProjectByToken = sinon
.stub()
.callsArgWith(2, null, null, null)
.resolves(null)
})
it('should try to find projects with both kinds of token', function (done) {
this.TokenAccessHandler.validateTokenForAnonymousAccess(
it('should try to find projects with both kinds of token', async function () {
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token,
(err, allowed) => {
expect(err).to.not.exist
expect(
this.TokenAccessHandler.getProjectByToken.callCount
).to.equal(1)
done()
}
this.token
)
expect(
this.TokenAccessHandler.promises.getProjectByToken.callCount
).to.equal(1)
})
it('should not allow any access', function (done) {
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)
done()
}
)
it('should not allow any access', async function () {
const { isValidReadAndWrite, isValidReadOnly } =
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
expect(isValidReadAndWrite).to.equal(false)
expect(isValidReadOnly).to.equal(false)
})
})
describe('when findProject produces an error', function () {
beforeEach(function () {
this.TokenAccessHandler.getProjectByToken = sinon
this.TokenAccessHandler.promises.getProjectByToken = sinon
.stub()
.callsArgWith(2, new Error('woops'))
.rejects(new Error('woops'))
})
it('should try to find projects with both kinds of token', function (done) {
this.TokenAccessHandler.validateTokenForAnonymousAccess(
this.projectId,
this.token,
(err, allowed) => {
expect(err).to.exist
expect(allowed).to.not.exist
expect(
this.TokenAccessHandler.getProjectByToken.callCount
).to.equal(1)
done()
}
)
it('should try to find projects with both kinds of token', async function () {
await expect(
this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
).to.be.rejected
expect(
this.TokenAccessHandler.promises.getProjectByToken.callCount
).to.equal(1)
})
it('should produce an error and not allow access', function (done) {
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)
done()
}
)
it('should produce an error and not allow access', async function () {
await expect(
this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
).to.be.rejected
})
})
@ -443,22 +386,20 @@ describe('TokenAccessHandler', function () {
this.TokenAccessHandler.getTokenType = sinon
.stub()
.returns('readAndWrite')
this.TokenAccessHandler.getProjectByToken = sinon
this.TokenAccessHandler.promises.getProjectByToken = sinon
.stub()
.callsArgWith(2, null, this.project)
.resolves(this.project)
})
it('should not allow any access', function (done) {
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)
done()
}
)
it('should not allow any access', async function () {
const { isValidReadAndWrite, isValidReadOnly } =
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
expect(isValidReadAndWrite).to.equal(false)
expect(isValidReadOnly).to.equal(false)
})
})
@ -467,66 +408,60 @@ describe('TokenAccessHandler', function () {
this.TokenAccessHandler.getTokenType = sinon
.stub()
.returns('readOnly')
this.TokenAccessHandler.getProjectByToken = sinon
this.TokenAccessHandler.promises.getProjectByToken = sinon
.stub()
.callsArgWith(2, null, this.project)
.resolves(this.project)
})
it('should not allow any access', function (done) {
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)
done()
}
)
it('should not allow any access', async function () {
const { isValidReadAndWrite, isValidReadOnly } =
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
expect(isValidReadAndWrite).to.equal(false)
expect(isValidReadOnly).to.equal(false)
})
})
describe('with nothing', function () {
beforeEach(function () {
this.TokenAccessHandler.getProjectByToken = sinon
this.TokenAccessHandler.promises.getProjectByToken = sinon
.stub()
.callsArgWith(1, null, null, null)
.resolves(null)
})
it('should not allow any access', function (done) {
this.TokenAccessHandler.validateTokenForAnonymousAccess(
this.projectId,
null,
(err, rw, ro) => {
expect(err).to.not.exist
expect(rw).to.equal(false)
expect(ro).to.equal(false)
done()
}
)
it('should not allow any access', async function () {
const { isValidReadAndWrite, isValidReadOnly } =
await this.TokenAccessHandler.promises.validateTokenForAnonymousAccess(
this.projectId,
this.token
)
expect(isValidReadAndWrite).to.equal(false)
expect(isValidReadOnly).to.equal(false)
})
})
})
})
describe('getDocPublishedInfo', function () {
beforeEach(function () {
this.callback = sinon.stub()
})
describe('when v1 api not set', function () {
beforeEach(function () {
this.settings.apis = { v1: undefined }
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)
expect(
this.callback.calledWith(null, {
allow: true,
})
).to.equal(true)
it('should not check access and return default info', async function () {
const info =
await this.TokenAccessHandler.promises.getV1DocPublishedInfo(
this.token
)
expect(this.V1Api.promises.request.called).to.equal(false)
expect(info).to.deep.equal({
allow: true,
})
})
})
@ -537,63 +472,53 @@ describe('TokenAccessHandler', function () {
describe('on V1Api.request success', function () {
beforeEach(function () {
this.V1Api.request = sinon
this.V1Api.promises.request = sinon
.stub()
.callsArgWith(1, null, null, 'mock-data')
this.TokenAccessHandler.getV1DocPublishedInfo(
this.token,
this.callback
)
.resolves({ body: 'mock-data' })
})
it('should return response body', function () {
it('should return response body', async function () {
const info =
await this.TokenAccessHandler.promises.getV1DocPublishedInfo(
this.token
)
expect(
this.V1Api.request.calledWith({
this.V1Api.promises.request.calledWith({
url: `/api/v1/overleaf/docs/${this.token}/is_published`,
})
).to.equal(true)
expect(this.callback.calledWith(null, 'mock-data')).to.equal(true)
expect(info).to.equal('mock-data')
})
})
describe('on V1Api.request error', function () {
beforeEach(function () {
this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
this.TokenAccessHandler.getV1DocPublishedInfo(
this.token,
this.callback
)
this.V1Api.promises.request = sinon.stub().rejects('error')
})
it('should callback with error', function () {
expect(this.callback.calledWith('error')).to.equal(true)
it('should be rejected', async function () {
await expect(
this.TokenAccessHandler.promises.getV1DocPublishedInfo(this.token)
).to.be.rejected
})
})
})
})
describe('getV1DocInfo', function () {
beforeEach(function () {
this.callback = sinon.stub()
})
describe('when v1 api not set', function () {
beforeEach(function () {
this.TokenAccessHandler.getV1DocInfo(
it('should not check access and return default info', async function () {
const info = await this.TokenAccessHandler.promises.getV1DocInfo(
this.token,
this.v2UserId,
this.callback
this.v2UserId
)
})
it('should not check access and return default info', function () {
expect(this.V1Api.request.called).to.equal(false)
expect(
this.callback.calledWith(null, {
exists: true,
exported: false,
})
).to.equal(true)
expect(this.V1Api.promises.request.called).to.equal(false)
expect(info).to.deep.equal({
exists: true,
exported: false,
})
})
})
@ -604,38 +529,38 @@ describe('TokenAccessHandler', function () {
describe('on V1Api.request success', function () {
beforeEach(function () {
this.V1Api.request = sinon
this.V1Api.promises.request = sinon
.stub()
.callsArgWith(1, null, null, 'mock-data')
this.TokenAccessHandler.getV1DocInfo(
this.token,
this.v2UserId,
this.callback
)
.resolves({ body: 'mock-data' })
})
it('should return response body', function () {
it('should return response body', async function () {
const info = await this.TokenAccessHandler.promises.getV1DocInfo(
this.token,
this.v2UserId
)
expect(
this.V1Api.request.calledWith({
this.V1Api.promises.request.calledWith({
url: `/api/v1/overleaf/docs/${this.token}/info`,
})
).to.equal(true)
expect(this.callback.calledWith(null, 'mock-data')).to.equal(true)
expect(info).to.equal('mock-data')
})
})
describe('on V1Api.request error', function () {
beforeEach(function () {
this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
this.TokenAccessHandler.getV1DocInfo(
this.token,
this.v2UserId,
this.callback
)
this.V1Api.promises.request = sinon.stub().rejects('error')
})
it('should callback with error', function () {
expect(this.callback.calledWith('error')).to.equal(true)
it('should be rejected', async function () {
await expect(
this.TokenAccessHandler.promises.getV1DocInfo(
this.token,
this.v2UserId
)
).to.be.rejected
})
})
})