PermissionsController.mjs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // @ts-check
  2. import { ForbiddenError, UserNotFoundError } from '../Errors/Errors.js'
  3. import PermissionsManager from './PermissionsManager.mjs'
  4. import Modules from '../../infrastructure/Modules.mjs'
  5. import { expressify } from '@overleaf/promise-utils'
  6. import Features from '../../infrastructure/Features.mjs'
  7. /**
  8. * @typedef {(import('express').Request)} Request
  9. * @typedef {(import('express').Response)} Response
  10. * @typedef {(import('express').NextFunction)} NextFunction
  11. * @typedef {import('./PermissionsManager.mjs').Capability} Capability
  12. */
  13. const {
  14. getUserCapabilities,
  15. getUserRestrictions,
  16. combineGroupPolicies,
  17. combineAllowedProperties,
  18. promises: { assertUserPermissions },
  19. } = PermissionsManager
  20. /**
  21. * Function that returns middleware to add an `assertPermission` function to the request object to check if the user has a specific capability.
  22. * @returns {() => (req: Request, res: Response, next: NextFunction) => void} The middleware function that adds the `assertPermission` function to the request object.
  23. */
  24. function useCapabilities() {
  25. /**
  26. * @param {any} req
  27. * @param {any} res
  28. * @param {any} next
  29. */
  30. const middleware = async function (req, res, next) {
  31. // attach the user's capabilities to the request object
  32. req.capabilitySet = new Set()
  33. // provide a function to assert that a capability is present
  34. /** @param {any} capability */
  35. req.assertPermission = capability => {
  36. if (!req.capabilitySet.has(capability)) {
  37. throw new ForbiddenError(
  38. `user does not have permission for ${capability}`
  39. )
  40. }
  41. }
  42. if (!req.user) {
  43. return next()
  44. }
  45. try {
  46. /**
  47. * @type {{groupPolicy: Record<string, boolean>}[][]}
  48. */
  49. const hookResponses = await Modules.promises.hooks.fire(
  50. 'getGroupPolicyForUser',
  51. req.user
  52. )
  53. // merge array of all results from all modules
  54. const results = hookResponses.flat()
  55. if (results.length > 0) {
  56. // get the combined group policy applying to the user
  57. const groupPolicies = results.map(
  58. /** @param {any} result */ result => result.groupPolicy
  59. )
  60. const combinedGroupPolicy = combineGroupPolicies(groupPolicies)
  61. // attach the new capabilities to the request object
  62. for (const cap of getUserCapabilities(combinedGroupPolicy)) {
  63. req.capabilitySet.add(cap)
  64. }
  65. // also attach the user's restrictions (the capabilities they don't have)
  66. req.userRestrictions = getUserRestrictions(combinedGroupPolicy)
  67. // attach allowed properties to the request object
  68. const allowedProperties = combineAllowedProperties(results)
  69. for (const [prop, value] of Object.entries(allowedProperties)) {
  70. req[prop] = value
  71. }
  72. }
  73. next()
  74. } catch (/** @type {any} */ error) {
  75. if (error instanceof UserNotFoundError) {
  76. // the user is logged in but doesn't exist in the database
  77. // this can happen if the user has just deleted their account
  78. return next()
  79. } else {
  80. next(error)
  81. }
  82. }
  83. }
  84. return expressify(middleware)
  85. }
  86. /**
  87. * Function that returns middleware to check if the user has permission to access a resource.
  88. * @param {...Capability} requiredCapabilities - the capabilities required to access the resource.
  89. * @returns {(req: Request, res: Response, next: NextFunction) => void} The middleware function that checks if the user has the required capabilities.
  90. */
  91. function requirePermission(...requiredCapabilities) {
  92. if (
  93. requiredCapabilities.length === 0 ||
  94. requiredCapabilities.some(
  95. /** @param {any} capability */ capability =>
  96. typeof capability !== 'string'
  97. )
  98. ) {
  99. throw new Error('invalid required capabilities')
  100. }
  101. /**
  102. * @param {Request} req
  103. * @param {Response} res
  104. * @param {NextFunction} next
  105. */
  106. const doRequest = async function (req, res, next) {
  107. if (!Features.hasFeature('saas')) {
  108. return next()
  109. }
  110. const user = req.user || req.oauth_user
  111. if (!user) {
  112. return next(new Error('no user'))
  113. }
  114. try {
  115. await assertUserPermissions(user, requiredCapabilities)
  116. next()
  117. } catch (/** @type {any} */ error) {
  118. next(error)
  119. }
  120. }
  121. return doRequest
  122. }
  123. export default {
  124. requirePermission,
  125. useCapabilities,
  126. }