Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
GeoIpLookupResultNormalizer
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
3 / 3
22
100.00% covered (success)
100.00%
1 / 1
 normalizeResolved
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
16
 normalizeNotFound
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 normalizeUnavailable
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
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\GeoIp;
10
11use OCA\Libresign\Enum\SignerIpGeolocationStatus;
12use OCA\Libresign\Enum\SignerIpGeolocationUnavailableReason;
13use OCA\Libresign\Vendor\GeoIp2\Model\City;
14
15/**
16 * Normalizes MaxMind City lookup results into signer metadata.
17 *
18 * Only fields present in the database result are stored. Postal address data is
19 * intentionally omitted.
20 */
21class GeoIpLookupResultNormalizer {
22    /**
23     * @return array{
24     *     status: string,
25     *     sourceIp?: string,
26     *     countryCode?: string,
27     *     country?: string,
28     *     regionCode?: string,
29     *     region?: string,
30     *     city?: string,
31     *     latitude?: float,
32     *     longitude?: float,
33     *     accuracyRadius?: int,
34     * }
35     */
36    public function normalizeResolved(City $city, ?string $sourceIp): array {
37        $result = [
38            'status' => SignerIpGeolocationStatus::RESOLVED->value,
39        ];
40
41        if ($sourceIp !== null && $sourceIp !== '') {
42            $result['sourceIp'] = $sourceIp;
43        }
44
45        $countryCode = $city->country->isoCode;
46        if (is_string($countryCode) && $countryCode !== '') {
47            $result['countryCode'] = $countryCode;
48        }
49
50        $countryName = $city->country->name;
51        if (is_string($countryName) && $countryName !== '') {
52            $result['country'] = $countryName;
53        }
54
55        $regionCode = $city->mostSpecificSubdivision->isoCode;
56        if (is_string($regionCode) && $regionCode !== '') {
57            $result['regionCode'] = $regionCode;
58        }
59
60        $regionName = $city->mostSpecificSubdivision->name;
61        if (is_string($regionName) && $regionName !== '') {
62            $result['region'] = $regionName;
63        }
64
65        $cityName = $city->city->name;
66        if (is_string($cityName) && $cityName !== '') {
67            $result['city'] = $cityName;
68        }
69
70        $latitude = $city->location->latitude;
71        $longitude = $city->location->longitude;
72        if ($latitude !== null && $longitude !== null) {
73            $result['latitude'] = $latitude;
74            $result['longitude'] = $longitude;
75
76            $accuracyRadius = $city->location->accuracyRadius;
77            if ($accuracyRadius !== null) {
78                $result['accuracyRadius'] = $accuracyRadius;
79            }
80        }
81
82        return $result;
83    }
84
85    /**
86     * @return array{status: string, sourceIp?: string}
87     */
88    public function normalizeNotFound(?string $sourceIp): array {
89        $result = [
90            'status' => SignerIpGeolocationStatus::NOT_FOUND->value,
91        ];
92        if ($sourceIp !== null && $sourceIp !== '') {
93            $result['sourceIp'] = $sourceIp;
94        }
95        return $result;
96    }
97
98    /**
99     * @return array{status: string, sourceIp?: string, reason: string}
100     */
101    public function normalizeUnavailable(
102        SignerIpGeolocationUnavailableReason $reason,
103        ?string $sourceIp,
104    ): array {
105        $result = [
106            'status' => SignerIpGeolocationStatus::UNAVAILABLE->value,
107            'reason' => $reason->value,
108        ];
109        if ($sourceIp !== null && $sourceIp !== '') {
110            $result['sourceIp'] = $sourceIp;
111        }
112        return $result;
113    }
114}