Merge pull request #17596 from overleaf/rh-permissions-policy

[web] Add Permissions-Policy header

GitOrigin-RevId: 8934bbbda411102580d9ef8af135dcdc147627f9
This commit is contained in:
roo hutton
2024-04-08 08:04:29 +00:00
committed by Copybot
parent 67e9a40e87
commit 8644e239c6
6 changed files with 282 additions and 0 deletions
@@ -0,0 +1,88 @@
// @ts-check
const Settings = require('@overleaf/settings')
/**
* @typedef {import('./types').HttpPermissionsPolicy} HttpPermissionsPolicy
* */
class HttpPermissionsPolicyMiddleware {
/**
* Initialise the middleware with a Permissions Policy config
* @param {HttpPermissionsPolicy} policy
*/
constructor(policy) {
this.middleware = this.middleware.bind(this)
if (policy) {
this.policy = this.buildPermissionsPolicy(policy)
}
}
/**
* Checks the provided policy is valid
* @param {HttpPermissionsPolicy} policy
* @returns {boolean}
*/
validatePermissionsPolicy(policy) {
let policyIsValid = true
if (!policy.allowed) {
return true
}
for (const [directive, origins] of Object.entries(policy.allowed)) {
// Do any directives in the allowlist clash with the denylist?
if (policy.blocked && policy.blocked.includes(directive)) {
policyIsValid = false
}
if (!origins) {
policyIsValid = false
}
}
return policyIsValid
}
/**
* Constructs a Permissions-Policy header string from the given policy configuration
* @param {HttpPermissionsPolicy} policy
* @returns {string}
*/
buildPermissionsPolicy(policy) {
if (!this.validatePermissionsPolicy(policy)) {
throw new Error('Invalid Permissions-Policy header configuration')
}
const policyElements = []
if (policy.blocked && policy.blocked.length > 0) {
policyElements.push(
policy.blocked.map(policyElement => `${policyElement}=()`).join(', ')
)
}
if (policy.allowed && Object.entries(policy.allowed).length > 0) {
policyElements.push(
Object.keys(policy.allowed)
.map(allowKey => `${allowKey}=(${policy.allowed[allowKey]})`)
.join(', ')
)
}
return policyElements.join(', ')
}
middleware(req, res, next) {
if (this.policy && Settings.useHttpPermissionsPolicy) {
const originalRender = res.render
res.render = (...args) => {
res.setHeader('Permissions-Policy', this.policy)
originalRender.apply(res, args)
}
}
next()
}
}
module.exports = HttpPermissionsPolicyMiddleware
@@ -9,6 +9,7 @@ const Router = require('../router')
const helmet = require('helmet')
const UserSessionsRedis = require('../Features/User/UserSessionsRedis')
const Csrf = require('./Csrf')
const HttpPermissionsPolicyMiddleware = require('./HttpPermissionsPolicy')
const sessionsRedisClient = UserSessionsRedis.client()
@@ -143,6 +144,15 @@ if (Settings.blockCrossOriginRequests) {
app.use(Csrf.blockCrossOriginRequests())
}
if (Settings.useHttpPermissionsPolicy) {
const httpPermissionsPolicy = new HttpPermissionsPolicyMiddleware(
Settings.httpPermissions
)
logger.debug('adding permissions policy config', Settings.httpPermissions)
webRouter.use(httpPermissionsPolicy.middleware)
}
RedirectManager.apply(webRouter)
webRouter.use(cookieParser(Settings.security.sessionSecret))
@@ -0,0 +1,8 @@
export interface HttpPermissionsPolicyRule {
[key: string]: string
}
export interface HttpPermissionsPolicy {
blocked: [string]
allowed: HttpPermissionsPolicyRule
}