mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
replace staff access with role based sytem (#30004)
- remove references to staff access GitOrigin-RevId: 5d7df3ae8bc78aa02b65ec0dac0a323520c3df15
This commit is contained in:
committed by
Copybot
parent
37b8bb15e6
commit
ff8f77d85c
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user