AuthorizationManager.js 7.5 KB

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