From 0dae38bb55ed8caa3c69af8325913e3425b2a128 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 27 Aug 2025 11:13:54 +0200 Subject: [PATCH] [web] Update `UserMembershipMiddleware` with flexible `requireEntityAccess` (#28018) * Update `requireGroupSettingsReadAccess` to be available to all admins when adminRolesEnabled is true * Update `UserMembershipMiddleware` with a flexible `requireEntityAccess` method * Update `UserMembershipMiddleware` further Update endpoints permissions when admin roles are enabled: `GET /manage/groups/:id/audit-logs` -> view-audit-log `GET /manage/groups/:id/audit-logs/zip` -> view-audit-log `GET /manage/groups/:id/settings` -> all admins `GET /subscription/:id/sso_configuration_test` -> all admins `GET /manage/groups/:id/members` -> all admins `DELETE /manage/groups/:id/user/:user_id` -> `modify-group-member`/`modify-managed-group-member` `GET /manage/groups/:id/members/export` -> all admins * Update `requireEntityAccess` to parameters to an object * Rename `hasAdminAccess` to `hasAnyAdminRole` GitOrigin-RevId: 740ea5148edc50987fbc86607b1aaa7b7523ffcb --- .../UserMembershipAuthorization.js | 14 ++- .../UserMembershipMiddleware.js | 103 +++--------------- .../UserMembership/UserMembershipRouter.mjs | 30 +++-- 3 files changed, 50 insertions(+), 97 deletions(-) diff --git a/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.js b/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.js index fa3b4c4261..2cda54a2e7 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.js +++ b/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.js @@ -1,4 +1,9 @@ -const { hasAdminCapability } = require('../Helpers/AdminAuthorizationHelper') +const { + hasAdminCapability, + hasAdminAccess, +} = require('../Helpers/AdminAuthorizationHelper') +const SessionManager = require('../Authentication/SessionManager') +const Settings = require('@overleaf/settings') const UserMembershipAuthorization = { hasStaffAccess(requiredStaffAccess) { @@ -16,6 +21,13 @@ const UserMembershipAuthorization = { hasAdminCapability, + hasAnyAdminRole(req) { + return ( + Settings.adminRolesEnabled && + hasAdminAccess(SessionManager.getSessionUser(req.session)) + ) + }, + hasModifyGroupMemberCapability(req, res) { return hasAdminCapability( req.entity.managedUsersEnabled diff --git a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js index 028b0c8489..20a7ddd7c9 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js +++ b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js @@ -12,17 +12,6 @@ const { useAdminCapabilities } = require('../Helpers/AdminAuthorizationHelper') // set of middleware arrays or functions that checks user access to an entity // (publisher, institution, group, template, etc.) const UserMembershipMiddleware = { - requireTeamMetricsAccess: [ - AuthenticationController.requireLogin(), - fetchEntityConfig('team'), - fetchEntity(), - requireEntity(), - allowAccessIfAny([ - UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupMetrics'), - ]), - ], - requireGroup: [fetchEntityConfig('group'), fetchEntity(), requireEntity()], requireGroupAccess: [ @@ -32,28 +21,37 @@ const UserMembershipMiddleware = { requireEntity(), ], - requireGroupMemberAccess: [ + requireEntityAccess: ({ entityName, staffAccess, adminCapability }) => [ AuthenticationController.requireLogin(), - fetchEntityConfig('groupMember'), + fetchEntityConfig(entityName), fetchEntity(), requireEntity(), - allowAccessIfAny([UserMembershipAuthorization.hasEntityAccess()]), + allowAccessIfAny( + [ + UserMembershipAuthorization.hasEntityAccess(), + staffAccess && UserMembershipAuthorization.hasStaffAccess(staffAccess), + adminCapability && + UserMembershipAuthorization.hasAdminCapability(adminCapability), + ].filter(Boolean) + ), ], - requireGroupManagementAccess: [ + requireEntityAccessOrAdminAccess: entityName => [ AuthenticationController.requireLogin(), - fetchEntityConfig('group'), + fetchEntityConfig(entityName), fetchEntity(), requireEntity(), allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), UserMembershipAuthorization.hasStaffAccess('groupManagement'), + // allow to all admins when `adminRolesEnabled` is true + UserMembershipAuthorization.hasAnyAdminRole, ]), ], - requireGroupMemberManagementAccess: [ + requireGroupMemberManagement: entityName => [ AuthenticationController.requireLogin(), - fetchEntityConfig('group'), + fetchEntityConfig(entityName), fetchEntity(), requireEntity(), useAdminCapabilities, @@ -64,75 +62,6 @@ const UserMembershipMiddleware = { ]), ], - 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'), - ]), - ], - - requireGroupManagersWriteAccess: [ - AuthenticationController.requireLogin(), - fetchEntityConfig('groupManagers'), - fetchEntity(), - requireEntity(), - useAdminCapabilities, - allowAccessIfAny([ - UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupManagement'), - UserMembershipAuthorization.hasAdminCapability('modify-group-manager'), - ]), - ], - - requireGroupAdminAccess: [ - AuthenticationController.requireLogin(), - fetchEntityConfig('groupAdmin'), - fetchEntity(), - requireEntity(), - allowAccessIfAny([ - UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupManagement'), - ]), - ], - - requireGroupSettingsReadAccess: [ - AuthenticationController.requireLogin(), - fetchEntityConfig('groupAdmin'), - fetchEntity(), - requireEntity(), - allowAccessIfAny([ - UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupManagement'), - ]), - ], - - requireGroupSettingsWriteAccess: [ - AuthenticationController.requireLogin(), - fetchEntityConfig('groupAdmin'), - fetchEntity(), - requireEntity(), - allowAccessIfAny([ - UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupManagement'), - UserMembershipAuthorization.hasAdminCapability('modify-group-setting'), - ]), - ], - requireInstitutionMetricsAccess: [ AuthenticationController.requireLogin(), fetchEntityConfig('institution'), diff --git a/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs b/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs index 5f6c87df21..6676c4c19e 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs @@ -21,34 +21,34 @@ export default { // group members routes webRouter.get( '/manage/groups/:id/members', - UserMembershipMiddleware.requireGroupManagementAccess, + UserMembershipMiddleware.requireEntityAccessOrAdminAccess('group'), UserMembershipController.manageGroupMembers ) webRouter.post( '/manage/groups/:id/invites', - UserMembershipMiddleware.requireGroupMemberManagementAccess, + UserMembershipMiddleware.requireGroupMemberManagement('group'), RateLimiterMiddleware.rateLimit(rateLimiters.createTeamInvite), TeamInvitesController.createInvite ) webRouter.post( '/manage/groups/:id/resendInvite', - UserMembershipMiddleware.requireGroupMemberManagementAccess, + UserMembershipMiddleware.requireGroupMemberManagement('group'), RateLimiterMiddleware.rateLimit(rateLimiters.createTeamInvite), TeamInvitesController.resendInvite ) webRouter.delete( '/manage/groups/:id/user/:user_id', - UserMembershipMiddleware.requireGroupManagementAccess, + UserMembershipMiddleware.requireGroupMemberManagement('group'), SubscriptionGroupController.removeUserFromGroup ) webRouter.delete( '/manage/groups/:id/invites/:email', - UserMembershipMiddleware.requireGroupMemberManagementAccess, + UserMembershipMiddleware.requireGroupMemberManagement('group'), TeamInvitesController.revokeInvite ) webRouter.get( '/manage/groups/:id/members/export', - UserMembershipMiddleware.requireGroupManagementAccess, + UserMembershipMiddleware.requireEntityAccessOrAdminAccess('group'), RateLimiterMiddleware.rateLimit(rateLimiters.exportTeamCsv), UserMembershipController.exportCsv ) @@ -56,17 +56,29 @@ export default { // group managers routes webRouter.get( '/manage/groups/:id/managers', - UserMembershipMiddleware.requireGroupManagersManagementAccess, + UserMembershipMiddleware.requireEntityAccess({ + entityName: 'groupManagers', + staffAccess: 'groupManagement', + adminCapability: 'view-group-manager', + }), UserMembershipController.manageGroupManagers ) webRouter.post( '/manage/groups/:id/managers', - UserMembershipMiddleware.requireGroupManagersWriteAccess, + UserMembershipMiddleware.requireEntityAccess({ + entityName: 'groupManagers', + staffAccess: 'groupManagement', + adminCapability: 'modify-group-manager', + }), UserMembershipController.add ) webRouter.delete( '/manage/groups/:id/managers/:userId', - UserMembershipMiddleware.requireGroupManagersWriteAccess, + UserMembershipMiddleware.requireEntityAccess({ + entityName: 'groupManagers', + staffAccess: 'groupManagement', + adminCapability: 'modify-group-manager', + }), UserMembershipController.remove )