238 lines
8.8 KiB
Python
238 lines
8.8 KiB
Python
from __future__ import annotations
|
|
|
|
import socket
|
|
import time
|
|
from urllib.parse import quote
|
|
from typing import Any
|
|
|
|
import httpx
|
|
from flask import jsonify, g, request
|
|
|
|
from .. import settings
|
|
from .. import ariadne_client
|
|
from ..db import connect
|
|
from ..keycloak import admin_client, require_auth, require_account_access
|
|
from ..nextcloud_mail_sync import trigger as trigger_nextcloud_mail_sync
|
|
from ..utils import random_password
|
|
from ..firefly_user_sync import trigger as trigger_firefly_user_sync
|
|
from ..wger_user_sync import trigger as trigger_wger_user_sync
|
|
|
|
|
|
def _tcp_check(host: str, port: int, timeout_sec: float) -> bool:
|
|
if not host or port <= 0:
|
|
return False
|
|
try:
|
|
with socket.create_connection((host, port), timeout=timeout_sec):
|
|
return True
|
|
except OSError:
|
|
return False
|
|
|
|
|
|
def register_account_actions(app) -> None:
|
|
"""Register account mutation and admin-action endpoints."""
|
|
|
|
@app.route("/api/account/mailu/rotate", methods=["POST"])
|
|
@require_auth
|
|
def account_mailu_rotate() -> Any:
|
|
ok, resp = require_account_access()
|
|
if not ok:
|
|
return resp
|
|
if ariadne_client.enabled():
|
|
return ariadne_client.proxy("POST", "/api/account/mailu/rotate")
|
|
if not admin_client().ready():
|
|
return jsonify({"error": "server not configured"}), 503
|
|
|
|
username = g.keycloak_username
|
|
if not username:
|
|
return jsonify({"error": "missing username"}), 400
|
|
|
|
password = random_password()
|
|
try:
|
|
admin_client().set_user_attribute(username, "mailu_app_password", password)
|
|
except Exception:
|
|
return jsonify({"error": "failed to update mail password"}), 502
|
|
|
|
sync_enabled = bool(settings.MAILU_SYNC_URL)
|
|
sync_ok = False
|
|
sync_error = ""
|
|
if sync_enabled:
|
|
try:
|
|
with httpx.Client(timeout=30) as client:
|
|
resp = client.post(
|
|
settings.MAILU_SYNC_URL,
|
|
json={"ts": int(time.time()), "wait": True, "reason": "portal_mailu_rotate"},
|
|
)
|
|
sync_ok = resp.status_code == 200
|
|
if not sync_ok:
|
|
sync_error = f"sync status {resp.status_code}"
|
|
except Exception:
|
|
sync_error = "sync request failed"
|
|
|
|
nextcloud_sync: dict[str, Any] = {"status": "skipped"}
|
|
try:
|
|
nextcloud_sync = trigger_nextcloud_mail_sync(username, wait=True)
|
|
except Exception:
|
|
nextcloud_sync = {"status": "error"}
|
|
|
|
return jsonify(
|
|
{
|
|
"password": password,
|
|
"sync_enabled": sync_enabled,
|
|
"sync_ok": sync_ok,
|
|
"sync_error": sync_error,
|
|
"nextcloud_sync": nextcloud_sync,
|
|
}
|
|
)
|
|
|
|
@app.route("/api/account/wger/reset", methods=["POST"])
|
|
@require_auth
|
|
def account_wger_reset() -> Any:
|
|
ok, resp = require_account_access()
|
|
if not ok:
|
|
return resp
|
|
if ariadne_client.enabled():
|
|
return ariadne_client.proxy("POST", "/api/account/wger/reset")
|
|
if not admin_client().ready():
|
|
return jsonify({"error": "server not configured"}), 503
|
|
|
|
username = g.keycloak_username
|
|
if not username:
|
|
return jsonify({"error": "missing username"}), 400
|
|
|
|
keycloak_email = g.keycloak_email or ""
|
|
mailu_email = ""
|
|
try:
|
|
user = admin_client().find_user(username) or {}
|
|
attrs = user.get("attributes") if isinstance(user, dict) else None
|
|
if isinstance(attrs, dict):
|
|
raw_mailu = attrs.get("mailu_email")
|
|
if isinstance(raw_mailu, list) and raw_mailu:
|
|
mailu_email = str(raw_mailu[0])
|
|
elif isinstance(raw_mailu, str) and raw_mailu:
|
|
mailu_email = raw_mailu
|
|
except Exception:
|
|
pass
|
|
|
|
email = mailu_email or f"{username}@{settings.MAILU_DOMAIN}"
|
|
password = random_password()
|
|
|
|
try:
|
|
result = trigger_wger_user_sync(username, email, password, wait=True)
|
|
status_val = result.get("status") if isinstance(result, dict) else "error"
|
|
if status_val != "ok":
|
|
raise RuntimeError(f"wger sync {status_val}")
|
|
except Exception as exc:
|
|
message = str(exc).strip() or "wger sync failed"
|
|
return jsonify({"error": message}), 502
|
|
|
|
try:
|
|
admin_client().set_user_attribute(username, "wger_password", password)
|
|
admin_client().set_user_attribute(
|
|
username,
|
|
"wger_password_updated_at",
|
|
time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
)
|
|
except Exception:
|
|
return jsonify({"error": "failed to store wger password"}), 502
|
|
|
|
return jsonify({"status": "ok", "password": password})
|
|
|
|
@app.route("/api/account/wger/rotation/check", methods=["POST"])
|
|
@require_auth
|
|
def account_wger_rotation_check() -> Any:
|
|
ok, resp = require_account_access()
|
|
if not ok:
|
|
return resp
|
|
if ariadne_client.enabled():
|
|
return ariadne_client.proxy("POST", "/api/account/wger/rotation/check")
|
|
return jsonify({"error": "server not configured"}), 503
|
|
|
|
@app.route("/api/account/firefly/reset", methods=["POST"])
|
|
@require_auth
|
|
def account_firefly_reset() -> Any:
|
|
ok, resp = require_account_access()
|
|
if not ok:
|
|
return resp
|
|
if ariadne_client.enabled():
|
|
return ariadne_client.proxy("POST", "/api/account/firefly/reset")
|
|
if not admin_client().ready():
|
|
return jsonify({"error": "server not configured"}), 503
|
|
|
|
username = g.keycloak_username
|
|
if not username:
|
|
return jsonify({"error": "missing username"}), 400
|
|
|
|
keycloak_email = g.keycloak_email or ""
|
|
mailu_email = ""
|
|
try:
|
|
user = admin_client().find_user(username) or {}
|
|
attrs = user.get("attributes") if isinstance(user, dict) else None
|
|
if isinstance(attrs, dict):
|
|
raw_mailu = attrs.get("mailu_email")
|
|
if isinstance(raw_mailu, list) and raw_mailu:
|
|
mailu_email = str(raw_mailu[0])
|
|
elif isinstance(raw_mailu, str) and raw_mailu:
|
|
mailu_email = raw_mailu
|
|
except Exception:
|
|
pass
|
|
|
|
email = mailu_email or f"{username}@{settings.MAILU_DOMAIN}"
|
|
password = random_password(24)
|
|
|
|
try:
|
|
result = trigger_firefly_user_sync(username, email, password, wait=True)
|
|
status_val = result.get("status") if isinstance(result, dict) else "error"
|
|
if status_val != "ok":
|
|
raise RuntimeError(f"firefly sync {status_val}")
|
|
except Exception as exc:
|
|
message = str(exc).strip() or "firefly sync failed"
|
|
return jsonify({"error": message}), 502
|
|
|
|
try:
|
|
admin_client().set_user_attribute(username, "firefly_password", password)
|
|
admin_client().set_user_attribute(
|
|
username,
|
|
"firefly_password_updated_at",
|
|
time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
)
|
|
except Exception:
|
|
return jsonify({"error": "failed to store firefly password"}), 502
|
|
|
|
return jsonify({"status": "ok", "password": password})
|
|
|
|
@app.route("/api/account/firefly/rotation/check", methods=["POST"])
|
|
@require_auth
|
|
def account_firefly_rotation_check() -> Any:
|
|
ok, resp = require_account_access()
|
|
if not ok:
|
|
return resp
|
|
if ariadne_client.enabled():
|
|
return ariadne_client.proxy("POST", "/api/account/firefly/rotation/check")
|
|
return jsonify({"error": "server not configured"}), 503
|
|
|
|
@app.route("/api/account/nextcloud/mail/sync", methods=["POST"])
|
|
@require_auth
|
|
def account_nextcloud_mail_sync() -> Any:
|
|
ok, resp = require_account_access()
|
|
if not ok:
|
|
return resp
|
|
if ariadne_client.enabled():
|
|
payload = request.get_json(silent=True) or {}
|
|
return ariadne_client.proxy("POST", "/api/account/nextcloud/mail/sync", payload=payload)
|
|
if not admin_client().ready():
|
|
return jsonify({"error": "server not configured"}), 503
|
|
|
|
username = g.keycloak_username
|
|
if not username:
|
|
return jsonify({"error": "missing username"}), 400
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
wait = bool(payload.get("wait", True))
|
|
|
|
try:
|
|
result = trigger_nextcloud_mail_sync(username, wait=wait)
|
|
return jsonify(result)
|
|
except Exception as exc:
|
|
message = str(exc).strip() or "failed to sync nextcloud mail"
|
|
return jsonify({"error": message}), 502
|