[web] Map admin capabilities to project PrivilegeLevels (#27488)

* Add capability `copy-project`

* Check `copy-project` (frontend)

* Update tests

* Suggestion: map `modify-project`-`PrivilegeLevels.OWNER` and `view-project`-`PrivilegeLevels.READ_ONLY`

* Suggestion: remove capability `copy-project`. Use `view-project` instead

* Revert unrelated changes

* Add tests on AuthorizationManager when `adminRolesEnabled`

* Update `Modules.promises.hooks.fire` stubs with `.withArgs('getAdminCapabilities')`

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>

* Use `getAdminCapabilities` from AdminAuthorizationHelper.js

---------

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: 61167509c4a035c99831a5b0346347c2e6b5fae0
This commit is contained in:
Antoine Clausse
2025-08-07 18:06:49 +02:00
committed by Copybot
parent d145d309be
commit 422e892231
2 changed files with 56 additions and 8 deletions

View File

@@ -8,7 +8,10 @@ const PrivilegeLevels = require('./PrivilegeLevels')
const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
const PublicAccessLevels = require('./PublicAccessLevels')
const Errors = require('../Errors/Errors')
const { hasAdminAccess } = require('../Helpers/AdminAuthorizationHelper')
const {
hasAdminAccess,
getAdminCapabilities,
} = require('../Helpers/AdminAuthorizationHelper')
const Settings = require('@overleaf/settings')
const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
@@ -146,10 +149,18 @@ async function getPrivilegeLevelForProjectWithUser(
projectAccess,
opts = {}
) {
if (!opts.ignoreSiteAdmin) {
if (await isUserSiteAdmin(userId)) {
let adminReadOnly = false
if (!opts.ignoreSiteAdmin && (await isUserSiteAdmin(userId))) {
if (!Settings.adminRolesEnabled) {
return PrivilegeLevels.OWNER
}
const { adminCapabilities } = await getAdminCapabilities({ _id: userId })
if (adminCapabilities.includes('modify-project')) {
return PrivilegeLevels.OWNER
}
if (adminCapabilities.includes('view-project')) {
adminReadOnly = true
}
}
projectAccess =
@@ -174,6 +185,10 @@ async function getPrivilegeLevelForProjectWithUser(
}
}
if (adminReadOnly) {
return PrivilegeLevels.READ_ONLY
}
return PrivilegeLevels.NONE
}

View File

@@ -58,7 +58,12 @@ describe('AuthorizationManager', function () {
.resolves({ metadata: { user_id: new ObjectId() } }),
},
}
this.Modules = { promises: { hooks: { fire: sinon.stub() } } }
this.settings = {
passwordStrengthOptions: {},
adminPrivilegeAvailable: true,
adminRolesEnabled: false,
}
this.AuthorizationManager = SandboxedModule.require(modulePath, {
requires: {
'mongodb-legacy': { ObjectId },
@@ -69,10 +74,8 @@ describe('AuthorizationManager', function () {
'../TokenAccess/TokenAccessHandler': this.TokenAccessHandler,
'../DocumentUpdater/DocumentUpdaterHandler':
this.DocumentUpdaterHandler,
'@overleaf/settings': {
passwordStrengthOptions: {},
adminPrivilegeAvailable: true,
},
'../../infrastructure/Modules': this.Modules,
'@overleaf/settings': this.settings,
},
})
})
@@ -678,6 +681,36 @@ function testPermission(permission, privilegeLevels) {
})
expectPermission(permission, privilegeLevels.siteAdmin || false)
})
describe('admin without permissions', function () {
beforeEach(function () {
this.user.isAdmin = true
this.settings.adminRolesEnabled = true
this.Modules.promises.hooks.fire
.withArgs('getAdminCapabilities')
.resolves([])
})
expectPermission(permission, false)
})
describe('admin with `view-project`', function () {
beforeEach(function () {
this.user.isAdmin = true
this.settings.adminRolesEnabled = true
this.Modules.promises.hooks.fire
.withArgs('getAdminCapabilities')
.resolves([['view-project']])
})
expectPermission(permission, privilegeLevels.readOnly || false)
})
describe('admin with `modify-project`', function () {
beforeEach(function () {
this.user.isAdmin = true
this.settings.adminRolesEnabled = true
this.Modules.promises.hooks.fire
.withArgs('getAdminCapabilities')
.resolves([['view-project', 'modify-project']])
})
expectPermission(permission, privilegeLevels.siteAdmin || false)
})
describe('when user is owner', function () {
setupUserPrivilegeLevel(PrivilegeLevels.OWNER)