🏗 Фаза 1-4: Архитектура, ядро, проверки, API, генератор отчетов
- scanner/engine.py — Playwright-движок сканирования - scanner/models.py — Pydantic модели - scanner/checks/ — 5 проверок (cookie, privacy, forms, server, external) - api/server.py — FastAPI REST API - reports/generator.py — PDF через WeasyPrint + Jinja2 - main.py — CLI + сервер Проверки: ✅ cookie_banner — поиск cookie-баннера ✅ privacy_link — проверка ссылки на политику ✅ form_checkbox — проверка чекбоксов согласия ✅ server_location — геолокация сервера (RФ или нет) ✅ external_scripts — внешние скрипты аналитики
This commit is contained in:
commit
de18090304
20 files changed
+1665
No files matched your search
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Проверка наличия ссылки на Политику конфиденциальности.
|
||||
Согласно 152-ФЗ, оператор обязан опубликовать документ,
|
||||
определяющий его политику в отношении обработки персональных данных.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from playwright.async_api import Page
|
||||
|
||||
from scanner.checks.base import BaseCheck
|
||||
from scanner.models import CheckResult, RiskLevel
|
||||
|
||||
|
||||
class PrivacyLinkCheck(BaseCheck):
|
||||
"""Проверяет наличие и доступность ссылки на Политику конфиденциальности."""
|
||||
|
||||
name = "privacy_link"
|
||||
title = "Ссылка на Политику конфиденциальности"
|
||||
description = "Проверяет, есть ли на сайте ссылка на политику конфиденциальности"
|
||||
risk_on_fail = RiskLevel.HIGH
|
||||
default_weight = 15
|
||||
|
||||
PRIVACY_PATTERNS = [
|
||||
r"privacy",
|
||||
r"политик[аые]",
|
||||
r"конфиденциальн[аыо]",
|
||||
r"персональные\s*данные",
|
||||
r"personal\s*data",
|
||||
r"data\s*policy",
|
||||
r"обработка\s*данных",
|
||||
r"user\s*privacy",
|
||||
]
|
||||
|
||||
async def check(self, page: Page, url: str) -> CheckResult:
|
||||
try:
|
||||
# Получаем все ссылки на странице
|
||||
links = await page.query_selector_all("a[href]")
|
||||
link_texts = []
|
||||
|
||||
for link in links:
|
||||
href = await link.get_attribute("href") or ""
|
||||
text = (await link.inner_text()).strip()
|
||||
if text:
|
||||
link_texts.append({"text": text, "href": href})
|
||||
|
||||
# Ищем ссылки на политику
|
||||
found_links = []
|
||||
for lt in link_texts:
|
||||
combined = (lt["text"] + " " + lt["href"]).lower()
|
||||
if any(re.search(p, combined, re.UNICODE) for p in self.PRIVACY_PATTERNS):
|
||||
full_url = urljoin(url, lt["href"])
|
||||
found_links.append({"text": lt["text"], "url": full_url})
|
||||
|
||||
if not found_links:
|
||||
return self._fail_result(
|
||||
message="Ссылка на Политику конфиденциальности не найдена.",
|
||||
details={"total_links": len(link_texts), "page_text_sample": link_texts[:3]},
|
||||
)
|
||||
|
||||
# Проверяем, что ссылка ведет на рабочую страницу
|
||||
policy_url = found_links[0]["url"]
|
||||
try:
|
||||
await page.goto(policy_url, timeout=5000)
|
||||
policy_text = await page.evaluate("() => document.body.innerText")
|
||||
await page.goto(url, timeout=5000) # Возвращаемся на главную
|
||||
|
||||
if len(policy_text) < 50:
|
||||
return self._fail_result(
|
||||
message=f"Страница политики ({policy_url}) слишком короткая ({len(policy_text)} символов).",
|
||||
details={"policy_url": policy_url, "char_count": len(policy_text)},
|
||||
)
|
||||
|
||||
return self._pass_result(
|
||||
message=f"Найдена ссылка на Политику конфиденциальности ({policy_url})",
|
||||
details={
|
||||
"policy_url": policy_url,
|
||||
"link_text": found_links[0]["text"],
|
||||
"char_count": len(policy_text),
|
||||
},
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return self._fail_result(
|
||||
message=f"Страница политики не доступна: {str(e)}",
|
||||
details={"policy_url": policy_url},
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return self._error_result(message=f"Ошибка при проверке ссылки на политику: {str(e)}")
|
||||
Reference in new issue
Block a user