STABLE: Final schema sync, optimized gitignore

This commit is contained in:
Kincses
2026-02-26 08:19:25 +01:00
parent 893f39fa15
commit 505543330a
203 changed files with 11590 additions and 9542 deletions

View File

@@ -1,22 +1,21 @@
# /opt/docker/dev/service_finder/backend/app/services/security_service.py
import logging
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
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
from app.models.system 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))
""" Lekéri a korlátokat a központi system_parameters-ből. """
stmt = select(SystemParameter).where(SystemParameter.key.in_(["SECURITY_MAX_RECORDS_PER_HOUR", "SECURITY_DUAL_CONTROL_ENABLED"]))
res = await db.execute(stmt)
params = {p.key: p.value for p in res.scalars().all()}
@@ -25,145 +24,71 @@ class SecurityService:
"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."""
async def log_event(self, db: AsyncSession, user_id: Optional[int], action: str, severity: LogSeverity, **kwargs):
""" LOGIKA MEGŐRIZVE: Audit naplózás + Emergency Lock trigger. """
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
user_id=user_id, severity=severity, action=action,
target_type=kwargs.get("target_type"), target_id=kwargs.get("target_id"),
old_data=kwargs.get("old_data"), new_data=kwargs.get("new_data"),
ip_address=kwargs.get("ip"), user_agent=kwargs.get("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 self._execute_emergency_lock(db, user_id, f"Auto-lock 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)."""
async def request_action(self, db: AsyncSession, requester_id: int, action_type: str, payload: Dict, reason: str):
""" NÉGY SZEM ELV: Jóváhagyási kérelem indítása. """
new_action = PendingAction(
requester_id=requester_id,
action_type=action_type,
payload=payload,
reason=reason,
status=ActionStatus.pending
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."""
""" Jóváhagyás végrehajtása (Logic Preserved: Ön-jóváhagyás tiltva). """
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.")
raise Exception("Művelet nem található.")
if action.requester_id == approver_id:
raise Exception("Önmagad kérését nem hagyhatod jóvá! (Négy szem elv)")
raise Exception("Saját kérést nem hagyhatsz jóvá!")
# ITT TÖRTÉNIK A TÉNYLEGES ÜZLETI LOGIKA (Példa: Rangmódosítás)
# Üzleti logika (pl. Role változtatá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}")
target_user = (await db.execute(select(User).where(User.id == action.payload.get("user_id")))).scalar_one_or_none()
if target_user: target_user.role = action.payload.get("new_role")
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}"
)
action.processed_at = datetime.now(timezone.utc)
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)."""
""" DATA THROTTLING: Adatlopás elleni védelem. """
config = await self.get_sec_config(db)
one_hour_ago = datetime.now() - timedelta(hours=1)
limit_time = datetime.now(timezone.utc) - 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_%")
)
and_(AuditLog.user_id == user_id, AuditLog.timestamp >= limit_time, 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
await self.log_event(db, user_id, "MASS_DATA_ACCESS", LogSeverity.emergency, reason=f"Count: {count}")
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()
user = (await db.execute(select(User).where(User.id == user_id))).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
logger.critical(f"🚨 EMERGENCY LOCK: User {user_id} suspended. Reason: {reason}")
security_service = SecurityService()