comms: use synapse admin token

This commit is contained in:
Brad Stein 2026-01-27 04:49:15 -03:00
parent b3386c633a
commit 00aaf23b54
3 changed files with 24 additions and 11 deletions

View File

@ -102,6 +102,10 @@ class CommsService:
def _client(self) -> httpx.Client: def _client(self) -> httpx.Client:
return self._client_factory(timeout=settings.comms_timeout_sec) return self._client_factory(timeout=settings.comms_timeout_sec)
def _admin_token(self, fallback: str) -> str:
token = settings.comms_synapse_admin_token
return token if token else fallback
def _mas_admin_token(self, client: httpx.Client) -> str: def _mas_admin_token(self, client: httpx.Client) -> str:
if not settings.comms_mas_admin_client_id or not settings.comms_mas_admin_client_secret: if not settings.comms_mas_admin_client_id or not settings.comms_mas_admin_client_secret:
raise RuntimeError("mas admin client credentials missing") raise RuntimeError("mas admin client credentials missing")
@ -218,13 +222,14 @@ class CommsService:
def _synapse_list_users(self, client: httpx.Client, token: str) -> list[dict[str, Any]]: def _synapse_list_users(self, client: httpx.Client, token: str) -> list[dict[str, Any]]:
users: list[dict[str, Any]] = [] users: list[dict[str, Any]] = []
from_token = None from_token = None
admin_token = self._admin_token(token)
while True: while True:
url = "{}/_synapse/admin/v2/users?local=true&deactivated=false&limit=100".format( url = "{}/_synapse/admin/v2/users?local=true&deactivated=false&limit=100".format(
settings.comms_synapse_base settings.comms_synapse_base
) )
if from_token: if from_token:
url += f"&from={urllib.parse.quote(from_token)}" url += f"&from={urllib.parse.quote(from_token)}"
resp = client.get(url, headers=_auth(token)) resp = client.get(url, headers=_auth(admin_token))
resp.raise_for_status() resp.raise_for_status()
payload = resp.json() payload = resp.json()
users.extend([item for item in payload.get("users", []) if isinstance(item, dict)]) users.extend([item for item in payload.get("users", []) if isinstance(item, dict)])
@ -247,10 +252,11 @@ class CommsService:
return now_ms - last_seen > stale_ms return now_ms - last_seen > stale_ms
def _prune_guest(self, client: httpx.Client, token: str, user_id: str) -> bool: def _prune_guest(self, client: httpx.Client, token: str, user_id: str) -> bool:
admin_token = self._admin_token(token)
try: try:
resp = client.delete( resp = client.delete(
f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}", f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}",
headers=_auth(token), headers=_auth(admin_token),
params={"erase": "true"}, params={"erase": "true"},
) )
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
@ -280,9 +286,10 @@ class CommsService:
return resp.json().get("displayname") return resp.json().get("displayname")
def _get_displayname_admin(self, client: httpx.Client, token: str, user_id: str) -> str | None: def _get_displayname_admin(self, client: httpx.Client, token: str, user_id: str) -> str | None:
admin_token = self._admin_token(token)
resp = client.get( resp = client.get(
f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}", f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}",
headers=_auth(token), headers=_auth(admin_token),
) )
if resp.status_code == HTTP_NOT_FOUND: if resp.status_code == HTTP_NOT_FOUND:
return None return None
@ -314,9 +321,10 @@ class CommsService:
) )
def _set_displayname_admin(self, client: httpx.Client, token: str, user_id: str, name: str) -> bool: def _set_displayname_admin(self, client: httpx.Client, token: str, user_id: str, name: str) -> bool:
admin_token = self._admin_token(token)
resp = client.put( resp = client.put(
f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}", f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}",
headers=_auth(token), headers=_auth(admin_token),
json={"displayname": name}, json={"displayname": name},
) )
return resp.status_code in (HTTP_OK, HTTP_CREATED, HTTP_NO_CONTENT) return resp.status_code in (HTTP_OK, HTTP_CREATED, HTTP_NO_CONTENT)
@ -843,13 +851,14 @@ class CommsService:
} }
def _ensure_user(self, client: httpx.Client, token: str, localpart: str, password: str, admin: bool) -> None: def _ensure_user(self, client: httpx.Client, token: str, localpart: str, password: str, admin: bool) -> None:
admin_token = self._admin_token(token)
user_id = _canon_user(localpart, settings.comms_server_name) user_id = _canon_user(localpart, settings.comms_server_name)
url = f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}" url = f"{settings.comms_synapse_base}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}"
resp = client.get(url, headers=_auth(token)) resp = client.get(url, headers=_auth(admin_token))
if resp.status_code == HTTP_OK: if resp.status_code == HTTP_OK:
return return
payload = {"password": password, "admin": admin, "deactivated": False} payload = {"password": password, "admin": admin, "deactivated": False}
create = client.put(url, headers=_auth(token), json=payload) create = client.put(url, headers=_auth(admin_token), json=payload)
if create.status_code not in (HTTP_OK, HTTP_CREATED): if create.status_code not in (HTTP_OK, HTTP_CREATED):
raise RuntimeError(f"create user {user_id} failed: {create.status_code} {create.text}") raise RuntimeError(f"create user {user_id} failed: {create.status_code} {create.text}")
@ -906,20 +915,22 @@ class CommsService:
return room_id return room_id
def _join_user(self, client: httpx.Client, token: str, room_id: str, user_id: str) -> None: def _join_user(self, client: httpx.Client, token: str, room_id: str, user_id: str) -> None:
admin_token = self._admin_token(token)
client.post( client.post(
f"{settings.comms_synapse_base}/_synapse/admin/v1/join/{urllib.parse.quote(room_id)}", f"{settings.comms_synapse_base}/_synapse/admin/v1/join/{urllib.parse.quote(room_id)}",
headers=_auth(token), headers=_auth(admin_token),
json={"user_id": user_id}, json={"user_id": user_id},
) )
def _join_all_locals(self, client: httpx.Client, token: str, room_id: str) -> None: def _join_all_locals(self, client: httpx.Client, token: str, room_id: str) -> None:
users: list[str] = [] users: list[str] = []
from_token = None from_token = None
admin_token = self._admin_token(token)
while True: while True:
url = f"{settings.comms_synapse_base}/_synapse/admin/v2/users?local=true&deactivated=false&limit=100" url = f"{settings.comms_synapse_base}/_synapse/admin/v2/users?local=true&deactivated=false&limit=100"
if from_token: if from_token:
url += f"&from={from_token}" url += f"&from={from_token}"
resp = client.get(url, headers=_auth(token)) resp = client.get(url, headers=_auth(admin_token))
payload = resp.json() payload = resp.json()
users.extend([u["name"] for u in payload.get("users", []) if isinstance(u, dict) and u.get("name")]) users.extend([u["name"] for u in payload.get("users", []) if isinstance(u, dict) and u.get("name")])
from_token = payload.get("next_token") from_token = payload.get("next_token")

View File

@ -166,7 +166,7 @@ _K8S_ROLES: list[dict[str, str]] = [
"namespace": "maintenance", "namespace": "maintenance",
"service_accounts": "ariadne,maintenance-vault-sync", "service_accounts": "ariadne,maintenance-vault-sync",
"read_paths": "maintenance/ariadne-db portal/bstein-dev-home-keycloak-admin mailu/mailu-db-secret " "read_paths": "maintenance/ariadne-db portal/bstein-dev-home-keycloak-admin mailu/mailu-db-secret "
"mailu/mailu-initial-account-secret shared/harbor-pull", "mailu/mailu-initial-account-secret comms/synapse-admin shared/harbor-pull",
"write_paths": "", "write_paths": "",
}, },
{ {
@ -225,8 +225,8 @@ _K8S_ROLES: list[dict[str, str]] = [
"service_accounts": "comms-secrets-ensure,mas-db-ensure,mas-admin-client-secret-writer,othrys-synapse-signingkey-job", "service_accounts": "comms-secrets-ensure,mas-db-ensure,mas-admin-client-secret-writer,othrys-synapse-signingkey-job",
"read_paths": "", "read_paths": "",
"write_paths": "comms/turn-shared-secret comms/livekit-api comms/synapse-redis comms/synapse-macaroon " "write_paths": "comms/turn-shared-secret comms/livekit-api comms/synapse-redis comms/synapse-macaroon "
"comms/atlasbot-credentials-runtime comms/synapse-db comms/mas-db comms/mas-admin-client-runtime " "comms/atlasbot-credentials-runtime comms/synapse-db comms/synapse-admin comms/synapse-registration "
"comms/mas-secrets-runtime comms/othrys-synapse-signingkey", "comms/mas-db comms/mas-admin-client-runtime comms/mas-secrets-runtime comms/othrys-synapse-signingkey",
}, },
] ]

View File

@ -152,6 +152,7 @@ class Settings:
comms_synapse_db_name: str comms_synapse_db_name: str
comms_synapse_db_user: str comms_synapse_db_user: str
comms_synapse_db_password: str comms_synapse_db_password: str
comms_synapse_admin_token: str
comms_timeout_sec: float comms_timeout_sec: float
comms_guest_stale_days: int comms_guest_stale_days: int
@ -403,6 +404,7 @@ class Settings:
"comms_synapse_db_name": _env("COMMS_SYNAPSE_DB_NAME", "synapse"), "comms_synapse_db_name": _env("COMMS_SYNAPSE_DB_NAME", "synapse"),
"comms_synapse_db_user": _env("COMMS_SYNAPSE_DB_USER", "synapse"), "comms_synapse_db_user": _env("COMMS_SYNAPSE_DB_USER", "synapse"),
"comms_synapse_db_password": _env("COMMS_SYNAPSE_DB_PASSWORD", ""), "comms_synapse_db_password": _env("COMMS_SYNAPSE_DB_PASSWORD", ""),
"comms_synapse_admin_token": _env("COMMS_SYNAPSE_ADMIN_TOKEN", ""),
"comms_timeout_sec": _env_float("COMMS_TIMEOUT_SEC", 30.0), "comms_timeout_sec": _env_float("COMMS_TIMEOUT_SEC", 30.0),
"comms_guest_stale_days": _env_int("COMMS_GUEST_STALE_DAYS", 14), "comms_guest_stale_days": _env_int("COMMS_GUEST_STALE_DAYS", 14),
} }