Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.62% covered (success)
98.62%
143 / 145
88.24% covered (warning)
88.24%
15 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
phpbb_update_get_updates_test
98.62% covered (success)
98.62%
143 / 145
88.24% covered (warning)
88.24%
15 / 17
21
0.00% covered (danger)
0.00%
0 / 1
 setUp
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 tearDown
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 test_download_success
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 test_download_failure
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 test_validate_success
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 test_validate_file_not_exist
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 test_validate_sig_not_exist
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 test_validate_file_not_accessible
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 test_validate_sig_not_accessible
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
2.00
 test_validate_sig_not_base64
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 test_validate_invalid_pub_key
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 test_validate_fail
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 test_validate_invalid_pub_key_length
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 test_extract_success
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 test_extract_failure
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 test_copy_success
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 test_copy_failure
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 *
4 * This file is part of the phpBB Forum Software package.
5 *
6 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7 * @license GNU General Public License, version 2 (GPL-2.0)
8 *
9 * For full copyright and license information, please see
10 * the docs/CREDITS.txt file.
11 *
12 */
13
14use GuzzleHttp\Client;
15use GuzzleHttp\Exception\ClientException;
16use GuzzleHttp\Psr7\Response;
17use phpbb\filesystem\exception\filesystem_exception;
18use phpbb\filesystem\filesystem_interface;
19use phpbb\update\get_updates;
20
21class phpbb_update_get_updates_test extends phpbb_test_case
22{
23    private $filesystem;
24    private $http_client;
25    private $zipper;
26    private $update;
27    private $public_key = 'atest_public_keyatest_public_keyatest_public_keyatest_public_key';
28
29    private $file_path = __DIR__ . '/../tmp/download.zip';
30
31    private $signature_path = __DIR__ . '/../tmp/signature.sig';
32
33    private $phpbb_root_path;
34
35    public function setUp(): void
36    {
37        global $phpbb_root_path;
38
39        parent::setUp();
40
41        $this->filesystem = $this->createMock(filesystem_interface::class);
42        $this->http_client = $this->createMock(Client::class);
43        $this->zipper = $this->createMock(ZipArchive::class);
44        $this->phpbb_root_path = $phpbb_root_path;
45
46        // Set up the `get_updates` instance with injected mocks.
47        $this->update = new get_updates($this->filesystem, base64_encode($this->public_key), $this->phpbb_root_path);
48    }
49
50    public function tearDown(): void
51    {
52        if (file_exists($this->file_path))
53        {
54            unlink($this->file_path);
55        }
56
57        if (file_exists($this->signature_path))
58        {
59            unlink($this->signature_path);
60        }
61
62        parent::tearDown();
63    }
64
65    public function test_download_success()
66    {
67        $response_mock = $this->createMock(Response::class);
68        $this->http_client->expects($this->once())
69            ->method('request')
70            ->with('GET', 'http://example.com/update.zip', [
71                'sink' => '/path/to/storage',
72                'allow_redirects' => false
73            ])
74            ->willReturn($response_mock);
75
76        $client_reflection = new \ReflectionProperty($this->update, 'http_client');
77        $client_reflection->setValue($this->update, $this->http_client);
78
79        $result = $this->update->download('http://example.com/update.zip', '/path/to/storage');
80        $this->assertTrue($result);
81    }
82
83    public function test_download_failure()
84    {
85        $this->http_client->expects($this->once())
86            ->method('request')
87            ->willReturnCallback(function ($method, $url, $options)
88            {
89                throw new ClientException('bad client', new \GuzzleHttp\Psr7\Request($method, $url), new \GuzzleHttp\Psr7\Response());
90            });
91        $client_reflection = new \ReflectionProperty($this->update, 'http_client');
92        $client_reflection->setValue($this->update, $this->http_client);
93
94        $result = $this->update->download('http://example.com/update.zip', '/path/to/storage');
95        $this->assertFalse($result);
96    }
97
98    public function test_validate_success()
99    {
100        $keypair = sodium_crypto_sign_keypair();
101
102        $secret_key = sodium_crypto_sign_secretkey($keypair);
103        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair));
104
105        file_put_contents($this->file_path, 'test file content');
106
107        $hash = hash_file('sha384', $this->file_path, true);
108        file_put_contents($this->signature_path, base64_encode(sodium_crypto_sign_detached($hash, $secret_key)));
109
110        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
111        $client_reflection->setValue($this->update, $public_key);
112
113        $this->assertTrue($this->update->validate($this->file_path, $this->signature_path));
114    }
115
116    public function test_validate_file_not_exist()
117    {
118        $file_path = __DIR__ . '/../tmp/download.zip';
119        $signature_path = __DIR__ . '/../tmp/signature.sig';
120
121        $keypair = sodium_crypto_sign_keypair();
122
123        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair));
124
125        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
126        $client_reflection->setValue($this->update, $public_key);
127
128        $this->assertFalse($this->update->validate($file_path, $signature_path));
129    }
130
131    public function test_validate_sig_not_exist()
132    {
133        $keypair = sodium_crypto_sign_keypair();
134
135        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair));
136
137        file_put_contents($this->file_path, 'test file content');
138
139        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
140        $client_reflection->setValue($this->update, $public_key);
141
142        $this->assertFalse($this->update->validate($this->file_path, $this->signature_path));
143    }
144
145    public function test_validate_file_not_accessible()
146    {
147        if (strtolower(substr(PHP_OS, 0, 3)) === 'win')
148        {
149            $this->markTestSkipped('Unable to test unreadable files on Windows');
150        }
151
152        $keypair = sodium_crypto_sign_keypair();
153
154        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair));
155
156        file_put_contents($this->file_path, 'test file content');
157
158        chmod($this->file_path, 0000);
159
160        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
161        $client_reflection->setValue($this->update, $public_key);
162
163        $this->assertFalse($this->update->validate($this->file_path, $this->signature_path));
164
165        chmod($this->file_path, 0666);
166    }
167
168    public function test_validate_sig_not_accessible()
169    {
170        if (strtolower(substr(PHP_OS, 0, 3)) === 'win')
171        {
172            $this->markTestSkipped('Unable to test unreadable files on Windows');
173        }
174
175        $keypair = sodium_crypto_sign_keypair();
176
177        $secret_key = sodium_crypto_sign_secretkey($keypair);
178        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair));
179
180        file_put_contents($this->file_path, 'test file content');
181
182        $hash = hash_file('sha384', $this->file_path, true);
183        file_put_contents($this->signature_path, base64_encode(sodium_crypto_sign_detached($hash, $secret_key)));
184
185        chmod($this->signature_path, 0000);
186
187        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
188        $client_reflection->setValue($this->update, $public_key);
189
190        $this->assertFalse($this->update->validate($this->file_path, $this->signature_path));
191
192        chmod($this->signature_path, 0666);
193    }
194
195    public function test_validate_sig_not_base64()
196    {
197        $keypair = sodium_crypto_sign_keypair();
198
199        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair));
200
201        file_put_contents($this->file_path, 'test file content');
202
203        file_put_contents($this->signature_path, 'SGVsbG8gV29ybGQ===');
204
205        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
206        $client_reflection->setValue($this->update, $public_key);
207
208        $this->assertFalse($this->update->validate($this->file_path, $this->signature_path));
209    }
210
211    public function test_validate_invalid_pub_key()
212    {
213        $keypair = sodium_crypto_sign_keypair();
214
215        $secret_key = sodium_crypto_sign_secretkey($keypair);
216
217        file_put_contents($this->file_path, 'test file content');
218
219        $hash = hash_file('sha384', $this->file_path, true);
220        file_put_contents($this->signature_path, base64_encode(sodium_crypto_sign_detached($hash, $secret_key)));
221
222        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
223        $client_reflection->setValue($this->update, '!not!base64');
224
225        $this->assertFalse($this->update->validate($this->file_path, $this->signature_path));
226    }
227
228    public function test_validate_fail()
229    {
230        $keypair = sodium_crypto_sign_keypair();
231
232        $secret_key = sodium_crypto_sign_secretkey($keypair);
233
234        // Recreate keypair for different public key
235        $keypair = sodium_crypto_sign_keypair();
236        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair));
237
238        file_put_contents($this->file_path, 'test file content');
239
240        $hash = hash_file('sha384', $this->file_path, true);
241        file_put_contents($this->signature_path, base64_encode(sodium_crypto_sign_detached($hash, $secret_key)));
242
243        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
244        $client_reflection->setValue($this->update, $public_key);
245
246        $this->assertFalse($this->update->validate($this->file_path, $this->signature_path));
247    }
248
249    public function test_validate_invalid_pub_key_length()
250    {
251        $keypair = sodium_crypto_sign_keypair();
252
253        $secret_key = sodium_crypto_sign_secretkey($keypair);
254        $public_key = base64_encode(sodium_crypto_sign_publickey($keypair) . 'Foo=');
255
256        file_put_contents($this->file_path, 'test file content');
257
258        $hash = hash_file('sha384', $this->file_path, true);
259        file_put_contents($this->signature_path, base64_encode(sodium_crypto_sign_detached($hash, $secret_key)));
260
261        $client_reflection = new \ReflectionProperty($this->update, 'public_key');
262        $client_reflection->setValue($this->update, $public_key);
263
264        $this->assertFalse($this->update->validate($this->file_path, $this->signature_path));
265    }
266
267    public function test_extract_success()
268    {
269        $this->zipper->expects($this->once())
270            ->method('open')
271            ->with('/path/to/zipfile.zip')
272            ->willReturn(true);
273
274        $this->zipper->expects($this->once())
275            ->method('extractTo')
276            ->with('/path/to/extract')
277            ->willReturn(true);
278
279        $this->zipper->expects($this->once())
280            ->method('close');
281
282        $zipperReflection = new \ReflectionProperty($this->update, 'zipper');
283        $zipperReflection->setValue($this->update, $this->zipper);
284
285        $result = $this->update->extract('/path/to/zipfile.zip', '/path/to/extract');
286        $this->assertTrue($result);
287    }
288
289    public function test_extract_failure()
290    {
291        $this->zipper->expects($this->once())
292            ->method('open')
293            ->with('/path/to/zipfile.zip')
294            ->willReturn(false);
295
296        $zipperReflection = new \ReflectionProperty($this->update, 'zipper');
297        $zipperReflection->setValue($this->update, $this->zipper);
298
299        $result = $this->update->extract('/path/to/zipfile.zip', '/path/to/extract');
300        $this->assertFalse($result);
301    }
302
303    public function test_copy_success()
304    {
305        $this->filesystem->expects($this->once())
306            ->method('mirror')
307            ->with('/source/dir', $this->phpbb_root_path);
308
309        $result = $this->update->copy('/source/dir');
310        $this->assertTrue($result);
311    }
312
313    public function test_copy_failure()
314    {
315        $this->filesystem->expects($this->once())
316            ->method('mirror')
317            ->willThrowException(new filesystem_exception());
318
319        $result = $this->update->copy('/source/dir');
320        $this->assertFalse($result);
321    }
322}