Sage100-vps/security/fingerprint.py
2026-01-02 17:56:28 +03:00

122 lines
4.1 KiB
Python

from fastapi import Request
from typing import Dict
import hashlib
import hmac
import logging
from config.config import settings
logger = logging.getLogger(__name__)
class DeviceFingerprint:
COMPONENT_EXTRACTORS = {
"user_agent": lambda r: r.headers.get("User-Agent", ""),
"accept_language": lambda r: r.headers.get("Accept-Language", ""),
"accept_encoding": lambda r: r.headers.get("Accept-Encoding", ""),
"accept": lambda r: r.headers.get("Accept", ""),
"connection": lambda r: r.headers.get("Connection", ""),
"cache_control": lambda r: r.headers.get("Cache-Control", ""),
"client_ip": lambda r: DeviceFingerprint._get_client_ip(r),
"sec_ch_ua": lambda r: r.headers.get("Sec-CH-UA", ""),
"sec_ch_ua_platform": lambda r: r.headers.get("Sec-CH-UA-Platform", ""),
"sec_ch_ua_mobile": lambda r: r.headers.get("Sec-CH-UA-Mobile", ""),
}
@staticmethod
def _get_client_ip(request: Request) -> str:
forwarded = request.headers.get("X-Forwarded-For")
if forwarded:
return forwarded.split(",")[0].strip()
real_ip = request.headers.get("X-Real-IP")
if real_ip:
return real_ip
if request.client:
return request.client.host
return ""
@classmethod
def extract_components(cls, request: Request) -> Dict[str, str]:
components = {}
for component_name in settings.fingerprint_components:
extractor = cls.COMPONENT_EXTRACTORS.get(component_name)
if extractor:
try:
value = extractor(request)
components[component_name] = value if value else ""
except Exception as e:
logger.warning(f"Erreur extraction composant {component_name}: {e}")
components[component_name] = ""
else:
logger.warning(f"Extracteur inconnu pour composant: {component_name}")
return components
@classmethod
def generate_hash(cls, request: Request, include_ip: bool = False) -> str:
components = cls.extract_components(request)
if not include_ip and "client_ip" in components:
del components["client_ip"]
sorted_keys = sorted(components.keys())
fingerprint_data = "|".join(f"{k}:{components[k]}" for k in sorted_keys)
secret = settings.fingerprint_secret or settings.jwt_secret
signature = hmac.new(
secret.encode(), fingerprint_data.encode(), hashlib.sha256
).hexdigest()
return signature
@classmethod
def generate_from_components(cls, components: Dict[str, str]) -> str:
sorted_keys = sorted(components.keys())
fingerprint_data = "|".join(f"{k}:{components.get(k, '')}" for k in sorted_keys)
secret = settings.fingerprint_secret or settings.jwt_secret
signature = hmac.new(
secret.encode(), fingerprint_data.encode(), hashlib.sha256
).hexdigest()
return signature
@classmethod
def validate(
cls, request: Request, stored_hash: str, include_ip: bool = False
) -> bool:
if not stored_hash:
return True
current_hash = cls.generate_hash(request, include_ip=include_ip)
return hmac.compare_digest(current_hash, stored_hash)
@classmethod
def get_device_info(cls, request: Request) -> Dict[str, str]:
user_agent = request.headers.get("User-Agent", "")
return {
"user_agent": user_agent[:500] if user_agent else "",
"ip_address": cls._get_client_ip(request),
"accept_language": request.headers.get("Accept-Language", "")[:100],
"fingerprint_hash": cls.generate_hash(request),
}
def get_fingerprint_hash(request: Request) -> str:
return DeviceFingerprint.generate_hash(request)
def validate_fingerprint(request: Request, stored_hash: str) -> bool:
return DeviceFingerprint.validate(request, stored_hash)
def get_client_ip(request: Request) -> str:
return DeviceFingerprint._get_client_ip(request)