Completed
Push — master ( 96d93d...d48be5 )
by Rogier
26s queued 14s
created

LocalChallengeTest::validateTxtRecords()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 9
rs 10
cc 3
nc 3
nop 2
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(string $domain, string $token, string $keyAuthorization, HttpClientInterface $httpClient): void
15
    {
16
        $response = $httpClient->get($domain . '/.well-known/acme-challenge/' . $token, maxRedirects: 1);
17
18
        $body = $response->getBody();
19
20
        if (is_array($body)) {
21
            $body = json_encode($body, JSON_THROW_ON_ERROR);
22
        }
23
24
        if (trim($body) === $keyAuthorization) {
25
            return;
26
        }
27
28
        throw DomainValidationException::localHttpChallengeTestFailed(
29
            $domain,
30
            $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

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