AuthorizationManager.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. const { callbackify } = require('util')
  2. const { ObjectId } = require('mongodb-legacy')
  3. const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
  4. const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
  5. const ProjectGetter = require('../Project/ProjectGetter')
  6. const { User } = require('../../models/User')
  7. const PrivilegeLevels = require('./PrivilegeLevels')
  8. const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
  9. const PublicAccessLevels = require('./PublicAccessLevels')
  10. const Errors = require('../Errors/Errors')
  11. const { hasAdminAccess } = require('../Helpers/AdminAuthorizationHelper')
  12. const Settings = require('@overleaf/settings')
  13. const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
  14. function isRestrictedUser(
  15. userId,
  16. privilegeLevel,
  17. isTokenMember,
  18. isInvitedMember
  19. ) {
  20. if (privilegeLevel === PrivilegeLevels.NONE) {
  21. return true
  22. }
  23. return (
  24. privilegeLevel === PrivilegeLevels.READ_ONLY &&
  25. (isTokenMember || !userId) &&
  26. !isInvitedMember
  27. )
  28. }
  29. async function isRestrictedUserForProject(userId, projectId, token) {
  30. const privilegeLevel = await getPrivilegeLevelForProject(
  31. userId,
  32. projectId,
  33. token
  34. )
  35. const isTokenMember = await CollaboratorsHandler.promises.userIsTokenMember(
  36. userId,
  37. projectId
  38. )
  39. const isInvitedMember =
  40. await CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
  41. userId,
  42. projectId
  43. )
  44. return isRestrictedUser(
  45. userId,
  46. privilegeLevel,
  47. isTokenMember,
  48. isInvitedMember
  49. )
  50. }
  51. async function getPublicAccessLevel(projectId) {
  52. if (!ObjectId.isValid(projectId)) {
  53. throw new Error('invalid project id')
  54. }
  55. // Note, the Project property in the DB is `publicAccesLevel`, without the second `s`
  56. const project = await ProjectGetter.promises.getProject(projectId, {
  57. publicAccesLevel: 1,
  58. })
  59. if (!project) {
  60. throw new Errors.NotFoundError(`no project found with id ${projectId}`)
  61. }
  62. return project.publicAccesLevel
  63. }
  64. /**
  65. * Get the privilege level that the user has for the project.
  66. *
  67. * @param userId - The id of the user that wants to access the project.
  68. * @param projectId - The id of the project to be accessed.
  69. * @param {string} token
  70. * @param {Object} opts
  71. * @param {boolean} opts.ignoreSiteAdmin - Do not consider whether the user is
  72. * a site admin.
  73. * @param {boolean} opts.ignorePublicAccess - Do not consider the project is
  74. * publicly accessible.
  75. *
  76. * @returns {string|boolean} The privilege level. One of "owner",
  77. * "readAndWrite", "readOnly" or false.
  78. */
  79. async function getPrivilegeLevelForProject(
  80. userId,
  81. projectId,
  82. token,
  83. opts = {}
  84. ) {
  85. if (userId) {
  86. return getPrivilegeLevelForProjectWithUser(userId, projectId, opts)
  87. } else {
  88. return getPrivilegeLevelForProjectWithoutUser(projectId, token, opts)
  89. }
  90. }
  91. // User is present, get their privilege level from database
  92. async function getPrivilegeLevelForProjectWithUser(
  93. userId,
  94. projectId,
  95. opts = {}
  96. ) {
  97. if (!opts.ignoreSiteAdmin) {
  98. if (await isUserSiteAdmin(userId)) {
  99. return PrivilegeLevels.OWNER
  100. }
  101. }
  102. const privilegeLevel =
  103. await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
  104. userId,
  105. projectId
  106. )
  107. if (privilegeLevel && privilegeLevel !== PrivilegeLevels.NONE) {
  108. // The user has direct access
  109. return privilegeLevel
  110. }
  111. if (!opts.ignorePublicAccess) {
  112. // Legacy public-access system
  113. // User is present (not anonymous), but does not have direct access
  114. const publicAccessLevel = await getPublicAccessLevel(projectId)
  115. if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
  116. return PrivilegeLevels.READ_ONLY
  117. }
  118. if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
  119. return PrivilegeLevels.READ_AND_WRITE
  120. }
  121. }
  122. return PrivilegeLevels.NONE
  123. }
  124. // User is Anonymous, Try Token-based access
  125. async function getPrivilegeLevelForProjectWithoutUser(
  126. projectId,
  127. token,
  128. opts = {}
  129. ) {
  130. const publicAccessLevel = await getPublicAccessLevel(projectId)
  131. if (!opts.ignorePublicAccess) {
  132. if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
  133. // Legacy public read-only access for anonymous user
  134. return PrivilegeLevels.READ_ONLY
  135. }
  136. if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
  137. // Legacy public read-write access for anonymous user
  138. return PrivilegeLevels.READ_AND_WRITE
  139. }
  140. }
  141. if (publicAccessLevel === PublicAccessLevels.TOKEN_BASED) {
  142. return getPrivilegeLevelForProjectWithToken(projectId, token)
  143. }
  144. // Deny anonymous user access
  145. return PrivilegeLevels.NONE
  146. }
  147. async function getPrivilegeLevelForProjectWithToken(projectId, token) {
  148. // Anonymous users can have read-only access to token-based projects,
  149. // while read-write access must be logged in,
  150. // unless the `enableAnonymousReadAndWriteSharing` setting is enabled
  151. const { isValidReadAndWrite, isValidReadOnly } =
  152. await TokenAccessHandler.promises.validateTokenForAnonymousAccess(
  153. projectId,
  154. token
  155. )
  156. if (isValidReadOnly) {
  157. // Grant anonymous user read-only access
  158. return PrivilegeLevels.READ_ONLY
  159. }
  160. if (isValidReadAndWrite) {
  161. // Grant anonymous user read-and-write access
  162. return PrivilegeLevels.READ_AND_WRITE
  163. }
  164. // Deny anonymous access
  165. return PrivilegeLevels.NONE
  166. }
  167. async function canUserReadProject(userId, projectId, token) {
  168. const privilegeLevel = await getPrivilegeLevelForProject(
  169. userId,
  170. projectId,
  171. token
  172. )
  173. return [
  174. PrivilegeLevels.OWNER,
  175. PrivilegeLevels.READ_AND_WRITE,
  176. PrivilegeLevels.READ_ONLY,
  177. PrivilegeLevels.REVIEW,
  178. ].includes(privilegeLevel)
  179. }
  180. async function canUserWriteProjectContent(userId, projectId, token) {
  181. const privilegeLevel = await getPrivilegeLevelForProject(
  182. userId,
  183. projectId,
  184. token
  185. )
  186. return [PrivilegeLevels.OWNER, PrivilegeLevels.READ_AND_WRITE].includes(
  187. privilegeLevel
  188. )
  189. }
  190. async function canUserWriteProjectSettings(userId, projectId, token) {
  191. const privilegeLevel = await getPrivilegeLevelForProject(
  192. userId,
  193. projectId,
  194. token,
  195. { ignorePublicAccess: true }
  196. )
  197. return [PrivilegeLevels.OWNER, PrivilegeLevels.READ_AND_WRITE].includes(
  198. privilegeLevel
  199. )
  200. }
  201. async function canUserRenameProject(userId, projectId, token) {
  202. const privilegeLevel = await getPrivilegeLevelForProject(
  203. userId,
  204. projectId,
  205. token
  206. )
  207. return privilegeLevel === PrivilegeLevels.OWNER
  208. }
  209. async function canUserAdminProject(userId, projectId, token) {
  210. const privilegeLevel = await getPrivilegeLevelForProject(
  211. userId,
  212. projectId,
  213. token
  214. )
  215. return privilegeLevel === PrivilegeLevels.OWNER
  216. }
  217. async function isUserSiteAdmin(userId) {
  218. if (!userId) {
  219. return false
  220. }
  221. if (!Settings.adminPrivilegeAvailable) return false
  222. const user = await User.findOne({ _id: userId }, { isAdmin: 1 }).exec()
  223. return hasAdminAccess(user)
  224. }
  225. async function canUserDeleteOrResolveThread(
  226. userId,
  227. projectId,
  228. docId,
  229. threadId,
  230. token
  231. ) {
  232. const privilegeLevel = await getPrivilegeLevelForProject(
  233. userId,
  234. projectId,
  235. token,
  236. { ignorePublicAccess: true }
  237. )
  238. if (
  239. privilegeLevel === PrivilegeLevels.OWNER ||
  240. privilegeLevel === PrivilegeLevels.READ_AND_WRITE
  241. ) {
  242. return true
  243. }
  244. if (privilegeLevel !== PrivilegeLevels.REVIEW) {
  245. return false
  246. }
  247. const comment = await DocumentUpdaterHandler.promises.getComment(
  248. projectId,
  249. docId,
  250. threadId
  251. )
  252. return comment.metadata.user_id === userId
  253. }
  254. async function canUserSendOrReopenComment(userId, projectId, token) {
  255. const privilegeLevel = await getPrivilegeLevelForProject(
  256. userId,
  257. projectId,
  258. token
  259. )
  260. return (
  261. privilegeLevel === PrivilegeLevels.OWNER ||
  262. privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
  263. privilegeLevel === PrivilegeLevels.REVIEW
  264. )
  265. }
  266. module.exports = {
  267. canUserReadProject: callbackify(canUserReadProject),
  268. canUserWriteProjectContent: callbackify(canUserWriteProjectContent),
  269. canUserSendOrReopenComment: callbackify(canUserSendOrReopenComment),
  270. canUserDeleteOrResolveThread: callbackify(canUserDeleteOrResolveThread),
  271. canUserWriteProjectSettings: callbackify(canUserWriteProjectSettings),
  272. canUserRenameProject: callbackify(canUserRenameProject),
  273. canUserAdminProject: callbackify(canUserAdminProject),
  274. getPrivilegeLevelForProject: callbackify(getPrivilegeLevelForProject),
  275. isRestrictedUser,
  276. isRestrictedUserForProject: callbackify(isRestrictedUserForProject),
  277. isUserSiteAdmin: callbackify(isUserSiteAdmin),
  278. promises: {
  279. canUserReadProject,
  280. canUserWriteProjectContent,
  281. canUserSendOrReopenComment,
  282. canUserDeleteOrResolveThread,
  283. canUserWriteProjectSettings,
  284. canUserRenameProject,
  285. canUserAdminProject,
  286. getPrivilegeLevelForProject,
  287. isRestrictedUserForProject,
  288. isUserSiteAdmin,
  289. },
  290. }