not->toBeFalse(); $depth = 0; $len = strlen($src); for ($i = strpos($src, '{', $start); $i < $len; $i++) { if ($src[$i] === '{') { $depth++; } elseif ($src[$i] === '}') { $depth--; if ($depth === 0) { return substr($src, $start, $i - $start + 1); } } } expect(false)->toBeTrue("$fn() is unbalanced"); } test('the rehash update is scoped to realm 0', function () use ($localAuthSrc) { $body = _local_auth_fn_body($localAuthSrc, 'rehashIfNeeded'); expect($body)->toContain('SET password = ?'); expect(preg_match('/UPDATE user_auth\s+SET password = \?\s+WHERE username = \?\s+AND realm = 0/', $body))->toBe(1); }); test('rehash only runs after a successful login, without a second verify', function () use ($localAuthSrc) { $authenticateBody = _local_auth_fn_body($localAuthSrc, 'authenticate'); // verifyCredential() failure returns before rehashIfNeeded() is ever called $guard = strpos($authenticateBody, '!cacti_sizeof($user)'); $rehash = strpos($authenticateBody, 'rehashIfNeeded('); expect($guard)->not->toBeFalse(); expect($rehash)->not->toBeFalse(); expect($guard)->toBeLessThan($rehash); $rehashBody = _local_auth_fn_body($localAuthSrc, 'rehashIfNeeded'); // the redundant second password verification is gone expect($rehashBody)->not->toContain('compat_password_verify('); // and it no longer re-reads the hash it already has from verifyCredential() expect($rehashBody)->not->toContain("db_fetch_cell_prepared('SELECT password"); });