TokenAccessHandler.coffee 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. Project = require('../../models/Project').Project
  2. CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
  3. PublicAccessLevels = require '../Authorization/PublicAccessLevels'
  4. PrivilegeLevels = require '../Authorization/PrivilegeLevels'
  5. UserGetter = require '../User/UserGetter'
  6. ObjectId = require("mongojs").ObjectId
  7. Settings = require('settings-sharelatex')
  8. logger = require('logger-sharelatex')
  9. V1Api = require "../V1/V1Api"
  10. crypto = require 'crypto'
  11. module.exports = TokenAccessHandler =
  12. ANONYMOUS_READ_AND_WRITE_ENABLED:
  13. Settings.allowAnonymousReadAndWriteSharing == true
  14. _extractNumericPrefix: (token) ->
  15. token.match(/^(\d+)\w+/)
  16. _getProjectByReadOnlyToken: (token, callback=(err, project)->) ->
  17. Project.findOne {
  18. 'tokens.readOnly': token
  19. }, {_id: 1, tokens: 1, publicAccesLevel: 1, owner_ref: 1}, callback
  20. _getProjectByEitherToken: (token, callback=(err, project)->) ->
  21. TokenAccessHandler._getProjectByReadOnlyToken token, (err, project) ->
  22. return callback(err) if err?
  23. if project?
  24. return callback(null, project)
  25. TokenAccessHandler._getProjectByReadAndWriteToken token, (err, project) ->
  26. return callback(err) if err?
  27. callback(null, project)
  28. _getProjectByReadAndWriteToken: (token, callback=(err, project)->) ->
  29. numericPrefixMatch = TokenAccessHandler._extractNumericPrefix(token)
  30. if !numericPrefixMatch
  31. return callback(null, null)
  32. numerics = numericPrefixMatch[1]
  33. Project.findOne {
  34. 'tokens.readAndWritePrefix': numerics
  35. }, {_id: 1, tokens: 1, publicAccesLevel: 1, owner_ref: 1}, (err, project) ->
  36. return callback(err) if err?
  37. if !project?
  38. return callback(null, null)
  39. try
  40. if !crypto.timingSafeEqual(new Buffer(token), new Buffer(project.tokens.readAndWrite))
  41. logger.err {token}, "read-and-write token match on numeric section, but not on full token"
  42. return callback(null, null)
  43. else
  44. return callback(null, project)
  45. catch err
  46. logger.err {token, cryptoErr: err}, "error comparing tokens"
  47. return callback(null, null)
  48. findProjectWithReadOnlyToken: (token, callback=(err, project, projectExists)->) ->
  49. TokenAccessHandler._getProjectByReadOnlyToken token, (err, project) ->
  50. if err?
  51. return callback(err)
  52. if !project?
  53. return callback(null, null, false) # Project doesn't exist, so we handle differently
  54. if project.publicAccesLevel != PublicAccessLevels.TOKEN_BASED
  55. return callback(null, null, true) # Project does exist, but it isn't token based
  56. return callback(null, project, true)
  57. findProjectWithReadAndWriteToken: (token, callback=(err, project, projectExists)->) ->
  58. TokenAccessHandler._getProjectByReadAndWriteToken token, (err, project) ->
  59. if err?
  60. return callback(err)
  61. if !project?
  62. return callback(null, null, false) # Project doesn't exist, so we handle differently
  63. if project.publicAccesLevel != PublicAccessLevels.TOKEN_BASED
  64. return callback(null, null, true) # Project does exist, but it isn't token based
  65. return callback(null, project, true)
  66. _userIsMember: (userId, projectId, callback=(err, isMember)->) ->
  67. CollaboratorsHandler.isUserInvitedMemberOfProject userId, projectId, callback
  68. findProjectWithHigherAccess: (token, userId, callback=(err, project)->) ->
  69. TokenAccessHandler._getProjectByEitherToken token, (err, project) ->
  70. return callback(err) if err?
  71. if !project?
  72. return callback(null, null)
  73. projectId = project._id
  74. TokenAccessHandler._userIsMember userId, projectId, (err, isMember) ->
  75. return callback(err) if err?
  76. callback(
  77. null,
  78. if isMember == true then project else null
  79. )
  80. addReadOnlyUserToProject: (userId, projectId, callback=(err)->) ->
  81. userId = ObjectId(userId.toString())
  82. projectId = ObjectId(projectId.toString())
  83. Project.update {
  84. _id: projectId
  85. }, {
  86. $addToSet: {tokenAccessReadOnly_refs: userId}
  87. }, callback
  88. addReadAndWriteUserToProject: (userId, projectId, callback=(err)->) ->
  89. userId = ObjectId(userId.toString())
  90. projectId = ObjectId(projectId.toString())
  91. Project.update {
  92. _id: projectId
  93. }, {
  94. $addToSet: {tokenAccessReadAndWrite_refs: userId}
  95. }, callback
  96. grantSessionTokenAccess: (req, projectId, token) ->
  97. if req.session?
  98. if !req.session.anonTokenAccess?
  99. req.session.anonTokenAccess = {}
  100. req.session.anonTokenAccess[projectId.toString()] = token.toString()
  101. getRequestToken: (req, projectId) ->
  102. token = (
  103. req?.session?.anonTokenAccess?[projectId.toString()] or
  104. req?.headers['x-sl-anonymous-access-token']
  105. )
  106. return token
  107. isValidToken: (projectId, token, callback=(err, isValidReadAndWrite, isValidReadOnly)->) ->
  108. if !token
  109. return callback null, false, false
  110. _validate = (project) ->
  111. project? and
  112. project.publicAccesLevel == PublicAccessLevels.TOKEN_BASED and
  113. project._id.toString() == projectId.toString()
  114. TokenAccessHandler.findProjectWithReadAndWriteToken token, (err, readAndWriteProject) ->
  115. return callback(err) if err?
  116. isValidReadAndWrite = _validate(readAndWriteProject)
  117. TokenAccessHandler.findProjectWithReadOnlyToken token, (err, readOnlyProject) ->
  118. return callback(err) if err?
  119. isValidReadOnly = _validate(readOnlyProject)
  120. callback null, isValidReadAndWrite, isValidReadOnly
  121. protectTokens: (project, privilegeLevel) ->
  122. if project? && project.tokens?
  123. if privilegeLevel == PrivilegeLevels.OWNER
  124. return
  125. if privilegeLevel != PrivilegeLevels.READ_AND_WRITE
  126. project.tokens.readAndWrite = ''
  127. project.tokens.readAndWritePrefix = ''
  128. if privilegeLevel != PrivilegeLevels.READ_ONLY
  129. project.tokens.readOnly = ''
  130. getV1DocPublishedInfo: (token, callback = (err, publishedInfo) ->) ->
  131. # default to allowing access
  132. return callback(null, {
  133. allow: true
  134. }) unless Settings.apis?.v1?
  135. V1Api.request { url: "/api/v1/sharelatex/docs/#{token}/is_published" }, (err, response, body) ->
  136. return callback err if err?
  137. callback null, body
  138. getV1DocInfo: (token, v2UserId, callback=(err, info)->) ->
  139. # default to not exported
  140. return callback(null, {
  141. exists: true
  142. exported: false
  143. }) unless Settings.apis?.v1?
  144. UserGetter.getUser v2UserId, { overleaf: 1 }, (err, user) ->
  145. return callback(err) if err?
  146. v1UserId = user.overleaf?.id
  147. V1Api.request { url: "/api/v1/sharelatex/users/#{v1UserId}/docs/#{token}/info" }, (err, response, body) ->
  148. return callback err if err?
  149. callback null, body
  150. module.exports.READ_AND_WRITE_TOKEN_REGEX = /^(\d+)(\w+)$/
  151. module.exports.READ_AND_WRITE_URL_REGEX = /^\/(\d+)(\w+)$/
  152. module.exports.READ_ONLY_TOKEN_REGEX = /^([a-z]{12})$/
  153. module.exports.READ_ONLY_URL_REGEX = /^\/read\/([a-z]{12})$/