Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.56% covered (warning)
50.56%
90 / 178
63.16% covered (warning)
63.16%
12 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
phpbb_passwords_manager_test
50.56% covered (warning)
50.56%
90 / 178
63.16% covered (warning)
63.16%
12 / 19
107.68
0.00% covered (danger)
0.00%
0 / 1
 setUp
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
1
 hash_password_data
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 test_hash_password
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 check_password_data
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 test_check_password
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 check_hash_exceptions_data
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
2
 test_check_hash_exceptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 data_hash_password_length
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 test_hash_password_length
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 test_hash_password_8bit_bcrypt
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 combined_hash_data
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
2
 test_combined_hash_password
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 test_unique_id
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 test_check_hash_with_large_input
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 test_hash_password_with_large_input
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 data_test_string_compare
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 test_string_compare
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 data_driver_interface_driver
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 test_driver_interface_driver
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
2
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
14class phpbb_passwords_manager_test extends \phpbb_test_case
15{
16    /** @var \phpbb\passwords\driver\helper */
17    protected $driver_helper;
18
19    /** @var \phpbb\passwords\helper */
20    protected $helper;
21
22    /** @var \phpbb\passwords\manager */
23    protected $manager;
24
25    protected $passwords_drivers;
26
27    protected $pw_characters = '0123456789abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVXYZ.,_!?/\\';
28
29    protected $default_pw = 'foobar';
30
31    protected function setUp(): void
32    {
33        // Prepare dependencies for manager and driver
34        $config =  new \phpbb\config\config(array());
35        $this->driver_helper = new \phpbb\passwords\driver\helper($config);
36        $request = new phpbb_mock_request(array(), array(), array(), array(), array('password' => 'töst'));
37        $phpbb_root_path = __DIR__ . '/../../phpBB/';
38        $php_ext = 'php';
39
40        $this->passwords_drivers = array(
41            'passwords.driver.bcrypt_2y'        => new \phpbb\passwords\driver\bcrypt_2y($config, $this->driver_helper, 10),
42            'passwords.driver.bcrypt'        => new \phpbb\passwords\driver\bcrypt($config, $this->driver_helper, 10),
43            'passwords.driver.salted_md5'        => new \phpbb\passwords\driver\salted_md5($config, $this->driver_helper),
44            'passwords.driver.phpass'        => new \phpbb\passwords\driver\phpass($config, $this->driver_helper),
45            'passwords.driver.convert_password'    => new \phpbb\passwords\driver\convert_password($config, $this->driver_helper),
46            'passwords.driver.sha1_smf'        => new \phpbb\passwords\driver\sha1_smf($config, $this->driver_helper),
47            'passwords.driver.sha1'            => new \phpbb\passwords\driver\sha1($config, $this->driver_helper),
48            'passwords.driver.sha1_wcf1'        => new \phpbb\passwords\driver\sha1_wcf1($config, $this->driver_helper),
49            'passwords.driver.md5_mybb'        => new \phpbb\passwords\driver\md5_mybb($config, $this->driver_helper),
50            'passwords.driver.md5_vb'        => new \phpbb\passwords\driver\md5_vb($config, $this->driver_helper),
51            'passwords.driver.sha_xf1'    => new \phpbb\passwords\driver\sha_xf1($config, $this->driver_helper),
52        );
53        $this->passwords_drivers['passwords.driver.md5_phpbb2']    = new \phpbb\passwords\driver\md5_phpbb2($request, $this->passwords_drivers['passwords.driver.salted_md5'], $this->driver_helper, $phpbb_root_path, $php_ext);
54        $this->passwords_drivers['passwords.driver.bcrypt_wcf2'] = new \phpbb\passwords\driver\bcrypt_wcf2($this->passwords_drivers['passwords.driver.bcrypt'], $this->driver_helper);
55
56        $this->helper = new \phpbb\passwords\helper;
57        // Set up passwords manager
58        $this->manager = new \phpbb\passwords\manager($config, $this->passwords_drivers, $this->helper, array_keys($this->passwords_drivers));
59    }
60
61    public static function hash_password_data()
62    {
63        return array(
64            array('', '2y', 60),
65            array('passwords.driver.bcrypt_2y', '2y', 60),
66            array('passwords.driver.bcrypt', '2a', 60),
67            array('passwords.driver.salted_md5', 'H', 34),
68            array('passwords.driver.foobar', '', false),
69        );
70    }
71
72    /**
73    * @dataProvider hash_password_data
74    */
75    public function test_hash_password($type, $prefix, $length)
76    {
77        $password = $this->default_pw;
78
79        if (!$length)
80        {
81            $this->assertEquals(false, $hash = $this->manager->hash($password, $type));
82            return;
83        }
84        $time = microtime(true);
85
86        // Limit each test to 1 second
87        while ((microtime(true) - $time) < 1)
88        {
89            $hash = $this->manager->hash($password, $type);
90            preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match);
91            $this->assertEquals($prefix, $match[1]);
92            $this->assertEquals($length, strlen($hash));
93            $password .= $this->pw_characters[mt_rand(0, 66)];
94        }
95    }
96
97    public static function check_password_data()
98    {
99        return array(
100            array('passwords.driver.bcrypt_2y'),
101            array('passwords.driver.bcrypt'),
102            array('passwords.driver.salted_md5'),
103            array('passwords.driver.phpass'),
104        );
105    }
106
107    /**
108    * @dataProvider check_password_data
109    */
110    public function test_check_password($hash_type)
111    {
112        $password = $this->default_pw;
113        $time = microtime(true);
114        // Limit each test to 1 second
115        while ((microtime(true) - $time) < 1)
116        {
117            $hash = $this->manager->hash($password, $hash_type);
118            $this->assertEquals(true, $this->manager->check($password, $hash));
119            $password .= $this->pw_characters[mt_rand(0, 66)];
120            $this->assertEquals(false, $this->manager->check($password, $hash));
121        }
122
123        // Check if convert_flag is correctly set
124        $default_type = 'passwords.driver.bcrypt_2y';
125        $this->assertEquals(($hash_type !== $default_type), $this->manager->convert_flag);
126    }
127
128
129    public static function check_hash_exceptions_data()
130    {
131        return array(
132            array('3858f62230ac3c915f300c664312c63f', true),
133            array('$CP$3858f62230ac3c915f300c664312c63f', true), // md5_phpbb2
134            array('$CP$3858f62230ac3c915f300c', false),
135            array('$S$b57a939fa4f2c04413a4eea9734a0903647b7adb93181295', false),
136            array('$2a\S$kkkkaakdkdiej39023903204j2k3490234jk234j02349', false),
137            array('$H$kklk938d023k//k3023', false),
138            array('$H$3PtYMgXb39lrIWkgoxYLWtRkZtY3AY/', false),
139            array('$2a$kwiweorurlaeirw', false),
140            array('6f9e2a1899e1f15708fd2e554103480eb53e8b57', false),
141            array('6f9e2a1899e1f15708fd2e554103480eb53e8b57', false, 'foobar', array('login_name' => 'test')),
142            array('$CP$6f9e2a1899e1f15708fd2e554103480eb53e8b57', true, 'foobar', array('login_name' => 'test')), // sha1_smf
143            array('6f9e2a1899', false, 'foobar', array('login_name' => 'test')),
144            array('ae2fc75e20ee25d4520766788fbc96ae', false, 'fööbar'),
145            array('$CP$ae2fc75e20ee25d4520766788fbc96ae', false, 'fööbar'),
146            array('$CP$ae2fc75e20ee25d4520766788fbc96ae', true, utf8_decode('fööbar')), // md5_phpbb2
147            array('b86ee7e24008bfd2890dcfab1ed31333', false, 'foobar', array('user_passwd_salt' => 'yeOtfFO6')),
148            array('$CP$b86ee7e24008bfd2890dcfab1ed31333', true, 'foobar', array('user_passwd_salt' => 'yeOtfFO6')), // md5_mybb
149            array('$CP$b452c54c44c588fc095d2d000935c470', true, 'foobar', array('user_passwd_salt' => '9^F')), // md5_vb
150            array('$CP$f23a8241bd115d270c703213e3ef7f52', true, 'foobar', array('user_passwd_salt' => 'iaU*U%`CBl;/e~>D%do2m@Xf/,KZB0')), // md5_vb
151            array('$CP$fc46b9d9386167ce365ea3b891bf5dc31ddcd3ff', true, 'foobar', array('user_passwd_salt' => '1a783e478d63f6422783a868db667aed3a857840')), // sha_wcf1
152            array('$2a$08$p8h14U0jsEiVb1Luy.s8oOTXSQ0hVWUXpcNGBoCezeYNXrQyCKHfi', false),
153            array('$CP$$2a$08$p8h14U0jsEiVb1Luy.s8oOTXSQ0hVWUXpcNGBoCezeYNXrQyCKHfi', true), // bcrypt_wcf2
154            array('$CP$7f65d2fa8a826d232f8134772252f8b1aaef8594b1edcabd9ab65e5b0f236ff0', true, 'foobar', array('user_passwd_salt' => '15b6c02cedbd727f563dcca607a89b085287b448966f19c0cc78cae263b1e38c')), // sha_xf1
155            array('$CP$69962ae2079420573a3948cc4dedbabd35680051', true, 'foobar', array('user_passwd_salt' => '15b6c02cedbd727f563dcca607a89b085287b448966f19c0cc78cae263b1e38c')), // sha_xf1
156        );
157    }
158
159    /**
160    * @dataProvider check_hash_exceptions_data
161    */
162    public function test_check_hash_exceptions($hash, $expected, $password = 'foobar', $user_row = array())
163    {
164        $this->assertEquals($expected, $this->manager->check($password, $hash, $user_row));
165    }
166
167    public static function data_hash_password_length()
168    {
169        return array(
170            array('passwords.driver.bcrypt', false),
171            array('passwords.driver.bcrypt_2y', false),
172            array('passwords.driver.salted_md5', '3858f62230ac3c915f300c664312c63f'),
173            array('passwords.driver.phpass', '3858f62230ac3c915f300c664312c63f'),
174        );
175    }
176
177    /**
178    * @dataProvider data_hash_password_length
179    */
180    public function test_hash_password_length($driver, $expected)
181    {
182        $this->assertEquals($expected, $this->passwords_drivers[$driver]->hash('foobar', 'foobar'));
183    }
184
185    public function test_hash_password_8bit_bcrypt()
186    {
187        $this->assertEquals(false, $this->manager->hash('foobar𝄞', 'passwords.driver.bcrypt'));
188        $this->assertNotEquals(false, $this->manager->hash('foobar𝄞', 'passwords.driver.bcrypt_2y'));
189    }
190
191    public static function combined_hash_data()
192    {
193        return array(
194            array(
195                'passwords.driver.salted_md5',
196                array('passwords.driver.bcrypt_2y'),
197            ),
198            array(
199                'passwords.driver.salted_md5',
200                array('passwords.driver.bcrypt'),
201            ),
202            array(
203                'passwords.driver.phpass',
204                array('passwords.driver.salted_md5'),
205            ),
206            array(
207                'passwords.driver.salted_md5',
208                array('passwords.driver.bcrypt_2y', 'passwords.driver.bcrypt'),
209            ),
210            array(
211                'passwords.driver.salted_md5',
212                array('passwords.driver.salted_md5'),
213                false,
214            ),
215            array(
216                'passwords.driver.bcrypt_2y',
217                array('passwords.driver.salted_md4'),
218                false,
219            ),
220            array(
221                '$H$',
222                array('$2y$'),
223            ),
224        );
225    }
226
227    /**
228    * @dataProvider combined_hash_data
229    */
230    public function test_combined_hash_password($first_type, $second_type, $expected = true)
231    {
232        $password = $this->default_pw;
233        $time = microtime(true);
234        // Limit each test to 1 second
235        while ((microtime(true) - $time) < 1)
236        {
237            $hash = $this->manager->hash($password, $first_type);
238            $combined_hash = $this->manager->hash($hash, $second_type);
239            $this->assertEquals($expected, $this->manager->check($password, $combined_hash));
240            $password .= $this->pw_characters[mt_rand(0, 66)];
241            $this->assertEquals(false, $this->manager->check($password, $combined_hash));
242
243            // If we are expecting the check to fail then there is
244            // no need to run this more than once
245            if (!$expected)
246            {
247                break;
248            }
249        }
250    }
251
252    public function test_unique_id()
253    {
254        $time = microtime(true);
255        $first_id = $this->driver_helper->unique_id();
256        // Limit test to 1 second
257        while ((microtime(true) - $time) < 1)
258        {
259            $this->assertNotSame($first_id, $this->driver_helper->unique_id());
260        }
261    }
262
263    public function test_check_hash_with_large_input()
264    {
265        // 16 MB password, should be rejected quite fast
266        $start_time = time();
267        $this->assertFalse($this->manager->check(str_repeat('a', 1024 * 1024 * 16), '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1'));
268        $this->assertLessThanOrEqual(5, time() - $start_time);
269    }
270
271    public function test_hash_password_with_large_input()
272    {
273        // 16 MB password, should be rejected quite fast
274        $start_time = time();
275        $this->assertFalse($this->manager->hash(str_repeat('a', 1024 * 1024 * 16)));
276        $this->assertLessThanOrEqual(5, time() - $start_time);
277    }
278
279    public static function data_test_string_compare()
280    {
281        return array(
282            array('foo', 'bar', false),
283            array(1, '1', false),
284            array('one', 'one', true),
285            array('foobar', 'foobaf', false),
286        );
287    }
288
289    /**
290     * @dataProvider data_test_string_compare
291     */
292    public function test_string_compare($a, $b, $expected)
293    {
294        $this->assertSame($expected, $this->driver_helper->string_compare($a, $b));
295    }
296
297    public static function data_driver_interface_driver()
298    {
299        return array(
300            array(false, false, false),
301            array(true, false, false),
302            array(true, true, true),
303        );
304    }
305
306    /**
307     * @dataProvider data_driver_interface_driver
308     */
309    public function test_driver_interface_driver($use_new_interface, $needs_rehash, $expected)
310    {
311        if ($use_new_interface)
312        {
313            $test_driver = $this->createMock('\phpbb\passwords\driver\rehashable_driver_interface');
314            $test_driver->method('needs_rehash')
315                ->willReturn($needs_rehash);
316        }
317        else
318        {
319            $test_driver = $this->createMock('\phpbb\passwords\driver\driver_interface');
320        }
321        $config = new \phpbb\config\config(array());
322
323        $test_driver->method('is_supported')
324            ->willReturn(true);
325        $test_driver->method('get_prefix')
326            ->willReturn('$test$');
327        $test_driver->method('check')
328            ->with($this->anything())
329            ->willReturn(true);
330        $passwords_drivers = array(
331            'passwords.driver.foobar'        => $test_driver,
332            'passwords.driver.bcrypt_2y'    => new \phpbb\passwords\driver\bcrypt_2y($config, $this->driver_helper, 10),
333        );
334        // Set up another manager
335        $foobar_manager = new \phpbb\passwords\manager($config, $passwords_drivers, $this->helper, array('passwords.driver.foobar'));
336
337        $this->assertTrue($foobar_manager->check('foobar', '$test$somerandomstuff'));
338        $this->assertEquals($expected, $foobar_manager->convert_flag);
339
340        // Should always return true in case a different driver is default
341        $foobar_manager = new \phpbb\passwords\manager($config, $passwords_drivers, $this->helper, array('passwords.driver.bcrypt_2y', 'passwords.driver.foobar'));
342
343        $this->assertTrue($foobar_manager->check('foobar', '$test$somerandomstuff'));
344        $this->assertTrue($foobar_manager->convert_flag);
345    }
346}