DomainValidation::allChallengesPassed()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 20
rs 9.6111
cc 5
nc 3
nop 1
1
<?php
2
3
namespace Rogierw\RwAcme\Endpoints;
4
5
use Rogierw\RwAcme\DTO\AccountData;
6
use Rogierw\RwAcme\DTO\DomainValidationData;
7
use Rogierw\RwAcme\DTO\OrderData;
8
use Rogierw\RwAcme\Enums\AuthorizationChallengeEnum;
9
use Rogierw\RwAcme\Exceptions\DomainValidationException;
10
use Rogierw\RwAcme\Http\Response;
11
use Rogierw\RwAcme\Support\Arr;
12
use Rogierw\RwAcme\Support\DnsDigest;
13
use Rogierw\RwAcme\Support\JsonWebKey;
14
use Rogierw\RwAcme\Support\LocalChallengeTest;
15
use Rogierw\RwAcme\Support\Thumbprint;
16
17
class DomainValidation extends Endpoint
18
{
19
    /** @return DomainValidationData[] */
20
    public function status(OrderData $orderData): array
21
    {
22
        $data = [];
23
24
        foreach ($orderData->domainValidationUrls as $domainValidationUrl) {
25
            $response = $this->client
26
                ->getHttpClient()
27
                ->post(
28
                    $domainValidationUrl,
29
                    $this->createKeyId($orderData->accountUrl, $domainValidationUrl)
30
                );
31
32
            if ($response->getHttpResponseCode() === 200) {
33
                $data[] = DomainValidationData::fromResponse($response);
34
35
                continue;
36
            }
37
38
            $this->logResponse('error', 'Cannot get domain validation', $response);
39
        }
40
41
        return $data;
42
    }
43
44
    /** @param DomainValidationData[] $challenges */
45
    public function getValidationData(array $challenges, ?AuthorizationChallengeEnum $authChallenge = null): array
46
    {
47
        $thumbprint = Thumbprint::make($this->getAccountPrivateKey());
48
49
        $authorizations = [];
50
        foreach ($challenges as $domainValidationData) {
51
            if (
52
                (is_null($authChallenge) || $authChallenge === AuthorizationChallengeEnum::HTTP)
53
                && !empty($domainValidationData->file)
54
            ) {
55
                $authorizations[] = [
56
                    'identifier' => $domainValidationData->identifier['value'],
57
                    'type' => $domainValidationData->file['type'],
58
                    'filename' => $domainValidationData->file['token'],
59
                    'content' => $domainValidationData->file['token'].'.'.$thumbprint,
60
                ];
61
            }
62
63
            if (
64
                (is_null($authChallenge) || $authChallenge === AuthorizationChallengeEnum::DNS)
65
                && !empty($domainValidationData->dns)
66
            ) {
67
                $authorizations[] = [
68
                    'identifier' => $domainValidationData->identifier['value'],
69
                    'type' => $domainValidationData->dns['type'],
70
                    'name' => '_acme-challenge',
71
                    'value' => DnsDigest::make($domainValidationData->dns['token'], $thumbprint),
72
                ];
73
            }
74
        }
75
76
        return $authorizations;
77
    }
78
79
    /** @throws \Rogierw\RwAcme\Exceptions\DomainValidationException */
80
    public function start(
81
        AccountData $accountData,
82
        DomainValidationData $domainValidation,
83
        AuthorizationChallengeEnum $authChallenge,
84
        bool $localTest = true
85
    ): Response {
86
        $this->client->logger('info', sprintf(
87
            'Start %s challenge for %s',
88
            $authChallenge->value,
89
            Arr::get($domainValidation->identifier, 'value', '')
90
        ));
91
92
        $type = $authChallenge === AuthorizationChallengeEnum::DNS ? 'dns' : 'file';
93
        $thumbprint = JsonWebKey::thumbprint(JsonWebKey::compute($this->getAccountPrivateKey()));
94
95
        if (empty($domainValidation->{$type})) {
96
            throw new DomainValidationException(sprintf('No %s challenge found for %s', $type, $domainValidation->identifier['value']));
97
        }
98
99
        $keyAuthorization = $domainValidation->{$type}['token'].'.'.$thumbprint;
100
101
        if ($localTest) {
102
            if ($authChallenge === AuthorizationChallengeEnum::HTTP) {
103
                LocalChallengeTest::http(
104
                    $domainValidation->identifier['value'],
105
                    $domainValidation->file['token'],
106
                    $keyAuthorization,
107
                    $this->client->getHttpClient()
108
                );
109
            }
110
111
            if ($authChallenge === AuthorizationChallengeEnum::DNS) {
112
                LocalChallengeTest::dns(
113
                    $domainValidation->identifier['value'],
114
                    '_acme-challenge',
115
                    DnsDigest::make($domainValidation->{$type}['token'], $thumbprint),
116
                );
117
            }
118
        }
119
120
        $payload = [
121
            'keyAuthorization' => $keyAuthorization,
122
        ];
123
124
        $data = $this->createKeyId($accountData->url, $domainValidation->{$type}['url'], $payload);
125
126
        $response = $this->client->getHttpClient()->post($domainValidation->{$type}['url'], $data);
127
128
        if ($response->getHttpResponseCode() >= 400) {
129
            $this->logResponse(
130
                'error',
131
                $response->getBody()['detail'] ?? 'Unknown error',
132
                $response,
133
                ['payload' => $payload, 'data' => $data]
134
            );
135
        }
136
137
        return $response;
138
    }
139
140
    public function allChallengesPassed(OrderData $orderData): bool
141
    {
142
        $count = 0;
143
        while (($status = $this->status($orderData)) && $count < 4) {
144
            if ($this->challengeSucceeded($status)) {
145
                break;
146
            }
147
148
            if ($count === 3) {
149
                return false;
150
            }
151
152
            $this->client->logger('info', 'Challenge is not valid yet. Another attempt in 5 seconds.');
153
154
            sleep(5);
155
156
            $count++;
157
        }
158
159
        return true;
160
    }
161
162
    /** @param DomainValidationData[] $domainValidation */
163
    private function challengeSucceeded(array $domainValidation): bool
164
    {
165
        // Verify if the challenges have been passed.
166
        foreach ($domainValidation as $status) {
167
            $this->client->logger(
168
                'info',
169
                "Check {$status->identifier['type']} challenge of {$status->identifier['value']}."
170
            );
171
172
            if (!$status->isValid()) {
173
                return false;
174
            }
175
        }
176
177
        $this->client->logger('info', 'Challenge has been passed.');
178
179
        return true;
180
    }
181
}
182