#!/usr/bin/env python3 """HoloLake enterprise identity, relationship and responsibility receipt service. The service binds only to loopback. Nginx exposes exact routes. Credentials are verified against the local Forgejo API and are never stored or logged. """ from __future__ import annotations import base64 import http.cookiejar import hashlib import hmac import json import os import re import sqlite3 import time import urllib.error import urllib.parse import urllib.request import uuid from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path BIND = os.environ.get("GH_ENTERPRISE_IDENTITY_BIND", "127.0.0.1") PORT = int(os.environ.get("GH_ENTERPRISE_IDENTITY_PORT", "8032")) DB_PATH = os.environ.get( "GH_ENTERPRISE_IDENTITY_DB", "/var/lib/guanghu-enterprise-identity/identity.sqlite3", ) REGISTRY_PATH = os.environ.get( "GH_ENTERPRISE_IDENTITY_REGISTRY", "/etc/guanghu/enterprise-identity-registry.json", ) FORGEJO_USER_API = os.environ.get( "GH_ENTERPRISE_FORGEJO_USER_API", "http://127.0.0.1:3341/api/v1/user" ) FORGEJO_WEB_BASE = os.environ.get( "GH_ENTERPRISE_FORGEJO_WEB_BASE", "https://guanghu.chat/code" ).rstrip("/") FORGEJO_API_BASE = os.environ.get( "GH_ENTERPRISE_FORGEJO_API_BASE", "http://127.0.0.1:3341/api/v1" ).rstrip("/") RECEIPT_KEY = os.environ.get("GH_ENTERPRISE_RECEIPT_KEY", "") MAX_BODY = 16_384 USERNAME = re.compile(r"^[A-Za-z0-9_-]{1,40}$") DECISIONS = {"ACCEPT", "REJECT", "DEFER", "ACCEPT_WITH_CHANGES"} def now() -> int: return int(time.time()) def canonical(value: object) -> bytes: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode() def load_registry() -> dict: registry = json.loads(Path(REGISTRY_PATH).read_text(encoding="utf-8")) if registry.get("schema") != "guanghu.enterprise-identity-registry/v1": raise ValueError("enterprise identity registry schema invalid") humans = registry.get("humans") if not isinstance(humans, list) or not humans: raise ValueError("enterprise identity registry is empty") numbers = [item.get("human_number") for item in humans] usernames = [item.get("username") for item in humans] if len(numbers) != len(set(numbers)) or len(usernames) != len(set(usernames)): raise ValueError("enterprise identity registry identities must be unique") for item in humans: if not USERNAME.fullmatch(str(item.get("username", ""))): raise ValueError("enterprise username invalid") for persona in item.get("personas", []): if persona.get("species") != "AGE" or str(persona.get("current_persona_identity", "")).startswith("AGE-"): raise ValueError("AGE is a species and cannot be used as a persona identity number") return registry def database() -> sqlite3.Connection: path = Path(DB_PATH) path.parent.mkdir(parents=True, exist_ok=True) db = sqlite3.connect(path) db.row_factory = sqlite3.Row db.executescript( """ PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; CREATE TABLE IF NOT EXISTS relationship_receipts ( receipt_id TEXT PRIMARY KEY, idempotency_key TEXT NOT NULL UNIQUE, human_number TEXT NOT NULL, username TEXT NOT NULL, registry_version TEXT NOT NULL, decision TEXT NOT NULL, observed_at INTEGER NOT NULL, receipt_hash TEXT NOT NULL, receipt_signature TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS responsibility_receipts ( receipt_id TEXT PRIMARY KEY, idempotency_key TEXT NOT NULL UNIQUE, human_number TEXT NOT NULL, username TEXT NOT NULL, responsibility_domain TEXT NOT NULL, responsibility_version TEXT NOT NULL, decision TEXT NOT NULL, note TEXT NOT NULL, observed_at INTEGER NOT NULL, receipt_hash TEXT NOT NULL, receipt_signature TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS credential_rotation_receipts ( receipt_id TEXT PRIMARY KEY, human_number TEXT NOT NULL, username TEXT NOT NULL, observed_at INTEGER NOT NULL, receipt_hash TEXT NOT NULL, receipt_signature TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS audit ( sequence INTEGER PRIMARY KEY AUTOINCREMENT, observed_at INTEGER NOT NULL, kind TEXT NOT NULL, human_number_hash TEXT NOT NULL, receipt_id TEXT NOT NULL ); """ ) return db def find_human(registry: dict, number: str) -> dict | None: encoded = number.encode("utf-8") return next( ( item for item in registry["humans"] if hmac.compare_digest(item["human_number"].encode("utf-8"), encoded) ), None, ) def parse_basic(header: str) -> tuple[str, str] | None: if not header.startswith("Basic "): return None try: decoded = base64.b64decode(header[6:], validate=True).decode("utf-8") username, password = decoded.split(":", 1) except (ValueError, UnicodeDecodeError): return None if not USERNAME.fullmatch(username) or not password or len(password) > 512: return None return username, password def verify_forgejo(username: str, password: str) -> bool: request = urllib.request.Request(FORGEJO_USER_API) credential = base64.b64encode(f"{username}:{password}".encode()).decode() request.add_header("Authorization", f"Basic {credential}") request.add_header("Accept", "application/json") try: with urllib.request.urlopen(request, timeout=10) as response: body = json.load(response) return response.status == 200 and hmac.compare_digest(str(body.get("login", "")), username) except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError, ValueError): return False def rotate_forgejo_password(username: str, current_password: str, new_password: str) -> bool: """Use Forgejo's own first-login session to rotate a forced-change password. This needs no standing admin token: the old credential opens a normal user session and Forgejo itself admits only the forced password-change form. """ jar = http.cookiejar.CookieJar() opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar)) try: opener.open(FORGEJO_WEB_BASE + "/user/login", timeout=15).read() login = urllib.parse.urlencode( {"user_name": username, "password": current_password} ).encode() login_request = urllib.request.Request( FORGEJO_WEB_BASE + "/user/login", data=login ) login_request.add_header("Content-Type", "application/x-www-form-urlencoded") with opener.open(login_request, timeout=15) as response: response.read() if not urllib.parse.urlparse(response.geturl()).path.endswith( "/user/settings/change_password" ): return False change = urllib.parse.urlencode( {"password": new_password, "retype": new_password} ).encode() change_request = urllib.request.Request( FORGEJO_WEB_BASE + "/user/settings/change_password", data=change ) change_request.add_header("Content-Type", "application/x-www-form-urlencoded") with opener.open(change_request, timeout=15) as response: response.read() return verify_forgejo(username, new_password) except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError, ValueError): return False def signed_receipt(payload: dict) -> dict: if len(RECEIPT_KEY) < 32: raise RuntimeError("receipt signing key unavailable") body = canonical(payload) receipt_hash = hashlib.sha256(body).hexdigest() signature = hmac.new(RECEIPT_KEY.encode(), body, hashlib.sha256).hexdigest() return {**payload, "receipt_hash": receipt_hash, "receipt_signature": signature} def stable_receipt_id(prefix: str, human_number: str, idempotency_key: str) -> str: """Keep one receipt path across safe client retries without exposing the key.""" material = f"{prefix}\n{human_number}\n{idempotency_key}".encode() digest = hmac.new(RECEIPT_KEY.encode(), material, hashlib.sha256).hexdigest()[:32] return f"{prefix}-{digest.upper()}" def repository_receipt_path(kind: str, receipt_id: str) -> str: if kind not in {"relationship", "responsibility"} or not re.fullmatch( r"GH-(?:REL|RESP)-[A-F0-9]{32}", receipt_id ): raise ValueError("repository receipt path input invalid") return f".guanghu/receipts/{kind}/{receipt_id}.json" def project_receipt_to_repository( human: dict, username: str, password: str, kind: str, receipt: dict ) -> dict: """Commit a signed receipt with the human's own Forgejo authority. No administrator token or server-side repository credential is held. A retry that finds the deterministic path already present must read back the exact bytes before treating the projection as idempotent. """ repository = str(human["repository"]) if repository.split("/", 1)[0] != username: raise RuntimeError("repository owner does not match authenticated user") path = repository_receipt_path(kind, str(receipt["receipt_id"])) endpoint = ( f"{FORGEJO_API_BASE}/repos/{urllib.parse.quote(repository, safe='/')}" f"/contents/{urllib.parse.quote(path, safe='/')}" ) content = canonical(receipt) + b"\n" authorization = "Basic " + base64.b64encode(f"{username}:{password}".encode()).decode() create_body = canonical( { "branch": "main", "content": base64.b64encode(content).decode(), "message": f"receipt({kind}): {receipt['receipt_id']}", } ) request = urllib.request.Request(endpoint, data=create_body, method="POST") request.add_header("Authorization", authorization) request.add_header("Accept", "application/json") request.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(request, timeout=15) as response: result = json.load(response) if response.status != 201: raise RuntimeError(f"repository projection returned {response.status}") commit = str(result.get("commit", {}).get("sha", "")) if not re.fullmatch(r"[0-9a-f]{40,64}", commit): raise RuntimeError("repository projection commit missing") return { "state": "COMMITTED", "repository": repository, "path": path, "commit": commit, } except urllib.error.HTTPError as error: if error.code != 422: raise RuntimeError(f"repository projection failed: {error.code}") from error read_request = urllib.request.Request(endpoint + "?ref=main") read_request.add_header("Authorization", authorization) read_request.add_header("Accept", "application/json") try: with urllib.request.urlopen(read_request, timeout=15) as response: existing = json.load(response) except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError, ValueError) as error: raise RuntimeError("repository projection readback failed") from error try: existing_content = base64.b64decode(str(existing["content"]), validate=True) except (KeyError, ValueError) as error: raise RuntimeError("repository projection readback invalid") from error if not hmac.compare_digest(existing_content, content): raise RuntimeError("repository receipt path already contains different bytes") return { "state": "IDEMPOTENT_READBACK", "repository": repository, "path": path, "commit": str(existing.get("sha", "")), } def public_projection(registry: dict, human: dict, db: sqlite3.Connection | None = None) -> dict: projection = { "status": "RESOLVED", "canonical_id": human["human_number"], "subject": { "id": human["human_number"], "name": human["display_name"], "domain": human["responsibility_domain"], }, "work_entry": { "domain": registry["work_entry_domain"], "channel": registry["work_entry_channel"], }, "repository_binding": { "host": "guanghu.chat", "username": human["username"], "repository": human["repository"], "private": True, }, "persona_relationships": human["personas"], "persona_identity_governance": registry["persona_identity_governance"], "registry_version": registry["version"], } if db is not None: relationship = db.execute( "SELECT decision,observed_at,receipt_hash FROM relationship_receipts WHERE human_number=? ORDER BY observed_at DESC LIMIT 1", (human["human_number"],), ).fetchone() responsibility = db.execute( "SELECT decision,observed_at,receipt_hash,responsibility_version FROM responsibility_receipts WHERE human_number=? ORDER BY observed_at DESC LIMIT 1", (human["human_number"],), ).fetchone() projection["relationship_confirmation"] = dict(relationship) if relationship else None projection["responsibility_receipt"] = dict(responsibility) if responsibility else None return projection class Handler(BaseHTTPRequestHandler): server_version = "GuanghuEnterpriseIdentity/1.0" def log_message(self, fmt: str, *args: object) -> None: # Never include headers or request bodies in logs. print("[enterprise-identity] " + fmt % args) def respond(self, status: int, body: dict) -> None: encoded = json.dumps(body, ensure_ascii=False).encode() self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(encoded))) self.send_header("Cache-Control", "no-store") self.send_header("X-Content-Type-Options", "nosniff") self.end_headers() self.wfile.write(encoded) def body(self) -> dict: length = int(self.headers.get("Content-Length", "0")) if length < 1 or length > MAX_BODY: raise ValueError("request body size invalid") value = json.loads(self.rfile.read(length).decode()) if not isinstance(value, dict): raise ValueError("JSON object required") return value def authenticated_human(self, registry: dict, payload: dict) -> tuple[dict, str, str] | None: credentials = parse_basic(self.headers.get("Authorization", "")) number = str(payload.get("human_number", "")) human = find_human(registry, number) if not credentials or not human: return None username, password = credentials if not hmac.compare_digest(username, human["username"]) or not verify_forgejo(username, password): return None return human, username, password def do_GET(self) -> None: try: registry = load_registry() except (OSError, ValueError, json.JSONDecodeError) as error: return self.respond(503, {"ok": False, "error": f"registry unavailable: {error}"}) parsed = urllib.parse.urlparse(self.path) if parsed.path == "/health": return self.respond(200, {"ok": True, "service": "guanghu-enterprise-identity", "registry_version": registry["version"], "age_species": "AGE", "valid_age_individual_numbers": 0}) if parsed.path == "/v1/resolve": number = urllib.parse.parse_qs(parsed.query).get("id", [""])[0] human = find_human(registry, number) return self.respond(200 if human else 404, public_projection(registry, human) if human else {"status": "NOT_FOUND"}) return self.respond(404, {"ok": False, "error": "not found"}) def do_POST(self) -> None: try: registry = load_registry() payload = self.body() except (OSError, ValueError, json.JSONDecodeError) as error: return self.respond(400, {"ok": False, "error": str(error)}) if self.path == "/v1/change-password": credentials = parse_basic(self.headers.get("Authorization", "")) human = find_human(registry, str(payload.get("human_number", ""))) new_password = str(payload.get("new_password", "")) if not credentials or not human: return self.respond(401, {"ok": False, "error": "enterprise account authentication failed"}) username, current_password = credentials if not hmac.compare_digest(username, human["username"]): return self.respond(401, {"ok": False, "error": "enterprise account authentication failed"}) if ( len(new_password) < 14 or len(new_password) > 128 or hmac.compare_digest(current_password, new_password) or new_password.isdigit() or new_password.isalpha() ): return self.respond(400, {"ok": False, "error": "new password does not meet the first-login policy"}) if not rotate_forgejo_password(username, current_password, new_password): return self.respond(401, {"ok": False, "error": "first-login password rotation failed"}) observed = now() receipt_id = "GH-CRED-" + uuid.uuid4().hex.upper() receipt = signed_receipt({ "receipt_id": receipt_id, "human_number": human["human_number"], "username": username, "observed_at": observed, "password_changed": True, }) db = database() try: db.execute( "INSERT INTO credential_rotation_receipts VALUES (?,?,?,?,?,?)", (receipt_id, human["human_number"], username, observed, receipt["receipt_hash"], receipt["receipt_signature"]), ) db.execute( "INSERT INTO audit(observed_at,kind,human_number_hash,receipt_id) VALUES (?,?,?,?)", (observed, "CREDENTIAL_ROTATION", hashlib.sha256(human["human_number"].encode()).hexdigest(), receipt_id), ) db.commit() finally: db.close() return self.respond(200, {"ok": True, "receipt": receipt}) authenticated = self.authenticated_human(registry, payload) if not authenticated: return self.respond(401, {"ok": False, "error": "enterprise account authentication failed"}) human, username, password = authenticated idempotency_key = str(payload.get("idempotency_key", "")) if not re.fullmatch(r"[A-Za-z0-9_-]{16,96}", idempotency_key): return self.respond(400, {"ok": False, "error": "valid idempotency_key required"}) db = database() try: if self.path == "/v1/relationship-confirmations": decision = str(payload.get("decision", "")) if decision not in {"CONFIRM", "REJECT"}: return self.respond(400, {"ok": False, "error": "relationship decision invalid"}) existing = db.execute("SELECT * FROM relationship_receipts WHERE idempotency_key=?", (idempotency_key,)).fetchone() if existing: return self.respond(200, {"ok": True, "idempotent": True, "receipt": dict(existing)}) observed = now() receipt_id = stable_receipt_id("GH-REL", human["human_number"], idempotency_key) receipt = signed_receipt({"receipt_id":receipt_id,"human_number":human["human_number"],"username":username,"registry_version":registry["version"],"decision":decision,"observed_at":observed}) try: projection = project_receipt_to_repository(human, username, password, "relationship", receipt) except RuntimeError as error: return self.respond(503, {"ok": False, "error": str(error)}) db.execute("INSERT INTO relationship_receipts VALUES (?,?,?,?,?,?,?,?,?)", (receipt_id,idempotency_key,human["human_number"],username,registry["version"],decision,observed,receipt["receipt_hash"],receipt["receipt_signature"])) db.execute("INSERT INTO audit(observed_at,kind,human_number_hash,receipt_id) VALUES (?,?,?,?)", (observed,"RELATIONSHIP_CONFIRMATION",hashlib.sha256(human["human_number"].encode()).hexdigest(),receipt_id)) db.commit() return self.respond(201, {"ok": True, "receipt": receipt, "repository_projection": projection}) if self.path == "/v1/responsibility-receipts": decision = str(payload.get("decision", "")) note = str(payload.get("note", ""))[:1000] version = str(payload.get("responsibility_version", "")) if decision not in DECISIONS or not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}\.[0-9]+", version): return self.respond(400, {"ok": False, "error": "responsibility decision or version invalid"}) existing = db.execute("SELECT * FROM responsibility_receipts WHERE idempotency_key=?", (idempotency_key,)).fetchone() if existing: return self.respond(200, {"ok": True, "idempotent": True, "receipt": dict(existing)}) observed = now() receipt_id = stable_receipt_id("GH-RESP", human["human_number"], idempotency_key) receipt = signed_receipt({"receipt_id":receipt_id,"human_number":human["human_number"],"username":username,"responsibility_domain":human["responsibility_domain"],"responsibility_version":version,"decision":decision,"note":note,"observed_at":observed}) try: projection = project_receipt_to_repository(human, username, password, "responsibility", receipt) except RuntimeError as error: return self.respond(503, {"ok": False, "error": str(error)}) db.execute("INSERT INTO responsibility_receipts VALUES (?,?,?,?,?,?,?,?,?,?,?)", (receipt_id,idempotency_key,human["human_number"],username,human["responsibility_domain"],version,decision,note,observed,receipt["receipt_hash"],receipt["receipt_signature"])) db.execute("INSERT INTO audit(observed_at,kind,human_number_hash,receipt_id) VALUES (?,?,?,?)", (observed,"RESPONSIBILITY_RECEIPT",hashlib.sha256(human["human_number"].encode()).hexdigest(),receipt_id)) db.commit() return self.respond(201, {"ok": True, "receipt": receipt, "repository_projection": projection}) if self.path == "/v1/me/entry": return self.respond(200, {"ok": True, "entry": public_projection(registry, human, db)}) return self.respond(404, {"ok": False, "error": "not found"}) finally: db.close() if __name__ == "__main__": load_registry() if len(RECEIPT_KEY) < 32: raise SystemExit("GH_ENTERPRISE_RECEIPT_KEY must contain at least 32 characters") ThreadingHTTPServer((BIND, PORT), Handler).serve_forever()