Răsfoiți Sursa

Add hasAdminCapabilities function to ExpressLocals

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

GitOrigin-RevId: 6bc4e38385b421aa44ee9385e28f3c59b09e3ade
Andrew Rumble 1 an în urmă
părinte
comite
192aacbecd

+ 14 - 0
services/web/app/src/Features/Helpers/AdminAuthorizationHelper.js

@@ -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

+ 31 - 0
services/web/app/src/infrastructure/ExpressLocals.js

@@ -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 = {}

+ 60 - 0
services/web/test/unit/src/HelperFiles/AdminAuthorizationHelperTests.js

@@ -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
+      })
+    })
+  })
+})