AuthorizationManager.js 11 KB

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