Completed
Pull Request — master (#8)
by John
02:04
created

LEAccount::updateAccount()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 19
nc 5
nop 1
dl 0
loc 28
ccs 18
cts 18
cp 1
crap 6
rs 9.0111
c 0
b 0
f 0
1
<?php
2
3
namespace LEClient;
4
5
use LEClient\Exceptions\LEAccountException;
6
7
/**
8
 * LetsEncrypt Account class, containing the functions and data associated with a LetsEncrypt account.
9
 *
10
 * PHP version 5.2.0
11
 *
12
 * MIT License
13
 *
14
 * Copyright (c) 2018 Youri van Weegberg
15
 *
16
 * Permission is hereby granted, free of charge, to any person obtaining a copy
17
 * of this software and associated documentation files (the "Software"), to deal
18
 * in the Software without restriction, including without limitation the rights
19
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
 * copies of the Software, and to permit persons to whom the Software is
21
 * furnished to do so, subject to the following conditions:
22
 *
23
 * The above copyright notice and this permission notice shall be included in all
24
 * copies or substantial portions of the Software.
25
 *
26
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
 * SOFTWARE.
33
 *
34
 * @author     Youri van Weegberg <[email protected]>
35
 * @copyright  2018 Youri van Weegberg
36
 * @license    https://opensource.org/licenses/mit-license.php  MIT License
37
 * @link       https://github.com/yourivw/LEClient
38
 * @since      Class available since Release 1.0.0
39
 */
40
class LEAccount
41 12
{
42
	private $connector;
43 12
	private $accountKeys;
44 12
45 12
	public $id;
46
	public $key;
47 12
	public $contact;
48 12
	public $agreement;
49
	public $initialIp;
50 12
	public $createdAt;
51 12
	public $status;
52 12
53
	private $log;
54 12
55
    /**
56 4
     * Initiates the LetsEncrypt Account class.
57
     *
58 12
     * @param LEConnector	$connector 		The LetsEncrypt Connector instance to use for HTTP requests.
59 2
     * @param int 			$log 			The level of logging. Defaults to no logging. LOG_OFF, LOG_STATUS, LOG_DEBUG accepted.
60
     * @param array 		$email	 		The array of strings containing e-mail addresses. Only used when creating a new account.
61 12
     * @param array 		$accountKeys 	Array containing location of account keys files.
62 12
     */
63
	public function __construct($connector, $log, $email, $accountKeys)
64
	{
65
		$this->connector = $connector;
66
		$this->accountKeys = $accountKeys;
67
		$this->log = $log;
68
69
		if(!file_exists($this->accountKeys['private_key']) OR !file_exists($this->accountKeys['public_key']))
70
		{
71
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
72
			{
73 12
				$this->log->info('No account found, attempting to create account.');
74 12
			}
75 12
			else if($this->log >= LECLient::LOG_STATUS) LEFunctions::log('No account found, attempting to create account.', 'function LEAccount __construct');
0 ignored issues
show
Bug introduced by
'No account found, attempting to create account.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

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

75
			else if($this->log >= LECLient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'No account found, attempting to create account.', 'function LEAccount __construct');
Loading history...
76
			
77 12
			LEFunctions::RSAgenerateKeys(null, $this->accountKeys['private_key'], $this->accountKeys['public_key']);
78 12
			$this->connector->accountURL = $this->createLEAccount($email);
79 12
		}
80
		else
81 12
		{
82 12
			$this->connector->accountURL = $this->getLEAccount();
83 12
		}
84 12
		if($this->connector->accountURL == false) throw LEAccountException::AccountNotFoundException();
0 ignored issues
show
introduced by
The condition $this->connector->accountURL == false is always false.
Loading history...
85
		$this->getLEAccountData();
86
	}
87
88
    /**
89
     * Creates a new LetsEncrypt account.
90
     *
91
     * @param array 	$email 	The array of strings containing e-mail addresses.
92
     *
93
     * @return object	Returns the new account URL when the account was successfully created, false if not.
94
     */
95
	private function createLEAccount($email)
96
	{
97 4
		$contact = array_map(function($addr) { return empty($addr) ? '' : (strpos($addr, 'mailto') === false ? 'mailto:' . $addr : $addr); }, $email);
98
99 4
		$sign = $this->connector->signRequestJWK(array('contact' => $contact, 'termsOfServiceAgreed' => true), $this->connector->newAccount);
100 4
		$post = $this->connector->post($this->connector->newAccount, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

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

100
		$post = $this->connector->post($this->connector->newAccount, /** @scrutinizer ignore-type */ $sign);
Loading history...
101
		if($post['status'] === 201)
102 4
		{
103 2
			if(preg_match('~Location: (\S+)~i', $post['header'], $matches)) return trim($matches[1]);
104 2
		}
105
		return false;
106
	}
107 2
108
    /**
109
     * Gets the LetsEncrypt account URL associated with the stored account keys.
110
     *
111
     * @return object	Returns the account URL if it is found, or false when none is found.
112
     */
113 12
	private function getLEAccount()
114
	{
115 12
		$sign = $this->connector->signRequestJWK(array('onlyReturnExisting' => true), $this->connector->newAccount);
116 12
		$post = $this->connector->post($this->connector->newAccount, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

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

116
		$post = $this->connector->post($this->connector->newAccount, /** @scrutinizer ignore-type */ $sign);
Loading history...
117 12
118 12
		if($post['status'] === 200)
119
		{
120 12
			if(preg_match('~Location: (\S+)~i', $post['header'], $matches)) return trim($matches[1]);
121 12
		}
122 12
		return false;
123 12
	}
124 12
125 12
    /**
126 12
     * Gets the LetsEncrypt account data from the account URL.
127 12
     */
128 12
	private function getLEAccountData()
129
	{
130
		$sign = $this->connector->signRequestKid(array('' => ''), $this->connector->accountURL, $this->connector->accountURL);
131
		$post = $this->connector->post($this->connector->accountURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

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

131
		$post = $this->connector->post($this->connector->accountURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
132
		if($post['status'] === 200)
133
		{
134 12
			$this->id = isset($post['body']['id']) ? $post['body']['id'] : '';
135
			$this->key = $post['body']['key'];
136
			$this->contact = $post['body']['contact'];
137
			$this->agreement = isset($post['body']['agreement']) ? $post['body']['agreement'] : '';
138
			$this->initialIp = $post['body']['initialIp'];
139
			$this->createdAt = $post['body']['createdAt'];
140
			$this->status = $post['body']['status'];
141
		}
142
		else
143
		{
144
			throw LEAccountException::AccountNotFoundException();
145 2
		}
146 2
	}
147 2
148
    /**
149 2
     * Updates account data. Now just supporting new contact information.
150 2
     *
151 2
     * @param array 	$email	The array of strings containing e-mail adresses.
152 2
     *
153
     * @return boolean	Returns true if the update is successful, false if not.
154 2
     */
155 2
	public function updateAccount($email)
156
	{
157
		$contact = array_map(function($addr) { return empty($addr) ? '' : (strpos($addr, 'mailto') === false ? 'mailto:' . $addr : $addr); }, $email);
158
159
		$sign = $this->connector->signRequestKid(array('contact' => $contact), $this->connector->accountURL, $this->connector->accountURL);
160
		$post = $this->connector->post($this->connector->accountURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

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

160
		$post = $this->connector->post($this->connector->accountURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
161 2
		if($post['status'] === 200)
162 2
		{
163 2
			$this->id = isset($post['body']['id']) ? $post['body']['id'] : '';
164 2
			$this->key = $post['body']['key'];
165 2
			$this->contact = $post['body']['contact'];
166 2
			$this->agreement = isset($post['body']['agreement']) ? $post['body']['agreement'] : '';
167 2
			$this->initialIp = $post['body']['initialIp'];
168
			$this->createdAt = $post['body']['createdAt'];
169 2
			$this->status = $post['body']['status'];
170 2
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
171
			{
172
				$this->log->info('Account data updated.');
173
			}
174
			else if($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Account data updated.', 'function updateAccount');
0 ignored issues
show
Bug introduced by
'Account data updated.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

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

174
			else if($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Account data updated.', 'function updateAccount');
Loading history...
175
			return true;
176
		}
177
		else
178 2
		{
179
			return false;
180 2
		}
181
	}
182 2
183 2
    /**
184
     * Creates new RSA account keys and updates the keys with LetsEncrypt.
185
     *
186
     * @return boolean	Returns true if the update is successful, false if not.
187
     */
188
	public function changeAccountKeys()
189
	{
190 2
		LEFunctions::RSAgenerateKeys(null, $this->accountKeys['private_key'].'.new', $this->accountKeys['public_key'].'.new');
191 2
		$oldPrivateKey = openssl_pkey_get_private(file_get_contents($this->accountKeys['private_key']));
192 2
		$oldDetails = openssl_pkey_get_details($oldPrivateKey);
193 2
		$innerPayload = array('account' => $this->connector->accountURL, 'oldKey' => array(
194 2
			"kty" => "RSA",
195
			"n" => LEFunctions::Base64UrlSafeEncode($oldDetails["rsa"]["n"]),
196 2
			"e" => LEFunctions::Base64UrlSafeEncode($oldDetails["rsa"]["e"])
197 2
		));
198 2
		$outerPayload = $this->connector->signRequestJWK($innerPayload, $this->connector->keyChange, $this->accountKeys['private_key'].'.new');
199 2
		$sign = $this->connector->signRequestKid($outerPayload, $this->connector->accountURL, $this->connector->keyChange);
0 ignored issues
show
Bug introduced by
$outerPayload of type string is incompatible with the type array expected by parameter $payload of LEClient\LEConnector::signRequestKid(). ( Ignorable by Annotation )

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

199
		$sign = $this->connector->signRequestKid(/** @scrutinizer ignore-type */ $outerPayload, $this->connector->accountURL, $this->connector->keyChange);
Loading history...
200
		$post = $this->connector->post($this->connector->keyChange, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

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

200
		$post = $this->connector->post($this->connector->keyChange, /** @scrutinizer ignore-type */ $sign);
Loading history...
201 2
		if($post['status'] === 200)
202 2
		{
203 2
			unlink($this->accountKeys['private_key']);
204 2
			unlink($this->accountKeys['public_key']);
205
			rename($this->accountKeys['private_key'].'.new', $this->accountKeys['private_key']);
206 2
			rename($this->accountKeys['public_key'].'.new', $this->accountKeys['public_key']);
207 2
			
208
			$this->getLEAccountData();
209
210
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
211
			{
212
				$this->log->info('Account keys changed.');
213 2
			}
214
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Account keys changed.', 'function changeAccountKey');
0 ignored issues
show
Bug introduced by
'Account keys changed.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

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

214
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Account keys changed.', 'function changeAccountKey');
Loading history...
215 2
			return true;
216 2
		}
217
		else
218 2
		{
219 2
			return false;
220
		}
221
	}
222
223
    /**
224
     * Deactivates the LetsEncrypt account.
225
     *
226
     * @return boolean	Returns true if the deactivation is successful, false if not.
227 2
     */
228
	public function deactivateAccount()
229 2
	{
230 2
		$sign = $this->connector->signRequestKid(array('status' => 'deactivated'), $this->connector->accountURL, $this->connector->accountURL);
231 2
		$post = $this->connector->post($this->connector->accountURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

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

231
		$post = $this->connector->post($this->connector->accountURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
232 2
		if($post['status'] === 200)
233
		{
234 2
			$this->connector->accountDeactivated = true;
235 2
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
236
			{
237
				$this->log->info('Account deactivated.');
238
			}
239
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Account deactivated.', 'function deactivateAccount');
0 ignored issues
show
Bug introduced by
'Account deactivated.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

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

239
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Account deactivated.', 'function deactivateAccount');
Loading history...
240
			
241
			return true;
242 2
		}
243 2
		else
244 2
		{
245
			return false;
246
		}
247
	}
248
}
249