LocalChallengeTest   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Importance

Changes 12
Bugs 1 Features 2
Metric Value
wmc 18
eloc 45
c 12
b 1
f 2
dl 0
loc 102
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A dns() 0 22 4
A validateTxtRecords() 0 9 3
A getRecords() 0 7 1
A getNameserver() 0 9 2
A http() 0 21 3
A validateCnameRecords() 0 19 5
1
<?php
2
3
namespace Rogierw\RwAcme\Support;
4
5
use Rogierw\RwAcme\Exceptions\DomainValidationException;
6
use Rogierw\RwAcme\Interfaces\HttpClientInterface;
7
use RuntimeException;
8
use Spatie\Dns\Dns;
9
10
class LocalChallengeTest
11
{
12
    private const DEFAULT_NAMESERVER = 'dns.google.com';
13
14
    public static function http(
15
        string $domain,
16
        string $token,
17
        string $keyAuthorization,
18
        HttpClientInterface $httpClient
19
    ): void {
20
        $response = $httpClient->get($domain . '/.well-known/acme-challenge/' . $token, maxRedirects: 1);
21
22
        $body = $response->getBody();
23
24
        if (is_array($body)) {
25
            $body = json_encode($body, JSON_THROW_ON_ERROR);
26
        }
27
28
        if (trim($body) === $keyAuthorization) {
29
            return;
30
        }
31
32
        throw DomainValidationException::localHttpChallengeTestFailed(
33
            $domain,
34
            $response->getHttpResponseCode()
0 ignored issues
show
Bug introduced by
It seems like $response->getHttpResponseCode() can also be of type null; however, parameter $code of Rogierw\RwAcme\Exception...tpChallengeTestFailed() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

34
            /** @scrutinizer ignore-type */ $response->getHttpResponseCode()
Loading history...
35
        );
36
    }
37
38
    public static function dns(string $domain, string $name, string $value): void
39
    {
40
        try {
41
            $challenge = sprintf('%s.%s', $name, $domain);
42
43
            // Try to validate TXT records directly.
44
            $nameserver = self::getNameserver($domain);
45
            $txtRecords = self::getRecords($nameserver, $challenge, DNS_TXT);
46
            if (self::validateTxtRecords($txtRecords, $value)) {
47
                return;
48
            }
49
50
            // Try to validate a CNAME record pointing to a TXT record containing the correct value.
51
            $cnameRecords = self::getRecords($nameserver, $challenge, DNS_CNAME);
52
            if (self::validateCnameRecords($cnameRecords, $value)) {
53
                return;
54
            }
55
        } catch (RuntimeException) {
56
            // An exception can be thrown by the Dns class when a lookup fails.
57
        }
58
59
        throw DomainValidationException::localDnsChallengeTestFailed($domain);
60
    }
61
62
    private static function validateTxtRecords(array $records, string $value): bool
63
    {
64
        foreach ($records as $record) {
65
            if ($record->txt() === $value) {
66
                return true;
67
            }
68
        }
69
70
        return false;
71
    }
72
73
    private static function validateCnameRecords(array $records, string $value): bool
74
    {
75
        foreach ($records as $record) {
76
            $nameserver = self::getNameserver($record->target());
77
            $txtRecords = self::getRecords($nameserver, $record->target(), DNS_TXT);
78
            if (self::validateTxtRecords($txtRecords, $value)) {
79
                return true;
80
            }
81
82
            // If this is another CNAME, follow it.
83
            $cnameRecords = self::getRecords($nameserver, $record->target(), DNS_CNAME);
84
            if (!empty($cnameRecords)) {
85
                if (self::validateCnameRecords($cnameRecords, $value)) {
86
                    return true;
87
                }
88
            }
89
        }
90
91
        return false;
92
    }
93
94
    private static function getNameserver(string $domain): string
95
    {
96
        $dnsResolver = new Dns();
97
98
        $result = $dnsResolver->getRecords($domain, DNS_NS);
99
100
        return empty($result)
101
            ? self::DEFAULT_NAMESERVER
102
            : $result[0]->target();
103
    }
104
105
    private static function getRecords(string $nameserver, string $name, int $dnsType): array
106
    {
107
        $dnsResolver = new Dns();
108
109
        return $dnsResolver
110
            ->useNameserver($nameserver)
111
            ->getRecords($name, $dnsType);
112
    }
113
}
114