Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
54.39% covered (warning)
54.39%
31 / 57
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
SignerIpGeolocationPolicy
54.39% covered (warning)
54.39%
31 / 57
0.00% covered (danger)
0.00%
0 / 2
36.35
0.00% covered (danger)
0.00%
0 / 1
 keys
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get
57.41% covered (warning)
57.41%
31 / 54
0.00% covered (danger)
0.00%
0 / 1
29.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\SignerIpGeolocation;
10
11use OCA\Libresign\Enum\SignerIpGeolocationMode;
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 SignerIpGeolocationPolicy implements IPolicyDefinitionProvider {
21    public const KEY = 'signer_ip_geolocation';
22    public const SYSTEM_APP_CONFIG_KEY = 'signer_ip_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: SignerIpGeolocationPolicyValue::defaults(),
37                allowedValues: static fn (): array => [],
38                normalizer: static fn (mixed $rawValue): array => SignerIpGeolocationPolicyValue::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 (SignerIpGeolocationMode::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}