user-engine/src/user_engine/operations_status.py

144 lines
5.6 KiB
Python
Raw Normal View History

"""Bounded, non-sending diagnostics rendered only after platform authorization."""
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone
from html import escape
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
from urllib.error import HTTPError
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
return None
def sign_in_status(base):
try:
response = build_opener(NoRedirect()).open(
base.rstrip("/") + "/readyz", timeout=5
)
except HTTPError as exc:
response = exc
with response:
raw = response.read(16385)
if len(raw) > 16384:
raise ValueError("oversized status")
return json.loads(raw)
def check_services(oidc, delivery):
def safe(call):
try:
return call()
except Exception:
return {}
with ThreadPoolExecutor(max_workers=2) as pool:
sign = pool.submit(
safe, lambda: sign_in_status(oidc.backend_url) if oidc else {}
)
mail = pool.submit(
safe,
lambda: delivery.mail_status() if hasattr(delivery, "mail_status") else {},
)
sign, mail = sign.result(), mail.result()
values = {}
sign_checks = sign.get("checks", []) if isinstance(sign, dict) else []
for row in sign_checks if isinstance(sign_checks, list) else []:
if isinstance(row, dict):
values[row.get("name")] = row.get("status")
checks = [
(
"Directory sign-in",
values.get("lldap"),
"Sign-in and identity lookup",
"Retry the check; if it still fails, ask the identity service owner to restore directory connectivity.",
),
(
"Primary authentication",
values.get("authelia"),
"Fresh sign-in",
"Retry after the authentication service recovers. Existing sessions do not prove new sign-in works.",
),
(
"Authenticator provider",
values.get("privacyidea"),
"MFA verification and enrollment",
"Retry after the MFA service recovers. Do not disable MFA to work around an outage.",
),
(
"Factor-read credential",
values.get("factor_reader"),
"Enrollment decisions and MFA verification",
"Renewal is automatic. A failure can mean expiry, lost permissions or projection failure; ask the credential owner to run the scoped renewal check.",
),
]
statuses = mail.get("checks", {}) if isinstance(mail, dict) else {}
if not isinstance(statuses, dict):
statuses = {}
checks += [
(
"Mail record store",
statuses.get("database"),
"Durable delivery tracking",
"Restore the mail store before resuming delivery.",
),
(
"SMTP authentication",
statuses.get("smtp_authentication"),
"Invitation and verification mail",
"Check the configured mail lane. Use assisted password setup when the person cannot receive email.",
),
]
rows = ""
for name, status, impact, recovery in checks:
label = {
"ok": "Check passed",
"failed": "Unavailable",
"unconfigured": "Not configured",
}.get(status, "Unknown — check unavailable")
rows += (
"<tr><td>"
+ escape(name)
+ "</td><td>"
+ escape(label)
+ "</td><td>"
+ escape(impact)
+ "</td><td>"
+ escape(recovery)
+ "</td></tr>"
)
return (
"<section><h2>Checked service state</h2><p>Checked at "
+ escape(datetime.now(timezone.utc).isoformat(timespec="seconds"))
+ ". SMTP authentication may be cached for 30 seconds. These checks send no email and do not prove inbox receipt.</p><table><thead><tr><th>Component</th><th>State</th><th>Affected journeys</th><th>Recovery</th></tr></thead><tbody>"
+ rows
+ '</tbody></table><p><a href="/platform/operations">Check services again</a></p></section>'
)
def selected_mail_status(delivery, event):
if event.event_type not in {"family_member.invited", "family_invitation.resent"}:
return ""
try:
receipt = delivery.mail_delivery_status(event.event_id)
state = receipt.get("state") if isinstance(receipt, dict) else None
except Exception:
state = None
detail = {
"provider_accepted": "The mail provider accepted this event. This does not prove inbox receipt. Retrying this same record reconciles the remaining event delivery without submitting another invitation.",
"unknown": "The SMTP outcome is uncertain. Automatic resending is blocked. Ask the mail service owner to reconcile the provider record using this delivery ID; use assisted password setup if access is urgent.",
"failed": "The previous mail attempt definitely failed. Review a delivery attempt to retry this same record.",
"not_found": "No mail submission is recorded. Review a delivery attempt to submit this selected record.",
"unconfigured": "The mail lane is not configured. Use assisted password setup or restore the mail lane.",
}.get(
state,
"Mail evidence is unavailable. Check the mail service and retry this status check; do not infer that a message was sent or received.",
)
return (
"<section><h2>Selected mail evidence</h2><p>"
+ escape(detail)
+ "</p></section>"
)