| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- 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')
- const { ObjectId } = require('mongojs')
- const Settings = require('settings-sharelatex')
- const logger = require('logger-sharelatex')
- const V1Api = require('../V1/V1Api')
- const crypto = require('crypto')
- const TokenAccessHandler = {
- 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})$/,
- _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
- )
- },
- _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) {
- const numericPrefixMatch = TokenAccessHandler._extractNumericPrefix(token)
- if (!numericPrefixMatch) {
- return callback(null, null)
- }
- const numerics = numericPrefixMatch[1]
- Project.findOne(
- {
- '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(
- Buffer.from(token),
- Buffer.from(project.tokens.readAndWrite)
- )
- ) {
- 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) {
- 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)
- })
- })
- },
- addReadOnlyUserToProject(userId, projectId, callback) {
- userId = ObjectId(userId.toString())
- projectId = ObjectId(projectId.toString())
- Project.update(
- {
- _id: projectId
- },
- {
- $addToSet: { tokenAccessReadOnly_refs: userId }
- },
- callback
- )
- },
- addReadAndWriteUserToProject(userId, projectId, callback) {
- userId = ObjectId(userId.toString())
- projectId = ObjectId(projectId.toString())
- Project.update(
- {
- _id: projectId
- },
- {
- $addToSet: { tokenAccessReadAndWrite_refs: userId }
- },
- callback
- )
- },
- grantSessionTokenAccess(req, projectId, token) {
- if (!req.session) {
- return
- }
- if (!req.session.anonTokenAccess) {
- req.session.anonTokenAccess = {}
- }
- req.session.anonTokenAccess[projectId.toString()] = token.toString()
- },
- getRequestToken(req, projectId) {
- const token =
- (req.session &&
- req.session.anonTokenAccess &&
- req.session.anonTokenAccess[projectId.toString()]) ||
- req.headers['x-sl-anonymous-access-token']
- 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()
- TokenAccessHandler.findProjectWithReadAndWriteToken(token, function(
- err,
- readAndWriteProject
- ) {
- if (err != null) {
- return callback(err)
- }
- const isValidReadAndWrite = _validate(readAndWriteProject)
- TokenAccessHandler.findProjectWithReadOnlyToken(token, function(
- err,
- readOnlyProject
- ) {
- if (err != null) {
- return callback(err)
- }
- const isValidReadOnly = _validate(readOnlyProject)
- callback(null, isValidReadAndWrite, isValidReadOnly)
- })
- })
- },
- protectTokens(project, privilegeLevel) {
- 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 = ''
- }
- },
- getV1DocPublishedInfo(token, callback) {
- // default to allowing access
- if (!Settings.apis.v1 || !Settings.apis.v1.url) {
- return callback(null, { allow: true })
- }
- V1Api.request(
- { url: `/api/v1/sharelatex/docs/${token}/is_published` },
- function(err, response, body) {
- if (err != null) {
- return callback(err)
- }
- callback(null, body)
- }
- )
- },
- getV1DocInfo(token, v2UserId, callback) {
- if (!Settings.apis || !Settings.apis.v1) {
- return callback(null, {
- exists: true,
- exported: false
- })
- }
- UserGetter.getUser(v2UserId, { overleaf: 1 }, function(err, user) {
- if (err != null) {
- return callback(err)
- }
- const v1UserId = user.overleaf != null ? user.overleaf.id : undefined
- if (!v1UserId) {
- return callback(null, null)
- }
- V1Api.request(
- { url: `/api/v1/sharelatex/users/${v1UserId}/docs/${token}/info` },
- function(err, response, body) {
- if (err != null) {
- return callback(err)
- }
- callback(null, body)
- }
- )
- })
- }
- }
- module.exports = TokenAccessHandler
|