AccountService   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 19
eloc 78
c 1
b 0
f 0
dl 0
loc 177
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 6 1
A __construct() 0 5 1
A getAccount() 0 13 1
A getTmpPublicKeyPath() 0 3 1
A getPrivateKeyPath() 0 3 1
A getTmpPrivateKeyPath() 0 3 1
A createAccountFromResponse() 0 6 1
A update() 0 16 1
A getPublicKeyPath() 0 3 1
A getOrCreate() 0 6 3
A create() 0 5 1
A contactFromEmails() 0 5 2
A createAccount() 0 14 1
A keyRollover() 0 34 2
A deactivate() 0 16 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the LetsEncrypt ACME client.
7
 *
8
 * @author    Ivanov Aleksandr <[email protected]>
9
 * @copyright 2019-2020
10
 * @license   https://github.com/misantron/letsencrypt-client/blob/master/LICENSE MIT License
11
 */
12
13
namespace LetsEncrypt\Service;
14
15
use LetsEncrypt\Assertion\Assert;
16
use LetsEncrypt\Certificate\Bundle;
17
use LetsEncrypt\Entity\Account;
18
use LetsEncrypt\Exception\AccountException;
19
use LetsEncrypt\Helper\KeyGeneratorAwareTrait;
20
use LetsEncrypt\Http\ConnectorAwareTrait;
21
use LetsEncrypt\Http\Response;
22
23
class AccountService
24
{
25
    use ConnectorAwareTrait;
26
    use KeyGeneratorAwareTrait;
27
28
    /**
29
     * @var string
30
     */
31
    private $keysPath;
32
33
    public function __construct(string $keysPath)
34
    {
35
        Assert::directory($keysPath, 'Account keys directory path %s is not a directory');
36
37
        $this->keysPath = rtrim($keysPath, DIRECTORY_SEPARATOR);
38
    }
39
40
    public function get(): Account
41
    {
42
        Assert::fileExists($this->getPrivateKeyPath(), 'Private key %s does not exist');
43
        Assert::fileExists($this->getPublicKeyPath(), 'Public key %s does not exist');
44
45
        return $this->getAccount();
46
    }
47
48
    public function getOrCreate(array $emails): Account
49
    {
50
        if (file_exists($this->getPrivateKeyPath()) && file_exists($this->getPublicKeyPath())) {
51
            return $this->get();
52
        }
53
        return $this->create($emails);
54
    }
55
56
    public function create(array $emails): Account
57
    {
58
        $this->keyGenerator->rsa($this->getPrivateKeyPath(), $this->getPublicKeyPath());
59
60
        return $this->createAccount($emails);
61
    }
62
63
    public function update(array $emails): Account
64
    {
65
        $account = $this->getAccount();
66
67
        $payload = [
68
            'contact' => $this->contactFromEmails($emails),
69
        ];
70
71
        $response = $this->connector->signedKIDRequest(
72
            $account->getUrl(),
73
            $account->getUrl(),
74
            $payload,
75
            $this->getPrivateKeyPath()
76
        );
77
78
        return $this->createAccountFromResponse($response);
79
    }
80
81
    public function keyRollover(): void
82
    {
83
        $account = $this->getAccount();
84
85
        // generate new key pair
86
        $this->keyGenerator->rsa($this->getTmpPrivateKeyPath(), $this->getTmpPublicKeyPath());
87
88
        $payload = [
89
            'account' => $account->getUrl(),
90
            'oldKey' => $this->connector->getSigner()->jwk($this->getPrivateKeyPath()),
91
        ];
92
93
        $signedPayload = $this->connector->signedJWS(
94
            $this->connector->getAccountKeyChangeUrl(),
95
            $payload,
96
            $this->getTmpPrivateKeyPath()
97
        );
98
99
        $response = $this->connector->signedKIDRequest(
100
            $account->getUrl(),
101
            $this->connector->getAccountKeyChangeUrl(),
102
            $signedPayload,
103
            $this->getPrivateKeyPath()
104
        );
105
106
        if (!$response->isStatusOk()) {
107
            throw new AccountException('Account key rollover failed');
108
        }
109
110
        unlink($this->getPrivateKeyPath());
111
        unlink($this->getPublicKeyPath());
112
113
        rename($this->getTmpPrivateKeyPath(), $this->getPrivateKeyPath());
114
        rename($this->getTmpPublicKeyPath(), $this->getPublicKeyPath());
115
    }
116
117
    public function deactivate(): Account
118
    {
119
        $account = $this->getAccount();
120
121
        $payload = [
122
            'status' => 'deactivated',
123
        ];
124
125
        $response = $this->connector->signedKIDRequest(
126
            $account->getUrl(),
127
            $account->getUrl(),
128
            $payload,
129
            $this->getPrivateKeyPath()
130
        );
131
132
        return $this->createAccountFromResponse($response);
133
    }
134
135
    private function createAccount(array $emails): Account
136
    {
137
        $payload = [
138
            'contact' => $this->contactFromEmails($emails),
139
            'termsOfServiceAgreed' => true,
140
        ];
141
142
        $response = $this->connector->signedJWSRequest(
143
            $this->connector->getNewAccountUrl(),
144
            $payload,
145
            $this->getPrivateKeyPath()
146
        );
147
148
        return $this->createAccountFromResponse($response);
149
    }
150
151
    private function getAccount(): Account
152
    {
153
        $payload = [
154
            'onlyReturnExisting' => true,
155
        ];
156
157
        $response = $this->connector->signedJWSRequest(
158
            $this->connector->getNewAccountUrl(),
159
            $payload,
160
            $this->getPrivateKeyPath()
161
        );
162
163
        return $this->createAccountFromResponse($response);
164
    }
165
166
    private function contactFromEmails(array $emails): array
167
    {
168
        return array_map(static function (string $email) {
169
            return strpos($email, 'mailto') === false ? 'mailto:' . $email : $email;
170
        }, $emails);
171
    }
172
173
    private function createAccountFromResponse(Response $response): Account
174
    {
175
        return new Account(
176
            $response->getDecodedContent(),
177
            $response->getLocation(),
178
            $this->getPrivateKeyPath()
179
        );
180
    }
181
182
    private function getPrivateKeyPath(): string
183
    {
184
        return $this->keysPath . DIRECTORY_SEPARATOR . Bundle::PRIVATE_KEY;
185
    }
186
187
    private function getTmpPrivateKeyPath(): string
188
    {
189
        return $this->getPrivateKeyPath() . '.new';
190
    }
191
192
    private function getPublicKeyPath(): string
193
    {
194
        return $this->keysPath . DIRECTORY_SEPARATOR . Bundle::PUBLIC_KEY;
195
    }
196
197
    private function getTmpPublicKeyPath(): string
198
    {
199
        return $this->getPublicKeyPath() . '.new';
200
    }
201
}
202