2019-05-29 05:21:06 -04:00
|
|
|
const { Project } = require('../../models/Project')
|
|
|
|
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
|
|
|
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
|
|
|
|
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
|
|
|
const UserGetter = require('../User/UserGetter')
|
|
|
|
const { ObjectId } = require('mongojs')
|
|
|
|
const Settings = require('settings-sharelatex')
|
|
|
|
const logger = require('logger-sharelatex')
|
|
|
|
const V1Api = require('../V1/V1Api')
|
|
|
|
const crypto = require('crypto')
|
|
|
|
|
2019-07-11 09:00:54 -04:00
|
|
|
const TokenAccessHandler = {
|
2019-05-29 05:21:06 -04:00
|
|
|
ANONYMOUS_READ_AND_WRITE_ENABLED:
|
|
|
|
Settings.allowAnonymousReadAndWriteSharing === true,
|
2019-07-11 09:00:54 -04:00
|
|
|
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})$/,
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
_extractNumericPrefix(token) {
|
|
|
|
return token.match(/^(\d+)\w+/)
|
|
|
|
},
|
|
|
|
|
|
|
|
_getProjectByReadOnlyToken(token, callback) {
|
2019-07-11 09:00:54 -04:00
|
|
|
Project.findOne(
|
2019-05-29 05:21:06 -04:00
|
|
|
{
|
|
|
|
'tokens.readOnly': token
|
|
|
|
},
|
|
|
|
{ _id: 1, tokens: 1, publicAccesLevel: 1, owner_ref: 1 },
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
_getProjectByEitherToken(token, callback) {
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler._getProjectByReadOnlyToken(token, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
project
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
if (project != null) {
|
|
|
|
return callback(null, project)
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler._getProjectByReadAndWriteToken(token, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
project
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
callback(null, project)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
_getProjectByReadAndWriteToken(token, callback) {
|
|
|
|
const numericPrefixMatch = TokenAccessHandler._extractNumericPrefix(token)
|
|
|
|
if (!numericPrefixMatch) {
|
|
|
|
return callback(null, null)
|
|
|
|
}
|
|
|
|
const numerics = numericPrefixMatch[1]
|
2019-07-11 09:00:54 -04:00
|
|
|
Project.findOne(
|
2019-05-29 05:21:06 -04:00
|
|
|
{
|
|
|
|
'tokens.readAndWritePrefix': numerics
|
|
|
|
},
|
|
|
|
{ _id: 1, tokens: 1, publicAccesLevel: 1, owner_ref: 1 },
|
|
|
|
function(err, project) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
if (project == null) {
|
|
|
|
return callback(null, null)
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
if (
|
|
|
|
!crypto.timingSafeEqual(
|
2019-07-11 09:00:54 -04:00
|
|
|
Buffer.from(token),
|
|
|
|
Buffer.from(project.tokens.readAndWrite)
|
2019-05-29 05:21:06 -04:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
logger.err(
|
|
|
|
{ token },
|
|
|
|
'read-and-write token match on numeric section, but not on full token'
|
|
|
|
)
|
|
|
|
return callback(null, null)
|
|
|
|
} else {
|
|
|
|
return callback(null, project)
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
err = error
|
|
|
|
logger.err({ token, cryptoErr: err }, 'error comparing tokens')
|
|
|
|
return callback(null, null)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
findProjectWithReadOnlyToken(token, callback) {
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler._getProjectByReadOnlyToken(token, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
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
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
callback(null, project, true)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
findProjectWithReadAndWriteToken(token, callback) {
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler._getProjectByReadAndWriteToken(token, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
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
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
callback(null, project, true)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
_userIsMember(userId, projectId, callback) {
|
2019-07-11 09:00:54 -04:00
|
|
|
CollaboratorsHandler.isUserInvitedMemberOfProject(
|
2019-05-29 05:21:06 -04:00
|
|
|
userId,
|
|
|
|
projectId,
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
findProjectWithHigherAccess(token, userId, callback) {
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler._getProjectByEitherToken(token, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
project
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
if (project == null) {
|
|
|
|
return callback(null, null)
|
|
|
|
}
|
|
|
|
const projectId = project._id
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler._userIsMember(userId, projectId, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
isMember
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
callback(null, isMember === true ? project : null)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
addReadOnlyUserToProject(userId, projectId, callback) {
|
|
|
|
userId = ObjectId(userId.toString())
|
|
|
|
projectId = ObjectId(projectId.toString())
|
2019-07-11 09:00:54 -04:00
|
|
|
Project.update(
|
2019-05-29 05:21:06 -04:00
|
|
|
{
|
|
|
|
_id: projectId
|
|
|
|
},
|
|
|
|
{
|
|
|
|
$addToSet: { tokenAccessReadOnly_refs: userId }
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
addReadAndWriteUserToProject(userId, projectId, callback) {
|
|
|
|
userId = ObjectId(userId.toString())
|
|
|
|
projectId = ObjectId(projectId.toString())
|
2019-07-11 09:00:54 -04:00
|
|
|
Project.update(
|
2019-05-29 05:21:06 -04:00
|
|
|
{
|
|
|
|
_id: projectId
|
|
|
|
},
|
|
|
|
{
|
|
|
|
$addToSet: { tokenAccessReadAndWrite_refs: userId }
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
grantSessionTokenAccess(req, projectId, token) {
|
2019-07-11 09:00:54 -04:00
|
|
|
if (!req.session) {
|
|
|
|
return
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
if (!req.session.anonTokenAccess) {
|
|
|
|
req.session.anonTokenAccess = {}
|
|
|
|
}
|
|
|
|
req.session.anonTokenAccess[
|
|
|
|
projectId.toString()
|
|
|
|
] = token.toString()
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
getRequestToken(req, projectId) {
|
2019-07-11 09:00:54 -04:00
|
|
|
const token = req.session && req.session.anonTokenAccess && req.session.anonTokenAccess[ projectId.toString() ] || req.headers['x-sl-anonymous-access-token']
|
2019-05-29 05:21:06 -04:00
|
|
|
return token
|
|
|
|
},
|
|
|
|
|
|
|
|
isValidToken(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()
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler.findProjectWithReadAndWriteToken(token, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
readAndWriteProject
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
const isValidReadAndWrite = _validate(readAndWriteProject)
|
2019-07-11 09:00:54 -04:00
|
|
|
TokenAccessHandler.findProjectWithReadOnlyToken(token, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
readOnlyProject
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
const isValidReadOnly = _validate(readOnlyProject)
|
2019-07-11 09:00:54 -04:00
|
|
|
callback(null, isValidReadAndWrite, isValidReadOnly)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
protectTokens(project, privilegeLevel) {
|
2019-07-11 09:00:54 -04:00
|
|
|
if (!project || !project.tokens) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (privilegeLevel === PrivilegeLevels.OWNER) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (privilegeLevel !== PrivilegeLevels.READ_AND_WRITE) {
|
|
|
|
project.tokens.readAndWrite = ''
|
|
|
|
project.tokens.readAndWritePrefix = ''
|
|
|
|
}
|
|
|
|
if (privilegeLevel !== PrivilegeLevels.READ_ONLY) {
|
|
|
|
project.tokens.readOnly = ''
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
getV1DocPublishedInfo(token, callback) {
|
|
|
|
// default to allowing access
|
2019-07-11 09:00:54 -04:00
|
|
|
if (!Settings.apis || !Settings.apis.v1) {
|
|
|
|
return callback(null, { allow: true })
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
V1Api.request(
|
2019-05-29 05:21:06 -04:00
|
|
|
{ url: `/api/v1/sharelatex/docs/${token}/is_published` },
|
|
|
|
function(err, response, body) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
callback(null, body)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
getV1DocInfo(token, v2UserId, callback) {
|
2019-07-11 09:00:54 -04:00
|
|
|
if (!Settings.apis || !Settings.apis.v1) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(null, {
|
|
|
|
exists: true,
|
|
|
|
exported: false
|
|
|
|
})
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
UserGetter.getUser(v2UserId, { overleaf: 1 }, function(err, user) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
const v1UserId = user.overleaf != null ? user.overleaf.id : undefined
|
2019-07-11 09:00:54 -04:00
|
|
|
V1Api.request(
|
2019-05-29 05:21:06 -04:00
|
|
|
{ url: `/api/v1/sharelatex/users/${v1UserId}/docs/${token}/info` },
|
|
|
|
function(err, response, body) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2019-07-11 09:00:54 -04:00
|
|
|
callback(null, body)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 09:00:54 -04:00
|
|
|
module.exports = TokenAccessHandler
|