Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.74% covered (success)
94.74%
18 / 19
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SignerIpGeolocationPolicyService
94.74% covered (success)
94.74%
18 / 19
80.00% covered (warning)
80.00%
4 / 5
9.01
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getPolicyValue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isEnabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 collectMetadata
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 mergeIntoMetadata
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
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\SignerIpGeolocation;
10
11use OCA\Libresign\Db\File as FileEntity;
12use OCA\Libresign\Db\FileMapper;
13use OCA\Libresign\Service\GeoIp\GeoIpLookupService;
14use OCA\Libresign\Service\Policy\FrozenFilePolicySnapshotResolver;
15use OCA\Libresign\Service\Policy\Provider\SignerIpGeolocation\SignerIpGeolocationPolicy;
16use OCA\Libresign\Service\Policy\Provider\SignerIpGeolocation\SignerIpGeolocationPolicyValue;
17use OCA\Libresign\Service\SignerGeolocation\SignerGeolocationMetadataValidator;
18use OCP\IRequest;
19
20/**
21 * Reads frozen IP geolocation policy from a signature request and collects IP
22 * metadata at signing time. The live PolicyService is never consulted.
23 */
24class SignerIpGeolocationPolicyService {
25    /** @var FrozenFilePolicySnapshotResolver<array{mode: string}> */
26    private FrozenFilePolicySnapshotResolver $frozenSnapshotResolver;
27
28    public function __construct(
29        FileMapper $fileMapper,
30        private GeoIpLookupService $geoIpLookupService,
31    ) {
32        $this->frozenSnapshotResolver = new FrozenFilePolicySnapshotResolver(
33            $fileMapper,
34            SignerIpGeolocationPolicy::KEY,
35            SignerIpGeolocationPolicyValue::normalize(...),
36        );
37    }
38
39    /**
40     * @return array{mode: string}
41     */
42    public function getPolicyValue(?FileEntity $file = null): array {
43        return $this->frozenSnapshotResolver->findSnapshot($file)
44            ?? SignerIpGeolocationPolicyValue::defaults();
45    }
46
47    public function isEnabled(?FileEntity $file = null): bool {
48        return SignerIpGeolocationPolicyValue::isEnabled($this->getPolicyValue($file));
49    }
50
51    /**
52     * Collect IP geolocation metadata when the frozen snapshot enables it.
53     *
54     * Never blocks signing: lookup failures are stored as unavailable/not_found.
55     *
56     * @return array<string, mixed>|null null when IP geolocation is disabled for this request
57     */
58    public function collectMetadata(?FileEntity $file, IRequest $request): ?array {
59        if (!$this->isEnabled($file)) {
60            return null;
61        }
62
63        $remoteAddress = $request->getRemoteAddress();
64        $sourceIp = is_string($remoteAddress) ? trim($remoteAddress) : '';
65        return $this->geoIpLookupService->lookup($sourceIp === '' ? null : $sourceIp);
66    }
67
68    /**
69     * Merge IP metadata into the signing metadata bag under geolocation.ip.
70     *
71     * @param array<string, mixed> $metadata
72     * @param array<string, mixed> $ipGeolocation
73     * @return array<string, mixed>
74     */
75    public function mergeIntoMetadata(array $metadata, array $ipGeolocation): array {
76        $geolocation = $metadata[SignerGeolocationMetadataValidator::METADATA_GEOLOCATION_KEY] ?? [];
77        if (!is_array($geolocation)) {
78            $geolocation = [];
79        }
80        $geolocation[SignerGeolocationMetadataValidator::METADATA_IP_KEY] = $ipGeolocation;
81        $metadata[SignerGeolocationMetadataValidator::METADATA_GEOLOCATION_KEY] = $geolocation;
82        return $metadata;
83    }
84}