Catalyst / admin/Synapse-Cortex 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Cortex

public

Self Hosted ITSM Tool with RBAC/Tenanting and MFA

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Synapse-Cortex / Synapse-Cortexv2 / app / utils.py 2635 B · main
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import hashlib
import re
import uuid
from typing import Iterable, Optional

MAC_ADDRESS_PATTERN = re.compile(r"^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$")

# Best-effort keyword hints against the original default category names, used
# only as a fallback when a tenant's option names don't directly match
# NetscanXi's free-text device_type guess. Asset types are admin-configurable
# (see app.models.AssetTypeOption), not a fixed enum, so the primary match is
# always against whatever names actually exist for the tenant right now.
_DEVICE_TYPE_KEYWORD_HINTS = (
    ("Server", ("server",)),
    ("Network Device", ("router", "switch", "firewall", "access point", "network")),
    ("Printer", ("printer",)),
    ("Laptop", ("laptop", "notebook")),
    ("Mobile", ("mobile", "phone", "tablet")),
    ("Workstation", ("workstation", "desktop")),
)


def match_asset_type_name(raw: Optional[str], available_names: Iterable[str]) -> str:
    """Best-effort match of NetscanXi's free-text device_type guess against a
    tenant's current AssetTypeOption names. The raw string is always
    preserved verbatim in Asset.netscanxi_device_type regardless of how (or
    whether) this resolves, so nothing is lost even when the guess is wrong.

    Falls back to an option named "Other" if present, else the first
    available option name, so this only ever returns a name the caller can
    use to look up an existing option - never an invented one.
    """
    names = list(available_names)
    other = next((n for n in names if n.strip().lower() == "other"), None)
    fallback = other or (names[0] if names else "Other")
    if not raw:
        return fallback

    lowered = raw.lower()
    for name in names:
        if name.lower() in lowered or lowered in name.lower():
            return name
    for hint_name, keywords in _DEVICE_TYPE_KEYWORD_HINTS:
        if hint_name in names and any(keyword in lowered for keyword in keywords):
            return hint_name
    return fallback


def normalize_mac(mac_address: str) -> str:
    return mac_address.strip().lower().replace("-", ":")


def is_valid_mac(mac_address: str) -> bool:
    return bool(MAC_ADDRESS_PATTERN.match(mac_address.strip()))


def generate_asset_id(tenant_id: uuid.UUID, mac_address: str) -> str:
    """Derive a deterministic asset_id from the tenant and the device MAC address.

    Same tenant + MAC always yields the same asset_id, logically binding the
    CMDB record to the physical NIC it was registered against.
    """
    digest = hashlib.sha256(f"{tenant_id}:{normalize_mac(mac_address)}".encode()).hexdigest()
    return f"AST-{digest[:10].upper()}"