Исправлено: - engine: _calculate_score() при ошибке загрузки (score не 100 для мёртвых сайтов) - privacy_link: замена page.goto() на aiohttp (гонка данных) - form_checkbox: 3-уровневый поиск label + dark-pattern detection - server_location: синхронный urllib → aiohttp - api/server: перепутанные request/response модели в batch - main.py: csv --checks флаг - engine: фазовая модель (cookie_audit отдельно, остальные параллельно) - engine: retry-логика (3 попытки, exponential backoff) - PDF: emoji заменены на текст (WeasyPrint) Добавлено: - consent_doc: проверка отдельного согласия на обработку ПД - operator_info: проверка ИНН/ОГРН/DPO (читает главную + политику) - cookie_audit: 4-фазный аудит (load→detect→reject→compare) - llm_policy: опциональный LLM-анализ 16 разделов (--deep) - external_scripts: YAML-словари trackers.yaml (critical/warning/russian) - logging (output.py), .gitignore, pyproject.toml - smoke-тесты (16/16)
115 lines
4.7 KiB
Python
115 lines
4.7 KiB
Python
"""
|
|
Проверка геолокации сервера.
|
|
Согласно 152-ФЗ (ст. 18), сбор персональных данных граждан РФ
|
|
должен производиться с использованием баз данных, находящихся на территории РФ.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import socket
|
|
from urllib.parse import urlparse
|
|
|
|
from playwright.async_api import Page
|
|
|
|
from scanner.checks.base import BaseCheck
|
|
from scanner.models import CheckResult, RiskLevel
|
|
|
|
|
|
class ServerLocationCheck(BaseCheck):
|
|
"""Проверяет, находится ли сервер сайта в России."""
|
|
|
|
name = "server_location"
|
|
title = "Геолокация сервера"
|
|
description = "Проверяет, находится ли сервер сайта на территории РФ"
|
|
risk_on_fail = RiskLevel.CRITICAL
|
|
default_weight = 25
|
|
|
|
# Популярные русские DNS-серверы/хостеры
|
|
RUSSIAN_HOSTERS = [
|
|
"yandex", "mail.ru", "vk", "selectel", "timeweb", "beget",
|
|
"reg.ru", "nic.ru", "rtcdn", "astra", "sbercloud",
|
|
"cloud4russia", "гостех", "ростелеком", "мтс", "билайн",
|
|
"мегафон", "вымпелком", "netology",
|
|
"yandex.net", "mail.ru", "vk.com", "selectel.cloud",
|
|
]
|
|
|
|
async def check(self, page: Page, url: str) -> CheckResult:
|
|
try:
|
|
# Извлекаем домен из URL
|
|
parsed = urlparse(url)
|
|
hostname = parsed.hostname or ""
|
|
|
|
if not hostname:
|
|
return self._skip_result(message="Не удалось извлечь домен из URL.")
|
|
|
|
# Получаем IP через DNS
|
|
try:
|
|
ip = socket.gethostbyname(hostname)
|
|
except socket.gaierror:
|
|
return self._skip_result(message=f"Не удалось разрешить DNS для {hostname}.")
|
|
|
|
# Определяем страну по IP
|
|
country = await self._get_country(ip)
|
|
|
|
if not country:
|
|
return self._skip_result(message=f"Не удалось определить страну для IP {ip}.")
|
|
|
|
# Проверяем, Россия ли это
|
|
if country.lower() == "ru" or "Россия" in country or "russia" in country.lower():
|
|
return self._pass_result(
|
|
message=f"Сервер находится в России (IP: {ip}, Страна: {country})",
|
|
details={
|
|
"ip": ip,
|
|
"country": country,
|
|
},
|
|
)
|
|
|
|
# Сервер за рубежом — критическое нарушение
|
|
return self._fail_result(
|
|
message=(
|
|
f"Сервер находится ЗА ПРЕДЕЛАМИ РОССИИ ({country}, IP: {ip}). "
|
|
f"Это нарушение ст. 18 152-ФЗ: базы данных граждан РФ должны быть в РФ."
|
|
),
|
|
details={
|
|
"ip": ip,
|
|
"country": country,
|
|
},
|
|
)
|
|
|
|
except Exception as e:
|
|
return self._error_result(message=f"Ошибка при проверке геолокации: {str(e)}")
|
|
|
|
async def _get_country(self, ip: str) -> str | None:
|
|
"""Получает страну по IP через публичный API (асинхронно)."""
|
|
try:
|
|
import aiohttp
|
|
|
|
apis = [
|
|
f"https://ipapi.co/{ip}/json/",
|
|
f"https://ip-api.com/json/{ip}?fields=country",
|
|
]
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
for api_url in apis:
|
|
try:
|
|
async with session.get(
|
|
api_url,
|
|
headers={"User-Agent": "Mozilla/5.0"},
|
|
timeout=aiohttp.ClientTimeout(total=5),
|
|
) as response:
|
|
if response.status != 200:
|
|
continue
|
|
data = await response.json()
|
|
# ipapi.co формат
|
|
if "country_name" in data:
|
|
return data["country_name"]
|
|
# ip-api.com формат
|
|
if "country" in data:
|
|
return data["country"]
|
|
except Exception:
|
|
continue
|
|
|
|
return None
|
|
|
|
except Exception:
|
|
return None
|