= self::MAX_FAILS) { throw new ExchangeException('Слишком много неудачных попыток входа, повторите позже'); } $login = Settings::str('login'); $hash = Settings::str('password_hash'); if ($login === '' || $hash === '') { throw new ExchangeException('Логин и пароль обмена не заданы в настройках плагина'); } [$user, $pass] = self::basicCredentials(); $ok = $user !== null && hash_equals($login, $user) && wp_check_password((string) $pass, $hash); if (!$ok) { set_transient($failKey, $fails + 1, self::LOCK_SECONDS); usleep(random_int(200000, 600000)); Log::warning(sprintf('Неудачная авторизация 1С с IP %s (логин: %s)', $ip, sanitize_user((string) $user))); throw new ExchangeException( $user === null ? 'Сервер не получил логин/пароль (заголовок Authorization). См. раздел помощи в настройках плагина.' : 'Неверный логин или пароль' ); } delete_transient($failKey); $token = bin2hex(random_bytes(32)); set_transient(self::sessionKey($token), ['ip' => $ip, 'time' => time()], self::SESSION_TTL); Log::debug('Авторизация 1С успешна, IP ' . $ip); return "success\n" . self::COOKIE . "\n" . $token . "\n"; } /** * Проверка cookie сессии на всех шагах после checkauth. */ public static function requireSession(): void { $token = isset($_COOKIE[self::COOKIE]) ? sanitize_text_field(wp_unslash($_COOKIE[self::COOKIE])) : ''; if (preg_match('/^[a-f0-9]{64}$/', $token)) { $key = self::sessionKey($token); $session = get_transient($key); if (is_array($session)) { // Продлеваем сессию: загрузка большого каталога может идти долго. set_transient($key, $session, self::SESSION_TTL); return; } } throw new ExchangeException('Сессия не найдена или истекла, выполните авторизацию заново'); } /** * @return array{0: ?string, 1: ?string} */ private static function basicCredentials(): array { // WordPress добавляет слеши ко всем входным данным, включая $_SERVER, — снимаем их. // Логин и пароль не «очищаем»: это изменило бы пароль. Они только сравниваются (hash_equals / wp_check_password). if (isset($_SERVER['PHP_AUTH_USER'])) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- сырой пароль, только для проверки хеша return [(string) wp_unslash($_SERVER['PHP_AUTH_USER']), (string) wp_unslash($_SERVER['PHP_AUTH_PW'] ?? '')]; } // PHP в режиме CGI/FastCGI сам заголовок не разбирает. foreach (['HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION'] as $key) { $header = isset($_SERVER[$key]) ? sanitize_text_field(wp_unslash($_SERVER[$key])) : ''; if (stripos($header, 'basic ') === 0) { $decoded = base64_decode(substr($header, 6), true); if ($decoded !== false && str_contains($decoded, ':')) { return explode(':', $decoded, 2); } } } return [null, null]; } private static function sessionKey(string $token): string { return 'gersync_s_' . hash('sha256', $token); } }