UserMembershipMiddleware.js 9.5 KB

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