overleaf/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js
Timothée Alby 5f107374a6 Merge pull request #2195 from overleaf/em-collab-permissions
Move collaborators code to async/await

GitOrigin-RevId: 55b5dd8154d024e2cee738208c45a8139870b92b
2019-10-07 08:46:36 +00:00

289 lines
8.5 KiB
JavaScript

const { expressify } = require('../../util/promises')
const async = require('async')
const UserMembershipAuthorization = require('./UserMembershipAuthorization')
const AuthenticationController = require('../Authentication/AuthenticationController')
const UserMembershipHandler = require('./UserMembershipHandler')
const EntityConfigs = require('./UserMembershipEntityConfigs')
const Errors = require('../Errors/Errors')
const HttpErrors = require('@overleaf/o-error/http')
const TemplatesManager = require('../Templates/TemplatesManager')
// set of middleware arrays or functions that checks user access to an entity
// (publisher, institution, group, template, etc.)
let UserMembershipMiddleware = {
requireTeamMetricsAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('team'),
fetchEntity(),
requireEntity(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupMetrics')
])
],
requireGroupManagementAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('group'),
fetchEntity(),
requireEntity(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupManagement')
])
],
requireGroupMetricsAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('group'),
fetchEntity(),
requireEntity(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupMetrics')
])
],
requireGroupManagersManagementAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('groupManagers'),
fetchEntity(),
requireEntity(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupManagement')
])
],
requireInstitutionMetricsAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('institution'),
fetchEntity(),
requireEntityOrCreate('institutionManagement'),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('institutionMetrics')
])
],
requireInstitutionManagementAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('institution'),
fetchEntity(),
requireEntityOrCreate('institutionManagement'),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('institutionManagement')
])
],
requireInstitutionManagementStaffAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('institutionManagement')
]),
fetchEntityConfig('institution'),
fetchEntity(),
requireEntityOrCreate('institutionManagement')
],
requirePublisherMetricsAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('publisher'),
fetchEntity(),
requireEntityOrCreate('publisherManagement'),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherMetrics')
])
],
requirePublisherManagementAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('publisher'),
fetchEntity(),
requireEntityOrCreate('publisherManagement'),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherManagement')
])
],
requireConversionMetricsAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('publisher'),
fetchEntity(),
requireEntityOrCreate('publisherManagement'),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherMetrics')
])
],
requireAdminMetricsAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('adminMetrics')
])
],
requireTemplateMetricsAccess: [
AuthenticationController.requireLogin(),
fetchV1Template(),
requireV1Template(),
fetchEntityConfig('publisher'),
fetchPublisherFromTemplate(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherMetrics')
])
],
requirePublisherCreationAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('publisherManagement')
]),
fetchEntityConfig('publisher')
],
requireInstitutionCreationAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('institutionManagement')
]),
fetchEntityConfig('institution')
],
// graphs access is an edge-case:
// - the entity id is in `req.query.resource_id`. It must be set as
// `req.params.id`
// - the entity name is in `req.query.resource_type` and is used to find the
// require middleware depending on the entity name
requireGraphAccess(req, res, next) {
req.params.id = req.query.resource_id
let entityName = req.query.resource_type
if (!entityName) {
return next(new HttpErrors.NotFoundError('resource_type param missing'))
}
entityName = entityName.charAt(0).toUpperCase() + entityName.slice(1)
const middleware =
UserMembershipMiddleware[`require${entityName}MetricsAccess`]
if (!middleware) {
return next(
new HttpErrors.NotFoundError(`incorrect entity name: ${entityName}`)
)
}
// run the list of middleware functions in series. This is essencially
// a poor man's middleware runner
async.eachSeries(middleware, (fn, callback) => fn(req, res, callback), next)
}
}
module.exports = UserMembershipMiddleware
// fetch entity config and set it in the request
function fetchEntityConfig(entityName) {
return (req, res, next) => {
const entityConfig = EntityConfigs[entityName]
req.entityName = entityName
req.entityConfig = entityConfig
next()
}
}
// fetch the entity with id and config, and set it in the request
function fetchEntity() {
return expressify(async (req, res, next) => {
let entity = await UserMembershipHandler.promises.getEntityWithoutAuthorizationCheck(
req.params.id,
req.entityConfig
)
req.entity = entity
next()
})
}
function fetchPublisherFromTemplate() {
return (req, res, next) => {
if (req.template.brand.slug) {
// set the id as the publisher's id as it's the entity used for access
// control
req.params.id = req.template.brand.slug
return fetchEntity()(req, res, next)
} else {
return next()
}
}
}
// ensure an entity was found, or fail with 404
function requireEntity() {
return (req, res, next) => {
if (req.entity) {
return next()
}
throw new Errors.NotFoundError(
`no '${req.entityName}' entity with '${req.params.id}'`
)
}
}
// ensure an entity was found or redirect to entity creation page if the user
// has permissions to create the entity, or fail with 404
function requireEntityOrCreate(creationStaffAccess) {
return (req, res, next) => {
if (req.entity) {
return next()
}
if (UserMembershipAuthorization.hasStaffAccess(creationStaffAccess)(req)) {
res.redirect(`/entities/${req.entityName}/create/${req.params.id}`)
return
}
throw new Errors.NotFoundError(
`no '${req.entityName}' entity with '${req.params.id}'`
)
}
}
// fetch the template from v1, and set it in the request
function fetchV1Template() {
return expressify(async (req, res, next) => {
const templateId = req.params.id
const body = await TemplatesManager.promises.fetchFromV1(templateId)
req.template = {
id: body.id,
title: body.title,
brand: body.brand
}
next()
})
}
// ensure a template was found, or fail with 404
function requireV1Template() {
return (req, res, next) => {
if (req.template.id) {
return next()
}
throw new Errors.NotFoundError('no template found')
}
}
// run a serie of synchronous access functions and call `next` if any of the
// retur values is truly. Redirect to restricted otherwise
function allowAccessIfAny(accessFunctions) {
return (req, res, next) => {
for (let accessFunction of accessFunctions) {
if (accessFunction(req)) {
return next()
}
}
next(new HttpErrors.ForbiddenError({}))
}
}