Add hasAdminCapabilities function to ExpressLocals

This will be available in pug to allow admin capabilities to be used.

GitOrigin-RevId: 6bc4e38385b421aa44ee9385e28f3c59b09e3ade
This commit is contained in:
Andrew Rumble
2025-07-28 16:41:33 +01:00
committed by Copybot
parent a9d642776a
commit 192aacbecd
3 changed files with 105 additions and 0 deletions

View File

@@ -1,8 +1,10 @@
const Settings = require('@overleaf/settings')
const Modules = require('../../infrastructure/Modules')
module.exports = {
hasAdminAccess,
canRedirectToAdminDomain,
getAdminCapabilities,
}
function hasAdminAccess(user) {
@@ -11,6 +13,18 @@ function hasAdminAccess(user) {
return Boolean(user.isAdmin)
}
async function getAdminCapabilities(user) {
const rawAdminCapabilties = await Modules.promises.hooks.fire(
'getAdminCapabilities',
user
)
return {
adminCapabilities: [...new Set(rawAdminCapabilties.flat())],
adminCapabilitiesAvailable: rawAdminCapabilties.length > 0,
}
}
function canRedirectToAdminDomain(user) {
if (Settings.adminPrivilegeAvailable) return false
if (!Settings.adminUrl) return false

View File

@@ -14,12 +14,14 @@ const Modules = require('./Modules')
const Errors = require('../Features/Errors/Errors')
const {
canRedirectToAdminDomain,
getAdminCapabilities,
hasAdminAccess,
} = require('../Features/Helpers/AdminAuthorizationHelper')
const {
addOptionalCleanupHandlerAfterDrainingConnections,
} = require('./GracefulShutdown')
const { sanitizeSessionUserForFrontEnd } = require('./FrontEndUser')
const { expressify } = require('@overleaf/promise-utils')
const IEEE_BRAND_ID = Settings.ieeeBrandId
@@ -312,6 +314,35 @@ module.exports = function (webRouter, privateApiRouter, publicApiRouter) {
next()
})
webRouter.use(
expressify(async function (req, res, next) {
const user = SessionManager.getSessionUser(req.session)
try {
const { adminCapabilities, adminCapabilitiesAvailable } =
await getAdminCapabilities(user)
res.locals.hasAdminCapability = capability => {
if (!hasAdminAccess(user)) {
return false
}
if (!adminCapabilitiesAvailable) {
// If admin capabilities are not available, then all admins have all capabilities
return true
}
return adminCapabilities.includes(capability)
}
} catch (error) {
if (user) {
// This is unexpected, it probably means that the session user does not exist.
logger.warn({ error, req, user }, 'Failed to get admin capabilities')
}
// adminCapabilitiesAvailable should be true if we are here so deny to be safe
res.locals.hasAdminCapability = () => false
}
next()
})
)
webRouter.use(function (req, res, next) {
// Clone the nav settings so they can be modified for each request
res.locals.nav = {}

View File

@@ -0,0 +1,60 @@
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const modulePath =
'../../../../app/src/Features/Helpers/AdminAuthorizationHelper'
describe('AdminAuthorizationHelper', function () {
beforeEach(function () {
this.fireHook = sinon.stub().resolves([])
this.AdminAuthorizationHelper = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': {
adminPrivilegeAvailable: true,
adminUrl: 'https://admin.overleaf.com',
},
'../../infrastructure/Modules': {
promises: {
hooks: {
fire: this.fireHook,
},
},
},
},
})
})
describe('getAdminCapabilities', function () {
describe('when modules return capabilities', function () {
let result
const module1Capabilities = ['capability1', 'capability2']
const module2Capabilities = ['capability2', 'capability3']
beforeEach(async function () {
this.fireHook.resolves([module1Capabilities, module2Capabilities])
result = await this.AdminAuthorizationHelper.getAdminCapabilities({})
})
it('returns true for adminCapabilitiesAvailable', async function () {
expect(result.adminCapabilitiesAvailable).to.be.true
})
it('returns a flattened array of the returned capabilities', function () {
expect(result.adminCapabilities)
.to.be.an('array')
.that.includes(...module1Capabilities, ...module2Capabilities)
})
})
describe('when no module returns capabilities', function () {
let result
beforeEach(async function () {
result = await this.AdminAuthorizationHelper.getAdminCapabilities({})
})
it('returns false for adminCapabilitiesAvailable', function () {
expect(result.adminCapabilitiesAvailable).to.be.false
})
it('returns an empty adminCapabilities array', function () {
expect(result.adminCapabilities).to.be.an('array').that.is.empty
})
})
})
})