mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-11 23:20:47 +02:00
[web] Add admin permission modify-group-manager (#27642)
* Add capacity `modify-group-manager`
* Check `modify-group-manager` (backend)
* Check `modify-group-manager` (frontend)
* Update tests
* Rename AdminPermissions to mjs
* Add `ol-adminCapabilities` in frontend tests
* Allow modifying group managers if `adminRolesEnabled` is false
* Add `adminPrivilegeAvailable` check
* Update: set `ol-canModify` boolean instead of `ol-adminCapabilities`
* Mock `hasAnyAccess`
* Use `hasAdminCapability` helper
* Add `ol-canModify` to types
* Remove `isAdminMiddleware` as we don't want to relax the permissions for now
* Fix: pass `res` to `hasAnyAccess` (!!)
* * Check `hasWriteAccess` (`hasAdminCapability('modify-group-manager')` or `staffAccess.groupManagement`) in the Pug file
* Fix: Check `hasWriteAccess` in the publisher and institution pug files (!)
* Revert `hasAnyAccess` changes
* Rename `ol-canModify` to `ol-hasWriteAccess` for consistency with other variables
* Remove redundant file AdminPermissions.mjs
* Update unit test
* Revert changes to UserMembershipController.test.mjs
* Rename to `requireGroupManagersWriteAccess`
GitOrigin-RevId: f3f0b1b17abd1d2f0c363688e87d9063de886e3c
This commit is contained in:
@@ -121,6 +121,7 @@ async function _renderManagersPage(req, res, next, template) {
|
||||
name: entityName,
|
||||
users,
|
||||
groupId: entityPrimaryKey,
|
||||
entityAccess: UserMembershipAuthorization.hasEntityAccess()(req),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,19 @@ const UserMembershipMiddleware = {
|
||||
]),
|
||||
],
|
||||
|
||||
requireGroupManagersWriteAccess: [
|
||||
AuthenticationController.requireLogin(),
|
||||
fetchEntityConfig('groupManagers'),
|
||||
fetchEntity(),
|
||||
requireEntity(),
|
||||
useAdminCapabilities,
|
||||
allowAccessIfAny([
|
||||
UserMembershipAuthorization.hasEntityAccess(),
|
||||
UserMembershipAuthorization.hasStaffAccess('groupManagement'),
|
||||
UserMembershipAuthorization.hasAdminCapability('modify-group-manager'),
|
||||
]),
|
||||
],
|
||||
|
||||
requireGroupAdminAccess: [
|
||||
AuthenticationController.requireLogin(),
|
||||
fetchEntityConfig('groupAdmin'),
|
||||
|
||||
@@ -61,12 +61,12 @@ export default {
|
||||
)
|
||||
webRouter.post(
|
||||
'/manage/groups/:id/managers',
|
||||
UserMembershipMiddleware.requireGroupManagersManagementAccess,
|
||||
UserMembershipMiddleware.requireGroupManagersWriteAccess,
|
||||
UserMembershipController.add
|
||||
)
|
||||
webRouter.delete(
|
||||
'/manage/groups/:id/managers/:userId',
|
||||
UserMembershipMiddleware.requireGroupManagersManagementAccess,
|
||||
UserMembershipMiddleware.requireGroupManagersWriteAccess,
|
||||
UserMembershipController.remove
|
||||
)
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ 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)
|
||||
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)
|
||||
meta(name='ol-groupName' data-type='string' content=name)
|
||||
meta(name='ol-hasWriteAccess' data-type='boolean' content=hasWriteAccess)
|
||||
|
||||
block content
|
||||
main#subscription-manage-group-root.content.content-alt
|
||||
|
||||
@@ -8,6 +8,7 @@ block append meta
|
||||
meta(name='ol-users' data-type='json' content=users)
|
||||
meta(name='ol-groupId' data-type='string' content=groupId)
|
||||
meta(name='ol-groupName' data-type='string' content=name)
|
||||
meta(name='ol-hasWriteAccess' data-type='boolean' content)
|
||||
|
||||
block content
|
||||
main#subscription-manage-group-root.content.content-alt
|
||||
|
||||
@@ -8,6 +8,7 @@ block append meta
|
||||
meta(name='ol-users' data-type='json' content=users)
|
||||
meta(name='ol-groupId' data-type='string' content=groupId)
|
||||
meta(name='ol-groupName' data-type='string' content=name)
|
||||
meta(name='ol-hasWriteAccess' data-type='boolean' content)
|
||||
|
||||
block content
|
||||
main#subscription-manage-group-root.content.content-alt
|
||||
|
||||
@@ -56,6 +56,7 @@ export function ManagersTable({
|
||||
const [inviteError, setInviteError] = useState<APIError>()
|
||||
const [removeMemberInflightCount, setRemoveMemberInflightCount] = useState(0)
|
||||
const [removeMemberError, setRemoveMemberError] = useState<APIError>()
|
||||
const hasWriteAccess = getMeta('ol-hasWriteAccess')
|
||||
|
||||
const addManagers = useCallback(
|
||||
(e: FormEvent | React.MouseEvent) => {
|
||||
@@ -181,13 +182,15 @@ export function ManagersTable({
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="cell-checkbox">
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
onChange={handleSelectAllClick}
|
||||
checked={selectedUsers.length === users.length}
|
||||
aria-label={t('select_all')}
|
||||
data-testid="select-all-checkbox"
|
||||
/>
|
||||
{hasWriteAccess && (
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
onChange={handleSelectAllClick}
|
||||
checked={selectedUsers.length === users.length}
|
||||
aria-label={t('select_all')}
|
||||
data-testid="select-all-checkbox"
|
||||
/>
|
||||
)}
|
||||
</th>
|
||||
<th>{t('email')}</th>
|
||||
<th className="cell-name">{t('name')}</th>
|
||||
@@ -225,45 +228,50 @@ export function ManagersTable({
|
||||
selectUser={selectUser}
|
||||
unselectUser={unselectUser}
|
||||
selected={selectedUsers.includes(user)}
|
||||
hasWriteAccess={hasWriteAccess}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</OLTable>
|
||||
</div>
|
||||
<hr />
|
||||
<div>
|
||||
<p className="small">{t('add_more_managers')}</p>
|
||||
<ErrorAlert error={inviteError} />
|
||||
<form onSubmit={addManagers} data-testid="add-members-form">
|
||||
<OLRow>
|
||||
<OLCol xs={6}>
|
||||
<OLFormControl
|
||||
type="input"
|
||||
placeholder="jane@example.com, joe@example.com"
|
||||
aria-describedby="add-members-description"
|
||||
value={emailString}
|
||||
onChange={handleEmailsChange}
|
||||
/>
|
||||
</OLCol>
|
||||
<OLCol xs={4}>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={addManagers}
|
||||
isLoading={inviteUserInflightCount > 0}
|
||||
>
|
||||
{t('add')}
|
||||
</OLButton>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<OLRow>
|
||||
<OLCol xs={8}>
|
||||
<OLFormText>
|
||||
{t('add_comma_separated_emails_help')}
|
||||
</OLFormText>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</form>
|
||||
</div>
|
||||
{hasWriteAccess && (
|
||||
<>
|
||||
<hr />
|
||||
<div>
|
||||
<p className="small">{t('add_more_managers')}</p>
|
||||
<ErrorAlert error={inviteError} />
|
||||
<form onSubmit={addManagers} data-testid="add-members-form">
|
||||
<OLRow>
|
||||
<OLCol xs={6}>
|
||||
<OLFormControl
|
||||
type="input"
|
||||
placeholder="jane@example.com, joe@example.com"
|
||||
aria-describedby="add-members-description"
|
||||
value={emailString}
|
||||
onChange={handleEmailsChange}
|
||||
/>
|
||||
</OLCol>
|
||||
<OLCol xs={4}>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={addManagers}
|
||||
isLoading={inviteUserInflightCount > 0}
|
||||
>
|
||||
{t('add')}
|
||||
</OLButton>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<OLRow>
|
||||
<OLCol xs={8}>
|
||||
<OLFormText>
|
||||
{t('add_comma_separated_emails_help')}
|
||||
</OLFormText>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</OLCard>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
|
||||
@@ -10,6 +10,7 @@ type GroupMemberRowProps = {
|
||||
selectUser: (user: User) => void
|
||||
unselectUser: (user: User) => void
|
||||
selected: boolean
|
||||
hasWriteAccess: boolean
|
||||
}
|
||||
|
||||
export default function UserRow({
|
||||
@@ -17,6 +18,7 @@ export default function UserRow({
|
||||
selectUser,
|
||||
unselectUser,
|
||||
selected,
|
||||
hasWriteAccess,
|
||||
}: GroupMemberRowProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -34,13 +36,15 @@ export default function UserRow({
|
||||
return (
|
||||
<tr key={`user-${user.email}`} className="managed-entity-row">
|
||||
<td className="cell-checkbox">
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
checked={selected}
|
||||
onChange={e => handleSelectUser(e, user)}
|
||||
aria-label={t('select_user')}
|
||||
data-testid="select-single-checkbox"
|
||||
/>
|
||||
{hasWriteAccess && (
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
checked={selected}
|
||||
onChange={e => handleSelectUser(e, user)}
|
||||
aria-label={t('select_user')}
|
||||
data-testid="select-single-checkbox"
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>{user.email}</td>
|
||||
<td className="cell-name">
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ describe('group managers', function () {
|
||||
win.metaAttributesCache.set('ol-users', [JOHN_DOE, BOBBY_LAPOINTE])
|
||||
win.metaAttributesCache.set('ol-groupId', GROUP_ID)
|
||||
win.metaAttributesCache.set('ol-groupName', 'My Awesome Team')
|
||||
win.metaAttributesCache.set('ol-hasWriteAccess', true)
|
||||
})
|
||||
|
||||
cy.mount(<GroupManagers />)
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ describe('institution managers', function () {
|
||||
win.metaAttributesCache.set('ol-users', [JOHN_DOE, BOBBY_LAPOINTE])
|
||||
win.metaAttributesCache.set('ol-groupId', GROUP_ID)
|
||||
win.metaAttributesCache.set('ol-groupName', 'My Awesome Institution')
|
||||
win.metaAttributesCache.set('ol-hasWriteAccess', true)
|
||||
})
|
||||
|
||||
cy.mount(<InstitutionManagers />)
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ describe('publisher managers', function () {
|
||||
win.metaAttributesCache.set('ol-users', [JOHN_DOE, BOBBY_LAPOINTE])
|
||||
win.metaAttributesCache.set('ol-groupId', GROUP_ID)
|
||||
win.metaAttributesCache.set('ol-groupName', 'My Awesome Publisher')
|
||||
win.metaAttributesCache.set('ol-hasWriteAccess', true)
|
||||
})
|
||||
|
||||
cy.mount(<PublisherManagers />)
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
UserNotFoundError,
|
||||
UserAlreadyAddedError,
|
||||
} from '../../../../app/src/Features/UserMembership/UserMembershipErrors.js'
|
||||
|
||||
const assertCalledWith = sinon.assert.calledWith
|
||||
|
||||
const modulePath =
|
||||
@@ -46,6 +47,7 @@ describe('UserMembershipController', function () {
|
||||
institution.name = 'Test Institution Name'
|
||||
callback(null, institution)
|
||||
},
|
||||
managerIds: ['mock-member-id-1'],
|
||||
}
|
||||
ctx.users = [
|
||||
{
|
||||
@@ -205,6 +207,7 @@ describe('UserMembershipController', function () {
|
||||
ctx.req.user = ctx.user
|
||||
ctx.req.entity = ctx.subscription
|
||||
ctx.req.entityConfig = EntityConfigs.group
|
||||
ctx.Modules.promises.hooks.fire.resolves([])
|
||||
})
|
||||
|
||||
it('get users', async function (ctx) {
|
||||
@@ -245,6 +248,7 @@ describe('UserMembershipController', function () {
|
||||
})
|
||||
|
||||
it('render group managers view', async function (ctx) {
|
||||
ctx.req.user = ctx.user
|
||||
ctx.req.entityConfig = EntityConfigs.groupManagers
|
||||
await ctx.UserMembershipController.manageGroupManagers(ctx.req, {
|
||||
render: (viewPath, viewParams) => {
|
||||
@@ -255,6 +259,7 @@ describe('UserMembershipController', function () {
|
||||
})
|
||||
|
||||
it('render institution view', async function (ctx) {
|
||||
ctx.req.user = ctx.user
|
||||
ctx.req.entity = ctx.institution
|
||||
ctx.req.entityConfig = EntityConfigs.institution
|
||||
await ctx.UserMembershipController.manageInstitutionManagers(ctx.req, {
|
||||
|
||||
@@ -5,6 +5,7 @@ export type AdminCapability =
|
||||
| 'create-subscription'
|
||||
| 'modify-feature-override'
|
||||
| 'modify-group'
|
||||
| 'modify-group-manager'
|
||||
| 'modify-group-member'
|
||||
| 'modify-group-setting'
|
||||
| 'modify-login-status'
|
||||
|
||||
Reference in New Issue
Block a user