UserMembershipMiddleware.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. const { expressify } = require('@overleaf/promise-utils')
  2. const async = require('async')
  3. const UserMembershipAuthorization = require('./UserMembershipAuthorization')
  4. const AuthenticationController = require('../Authentication/AuthenticationController')
  5. const UserMembershipHandler = require('./UserMembershipHandler')
  6. const EntityConfigs = require('./UserMembershipEntityConfigs')
  7. const Errors = require('../Errors/Errors')
  8. const HttpErrorHandler = require('../Errors/HttpErrorHandler')
  9. const TemplatesManager = require('../Templates/TemplatesManager')
  10. // set of middleware arrays or functions that checks user access to an entity
  11. // (publisher, institution, group, template, etc.)
  12. const UserMembershipMiddleware = {
  13. requireTeamMetricsAccess: [
  14. AuthenticationController.requireLogin(),
  15. fetchEntityConfig('team'),
  16. fetchEntity(),
  17. requireEntity(),
  18. allowAccessIfAny([
  19. UserMembershipAuthorization.hasEntityAccess(),
  20. UserMembershipAuthorization.hasStaffAccess('groupMetrics'),
  21. ]),
  22. ],
  23. requireGroup: [fetchEntityConfig('group'), fetchEntity(), requireEntity()],
  24. requireGroupAccess: [
  25. AuthenticationController.requireLogin(),
  26. fetchEntityConfig('group'),
  27. fetchEntity(),
  28. requireEntity(),
  29. ],
  30. requireGroupMemberAccess: [
  31. AuthenticationController.requireLogin(),
  32. fetchEntityConfig('groupMember'),
  33. fetchEntity(),
  34. requireEntity(),
  35. allowAccessIfAny([UserMembershipAuthorization.hasEntityAccess()]),
  36. ],
  37. requireGroupManagementAccess: [
  38. AuthenticationController.requireLogin(),
  39. fetchEntityConfig('group'),
  40. fetchEntity(),
  41. requireEntity(),
  42. allowAccessIfAny([
  43. UserMembershipAuthorization.hasEntityAccess(),
  44. UserMembershipAuthorization.hasStaffAccess('groupManagement'),
  45. ]),
  46. ],
  47. requireGroupMetricsAccess: [
  48. AuthenticationController.requireLogin(),
  49. fetchEntityConfig('group'),
  50. fetchEntity(),
  51. requireEntity(),
  52. allowAccessIfAny([
  53. UserMembershipAuthorization.hasEntityAccess(),
  54. UserMembershipAuthorization.hasStaffAccess('groupMetrics'),
  55. ]),
  56. ],
  57. requireGroupManagersManagementAccess: [
  58. AuthenticationController.requireLogin(),
  59. fetchEntityConfig('groupManagers'),
  60. fetchEntity(),
  61. requireEntity(),
  62. allowAccessIfAny([
  63. UserMembershipAuthorization.hasEntityAccess(),
  64. UserMembershipAuthorization.hasStaffAccess('groupManagement'),
  65. ]),
  66. ],
  67. requireGroupAdminAccess: [
  68. AuthenticationController.requireLogin(),
  69. fetchEntityConfig('groupAdmin'),
  70. fetchEntity(),
  71. requireEntity(),
  72. allowAccessIfAny([
  73. UserMembershipAuthorization.hasEntityAccess(),
  74. UserMembershipAuthorization.hasStaffAccess('groupManagement'),
  75. ]),
  76. ],
  77. requireInstitutionMetricsAccess: [
  78. AuthenticationController.requireLogin(),
  79. fetchEntityConfig('institution'),
  80. fetchEntity(),
  81. requireEntityOrCreate('institutionManagement'),
  82. allowAccessIfAny([
  83. UserMembershipAuthorization.hasEntityAccess(),
  84. UserMembershipAuthorization.hasStaffAccess('institutionMetrics'),
  85. ]),
  86. ],
  87. requireInstitutionManagementAccess: [
  88. AuthenticationController.requireLogin(),
  89. fetchEntityConfig('institution'),
  90. fetchEntity(),
  91. requireEntityOrCreate('institutionManagement'),
  92. allowAccessIfAny([
  93. UserMembershipAuthorization.hasEntityAccess(),
  94. UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
  95. ]),
  96. ],
  97. requireInstitutionManagementStaffAccess: [
  98. AuthenticationController.requireLogin(),
  99. allowAccessIfAny([
  100. UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
  101. ]),
  102. fetchEntityConfig('institution'),
  103. fetchEntity(),
  104. requireEntityOrCreate('institutionManagement'),
  105. ],
  106. requirePublisherMetricsAccess: [
  107. AuthenticationController.requireLogin(),
  108. fetchEntityConfig('publisher'),
  109. fetchEntity(),
  110. requireEntityOrCreate('publisherManagement'),
  111. allowAccessIfAny([
  112. UserMembershipAuthorization.hasEntityAccess(),
  113. UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
  114. ]),
  115. ],
  116. requirePublisherManagementAccess: [
  117. AuthenticationController.requireLogin(),
  118. fetchEntityConfig('publisher'),
  119. fetchEntity(),
  120. requireEntityOrCreate('publisherManagement'),
  121. allowAccessIfAny([
  122. UserMembershipAuthorization.hasEntityAccess(),
  123. UserMembershipAuthorization.hasStaffAccess('publisherManagement'),
  124. ]),
  125. ],
  126. requireConversionMetricsAccess: [
  127. AuthenticationController.requireLogin(),
  128. fetchEntityConfig('publisher'),
  129. fetchEntity(),
  130. requireEntityOrCreate('publisherManagement'),
  131. allowAccessIfAny([
  132. UserMembershipAuthorization.hasEntityAccess(),
  133. UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
  134. ]),
  135. ],
  136. requireAdminMetricsAccess: [
  137. AuthenticationController.requireLogin(),
  138. allowAccessIfAny([
  139. UserMembershipAuthorization.hasStaffAccess('adminMetrics'),
  140. ]),
  141. ],
  142. requireTemplateMetricsAccess: [
  143. AuthenticationController.requireLogin(),
  144. fetchV1Template(),
  145. requireV1Template(),
  146. fetchEntityConfig('publisher'),
  147. fetchPublisherFromTemplate(),
  148. allowAccessIfAny([
  149. UserMembershipAuthorization.hasEntityAccess(),
  150. UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
  151. ]),
  152. ],
  153. requirePublisherCreationAccess: [
  154. AuthenticationController.requireLogin(),
  155. allowAccessIfAny([
  156. UserMembershipAuthorization.hasStaffAccess('publisherManagement'),
  157. ]),
  158. fetchEntityConfig('publisher'),
  159. ],
  160. requireInstitutionCreationAccess: [
  161. AuthenticationController.requireLogin(),
  162. allowAccessIfAny([
  163. UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
  164. ]),
  165. fetchEntityConfig('institution'),
  166. ],
  167. requireSplitTestMetricsAccess: [
  168. AuthenticationController.requireLogin(),
  169. allowAccessIfAny([
  170. UserMembershipAuthorization.hasStaffAccess('splitTestMetrics'),
  171. UserMembershipAuthorization.hasStaffAccess('splitTestManagement'),
  172. ]),
  173. ],
  174. requireSplitTestManagementAccess: [
  175. AuthenticationController.requireLogin(),
  176. allowAccessIfAny([
  177. UserMembershipAuthorization.hasStaffAccess('splitTestManagement'),
  178. ]),
  179. ],
  180. // graphs access is an edge-case:
  181. // - the entity id is in `req.query.resource_id`. It must be set as
  182. // `req.params.id`
  183. // - the entity name is in `req.query.resource_type` and is used to find the
  184. // require middleware depending on the entity name
  185. requireGraphAccess(req, res, next) {
  186. req.params.id = req.query.resource_id
  187. let entityName = req.query.resource_type
  188. if (!entityName) {
  189. return HttpErrorHandler.notFound(req, res, 'resource_type param missing')
  190. }
  191. entityName = entityName.charAt(0).toUpperCase() + entityName.slice(1)
  192. const middleware =
  193. UserMembershipMiddleware[`require${entityName}MetricsAccess`]
  194. if (!middleware) {
  195. return HttpErrorHandler.notFound(
  196. req,
  197. res,
  198. `incorrect entity name: ${entityName}`
  199. )
  200. }
  201. // run the list of middleware functions in series. This is essencially
  202. // a poor man's middleware runner
  203. async.eachSeries(middleware, (fn, callback) => fn(req, res, callback), next)
  204. },
  205. }
  206. module.exports = UserMembershipMiddleware
  207. // fetch entity config and set it in the request
  208. function fetchEntityConfig(entityName) {
  209. return (req, res, next) => {
  210. const entityConfig = EntityConfigs[entityName]
  211. req.entityName = entityName
  212. req.entityConfig = entityConfig
  213. next()
  214. }
  215. }
  216. // fetch the entity with id and config, and set it in the request
  217. function fetchEntity() {
  218. return expressify(async (req, res, next) => {
  219. req.entity =
  220. await UserMembershipHandler.promises.getEntityWithoutAuthorizationCheck(
  221. req.params.id,
  222. req.entityConfig
  223. )
  224. next()
  225. })
  226. }
  227. function fetchPublisherFromTemplate() {
  228. return (req, res, next) => {
  229. if (req.template.brand.slug) {
  230. // set the id as the publisher's id as it's the entity used for access
  231. // control
  232. req.params.id = req.template.brand.slug
  233. return fetchEntity()(req, res, next)
  234. } else {
  235. return next()
  236. }
  237. }
  238. }
  239. // ensure an entity was found, or fail with 404
  240. function requireEntity() {
  241. return (req, res, next) => {
  242. if (req.entity) {
  243. return next()
  244. }
  245. throw new Errors.NotFoundError(
  246. `no '${req.entityName}' entity with '${req.params.id}'`
  247. )
  248. }
  249. }
  250. // ensure an entity was found or redirect to entity creation page if the user
  251. // has permissions to create the entity, or fail with 404
  252. function requireEntityOrCreate(creationStaffAccess) {
  253. return (req, res, next) => {
  254. if (req.entity) {
  255. return next()
  256. }
  257. if (UserMembershipAuthorization.hasStaffAccess(creationStaffAccess)(req)) {
  258. res.redirect(`/entities/${req.entityName}/create/${req.params.id}`)
  259. return
  260. }
  261. throw new Errors.NotFoundError(
  262. `no '${req.entityName}' entity with '${req.params.id}'`
  263. )
  264. }
  265. }
  266. // fetch the template from v1, and set it in the request
  267. function fetchV1Template() {
  268. return expressify(async (req, res, next) => {
  269. const templateId = req.params.id
  270. const body = await TemplatesManager.promises.fetchFromV1(templateId)
  271. req.template = {
  272. id: body.id,
  273. title: body.title,
  274. brand: body.brand,
  275. }
  276. next()
  277. })
  278. }
  279. // ensure a template was found, or fail with 404
  280. function requireV1Template() {
  281. return (req, res, next) => {
  282. if (req.template.id) {
  283. return next()
  284. }
  285. throw new Errors.NotFoundError('no template found')
  286. }
  287. }
  288. // run a serie of synchronous access functions and call `next` if any of the
  289. // retur values is truly. Redirect to restricted otherwise
  290. function allowAccessIfAny(accessFunctions) {
  291. return (req, res, next) => {
  292. for (const accessFunction of accessFunctions) {
  293. if (accessFunction(req)) {
  294. return next()
  295. }
  296. }
  297. HttpErrorHandler.forbidden(req, res)
  298. }
  299. }