diff --git a/services/web/app/src/Features/UserMembership/UserMembershipController.mjs b/services/web/app/src/Features/UserMembership/UserMembershipController.mjs index eaac266c8d..b4d457ca7e 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipController.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipController.mjs @@ -121,6 +121,7 @@ async function _renderManagersPage(req, res, next, template) { name: entityName, users, groupId: entityPrimaryKey, + entityAccess: UserMembershipAuthorization.hasEntityAccess()(req), }) } diff --git a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js index 5c47baa85a..028b0c8489 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js +++ b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js @@ -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'), diff --git a/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs b/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs index 10633d3440..5f6c87df21 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipRouter.mjs @@ -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 ) 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 34414ddbf2..09685270a5 100644 --- a/services/web/app/views/user_membership/group-managers-react.pug +++ b/services/web/app/views/user_membership/group-managers-react.pug @@ -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 diff --git a/services/web/app/views/user_membership/institution-managers-react.pug b/services/web/app/views/user_membership/institution-managers-react.pug index 9793058f6f..e174ae6796 100644 --- a/services/web/app/views/user_membership/institution-managers-react.pug +++ b/services/web/app/views/user_membership/institution-managers-react.pug @@ -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 diff --git a/services/web/app/views/user_membership/publisher-managers-react.pug b/services/web/app/views/user_membership/publisher-managers-react.pug index 2f805079a7..e13856686d 100644 --- a/services/web/app/views/user_membership/publisher-managers-react.pug +++ b/services/web/app/views/user_membership/publisher-managers-react.pug @@ -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 diff --git a/services/web/frontend/js/features/group-management/components/managers-table.tsx b/services/web/frontend/js/features/group-management/components/managers-table.tsx index 6edf2490e6..dce0231376 100644 --- a/services/web/frontend/js/features/group-management/components/managers-table.tsx +++ b/services/web/frontend/js/features/group-management/components/managers-table.tsx @@ -56,6 +56,7 @@ export function ManagersTable({ const [inviteError, setInviteError] = useState() const [removeMemberInflightCount, setRemoveMemberInflightCount] = useState(0) const [removeMemberError, setRemoveMemberError] = useState() + const hasWriteAccess = getMeta('ol-hasWriteAccess') const addManagers = useCallback( (e: FormEvent | React.MouseEvent) => { @@ -181,13 +182,15 @@ export function ManagersTable({ - + {hasWriteAccess && ( + + )} {t('email')} {t('name')} @@ -225,45 +228,50 @@ export function ManagersTable({ selectUser={selectUser} unselectUser={unselectUser} selected={selectedUsers.includes(user)} + hasWriteAccess={hasWriteAccess} /> ))} -
-
-

{t('add_more_managers')}

- -
- - - - - - 0} - > - {t('add')} - - - - - - - {t('add_comma_separated_emails_help')} - - - -
-
+ {hasWriteAccess && ( + <> +
+
+

{t('add_more_managers')}

+ +
+ + + + + + 0} + > + {t('add')} + + + + + + + {t('add_comma_separated_emails_help')} + + + +
+
+ + )} diff --git a/services/web/frontend/js/features/group-management/components/user-row.tsx b/services/web/frontend/js/features/group-management/components/user-row.tsx index 85c018a2da..b460072a68 100644 --- a/services/web/frontend/js/features/group-management/components/user-row.tsx +++ b/services/web/frontend/js/features/group-management/components/user-row.tsx @@ -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 ( - handleSelectUser(e, user)} - aria-label={t('select_user')} - data-testid="select-single-checkbox" - /> + {hasWriteAccess && ( + handleSelectUser(e, user)} + aria-label={t('select_user')} + data-testid="select-single-checkbox" + /> + )} {user.email} diff --git a/services/web/test/frontend/features/group-management/components/group-managers.spec.tsx b/services/web/test/frontend/features/group-management/components/group-managers.spec.tsx index d5c31c440f..b13884e10b 100644 --- a/services/web/test/frontend/features/group-management/components/group-managers.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/group-managers.spec.tsx @@ -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() diff --git a/services/web/test/frontend/features/group-management/components/institution-managers.spec.tsx b/services/web/test/frontend/features/group-management/components/institution-managers.spec.tsx index 5754eafe48..263f0b38a9 100644 --- a/services/web/test/frontend/features/group-management/components/institution-managers.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/institution-managers.spec.tsx @@ -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() diff --git a/services/web/test/frontend/features/group-management/components/publisher-managers.spec.tsx b/services/web/test/frontend/features/group-management/components/publisher-managers.spec.tsx index d8fea9d7c1..9db721f39a 100644 --- a/services/web/test/frontend/features/group-management/components/publisher-managers.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/publisher-managers.spec.tsx @@ -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() diff --git a/services/web/test/unit/src/UserMembership/UserMembershipController.test.mjs b/services/web/test/unit/src/UserMembership/UserMembershipController.test.mjs index 5fcb791ec5..32f8cadf05 100644 --- a/services/web/test/unit/src/UserMembership/UserMembershipController.test.mjs +++ b/services/web/test/unit/src/UserMembership/UserMembershipController.test.mjs @@ -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, { diff --git a/services/web/types/admin-capabilities.ts b/services/web/types/admin-capabilities.ts index 8e8e02f79d..cd1d3326a4 100644 --- a/services/web/types/admin-capabilities.ts +++ b/services/web/types/admin-capabilities.ts @@ -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'