diff --git a/services/web/app/src/Features/Authentication/AuthenticationController.mjs b/services/web/app/src/Features/Authentication/AuthenticationController.mjs index c84479b25a..16acb68605 100644 --- a/services/web/app/src/Features/Authentication/AuthenticationController.mjs +++ b/services/web/app/src/Features/Authentication/AuthenticationController.mjs @@ -62,20 +62,6 @@ function checkCredentials(userDetailsMap, user, password) { 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 const AuthenticationController = { serializeUser(user, callback) { @@ -100,9 +86,7 @@ const AuthenticationController = { } if (user.isAdmin) { lightUser.isAdmin = true - } - if (userHasStaffAccess(user)) { - lightUser.staffAccess = reduceStaffAccess(user.staffAccess) + lightUser.adminRoles = user.adminRoles } callback(null, lightUser) diff --git a/services/web/app/src/Features/Helpers/AuthorizationHelper.mjs b/services/web/app/src/Features/Helpers/AuthorizationHelper.mjs deleted file mode 100644 index 9d16102113..0000000000 --- a/services/web/app/src/Features/Helpers/AuthorizationHelper.mjs +++ /dev/null @@ -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 -} diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.mjs b/services/web/app/src/Features/Subscription/SubscriptionController.mjs index 42a7ab9cf1..cd1951eb12 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.mjs +++ b/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) { const user = SessionManager.getSessionUser(req.session) await SplitTestHandler.promises.getAssignment(req, res, 'pause-subscription') @@ -337,7 +304,7 @@ async function userSubscriptionPage(req, res) { title: 'your_subscriptions', plans: plansData?.plans, planCodesChangingAtTermEnd: plansData?.planCodesChangingAtTermEnd, - user: _trimStaffAccess(user), + user, hasSubscription, fromPlansPage, redirectedPaymentErrorCode, diff --git a/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.mjs b/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.mjs index e89d0b6946..025ecc63fe 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipAuthorization.mjs @@ -1,34 +1,17 @@ import AdminAuthorizationHelper from '../Helpers/AdminAuthorizationHelper.mjs' import SessionManager from '../Authentication/SessionManager.mjs' -import Settings from '@overleaf/settings' - -const { hasAdminCapability, hasAdminAccess } = AdminAuthorizationHelper const UserMembershipAuthorization = { - hasStaffAccess(requiredStaffAccess) { - return req => { - if (!req.user) { - return false - } - return ( - requiredStaffAccess && - req.user.staffAccess && - req.user.staffAccess[requiredStaffAccess] - ) - } - }, + hasAdminCapability: AdminAuthorizationHelper.hasAdminCapability, - hasAdminCapability, - - hasAnyAdminRole(req) { - return ( - Settings.adminRolesEnabled && - hasAdminAccess(SessionManager.getSessionUser(req.session)) + hasAdminAccess(req) { + return AdminAuthorizationHelper.hasAdminAccess( + SessionManager.getSessionUser(req.session) ) }, hasModifyGroupMemberCapability(req, res) { - return hasAdminCapability( + return AdminAuthorizationHelper.hasAdminCapability( req.entity.managedUsersEnabled ? 'modify-managed-group-member' : 'modify-group-member', diff --git a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs index 6088303391..dfaaee3b3e 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs @@ -23,7 +23,7 @@ const UserMembershipMiddleware = { requireEntity(), allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupMetrics'), + UserMembershipAuthorization.hasAdminAccess, ]), ], @@ -36,7 +36,7 @@ const UserMembershipMiddleware = { requireEntity(), ], - requireEntityAccess: ({ entityName, staffAccess, adminCapability }) => [ + requireEntityAccess: ({ entityName, adminCapability }) => [ AuthenticationController.requireLogin(), fetchEntityConfig(entityName), fetchEntity(), @@ -44,7 +44,6 @@ const UserMembershipMiddleware = { allowAccessIfAny( [ UserMembershipAuthorization.hasEntityAccess(), - staffAccess && UserMembershipAuthorization.hasStaffAccess(staffAccess), adminCapability && UserMembershipAuthorization.hasAdminCapability(adminCapability), ].filter(Boolean) @@ -58,9 +57,7 @@ const UserMembershipMiddleware = { requireEntity(), allowAccessIfAny([ 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, allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupManagement'), UserMembershipAuthorization.hasModifyGroupMemberCapability, ]), ], @@ -84,7 +80,7 @@ const UserMembershipMiddleware = { requireEntity(), allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('groupMetrics'), + UserMembershipAuthorization.hasAdminAccess, ]), ], @@ -92,10 +88,10 @@ const UserMembershipMiddleware = { AuthenticationController.requireLogin(), fetchEntityConfig('institution'), fetchEntity(), - requireEntityOrCreate('institutionManagement'), + requireEntityOrCreate(), allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('institutionMetrics'), + UserMembershipAuthorization.hasAdminAccess, ]), ], @@ -103,31 +99,40 @@ const UserMembershipMiddleware = { AuthenticationController.requireLogin(), fetchEntityConfig('institution'), fetchEntity(), - requireEntityOrCreate('institutionManagement'), + requireEntityOrCreate(), + useAdminCapabilities, allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('institutionManagement'), + UserMembershipAuthorization.hasAdminCapability( + 'modify-institution-manager' + ), ]), ], - requireInstitutionManagementStaffAccess: [ + requireInstitutionAIAccess: [ AuthenticationController.requireLogin(), - allowAccessIfAny([ - UserMembershipAuthorization.hasStaffAccess('institutionManagement'), - ]), fetchEntityConfig('institution'), fetchEntity(), - requireEntityOrCreate('institutionManagement'), + requireEntityOrCreate(), + allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]), + ], + + requireInstitutionStaffHubAccess: [ + AuthenticationController.requireLogin(), + fetchEntityConfig('institution'), + fetchEntity(), + requireEntityOrCreate(), + allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]), ], requirePublisherMetricsAccess: [ AuthenticationController.requireLogin(), fetchEntityConfig('publisher'), fetchEntity(), - requireEntityOrCreate('publisherManagement'), + requireEntityOrCreate(), allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('publisherMetrics'), + UserMembershipAuthorization.hasAdminAccess, ]), ], @@ -135,10 +140,13 @@ const UserMembershipMiddleware = { AuthenticationController.requireLogin(), fetchEntityConfig('publisher'), fetchEntity(), - requireEntityOrCreate('publisherManagement'), + requireEntityOrCreate(), + useAdminCapabilities, allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('publisherManagement'), + UserMembershipAuthorization.hasAdminCapability( + 'modify-publisher-manager' + ), ]), ], @@ -146,18 +154,16 @@ const UserMembershipMiddleware = { AuthenticationController.requireLogin(), fetchEntityConfig('publisher'), fetchEntity(), - requireEntityOrCreate('publisherManagement'), + requireEntityOrCreate(), allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('publisherMetrics'), + UserMembershipAuthorization.hasAdminAccess, ]), ], requireAdminMetricsAccess: [ AuthenticationController.requireLogin(), - allowAccessIfAny([ - UserMembershipAuthorization.hasStaffAccess('adminMetrics'), - ]), + allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]), ], requireTemplateMetricsAccess: [ @@ -168,23 +174,19 @@ const UserMembershipMiddleware = { fetchPublisherFromTemplate(), allowAccessIfAny([ UserMembershipAuthorization.hasEntityAccess(), - UserMembershipAuthorization.hasStaffAccess('publisherMetrics'), + UserMembershipAuthorization.hasAdminAccess, ]), ], requirePublisherCreationAccess: [ AuthenticationController.requireLogin(), - allowAccessIfAny([ - UserMembershipAuthorization.hasStaffAccess('publisherManagement'), - ]), + allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]), fetchEntityConfig('publisher'), ], requireInstitutionCreationAccess: [ AuthenticationController.requireLogin(), - allowAccessIfAny([ - UserMembershipAuthorization.hasStaffAccess('institutionManagement'), - ]), + allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]), fetchEntityConfig('institution'), ], @@ -192,8 +194,6 @@ const UserMembershipMiddleware = { AuthenticationController.requireLogin(), useAdminCapabilities, allowAccessIfAny([ - UserMembershipAuthorization.hasStaffAccess('splitTestMetrics'), - UserMembershipAuthorization.hasStaffAccess('splitTestManagement'), UserMembershipAuthorization.hasAdminCapability('view-split-test'), ]), ], @@ -202,7 +202,6 @@ const UserMembershipMiddleware = { AuthenticationController.requireLogin(), useAdminCapabilities, allowAccessIfAny([ - UserMembershipAuthorization.hasStaffAccess('splitTestManagement'), 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 // has permissions to create the entity, or fail with 404 -function requireEntityOrCreate(creationStaffAccess) { +function requireEntityOrCreate() { return (req, res, next) => { if (req.entity) { return next() } - if (UserMembershipAuthorization.hasStaffAccess(creationStaffAccess)(req)) { + if (UserMembershipAuthorization.hasAdminAccess(req)) { res.redirect(`/entities/${req.entityName}/create/${req.params.id}`) return } diff --git a/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs b/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs index 36d02b4818..798d2cba35 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs @@ -58,7 +58,6 @@ export default { '/manage/groups/:id/managers', UserMembershipMiddleware.requireEntityAccess({ entityName: 'groupManagers', - staffAccess: 'groupManagement', adminCapability: 'view-group-manager', }), UserMembershipController.manageGroupManagers @@ -67,7 +66,6 @@ export default { '/manage/groups/:id/managers', UserMembershipMiddleware.requireEntityAccess({ entityName: 'groupManagers', - staffAccess: 'groupManagement', adminCapability: 'modify-group-manager', }), UserMembershipController.add @@ -76,7 +74,6 @@ export default { '/manage/groups/:id/managers/:userId', UserMembershipMiddleware.requireEntityAccess({ entityName: 'groupManagers', - staffAccess: 'groupManagement', adminCapability: 'modify-group-manager', }), UserMembershipController.remove diff --git a/services/web/app/src/models/User.mjs b/services/web/app/src/models/User.mjs index e95cdef18f..041a1e8a11 100644 --- a/services/web/app/src/models/User.mjs +++ b/services/web/app/src/models/User.mjs @@ -59,17 +59,6 @@ export const UserSchema = new Schema( }, isAdmin: { type: Boolean, default: false }, 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: { type: Date, default() { diff --git a/services/web/app/views/layout-react.pug b/services/web/app/views/layout-react.pug index d9e7a83b52..757bb50ddc 100644 --- a/services/web/app/views/layout-react.pug +++ b/services/web/app/views/layout-react.pug @@ -11,9 +11,8 @@ block append meta - const canDisplayAdminMenu = hasAdminAccess() - const canDisplayAdminRedirect = canRedirectToAdminDomain() - const sessionUser = getSessionUser() - - const staffAccess = sessionUser?.staffAccess - 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 canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu - const enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on') diff --git a/services/web/app/views/layout/navbar-marketing.pug b/services/web/app/views/layout/navbar-marketing.pug index ddd535bb47..d99e4db580 100644 --- a/services/web/app/views/layout/navbar-marketing.pug +++ b/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 canDisplayAdminRedirect = canRedirectToAdminDomain() - 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 canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu diff --git a/services/web/app/views/user_membership/group-managers-react.pug b/services/web/app/views/user_membership/group-managers-react.pug index 09685270a5..b7c0a1d929 100644 --- a/services/web/app/views/user_membership/group-managers-react.pug +++ b/services/web/app/views/user_membership/group-managers-react.pug @@ -4,7 +4,7 @@ block entrypointVar - entrypoint = 'pages/user/subscription/group-management/group-managers' 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-users' data-type='json' content=users) meta(name='ol-groupId' data-type='string' content=groupId) diff --git a/services/web/app/views/user_membership/group-members-react.pug b/services/web/app/views/user_membership/group-members-react.pug index e950f5f224..de627711b3 100644 --- a/services/web/app/views/user_membership/group-members-react.pug +++ b/services/web/app/views/user_membership/group-members-react.pug @@ -4,7 +4,7 @@ block entrypointVar - entrypoint = 'pages/user/subscription/group-management/group-members' 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-users' data-type='json' content=users) meta(name='ol-groupId' data-type='string' content=groupId) diff --git a/services/web/scripts/back_fill_staff_access.mjs b/services/web/scripts/back_fill_staff_access.mjs deleted file mode 100644 index eb3fcb97e8..0000000000 --- a/services/web/scripts/back_fill_staff_access.mjs +++ /dev/null @@ -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) -} diff --git a/services/web/test/acceptance/src/UserMembershipAuthorizationTests.mjs b/services/web/test/acceptance/src/UserMembershipAuthorizationTests.mjs index 1a4a59c538..5c6f422155 100644 --- a/services/web/test/acceptance/src/UserMembershipAuthorizationTests.mjs +++ b/services/web/test/acceptance/src/UserMembershipAuthorizationTests.mjs @@ -6,6 +6,7 @@ import Subscription from './helpers/Subscription.mjs' import Publisher from './helpers/Publisher.mjs' import sinon from 'sinon' import RecurlyClient from '../../../app/src/Features/Subscription/RecurlyClient.mjs' +import Settings from '@overleaf/settings' import Features from '../../../app/src/infrastructure/Features.mjs' describe('UserMembershipAuthorization', function () { @@ -16,6 +17,16 @@ describe('UserMembershipAuthorization', function () { this.user = new User() 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) }) @@ -25,6 +36,8 @@ describe('UserMembershipAuthorization', function () { } RecurlyClient.promises.getSubscription.restore() + this.adminRolesEnabledStub.restore() + this.adminPrivilegeAvailableStub.restore() }) describe('group', function () { @@ -92,13 +105,14 @@ describe('UserMembershipAuthorization', function () { }) describe('creation', function () { - it('should allow staff only', function (done) { + it('should allow admin only', function (done) { const url = `/entities/institution/create/foo` async.series( [ this.user.login.bind(this.user), 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), expectAccess(this.user, url, 200), ], @@ -135,13 +149,14 @@ describe('UserMembershipAuthorization', function () { }) describe('creation', function () { - it('should redirect staff only', function (done) { + it('should redirect admin only', function (done) { const url = `/manage/publishers/foo/managers` async.series( [ this.user.login.bind(this.user), 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), 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` async.series( [ 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), expectAccess(this.user, url, 200), ], diff --git a/services/web/test/acceptance/src/helpers/User.mjs b/services/web/test/acceptance/src/helpers/User.mjs index 75159ecad6..5d592beeca 100644 --- a/services/web/test/acceptance/src/helpers/User.mjs +++ b/services/web/test/acceptance/src/helpers/User.mjs @@ -580,13 +580,7 @@ class User { } 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) { diff --git a/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs b/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs index fd14135c72..2117857fcb 100644 --- a/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs +++ b/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs @@ -35,34 +35,6 @@ describe('AuthenticationController', function () { referal_id: 1234, 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.req = new MockRequest(vi) ctx.res = new MockResponse(vi) @@ -325,41 +297,6 @@ describe('AuthenticationController', function () { 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 () { diff --git a/services/web/test/unit/src/HelperFiles/AuthorizationHelper.test.mjs b/services/web/test/unit/src/HelperFiles/AuthorizationHelper.test.mjs deleted file mode 100644 index 743a51bd96..0000000000 --- a/services/web/test/unit/src/HelperFiles/AuthorizationHelper.test.mjs +++ /dev/null @@ -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 - }) - }) -}) diff --git a/services/web/types/admin-capabilities.ts b/services/web/types/admin-capabilities.ts index 691e57e7e9..7f887d10ec 100644 --- a/services/web/types/admin-capabilities.ts +++ b/services/web/types/admin-capabilities.ts @@ -11,25 +11,27 @@ export type AdminCapability = | 'modify-group-manager' | 'modify-group-member' | 'modify-group-setting' + | 'modify-institution-manager' | 'modify-managed-group' | 'modify-managed-group-member' - | 'modify-user-account-status' | 'modify-project-content' | 'modify-project-setting' + | 'modify-publisher-manager' | 'manage-survey' | 'modify-split-test' + | 'modify-user-account-status' | 'modify-user-email' | 'modify-user-name' + | 'update-stripe-customer-segment' | 'view-audit-log' | 'view-group-manager' | 'view-project-content' | 'view-project-setting' - | 'view-session' | 'view-script-log' + | 'view-session' | 'view-split-test' | 'view-user-additional-info' | 'create-stripe-account' - | 'update-stripe-customer-segment' export type AdminRole = | 'engagement'