Jelajahi Sumber

replace staff access with role based sytem (#30004)

- remove references to staff access

GitOrigin-RevId: 5d7df3ae8bc78aa02b65ec0dac0a323520c3df15
Anna Claire Fields 6 bulan lalu
induk
melakukan
ff8f77d85c

+ 1 - 17
services/web/app/src/Features/Authentication/AuthenticationController.mjs

@@ -62,20 +62,6 @@ function checkCredentials(userDetailsMap, user, password) {
   return isValid
   return isValid
 }
 }
 
 
-function reduceStaffAccess(staffAccess) {
-  const reducedStaffAccess = {}
-  for (const field in staffAccess) {
-    if (staffAccess[field]) {
-      reducedStaffAccess[field] = true
-    }
-  }
-  return reducedStaffAccess
-}
-
-function userHasStaffAccess(user) {
-  return user.staffAccess && Object.values(user.staffAccess).includes(true)
-}
-
 // TODO: Finish making these methods async
 // TODO: Finish making these methods async
 const AuthenticationController = {
 const AuthenticationController = {
   serializeUser(user, callback) {
   serializeUser(user, callback) {
@@ -100,9 +86,7 @@ const AuthenticationController = {
     }
     }
     if (user.isAdmin) {
     if (user.isAdmin) {
       lightUser.isAdmin = true
       lightUser.isAdmin = true
-    }
-    if (userHasStaffAccess(user)) {
-      lightUser.staffAccess = reduceStaffAccess(user.staffAccess)
+      lightUser.adminRoles = user.adminRoles
     }
     }
 
 
     callback(null, lightUser)
     callback(null, lightUser)

+ 0 - 16
services/web/app/src/Features/Helpers/AuthorizationHelper.mjs

@@ -1,16 +0,0 @@
-import { UserSchema } from '../../models/User.mjs'
-
-export default {
-  hasAnyStaffAccess,
-}
-
-function hasAnyStaffAccess(user) {
-  if (!user.staffAccess) {
-    return false
-  }
-
-  for (const key of Object.keys(UserSchema.obj.staffAccess)) {
-    if (user.staffAccess[key]) return true
-  }
-  return false
-}

+ 1 - 34
services/web/app/src/Features/Subscription/SubscriptionController.mjs

@@ -172,39 +172,6 @@ function formatGroupPlansDataForDash() {
   }
   }
 }
 }
 
 
-/**
- * Trim the staffAccess object to only include allowed fields
- * @param {Object} user - The user object with mongoose object fields
- * @returns {Object} - User object with trimmed staffAccess
- */
-function _trimStaffAccess(user) {
-  if (!user || !user.staffAccess) return user
-
-  const allowedFields = [
-    'publisherMetrics',
-    'publisherManagement',
-    'institutionMetrics',
-    'institutionManagement',
-    'groupMetrics',
-    'groupManagement',
-    'adminMetrics',
-    'splitTestMetrics',
-    'splitTestManagement',
-  ]
-
-  const trimmedStaffAccess = allowedFields.reduce((acc, key) => {
-    if (key in user.staffAccess) {
-      acc[key] = user.staffAccess[key]
-    }
-    return acc
-  }, {})
-
-  return {
-    ...user,
-    staffAccess: trimmedStaffAccess,
-  }
-}
-
 async function userSubscriptionPage(req, res) {
 async function userSubscriptionPage(req, res) {
   const user = SessionManager.getSessionUser(req.session)
   const user = SessionManager.getSessionUser(req.session)
   await SplitTestHandler.promises.getAssignment(req, res, 'pause-subscription')
   await SplitTestHandler.promises.getAssignment(req, res, 'pause-subscription')
@@ -337,7 +304,7 @@ async function userSubscriptionPage(req, res) {
     title: 'your_subscriptions',
     title: 'your_subscriptions',
     plans: plansData?.plans,
     plans: plansData?.plans,
     planCodesChangingAtTermEnd: plansData?.planCodesChangingAtTermEnd,
     planCodesChangingAtTermEnd: plansData?.planCodesChangingAtTermEnd,
-    user: _trimStaffAccess(user),
+    user,
     hasSubscription,
     hasSubscription,
     fromPlansPage,
     fromPlansPage,
     redirectedPaymentErrorCode,
     redirectedPaymentErrorCode,

+ 5 - 22
services/web/app/src/Features/UserMembership/UserMembershipAuthorization.mjs

@@ -1,34 +1,17 @@
 import AdminAuthorizationHelper from '../Helpers/AdminAuthorizationHelper.mjs'
 import AdminAuthorizationHelper from '../Helpers/AdminAuthorizationHelper.mjs'
 import SessionManager from '../Authentication/SessionManager.mjs'
 import SessionManager from '../Authentication/SessionManager.mjs'
-import Settings from '@overleaf/settings'
-
-const { hasAdminCapability, hasAdminAccess } = AdminAuthorizationHelper
 
 
 const UserMembershipAuthorization = {
 const UserMembershipAuthorization = {
-  hasStaffAccess(requiredStaffAccess) {
-    return req => {
-      if (!req.user) {
-        return false
-      }
-      return (
-        requiredStaffAccess &&
-        req.user.staffAccess &&
-        req.user.staffAccess[requiredStaffAccess]
-      )
-    }
-  },
-
-  hasAdminCapability,
+  hasAdminCapability: AdminAuthorizationHelper.hasAdminCapability,
 
 
-  hasAnyAdminRole(req) {
-    return (
-      Settings.adminRolesEnabled &&
-      hasAdminAccess(SessionManager.getSessionUser(req.session))
+  hasAdminAccess(req) {
+    return AdminAuthorizationHelper.hasAdminAccess(
+      SessionManager.getSessionUser(req.session)
     )
     )
   },
   },
 
 
   hasModifyGroupMemberCapability(req, res) {
   hasModifyGroupMemberCapability(req, res) {
-    return hasAdminCapability(
+    return AdminAuthorizationHelper.hasAdminCapability(
       req.entity.managedUsersEnabled
       req.entity.managedUsersEnabled
         ? 'modify-managed-group-member'
         ? 'modify-managed-group-member'
         : 'modify-group-member',
         : 'modify-group-member',

+ 37 - 38
services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs

@@ -23,7 +23,7 @@ const UserMembershipMiddleware = {
     requireEntity(),
     requireEntity(),
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('groupMetrics'),
+      UserMembershipAuthorization.hasAdminAccess,
     ]),
     ]),
   ],
   ],
 
 
@@ -36,7 +36,7 @@ const UserMembershipMiddleware = {
     requireEntity(),
     requireEntity(),
   ],
   ],
 
 
-  requireEntityAccess: ({ entityName, staffAccess, adminCapability }) => [
+  requireEntityAccess: ({ entityName, adminCapability }) => [
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     fetchEntityConfig(entityName),
     fetchEntityConfig(entityName),
     fetchEntity(),
     fetchEntity(),
@@ -44,7 +44,6 @@ const UserMembershipMiddleware = {
     allowAccessIfAny(
     allowAccessIfAny(
       [
       [
         UserMembershipAuthorization.hasEntityAccess(),
         UserMembershipAuthorization.hasEntityAccess(),
-        staffAccess && UserMembershipAuthorization.hasStaffAccess(staffAccess),
         adminCapability &&
         adminCapability &&
           UserMembershipAuthorization.hasAdminCapability(adminCapability),
           UserMembershipAuthorization.hasAdminCapability(adminCapability),
       ].filter(Boolean)
       ].filter(Boolean)
@@ -58,9 +57,7 @@ const UserMembershipMiddleware = {
     requireEntity(),
     requireEntity(),
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('groupManagement'),
-      // allow to all admins when `adminRolesEnabled` is true
-      UserMembershipAuthorization.hasAnyAdminRole,
+      UserMembershipAuthorization.hasAdminCapability('modify-group'),
     ]),
     ]),
   ],
   ],
 
 
@@ -72,7 +69,6 @@ const UserMembershipMiddleware = {
     useAdminCapabilities,
     useAdminCapabilities,
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('groupManagement'),
       UserMembershipAuthorization.hasModifyGroupMemberCapability,
       UserMembershipAuthorization.hasModifyGroupMemberCapability,
     ]),
     ]),
   ],
   ],
@@ -84,7 +80,7 @@ const UserMembershipMiddleware = {
     requireEntity(),
     requireEntity(),
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('groupMetrics'),
+      UserMembershipAuthorization.hasAdminAccess,
     ]),
     ]),
   ],
   ],
 
 
@@ -92,10 +88,10 @@ const UserMembershipMiddleware = {
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     fetchEntityConfig('institution'),
     fetchEntityConfig('institution'),
     fetchEntity(),
     fetchEntity(),
-    requireEntityOrCreate('institutionManagement'),
+    requireEntityOrCreate(),
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('institutionMetrics'),
+      UserMembershipAuthorization.hasAdminAccess,
     ]),
     ]),
   ],
   ],
 
 
@@ -103,31 +99,40 @@ const UserMembershipMiddleware = {
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     fetchEntityConfig('institution'),
     fetchEntityConfig('institution'),
     fetchEntity(),
     fetchEntity(),
-    requireEntityOrCreate('institutionManagement'),
+    requireEntityOrCreate(),
+    useAdminCapabilities,
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
+      UserMembershipAuthorization.hasAdminCapability(
+        'modify-institution-manager'
+      ),
     ]),
     ]),
   ],
   ],
 
 
-  requireInstitutionManagementStaffAccess: [
+  requireInstitutionAIAccess: [
+    AuthenticationController.requireLogin(),
+    fetchEntityConfig('institution'),
+    fetchEntity(),
+    requireEntityOrCreate(),
+    allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
+  ],
+
+  requireInstitutionStaffHubAccess: [
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
-    allowAccessIfAny([
-      UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
-    ]),
     fetchEntityConfig('institution'),
     fetchEntityConfig('institution'),
     fetchEntity(),
     fetchEntity(),
-    requireEntityOrCreate('institutionManagement'),
+    requireEntityOrCreate(),
+    allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
   ],
   ],
 
 
   requirePublisherMetricsAccess: [
   requirePublisherMetricsAccess: [
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     fetchEntityConfig('publisher'),
     fetchEntityConfig('publisher'),
     fetchEntity(),
     fetchEntity(),
-    requireEntityOrCreate('publisherManagement'),
+    requireEntityOrCreate(),
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
+      UserMembershipAuthorization.hasAdminAccess,
     ]),
     ]),
   ],
   ],
 
 
@@ -135,10 +140,13 @@ const UserMembershipMiddleware = {
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     fetchEntityConfig('publisher'),
     fetchEntityConfig('publisher'),
     fetchEntity(),
     fetchEntity(),
-    requireEntityOrCreate('publisherManagement'),
+    requireEntityOrCreate(),
+    useAdminCapabilities,
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('publisherManagement'),
+      UserMembershipAuthorization.hasAdminCapability(
+        'modify-publisher-manager'
+      ),
     ]),
     ]),
   ],
   ],
 
 
@@ -146,18 +154,16 @@ const UserMembershipMiddleware = {
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     fetchEntityConfig('publisher'),
     fetchEntityConfig('publisher'),
     fetchEntity(),
     fetchEntity(),
-    requireEntityOrCreate('publisherManagement'),
+    requireEntityOrCreate(),
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
+      UserMembershipAuthorization.hasAdminAccess,
     ]),
     ]),
   ],
   ],
 
 
   requireAdminMetricsAccess: [
   requireAdminMetricsAccess: [
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
-    allowAccessIfAny([
-      UserMembershipAuthorization.hasStaffAccess('adminMetrics'),
-    ]),
+    allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
   ],
   ],
 
 
   requireTemplateMetricsAccess: [
   requireTemplateMetricsAccess: [
@@ -168,23 +174,19 @@ const UserMembershipMiddleware = {
     fetchPublisherFromTemplate(),
     fetchPublisherFromTemplate(),
     allowAccessIfAny([
     allowAccessIfAny([
       UserMembershipAuthorization.hasEntityAccess(),
       UserMembershipAuthorization.hasEntityAccess(),
-      UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
+      UserMembershipAuthorization.hasAdminAccess,
     ]),
     ]),
   ],
   ],
 
 
   requirePublisherCreationAccess: [
   requirePublisherCreationAccess: [
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
-    allowAccessIfAny([
-      UserMembershipAuthorization.hasStaffAccess('publisherManagement'),
-    ]),
+    allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
     fetchEntityConfig('publisher'),
     fetchEntityConfig('publisher'),
   ],
   ],
 
 
   requireInstitutionCreationAccess: [
   requireInstitutionCreationAccess: [
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
-    allowAccessIfAny([
-      UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
-    ]),
+    allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
     fetchEntityConfig('institution'),
     fetchEntityConfig('institution'),
   ],
   ],
 
 
@@ -192,8 +194,6 @@ const UserMembershipMiddleware = {
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     useAdminCapabilities,
     useAdminCapabilities,
     allowAccessIfAny([
     allowAccessIfAny([
-      UserMembershipAuthorization.hasStaffAccess('splitTestMetrics'),
-      UserMembershipAuthorization.hasStaffAccess('splitTestManagement'),
       UserMembershipAuthorization.hasAdminCapability('view-split-test'),
       UserMembershipAuthorization.hasAdminCapability('view-split-test'),
     ]),
     ]),
   ],
   ],
@@ -202,7 +202,6 @@ const UserMembershipMiddleware = {
     AuthenticationController.requireLogin(),
     AuthenticationController.requireLogin(),
     useAdminCapabilities,
     useAdminCapabilities,
     allowAccessIfAny([
     allowAccessIfAny([
-      UserMembershipAuthorization.hasStaffAccess('splitTestManagement'),
       UserMembershipAuthorization.hasAdminCapability('modify-split-test'),
       UserMembershipAuthorization.hasAdminCapability('modify-split-test'),
     ]),
     ]),
   ],
   ],
@@ -325,13 +324,13 @@ function requireEntity() {
 
 
 // ensure an entity was found or redirect to entity creation page if the user
 // ensure an entity was found or redirect to entity creation page if the user
 // has permissions to create the entity, or fail with 404
 // has permissions to create the entity, or fail with 404
-function requireEntityOrCreate(creationStaffAccess) {
+function requireEntityOrCreate() {
   return (req, res, next) => {
   return (req, res, next) => {
     if (req.entity) {
     if (req.entity) {
       return next()
       return next()
     }
     }
 
 
-    if (UserMembershipAuthorization.hasStaffAccess(creationStaffAccess)(req)) {
+    if (UserMembershipAuthorization.hasAdminAccess(req)) {
       res.redirect(`/entities/${req.entityName}/create/${req.params.id}`)
       res.redirect(`/entities/${req.entityName}/create/${req.params.id}`)
       return
       return
     }
     }

+ 0 - 3
services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs

@@ -58,7 +58,6 @@ export default {
       '/manage/groups/:id/managers',
       '/manage/groups/:id/managers',
       UserMembershipMiddleware.requireEntityAccess({
       UserMembershipMiddleware.requireEntityAccess({
         entityName: 'groupManagers',
         entityName: 'groupManagers',
-        staffAccess: 'groupManagement',
         adminCapability: 'view-group-manager',
         adminCapability: 'view-group-manager',
       }),
       }),
       UserMembershipController.manageGroupManagers
       UserMembershipController.manageGroupManagers
@@ -67,7 +66,6 @@ export default {
       '/manage/groups/:id/managers',
       '/manage/groups/:id/managers',
       UserMembershipMiddleware.requireEntityAccess({
       UserMembershipMiddleware.requireEntityAccess({
         entityName: 'groupManagers',
         entityName: 'groupManagers',
-        staffAccess: 'groupManagement',
         adminCapability: 'modify-group-manager',
         adminCapability: 'modify-group-manager',
       }),
       }),
       UserMembershipController.add
       UserMembershipController.add
@@ -76,7 +74,6 @@ export default {
       '/manage/groups/:id/managers/:userId',
       '/manage/groups/:id/managers/:userId',
       UserMembershipMiddleware.requireEntityAccess({
       UserMembershipMiddleware.requireEntityAccess({
         entityName: 'groupManagers',
         entityName: 'groupManagers',
-        staffAccess: 'groupManagement',
         adminCapability: 'modify-group-manager',
         adminCapability: 'modify-group-manager',
       }),
       }),
       UserMembershipController.remove
       UserMembershipController.remove

+ 0 - 11
services/web/app/src/models/User.mjs

@@ -59,17 +59,6 @@ export const UserSchema = new Schema(
     },
     },
     isAdmin: { type: Boolean, default: false },
     isAdmin: { type: Boolean, default: false },
     adminRoles: { type: Array },
     adminRoles: { type: Array },
-    staffAccess: {
-      publisherMetrics: { type: Boolean, default: false },
-      publisherManagement: { type: Boolean, default: false },
-      institutionMetrics: { type: Boolean, default: false },
-      institutionManagement: { type: Boolean, default: false },
-      groupMetrics: { type: Boolean, default: false },
-      groupManagement: { type: Boolean, default: false },
-      adminMetrics: { type: Boolean, default: false },
-      splitTestMetrics: { type: Boolean, default: false },
-      splitTestManagement: { type: Boolean, default: false },
-    },
     signUpDate: {
     signUpDate: {
       type: Date,
       type: Date,
       default() {
       default() {

+ 1 - 2
services/web/app/views/layout-react.pug

@@ -11,9 +11,8 @@ block append meta
 	- const canDisplayAdminMenu = hasAdminAccess()
 	- const canDisplayAdminMenu = hasAdminAccess()
 	- const canDisplayAdminRedirect = canRedirectToAdminDomain()
 	- const canDisplayAdminRedirect = canRedirectToAdminDomain()
 	- const sessionUser = getSessionUser()
 	- const sessionUser = getSessionUser()
-	- const staffAccess = sessionUser?.staffAccess
 	- const canDisplayProjectUrlLookup = settings.adminPrivilegeAvailable && canDisplayAdminMenu && hasAdminCapability('view-project-setting', false)
 	- const canDisplayProjectUrlLookup = settings.adminPrivilegeAvailable && canDisplayAdminMenu && hasAdminCapability('view-project-setting', false)
-	- const canDisplaySplitTestMenu = hasFeature('saas') && ((canDisplayAdminMenu && hasAdminCapability('view-split-test')) || staffAccess?.splitTestMetrics || staffAccess?.splitTestManagement)
+	- const canDisplaySplitTestMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('view-split-test')
 	- const canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('manage-survey', false)
 	- const canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('manage-survey', false)
 	- const canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu
 	- const canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu
 	- const enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
 	- const enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')

+ 1 - 1
services/web/app/views/layout/navbar-marketing.pug

@@ -31,7 +31,7 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(
 		- var canDisplayAdminMenu = hasAdminAccess()
 		- var canDisplayAdminMenu = hasAdminAccess()
 		- var canDisplayAdminRedirect = canRedirectToAdminDomain()
 		- var canDisplayAdminRedirect = canRedirectToAdminDomain()
 		- var canDisplayProjectUrlLookup = settings.adminPrivilegeAvailable && canDisplayAdminMenu && hasAdminCapability('view-project-setting', false)
 		- var canDisplayProjectUrlLookup = settings.adminPrivilegeAvailable && canDisplayAdminMenu && hasAdminCapability('view-project-setting', false)
-		- var canDisplaySplitTestMenu = hasFeature('saas') && ((canDisplayAdminMenu && hasAdminCapability('view-split-test')) || (getSessionUser() && getSessionUser().staffAccess && (getSessionUser().staffAccess.splitTestMetrics || getSessionUser().staffAccess.splitTestManagement)))
+		- var canDisplaySplitTestMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('view-split-test')
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('manage-survey', false)
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('manage-survey', false)
 		- var canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu
 		- var canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu
 
 

+ 1 - 1
services/web/app/views/user_membership/group-managers-react.pug

@@ -4,7 +4,7 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/group-managers'
 	- entrypoint = 'pages/user/subscription/group-management/group-managers'
 
 
 block append meta
 block append meta
-	- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability('modify-group-manager')) || (getSessionUser().staffAccess && getSessionUser().staffAccess.groupManagement)
+	- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability('modify-group-manager'))
 	meta(name='ol-user' data-type='json' content=user)
 	meta(name='ol-user' data-type='json' content=user)
 	meta(name='ol-users' data-type='json' content=users)
 	meta(name='ol-users' data-type='json' content=users)
 	meta(name='ol-groupId' data-type='string' content=groupId)
 	meta(name='ol-groupId' data-type='string' content=groupId)

+ 1 - 1
services/web/app/views/user_membership/group-members-react.pug

@@ -4,7 +4,7 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/group-members'
 	- entrypoint = 'pages/user/subscription/group-management/group-members'
 
 
 block append meta
 block append meta
-	- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability(managedUsersActive ? 'modify-managed-group-member' : 'modify-group-member')) || (getSessionUser().staffAccess && getSessionUser().staffAccess.groupManagement)
+	- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability(managedUsersActive ? 'modify-managed-group-member' : 'modify-group-member'))
 	meta(name='ol-user' data-type='json' content=user)
 	meta(name='ol-user' data-type='json' content=user)
 	meta(name='ol-users' data-type='json' content=users)
 	meta(name='ol-users' data-type='json' content=users)
 	meta(name='ol-groupId' data-type='string' content=groupId)
 	meta(name='ol-groupId' data-type='string' content=groupId)

+ 0 - 94
services/web/scripts/back_fill_staff_access.mjs

@@ -1,94 +0,0 @@
-import {
-  db,
-  READ_PREFERENCE_SECONDARY,
-} from '../app/src/infrastructure/mongodb.mjs'
-import UserSessionsManager from '../app/src/Features/User/UserSessionsManager.mjs'
-import { scriptRunner } from './lib/ScriptRunner.mjs'
-
-const COMMIT = process.argv.includes('--commit')
-const KEEP_SESSIONS = process.argv.includes('--keep-sessions')
-
-const FULL_STAFF_ACCESS = {
-  publisherMetrics: true,
-  publisherManagement: true,
-  institutionMetrics: true,
-  institutionManagement: true,
-  groupMetrics: true,
-  groupManagement: true,
-  adminMetrics: true,
-  splitTestMetrics: true,
-  splitTestManagement: true,
-}
-
-function doesNotHaveFullStaffAccess(user) {
-  if (!user.staffAccess) {
-    return true
-  }
-  for (const field of Object.keys(FULL_STAFF_ACCESS)) {
-    if (!user.staffAccess[field]) {
-      return true
-    }
-  }
-  return false
-}
-
-function formatUser(user) {
-  user = Object.assign({}, user, user.staffAccess)
-  delete user.staffAccess
-  return user
-}
-
-async function main() {
-  const adminUsers = await db.users
-    .find(
-      { isAdmin: true },
-      {
-        projection: {
-          _id: 1,
-          email: 1,
-          staffAccess: 1,
-        },
-        readPreference: READ_PREFERENCE_SECONDARY,
-      }
-    )
-    .toArray()
-
-  console.log('All Admin users:')
-  console.table(adminUsers.map(formatUser))
-
-  const incompleteUsers = adminUsers.filter(doesNotHaveFullStaffAccess)
-  if (incompleteUsers.length === 0) {
-    console.warn('All Admin users have full staff access.')
-    return
-  }
-
-  console.log()
-  console.log('Incomplete staff access:')
-  console.table(incompleteUsers.map(formatUser))
-
-  if (COMMIT) {
-    for (const user of incompleteUsers) {
-      console.error(
-        `Granting ${user.email} (${user._id.toString()}) full staff access`
-      )
-      await db.users.updateOne(
-        { _id: user._id, isAdmin: true },
-        { $set: { staffAccess: FULL_STAFF_ACCESS } }
-      )
-      if (!KEEP_SESSIONS) {
-        await UserSessionsManager.promises.removeSessionsFromRedis(user)
-      }
-    }
-  } else {
-    console.warn('Use --commit to grant missing staff access.')
-  }
-}
-
-try {
-  await scriptRunner(main)
-  console.error('Done.')
-  process.exit(0)
-} catch (error) {
-  console.error({ error })
-  process.exit(1)
-}

+ 22 - 6
services/web/test/acceptance/src/UserMembershipAuthorizationTests.mjs

@@ -6,6 +6,7 @@ import Subscription from './helpers/Subscription.mjs'
 import Publisher from './helpers/Publisher.mjs'
 import Publisher from './helpers/Publisher.mjs'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import RecurlyClient from '../../../app/src/Features/Subscription/RecurlyClient.mjs'
 import RecurlyClient from '../../../app/src/Features/Subscription/RecurlyClient.mjs'
+import Settings from '@overleaf/settings'
 import Features from '../../../app/src/infrastructure/Features.mjs'
 import Features from '../../../app/src/infrastructure/Features.mjs'
 
 
 describe('UserMembershipAuthorization', function () {
 describe('UserMembershipAuthorization', function () {
@@ -16,6 +17,16 @@ describe('UserMembershipAuthorization', function () {
 
 
     this.user = new User()
     this.user = new User()
     sinon.stub(RecurlyClient.promises, 'getSubscription').resolves({})
     sinon.stub(RecurlyClient.promises, 'getSubscription').resolves({})
+
+    this.adminRolesEnabledStub = sinon.stub(Settings, 'adminRolesEnabled')
+    this.adminRolesEnabledStub.value(true)
+
+    this.adminPrivilegeAvailableStub = sinon.stub(
+      Settings,
+      'adminPrivilegeAvailable'
+    )
+    this.adminPrivilegeAvailableStub.value(true)
+
     async.series([this.user.ensureUserExists.bind(this.user)], done)
     async.series([this.user.ensureUserExists.bind(this.user)], done)
   })
   })
 
 
@@ -25,6 +36,8 @@ describe('UserMembershipAuthorization', function () {
     }
     }
 
 
     RecurlyClient.promises.getSubscription.restore()
     RecurlyClient.promises.getSubscription.restore()
+    this.adminRolesEnabledStub.restore()
+    this.adminPrivilegeAvailableStub.restore()
   })
   })
 
 
   describe('group', function () {
   describe('group', function () {
@@ -92,13 +105,14 @@ describe('UserMembershipAuthorization', function () {
     })
     })
 
 
     describe('creation', function () {
     describe('creation', function () {
-      it('should allow staff only', function (done) {
+      it('should allow admin only', function (done) {
         const url = `/entities/institution/create/foo`
         const url = `/entities/institution/create/foo`
         async.series(
         async.series(
           [
           [
             this.user.login.bind(this.user),
             this.user.login.bind(this.user),
             expectAccess(this.user, url, 403),
             expectAccess(this.user, url, 403),
-            cb => this.user.ensureStaffAccess('institutionManagement', cb),
+            cb => this.user.ensureAdmin(cb),
+            cb => this.user.ensureAdminRole('engineering', cb),
             this.user.login.bind(this.user),
             this.user.login.bind(this.user),
             expectAccess(this.user, url, 200),
             expectAccess(this.user, url, 200),
           ],
           ],
@@ -135,13 +149,14 @@ describe('UserMembershipAuthorization', function () {
     })
     })
 
 
     describe('creation', function () {
     describe('creation', function () {
-      it('should redirect staff only', function (done) {
+      it('should redirect admin only', function (done) {
         const url = `/manage/publishers/foo/managers`
         const url = `/manage/publishers/foo/managers`
         async.series(
         async.series(
           [
           [
             this.user.login.bind(this.user),
             this.user.login.bind(this.user),
             expectAccess(this.user, url, 404),
             expectAccess(this.user, url, 404),
-            cb => this.user.ensureStaffAccess('publisherManagement', cb),
+            cb => this.user.ensureAdmin(cb),
+            cb => this.user.ensureAdminRole('engineering', cb),
             this.user.login.bind(this.user),
             this.user.login.bind(this.user),
             expectAccess(this.user, url, 302, /\/create/),
             expectAccess(this.user, url, 302, /\/create/),
           ],
           ],
@@ -149,12 +164,13 @@ describe('UserMembershipAuthorization', function () {
         )
         )
       })
       })
 
 
-      it('should allow staff only', function (done) {
+      it('should allow admin only', function (done) {
         const url = `/entities/publisher/create/foo`
         const url = `/entities/publisher/create/foo`
         async.series(
         async.series(
           [
           [
             expectAccess(this.user, url, 403),
             expectAccess(this.user, url, 403),
-            cb => this.user.ensureStaffAccess('publisherManagement', cb),
+            cb => this.user.ensureAdmin(cb),
+            cb => this.user.ensureAdminRole('engineering', cb),
             this.user.login.bind(this.user),
             this.user.login.bind(this.user),
             expectAccess(this.user, url, 200),
             expectAccess(this.user, url, 200),
           ],
           ],

+ 1 - 7
services/web/test/acceptance/src/helpers/User.mjs

@@ -580,13 +580,7 @@ class User {
   }
   }
 
 
   ensureAdminRole(role, callback) {
   ensureAdminRole(role, callback) {
-    this.mongoUpdate({ $addToSet: { adminRoles: 'engineering' } }, callback)
-  }
-
-  ensureStaffAccess(flag, callback) {
-    const update = { $set: {} }
-    update.$set[`staffAccess.${flag}`] = true
-    this.mongoUpdate(update, callback)
+    this.mongoUpdate({ $addToSet: { adminRoles: role } }, callback)
   }
   }
 
 
   upgradeSomeFeatures(callback) {
   upgradeSomeFeatures(callback) {

+ 0 - 63
services/web/test/unit/src/Authentication/AuthenticationController.test.mjs

@@ -35,34 +35,6 @@ describe('AuthenticationController', function () {
       referal_id: 1234,
       referal_id: 1234,
       isAdmin: false,
       isAdmin: false,
     }
     }
-    ctx.staffUser = {
-      ...ctx.user,
-      staffAccess: {
-        publisherMetrics: true,
-        publisherManagement: false,
-        institutionMetrics: true,
-        institutionManagement: false,
-        groupMetrics: true,
-        groupManagement: false,
-        adminMetrics: true,
-        splitTestMetrics: false,
-        splitTestManagement: true,
-      },
-    }
-    ctx.noStaffAccessUser = {
-      ...ctx.user,
-      staffAccess: {
-        publisherMetrics: false,
-        publisherManagement: false,
-        institutionMetrics: false,
-        institutionManagement: false,
-        groupMetrics: false,
-        groupManagement: false,
-        adminMetrics: false,
-        splitTestMetrics: false,
-        splitTestManagement: false,
-      },
-    }
     ctx.password = 'banana'
     ctx.password = 'banana'
     ctx.req = new MockRequest(vi)
     ctx.req = new MockRequest(vi)
     ctx.res = new MockResponse(vi)
     ctx.res = new MockResponse(vi)
@@ -325,41 +297,6 @@ describe('AuthenticationController', function () {
         expect(ctx.callback).to.have.been.calledWith(null, isAdminMatcher)
         expect(ctx.callback).to.have.been.calledWith(null, isAdminMatcher)
       })
       })
     })
     })
-
-    describe('when staffAccess fields are provided', function () {
-      it('only returns the fields set to true', function (ctx) {
-        const expectedStaffAccess = {
-          publisherMetrics: true,
-          institutionMetrics: true,
-          groupMetrics: true,
-          adminMetrics: true,
-          splitTestManagement: true,
-        }
-        const staffAccessMatcher = sinon.match(value => {
-          return (
-            Object.keys(value.staffAccess).length ===
-            Object.keys(expectedStaffAccess).length
-          )
-        })
-
-        ctx.AuthenticationController.serializeUser(ctx.staffUser, ctx.callback)
-        expect(ctx.callback).to.have.been.calledWith(null, staffAccessMatcher)
-      })
-    })
-
-    describe('when all staffAccess fields are false', function () {
-      it('no staffAccess attribute is set', function (ctx) {
-        const staffAccessMatcher = sinon.match(value => {
-          return !('staffAccess' in value)
-        })
-
-        ctx.AuthenticationController.serializeUser(
-          ctx.noStaffAccessUser,
-          ctx.callback
-        )
-        expect(ctx.callback).to.have.been.calledWith(null, staffAccessMatcher)
-      })
-    })
   })
   })
 
 
   describe('passportLogin', function () {
   describe('passportLogin', function () {

+ 0 - 67
services/web/test/unit/src/HelperFiles/AuthorizationHelper.test.mjs

@@ -1,67 +0,0 @@
-import { vi, expect } from 'vitest'
-
-const modulePath = '../../../../app/src/Features/Helpers/AuthorizationHelper'
-
-describe('AuthorizationHelper', function () {
-  beforeEach(async function (ctx) {
-    vi.doMock('../../../../app/src/models/User', () => ({
-      UserSchema: {
-        obj: {
-          staffAccess: {
-            publisherMetrics: {},
-            publisherManagement: {},
-            institutionMetrics: {},
-            institutionManagement: {},
-            groupMetrics: {},
-            groupManagement: {},
-            adminMetrics: {},
-          },
-        },
-      },
-    }))
-
-    vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
-      default: (ctx.ProjectGetter = { promises: {} }),
-    }))
-
-    vi.doMock(
-      '../../../../app/src/Features/SplitTests/SplitTestHandler',
-      () => ({
-        default: (ctx.SplitTestHandler = {
-          promises: {},
-        }),
-      })
-    )
-
-    ctx.AuthorizationHelper = (await import(modulePath)).default
-  })
-
-  describe('hasAnyStaffAccess', function () {
-    it('with empty user', function (ctx) {
-      const user = {}
-      expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
-    })
-
-    it('with no access user', function (ctx) {
-      const user = { isAdmin: false, staffAccess: { adminMetrics: false } }
-      expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
-    })
-
-    it('with admin user', function (ctx) {
-      const user = { isAdmin: true }
-      expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
-    })
-
-    it('with staff user', function (ctx) {
-      const user = { staffAccess: { adminMetrics: true, somethingElse: false } }
-      expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.true
-    })
-
-    it('with non-staff user with extra attributes', function (ctx) {
-      // make sure that staffAccess attributes not declared on the model don't
-      // give user access
-      const user = { staffAccess: { adminMetrics: false, somethingElse: true } }
-      expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
-    })
-  })
-})

+ 5 - 3
services/web/types/admin-capabilities.ts

@@ -11,25 +11,27 @@ export type AdminCapability =
   | 'modify-group-manager'
   | 'modify-group-manager'
   | 'modify-group-member'
   | 'modify-group-member'
   | 'modify-group-setting'
   | 'modify-group-setting'
+  | 'modify-institution-manager'
   | 'modify-managed-group'
   | 'modify-managed-group'
   | 'modify-managed-group-member'
   | 'modify-managed-group-member'
-  | 'modify-user-account-status'
   | 'modify-project-content'
   | 'modify-project-content'
   | 'modify-project-setting'
   | 'modify-project-setting'
+  | 'modify-publisher-manager'
   | 'manage-survey'
   | 'manage-survey'
   | 'modify-split-test'
   | 'modify-split-test'
+  | 'modify-user-account-status'
   | 'modify-user-email'
   | 'modify-user-email'
   | 'modify-user-name'
   | 'modify-user-name'
+  | 'update-stripe-customer-segment'
   | 'view-audit-log'
   | 'view-audit-log'
   | 'view-group-manager'
   | 'view-group-manager'
   | 'view-project-content'
   | 'view-project-content'
   | 'view-project-setting'
   | 'view-project-setting'
-  | 'view-session'
   | 'view-script-log'
   | 'view-script-log'
+  | 'view-session'
   | 'view-split-test'
   | 'view-split-test'
   | 'view-user-additional-info'
   | 'view-user-additional-info'
   | 'create-stripe-account'
   | 'create-stripe-account'
-  | 'update-stripe-customer-segment'
 
 
 export type AdminRole =
 export type AdminRole =
   | 'engagement'
   | 'engagement'