PermissionsController.mjs 4.1 KB

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