Skip to content

Commit 1412fc2

Browse files
committed
feat: restrict policy workbench group targets to manageable scope
- Filter group targets by manageable_policy_group_ids for group-admin users - Remove redundant comment from AdminController footer template save method - Add unit tests for group-admin group filtering and probe access shortcut - Ensures group-admins only see groups they manage when adding policy rules Signed-off-by: Vitor Mattos <[email protected]>
1 parent 496f27b commit 1412fc2

3 files changed

Lines changed: 60 additions & 7 deletions

File tree

lib/Controller/AdminController.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,6 @@ public function getFooterTemplate(): DataResponse {
878878
public function saveFooterTemplate(string $template = '', int $width = 595, int $height = 50) {
879879
try {
880880
$this->footerService->saveTemplate($template);
881-
// Template was already persisted above; avoid a second save that can revert policy flags.
882881
$pdf = $this->footerService->renderPreviewPdf('', $width, $height);
883882

884883
return new DataDownloadResponse($pdf, 'footer-preview.pdf', 'application/pdf');

src/tests/views/Settings/PolicyWorkbench/useRealPolicyWorkbench.spec.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@ const { currentUserState } = vi.hoisted(() => ({
1414
},
1515
}))
1616

17+
const { configState } = vi.hoisted(() => ({
18+
configState: {
19+
can_manage_group_policies: true,
20+
manageable_policy_group_ids: [] as string[],
21+
},
22+
}))
23+
1724
vi.mock('@nextcloud/auth', () => ({
1825
getCurrentUser: vi.fn(() => currentUserState),
1926
}))
2027

2128
vi.mock('@nextcloud/initial-state', () => ({
2229
loadState: vi.fn((_app, key: string, defaultValue: unknown) => {
2330
if (key === 'config') {
24-
return { can_manage_group_policies: true }
31+
return configState
2532
}
2633

2734
return defaultValue
@@ -75,6 +82,8 @@ import { createRealPolicyWorkbenchState } from '../../../../views/Settings/Polic
7582
describe('useRealPolicyWorkbench', () => {
7683
beforeEach(() => {
7784
currentUserState.isAdmin = true
85+
configState.can_manage_group_policies = true
86+
configState.manageable_policy_group_ids = []
7887
axiosGet.mockReset()
7988
saveSystemPolicy.mockReset()
8089
saveGroupPolicy.mockReset()
@@ -458,6 +467,33 @@ describe('useRealPolicyWorkbench', () => {
458467
])
459468
})
460469

470+
it('filters group targets to manageable_policy_group_ids for group-admin', async () => {
471+
currentUserState.isAdmin = false
472+
configState.manageable_policy_group_ids = ['legal']
473+
474+
const state = createRealPolicyWorkbenchState()
475+
state.openSetting('signature_flow')
476+
state.startEditor({ scope: 'group' })
477+
478+
await Promise.resolve()
479+
await Promise.resolve()
480+
481+
expect(state.availableTargets).toEqual([
482+
{ id: 'legal', displayName: 'legal', isNoUser: true },
483+
])
484+
})
485+
486+
it('probeGroupAccess uses manageable_policy_group_ids shortcut for group-admin', async () => {
487+
currentUserState.isAdmin = false
488+
configState.manageable_policy_group_ids = ['legal']
489+
490+
const state = createRealPolicyWorkbenchState()
491+
await state.probeGroupAccess()
492+
493+
expect(state.canManageGroups).toBe(true)
494+
expect(axiosGet).not.toHaveBeenCalledWith('cloud/groups', expect.anything())
495+
})
496+
461497
it('probeGroupAccess sets canManageGroups to true when groups are returned', async () => {
462498
currentUserState.isAdmin = false
463499

src/views/Settings/PolicyWorkbench/useRealPolicyWorkbench.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,12 @@ export function createRealPolicyWorkbenchState() {
152152
const policiesStore = usePoliciesStore()
153153
const currentUser = getCurrentUser()
154154
const isInstanceAdmin = currentUser?.isAdmin === true
155-
const config = loadState<{ can_manage_group_policies?: boolean }>('libresign', 'config', {})
155+
const config = loadState<{ can_manage_group_policies?: boolean, manageable_policy_group_ids?: string[] }>('libresign', 'config', {})
156+
const manageablePolicyGroupIds = new Set(
157+
Array.isArray(config.manageable_policy_group_ids)
158+
? config.manageable_policy_group_ids.filter((groupId): groupId is string => typeof groupId === 'string' && groupId.trim().length > 0)
159+
: [],
160+
)
156161
const initialViewMode: 'system-admin' | 'group-admin' = currentUser?.isAdmin
157162
? 'system-admin'
158163
: config.can_manage_group_policies
@@ -179,6 +184,14 @@ export function createRealPolicyWorkbenchState() {
179184
const draftTouchVersion = ref(0)
180185
const editorInitialTouchVersion = ref(0)
181186

187+
function filterManageableGroupTargets(targets: PolicyTargetOption[]): PolicyTargetOption[] {
188+
if (isInstanceAdmin || manageablePolicyGroupIds.size === 0) {
189+
return targets
190+
}
191+
192+
return targets.filter((target) => manageablePolicyGroupIds.has(target.id))
193+
}
194+
182195
const visibleSettingSummaries = computed<PolicySettingSummary[]>(() => {
183196
const isGroupAdminMode = viewMode.value === 'group-admin'
184197

@@ -624,7 +637,7 @@ export function createRealPolicyWorkbenchState() {
624637
},
625638
})
626639

627-
return (data.ocs?.data?.groups ?? [])
640+
return filterManageableGroupTargets((data.ocs?.data?.groups ?? [])
628641
.map((group) => ({
629642
id: group.id,
630643
displayName: group.displayname || group.id,
@@ -633,7 +646,7 @@ export function createRealPolicyWorkbenchState() {
633646
: undefined,
634647
isNoUser: true,
635648
}))
636-
.sort((left, right) => left.displayName.localeCompare(right.displayName))
649+
.sort((left, right) => left.displayName.localeCompare(right.displayName)))
637650
}
638651

639652
const { data } = await axios.get<GroupListResponse>(generateOcsUrl('cloud/groups'), {
@@ -644,13 +657,13 @@ export function createRealPolicyWorkbenchState() {
644657
},
645658
})
646659

647-
return (data.ocs?.data?.groups ?? [])
660+
return filterManageableGroupTargets((data.ocs?.data?.groups ?? [])
648661
.map((groupId) => ({
649662
id: groupId,
650663
displayName: groupId,
651664
isNoUser: true,
652665
}))
653-
.sort((left, right) => left.displayName.localeCompare(right.displayName))
666+
.sort((left, right) => left.displayName.localeCompare(right.displayName)))
654667
}
655668

656669
async function fetchUsers(query = '', limit = 20, offset = 0): Promise<PolicyTargetOption[]> {
@@ -743,6 +756,11 @@ export function createRealPolicyWorkbenchState() {
743756
return
744757
}
745758

759+
if (manageablePolicyGroupIds.size > 0) {
760+
canManageGroups.value = true
761+
return
762+
}
763+
746764
try {
747765
const result = await fetchGroups('', 1, 0)
748766
canManageGroups.value = result.length > 0

0 commit comments

Comments
 (0)