Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.52% covered (success)
94.52%
69 / 73
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
PolicyContextFactory
94.52% covered (success)
94.52%
69 / 73
66.67% covered (warning)
66.67%
6 / 9
37.23
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
 forCurrentUser
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isCurrentActorSystemAdmin
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 forUser
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 forUserId
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 build
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
9
 parseActiveGroupScope
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
7.18
 resolveActorRole
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 getUserGroupIds
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
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\Runtime;
10
11use OCA\Libresign\Exception\LibresignException;
12use OCA\Libresign\Service\Policy\Model\ActiveGroupScope;
13use OCA\Libresign\Service\Policy\Model\ActorRole;
14use OCA\Libresign\Service\Policy\Model\PolicyContext;
15use OCP\AppFramework\Http;
16use OCP\Group\ISubAdmin;
17use OCP\IGroupManager;
18use OCP\IUser;
19use OCP\IUserManager;
20use OCP\IUserSession;
21
22final class PolicyContextFactory {
23    public function __construct(
24        private IUserManager $userManager,
25        private IGroupManager $groupManager,
26        private ISubAdmin $subAdmin,
27        private IUserSession $userSession,
28    ) {
29    }
30
31    /** @param array<string, mixed> $requestOverrides */
32    public function forCurrentUser(array $requestOverrides = [], ?array $activeContext = null): PolicyContext {
33        $user = $this->userSession->getUser();
34        $actorGroupIds = $activeContext !== null ? $this->getUserGroupIds($user) : null;
35        $scope = $this->parseActiveGroupScope($activeContext, $user, $actorGroupIds);
36        return $this->build($user?->getUID(), $user, $requestOverrides, $scope, $user, $actorGroupIds);
37    }
38
39    public function isCurrentActorSystemAdmin(): bool {
40        $user = $this->userSession->getUser();
41        if ($user === null) {
42            return false;
43        }
44
45        return $this->groupManager->isAdmin($user->getUID());
46    }
47
48    /** @param array<string, mixed> $requestOverrides */
49    public function forUser(?IUser $user, array $requestOverrides = [], ?array $activeContext = null): PolicyContext {
50        $currentActor = $this->userSession->getUser();
51        $actorGroupIds = $activeContext !== null ? $this->getUserGroupIds($currentActor) : null;
52        $scope = $this->parseActiveGroupScope($activeContext, $currentActor, $actorGroupIds);
53        return $this->build($user?->getUID(), $user, $requestOverrides, $scope, $currentActor, $actorGroupIds);
54    }
55
56    /** @param array<string, mixed> $requestOverrides */
57    public function forUserId(?string $userId, array $requestOverrides = [], ?array $activeContext = null): PolicyContext {
58        $user = null;
59        if ($userId !== null && $userId !== '') {
60            $loadedUser = $this->userManager->get($userId);
61            if ($loadedUser instanceof IUser) {
62                $user = $loadedUser;
63            }
64        }
65
66        $currentActor = $this->userSession->getUser();
67        $actorGroupIds = $activeContext !== null ? $this->getUserGroupIds($currentActor) : null;
68        $scope = $this->parseActiveGroupScope($activeContext, $currentActor, $actorGroupIds);
69        return $this->build($userId, $user, $requestOverrides, $scope, $currentActor, $actorGroupIds);
70    }
71
72    /** @param array<string, mixed> $requestOverrides */
73    private function build(?string $userId, ?IUser $user, array $requestOverrides = [], ?ActiveGroupScope $activeGroupScope = null, ?IUser $currentActor = null, ?array $currentActorGroupIds = null): PolicyContext {
74        $isCurrentActorContext = $user instanceof IUser
75            && $currentActor instanceof IUser
76            && $user->getUID() === $currentActor->getUID();
77        $sharedGroupIds = [];
78        if ($isCurrentActorContext) {
79            $sharedGroupIds = $currentActorGroupIds ?? $this->getUserGroupIds($user);
80        }
81
82        $actorGroupIds = $currentActorGroupIds ?? ($isCurrentActorContext ? $sharedGroupIds : null);
83
84        $context = (new PolicyContext())
85            ->setRequestOverrides($requestOverrides)
86            ->setActiveGroupScope($activeGroupScope)
87            ->setActorRole($this->resolveActorRole($currentActor, $actorGroupIds));
88
89        if ($userId !== null && $userId !== '') {
90            $context->setUserId($userId);
91            if ($user instanceof IUser) {
92                $context->setGroups($isCurrentActorContext ? $sharedGroupIds : $this->getUserGroupIds($user));
93            }
94        }
95
96        return $context;
97    }
98
99    /**
100     * @param array<string, mixed>|null $activeContext
101     * @param list<string>|null $currentActorGroupIds
102     */
103    private function parseActiveGroupScope(?array $activeContext, ?IUser $currentActor, ?array $currentActorGroupIds = null): ?ActiveGroupScope {
104        if ($activeContext === null) {
105            return null;
106        }
107
108        $type = $activeContext['type'] ?? null;
109        $id = $activeContext['id'] ?? null;
110        if ($type !== 'group' || !is_string($id) || trim($id) === '') {
111            throw new LibresignException('Only group active context is supported for policy overrides.', Http::STATUS_UNPROCESSABLE_ENTITY);
112        }
113
114        $groupId = trim($id);
115        if (!$currentActor instanceof IUser) {
116            throw new LibresignException('You are not allowed to use this policy context.', Http::STATUS_UNPROCESSABLE_ENTITY);
117        }
118
119        $actorGroupIds = $currentActorGroupIds ?? $this->getUserGroupIds($currentActor);
120        if (!in_array($groupId, $actorGroupIds, true)) {
121            throw new LibresignException('You are not allowed to use this policy context.', Http::STATUS_UNPROCESSABLE_ENTITY);
122        }
123
124        return new ActiveGroupScope($groupId);
125    }
126
127    private function resolveActorRole(?IUser $currentActor, ?array $currentActorGroupIds = null): ActorRole {
128        if (!$currentActor instanceof IUser) {
129            return ActorRole::regularUser();
130        }
131
132        $userId = $currentActor->getUID();
133        if ($this->groupManager->isAdmin($userId) === true) {
134            return ActorRole::systemAdmin();
135        }
136
137        $actorGroupIds = $currentActorGroupIds ?? $this->getUserGroupIds($currentActor);
138        $manageableGroupIds = array_values(array_filter(
139            $actorGroupIds,
140            static fn (mixed $groupId): bool => is_string($groupId) && trim($groupId) !== '',
141        ));
142        if ($manageableGroupIds === []) {
143            return ActorRole::regularUser();
144        }
145
146        if ($this->subAdmin->isSubAdmin($currentActor)) {
147            return ActorRole::groupAdmin(count($manageableGroupIds));
148        }
149
150        return ActorRole::regularUser();
151    }
152
153    /** @return list<string> */
154    private function getUserGroupIds(?IUser $user): array {
155        if (!$user instanceof IUser) {
156            return [];
157        }
158
159        $groupIds = $this->groupManager->getUserGroupIds($user);
160        return array_values(array_filter(
161            $groupIds,
162            static fn (mixed $groupId): bool => is_string($groupId) && trim($groupId) !== '',
163        ));
164    }
165}