Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.36% covered (success)
90.36%
75 / 83
61.54% covered (warning)
61.54%
8 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
PolicyManagementScopeService
90.36% covered (success)
90.36%
75 / 83
61.54% covered (warning)
61.54%
8 / 13
36.10
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolveVisibleRuleCountsForCurrentActor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canCurrentActorManageGroupPolicy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canCurrentActorManageUserPolicy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canCurrentActorManageScopedUserPolicy
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 resolveCurrentActorManageableGroupIdsForPolicy
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 canManageGroupPolicy
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 canManageUserPolicy
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
6.20
 resolveVisibleRuleCountsForActor
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 filterVisibleRuleCountsForManagedGroups
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
6.04
 normalizeRuleCounts
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 resolveSubAdminManagedGroupIds
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 resolveManageableGroupIdsForPolicy
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Service\Policy;
10
11use OCA\Libresign\Service\Policy\Provider\RequestSignGroups\RequestSignGroupsPolicy;
12use OCP\Group\ISubAdmin;
13use OCP\IGroupManager;
14use OCP\IUser;
15use OCP\IUserManager;
16use OCP\IUserSession;
17
18final class PolicyManagementScopeService {
19    public function __construct(
20        private IUserSession $userSession,
21        private IGroupManager $groupManager,
22        private IUserManager $userManager,
23        private ISubAdmin $subAdmin,
24        private PolicyService $policyService,
25        private PolicyAuthorizationService $policyAuthorizationService,
26    ) {
27    }
28
29    /**
30     * @return array<string, array{groupCount: int, userCount: int, everyoneCount: int}>
31     */
32    public function resolveVisibleRuleCountsForCurrentActor(): array {
33        return $this->resolveVisibleRuleCountsForActor($this->userSession->getUser());
34    }
35
36    public function canCurrentActorManageGroupPolicy(string $groupId, string $policyKey): bool {
37        return $this->canManageGroupPolicy($this->userSession->getUser(), $groupId, $policyKey);
38    }
39
40    public function canCurrentActorManageUserPolicy(string $userId): bool {
41        return $this->canManageUserPolicy($this->userSession->getUser(), $userId);
42    }
43
44    public function canCurrentActorManageScopedUserPolicy(string $userId, string $policyKey): bool {
45        $user = $this->userSession->getUser();
46
47        return $this->canManageUserPolicy($user, $userId)
48            && $this->policyService->canManageUserPolicyForUserId($policyKey, $userId);
49    }
50
51    /**
52     * @return list<string>|null Null means the current actor can manage all groups.
53     */
54    public function resolveCurrentActorManageableGroupIdsForPolicy(string $policyKey): ?array {
55        $user = $this->userSession->getUser();
56        if (!$user instanceof IUser) {
57            return [];
58        }
59
60        if ($this->groupManager->isAdmin($user->getUID())) {
61            return null;
62        }
63
64        if (!$this->subAdmin->isSubAdmin($user)) {
65            return [];
66        }
67
68        return $this->resolveManageableGroupIdsForPolicy($user, $policyKey);
69    }
70
71    private function canManageGroupPolicy(?IUser $user, string $groupId, string $policyKey): bool {
72        if (!$user instanceof IUser) {
73            return false;
74        }
75
76        if ($this->groupManager->isAdmin($user->getUID())) {
77            return true;
78        }
79
80        if (!$this->subAdmin->isSubAdmin($user)) {
81            return false;
82        }
83
84        return in_array($groupId, $this->resolveManageableGroupIdsForPolicy($user, $policyKey), true);
85    }
86
87    private function canManageUserPolicy(?IUser $user, string $userId): bool {
88        if (!$user instanceof IUser) {
89            return false;
90        }
91
92        if ($this->groupManager->isAdmin($user->getUID())) {
93            return true;
94        }
95
96        if (!$this->subAdmin->isSubAdmin($user)) {
97            return false;
98        }
99
100        $targetUser = $this->userManager->get($userId);
101        if (!$targetUser instanceof IUser) {
102            return false;
103        }
104
105        $managedGroupIds = array_values(array_map(
106            static fn ($group): string => $group->getGID(),
107            $this->subAdmin->getSubAdminsGroups($user),
108        ));
109        if ($managedGroupIds === []) {
110            return false;
111        }
112
113        $targetGroupIds = $this->groupManager->getUserGroupIds($targetUser);
114        return array_intersect($managedGroupIds, $targetGroupIds) !== [];
115    }
116
117    /**
118     * @return array<string, array{groupCount: int, userCount: int, everyoneCount: int}>
119     */
120    private function resolveVisibleRuleCountsForActor(?IUser $user): array {
121        if ($user === null) {
122            return [];
123        }
124
125        if ($this->groupManager->isAdmin($user->getUID())) {
126            return $this->policyService->getAllRuleCounts();
127        }
128
129        if ($this->subAdmin->isSubAdmin($user)) {
130            $groupIds = $this->resolveSubAdminManagedGroupIds($user);
131            return $this->filterVisibleRuleCountsForManagedGroups(
132                $this->policyService->getRuleCounts($groupIds, []),
133                $user,
134                $groupIds,
135            );
136        }
137
138        return [];
139    }
140
141    /**
142     * @param array<string, array{groupCount?: int, userCount?: int, everyoneCount?: int}> $ruleCounts
143     * @param list<string> $groupIds
144     * @return array<string, array{groupCount: int, userCount: int, everyoneCount: int}>
145     */
146    private function filterVisibleRuleCountsForManagedGroups(array $ruleCounts, IUser $user, array $groupIds): array {
147        /** @var array<string, array{groupCount: int, userCount: int, everyoneCount: int}> $normalizedRuleCounts */
148        $normalizedRuleCounts = [];
149        foreach ($ruleCounts as $policyKey => $counts) {
150            $normalizedRuleCounts[$policyKey] = $this->normalizeRuleCounts($counts);
151        }
152
153        $groupIds = array_values(array_unique(array_filter(
154            $groupIds,
155            static fn (string $groupId): bool => trim($groupId) !== '',
156        )));
157
158        if ($groupIds === []) {
159            return $normalizedRuleCounts;
160        }
161
162        foreach ($normalizedRuleCounts as $policyKey => $counts) {
163            if (($counts['groupCount'] ?? 0) <= 0) {
164                continue;
165            }
166
167            if (!$this->policyService->shouldFilterVisibleGroupCountsForCurrentActor($policyKey)) {
168                continue;
169            }
170
171            $counts['groupCount'] = $this->policyService->countVisibleGroupPoliciesForTargets(
172                $policyKey,
173                $this->resolveManageableGroupIdsForPolicy($user, $policyKey),
174            );
175            $normalizedRuleCounts[$policyKey] = $counts;
176        }
177
178        return $normalizedRuleCounts;
179    }
180
181    /**
182     * @param array{groupCount?: int, userCount?: int, everyoneCount?: int} $counts
183     * @return array{groupCount: int, userCount: int, everyoneCount: int}
184     */
185    private function normalizeRuleCounts(array $counts): array {
186        return [
187            'groupCount' => (int)($counts['groupCount'] ?? 0),
188            'userCount' => (int)($counts['userCount'] ?? 0),
189            'everyoneCount' => (int)($counts['everyoneCount'] ?? 0),
190        ];
191    }
192
193    /** @return list<string> */
194    private function resolveSubAdminManagedGroupIds(IUser $user): array {
195        return array_values(array_filter(
196            $this->groupManager->getUserGroupIds($user),
197            static fn (mixed $groupId): bool => is_string($groupId) && trim($groupId) !== '',
198        ));
199    }
200
201    /** @return list<string> */
202    private function resolveManageableGroupIdsForPolicy(IUser $user, string $policyKey): array {
203        if ($policyKey === RequestSignGroupsPolicy::KEY) {
204            return $this->policyAuthorizationService->getManageablePolicyGroupIds($user);
205        }
206
207        return $this->resolveSubAdminManagedGroupIds($user);
208    }
209}