fixes global

This commit is contained in:
user committed 2025-08-08 09:14:18 +03:00
1 parent 13dc4f39c8
commit cad0f6aebe
64 files changed
+10379 -254

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
from __future__ import annotations
import base64
import logging
from dataclasses import asdict
from hashlib import sha256
from typing import Any, Dict, Optional, Tuple
from app.core.crypto import get_ed25519_manager
from app.core.crypto.content_cipher import ContentCipher
from app.core.models.validation.validation_models import ValidationResult, ContentSignature
logger = logging.getLogger(__name__)
class ContentValidator:
"""
Основной валидатор контента:
- Проверка подписи источника (Ed25519)
- Проверка целостности контента/объектов (checksum/content_id)
- Интеграция с ContentCipher для дополнительной верификации
"""
def __init__(self, cipher: Optional[ContentCipher] = None):
self.cipher = cipher or ContentCipher()
logger.debug("ContentValidator initialized")
def verify_source_signature(
self,
payload: Dict[str, Any],
signature_b64: Optional[str],
public_key_hex: Optional[str],
) -> ValidationResult:
"""
Проверка Ed25519 подписи источника.
- payload должен сериализоваться идентично тому, что подписывалось.
- signature_b64 - base64 строка подписи.
- public_key_hex - hex публичного ключа источника.
"""
try:
if not signature_b64 or not public_key_hex:
logger.warning("verify_source_signature: missing signature/public key")
return ValidationResult(ok=False, reason="missing_signature_or_public_key")
crypto_mgr = get_ed25519_manager()
ok = crypto_mgr.verify_signature(payload, signature_b64, public_key_hex)
if not ok:
logger.warning("verify_source_signature: invalid signature")
return ValidationResult(ok=False, reason="invalid_signature")
logger.info("verify_source_signature: signature valid")
return ValidationResult(ok=True, details={"signer_key": public_key_hex})
except Exception as e:
logger.exception("verify_source_signature error")
return ValidationResult(ok=False, reason=str(e))
def check_content_integrity(
self,
encrypted_obj: Dict[str, Any],
expected_metadata: Optional[Dict[str, Any]] = None,
verify_signature: bool = True,
) -> ValidationResult:
"""
Делегирует проверку целостности ContentCipher:
- сверка content_id = sha256(ciphertext||nonce||tag||metadata_json)
- опциональная проверка встроенной подписи encrypted_obj (если есть signature/signер_pubkey)
"""
ok, err = self.cipher.verify_content_integrity(
encrypted_obj=encrypted_obj,
expected_metadata=expected_metadata,
verify_signature=verify_signature,
)
if not ok:
return ValidationResult(ok=False, reason=err or "integrity_failed")
return ValidationResult(ok=True)
def validate_content(
self,
content_meta: Dict[str, Any],
*,
checksum: Optional[str] = None,
source_signature: Optional[ContentSignature] = None,
encrypted_obj: Optional[Dict[str, Any]] = None,
verify_ed25519: bool = True,
) -> ValidationResult:
"""
Комплексная проверка валидности контента:
1) Если указан checksum (<algo>:<hex>), сверяем.
2) Если указан source_signature, проверяем Ed25519 подпись источника.
3) Если передан encrypted_obj, выполняем углублённую проверку ContentCipher.
content_meta — произвольная структура метаданных, которая была объектом подписи источника.
"""
# 1. Проверка checksum (формат: "sha256:<hex>")
if checksum:
try:
algo, hexval = checksum.split(":", 1)
algo = algo.lower()
if algo != "sha256":
logger.warning("validate_content: unsupported checksum algo: %s", algo)
return ValidationResult(ok=False, reason="unsupported_checksum_algo", details={"algo": algo})
# Вычислить sha256 по ожидаемым данным невозможно без исходных байт,
# поэтому здесь лишь проверка формата. Фактическая сверка должна происходить
# на уровне получателя с использованием известного буфера.
if not all(c in "0123456789abcdef" for c in hexval.lower()) or len(hexval) != 64:
return ValidationResult(ok=False, reason="invalid_checksum_format")
logger.debug("validate_content: checksum format looks valid (sha256)")
except Exception:
return ValidationResult(ok=False, reason="invalid_checksum")
# 2. Проверка подписи источника (если указана)
if verify_ed25519 and source_signature:
sig_check = self.verify_source_signature(
payload=content_meta,
signature_b64=source_signature.signature,
public_key_hex=source_signature.public_key_hex,
)
if not sig_check.ok:
return ValidationResult(ok=False, reason="source_signature_invalid", details=sig_check.to_dict())
# 3. Проверка целостности зашифрованного объекта (если присутствует)
if encrypted_obj:
integ = self.check_content_integrity(
encrypted_obj=encrypted_obj,
expected_metadata=encrypted_obj.get("metadata"),
verify_signature=verify_ed25519,
)
if not integ.ok:
return ValidationResult(ok=False, reason="encrypted_integrity_invalid", details=integ.to_dict())
logger.info("validate_content: content validation passed")
return ValidationResult(ok=True)
+119
View File
@@ -0,0 +1,119 @@
from __future__ import annotations
import base64
import logging
from typing import Any, Dict, Iterable, List, Optional, Tuple
from app.core.content.chunk_manager import ChunkManager
from app.core.crypto.content_cipher import ContentCipher
from app.core.models.content.chunk import ContentChunk
from app.core.models.validation.validation_models import ValidationResult
logger = logging.getLogger(__name__)
class IntegrityChecker:
"""
Расширенная проверка целостности контента/чанков поверх возможностей ChunkManager:
- Поблочная проверка каждой записи (хеш/подпись)
- Обнаружение повреждений и дубликатов
- Проверка "цепочки" контента (согласованность content_id/индексов)
"""
def __init__(self, chunk_manager: Optional[ChunkManager] = None, cipher: Optional[ContentCipher] = None):
self.chunk_manager = chunk_manager or ChunkManager()
self.cipher = cipher or self.chunk_manager.cipher
logger.debug("IntegrityChecker initialized")
def check_chunk_integrity(self, chunk: ContentChunk, verify_signature: bool = True) -> ValidationResult:
"""
Проверяет единичный чанк, используя ChunkManager.verify_chunk_integrity.
"""
ok, err = self.chunk_manager.verify_chunk_integrity(chunk, verify_signature=verify_signature)
if not ok:
logger.warning("check_chunk_integrity: chunk invalid: %s -> %s", chunk.chunk_id, err)
return ValidationResult(ok=False, reason=err or "chunk_invalid", details={"chunk_id": chunk.chunk_id})
return ValidationResult(ok=True, details={"chunk_id": chunk.chunk_id})
def detect_corruption(self, chunks: Iterable[ContentChunk]) -> ValidationResult:
"""
Выявляет повреждения и аномалии:
- дубликаты chunk_id/chunk_index
- несовпадение content_id между чанками
- несогласованность индексов (пропуски/повторы)
"""
try:
chunks_list: List[ContentChunk] = sorted(list(chunks), key=lambda c: c.chunk_index)
if not chunks_list:
return ValidationResult(ok=True, details={"message": "no chunks"})
content_ids = {c.content_id for c in chunks_list}
if len(content_ids) != 1:
return ValidationResult(ok=False, reason="mixed_content_ids", details={"content_ids": list(content_ids)})
seen_ids = set()
seen_indexes = set()
duplicates: List[str] = []
gaps: List[int] = []
for c in chunks_list:
if c.chunk_id in seen_ids:
duplicates.append(c.chunk_id)
else:
seen_ids.add(c.chunk_id)
if c.chunk_index in seen_indexes:
duplicates.append(f"index:{c.chunk_index}")
else:
seen_indexes.add(c.chunk_index)
if chunks_list:
min_idx = chunks_list[0].chunk_index
max_idx = chunks_list[-1].chunk_index
expected = set(range(min_idx, max_idx + 1))
gaps = sorted(list(expected - seen_indexes))
if duplicates or gaps:
return ValidationResult(
ok=False,
reason="structure_anomaly",
details={"duplicates": duplicates, "missing_indexes": gaps},
)
return ValidationResult(ok=True, details={"content_id": chunks_list[0].content_id})
except Exception as e:
logger.exception("detect_corruption error")
return ValidationResult(ok=False, reason=str(e))
def verify_content_chain(
self,
chunks: Iterable[ContentChunk],
verify_signatures: bool = True,
) -> ValidationResult:
"""
Полная проверка набора чанков:
1) detect_corruption на структуру/последовательность
2) check_chunk_integrity для каждого чанка (хеш/подпись)
"""
try:
chunks_list = list(chunks)
structure = self.detect_corruption(chunks_list)
if not structure.ok:
return structure
errors: List[Dict[str, Any]] = []
ok_count = 0
for c in chunks_list:
res = self.check_chunk_integrity(c, verify_signature=verify_signatures)
if not res.ok:
errors.append({"chunk_id": c.chunk_id, "error": res.reason})
else:
ok_count += 1
if errors:
return ValidationResult(ok=False, reason="chain_integrity_failed", details={"verified_ok": ok_count, "errors": errors})
return ValidationResult(ok=True, details={"verified_ok": ok_count})
except Exception as e:
logger.exception("verify_content_chain error")
return ValidationResult(ok=False, reason=str(e))
+119
View File
@@ -0,0 +1,119 @@
from __future__ import annotations
import logging
from dataclasses import asdict
from typing import Dict, Optional
from app.core.models.validation.validation_models import TrustScore, NodeTrust
logger = logging.getLogger(__name__)
class TrustManager:
"""
Управление доверием между нодами.
- Хранит score (0.0-1.0), blacklist и флаг manual_override.
- Предоставляет API для оценки/обновления/проверки доверия.
"""
def __init__(self, default_score: float = 0.5, min_trusted: float = 0.6):
self._nodes: Dict[str, NodeTrust] = {}
self.default_score = max(0.0, min(1.0, float(default_score)))
self.min_trusted = max(0.0, min(1.0, float(min_trusted)))
logger.debug("TrustManager initialized: default_score=%s, min_trusted=%s", self.default_score, self.min_trusted)
def _get_or_create(self, node_id: str) -> NodeTrust:
if node_id not in self._nodes:
self._nodes[node_id] = NodeTrust(node_id=node_id, score=self.default_score)
logger.info("TrustManager: new node registered with default score: %s", node_id)
return self._nodes[node_id]
def assess_node_trust(self, node_id: str) -> TrustScore:
"""
Вернуть текущий TrustScore для ноды.
"""
state = self._get_or_create(node_id)
logger.debug("assess_node_trust: %s -> score=%.3f, blacklisted=%s, override=%s",
node_id, state.score, state.blacklisted, state.manual_override)
return TrustScore(node_id=node_id, score=state.score, reason=("blacklisted" if state.blacklisted else None))
def update_trust_score(self, node_id: str, delta: float, *, reason: Optional[str] = None) -> TrustScore:
"""
Обновить score ноды на delta в диапазоне [0.0, 1.0].
Положительное delta увеличивает доверие, отрицательное — уменьшает.
"""
state = self._get_or_create(node_id)
prev = state.score
state.score = max(0.0, min(1.0, prev + float(delta)))
if reason:
state.note = reason
logger.info("update_trust_score: %s: %.3f -> %.3f (reason=%s)", node_id, prev, state.score, reason)
return TrustScore(node_id=node_id, score=state.score, reason=reason)
def set_blacklist(self, node_id: str, blacklisted: bool = True, *, note: Optional[str] = None) -> NodeTrust:
"""
Добавить/убрать ноду из blacklist.
"""
state = self._get_or_create(node_id)
state.blacklisted = bool(blacklisted)
if note:
state.note = note
logger.warning("set_blacklist: %s -> %s", node_id, state.blacklisted)
return state
def set_manual_override(self, node_id: str, override: bool = True, *, note: Optional[str] = None) -> NodeTrust:
"""
Установить ручной override доверия для ноды (форсированное доверие).
"""
state = self._get_or_create(node_id)
state.manual_override = bool(override)
if note:
state.note = note
logger.warning("set_manual_override: %s -> %s", node_id, state.manual_override)
return state
def is_node_trusted(self, node_id: str) -> bool:
"""
Возвращает True если нода считается доверенной:
- НЕ находится в blacklist
- Имеет score >= min_trusted
- ЛИБО установлен manual_override (в этом случае blacklist игнорируется только если override True)
"""
state = self._get_or_create(node_id)
if state.manual_override:
logger.debug("is_node_trusted: %s -> True (manual_override)", node_id)
return True
if state.blacklisted:
logger.debug("is_node_trusted: %s -> False (blacklisted)", node_id)
return False
trusted = state.score >= self.min_trusted
logger.debug("is_node_trusted: %s -> %s (score=%.3f, min_trusted=%.3f)", node_id, trusted, state.score, self.min_trusted)
return trusted
def export_state(self) -> Dict[str, Dict]:
"""
Экспорт текущего состояния (для сериализации/персистентности).
"""
return {nid: self._nodes[nid].to_dict() for nid in self._nodes}
def import_state(self, data: Dict[str, Dict]) -> None:
"""
Импорт состояния (восстановление из персистентного хранилища).
"""
self._nodes.clear()
for nid, raw in data.items():
try:
self._nodes[nid] = NodeTrust(
node_id=raw["node_id"],
score=float(raw.get("score", self.default_score)),
blacklisted=bool(raw.get("blacklisted", False)),
manual_override=bool(raw.get("manual_override", False)),
note=raw.get("note"),
updated_at=raw.get("updated_at"),
)
except Exception as e:
logger.error("Failed to import node trust record %s: %s", nid, e)
logger.info("TrustManager state imported: nodes=%d", len(self._nodes))