refactor(db): split attackers.py into an attackers/ subpackage
Splits the 494-line attackers.py into five submixin files plus a
composing AttackersMixin in attackers/__init__.py:
_core.py (~95) Attacker CRUD + _deserialize_attacker
behavior.py (~110) AttackerBehavior + _deserialize_behavior
sessions.py (~50) SessionProfile read/write
smtp.py (~70) SmtpTarget per-attacker + cross-attacker views
activity.py (~190) log-derived activity (commands, leaks,
artifacts, stored mail, session log, transcripts)
IdentitiesMixin.list_observations_for_identity calls
self._deserialize_attacker; MRO resolves it onto AttackersCoreMixin
through the composed SQLModelRepository class.
This commit is contained in:
49
decnet/web/db/sqlmodel_repo/attackers/sessions.py
Normal file
49
decnet/web/db/sqlmodel_repo/attackers/sessions.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""Per-session profile rows (keystroke-dynamics features land here at
|
||||
ingestion-time post-V2)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from decnet.web.db.models import SessionProfile
|
||||
|
||||
|
||||
class SessionProfilesMixin:
|
||||
async def upsert_session_profile(
|
||||
self,
|
||||
sid: str,
|
||||
data: dict[str, Any],
|
||||
) -> None:
|
||||
"""
|
||||
Write (or update) the session_profile row for *sid*.
|
||||
|
||||
Pre-v1, the typical call is the empty-write path at session close:
|
||||
`upsert_session_profile(sid, {"log_id": <id>})` — all keystroke
|
||||
feature columns stay NULL until the V2 ingestion job populates them.
|
||||
"""
|
||||
async with self._session() as session:
|
||||
result = await session.execute(
|
||||
select(SessionProfile).where(SessionProfile.sid == sid)
|
||||
)
|
||||
existing = result.scalar_one_or_none()
|
||||
if existing:
|
||||
for k, v in data.items():
|
||||
setattr(existing, k, v)
|
||||
session.add(existing)
|
||||
else:
|
||||
session.add(SessionProfile(sid=sid, **data))
|
||||
await session.commit()
|
||||
|
||||
async def get_session_profile(
|
||||
self,
|
||||
sid: str,
|
||||
) -> Optional[dict[str, Any]]:
|
||||
async with self._session() as session:
|
||||
result = await session.execute(
|
||||
select(SessionProfile).where(SessionProfile.sid == sid)
|
||||
)
|
||||
row = result.scalar_one_or_none()
|
||||
if not row:
|
||||
return None
|
||||
return row.model_dump(mode="json")
|
||||
Reference in New Issue
Block a user