mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-11-01 03:03:23 +01:00
150 lines
6.1 KiB
PHP
150 lines
6.1 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* The web player's session follows the panel's rules: signing in moves it to
|
|
* an id nobody has seen yet, and a player page starts it with the panel's
|
|
* cookie flags and only under an id this server issued.
|
|
*
|
|
* Every case runs in a child PHP: both paths end in exit(), and PHP refuses to
|
|
* touch a session once output has started.
|
|
*/
|
|
final class AuditPlayerSessionTest extends TestCase {
|
|
private const KNOWN = 'issuedbythisserver00000000';
|
|
private const UNKNOWN = 'neverissuedbythisserver000';
|
|
|
|
/** Signs the line `viewer` in at the v1 login, from a session that already carries a panel login. */
|
|
private const LOGIN = <<<'PHP'
|
|
final class AuditPlayerLineDb extends \XcVm\Core\Database\DatabaseHandler {
|
|
private array $rows = [];
|
|
|
|
public function __construct() {
|
|
$this->dbh = true;
|
|
}
|
|
|
|
public function query(string $query, mixed $buffered = false) {
|
|
$this->rows = str_contains($query, 'FROM `lines`') && func_get_args() === [$query, 'viewer', 'secret'] ? [[
|
|
'id' => 7, 'username' => 'viewer', 'password' => 'secret', 'is_e2' => 0, 'is_mag' => 0, 'is_stalker' => 0,
|
|
'exp_date' => null, 'admin_enabled' => 1, 'enabled' => 1, 'allowed_ips' => '[]', 'allowed_ua' => '[]',
|
|
'bouquet' => '[]', 'allowed_outputs' => '[1]', 'forced_country' => '', 'max_connections' => 1,
|
|
'is_restreamer' => 0, 'is_isplock' => 0, 'isp_desc' => '',
|
|
]] : [];
|
|
return true;
|
|
}
|
|
|
|
public function num_rows() {
|
|
return count($this->rows);
|
|
}
|
|
|
|
public function get_row() {
|
|
return $this->rows[0] ?? [];
|
|
}
|
|
|
|
public function get_rows($use_id = false, $column_as_id = '', $unique_row = true, $sub_row_id = '') {
|
|
return $this->rows;
|
|
}
|
|
}
|
|
|
|
foreach (['CLIENT_INVALID', 'CLIENT_IS_E2', 'CLIENT_IS_MAG', 'CLIENT_IS_STALKER', 'CLIENT_EXPIRED', 'CLIENT_BANNED', 'CLIENT_DISABLED', 'CLIENT_DISALLOWED'] as $rValue => $rName) {
|
|
define($rName, $rValue);
|
|
}
|
|
define('CACHE_TMP_PATH', $rDir);
|
|
define('CONS_TMP_PATH', $rDir);
|
|
$_SERVER['REMOTE_ADDR'] = '192.0.2.10';
|
|
$GLOBALS['rSettings'] = ['enable_cache' => false, 'county_override_1st' => 0, 'show_isps' => 0, 'allow_countries' => ['ALL']];
|
|
$GLOBALS['db'] = new AuditPlayerLineDb();
|
|
\XcVm\Infrastructure\Database\DatabaseFactory::set($GLOBALS['db']);
|
|
\XcVm\Core\Http\RequestManager::set(['username' => 'viewer', 'password' => $rPassword]);
|
|
|
|
session_id($rId);
|
|
session_start();
|
|
$_SESSION['hash'] = 4;
|
|
|
|
$rLogin = new ReflectionMethod(\XcVm\Public\Controllers\Player\PlayerLoginController::class, 'processLogin');
|
|
$rLogin->setAccessible(true);
|
|
$rLogin->invoke(new \XcVm\Public\Controllers\Player\PlayerLoginController());
|
|
PHP;
|
|
|
|
/** Opens a player page with the player's session cookie $rId. */
|
|
private const PAGE = <<<'PHP'
|
|
$_SERVER['XC_SCOPE'] = 'player';
|
|
$_COOKIE[\XcVm\Core\Bootstrap\Stage\SessionStage::PLAYER_COOKIE] = $rId;
|
|
(new \XcVm\Infrastructure\Bootstrap\PlayerScopeBootstrap())->boot();
|
|
PHP;
|
|
|
|
private string $rDir;
|
|
|
|
protected function setUp(): void {
|
|
$this->rDir = sys_get_temp_dir() . '/xcvm-player-session-' . bin2hex(random_bytes(4)) . '/';
|
|
mkdir($this->rDir, 0700, true);
|
|
}
|
|
|
|
protected function tearDown(): void {
|
|
exec('rm -rf ' . escapeshellarg($this->rDir));
|
|
}
|
|
|
|
/**
|
|
* Run $rCode with the variables $rVars and return the session it ends with.
|
|
*
|
|
* @return array{id: string, session: array<string, mixed>, cookie: array<string, mixed>, files: list<string>}
|
|
*/
|
|
private function child(string $rCode, array $rVars): array {
|
|
$rVars['rDir'] = $this->rDir;
|
|
$rSetup = 'require ' . var_export(dirname(__DIR__) . '/bootstrap.php', true) . ';'
|
|
. 'ini_set("session.save_path", ' . var_export($this->rDir, true) . ');'
|
|
. 'extract(' . var_export($rVars, true) . ');'
|
|
. 'register_shutdown_function(static function () use ($rDir) {'
|
|
. ' $rOut = ["id" => session_id(), "session" => $_SESSION ?? [], "cookie" => session_get_cookie_params()];'
|
|
. ' session_write_close();'
|
|
. ' echo json_encode($rOut + ["files" => array_map("basename", glob($rDir . "sess_*"))]);'
|
|
. '});';
|
|
|
|
$rProc = proc_open([PHP_BINARY, '-d', 'display_errors=1', '-r', $rSetup . $rCode], [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $rPipes);
|
|
$rOut = stream_get_contents($rPipes[1]);
|
|
$rErr = stream_get_contents($rPipes[2]);
|
|
proc_close($rProc);
|
|
|
|
$rResult = json_decode((string) $rOut, true);
|
|
$this->assertIsArray($rResult, $rOut . $rErr);
|
|
|
|
return $rResult;
|
|
}
|
|
|
|
public function testSigningInMovesTheSessionToAFreshId(): void {
|
|
$rEnd = $this->child(self::LOGIN, ['rId' => self::KNOWN, 'rPassword' => 'secret']);
|
|
|
|
$this->assertSame(7, $rEnd['session']['phash'] ?? null, 'the line did not sign in');
|
|
$this->assertNotSame(self::KNOWN, $rEnd['id'], 'the session id from before the login carries the signed-in session');
|
|
$this->assertSame(['sess_' . $rEnd['id']], $rEnd['files'], 'the old session is still on disk');
|
|
$this->assertSame(4, $rEnd['session']['hash'] ?? null, 'a panel login in the same session was lost');
|
|
}
|
|
|
|
/** A refused login leaves the session where it was. */
|
|
public function testARefusedLoginKeepsTheSession(): void {
|
|
$rEnd = $this->child(self::LOGIN, ['rId' => self::KNOWN, 'rPassword' => 'wrong']);
|
|
|
|
$this->assertArrayNotHasKey('phash', $rEnd['session']);
|
|
$this->assertSame(self::KNOWN, $rEnd['id']);
|
|
}
|
|
|
|
/** The player's own cookie (AuditDecisionPlayerCookieTest), with the panel's other flags. */
|
|
public function testAPlayerPageStartsTheSessionWithThePlayersCookieFlags(): void {
|
|
$rEnd = $this->child(self::PAGE, ['rId' => self::UNKNOWN]);
|
|
|
|
$this->assertNotSame(self::UNKNOWN, $rEnd['id'], 'the page took a session id this server never issued');
|
|
$this->assertNotSame('', $rEnd['id']);
|
|
$this->assertTrue($rEnd['cookie']['httponly'], 'scripts can read the session cookie');
|
|
$this->assertSame('Lax', $rEnd['cookie']['samesite']);
|
|
}
|
|
|
|
/** The id a player page issued on the way to the login form is the one the visitor comes back with. */
|
|
public function testAPlayerPageKeepsASessionThisServerIssued(): void {
|
|
touch($this->rDir . 'sess_' . self::KNOWN);
|
|
|
|
$rEnd = $this->child(self::PAGE, ['rId' => self::KNOWN]);
|
|
|
|
$this->assertSame(self::KNOWN, $rEnd['id']);
|
|
}
|
|
}
|