import logging from datetime import datetime, timedelta from typing import Optional, Any, Dict from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func, and_ from app.models.security import PendingAction, ActionStatus from app.models.history import AuditLog, LogSeverity from app.models.identity import User from app.models import SystemParameter logger = logging.getLogger(__name__) class SecurityService: @staticmethod async def get_sec_config(db: AsyncSession) -> Dict[str, Any]: """Lekéri a biztonsági korlátokat a központi rendszerparaméterekből.""" keys = ["SECURITY_MAX_RECORDS_PER_HOUR", "SECURITY_DUAL_CONTROL_ENABLED"] stmt = select(SystemParameter).where(SystemParameter.key.in_(keys)) res = await db.execute(stmt) params = {p.key: p.value for p in res.scalars().all()} return { "max_records": int(params.get("SECURITY_MAX_RECORDS_PER_HOUR", 500)), "dual_control": str(params.get("SECURITY_DUAL_CONTROL_ENABLED", "true")).lower() == "true" } # --- 1. SZINT: AUDIT & LOGGING (A Mindenlátó Szem) --- async def log_event( self, db: AsyncSession, user_id: Optional[int], action: str, severity: LogSeverity, old_data: Optional[Dict] = None, new_data: Optional[Dict] = None, ip: Optional[str] = None, ua: Optional[str] = None, target_type: Optional[str] = None, target_id: Optional[str] = None, reason: Optional[str] = None ): """Minden rendszerművelet rögzítése és azonnali biztonsági elemzése.""" new_log = AuditLog( user_id=user_id, severity=severity, action=action, target_type=target_type, target_id=target_id, old_data=old_data, new_data=new_data, ip_address=ip, user_agent=ua ) db.add(new_log) # Ha a szint EMERGENCY, azonnal lőjük le a júzert if severity == LogSeverity.emergency: await self._execute_emergency_lock(db, user_id, f"Auto-lock triggered by: {action}") await db.commit() # --- 2. SZINT: PENDING ACTIONS (Négy szem elv) --- async def request_action( self, db: AsyncSession, requester_id: int, action_type: str, payload: Dict, reason: str ): """Kritikus művelet kezdeményezése jóváhagyásra (nem hajtódik végre azonnal).""" new_action = PendingAction( requester_id=requester_id, action_type=action_type, payload=payload, reason=reason, status=ActionStatus.pending ) db.add(new_action) await self.log_event( db, requester_id, action=f"REQUEST_{action_type}", severity=LogSeverity.critical, new_data=payload, reason=f"Approval requested: {reason}" ) await db.commit() return new_action async def approve_action(self, db: AsyncSession, approver_id: int, action_id: int): """Művelet végrehajtása egy második admin által.""" stmt = select(PendingAction).where(PendingAction.id == action_id) action = (await db.execute(stmt)).scalar_one_or_none() if not action or action.status != ActionStatus.pending: raise Exception("A művelet nem található vagy már feldolgozták.") if action.requester_id == approver_id: raise Exception("Önmagad kérését nem hagyhatod jóvá! (Négy szem elv)") # ITT TÖRTÉNIK A TÉNYLEGES ÜZLETI LOGIKA (Példa: Rangmódosítás) if action.action_type == "CHANGE_ROLE": user_id = action.payload.get("user_id") new_role = action.payload.get("new_role") user_stmt = select(User).where(User.id == user_id) user = (await db.execute(user_stmt)).scalar_one_or_none() if user: user.role = new_role logger.info(f"Role for user {user_id} changed to {new_role} via approved action {action_id}") action.status = ActionStatus.approved action.approver_id = approver_id action.processed_at = func.now() await self.log_event( db, approver_id, action=f"APPROVE_{action.action_type}", severity=LogSeverity.info, target_id=str(action.id), reason=f"Approved action requested by {action.requester_id}" ) await db.commit() return True # --- 3. SZINT: DATA THROTTLING & EMERGENCY LOCK --- async def check_data_access_limit(self, db: AsyncSession, user_id: int): """Figyeli a tömeges adatlekérést (Adatlopás elleni védelem).""" config = await self.get_sec_config(db) one_hour_ago = datetime.now() - timedelta(hours=1) # Megszámoljuk az utolsó egy óra GET (lekérési) logjait stmt = select(func.count(AuditLog.id)).where( and_( AuditLog.user_id == user_id, AuditLog.timestamp >= one_hour_ago, AuditLog.action.like("GET_%") ) ) count = (await db.execute(stmt)).scalar() or 0 if count > config["max_records"]: await self.log_event( db, user_id, action="MASS_DATA_ACCESS_DETECTED", severity=LogSeverity.emergency, reason=f"Access count: {count} (Limit: {config['max_records']})" ) # A log_event automatikusan hívja a _execute_emergency_lock-ot return False return True async def _execute_emergency_lock(self, db: AsyncSession, user_id: int, reason: str): """Azonnali fiókfelfüggesztés vészhelyzet esetén.""" if not user_id: return stmt = select(User).where(User.id == user_id) user = (await db.execute(stmt)).scalar_one_or_none() if user: user.is_active = False logger.critical(f"🚨 SECURITY EMERGENCY LOCK: User {user_id} suspended. Reason: {reason}") # Itt lehetne bekötni egy külső SMS/Slack/Email riasztást security_service = SecurityService()