First version
This commit is contained in:
206
backend/models/event_model.py
Normal file
206
backend/models/event_model.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import json
|
||||
|
||||
from mapping_loader import get_mapping
|
||||
|
||||
|
||||
CATEGORY_LABELS = {
|
||||
"ApplicationManagement": "Application",
|
||||
"UserManagement": "User",
|
||||
"GroupManagement": "Group",
|
||||
"RoleManagement": "Role",
|
||||
"Device": "Device",
|
||||
"Policy": "Policy",
|
||||
"ResourceManagement": "Resource",
|
||||
}
|
||||
|
||||
|
||||
def _actor_display(actor: dict, resolved: dict = None, owners=None) -> str:
|
||||
"""Choose a human-readable actor label."""
|
||||
if resolved and resolved.get("name"):
|
||||
name = resolved["name"]
|
||||
if resolved.get("type") == "servicePrincipal" and owners:
|
||||
owners_str = ", ".join(owners[:3])
|
||||
return f"{name} (owners: {owners_str})" if owners_str else name
|
||||
return name
|
||||
|
||||
if not actor:
|
||||
return "Unknown actor"
|
||||
|
||||
user = actor.get("user", {}) or {}
|
||||
sp = actor.get("servicePrincipal", {}) or {}
|
||||
app = actor.get("app", {}) or {}
|
||||
upn = user.get("userPrincipalName") or user.get("mail")
|
||||
display = user.get("displayName")
|
||||
app_display = app.get("displayName")
|
||||
|
||||
if display and upn and display != upn:
|
||||
return f"{display} ({upn})"
|
||||
|
||||
return (
|
||||
display
|
||||
or upn
|
||||
or app_display
|
||||
or sp.get("displayName")
|
||||
or sp.get("appId")
|
||||
or actor.get("ipAddress")
|
||||
or user.get("id")
|
||||
or sp.get("id")
|
||||
or "Unknown actor"
|
||||
)
|
||||
|
||||
|
||||
def _target_displays(targets: list) -> list:
|
||||
"""Best-effort display labels for targets."""
|
||||
labels = []
|
||||
for t in targets or []:
|
||||
resolved = t.get("_resolved") or {}
|
||||
label = (
|
||||
resolved.get("name")
|
||||
or resolved.get("id")
|
||||
or t.get("displayName")
|
||||
or t.get("userPrincipalName")
|
||||
or t.get("logonId")
|
||||
or t.get("id")
|
||||
or ""
|
||||
)
|
||||
if label:
|
||||
labels.append(label)
|
||||
return labels
|
||||
|
||||
|
||||
def _target_types(targets: list) -> list:
|
||||
"""Collect target types for display mapping."""
|
||||
types = []
|
||||
for t in targets or []:
|
||||
resolved = t.get("_resolved") or {}
|
||||
t_type = (
|
||||
resolved.get("type")
|
||||
or t.get("type")
|
||||
)
|
||||
if t_type:
|
||||
types.append(t_type)
|
||||
return types
|
||||
|
||||
|
||||
def _display_summary(operation: str, target_labels: list, actor_label: str, target_types: list, category: str) -> str:
|
||||
action = operation or category or "Event"
|
||||
target = target_labels[0] if target_labels else None
|
||||
t_type = target_types[0] if target_types else None
|
||||
|
||||
target_piece = None
|
||||
if target and t_type:
|
||||
target_piece = f"{t_type.lower()}: {target}"
|
||||
elif target:
|
||||
target_piece = target
|
||||
|
||||
pieces = [p for p in [action, target_piece] if p]
|
||||
if actor_label:
|
||||
pieces.append(f"by {actor_label}")
|
||||
return " | ".join(pieces)
|
||||
|
||||
|
||||
def _render_summary(template: str, operation: str, actor: str, target: str, category: str, result: str, service: str) -> str:
|
||||
try:
|
||||
return template.format(
|
||||
operation=operation or category or "Event",
|
||||
actor=actor or "Unknown actor",
|
||||
target=target or "target",
|
||||
category=category or "Other",
|
||||
result=result or "",
|
||||
service=service or "",
|
||||
)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _make_dedupe_key(e: dict, normalized_fields: dict = None) -> str:
|
||||
"""
|
||||
Build a stable key to prevent duplicates across sources.
|
||||
Preference order:
|
||||
- source event id (id) + category
|
||||
- fallback to timestamp + category + operation + first target label
|
||||
"""
|
||||
norm = normalized_fields or {}
|
||||
eid = e.get("id") or e.get("_id") or norm.get("id")
|
||||
ts = e.get("activityDateTime") or e.get("timestamp") or norm.get("timestamp")
|
||||
category = e.get("category") or e.get("service") or norm.get("service")
|
||||
op = e.get("activityDisplayName") or e.get("operation") or norm.get("operation")
|
||||
target_labels = norm.get("target_displays") or []
|
||||
target = target_labels[0] if target_labels else None
|
||||
|
||||
if eid:
|
||||
return "|".join(filter(None, [eid, category]))
|
||||
|
||||
return "|".join(filter(None, [ts, category, op, target])) or None
|
||||
|
||||
|
||||
def normalize_event(e):
|
||||
actor = e.get("initiatedBy", {})
|
||||
targets = e.get("targetResources", [])
|
||||
resolved_actor = e.get("_resolvedActor")
|
||||
actor_owners = e.get("_resolvedActorOwners", [])
|
||||
target_labels = _target_displays(targets)
|
||||
target_types = _target_types(targets)
|
||||
actor_label = _actor_display(actor, resolved_actor, actor_owners)
|
||||
actor_upn = (actor.get("user") or {}).get("userPrincipalName") or (actor.get("user") or {}).get("mail")
|
||||
first_target_label = target_labels[0] if target_labels else None
|
||||
category = e.get("category")
|
||||
mapping = get_mapping()
|
||||
category_labels = mapping.get("category_labels") or {}
|
||||
summary_templates = mapping.get("summary_templates") or {}
|
||||
display_mapping = mapping.get("display") or {}
|
||||
display_category = category_labels.get(category, category or "Other")
|
||||
|
||||
operation = e.get("activityDisplayName")
|
||||
template = summary_templates.get(category) or summary_templates.get("default")
|
||||
summary = _render_summary(
|
||||
template,
|
||||
operation=operation,
|
||||
actor=actor_label,
|
||||
target=target_labels[0] if target_labels else None,
|
||||
category=display_category,
|
||||
result=e.get("result"),
|
||||
service=e.get("loggedByService") or e.get("category"),
|
||||
)
|
||||
|
||||
display_conf = display_mapping.get(category) or display_mapping.get("default", {})
|
||||
actor_field_pref = display_conf.get("actor_field", "actor_display")
|
||||
actor_label_text = display_conf.get("actor_label", "User")
|
||||
|
||||
if actor_field_pref == "actor_upn" and actor_upn:
|
||||
display_actor_value = actor_upn
|
||||
elif actor_field_pref == "target_display" and first_target_label:
|
||||
display_actor_value = first_target_label
|
||||
else:
|
||||
display_actor_value = actor_label
|
||||
|
||||
dedupe_key = _make_dedupe_key(e, {
|
||||
"id": e.get("id"),
|
||||
"timestamp": e.get("activityDateTime"),
|
||||
"service": e.get("category"),
|
||||
"operation": e.get("activityDisplayName"),
|
||||
"target_displays": target_labels,
|
||||
})
|
||||
|
||||
return {
|
||||
"id": e.get("id"),
|
||||
"timestamp": e.get("activityDateTime"),
|
||||
"service": e.get("category"),
|
||||
"operation": e.get("activityDisplayName"),
|
||||
"result": e.get("result"),
|
||||
"actor": actor,
|
||||
"actor_resolved": resolved_actor,
|
||||
"actor_owner_names": actor_owners,
|
||||
"actor_display": actor_label,
|
||||
"actor_upn": actor_upn,
|
||||
"display_actor_label": actor_label_text,
|
||||
"display_actor_value": display_actor_value,
|
||||
"targets": targets,
|
||||
"target_displays": target_labels,
|
||||
"target_types": target_types,
|
||||
"display_category": display_category,
|
||||
"display_summary": summary,
|
||||
"raw": e,
|
||||
"raw_text": json.dumps(e, default=str),
|
||||
"dedupe_key": dedupe_key,
|
||||
}
|
||||
Reference in New Issue
Block a user