mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 22:01:59 -06:00
Additional Changes
This commit is contained in:
@@ -9,6 +9,7 @@ from flask import Blueprint, jsonify, request, g
|
||||
|
||||
from Modules.auth.device_auth import DeviceAuthManager, require_device_auth
|
||||
from Modules.crypto.signing import ScriptSigner
|
||||
from Modules.guid_utils import normalize_guid
|
||||
|
||||
AGENT_CONTEXT_HEADER = "X-Borealis-Agent-Context"
|
||||
|
||||
@@ -102,13 +103,36 @@ def register(
|
||||
if not updates:
|
||||
return 0
|
||||
columns = ", ".join(f"{col} = ?" for col in updates.keys())
|
||||
params = list(updates.values())
|
||||
params.append(ctx.guid)
|
||||
values = list(updates.values())
|
||||
normalized_guid = normalize_guid(ctx.guid)
|
||||
selected_guid: Optional[str] = None
|
||||
if normalized_guid:
|
||||
cur.execute(
|
||||
"SELECT guid FROM devices WHERE UPPER(guid) = ?",
|
||||
(normalized_guid,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
for (stored_guid,) in rows or []:
|
||||
if stored_guid == ctx.guid:
|
||||
selected_guid = stored_guid
|
||||
break
|
||||
if not selected_guid and rows:
|
||||
selected_guid = rows[0][0]
|
||||
target_guid = selected_guid or ctx.guid
|
||||
cur.execute(
|
||||
f"UPDATE devices SET {columns} WHERE guid = ?",
|
||||
params,
|
||||
values + [target_guid],
|
||||
)
|
||||
return cur.rowcount
|
||||
updated = cur.rowcount
|
||||
if updated > 0 and normalized_guid and target_guid != normalized_guid:
|
||||
try:
|
||||
cur.execute(
|
||||
"UPDATE devices SET guid = ? WHERE guid = ?",
|
||||
(normalized_guid, target_guid),
|
||||
)
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
||||
return updated
|
||||
|
||||
try:
|
||||
rowcount = _apply_updates()
|
||||
|
||||
@@ -13,6 +13,7 @@ from flask import g, jsonify, request
|
||||
|
||||
from Modules.auth.dpop import DPoPValidator, DPoPVerificationError, DPoPReplayError
|
||||
from Modules.auth.rate_limit import SlidingWindowRateLimiter
|
||||
from Modules.guid_utils import normalize_guid
|
||||
|
||||
AGENT_CONTEXT_HEADER = "X-Borealis-Agent-Context"
|
||||
|
||||
@@ -87,7 +88,8 @@ class DeviceAuthManager:
|
||||
except Exception:
|
||||
raise DeviceAuthError("invalid_token")
|
||||
|
||||
guid = str(claims.get("guid") or "").strip()
|
||||
raw_guid = str(claims.get("guid") or "").strip()
|
||||
guid = normalize_guid(raw_guid)
|
||||
fingerprint = str(claims.get("ssl_key_fingerprint") or "").lower().strip()
|
||||
token_version = int(claims.get("token_version") or 0)
|
||||
if not guid or not fingerprint or token_version <= 0:
|
||||
@@ -110,11 +112,19 @@ class DeviceAuthManager:
|
||||
"""
|
||||
SELECT guid, ssl_key_fingerprint, token_version, status
|
||||
FROM devices
|
||||
WHERE guid = ?
|
||||
WHERE UPPER(guid) = ?
|
||||
""",
|
||||
(guid,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
rows = cur.fetchall()
|
||||
row = None
|
||||
for candidate in rows or []:
|
||||
candidate_guid = normalize_guid(candidate[0])
|
||||
if candidate_guid == guid:
|
||||
row = candidate
|
||||
break
|
||||
if row is None and rows:
|
||||
row = rows[0]
|
||||
|
||||
if not row:
|
||||
row = self._recover_device_record(
|
||||
@@ -125,8 +135,9 @@ class DeviceAuthManager:
|
||||
raise DeviceAuthError("device_not_found", status_code=403)
|
||||
|
||||
db_guid, db_fp, db_token_version, status = row
|
||||
db_guid_normalized = normalize_guid(db_guid)
|
||||
|
||||
if str(db_guid or "").lower() != guid.lower():
|
||||
if not db_guid_normalized or db_guid_normalized != guid:
|
||||
raise DeviceAuthError("device_guid_mismatch", status_code=403)
|
||||
|
||||
db_fp = (db_fp or "").lower().strip()
|
||||
@@ -182,7 +193,7 @@ class DeviceAuthManager:
|
||||
) -> Optional[tuple]:
|
||||
"""Attempt to recreate a missing device row for an authenticated token."""
|
||||
|
||||
guid = (guid or "").strip()
|
||||
guid = normalize_guid(guid)
|
||||
fingerprint = (fingerprint or "").strip()
|
||||
if not guid or not fingerprint:
|
||||
return None
|
||||
|
||||
@@ -24,6 +24,7 @@ from flask import Blueprint, jsonify, request
|
||||
from Modules.auth.rate_limit import SlidingWindowRateLimiter
|
||||
from Modules.crypto import keys as crypto_keys
|
||||
from Modules.enrollment.nonce_store import NonceCache
|
||||
from Modules.guid_utils import normalize_guid
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
|
||||
@@ -141,11 +142,11 @@ def register(
|
||||
if use_count < max_uses:
|
||||
return True, None
|
||||
|
||||
guid = str(record.get("used_by_guid") or "").strip()
|
||||
guid = normalize_guid(record.get("used_by_guid"))
|
||||
if not guid:
|
||||
return False, None
|
||||
cur.execute(
|
||||
"SELECT ssl_key_fingerprint FROM devices WHERE guid = ?",
|
||||
"SELECT ssl_key_fingerprint FROM devices WHERE UPPER(guid) = ?",
|
||||
(guid,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
@@ -159,31 +160,36 @@ def register(
|
||||
return False, None
|
||||
|
||||
def _normalize_host(hostname: str, guid: str, cur: sqlite3.Cursor) -> str:
|
||||
base = (hostname or "").strip() or guid
|
||||
guid_norm = normalize_guid(guid)
|
||||
base = (hostname or "").strip() or guid_norm
|
||||
base = base[:253]
|
||||
candidate = base
|
||||
suffix = 1
|
||||
while True:
|
||||
cur.execute(
|
||||
"SELECT guid FROM devices WHERE hostname = ? AND guid != ?",
|
||||
(candidate, guid),
|
||||
"SELECT guid FROM devices WHERE hostname = ?",
|
||||
(candidate,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return candidate
|
||||
existing_guid = normalize_guid(row[0])
|
||||
if existing_guid == guid_norm:
|
||||
return candidate
|
||||
candidate = f"{base}-{suffix}"
|
||||
suffix += 1
|
||||
if suffix > 50:
|
||||
return f"{guid}"
|
||||
return guid_norm
|
||||
|
||||
def _store_device_key(cur: sqlite3.Cursor, guid: str, fingerprint: str) -> None:
|
||||
guid_norm = normalize_guid(guid)
|
||||
added_at = _iso(_now())
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO device_keys (id, guid, ssl_key_fingerprint, added_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(str(uuid.uuid4()), guid, fingerprint, added_at),
|
||||
(str(uuid.uuid4()), guid_norm, fingerprint, added_at),
|
||||
)
|
||||
cur.execute(
|
||||
"""
|
||||
@@ -193,17 +199,18 @@ def register(
|
||||
AND ssl_key_fingerprint != ?
|
||||
AND retired_at IS NULL
|
||||
""",
|
||||
(_iso(_now()), guid, fingerprint),
|
||||
(_iso(_now()), guid_norm, fingerprint),
|
||||
)
|
||||
|
||||
def _ensure_device_record(cur: sqlite3.Cursor, guid: str, hostname: str, fingerprint: str) -> Dict[str, Any]:
|
||||
guid_norm = normalize_guid(guid)
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT guid, hostname, token_version, status, ssl_key_fingerprint, key_added_at
|
||||
FROM devices
|
||||
WHERE guid = ?
|
||||
WHERE UPPER(guid) = ?
|
||||
""",
|
||||
(guid,),
|
||||
(guid_norm,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
@@ -216,12 +223,13 @@ def register(
|
||||
"key_added_at",
|
||||
]
|
||||
record = dict(zip(keys, row))
|
||||
record["guid"] = normalize_guid(record.get("guid"))
|
||||
stored_fp = (record.get("ssl_key_fingerprint") or "").strip().lower()
|
||||
new_fp = (fingerprint or "").strip().lower()
|
||||
if not stored_fp and new_fp:
|
||||
cur.execute(
|
||||
"UPDATE devices SET ssl_key_fingerprint = ?, key_added_at = ? WHERE guid = ?",
|
||||
(fingerprint, _iso(_now()), guid),
|
||||
(fingerprint, _iso(_now()), record["guid"]),
|
||||
)
|
||||
record["ssl_key_fingerprint"] = fingerprint
|
||||
elif new_fp and stored_fp != new_fp:
|
||||
@@ -240,7 +248,7 @@ def register(
|
||||
status = 'active'
|
||||
WHERE guid = ?
|
||||
""",
|
||||
(fingerprint, now_iso, new_version, guid),
|
||||
(fingerprint, now_iso, new_version, record["guid"]),
|
||||
)
|
||||
cur.execute(
|
||||
"""
|
||||
@@ -249,7 +257,7 @@ def register(
|
||||
WHERE guid = ?
|
||||
AND revoked_at IS NULL
|
||||
""",
|
||||
(now_iso, guid),
|
||||
(now_iso, record["guid"]),
|
||||
)
|
||||
record["ssl_key_fingerprint"] = fingerprint
|
||||
record["token_version"] = new_version
|
||||
@@ -257,7 +265,7 @@ def register(
|
||||
record["key_added_at"] = now_iso
|
||||
return record
|
||||
|
||||
resolved_hostname = _normalize_host(hostname, guid, cur)
|
||||
resolved_hostname = _normalize_host(hostname, guid_norm, cur)
|
||||
created_at = int(time.time())
|
||||
key_added_at = _iso(_now())
|
||||
cur.execute(
|
||||
@@ -269,7 +277,7 @@ def register(
|
||||
VALUES (?, ?, ?, ?, ?, 1, 'active', ?)
|
||||
""",
|
||||
(
|
||||
guid,
|
||||
guid_norm,
|
||||
resolved_hostname,
|
||||
created_at,
|
||||
created_at,
|
||||
@@ -278,7 +286,7 @@ def register(
|
||||
),
|
||||
)
|
||||
return {
|
||||
"guid": guid,
|
||||
"guid": guid_norm,
|
||||
"hostname": resolved_hostname,
|
||||
"token_version": 1,
|
||||
"status": "active",
|
||||
@@ -620,7 +628,7 @@ def register(
|
||||
return jsonify({"error": "proof_replayed"}), 409
|
||||
|
||||
# Finalize enrollment
|
||||
effective_guid = guid or str(uuid.uuid4())
|
||||
effective_guid = normalize_guid(guid) if guid else normalize_guid(str(uuid.uuid4()))
|
||||
now_iso = _iso(_now())
|
||||
|
||||
device_record = _ensure_device_record(cur, effective_guid, hostname_claimed, fingerprint)
|
||||
|
||||
26
Data/Server/Modules/guid_utils.py
Normal file
26
Data/Server/Modules/guid_utils.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import string
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def normalize_guid(value: Optional[str]) -> str:
|
||||
"""
|
||||
Canonicalize GUID strings so the server treats different casings/formats uniformly.
|
||||
"""
|
||||
candidate = (value or "").strip()
|
||||
if not candidate:
|
||||
return ""
|
||||
candidate = candidate.strip("{}")
|
||||
try:
|
||||
return str(uuid.UUID(candidate)).upper()
|
||||
except Exception:
|
||||
cleaned = "".join(ch for ch in candidate if ch in string.hexdigits or ch == "-")
|
||||
cleaned = cleaned.strip("-")
|
||||
if cleaned:
|
||||
try:
|
||||
return str(uuid.UUID(cleaned)).upper()
|
||||
except Exception:
|
||||
pass
|
||||
return candidate.upper()
|
||||
Reference in New Issue
Block a user