mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge branch 'sk-token-csrf-protection'
GitOrigin-RevId: e71f7264be45b665502150e9ffbb85b3fc94665e
This commit is contained in:
@@ -4,10 +4,11 @@ const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
||||
const ProjectGetter = require('../Project/ProjectGetter')
|
||||
const { User } = require('../../models/User')
|
||||
const PrivilegeLevels = require('./PrivilegeLevels')
|
||||
const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
|
||||
const PublicAccessLevels = require('./PublicAccessLevels')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const { ObjectId } = require('mongojs')
|
||||
const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
|
||||
const { promisify } = require('util')
|
||||
|
||||
module.exports = AuthorizationManager = {
|
||||
isRestrictedUser(userId, privilegeLevel, isTokenMember) {
|
||||
@@ -163,28 +164,25 @@ module.exports = AuthorizationManager = {
|
||||
// Anonymous users can have read-only access to token-based projects,
|
||||
// while read-write access must be logged in,
|
||||
// unless the `enableAnonymousReadAndWriteSharing` setting is enabled
|
||||
TokenAccessHandler.isValidToken(projectId, token, function(
|
||||
err,
|
||||
isValidReadAndWrite,
|
||||
isValidReadOnly
|
||||
) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
projectId,
|
||||
token,
|
||||
function(err, isValidReadAndWrite, isValidReadOnly) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
if (isValidReadOnly) {
|
||||
// Grant anonymous user read-only access
|
||||
return callback(null, PrivilegeLevels.READ_ONLY, false, false)
|
||||
}
|
||||
if (isValidReadAndWrite) {
|
||||
// Grant anonymous user read-and-write access
|
||||
return callback(null, PrivilegeLevels.READ_AND_WRITE, false, false)
|
||||
}
|
||||
// Deny anonymous access
|
||||
callback(null, PrivilegeLevels.NONE, false, false)
|
||||
}
|
||||
if (isValidReadOnly) {
|
||||
// Grant anonymous user read-only access
|
||||
return callback(null, PrivilegeLevels.READ_ONLY, false, false)
|
||||
}
|
||||
if (
|
||||
isValidReadAndWrite &&
|
||||
TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED
|
||||
) {
|
||||
// Grant anonymous user read-and-write access
|
||||
return callback(null, PrivilegeLevels.READ_AND_WRITE, false, false)
|
||||
}
|
||||
// Deny anonymous access
|
||||
callback(null, PrivilegeLevels.NONE, false, false)
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
canUserReadProject(userId, projectId, token, callback) {
|
||||
@@ -280,3 +278,9 @@ module.exports = AuthorizationManager = {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationManager.promises = {
|
||||
getPrivilegeLevelForProject: promisify(
|
||||
AuthorizationManager.getPrivilegeLevelForProject
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
const PrivilegeLevels = {
|
||||
NONE: false,
|
||||
READ_ONLY: 'readOnly',
|
||||
READ_AND_WRITE: 'readAndWrite',
|
||||
OWNER: 'owner'
|
||||
}
|
||||
|
||||
module.exports = PrivilegeLevels
|
||||
|
||||
@@ -736,12 +736,15 @@ const ProjectController = {
|
||||
const { subscription } = results
|
||||
const { brandVariation } = results
|
||||
|
||||
const token = TokenAccessHandler.getRequestToken(req, projectId)
|
||||
const anonRequestToken = TokenAccessHandler.getRequestToken(
|
||||
req,
|
||||
projectId
|
||||
)
|
||||
const { isTokenMember } = results
|
||||
AuthorizationManager.getPrivilegeLevelForProject(
|
||||
userId,
|
||||
projectId,
|
||||
token,
|
||||
anonRequestToken,
|
||||
(error, privilegeLevel) => {
|
||||
let allowedFreeTrial
|
||||
if (error != null) {
|
||||
@@ -804,7 +807,7 @@ const ProjectController = {
|
||||
privilegeLevel,
|
||||
chatUrl: Settings.apis.chat.url,
|
||||
anonymous,
|
||||
anonymousAccessToken: req._anonymousAccessToken,
|
||||
anonymousAccessToken: anonymous ? anonRequestToken : null,
|
||||
isTokenMember,
|
||||
isRestrictedTokenMember: AuthorizationManager.isRestrictedUser(
|
||||
userId,
|
||||
@@ -931,7 +934,6 @@ const ProjectController = {
|
||||
archived,
|
||||
trashed,
|
||||
owner_ref: project.owner_ref,
|
||||
tokens: project.tokens,
|
||||
isV1Project: false
|
||||
}
|
||||
if (accessLevel === PrivilegeLevels.READ_ONLY && source === Sources.TOKEN) {
|
||||
|
||||
@@ -1,277 +1,306 @@
|
||||
/* eslint-disable
|
||||
camelcase,
|
||||
max-len,
|
||||
*/
|
||||
// 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 TokenAccessController
|
||||
const ProjectController = require('../Project/ProjectController')
|
||||
const AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
const TokenAccessHandler = require('./TokenAccessHandler')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const logger = require('logger-sharelatex')
|
||||
const settings = require('settings-sharelatex')
|
||||
const OError = require('@overleaf/o-error')
|
||||
const { expressify } = require('../../util/promises')
|
||||
const AuthorizationManager = require('../Authorization/AuthorizationManager')
|
||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
|
||||
module.exports = TokenAccessController = {
|
||||
_loadEditor(projectId, req, res, next) {
|
||||
req.params.Project_id = projectId.toString()
|
||||
return ProjectController.loadEditor(req, res, next)
|
||||
},
|
||||
const orderedPrivilegeLevels = [
|
||||
PrivilegeLevels.NONE,
|
||||
PrivilegeLevels.READ_ONLY,
|
||||
PrivilegeLevels.READ_AND_WRITE,
|
||||
PrivilegeLevels.OWNER
|
||||
]
|
||||
|
||||
_tryHigherAccess(token, userId, req, res, next) {
|
||||
return TokenAccessHandler.findProjectWithHigherAccess(
|
||||
async function _userAlreadyHasHigherPrivilege(
|
||||
userId,
|
||||
projectId,
|
||||
token,
|
||||
tokenType
|
||||
) {
|
||||
if (!Object.values(TokenAccessHandler.TOKEN_TYPES).includes(tokenType)) {
|
||||
throw new Error('bad token type')
|
||||
}
|
||||
const privilegeLevel = await AuthorizationManager.promises.getPrivilegeLevelForProject(
|
||||
userId,
|
||||
projectId,
|
||||
token
|
||||
)
|
||||
return (
|
||||
orderedPrivilegeLevels.indexOf(privilegeLevel) >=
|
||||
orderedPrivilegeLevels.indexOf(tokenType)
|
||||
)
|
||||
}
|
||||
|
||||
const makePostUrl = token => {
|
||||
if (TokenAccessHandler.isReadAndWriteToken(token)) {
|
||||
return `/${token}/grant`
|
||||
} else if (TokenAccessHandler.isReadOnlyToken(token)) {
|
||||
return `/read/${token}/grant`
|
||||
} else {
|
||||
throw new Error('invalid token type')
|
||||
}
|
||||
}
|
||||
|
||||
async function _handleV1Project(token, userId) {
|
||||
if (!userId) {
|
||||
return { v1Import: { status: 'mustLogin' } }
|
||||
} else {
|
||||
const docInfo = await TokenAccessHandler.promises.getV1DocInfo(
|
||||
token,
|
||||
userId,
|
||||
function(err, project) {
|
||||
if (err != null) {
|
||||
logger.warn(
|
||||
{ err, token, userId },
|
||||
'[TokenAccess] error finding project with higher access'
|
||||
)
|
||||
return next(err)
|
||||
}
|
||||
if (project == null) {
|
||||
logger.log(
|
||||
{ token, userId },
|
||||
'[TokenAccess] no project with higher access found for this user and token'
|
||||
)
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
logger.log(
|
||||
{ token, userId, projectId: project._id },
|
||||
'[TokenAccess] user has higher access to project, redirecting'
|
||||
)
|
||||
return res.redirect(302, `/project/${project._id}`)
|
||||
}
|
||||
userId
|
||||
)
|
||||
},
|
||||
|
||||
readAndWriteToken(req, res, next) {
|
||||
const userId = AuthenticationController.getLoggedInUserId(req)
|
||||
const token = req.params['read_and_write_token']
|
||||
logger.log(
|
||||
{ userId, token },
|
||||
'[TokenAccess] requesting read-and-write token access'
|
||||
)
|
||||
return TokenAccessHandler.findProjectWithReadAndWriteToken(token, function(
|
||||
err,
|
||||
project,
|
||||
projectExists
|
||||
) {
|
||||
if (err != null) {
|
||||
logger.warn(
|
||||
{ err, token, userId },
|
||||
'[TokenAccess] error getting project by readAndWrite token'
|
||||
)
|
||||
return next(err)
|
||||
if (!docInfo) {
|
||||
return { v1Import: { status: 'cannotImport' } }
|
||||
}
|
||||
if (!docInfo.exists) {
|
||||
return null
|
||||
}
|
||||
if (docInfo.exported) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
v1Import: {
|
||||
status: 'canImport',
|
||||
projectId: token,
|
||||
hasOwner: docInfo.has_owner,
|
||||
name: docInfo.name || 'Untitled',
|
||||
hasAssignment: docInfo.has_assignment,
|
||||
brandInfo: docInfo.brand_info
|
||||
}
|
||||
if (!projectExists && settings.overleaf) {
|
||||
logger.log(
|
||||
{ token, userId },
|
||||
'[TokenAccess] no project found for this token'
|
||||
)
|
||||
return TokenAccessController._handleV1Project(
|
||||
token,
|
||||
userId,
|
||||
`/${token}`,
|
||||
res,
|
||||
next
|
||||
)
|
||||
} else if (project == null) {
|
||||
if (userId == null) {
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
return TokenAccessController._tryHigherAccess(
|
||||
token,
|
||||
userId,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
} else {
|
||||
if (userId == null) {
|
||||
if (TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED) {
|
||||
TokenAccessHandler.grantSessionTokenAccess(req, project._id, token)
|
||||
req._anonymousAccessToken = token
|
||||
return TokenAccessController._loadEditor(
|
||||
project._id,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
} else {
|
||||
logger.log(
|
||||
{ token, projectId: project._id },
|
||||
'[TokenAccess] deny anonymous read-and-write token access'
|
||||
)
|
||||
AuthenticationController.setRedirectInSession(req)
|
||||
return res.redirect('/restricted')
|
||||
}
|
||||
}
|
||||
if (project.owner_ref.toString() === userId) {
|
||||
logger.log(
|
||||
{ userId, projectId: project._id },
|
||||
'[TokenAccess] user is already project owner'
|
||||
)
|
||||
return TokenAccessController._loadEditor(project._id, req, res, next)
|
||||
}
|
||||
logger.log(
|
||||
{ userId, projectId: project._id },
|
||||
'[TokenAccess] adding user to project with readAndWrite token'
|
||||
)
|
||||
return TokenAccessHandler.addReadAndWriteUserToProject(
|
||||
userId,
|
||||
project._id,
|
||||
function(err) {
|
||||
if (err != null) {
|
||||
logger.warn(
|
||||
{ err, token, userId, projectId: project._id },
|
||||
'[TokenAccess] error adding user to project with readAndWrite token'
|
||||
)
|
||||
return next(err)
|
||||
}
|
||||
return TokenAccessController._loadEditor(
|
||||
project._id,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
readOnlyToken(req, res, next) {
|
||||
const userId = AuthenticationController.getLoggedInUserId(req)
|
||||
const token = req.params['read_only_token']
|
||||
|
||||
return TokenAccessHandler.getV1DocPublishedInfo(token, function(
|
||||
err,
|
||||
doc_published_info
|
||||
) {
|
||||
if (err != null) {
|
||||
return next(err)
|
||||
}
|
||||
if (doc_published_info.allow === false) {
|
||||
return res.redirect(doc_published_info.published_path)
|
||||
}
|
||||
|
||||
return TokenAccessHandler.findProjectWithReadOnlyToken(token, function(
|
||||
err,
|
||||
project,
|
||||
projectExists
|
||||
) {
|
||||
if (err != null) {
|
||||
logger.warn(
|
||||
{ err, token, userId },
|
||||
'[TokenAccess] error getting project by readOnly token'
|
||||
)
|
||||
return next(err)
|
||||
}
|
||||
if (!projectExists && settings.overleaf) {
|
||||
logger.log(
|
||||
{ token, userId },
|
||||
'[TokenAccess] no project found for this token'
|
||||
)
|
||||
return TokenAccessController._handleV1Project(
|
||||
token,
|
||||
userId,
|
||||
`/read/${token}`,
|
||||
res,
|
||||
next
|
||||
)
|
||||
} else if (project == null) {
|
||||
if (userId == null) {
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
return TokenAccessController._tryHigherAccess(
|
||||
token,
|
||||
userId,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
} else {
|
||||
if (userId == null) {
|
||||
TokenAccessHandler.grantSessionTokenAccess(req, project._id, token)
|
||||
req._anonymousAccessToken = token
|
||||
return TokenAccessController._loadEditor(
|
||||
project._id,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
} else {
|
||||
if (project.owner_ref.toString() === userId) {
|
||||
return TokenAccessController._loadEditor(
|
||||
project._id,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
}
|
||||
logger.log(
|
||||
{ userId, projectId: project._id },
|
||||
'[TokenAccess] adding user to project with readOnly token'
|
||||
)
|
||||
return TokenAccessHandler.addReadOnlyUserToProject(
|
||||
userId,
|
||||
project._id,
|
||||
function(err) {
|
||||
if (err != null) {
|
||||
logger.warn(
|
||||
{ err, token, userId, projectId: project._id },
|
||||
'[TokenAccess] error adding user to project with readAndWrite token'
|
||||
)
|
||||
return next(err)
|
||||
}
|
||||
return TokenAccessController._loadEditor(
|
||||
project._id,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
_handleV1Project(token, userId, redirectPath, res, next) {
|
||||
if (userId == null) {
|
||||
return res.render('project/v2-import', { loginRedirect: redirectPath })
|
||||
} else {
|
||||
TokenAccessHandler.getV1DocInfo(token, userId, function(err, doc_info) {
|
||||
if (err != null) {
|
||||
return next(err)
|
||||
}
|
||||
if (!doc_info) {
|
||||
res.status(400)
|
||||
return res.render('project/cannot-import-v1-project')
|
||||
}
|
||||
if (!doc_info.exists) {
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
if (doc_info.exported) {
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
return res.render('project/v2-import', {
|
||||
projectId: token,
|
||||
hasOwner: doc_info.has_owner,
|
||||
name: doc_info.name || 'Untitled',
|
||||
hasAssignment: doc_info.has_assignment,
|
||||
brandInfo: doc_info.brand_info
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function tokenAccessPage(req, res, next) {
|
||||
const { token } = req.params
|
||||
if (!TokenAccessHandler.isValidToken(token)) {
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
try {
|
||||
if (TokenAccessHandler.isReadOnlyToken(token)) {
|
||||
const docPublishedInfo = await TokenAccessHandler.promises.getV1DocPublishedInfo(
|
||||
token
|
||||
)
|
||||
if (docPublishedInfo.allow === false) {
|
||||
return res.redirect(302, docPublishedInfo.published_path)
|
||||
}
|
||||
}
|
||||
res.render('project/token/access', {
|
||||
postUrl: makePostUrl(token)
|
||||
})
|
||||
} catch (err) {
|
||||
return next(
|
||||
new OError({
|
||||
message: 'error while rendering token access page',
|
||||
info: { token }
|
||||
}).withCause(err)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAndGetProjectOrResponseAction(
|
||||
tokenType,
|
||||
token,
|
||||
userId,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
) {
|
||||
// Try to get the project, and/or an alternative action to take.
|
||||
// Returns a tuple of [project, action]
|
||||
const project = await TokenAccessHandler.promises.getProjectByToken(
|
||||
tokenType,
|
||||
token
|
||||
)
|
||||
if (!project) {
|
||||
if (settings.overleaf) {
|
||||
const v1ImportData = await _handleV1Project(token, userId)
|
||||
return [
|
||||
null,
|
||||
() => {
|
||||
if (v1ImportData) {
|
||||
res.json(v1ImportData)
|
||||
} else {
|
||||
res.sendStatus(404)
|
||||
}
|
||||
}
|
||||
]
|
||||
} else {
|
||||
return [null, null]
|
||||
}
|
||||
}
|
||||
|
||||
const projectId = project._id
|
||||
const isAnonymousUser = !userId
|
||||
const tokenAccessEnabled = TokenAccessHandler.tokenAccessEnabledForProject(
|
||||
project
|
||||
)
|
||||
if (isAnonymousUser && tokenAccessEnabled) {
|
||||
if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE) {
|
||||
if (TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED) {
|
||||
logger.info({ projectId }, 'granting read-write anonymous access')
|
||||
TokenAccessHandler.grantSessionTokenAccess(req, projectId, token)
|
||||
return [
|
||||
null,
|
||||
() => {
|
||||
res.json({
|
||||
redirect: `/project/${projectId}`,
|
||||
grantAnonymousAccess: tokenType
|
||||
})
|
||||
}
|
||||
]
|
||||
} else {
|
||||
logger.warn(
|
||||
{ token, projectId },
|
||||
'[TokenAccess] deny anonymous read-and-write token access'
|
||||
)
|
||||
AuthenticationController.setRedirectInSession(req)
|
||||
return [
|
||||
null,
|
||||
() => {
|
||||
res.json({
|
||||
redirect: '/restricted',
|
||||
anonWriteAccessDenied: true
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_ONLY) {
|
||||
logger.info({ projectId }, 'granting read-only anonymous access')
|
||||
TokenAccessHandler.grantSessionTokenAccess(req, projectId, token)
|
||||
return [
|
||||
null,
|
||||
() => {
|
||||
res.json({
|
||||
redirect: `/project/${projectId}`,
|
||||
grantAnonymousAccess: tokenType
|
||||
})
|
||||
}
|
||||
]
|
||||
} else {
|
||||
throw new Error('unreachable')
|
||||
}
|
||||
}
|
||||
const userHasPrivilege = await _userAlreadyHasHigherPrivilege(
|
||||
userId,
|
||||
projectId,
|
||||
token,
|
||||
tokenType
|
||||
)
|
||||
if (userHasPrivilege) {
|
||||
return [
|
||||
null,
|
||||
() => {
|
||||
res.json({ redirect: `/project/${project._id}`, higherAccess: true })
|
||||
}
|
||||
]
|
||||
}
|
||||
if (!tokenAccessEnabled) {
|
||||
return [
|
||||
null,
|
||||
() => {
|
||||
next(new Errors.NotFoundError())
|
||||
}
|
||||
]
|
||||
}
|
||||
return [project, null]
|
||||
}
|
||||
|
||||
async function grantTokenAccessReadAndWrite(req, res, next) {
|
||||
const { token } = req.params
|
||||
const userId = AuthenticationController.getLoggedInUserId(req)
|
||||
if (!TokenAccessHandler.isReadAndWriteToken(token)) {
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE
|
||||
try {
|
||||
const [project, action] = await checkAndGetProjectOrResponseAction(
|
||||
tokenType,
|
||||
token,
|
||||
userId,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
if (action) {
|
||||
return action()
|
||||
}
|
||||
if (!project) {
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
await TokenAccessHandler.promises.addReadAndWriteUserToProject(
|
||||
userId,
|
||||
project._id
|
||||
)
|
||||
return res.json({
|
||||
redirect: `/project/${project._id}`,
|
||||
tokenAccessGranted: tokenType
|
||||
})
|
||||
} catch (err) {
|
||||
return next(
|
||||
new OError({
|
||||
message: 'error while trying to grant read-and-write token access',
|
||||
info: { token }
|
||||
}).withCause(err)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function grantTokenAccessReadOnly(req, res, next) {
|
||||
const { token } = req.params
|
||||
const userId = AuthenticationController.getLoggedInUserId(req)
|
||||
if (!TokenAccessHandler.isReadOnlyToken(token)) {
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_ONLY
|
||||
const docPublishedInfo = await TokenAccessHandler.promises.getV1DocPublishedInfo(
|
||||
token
|
||||
)
|
||||
if (docPublishedInfo.allow === false) {
|
||||
return res.json({ redirect: docPublishedInfo.published_path })
|
||||
}
|
||||
try {
|
||||
const [project, action] = await checkAndGetProjectOrResponseAction(
|
||||
tokenType,
|
||||
token,
|
||||
userId,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
)
|
||||
if (action) {
|
||||
return action()
|
||||
}
|
||||
if (!project) {
|
||||
return next(new Errors.NotFoundError())
|
||||
}
|
||||
await TokenAccessHandler.promises.addReadOnlyUserToProject(
|
||||
userId,
|
||||
project._id
|
||||
)
|
||||
return res.json({
|
||||
redirect: `/project/${project._id}`,
|
||||
tokenAccessGranted: tokenType
|
||||
})
|
||||
} catch (err) {
|
||||
return next(
|
||||
new OError({
|
||||
message: 'error while trying to grant read-only token access',
|
||||
info: { token }
|
||||
}).withCause(err)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
READ_ONLY_TOKEN_PATTERN: TokenAccessHandler.READ_ONLY_TOKEN_PATTERN,
|
||||
READ_AND_WRITE_TOKEN_PATTERN: TokenAccessHandler.READ_AND_WRITE_TOKEN_PATTERN,
|
||||
|
||||
tokenAccessPage: expressify(tokenAccessPage),
|
||||
grantTokenAccessReadOnly: expressify(grantTokenAccessReadOnly),
|
||||
grantTokenAccessReadAndWrite: expressify(grantTokenAccessReadAndWrite)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const { Project } = require('../../models/Project')
|
||||
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
||||
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
|
||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
@@ -8,63 +7,100 @@ const Settings = require('settings-sharelatex')
|
||||
const logger = require('logger-sharelatex')
|
||||
const V1Api = require('../V1/V1Api')
|
||||
const crypto = require('crypto')
|
||||
const { promisifyAll } = require('../../util/promises')
|
||||
|
||||
const READ_AND_WRITE_TOKEN_PATTERN = '([0-9]+[a-z]{6,12})'
|
||||
const READ_ONLY_TOKEN_PATTERN = '([a-z]{12})'
|
||||
|
||||
const TokenAccessHandler = {
|
||||
TOKEN_TYPES: {
|
||||
READ_ONLY: PrivilegeLevels.READ_ONLY,
|
||||
READ_AND_WRITE: PrivilegeLevels.READ_AND_WRITE
|
||||
},
|
||||
|
||||
ANONYMOUS_READ_AND_WRITE_ENABLED:
|
||||
Settings.allowAnonymousReadAndWriteSharing === true,
|
||||
READ_AND_WRITE_TOKEN_REGEX: /^(\d+)(\w+)$/,
|
||||
READ_AND_WRITE_URL_REGEX: /^\/(\d+)(\w+)$/,
|
||||
READ_ONLY_TOKEN_REGEX: /^([a-z]{12})$/,
|
||||
READ_ONLY_URL_REGEX: /^\/read\/([a-z]{12})$/,
|
||||
|
||||
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}$`),
|
||||
|
||||
getTokenType(token) {
|
||||
if (!token) {
|
||||
return null
|
||||
}
|
||||
if (token.match(`^${TokenAccessHandler.READ_ONLY_TOKEN_PATTERN}$`)) {
|
||||
return TokenAccessHandler.TOKEN_TYPES.READ_ONLY
|
||||
} else if (
|
||||
token.match(`^${TokenAccessHandler.READ_AND_WRITE_TOKEN_PATTERN}$`)
|
||||
) {
|
||||
return TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
isReadOnlyToken(token) {
|
||||
return (
|
||||
TokenAccessHandler.getTokenType(token) ===
|
||||
TokenAccessHandler.TOKEN_TYPES.READ_ONLY
|
||||
)
|
||||
},
|
||||
|
||||
isReadAndWriteToken(token) {
|
||||
return (
|
||||
TokenAccessHandler.getTokenType(token) ===
|
||||
TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE
|
||||
)
|
||||
},
|
||||
|
||||
isValidToken(token) {
|
||||
return TokenAccessHandler.getTokenType(token) != null
|
||||
},
|
||||
|
||||
tokenAccessEnabledForProject(project) {
|
||||
return project.publicAccesLevel === PublicAccessLevels.TOKEN_BASED
|
||||
},
|
||||
|
||||
_projectFindOne(query, callback) {
|
||||
Project.findOne(
|
||||
query,
|
||||
{
|
||||
_id: 1,
|
||||
tokens: 1,
|
||||
publicAccesLevel: 1,
|
||||
owner_ref: 1,
|
||||
name: 1
|
||||
},
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
getProjectByReadOnlyToken(token, callback) {
|
||||
TokenAccessHandler._projectFindOne({ 'tokens.readOnly': token }, callback)
|
||||
},
|
||||
|
||||
_extractNumericPrefix(token) {
|
||||
return token.match(/^(\d+)\w+/)
|
||||
},
|
||||
|
||||
_getProjectByReadOnlyToken(token, callback) {
|
||||
Project.findOne(
|
||||
{
|
||||
'tokens.readOnly': token
|
||||
},
|
||||
{ _id: 1, tokens: 1, publicAccesLevel: 1, owner_ref: 1 },
|
||||
callback
|
||||
)
|
||||
_extractStringSuffix(token) {
|
||||
return token.match(/^\d+(\w+)/)
|
||||
},
|
||||
|
||||
_getProjectByEitherToken(token, callback) {
|
||||
TokenAccessHandler._getProjectByReadOnlyToken(token, function(
|
||||
err,
|
||||
project
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
if (project != null) {
|
||||
return callback(null, project)
|
||||
}
|
||||
TokenAccessHandler._getProjectByReadAndWriteToken(token, function(
|
||||
err,
|
||||
project
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, project)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
_getProjectByReadAndWriteToken(token, callback) {
|
||||
getProjectByReadAndWriteToken(token, callback) {
|
||||
const numericPrefixMatch = TokenAccessHandler._extractNumericPrefix(token)
|
||||
if (!numericPrefixMatch) {
|
||||
return callback(null, null)
|
||||
}
|
||||
const numerics = numericPrefixMatch[1]
|
||||
Project.findOne(
|
||||
TokenAccessHandler._projectFindOne(
|
||||
{
|
||||
'tokens.readAndWritePrefix': numerics
|
||||
},
|
||||
{ _id: 1, tokens: 1, publicAccesLevel: 1, owner_ref: 1 },
|
||||
function(err, project) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
@@ -96,69 +132,14 @@ const TokenAccessHandler = {
|
||||
)
|
||||
},
|
||||
|
||||
findProjectWithReadOnlyToken(token, callback) {
|
||||
TokenAccessHandler._getProjectByReadOnlyToken(token, function(
|
||||
err,
|
||||
project
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
if (project == null) {
|
||||
return callback(null, null, false) // Project doesn't exist, so we handle differently
|
||||
}
|
||||
if (project.publicAccesLevel !== PublicAccessLevels.TOKEN_BASED) {
|
||||
return callback(null, null, true) // Project does exist, but it isn't token based
|
||||
}
|
||||
callback(null, project, true)
|
||||
})
|
||||
},
|
||||
|
||||
findProjectWithReadAndWriteToken(token, callback) {
|
||||
TokenAccessHandler._getProjectByReadAndWriteToken(token, function(
|
||||
err,
|
||||
project
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
if (project == null) {
|
||||
return callback(null, null, false) // Project doesn't exist, so we handle differently
|
||||
}
|
||||
if (project.publicAccesLevel !== PublicAccessLevels.TOKEN_BASED) {
|
||||
return callback(null, null, true) // Project does exist, but it isn't token based
|
||||
}
|
||||
callback(null, project, true)
|
||||
})
|
||||
},
|
||||
|
||||
_userIsMember(userId, projectId, callback) {
|
||||
CollaboratorsGetter.isUserInvitedMemberOfProject(
|
||||
userId,
|
||||
projectId,
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
findProjectWithHigherAccess(token, userId, callback) {
|
||||
TokenAccessHandler._getProjectByEitherToken(token, function(err, project) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
if (project == null) {
|
||||
return callback(null, null)
|
||||
}
|
||||
const projectId = project._id
|
||||
TokenAccessHandler._userIsMember(userId, projectId, function(
|
||||
err,
|
||||
isMember
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, isMember === true ? project : 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 {
|
||||
return callback(new Error('invalid token type'))
|
||||
}
|
||||
},
|
||||
|
||||
addReadOnlyUserToProject(userId, projectId, callback) {
|
||||
@@ -196,7 +177,7 @@ const TokenAccessHandler = {
|
||||
if (!req.session.anonTokenAccess) {
|
||||
req.session.anonTokenAccess = {}
|
||||
}
|
||||
req.session.anonTokenAccess[projectId.toString()] = token.toString()
|
||||
req.session.anonTokenAccess[projectId.toString()] = token
|
||||
},
|
||||
|
||||
getRequestToken(req, projectId) {
|
||||
@@ -208,32 +189,32 @@ const TokenAccessHandler = {
|
||||
return token
|
||||
},
|
||||
|
||||
isValidToken(projectId, token, callback) {
|
||||
validateTokenForAnonymousAccess(projectId, token, callback) {
|
||||
if (!token) {
|
||||
return callback(null, false, false)
|
||||
}
|
||||
const _validate = project =>
|
||||
project != null &&
|
||||
project.publicAccesLevel === PublicAccessLevels.TOKEN_BASED &&
|
||||
project._id.toString() === projectId.toString()
|
||||
TokenAccessHandler.findProjectWithReadAndWriteToken(token, function(
|
||||
err,
|
||||
readAndWriteProject
|
||||
) {
|
||||
if (err != null) {
|
||||
const tokenType = TokenAccessHandler.getTokenType(token)
|
||||
if (!tokenType) {
|
||||
return callback(new Error('invalid token type'))
|
||||
}
|
||||
TokenAccessHandler.getProjectByToken(tokenType, token, (err, project) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
const isValidReadAndWrite = _validate(readAndWriteProject)
|
||||
TokenAccessHandler.findProjectWithReadOnlyToken(token, function(
|
||||
err,
|
||||
readOnlyProject
|
||||
if (
|
||||
!project ||
|
||||
!TokenAccessHandler.tokenAccessEnabledForProject(project) ||
|
||||
project._id.toString() !== projectId.toString()
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
const isValidReadOnly = _validate(readOnlyProject)
|
||||
callback(null, isValidReadAndWrite, isValidReadOnly)
|
||||
})
|
||||
return callback(null, false, false)
|
||||
}
|
||||
// TODO: think about cleaning up this interface and its usage in AuthorizationManager
|
||||
return callback(
|
||||
null,
|
||||
tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE &&
|
||||
TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED,
|
||||
tokenType === TokenAccessHandler.TOKEN_TYPES.READ_ONLY
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -297,4 +278,18 @@ const TokenAccessHandler = {
|
||||
}
|
||||
}
|
||||
|
||||
TokenAccessHandler.promises = promisifyAll(TokenAccessHandler, {
|
||||
without: [
|
||||
'getTokenType',
|
||||
'tokenAccessEnabledForProject',
|
||||
'_extractNumericPrefix',
|
||||
'_extractStringSuffix',
|
||||
'_projectFindOne',
|
||||
'grantSessionTokenAccess',
|
||||
'getRequestToken',
|
||||
'protectTokens',
|
||||
'validateTokenForAnonymousAccess'
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = TokenAccessHandler
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let V1Api
|
||||
const request = require('request')
|
||||
const settings = require('settings-sharelatex')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const { promisifyAll } = require('../../util/promises')
|
||||
|
||||
// TODO: check what happens when these settings aren't defined
|
||||
const DEFAULT_V1_PARAMS = {
|
||||
@@ -36,7 +36,7 @@ const DEFAULT_V1_OAUTH_PARAMS = {
|
||||
|
||||
const v1OauthRequest = request.defaults(DEFAULT_V1_OAUTH_PARAMS)
|
||||
|
||||
module.exports = V1Api = {
|
||||
const V1Api = {
|
||||
request(options, callback) {
|
||||
if (callback == null) {
|
||||
return request(options)
|
||||
@@ -103,3 +103,11 @@ module.exports = V1Api = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
V1Api.promises = promisifyAll(V1Api, {
|
||||
multiResult: {
|
||||
request: ['response', 'body'],
|
||||
oauthRequest: ['response', 'body']
|
||||
}
|
||||
})
|
||||
module.exports = V1Api
|
||||
|
||||
@@ -1037,23 +1037,43 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
})
|
||||
|
||||
webRouter.get(
|
||||
'/read/:read_only_token([a-z]+)',
|
||||
`/read/:token(${TokenAccessController.READ_ONLY_TOKEN_PATTERN})`,
|
||||
RateLimiterMiddleware.rateLimit({
|
||||
endpointName: 'read-only-token',
|
||||
maxRequests: 15,
|
||||
timeInterval: 60
|
||||
}),
|
||||
TokenAccessController.readOnlyToken
|
||||
TokenAccessController.tokenAccessPage
|
||||
)
|
||||
|
||||
webRouter.get(
|
||||
'/:read_and_write_token([0-9]+[a-z]+)',
|
||||
`/:token(${TokenAccessController.READ_AND_WRITE_TOKEN_PATTERN})`,
|
||||
RateLimiterMiddleware.rateLimit({
|
||||
endpointName: 'read-and-write-token',
|
||||
maxRequests: 15,
|
||||
timeInterval: 60
|
||||
}),
|
||||
TokenAccessController.readAndWriteToken
|
||||
TokenAccessController.tokenAccessPage
|
||||
)
|
||||
|
||||
webRouter.post(
|
||||
`/:token(${TokenAccessController.READ_AND_WRITE_TOKEN_PATTERN})/grant`,
|
||||
RateLimiterMiddleware.rateLimit({
|
||||
endpointName: 'grant-token-access-read-write',
|
||||
maxRequests: 10,
|
||||
timeInterval: 60
|
||||
}),
|
||||
TokenAccessController.grantTokenAccessReadAndWrite
|
||||
)
|
||||
|
||||
webRouter.post(
|
||||
`/read/:token(${TokenAccessController.READ_ONLY_TOKEN_PATTERN})/grant`,
|
||||
RateLimiterMiddleware.rateLimit({
|
||||
endpointName: 'grant-token-access-read-only',
|
||||
maxRequests: 10,
|
||||
timeInterval: 60
|
||||
}),
|
||||
TokenAccessController.grantTokenAccessReadOnly
|
||||
)
|
||||
|
||||
webRouter.get('*', ErrorController.notFound)
|
||||
|
||||
155
services/web/app/views/project/token/access.pug
Normal file
155
services/web/app/views/project/token/access.pug
Normal file
@@ -0,0 +1,155 @@
|
||||
extends ../../layout
|
||||
|
||||
block vars
|
||||
- metadata = { viewport: true }
|
||||
|
||||
block content
|
||||
|
||||
script(type="template", id="overleaf-token-access-data")!= StringHelper.stringifyJsonForScript({ postUrl: postUrl, csrfToken: csrfToken})
|
||||
|
||||
div(
|
||||
ng-controller="TokenAccessPageController",
|
||||
ng-init="post()"
|
||||
)
|
||||
.editor.full-size
|
||||
div
|
||||
|
|
||||
a(href="/project" style="font-size: 2rem; margin-left: 1rem; color: #ddd;")
|
||||
i.fa.fa-arrow-left
|
||||
|
||||
.loading-screen(
|
||||
ng-show="mode == 'accessAttempt'"
|
||||
)
|
||||
.loading-screen-brand-container
|
||||
.loading-screen-brand()
|
||||
|
||||
h3.loading-screen-label.text-center
|
||||
| #{translate('join_project')}
|
||||
span(ng-show="accessInFlight == true")
|
||||
span.loading-screen-ellip .
|
||||
span.loading-screen-ellip .
|
||||
span.loading-screen-ellip .
|
||||
|
||||
|
||||
.global-alerts.text-center(ng-cloak)
|
||||
div(ng-show="accessError", ng-cloak)
|
||||
br
|
||||
div(ng-switch="accessError", ng-cloak)
|
||||
div(ng-switch-when="not_found")
|
||||
h4(aria-live="assertive")
|
||||
| Project not found
|
||||
|
||||
div(ng-switch-default)
|
||||
.alert.alert-danger(aria-live="assertive") #{translate('token_access_failure')}
|
||||
p
|
||||
a(href="/") #{translate('home')}
|
||||
|
||||
.loading-screen(
|
||||
ng-show="mode == 'v1Import'"
|
||||
)
|
||||
.container
|
||||
.row
|
||||
.col-sm-8.col-sm-offset-2
|
||||
h1.text-center Move project to Overleaf v2
|
||||
img.v2-import__img(
|
||||
src="/img/v1-import/v2-editor.png"
|
||||
alt="The new V2 editor."
|
||||
)
|
||||
|
||||
div(ng-if="v1ImportData.status == 'cannotImport'")
|
||||
h2.text-center
|
||||
| Cannot Access Overleaf v1 Project
|
||||
p.text-center.row-spaced-small
|
||||
| The project you are attempting to access must be imported to Overleaf v2 before it can be accessed. Please contact the project owner or
|
||||
|
|
||||
a(href="/contact") contact support
|
||||
|
|
||||
| for assistance.
|
||||
|
||||
div(ng-if="v1ImportData.status == 'mustLogin'")
|
||||
p.text-center.row-spaced-small
|
||||
| This project has not yet been moved into the new version of
|
||||
| Overleaf. You will need to log in and move it in order to
|
||||
| continue working on it.
|
||||
|
||||
.row-spaced.text-center
|
||||
a.btn.btn-primary(
|
||||
href="/login?redir={{ currentPath() }}"
|
||||
) Log In To Move Project
|
||||
|
||||
div(ng-if="v1ImportData.status == 'canImport'")
|
||||
div(ng-if="v1ImportData.hasOwner")
|
||||
p.text-center.row-spaced-small
|
||||
| #[strong() {{ getProjectName() }}] has not yet been moved into
|
||||
| the new version of Overleaf. You will need to move it in order
|
||||
| to continue working on it. It should only take a few seconds.
|
||||
|
||||
form(
|
||||
async-form="v2Import"
|
||||
name="v2ImportForm"
|
||||
action="{{ buildFormImportPath(v1ImportData.projectId) }}"
|
||||
method="POST"
|
||||
ng-cloak
|
||||
)
|
||||
input(name='_csrf', type='hidden', value=csrfToken)
|
||||
form-messages(for="v2ImportForm")
|
||||
input.row-spaced.btn.btn-primary.text-center.center-block(
|
||||
type="submit"
|
||||
ng-value="v2ImportForm.inflight || v2ImportForm.response.success ? 'Moving to v2...' : 'Move Project and Continue'"
|
||||
ng-disabled="v2ImportForm.inflight || v2ImportForm.response.success"
|
||||
)
|
||||
|
||||
.row-spaced-small.text-center
|
||||
a(href="{{ buildZipDownloadPath(v1ImportData.projectId) }}")
|
||||
| Download project zip file
|
||||
div(ng-if="!v1ImportData.hasOwner")
|
||||
p.text-center.row-spaced.small
|
||||
| #[strong() {{ getProjectName() }}] has not yet been moved into
|
||||
| the new version of Overleaf. This project was created
|
||||
| anonymously and therefore cannot be automatically imported.
|
||||
| Please download a zip file of the project and upload that to
|
||||
| continue editing it. If you would like to delete this project
|
||||
| after you have made a copy, please contact support.
|
||||
|
||||
.row-spaced.text-center
|
||||
a.btn.btn-primary(href="{{ buildZipDownloadPath(v1ImportData.projectId) }}")
|
||||
| Download project zip file
|
||||
|
||||
.row-spaced.text-center
|
||||
div(ng-if="v1ImportData.hasAssignment")
|
||||
p
|
||||
| #[span.fa.fa-exclamation-triangle]
|
||||
| This project is an assignment, and the assignment toolkit is
|
||||
| no longer supported in Overleaf v2. When you move it to
|
||||
| Overleaf v2, it will become a normal project.
|
||||
| #[a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ#assignment-tools") Please see our FAQ]
|
||||
| for more information or contact your instructor if you haven't
|
||||
| already submitted it.
|
||||
div(ng-if="!v1ImportData.hasAssignment")
|
||||
div(ng-switch="v1ImportData.brandInfo")
|
||||
div(ng-switch-when="'f1000'")
|
||||
p
|
||||
| #[span.fa.fa-exclamation-triangle]
|
||||
| This project is an F1000Research article, and our integration
|
||||
| with F1000Research has changed in Overleaf v2.
|
||||
| #[a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ#f1000research") Find out more about moving to Overleaf v2]
|
||||
div(ng-switch-when="'wellcome'")
|
||||
p
|
||||
| #[span.fa.fa-exclamation-triangle]
|
||||
| This project is an Wellcome Open Research article, and our
|
||||
| integration with Wellcome Open Research has changed in
|
||||
| Overleaf v2.
|
||||
| #[a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ#f1000research") Find out more about moving to Overleaf v2]
|
||||
div(ng-switch-default)
|
||||
a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ")
|
||||
| Find out more about moving to Overleaf v2
|
||||
|
||||
|
||||
block append foot-scripts
|
||||
script.
|
||||
$(document).ready(function () {
|
||||
setTimeout(function() {
|
||||
$('.loading-screen-brand').css('height', '20%')
|
||||
}, 500);
|
||||
});
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block vars
|
||||
- metadata = { viewport: true }
|
||||
|
||||
block content
|
||||
main.content
|
||||
.container
|
||||
.row
|
||||
.col-sm-8.col-sm-offset-2
|
||||
h1.text-center Move project to Overleaf v2
|
||||
img.v2-import__img(
|
||||
src="/img/v1-import/v2-editor.png"
|
||||
alt="The new V2 editor."
|
||||
)
|
||||
|
||||
if loginRedirect
|
||||
p.text-center.row-spaced-small
|
||||
| This project has not yet been moved into the new version of
|
||||
| Overleaf. You will need to log in and move it in order to
|
||||
| continue working on it.
|
||||
|
||||
.row-spaced.text-center
|
||||
a.btn.btn-primary(
|
||||
href="/login?redir=" + loginRedirect
|
||||
) Log In To Move Project
|
||||
else
|
||||
if hasOwner
|
||||
p.text-center.row-spaced-small
|
||||
| #[strong(ng-non-bindable) #{name}] has not yet been moved into
|
||||
| the new version of Overleaf. You will need to move it in order
|
||||
| to continue working on it. It should only take a few seconds.
|
||||
|
||||
form(
|
||||
async-form="v2Import"
|
||||
name="v2ImportForm"
|
||||
action="/overleaf/project/"+ projectId + "/import"
|
||||
method="POST"
|
||||
ng-cloak
|
||||
)
|
||||
input(name='_csrf', type='hidden', value=csrfToken)
|
||||
form-messages(for="v2ImportForm")
|
||||
input.row-spaced.btn.btn-primary.text-center.center-block(
|
||||
type="submit"
|
||||
ng-value="v2ImportForm.inflight || v2ImportForm.response.success ? 'Moving to v2...' : 'Move Project and Continue'"
|
||||
ng-disabled="v2ImportForm.inflight || v2ImportForm.response.success"
|
||||
)
|
||||
|
||||
.row-spaced-small.text-center
|
||||
a(href="/overleaf/project/" + projectId + "/download/zip") Download project zip file
|
||||
else
|
||||
p.text-center.row-spaced.small
|
||||
| #[strong(ng-non-bindable) #{name}] has not yet been moved into
|
||||
| the new version of Overleaf. This project was created
|
||||
| anonymously and therefore cannot be automatically imported.
|
||||
| Please download a zip file of the project and upload that to
|
||||
| continue editing it. If you would like to delete this project
|
||||
| after you have made a copy, please contact support.
|
||||
|
||||
.row-spaced.text-center
|
||||
a.btn.btn-primary(href="/overleaf/project/" + projectId + "/download/zip") Download project zip file
|
||||
|
||||
.row-spaced.text-center
|
||||
if hasAssignment
|
||||
p
|
||||
| #[span.fa.fa-exclamation-triangle]
|
||||
| This project is an assignment, and the assignment toolkit is
|
||||
| no longer supported in Overleaf v2. When you move it to
|
||||
| Overleaf v2, it will become a normal project.
|
||||
| #[a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ#assignment-tools") Please see our FAQ]
|
||||
| for more information or contact your instructor if you haven't
|
||||
| already submitted it.
|
||||
else if brandInfo == 'f1000'
|
||||
p
|
||||
| #[span.fa.fa-exclamation-triangle]
|
||||
| This project is an F1000Research article, and our integration
|
||||
| with F1000Research has changed in Overleaf v2.
|
||||
| #[a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ#f1000research") Find out more about moving to Overleaf v2]
|
||||
else if brandInfo == 'wellcome'
|
||||
p
|
||||
| #[span.fa.fa-exclamation-triangle]
|
||||
| This project is an Wellcome Open Research article, and our
|
||||
| integration with Wellcome Open Research has changed in
|
||||
| Overleaf v2.
|
||||
| #[a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ#f1000research") Find out more about moving to Overleaf v2]
|
||||
else
|
||||
a(href="https://www.overleaf.com/learn/how-to/Overleaf_v2_FAQ")
|
||||
| Find out more about moving to Overleaf v2
|
||||
@@ -29,6 +29,7 @@ services:
|
||||
ENABLED_LINKED_FILE_TYPES: 'url,project_file,project_output_file,mendeley,zotero'
|
||||
MOCHA_GREP: ${MOCHA_GREP}
|
||||
NODE_ENV: test
|
||||
# SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true'
|
||||
SHARELATEX_CONFIG:
|
||||
command: npm run test:acceptance:app
|
||||
depends_on:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define([
|
||||
'main/token-access',
|
||||
'main/project-list/index',
|
||||
'main/account-settings',
|
||||
'main/clear-sessions',
|
||||
|
||||
@@ -865,19 +865,7 @@ define(['base', 'main/project-list/services/project-list'], function(App) {
|
||||
ProjectListService
|
||||
) {
|
||||
$scope.projectLink = function(project) {
|
||||
if (
|
||||
project.accessLevel === 'readAndWrite' &&
|
||||
project.source === 'token'
|
||||
) {
|
||||
return `/${project.tokens.readAndWrite}`
|
||||
} else if (
|
||||
project.accessLevel === 'readOnly' &&
|
||||
project.source === 'token'
|
||||
) {
|
||||
return `/read/${project.tokens.readOnly}`
|
||||
} else {
|
||||
return `/project/${project.id}`
|
||||
}
|
||||
return `/project/${project.id}`
|
||||
}
|
||||
|
||||
$scope.isLinkSharingProject = project => project.source === 'token'
|
||||
|
||||
83
services/web/frontend/js/main/token-access.js
Normal file
83
services/web/frontend/js/main/token-access.js
Normal file
@@ -0,0 +1,83 @@
|
||||
define(['base'], App => {
|
||||
App.controller(
|
||||
'TokenAccessPageController',
|
||||
($scope, $http, $location, localStorage) => {
|
||||
window.S = $scope
|
||||
$scope.mode = 'accessAttempt' // 'accessAttempt' | 'v1Import'
|
||||
|
||||
$scope.v1ImportData = null
|
||||
|
||||
$scope.accessInFlight = false
|
||||
$scope.accessSuccess = false
|
||||
$scope.accessError = false
|
||||
|
||||
$scope.currentPath = () => {
|
||||
return $location.path()
|
||||
}
|
||||
|
||||
$scope.buildFormImportPath = projectId => {
|
||||
return `/overleaf/project/${projectId}/import`
|
||||
}
|
||||
|
||||
$scope.buildZipDownloadPath = projectId => {
|
||||
return `/overleaf/project/${projectId}/download/zip`
|
||||
}
|
||||
|
||||
$scope.getProjectName = () => {
|
||||
if (!$scope.v1ImportData || !$scope.v1ImportData.name) {
|
||||
return 'This project'
|
||||
} else {
|
||||
return $scope.v1ImportData.name
|
||||
}
|
||||
}
|
||||
|
||||
$scope.post = () => {
|
||||
$scope.mode = 'accessAttempt'
|
||||
const textData = $('#overleaf-token-access-data').text()
|
||||
let parsedData = JSON.parse(textData)
|
||||
const { postUrl, csrfToken } = parsedData
|
||||
$scope.accessInFlight = true
|
||||
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: postUrl,
|
||||
data: {
|
||||
_csrf: csrfToken
|
||||
}
|
||||
}).then(
|
||||
function successCallback(response) {
|
||||
$scope.accessInFlight = false
|
||||
$scope.accessError = false
|
||||
const { data } = response
|
||||
if (data.redirect) {
|
||||
const redirect = response.data.redirect
|
||||
if (!redirect) {
|
||||
console.warn(
|
||||
'no redirect supplied in success response data',
|
||||
response
|
||||
)
|
||||
$scope.accessError = true
|
||||
return
|
||||
}
|
||||
window.location.replace(redirect)
|
||||
} else if (data.v1Import) {
|
||||
$scope.mode = 'v1Import'
|
||||
$scope.v1ImportData = data.v1Import
|
||||
} else {
|
||||
console.warn(
|
||||
'invalid data from server in success response',
|
||||
response
|
||||
)
|
||||
$scope.accessError = true
|
||||
}
|
||||
},
|
||||
function errorCallback(response) {
|
||||
console.warn('error response from server', response)
|
||||
$scope.accessInFlight = false
|
||||
$scope.accessError = response.status === 404 ? 'not_found' : 'error'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -6,7 +6,7 @@ const request = require('./helpers/request')
|
||||
const settings = require('settings-sharelatex')
|
||||
const { db, ObjectId } = require('../../../app/src/infrastructure/mongojs')
|
||||
|
||||
const tryReadAccess = (user, projectId, test, callback) =>
|
||||
const tryEditorAccess = (user, projectId, test, callback) =>
|
||||
async.series(
|
||||
[
|
||||
cb =>
|
||||
@@ -32,23 +32,69 @@ const tryReadAccess = (user, projectId, test, callback) =>
|
||||
callback
|
||||
)
|
||||
|
||||
const tryReadOnlyTokenAccess = (user, token, test, callback) =>
|
||||
user.request.get(`/read/${token}`, (error, response, body) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
test(response, body)
|
||||
callback()
|
||||
})
|
||||
const tryReadOnlyTokenAccess = (
|
||||
user,
|
||||
token,
|
||||
testPageLoad,
|
||||
testFormPost,
|
||||
callback
|
||||
) => {
|
||||
_doTryTokenAccess(
|
||||
`/read/${token}`,
|
||||
user,
|
||||
token,
|
||||
testPageLoad,
|
||||
testFormPost,
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
const tryReadAndWriteTokenAccess = (user, token, test, callback) =>
|
||||
user.request.get(`/${token}`, (error, response, body) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
const tryReadAndWriteTokenAccess = (
|
||||
user,
|
||||
token,
|
||||
testPageLoad,
|
||||
testFormPost,
|
||||
callback
|
||||
) => {
|
||||
_doTryTokenAccess(
|
||||
`/${token}`,
|
||||
user,
|
||||
token,
|
||||
testPageLoad,
|
||||
testFormPost,
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
const _doTryTokenAccess = (
|
||||
url,
|
||||
user,
|
||||
token,
|
||||
testPageLoad,
|
||||
testFormPost,
|
||||
callback
|
||||
) => {
|
||||
user.request.get(url, (err, response, body) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
test(response, body)
|
||||
callback()
|
||||
testPageLoad(response, body)
|
||||
if (!testFormPost) {
|
||||
return callback()
|
||||
}
|
||||
user.request.post(
|
||||
`${url}/grant`,
|
||||
{ json: { token } },
|
||||
(err, response, body) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
testFormPost(response, body)
|
||||
callback()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const tryContentAccess = (user, projcetId, test, callback) => {
|
||||
// The real-time service calls this end point to determine the user's
|
||||
@@ -122,8 +168,16 @@ describe('TokenAccess', function() {
|
||||
this.other1 = new User()
|
||||
this.other2 = new User()
|
||||
this.anon = new User()
|
||||
this.siteAdmin = new User({ email: 'admin@example.com' })
|
||||
async.parallel(
|
||||
[
|
||||
cb =>
|
||||
this.siteAdmin.login(err => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
this.siteAdmin.ensureAdmin(cb)
|
||||
}),
|
||||
cb => this.owner.login(cb),
|
||||
cb => this.other1.login(cb),
|
||||
cb => this.other2.login(cb),
|
||||
@@ -153,7 +207,7 @@ describe('TokenAccess', function() {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -210,7 +264,7 @@ describe('TokenAccess', function() {
|
||||
[
|
||||
cb => {
|
||||
// deny access before token is used
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -228,6 +282,11 @@ describe('TokenAccess', function() {
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.redirect).to.equal(`/project/${this.projectId}`)
|
||||
expect(body.tokenAccessGranted).to.equal('readOnly')
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
@@ -248,6 +307,51 @@ describe('TokenAccess', function() {
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
cb => {
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
cb
|
||||
)
|
||||
}
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should redirect the admin to the project (with rw access)', function(done) {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
// use token
|
||||
tryReadOnlyTokenAccess(
|
||||
this.siteAdmin,
|
||||
this.tokens.readOnly,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.redirect).to.equal(`/project/${this.projectId}`)
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
cb => {
|
||||
// allow content access read-and-write
|
||||
tryContentAccess(
|
||||
this.siteAdmin,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(body.privilegeLevel).to.equal('owner')
|
||||
expect(body.isRestrictedUser).to.equal(false)
|
||||
},
|
||||
cb
|
||||
)
|
||||
}
|
||||
],
|
||||
done
|
||||
@@ -264,7 +368,7 @@ describe('TokenAccess', function() {
|
||||
[
|
||||
// no access before token is used
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -273,15 +377,30 @@ describe('TokenAccess', function() {
|
||||
},
|
||||
cb
|
||||
),
|
||||
// token goes nowhere
|
||||
cb =>
|
||||
tryReadOnlyTokenAccess(
|
||||
this.other1,
|
||||
this.tokens.readOnly,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
),
|
||||
// still no access
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(body).to.match(/.*\/restricted.*/)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryContentAccess(
|
||||
this.other1,
|
||||
@@ -328,7 +447,7 @@ describe('TokenAccess', function() {
|
||||
async.series(
|
||||
[
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -344,6 +463,20 @@ describe('TokenAccess', function() {
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.redirect).to.equal(`/project/${this.projectId}`)
|
||||
expect(body.grantAnonymousAccess).to.equal('readOnly')
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
@@ -377,7 +510,7 @@ describe('TokenAccess', function() {
|
||||
async.series(
|
||||
[
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -391,11 +524,25 @@ describe('TokenAccess', function() {
|
||||
tryReadOnlyTokenAccess(
|
||||
this.anon,
|
||||
this.tokens.readOnly,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
),
|
||||
// still no access
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(body).to.match(/.*\/restricted.*/)
|
||||
},
|
||||
cb
|
||||
),
|
||||
// should not allow the user to join the project
|
||||
cb =>
|
||||
tryAnonContentAccess(
|
||||
@@ -445,7 +592,7 @@ describe('TokenAccess', function() {
|
||||
[
|
||||
// deny access before the token is used
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -462,6 +609,20 @@ describe('TokenAccess', function() {
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.redirect).to.equal(`/project/${this.projectId}`)
|
||||
expect(body.tokenAccessGranted).to.equal('readAndWrite')
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
@@ -487,6 +648,140 @@ describe('TokenAccess', function() {
|
||||
)
|
||||
})
|
||||
|
||||
describe('upgrading from a read-only token', function() {
|
||||
beforeEach(function(done) {
|
||||
this.owner.createProject(
|
||||
`token-rw-upgrade-test${Math.random()}`,
|
||||
(err, projectId) => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
this.projectId = projectId
|
||||
this.owner.makeTokenBased(this.projectId, err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
this.owner.getProject(this.projectId, (err, project) => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
this.tokens = project.tokens
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should allow user to access project via read-only, then upgrade to read-write', function(done) {
|
||||
async.series(
|
||||
[
|
||||
// deny access before the token is used
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(response.headers.location).to.match(/\/restricted.*/)
|
||||
expect(body).to.match(/.*\/restricted.*/)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb => {
|
||||
// use read-only token
|
||||
tryReadOnlyTokenAccess(
|
||||
this.other1,
|
||||
this.tokens.readOnly,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.redirect).to.equal(`/project/${this.projectId}`)
|
||||
expect(body.tokenAccessGranted).to.equal('readOnly')
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
cb => {
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
cb => {
|
||||
// allow content access read-only
|
||||
tryContentAccess(
|
||||
this.other1,
|
||||
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
|
||||
)
|
||||
},
|
||||
//
|
||||
// Then switch to read-write token
|
||||
//
|
||||
cb =>
|
||||
tryReadAndWriteTokenAccess(
|
||||
this.other1,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.redirect).to.equal(`/project/${this.projectId}`)
|
||||
expect(body.tokenAccessGranted).to.equal('readAndWrite')
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryContentAccess(
|
||||
this.other1,
|
||||
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
|
||||
)
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('made private again', function() {
|
||||
beforeEach(function(done) {
|
||||
this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
|
||||
@@ -496,7 +791,7 @@ describe('TokenAccess', function() {
|
||||
async.series(
|
||||
[
|
||||
cb => {
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -510,12 +805,26 @@ describe('TokenAccess', function() {
|
||||
tryReadAndWriteTokenAccess(
|
||||
this.other1,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
cb => {
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(body).to.match(/.*\/restricted.*/)
|
||||
},
|
||||
cb
|
||||
)
|
||||
},
|
||||
cb => {
|
||||
tryContentAccess(
|
||||
this.other1,
|
||||
@@ -564,7 +873,7 @@ describe('TokenAccess', function() {
|
||||
async.series(
|
||||
[
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -578,8 +887,14 @@ describe('TokenAccess', function() {
|
||||
this.anon,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(body).to.match(/.*\/restricted.*/)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({
|
||||
redirect: '/restricted',
|
||||
anonWriteAccessDenied: true
|
||||
})
|
||||
},
|
||||
cb
|
||||
),
|
||||
@@ -629,7 +944,7 @@ describe('TokenAccess', function() {
|
||||
async.series(
|
||||
[
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -645,6 +960,20 @@ describe('TokenAccess', function() {
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.redirect).to.equal(`/project/${this.projectId}`)
|
||||
expect(body.grantAnonymousAccess).to.equal('readAndWrite')
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
@@ -671,7 +1000,7 @@ describe('TokenAccess', function() {
|
||||
async.series(
|
||||
[
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -684,11 +1013,24 @@ describe('TokenAccess', function() {
|
||||
tryReadAndWriteTokenAccess(
|
||||
this.anon,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.anon,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(body).to.match(/.*\/restricted.*/)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryAnonContentAccess(
|
||||
this.anon,
|
||||
@@ -746,15 +1088,19 @@ describe('TokenAccess', function() {
|
||||
this.owner,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(response.headers.location).to.equal(
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.body.redirect).to.equal(
|
||||
`/project/${this.projectId}`
|
||||
)
|
||||
expect(response.body.higherAccess).to.equal(true)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.owner,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -826,10 +1172,14 @@ describe('TokenAccess', function() {
|
||||
this.other1,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(response.headers.location).to.equal(
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.body.redirect).to.equal(
|
||||
`/project/${this.projectId}`
|
||||
)
|
||||
expect(response.body.higherAccess).to.equal(true)
|
||||
},
|
||||
cb
|
||||
),
|
||||
@@ -839,16 +1189,20 @@ describe('TokenAccess', function() {
|
||||
this.other1,
|
||||
this.tokens.readOnly,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(response.headers.location).to.equal(
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.body.redirect).to.equal(
|
||||
`/project/${this.projectId}`
|
||||
)
|
||||
expect(response.body.higherAccess).to.equal(true)
|
||||
},
|
||||
cb
|
||||
),
|
||||
// should allow the user access to the project
|
||||
cb =>
|
||||
tryReadAccess(
|
||||
tryEditorAccess(
|
||||
this.other1,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
@@ -867,6 +1221,16 @@ describe('TokenAccess', function() {
|
||||
cb
|
||||
),
|
||||
// should not allow a different user to join the project
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.other2,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(302)
|
||||
expect(body).to.match(/.*\/restricted.*/)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryContentAccess(
|
||||
this.other2,
|
||||
@@ -893,24 +1257,32 @@ describe('TokenAccess', function() {
|
||||
})
|
||||
|
||||
it('should show error page for read and write token', function(done) {
|
||||
const unimportedV1Token = '123abc'
|
||||
const unimportedV1Token = '123abcdefabcdef'
|
||||
tryReadAndWriteTokenAccess(
|
||||
this.owner,
|
||||
unimportedV1Token,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(400)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({ v1Import: { status: 'cannotImport' } })
|
||||
},
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should show error page for read only token to v1', function(done) {
|
||||
const unimportedV1Token = 'abcd'
|
||||
const unimportedV1Token = 'aaaaaabbbbbb'
|
||||
tryReadOnlyTokenAccess(
|
||||
this.owner,
|
||||
unimportedV1Token,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(400)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({ v1Import: { status: 'cannotImport' } })
|
||||
},
|
||||
done
|
||||
)
|
||||
@@ -928,33 +1300,44 @@ describe('TokenAccess', function() {
|
||||
return done(err)
|
||||
}
|
||||
this.projectId = projectId
|
||||
this.owner.makeTokenBased(this.projectId, err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
db.projects.update(
|
||||
{ _id: ObjectId(projectId) },
|
||||
{ $set: { overleaf: { id: 1234 } } },
|
||||
err => {
|
||||
db.users.update(
|
||||
{ _id: ObjectId(this.owner._id.toString()) },
|
||||
{ $set: { 'overleaf.id': 321321 } },
|
||||
err => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
this.owner.makeTokenBased(this.projectId, err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
this.owner.getProject(this.projectId, (err, project) => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
db.projects.update(
|
||||
{ _id: ObjectId(projectId) },
|
||||
{ $set: { overleaf: { id: 1234 } } },
|
||||
err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
this.owner.getProject(this.projectId, (err, project) => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
this.tokens = project.tokens
|
||||
const docInfo = {
|
||||
exists: true,
|
||||
exported: false,
|
||||
has_owner: true,
|
||||
name: 'Test Project Import Example'
|
||||
}
|
||||
MockV1Api.setDocInfo(this.tokens.readAndWrite, docInfo)
|
||||
MockV1Api.setDocInfo(this.tokens.readOnly, docInfo)
|
||||
db.projects.remove({ _id: ObjectId(projectId) }, done)
|
||||
})
|
||||
}
|
||||
this.tokens = project.tokens
|
||||
MockV1Api.setDocExported(this.tokens.readAndWrite, {
|
||||
exporting: true
|
||||
})
|
||||
MockV1Api.setDocExported(this.tokens.readOnly, {
|
||||
exporting: true
|
||||
})
|
||||
done()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -973,7 +1356,17 @@ describe('TokenAccess', function() {
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.include('ImportingController')
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({
|
||||
v1Import: {
|
||||
status: 'canImport',
|
||||
projectId: this.tokens.readAndWrite,
|
||||
hasOwner: true,
|
||||
name: 'Test Project Import Example'
|
||||
}
|
||||
})
|
||||
},
|
||||
cb
|
||||
),
|
||||
@@ -983,7 +1376,35 @@ describe('TokenAccess', function() {
|
||||
this.tokens.readOnly,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.include('ImportingController')
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({
|
||||
v1Import: {
|
||||
status: 'canImport',
|
||||
projectId: this.tokens.readOnly,
|
||||
hasOwner: true,
|
||||
name: 'Test Project Import Example'
|
||||
}
|
||||
})
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.owner,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryContentAccess(
|
||||
this.other2,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
)
|
||||
@@ -992,19 +1413,60 @@ describe('TokenAccess', function() {
|
||||
)
|
||||
})
|
||||
|
||||
describe('when importing check not configured', function() {
|
||||
beforeEach(function() {
|
||||
delete settings.projectImportingCheckMaxCreateDelta
|
||||
describe('when the v1 doc does not exist', function(done) {
|
||||
beforeEach(function(done) {
|
||||
const docInfo = null
|
||||
MockV1Api.setDocInfo(this.tokens.readAndWrite, docInfo)
|
||||
MockV1Api.setDocInfo(this.tokens.readOnly, docInfo)
|
||||
done()
|
||||
})
|
||||
|
||||
it('should load editor', function(done) {
|
||||
tryReadAndWriteTokenAccess(
|
||||
this.owner,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.include('IdeController')
|
||||
},
|
||||
it('should get a 404 response on the post endpoint', function(done) {
|
||||
async.series(
|
||||
[
|
||||
cb =>
|
||||
tryReadAndWriteTokenAccess(
|
||||
this.owner,
|
||||
this.tokens.readAndWrite,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryReadOnlyTokenAccess(
|
||||
this.owner,
|
||||
this.tokens.readOnly,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(200)
|
||||
},
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryEditorAccess(
|
||||
this.owner,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
),
|
||||
cb =>
|
||||
tryContentAccess(
|
||||
this.other2,
|
||||
this.projectId,
|
||||
(response, body) => {
|
||||
expect(response.statusCode).to.equal(404)
|
||||
},
|
||||
cb
|
||||
)
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
@@ -33,6 +33,16 @@ module.exports = MockV1Api = {
|
||||
return (this.users[id] = user)
|
||||
},
|
||||
|
||||
docInfo: {},
|
||||
|
||||
getDocInfo(token) {
|
||||
return this.docInfo[token] || null
|
||||
},
|
||||
|
||||
setDocInfo(token, info) {
|
||||
this.docInfo[token] = info
|
||||
},
|
||||
|
||||
exportId: null,
|
||||
|
||||
exportParams: null,
|
||||
@@ -241,10 +251,11 @@ module.exports = MockV1Api = {
|
||||
app.get(
|
||||
'/api/v1/sharelatex/users/:user_id/docs/:token/info',
|
||||
(req, res, next) => {
|
||||
return res.json({
|
||||
exists: true,
|
||||
const info = this.getDocInfo(req.params.token) || {
|
||||
exists: false,
|
||||
exported: false
|
||||
})
|
||||
}
|
||||
return res.json(info)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@ describe('AuthorizationManager', function() {
|
||||
},
|
||||
'../Errors/Errors': Errors,
|
||||
'../TokenAccess/TokenAccessHandler': (this.TokenAccessHandler = {
|
||||
isValidToken: sinon.stub().callsArgWith(2, null, false, false)
|
||||
validateTokenForAnonymousAccess: sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, false, false)
|
||||
}),
|
||||
'settings-sharelatex': { passwordStrengthOptions: {} }
|
||||
}
|
||||
@@ -160,7 +162,7 @@ describe('AuthorizationManager', function() {
|
||||
describe('with no user (anonymous)', function() {
|
||||
describe('when the token is not valid', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.isValidToken = sinon
|
||||
this.TokenAccessHandler.validateTokenForAnonymousAccess = sinon
|
||||
.stub()
|
||||
.withArgs(this.project_id, this.token)
|
||||
.yields(null, false, false)
|
||||
@@ -185,7 +187,7 @@ describe('AuthorizationManager', function() {
|
||||
})
|
||||
|
||||
it('should check if the token is valid', function() {
|
||||
return this.TokenAccessHandler.isValidToken
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess
|
||||
.calledWith(this.project_id, this.token)
|
||||
.should.equal(true)
|
||||
})
|
||||
@@ -198,90 +200,47 @@ describe('AuthorizationManager', function() {
|
||||
})
|
||||
|
||||
describe('when the token is valid for read-and-write', function() {
|
||||
describe('when read-write-sharing is not enabled', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = false
|
||||
this.TokenAccessHandler.isValidToken = sinon
|
||||
.stub()
|
||||
.withArgs(this.project_id, this.token)
|
||||
.yields(null, true, false)
|
||||
return this.AuthorizationManager.getPrivilegeLevelForProject(
|
||||
null,
|
||||
this.project_id,
|
||||
this.token,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
|
||||
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should not call AuthorizationManager.isUserSiteAdmin', function() {
|
||||
return this.AuthorizationManager.isUserSiteAdmin.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should check if the token is valid', function() {
|
||||
return this.TokenAccessHandler.isValidToken
|
||||
.calledWith(this.project_id, this.token)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should deny access', function() {
|
||||
return this.callback
|
||||
.calledWith(null, false, false, false)
|
||||
.should.equal(true)
|
||||
})
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.validateTokenForAnonymousAccess = sinon
|
||||
.stub()
|
||||
.withArgs(this.project_id, this.token)
|
||||
.yields(null, true, false)
|
||||
return this.AuthorizationManager.getPrivilegeLevelForProject(
|
||||
null,
|
||||
this.project_id,
|
||||
this.token,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
describe('when read-write-sharing is enabled', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = true
|
||||
this.TokenAccessHandler.isValidToken = sinon
|
||||
.stub()
|
||||
.withArgs(this.project_id, this.token)
|
||||
.yields(null, true, false)
|
||||
return this.AuthorizationManager.getPrivilegeLevelForProject(
|
||||
null,
|
||||
this.project_id,
|
||||
this.token,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
|
||||
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function() {
|
||||
return this.CollaboratorsGetter.getMemberIdPrivilegeLevel.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
it('should not call AuthorizationManager.isUserSiteAdmin', function() {
|
||||
return this.AuthorizationManager.isUserSiteAdmin.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should not call AuthorizationManager.isUserSiteAdmin', function() {
|
||||
return this.AuthorizationManager.isUserSiteAdmin.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
it('should check if the token is valid', function() {
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess
|
||||
.calledWith(this.project_id, this.token)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should check if the token is valid', function() {
|
||||
return this.TokenAccessHandler.isValidToken
|
||||
.calledWith(this.project_id, this.token)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should give read-write access', function() {
|
||||
return this.callback
|
||||
.calledWith(null, 'readAndWrite', false)
|
||||
.should.equal(true)
|
||||
})
|
||||
it('should give read-write access', function() {
|
||||
return this.callback
|
||||
.calledWith(null, 'readAndWrite', false)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the token is valid for read-only', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.isValidToken = sinon
|
||||
this.TokenAccessHandler.validateTokenForAnonymousAccess = sinon
|
||||
.stub()
|
||||
.withArgs(this.project_id, this.token)
|
||||
.yields(null, false, true)
|
||||
@@ -306,7 +265,7 @@ describe('AuthorizationManager', function() {
|
||||
})
|
||||
|
||||
it('should check if the token is valid', function() {
|
||||
return this.TokenAccessHandler.isValidToken
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess
|
||||
.calledWith(this.project_id, this.token)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
@@ -1323,11 +1323,6 @@ describe('ProjectController', function() {
|
||||
archived: false,
|
||||
trashed: false,
|
||||
owner_ref: 'defg',
|
||||
tokens: {
|
||||
readAndWrite: '1abcd',
|
||||
readAndWritePrefix: '1',
|
||||
readOnly: 'neiotsranteoia'
|
||||
},
|
||||
isV1Project: false
|
||||
})
|
||||
})
|
||||
@@ -1359,11 +1354,6 @@ describe('ProjectController', function() {
|
||||
archived: true,
|
||||
trashed: false,
|
||||
owner_ref: 'defg',
|
||||
tokens: {
|
||||
readAndWrite: '1abcd',
|
||||
readAndWritePrefix: '1',
|
||||
readOnly: 'neiotsranteoia'
|
||||
},
|
||||
isV1Project: false
|
||||
})
|
||||
})
|
||||
@@ -1390,11 +1380,6 @@ describe('ProjectController', function() {
|
||||
archived: false,
|
||||
trashed: false,
|
||||
owner_ref: null,
|
||||
tokens: {
|
||||
readAndWrite: '1abcd',
|
||||
readAndWritePrefix: '1',
|
||||
readOnly: 'neiotsranteoia'
|
||||
},
|
||||
isV1Project: false
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,11 +25,12 @@ const { ObjectId } = require('mongojs')
|
||||
|
||||
describe('TokenAccessHandler', function() {
|
||||
beforeEach(function() {
|
||||
this.token = 'sometokenthing'
|
||||
this.token = 'abcdefabcdef'
|
||||
this.projectId = ObjectId()
|
||||
this.project = {
|
||||
_id: this.projectId,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
publicAccesLevel: 'tokenBased',
|
||||
owner_ref: ObjectId()
|
||||
}
|
||||
this.userId = ObjectId()
|
||||
this.req = {}
|
||||
@@ -44,414 +45,78 @@ describe('TokenAccessHandler', function() {
|
||||
'../User/UserGetter': (this.UserGetter = {}),
|
||||
'../V1/V1Api': (this.V1Api = {
|
||||
request: sinon.stub()
|
||||
})
|
||||
}),
|
||||
crypto: (this.Crypto = require('crypto'))
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
describe('findProjectWithReadOnlyToken', function() {
|
||||
beforeEach(function() {
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, this.project))
|
||||
})
|
||||
|
||||
it('should call Project.findOne', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(this.Project.findOne.callCount).to.equal(1)
|
||||
expect(
|
||||
this.Project.findOne.calledWith({
|
||||
'tokens.readOnly': this.token
|
||||
})
|
||||
).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should produce a project object with no error', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.exist
|
||||
expect(project).to.deep.equal(this.project)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return projectExists flag as true', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project, projectExists) => {
|
||||
expect(projectExists).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('when Project.findOne produces an error', function() {
|
||||
beforeEach(function() {
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, new Error('woops')))
|
||||
})
|
||||
|
||||
it('should produce an error', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.exist
|
||||
expect(project).to.not.exist
|
||||
expect(err).to.be.instanceof(Error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when project does not have tokenBased access level', function() {
|
||||
beforeEach(function() {
|
||||
this.project.publicAccesLevel = 'private'
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, this.project, true))
|
||||
})
|
||||
|
||||
it('should not return a project', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.not.exist
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return projectExists flag as true', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project, projectExists) => {
|
||||
expect(projectExists).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when project does not exist', function() {
|
||||
beforeEach(function() {
|
||||
return (this.Project.findOne = sinon.stub().callsArgWith(2, null, null))
|
||||
})
|
||||
|
||||
it('should not return a project', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.not.exist
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return projectExists flag as false', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadOnlyToken(
|
||||
this.token,
|
||||
(err, project, projectExists) => {
|
||||
expect(projectExists).to.equal(false)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('findProjectWithReadAndWriteToken', function() {
|
||||
beforeEach(function() {
|
||||
this.token = '1234bcdf'
|
||||
this.tokenPrefix = '1234'
|
||||
this.project.tokens = {
|
||||
readOnly: 'atntntn',
|
||||
readAndWrite: this.token,
|
||||
readAndWritePrefix: this.tokenPrefix
|
||||
describe('getTokenType', function() {
|
||||
it('should determine tokens correctly', function() {
|
||||
const specs = {
|
||||
abcdefabcdef: 'readOnly',
|
||||
aaaaaabbbbbb: 'readOnly',
|
||||
'54325aaaaaa': 'readAndWrite',
|
||||
'54325aaaaaabbbbbb': 'readAndWrite',
|
||||
'': null,
|
||||
abc123def: null
|
||||
}
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, this.project))
|
||||
for (var 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 call Project.findOne', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadAndWriteToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(this.Project.findOne.callCount).to.equal(1)
|
||||
expect(
|
||||
this.Project.findOne.calledWith({
|
||||
'tokens.readAndWritePrefix': this.tokenPrefix
|
||||
})
|
||||
).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should produce a project object with no error', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadAndWriteToken(
|
||||
it('should get the project', function(done) {
|
||||
this.TokenAccessHandler.getProjectByReadOnlyToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.exist
|
||||
expect(project).to.deep.equal(this.project)
|
||||
return done()
|
||||
expect(this.Project.findOne.callCount).to.equal(1)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return projectExists flag as true', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadAndWriteToken(
|
||||
this.token,
|
||||
(err, project, projectExists) => {
|
||||
expect(projectExists).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('when Project.findOne produces an error', function() {
|
||||
beforeEach(function() {
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, new Error('woops')))
|
||||
})
|
||||
|
||||
it('should produce an error', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadAndWriteToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.exist
|
||||
expect(project).to.not.exist
|
||||
expect(err).to.be.instanceof(Error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when project does not have tokenBased access level', function() {
|
||||
beforeEach(function() {
|
||||
this.project.publicAccesLevel = 'private'
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, this.project, true))
|
||||
})
|
||||
|
||||
it('should not return a project', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadAndWriteToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.not.exist
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return projectExists flag as true', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadAndWriteToken(
|
||||
this.token,
|
||||
(err, project, projectExists) => {
|
||||
expect(projectExists).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the tokens have different lengths', function() {
|
||||
beforeEach(function() {
|
||||
this.project.tokens = {
|
||||
readOnly: 'atntntn',
|
||||
readAndWrite: this.token + 'some-other-characters',
|
||||
readAndWritePrefix: this.tokenPrefix
|
||||
}
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, this.project))
|
||||
})
|
||||
|
||||
it('should not return a project', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithReadAndWriteToken(
|
||||
this.token,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.not.exist
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('findProjectWithHigherAccess', function() {
|
||||
describe('when user does have higher access', function() {
|
||||
beforeEach(function() {
|
||||
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
|
||||
return (this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, true))
|
||||
})
|
||||
|
||||
it('should call Project.findOne', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(this.Project.findOne.callCount).to.equal(1)
|
||||
expect(
|
||||
this.Project.findOne.calledWith({
|
||||
'tokens.readOnly': this.token
|
||||
})
|
||||
).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should call isUserInvitedMemberOfProject', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(
|
||||
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.CollaboratorsGetter.isUserInvitedMemberOfProject.calledWith(
|
||||
this.userId,
|
||||
this.project._id
|
||||
)
|
||||
).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should produce a project object', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.exist
|
||||
expect(project).to.deep.equal(this.project)
|
||||
return 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)
|
||||
})
|
||||
|
||||
describe('when user does not have higher access', function() {
|
||||
beforeEach(function() {
|
||||
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
|
||||
return (this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, false))
|
||||
})
|
||||
|
||||
it('should call Project.findOne', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(this.Project.findOne.callCount).to.equal(1)
|
||||
expect(
|
||||
this.Project.findOne.calledWith({
|
||||
'tokens.readOnly': this.token
|
||||
})
|
||||
).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should call isUserInvitedMemberOfProject', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(
|
||||
this.CollaboratorsGetter.isUserInvitedMemberOfProject.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.CollaboratorsGetter.isUserInvitedMemberOfProject.calledWith(
|
||||
this.userId,
|
||||
this.project._id
|
||||
)
|
||||
).to.equal(true)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should not produce a project', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(err).to.not.exist
|
||||
expect(project).to.not.exist
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
afterEach(function() {
|
||||
this.Crypto.timingSafeEqual.restore()
|
||||
})
|
||||
|
||||
describe('when Project.findOne produces an error', function() {
|
||||
beforeEach(function() {
|
||||
return (this.Project.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, new Error('woops')))
|
||||
})
|
||||
|
||||
it('should produce an error', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(err).to.exist
|
||||
expect(project).to.not.exist
|
||||
expect(err).to.be.instanceof(Error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when isUserInvitedMemberOfProject produces an error', function() {
|
||||
beforeEach(function() {
|
||||
this.Project.findOne = sinon.stub().callsArgWith(2, null, this.project)
|
||||
return (this.CollaboratorsGetter.isUserInvitedMemberOfProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, new Error('woops')))
|
||||
})
|
||||
|
||||
it('should produce an error', function(done) {
|
||||
return this.TokenAccessHandler.findProjectWithHigherAccess(
|
||||
this.token,
|
||||
this.userId,
|
||||
(err, project) => {
|
||||
expect(err).to.exist
|
||||
expect(project).to.not.exist
|
||||
expect(err).to.be.instanceof(Error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
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()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -583,27 +248,22 @@ describe('TokenAccessHandler', function() {
|
||||
})
|
||||
})
|
||||
|
||||
describe('isValidToken', function() {
|
||||
describe('validateTokenForAnonymousAccess', function() {
|
||||
describe('when a read-only project is found', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken = sinon
|
||||
this.TokenAccessHandler.getTokenType = sinon.stub().returns('readOnly')
|
||||
this.TokenAccessHandler.getProjectByToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null)
|
||||
return (this.TokenAccessHandler.findProjectWithReadOnlyToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.project))
|
||||
.callsArgWith(2, null, this.project)
|
||||
})
|
||||
|
||||
it('should try to find projects with both kinds of token', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
this.TokenAccessHandler.getProjectByToken.callCount
|
||||
).to.equal(1)
|
||||
return done()
|
||||
}
|
||||
@@ -611,7 +271,7 @@ describe('TokenAccessHandler', function() {
|
||||
})
|
||||
|
||||
it('should allow read-only access', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, rw, ro) => {
|
||||
@@ -626,64 +286,93 @@ describe('TokenAccessHandler', function() {
|
||||
|
||||
describe('when a read-and-write project is found', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken = sinon
|
||||
this.TokenAccessHandler.getTokenType = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.project)
|
||||
return (this.TokenAccessHandler.findProjectWithReadOnlyToken = sinon
|
||||
.returns('readAndWrite')
|
||||
this.TokenAccessHandler.getProjectByToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null))
|
||||
.callsArgWith(2, null, this.project)
|
||||
})
|
||||
|
||||
it('should try to find projects with both kinds of token', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
).to.equal(1)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
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()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow read-and-write access', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
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 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.findProjectWithReadAndWriteToken = sinon
|
||||
this.TokenAccessHandler.getProjectByToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null)
|
||||
return (this.TokenAccessHandler.findProjectWithReadOnlyToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null))
|
||||
.callsArgWith(2, null, null, null)
|
||||
})
|
||||
|
||||
it('should try to find projects with both kinds of token', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
this.TokenAccessHandler.getProjectByToken.callCount
|
||||
).to.equal(1)
|
||||
return done()
|
||||
}
|
||||
@@ -691,7 +380,7 @@ describe('TokenAccessHandler', function() {
|
||||
})
|
||||
|
||||
it('should not allow any access', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, rw, ro) => {
|
||||
@@ -706,24 +395,18 @@ describe('TokenAccessHandler', function() {
|
||||
|
||||
describe('when findProject produces an error', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken = sinon
|
||||
this.TokenAccessHandler.getProjectByToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null)
|
||||
return (this.TokenAccessHandler.findProjectWithReadOnlyToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, new Error('woops')))
|
||||
.callsArgWith(2, new Error('woops'))
|
||||
})
|
||||
|
||||
it('should try to find projects with both kinds of token', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
this.TokenAccessHandler.getProjectByToken.callCount
|
||||
).to.equal(1)
|
||||
return done()
|
||||
}
|
||||
@@ -731,7 +414,7 @@ describe('TokenAccessHandler', function() {
|
||||
})
|
||||
|
||||
it('should produce an error and not allow access', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, rw, ro) => {
|
||||
@@ -752,33 +435,16 @@ describe('TokenAccessHandler', function() {
|
||||
|
||||
describe('for read-and-write project', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken = sinon
|
||||
this.TokenAccessHandler.getTokenType = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.project)
|
||||
return (this.TokenAccessHandler.findProjectWithReadOnlyToken = sinon
|
||||
.returns('readAndWrite')
|
||||
this.TokenAccessHandler.getProjectByToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null))
|
||||
})
|
||||
|
||||
it('should try to find projects with both kinds of token', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken
|
||||
.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
).to.equal(1)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
.callsArgWith(2, null, this.project)
|
||||
})
|
||||
|
||||
it('should not allow any access', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, rw, ro) => {
|
||||
@@ -793,33 +459,16 @@ describe('TokenAccessHandler', function() {
|
||||
|
||||
describe('for read-only project', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken = sinon
|
||||
this.TokenAccessHandler.getTokenType = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null)
|
||||
return (this.TokenAccessHandler.findProjectWithReadOnlyToken = sinon
|
||||
.returns('readOnly')
|
||||
this.TokenAccessHandler.getProjectByToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.project))
|
||||
})
|
||||
|
||||
it('should try to find projects with both kinds of token', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken
|
||||
.callCount
|
||||
).to.equal(1)
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
).to.equal(1)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
.callsArgWith(2, null, this.project)
|
||||
})
|
||||
|
||||
it('should not allow any access', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||
this.projectId,
|
||||
this.token,
|
||||
(err, rw, ro) => {
|
||||
@@ -831,58 +480,26 @@ describe('TokenAccessHandler', function() {
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with nothing', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.project)
|
||||
return (this.TokenAccessHandler.findProjectWithReadOnlyToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null))
|
||||
})
|
||||
describe('with nothing', function() {
|
||||
beforeEach(function() {
|
||||
this.TokenAccessHandler.getProjectByToken = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null, null)
|
||||
})
|
||||
|
||||
it('should not call findProjectWithReadOnlyToken', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
this.projectId,
|
||||
null,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
).to.equal(0)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should try to find projects with both kinds of token', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
this.projectId,
|
||||
null,
|
||||
(err, allowed) => {
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadAndWriteToken.callCount
|
||||
).to.equal(0)
|
||||
expect(
|
||||
this.TokenAccessHandler.findProjectWithReadOnlyToken.callCount
|
||||
).to.equal(0)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should not allow any access', function(done) {
|
||||
return this.TokenAccessHandler.isValidToken(
|
||||
this.projectId,
|
||||
null,
|
||||
(err, rw, ro) => {
|
||||
expect(err).to.not.exist
|
||||
expect(rw).to.equal(false)
|
||||
expect(ro).to.equal(false)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
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()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user