Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.16% covered (warning)
63.16%
36 / 57
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
SignerGeolocationPolicy
63.16% covered (warning)
63.16%
36 / 57
50.00% covered (danger)
50.00%
1 / 2
26.25
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
61.11% covered (warning)
61.11%
33 / 54
0.00% covered (danger)
0.00%
0 / 1
25.53
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\SignerGeolocation;
10
11use OCA\Libresign\Enum\SignerGeolocationMode;
12use OCA\Libresign\Service\Policy\Contract\IPolicyDefinition;
13use OCA\Libresign\Service\Policy\Contract\IPolicyDefinitionProvider;
14use OCA\Libresign\Service\Policy\Model\PolicyContext;
15use OCA\Libresign\Service\Policy\Model\PolicyLayer;
16use OCA\Libresign\Service\Policy\Model\PolicySpec;
17use OCA\Libresign\Service\Policy\Provider\Helper\DelegationLayerHelper;
18use OCA\Libresign\Service\Policy\Provider\Helper\PolicyKeyNormalizer;
19
20final class SignerGeolocationPolicy implements IPolicyDefinitionProvider {
21    public const KEY = 'signer_device_geolocation';
22    public const SYSTEM_APP_CONFIG_KEY = 'signer_device_geolocation';
23
24    #[\Override]
25    public function keys(): array {
26        return [
27            self::KEY,
28        ];
29    }
30
31    #[\Override]
32    public function get(string|\BackedEnum $policyKey): IPolicyDefinition {
33        return match (PolicyKeyNormalizer::normalize($policyKey)) {
34            self::KEY => new PolicySpec(
35                key: self::KEY,
36                defaultSystemValue: SignerGeolocationPolicyValue::defaults(),
37                allowedValues: static fn (): array => [],
38                normalizer: static fn (mixed $rawValue): array => SignerGeolocationPolicyValue::normalize($rawValue),
39                validator: static function (mixed $value): void {
40                    if (!is_array($value)) {
41                        throw new \InvalidArgumentException('Invalid value for ' . self::KEY);
42                    }
43                    if (!array_key_exists('mode', $value)) {
44                        throw new \InvalidArgumentException('Missing "mode" key in ' . self::KEY);
45                    }
46                    if (SignerGeolocationMode::tryFrom((string)$value['mode']) === null) {
47                        throw new \InvalidArgumentException('Invalid mode in ' . self::KEY);
48                    }
49                },
50                appConfigKey: self::SYSTEM_APP_CONFIG_KEY,
51                supportsRequestOverride: true,
52                requestLifecycle: PolicySpec::LIFECYCLE_REQUEST_SNAPSHOT,
53                supportedScopes: [
54                    PolicySpec::SCOPE_SYSTEM,
55                    PolicySpec::SCOPE_GROUP,
56                    PolicySpec::SCOPE_USER,
57                ],
58                groupPolicyManager: static function (PolicyContext $context, ?PolicyLayer $systemPolicy, array $groupLayers): bool {
59                    $actorRole = $context->getActorRole();
60
61                    if ($actorRole->canManageSystemPolicies) {
62                        return true;
63                    }
64
65                    if (!$actorRole->canManageGroupPolicies) {
66                        return false;
67                    }
68
69                    if ($actorRole->manageableGroupCount < 1) {
70                        return false;
71                    }
72
73                    if (DelegationLayerHelper::hasExplicitGlobalDelegation($systemPolicy)) {
74                        return true;
75                    }
76
77                    return DelegationLayerHelper::hasSystemCreatedGroupDelegation($groupLayers);
78                },
79                systemCreatedGroupRuleEditor: static function (PolicyContext $context, ?PolicyLayer $systemPolicy, PolicyLayer $existingPolicy): bool {
80                    $actorRole = $context->getActorRole();
81
82                    if ($actorRole->canManageSystemPolicies) {
83                        return true;
84                    }
85
86                    if (!$actorRole->canManageGroupPolicies) {
87                        return false;
88                    }
89
90                    if (!$existingPolicy->isVisibleToChild()) {
91                        return false;
92                    }
93
94                    if (!$existingPolicy->isAllowChildOverride()) {
95                        return false;
96                    }
97
98                    if ($existingPolicy->getValue() === null) {
99                        return false;
100                    }
101
102                    if (DelegationLayerHelper::hasExplicitGlobalDelegation($systemPolicy)) {
103                        return true;
104                    }
105
106                    return $existingPolicy->isCreatedBySystemAdmin();
107                },
108                supportsGroupAdminDelegation: true,
109            ),
110            default => throw new \InvalidArgumentException('Unknown policy key: ' . PolicyKeyNormalizer::normalize($policyKey)),
111        };
112    }
113}