Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.94% covered (success)
91.94%
57 / 62
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
RequestSignGroupsPolicy
91.94% covered (success)
91.94%
57 / 62
50.00% covered (danger)
50.00%
1 / 2
16.13
0.00% covered (danger)
0.00%
0 / 1
 keys
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 get
91.53% covered (success)
91.53%
54 / 59
0.00% covered (danger)
0.00%
0 / 1
15.14
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\Provider\RequestSignGroups;
10
11use OCA\Libresign\Service\Policy\Contract\IPolicyDefinition;
12use OCA\Libresign\Service\Policy\Contract\IPolicyDefinitionProvider;
13use OCA\Libresign\Service\Policy\Model\PolicyContext;
14use OCA\Libresign\Service\Policy\Model\PolicyLayer;
15use OCA\Libresign\Service\Policy\Model\PolicySpec;
16use OCA\Libresign\Service\Policy\Provider\Helper\DelegationLayerHelper;
17use OCA\Libresign\Service\Policy\Provider\Helper\PolicyKeyNormalizer;
18
19final class RequestSignGroupsPolicy implements IPolicyDefinitionProvider {
20    public const KEY = 'groups_request_sign';
21    public const SYSTEM_APP_CONFIG_KEY = self::KEY;
22
23    #[\Override]
24    public function keys(): array {
25        return [
26            self::KEY,
27        ];
28    }
29
30    #[\Override]
31    public function get(string|\BackedEnum $policyKey): IPolicyDefinition {
32        $normalizedKey = PolicyKeyNormalizer::normalize($policyKey);
33
34        return match ($normalizedKey) {
35            self::KEY => new PolicySpec(
36                key: self::KEY,
37                defaultSystemValue: RequestSignGroupsPolicyValue::encode([
38                    'allowGroups' => RequestSignGroupsPolicyValue::DEFAULT_ALLOW_GROUPS,
39                    'denyGroups' => RequestSignGroupsPolicyValue::DEFAULT_DENY_GROUPS,
40                ]),
41                allowedValues: static fn (PolicyContext $context): array => [],
42                normalizer: static fn (mixed $rawValue): mixed => RequestSignGroupsPolicyValue::encode($rawValue),
43                validator: static function (mixed $value): void {
44                    if (!is_string($value)) {
45                        throw new \InvalidArgumentException('Invalid value for ' . self::KEY);
46                    }
47
48                    $decoded = RequestSignGroupsPolicyValue::decodePolicy($value);
49                    if ($decoded['allowGroups'] === []) {
50                        throw new \InvalidArgumentException('At least one authorized group is required for ' . self::KEY);
51                    }
52                },
53                appConfigKey: self::SYSTEM_APP_CONFIG_KEY,
54                supportsUserPreference: false,
55                supportsRequestOverride: false,
56                requestLifecycle: PolicySpec::LIFECYCLE_RUNTIME,
57                supportedScopes: [PolicySpec::SCOPE_SYSTEM, PolicySpec::SCOPE_GROUP],
58                visibleGroupCountFilter: static function (PolicyContext $context, ?PolicyLayer $systemPolicy): bool {
59                    return !$context->getActorRole()->canManageSystemPolicies;
60                },
61                groupPolicyManager: static function (PolicyContext $context, ?PolicyLayer $systemPolicy, array $groupLayers): bool {
62                    $actorRole = $context->getActorRole();
63                    if ($actorRole->canManageSystemPolicies) {
64                        return true;
65                    }
66
67                    if (!$actorRole->canManageGroupPolicies) {
68                        return false;
69                    }
70
71                    if ($actorRole->manageableGroupCount < 1) {
72                        return false;
73                    }
74
75                    return DelegationLayerHelper::hasExplicitGlobalDelegation($systemPolicy)
76                        || DelegationLayerHelper::hasSystemCreatedGroupDelegation($groupLayers);
77                },
78                systemCreatedGroupRuleEditor: static function (PolicyContext $context, ?PolicyLayer $systemPolicy, PolicyLayer $existingPolicy): bool {
79                    $actorRole = $context->getActorRole();
80                    if ($actorRole->canManageSystemPolicies) {
81                        return true;
82                    }
83
84                    if (!$actorRole->canManageGroupPolicies) {
85                        return false;
86                    }
87
88                    if (!$existingPolicy->isVisibleToChild() || !$existingPolicy->isAllowChildOverride() || $existingPolicy->getValue() === null) {
89                        return false;
90                    }
91
92                    if (DelegationLayerHelper::hasExplicitGlobalDelegation($systemPolicy)) {
93                        return true;
94                    }
95
96                    return $existingPolicy->isCreatedBySystemAdmin();
97                },
98                supportsGroupAdminDelegation: true,
99                delegatedValueValidator: static function (mixed $proposedNormalizedValue): void {
100                    if (!is_string($proposedNormalizedValue)) {
101                        return;
102                    }
103
104                    $decoded = RequestSignGroupsPolicyValue::decodePolicy($proposedNormalizedValue);
105                    if ($decoded['denyGroups'] === []) {
106                        throw new \InvalidArgumentException('This group is already authorized by a system administrator. Add a deny rule to override it.');
107                    }
108                },
109            ),
110            default => throw new \InvalidArgumentException('Unknown policy key: ' . $normalizedKey),
111        };
112    }
113}