mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-09-30 12:02:05 +02:00
Atomic case-rename of the seven class-holding source directories to match the PSR-4 namespace casing, plus every consequent path reference. No code logic changes — pure structural rename. (config/content/resources/signals/migrations/ ministra/storage/tmp/vendor/www/bin stay lowercase.) - git mv: core→Core, domain→Domain, infrastructure→Infrastructure, streaming→Streaming, modules→Modules, cli→Cli, public→Public (module subdirs like Modules/plex stay lowercase; loaded by ModuleLoader, not Composer). - PHP filesystem paths updated across src/ + tests/: require/include, glob/scandir bases, view includes, asset/nginx-alias paths, autoload.php registerDirectories(), ModuleLoader/ModuleManager module roots, console.php discovery dirs. - src/composer.json PSR-4 vendored-lib paths → Core/Parsing/...; vendor/ regenerated. - nginx.conf: docroot + SCRIPT_FILENAME → Public/. SCRIPT_NAME and the public-facing /streaming/*.php compat routes are URLs, left unchanged. - Build/CI: Makefile LB_DIRS / LB_DIRS_TO_REMOVE / LB_FILES_TO_REMOVE PascalCased (closes the LB-archive privileged-leak blocker); phpstan.dist.neon paths/ scanDirectories/excludePaths; phpunit.xml.dist coverage; phpstan-baseline.neon regenerated (294→294 errors, no new resolution failures). - migrations/deleted_files.txt: old lowercase trees listed for client cleanup (assumes case-sensitive FS — the supported Linux target). Verified: grep-gate 0 live lowercase-dir require/include in src+tests; php -l clean; PHPUnit 292/292; PHPStan no errors; Composer first / XC_Autoloader last in SPL stack; global classes resolve from the renamed dirs.
214 lines
8.1 KiB
PHP
214 lines
8.1 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Консолидированный сервис аутентификации.
|
|
* Объединяет: CodeService, HMACService, HMACValidator.
|
|
*
|
|
* @package XC_VM_Domain_Auth
|
|
* @author Divarion_D <https://github.com/Divarion-D>
|
|
* @copyright 2025-2026 Vateron Media
|
|
* @link https://github.com/Vateron-Media/XC_VM
|
|
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
|
|
*/
|
|
|
|
class AuthService {
|
|
// ──────────────────────────────────────────────
|
|
// Из CodeService
|
|
// ──────────────────────────────────────────────
|
|
|
|
/**
|
|
* Create or update an access code from admin form data.
|
|
*
|
|
* Validates code length, reserved names and uniqueness, normalizes the
|
|
* group/whitelist fields, then upserts the row.
|
|
*
|
|
* @param array $rData Submitted form data (includes `edit` id when updating).
|
|
* @return array ['status' => STATUS_* constant, 'data' => payload].
|
|
*/
|
|
public static function processCode($rData) {
|
|
global $db;
|
|
if (isset($rData['edit'])) {
|
|
$rArray = AdminHelpers::overwriteData(AuthRepository::getCodeById($rData['edit']), $rData);
|
|
$rOrigCode = $rArray['code'];
|
|
} else {
|
|
$rArray = QueryHelper::verifyPostTable('access_codes', $rData);
|
|
$rOrigCode = null;
|
|
unset($rArray['id']);
|
|
}
|
|
|
|
if (isset($rData['enabled'])) {
|
|
$rArray['enabled'] = 1;
|
|
} else {
|
|
$rArray['enabled'] = 0;
|
|
}
|
|
|
|
if (isset($rData['groups'])) {
|
|
$rArray['groups'] = array();
|
|
foreach ($rData['groups'] as $rGroupID) {
|
|
$rArray['groups'][] = intval($rGroupID);
|
|
}
|
|
} elseif (!is_array($rArray['groups'] ?? null)) {
|
|
$rArray['groups'] = is_string($rArray['groups'] ?? null) ? (json_decode($rArray['groups'], true) ?: []) : [];
|
|
}
|
|
|
|
if (in_array($rData['type'], array(0, 1, 3, 4))) {
|
|
$rArray['groups'] = '[' . implode(',', array_map('intval', $rArray['groups'])) . ']';
|
|
} else {
|
|
$rArray['groups'] = '[]';
|
|
}
|
|
|
|
if (!isset($rData['whitelist'])) {
|
|
$rArray['whitelist'] = '[]';
|
|
}
|
|
|
|
if ($rData['type'] != 2 && strlen($rData['code']) < 8) {
|
|
return array('status' => STATUS_CODE_LENGTH, 'data' => $rData);
|
|
}
|
|
|
|
if ($rData['type'] == 2 && empty($rData['code'])) {
|
|
return array('status' => STATUS_INVALID_CODE, 'data' => $rData);
|
|
}
|
|
|
|
if (in_array($rData['code'], array('admin', 'stream', 'images', 'player_api', 'player', 'playlist', 'epg', 'live', 'movie', 'series', 'status', 'nginx_status', 'get', 'panel_api', 'xmltv', 'probe', 'thumb', 'timeshift', 'auth', 'vauth', 'tsauth', 'hls', 'play', 'key', 'api', 'c'))) {
|
|
return array('status' => STATUS_RESERVED_CODE, 'data' => $rData);
|
|
}
|
|
|
|
if (isset($rData['edit'])) {
|
|
$db->query('SELECT `id` FROM `access_codes` WHERE `code` = ? AND `id` <> ?;', $rData['code'], $rData['edit']);
|
|
} else {
|
|
$db->query('SELECT `id` FROM `access_codes` WHERE `code` = ?;', $rData['code']);
|
|
}
|
|
|
|
if (0 < $db->num_rows()) {
|
|
return array('status' => STATUS_EXISTS_CODE, 'data' => $rData);
|
|
}
|
|
|
|
$rPrepare = QueryHelper::prepareArray($rArray);
|
|
$rQuery = 'REPLACE INTO `access_codes`(' . $rPrepare['columns'] . ') VALUES(' . $rPrepare['placeholder'] . ');';
|
|
|
|
if ($db->query($rQuery, ...$rPrepare['data'])) {
|
|
$rInsertID = $db->last_insert_id();
|
|
AuthRepository::updateCodes();
|
|
return array('status' => STATUS_SUCCESS, 'data' => array('insert_id' => $rInsertID, 'orig_code' => $rOrigCode, 'new_code' => $rData['code']));
|
|
}
|
|
|
|
return array('status' => STATUS_FAILURE, 'data' => $rData);
|
|
}
|
|
|
|
// ──────────────────────────────────────────────
|
|
// Из HMACService
|
|
// ──────────────────────────────────────────────
|
|
|
|
/**
|
|
* Create or update an HMAC key from admin form data.
|
|
*
|
|
* Validates the 32-char key and description, enforces uniqueness, and stores
|
|
* the key encrypted with the live-streaming password.
|
|
*
|
|
* @param array $rData Submitted form data (includes `edit` id when updating).
|
|
* @return array ['status' => STATUS_* constant, 'data' => payload or insert_id].
|
|
*/
|
|
public static function processHMAC($rData) {
|
|
global $db, $rSettings;
|
|
if (isset($rData['edit'])) {
|
|
$rArray = AdminHelpers::overwriteData(AuthRepository::getHMACById($rData['edit']), $rData);
|
|
} else {
|
|
$rArray = QueryHelper::verifyPostTable('hmac_keys', $rData);
|
|
unset($rArray['id']);
|
|
}
|
|
|
|
if (isset($rData['enabled'])) {
|
|
$rArray['enabled'] = 1;
|
|
} else {
|
|
$rArray['enabled'] = 0;
|
|
}
|
|
|
|
if ($rData['keygen'] != 'HMAC KEY HIDDEN' && strlen($rData['keygen']) != 32) {
|
|
return array('status' => STATUS_NO_KEY, 'data' => $rData);
|
|
}
|
|
|
|
if (strlen($rData['notes']) == 0) {
|
|
return array('status' => STATUS_NO_DESCRIPTION, 'data' => $rData);
|
|
}
|
|
|
|
if (isset($rData['edit'])) {
|
|
if ($rData['keygen'] != 'HMAC KEY HIDDEN') {
|
|
$db->query('SELECT `id` FROM `hmac_keys` WHERE `key` = ? AND `id` <> ?;', Encryption::encrypt($rData['keygen'], $rSettings['live_streaming_pass'], OPENSSL_EXTRA), $rData['edit']);
|
|
if (0 < $db->num_rows()) {
|
|
return array('status' => STATUS_EXISTS_HMAC, 'data' => $rData);
|
|
}
|
|
}
|
|
} else {
|
|
$db->query('SELECT `id` FROM `hmac_keys` WHERE `key` = ?;', Encryption::encrypt($rData['keygen'], $rSettings['live_streaming_pass'], OPENSSL_EXTRA));
|
|
if (0 < $db->num_rows()) {
|
|
return array('status' => STATUS_EXISTS_HMAC, 'data' => $rData);
|
|
}
|
|
}
|
|
|
|
if ($rData['keygen'] != 'HMAC KEY HIDDEN') {
|
|
$rArray['key'] = Encryption::encrypt($rData['keygen'], $rSettings['live_streaming_pass'], OPENSSL_EXTRA);
|
|
}
|
|
|
|
$rPrepare = QueryHelper::prepareArray($rArray);
|
|
$rQuery = 'REPLACE INTO `hmac_keys`(' . $rPrepare['columns'] . ') VALUES(' . $rPrepare['placeholder'] . ');';
|
|
|
|
if ($db->query($rQuery, ...$rPrepare['data'])) {
|
|
$rInsertID = $db->last_insert_id();
|
|
return array('status' => STATUS_SUCCESS, 'data' => array('insert_id' => $rInsertID));
|
|
}
|
|
|
|
return array('status' => STATUS_FAILURE, 'data' => $rData);
|
|
}
|
|
|
|
// ──────────────────────────────────────────────
|
|
// Из HMACValidator
|
|
// ──────────────────────────────────────────────
|
|
|
|
/**
|
|
* Validate a streaming HMAC token against all enabled keys.
|
|
*
|
|
* Recomputes the SHA-256 HMAC over the stream parameters for each enabled key
|
|
* (from cache or DB) and returns the id of the first matching key.
|
|
*
|
|
* @param string $rHMAC Token supplied by the client.
|
|
* @param int|string $rExpiry Token expiry component.
|
|
* @param int|string $rStreamID Stream id component.
|
|
* @param string $rExtension Stream extension component.
|
|
* @param string $rIP Request IP (must match $rMACIP when both set).
|
|
* @param string $rMACIP Bound MAC/IP component.
|
|
* @param string $rIdentifier Optional identifier component.
|
|
* @param int $rMaxConnections Max-connections component.
|
|
* @return int|null Matching HMAC key id, or null if no key matches.
|
|
*/
|
|
public static function validateHMAC($rHMAC, $rExpiry, $rStreamID, $rExtension, $rIP = '', $rMACIP = '', $rIdentifier = '', $rMaxConnections = 0) {
|
|
global $db, $rSettings;
|
|
$rCached = $rSettings['enable_cache'];
|
|
if (0 < strlen($rIP) && 0 < strlen($rMACIP) && $rIP != $rMACIP) {
|
|
return null;
|
|
}
|
|
|
|
$rKeyID = null;
|
|
if ($rCached) {
|
|
$rKeys = igbinary_unserialize(file_get_contents(CACHE_TMP_PATH . 'hmac_keys'));
|
|
} else {
|
|
$rKeys = array();
|
|
$db->query('SELECT `id`, `key` FROM `hmac_keys` WHERE `enabled` = 1;');
|
|
foreach ($db->get_rows() as $rKey) {
|
|
$rKeys[] = $rKey;
|
|
}
|
|
}
|
|
|
|
foreach ($rKeys as $rKey) {
|
|
$rSecret = Encryption::decrypt($rKey['key'], $rSettings['live_streaming_pass'], OPENSSL_EXTRA);
|
|
$rResult = hash_hmac('sha256', (string) $rStreamID . '##' . $rExtension . '##' . $rExpiry . '##' . $rMACIP . '##' . $rIdentifier . '##' . $rMaxConnections, $rSecret);
|
|
|
|
if (md5($rResult) == md5($rHMAC)) {
|
|
$rKeyID = $rKey['id'];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $rKeyID;
|
|
}
|
|
}
|