AuthorizationManager.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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 await getPrivilegeLevelForProjectWithUser(
  87. userId,
  88. projectId,
  89. null,
  90. opts
  91. )
  92. } else {
  93. return await getPrivilegeLevelForProjectWithoutUser(projectId, token, opts)
  94. }
  95. }
  96. /**
  97. * Get the privilege level that the user has for the project.
  98. *
  99. * @param userId - The id of the user that wants to access the project.
  100. * @param projectId - The id of the project to be accessed.
  101. * @param {string} token
  102. * @param {ProjectAccess} projectAccess
  103. * @param {Object} opts
  104. * @param {boolean} opts.ignoreSiteAdmin - Do not consider whether the user is
  105. * a site admin.
  106. * @param {boolean} opts.ignorePublicAccess - Do not consider the project is
  107. * publicly accessible.
  108. *
  109. * @returns {string|boolean} The privilege level. One of "owner",
  110. * "readAndWrite", "readOnly" or false.
  111. */
  112. async function getPrivilegeLevelForProjectWithProjectAccess(
  113. userId,
  114. projectId,
  115. token,
  116. projectAccess,
  117. opts = {}
  118. ) {
  119. if (userId) {
  120. return await getPrivilegeLevelForProjectWithUser(
  121. userId,
  122. projectId,
  123. projectAccess,
  124. opts
  125. )
  126. } else {
  127. return await _getPrivilegeLevelForProjectWithoutUserWithPublicAccessLevel(
  128. projectId,
  129. token,
  130. projectAccess.publicAccessLevel(),
  131. opts
  132. )
  133. }
  134. }
  135. // User is present, get their privilege level from database
  136. async function getPrivilegeLevelForProjectWithUser(
  137. userId,
  138. projectId,
  139. projectAccess,
  140. opts = {}
  141. ) {
  142. if (!opts.ignoreSiteAdmin) {
  143. if (await isUserSiteAdmin(userId)) {
  144. return PrivilegeLevels.OWNER
  145. }
  146. }
  147. projectAccess =
  148. projectAccess ||
  149. (await CollaboratorsGetter.promises.getProjectAccess(projectId))
  150. const privilegeLevel = projectAccess.privilegeLevelForUser(userId)
  151. if (privilegeLevel && privilegeLevel !== PrivilegeLevels.NONE) {
  152. // The user has direct access
  153. return privilegeLevel
  154. }
  155. if (!opts.ignorePublicAccess) {
  156. // Legacy public-access system
  157. // User is present (not anonymous), but does not have direct access
  158. const publicAccessLevel = projectAccess.publicAccessLevel()
  159. if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
  160. return PrivilegeLevels.READ_ONLY
  161. }
  162. if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
  163. return PrivilegeLevels.READ_AND_WRITE
  164. }
  165. }
  166. return PrivilegeLevels.NONE
  167. }
  168. // User is Anonymous, Try Token-based access
  169. async function getPrivilegeLevelForProjectWithoutUser(
  170. projectId,
  171. token,
  172. opts = {}
  173. ) {
  174. return await _getPrivilegeLevelForProjectWithoutUserWithPublicAccessLevel(
  175. projectId,
  176. token,
  177. await getPublicAccessLevel(projectId),
  178. opts
  179. )
  180. }
  181. // User is Anonymous, Try Token-based access
  182. async function _getPrivilegeLevelForProjectWithoutUserWithPublicAccessLevel(
  183. projectId,
  184. token,
  185. publicAccessLevel,
  186. opts = {}
  187. ) {
  188. if (!opts.ignorePublicAccess) {
  189. if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
  190. // Legacy public read-only access for anonymous user
  191. return PrivilegeLevels.READ_ONLY
  192. }
  193. if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
  194. // Legacy public read-write access for anonymous user
  195. return PrivilegeLevels.READ_AND_WRITE
  196. }
  197. }
  198. if (publicAccessLevel === PublicAccessLevels.TOKEN_BASED) {
  199. return await getPrivilegeLevelForProjectWithToken(projectId, token)
  200. }
  201. // Deny anonymous user access
  202. return PrivilegeLevels.NONE
  203. }
  204. async function getPrivilegeLevelForProjectWithToken(projectId, token) {
  205. // Anonymous users can have read-only access to token-based projects,
  206. // while read-write access must be logged in,
  207. // unless the `enableAnonymousReadAndWriteSharing` setting is enabled
  208. const { isValidReadAndWrite, isValidReadOnly } =
  209. await TokenAccessHandler.promises.validateTokenForAnonymousAccess(
  210. projectId,
  211. token
  212. )
  213. if (isValidReadOnly) {
  214. // Grant anonymous user read-only access
  215. return PrivilegeLevels.READ_ONLY
  216. }
  217. if (isValidReadAndWrite) {
  218. // Grant anonymous user read-and-write access
  219. return PrivilegeLevels.READ_AND_WRITE
  220. }
  221. // Deny anonymous access
  222. return PrivilegeLevels.NONE
  223. }
  224. async function canUserReadProject(userId, projectId, token) {
  225. const privilegeLevel = await getPrivilegeLevelForProject(
  226. userId,
  227. projectId,
  228. token
  229. )
  230. return [
  231. PrivilegeLevels.OWNER,
  232. PrivilegeLevels.READ_AND_WRITE,
  233. PrivilegeLevels.READ_ONLY,
  234. PrivilegeLevels.REVIEW,
  235. ].includes(privilegeLevel)
  236. }
  237. async function canUserWriteProjectContent(userId, projectId, token) {
  238. const privilegeLevel = await getPrivilegeLevelForProject(
  239. userId,
  240. projectId,
  241. token
  242. )
  243. return [PrivilegeLevels.OWNER, PrivilegeLevels.READ_AND_WRITE].includes(
  244. privilegeLevel
  245. )
  246. }
  247. async function canUserWriteOrReviewProjectContent(userId, projectId, token) {
  248. const privilegeLevel = await getPrivilegeLevelForProject(
  249. userId,
  250. projectId,
  251. token
  252. )
  253. return (
  254. privilegeLevel === PrivilegeLevels.OWNER ||
  255. privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
  256. privilegeLevel === PrivilegeLevels.REVIEW
  257. )
  258. }
  259. async function canUserWriteProjectSettings(userId, projectId, token) {
  260. const privilegeLevel = await getPrivilegeLevelForProject(
  261. userId,
  262. projectId,
  263. token,
  264. { ignorePublicAccess: true }
  265. )
  266. return [PrivilegeLevels.OWNER, PrivilegeLevels.READ_AND_WRITE].includes(
  267. privilegeLevel
  268. )
  269. }
  270. async function canUserRenameProject(userId, projectId, token) {
  271. const privilegeLevel = await getPrivilegeLevelForProject(
  272. userId,
  273. projectId,
  274. token
  275. )
  276. return privilegeLevel === PrivilegeLevels.OWNER
  277. }
  278. async function canUserAdminProject(userId, projectId, token) {
  279. const privilegeLevel = await getPrivilegeLevelForProject(
  280. userId,
  281. projectId,
  282. token
  283. )
  284. return privilegeLevel === PrivilegeLevels.OWNER
  285. }
  286. async function isUserSiteAdmin(userId) {
  287. if (!userId) {
  288. return false
  289. }
  290. if (!Settings.adminPrivilegeAvailable) return false
  291. const user = await User.findOne({ _id: userId }, { isAdmin: 1 }).exec()
  292. return hasAdminAccess(user)
  293. }
  294. async function canUserDeleteOrResolveThread(
  295. userId,
  296. projectId,
  297. docId,
  298. threadId,
  299. token
  300. ) {
  301. const privilegeLevel = await getPrivilegeLevelForProject(
  302. userId,
  303. projectId,
  304. token,
  305. { ignorePublicAccess: true }
  306. )
  307. if (
  308. privilegeLevel === PrivilegeLevels.OWNER ||
  309. privilegeLevel === PrivilegeLevels.READ_AND_WRITE
  310. ) {
  311. return true
  312. }
  313. if (privilegeLevel !== PrivilegeLevels.REVIEW) {
  314. return false
  315. }
  316. const comment = await DocumentUpdaterHandler.promises.getComment(
  317. projectId,
  318. docId,
  319. threadId
  320. )
  321. return comment.metadata.user_id === userId
  322. }
  323. module.exports = {
  324. canUserReadProject: callbackify(canUserReadProject),
  325. canUserWriteProjectContent: callbackify(canUserWriteProjectContent),
  326. canUserWriteOrReviewProjectContent: callbackify(
  327. canUserWriteOrReviewProjectContent
  328. ),
  329. canUserDeleteOrResolveThread: callbackify(canUserDeleteOrResolveThread),
  330. canUserWriteProjectSettings: callbackify(canUserWriteProjectSettings),
  331. canUserRenameProject: callbackify(canUserRenameProject),
  332. canUserAdminProject: callbackify(canUserAdminProject),
  333. getPrivilegeLevelForProject: callbackify(getPrivilegeLevelForProject),
  334. isRestrictedUser,
  335. isRestrictedUserForProject: callbackify(isRestrictedUserForProject),
  336. isUserSiteAdmin: callbackify(isUserSiteAdmin),
  337. promises: {
  338. canUserReadProject,
  339. canUserWriteProjectContent,
  340. canUserWriteOrReviewProjectContent,
  341. canUserDeleteOrResolveThread,
  342. canUserWriteProjectSettings,
  343. canUserRenameProject,
  344. canUserAdminProject,
  345. getPrivilegeLevelForProject,
  346. getPrivilegeLevelForProjectWithProjectAccess,
  347. isRestrictedUserForProject,
  348. isUserSiteAdmin,
  349. },
  350. }