diff --git a/src/Domain/User/UserRepository.php b/src/Domain/User/UserRepository.php index 5e8ec43f..54e7dc93 100644 --- a/src/Domain/User/UserRepository.php +++ b/src/Domain/User/UserRepository.php @@ -425,12 +425,18 @@ class UserRepository { /** * Fetch a line by id. * - * @param int $rID Line id. + * @param int|string|null $rID Line id. Callers hand over nullable columns (a + * device's `pair_id`) and request values; anything + * that is not a positive id finds nothing. * @return array|null The line row, or null if not found. */ - public static function getLineById(int $rID) { + public static function getLineById($rID) { + if (!is_numeric($rID) || (int) $rID <= 0) { + return null; + } + $db = self::db(); - $db->query('SELECT * FROM `lines` WHERE `id` = ?;', $rID); + $db->query('SELECT * FROM `lines` WHERE `id` = ?;', (int) $rID); if ($db->num_rows() == 1) { return $db->get_row(); diff --git a/tests/Unit/UserRepositoryTest.php b/tests/Unit/UserRepositoryTest.php index 8a262f8c..8f731264 100644 --- a/tests/Unit/UserRepositoryTest.php +++ b/tests/Unit/UserRepositoryTest.php @@ -111,4 +111,16 @@ final class UserRepositoryTest extends TestCase { // An id-based lookup carries no username/password to re-verify. $this->assertTrue($this->call('verifyCachedCredentials', array(), 5, null, null)); } + + /** + * getLineById() is handed nullable columns (a MAG / Enigma2 device's + * `pair_id`, an activation code's `subscriber_id`) and raw request values. + * None of those is a line, and none may end the request: loading an + * unpaired MAG device used to throw, so deleting one answered an empty page. + */ + public function testGetLineByIdFindsNothingForANonId(): void { + foreach (array(null, '', 0, '0', -3, 'abc') as $rID) { + $this->assertNull(UserRepository::getLineById($rID), var_export($rID, true)); + } + } }