Files
inwx-scripts/inwx_common.py
T
maier 3a84aea64d Session-Caching: auch Code 2002 als abgelaufene Session behandeln
Abgelaufene INWX-Sessions liefern beim ersten Aufruf teils Code 2002
("Command use error") statt 2200. call_api() loest die automatische
Neuanmeldung nun auch fuer 2001/2002/2003 aus, sodass der Cache sich
selbst heilt und kein manueller Loeschvorgang mehr noetig ist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 19:36:53 +02:00

174 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Gemeinsame Helfer für die INWX-Skripte.
Kapselt das Laden der Zugangsdaten aus KeePassXC sowie den Login mit
Session-Caching: Nach dem ersten Login wird das Session-Cookie von INWX
lokal zwischengespeichert. Folgeaufrufe nutzen diese Session wieder und
benötigen daher weder das KeePass-Master-Passwort noch einen neuen Login,
solange die Session bei INWX noch gültig ist.
"""
import json
import os
import subprocess
import sys
import requests
# Import-Fix für die Library-Struktur (Linux/Mac)
try:
from INWX.Domrobot import ApiClient
except ImportError:
from inwx.Domrobot import ApiClient
# --- KONFIGURATION ---
API_URL = 'https://api.domrobot.com'
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
KP_DB_PATH = os.path.join(BASE_DIR, "hosting.kdbx")
KP_ENTRY_NAME = "inwx"
# Datei mit der zwischengespeicherten Session (enthält nur ein kurzlebiges
# Session-Cookie, niemals das Passwort).
SESSION_CACHE_PATH = os.path.join(BASE_DIR, ".inwx_session")
def get_creds():
"""Holt Benutzername und Passwort aus KeePassXC.
Das KeePass-Master-Passwort wird interaktiv abgefragt. Ist die
Umgebungsvariable KP_PW gesetzt, wird deren Wert stattdessen automatisch
an keepassxc-cli übergeben (praktisch in nicht-interaktiven Umgebungen).
"""
try:
# -s sorgt dafür, dass das Passwort nicht als 'PROTECTED' ausgegeben wird
cmd = ["keepassxc-cli", "show", "-s", KP_DB_PATH, KP_ENTRY_NAME]
master_pw = os.environ.get("KP_PW")
if master_pw:
# Master-Passwort über stdin an keepassxc-cli durchreichen.
result = subprocess.run(
cmd, input=master_pw + "\n",
capture_output=True, text=True,
)
if result.returncode != 0:
print(f"❌ KeePass-Fehler: {result.stderr.strip()}")
return None, None
output = result.stdout
else:
# Kein KP_PW gesetzt -> keepassxc-cli fragt interaktiv nach.
output = subprocess.check_output(cmd, text=True)
creds = {}
for line in output.splitlines():
if ":" in line:
k, v = line.split(":", 1)
creds[k.strip().lower()] = v.strip()
user = creds.get("username") or creds.get("benutzername") or creds.get("user")
password = creds.get("password") or creds.get("passwort")
return user, password
except Exception as e:
print(f"❌ KeePass-Fehler: {e}")
return None, None
def _load_cached_cookies(api):
"""Lädt gespeicherte Session-Cookies in den ApiClient. True bei Erfolg."""
if not os.path.exists(SESSION_CACHE_PATH):
return False
try:
with open(SESSION_CACHE_PATH, "r") as f:
cookies = json.load(f)
if not cookies:
return False
api.api_session.cookies = requests.utils.cookiejar_from_dict(cookies)
return True
except Exception:
return False
def _save_cached_cookies(api):
"""Speichert die Session-Cookies (Datei nur für den Benutzer lesbar, 0600)."""
try:
cookies = requests.utils.dict_from_cookiejar(api.api_session.cookies)
fd = os.open(SESSION_CACHE_PATH, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w") as f:
json.dump(cookies, f)
except Exception as e:
print(f"⚠️ Konnte Session nicht zwischenspeichern: {e}")
def _clear_cached_session():
"""Entfernt eine (abgelaufene) zwischengespeicherte Session."""
try:
os.remove(SESSION_CACHE_PATH)
except FileNotFoundError:
pass
except Exception:
pass
def _do_login(api):
"""Frischer Login via KeePass-Zugangsdaten; speichert die Session. True bei Erfolg."""
user, password = get_creds()
if not user or not password:
return False
print(f"Versuche Login für: {user}")
login_res = api.login(user, password)
if login_res['code'] != 1000:
print(f"❌ Login fehlgeschlagen: {login_res['msg']} (Code: {login_res['code']})")
return False
_save_cached_cookies(api)
print("✅ Login erfolgreich (Session zwischengespeichert).")
return True
def get_client():
"""Liefert einen einsatzbereiten ApiClient.
Nutzt eine zwischengespeicherte Session, falls vorhanden. Ob diese noch
gültig ist, wird erst beim ersten echten Aufruf via call_api() geprüft
so sparen wir uns eine zusätzliche Anfrage. Ist keine Session vorhanden,
wird sofort via KeePass eingeloggt.
"""
api = ApiClient(api_url=API_URL)
if _load_cached_cookies(api):
print("️ Verwende zwischengespeicherte Session.")
return api
if not _do_login(api):
sys.exit(1)
return api
def _is_auth_error(code):
"""True, wenn der INWX-Code auf eine fehlende/abgelaufene Session hindeutet.
22002299 sind die eigentlichen Authentifizierungs-/Autorisierungsfehler.
Zusätzlich liefert INWX bei Aufrufen ohne gültige Session teils Codes aus
dem 2000er-Bereich (z.B. 2002 „Command use error"); auch diese behandeln
wir als abgelaufene Session und melden uns einmal neu an.
"""
if code is None:
return False
if 2200 <= code < 2300:
return True
return code in (2001, 2002, 2003)
def call_api(api, method, params=None):
"""Wie ApiClient.call_api, meldet sich aber bei abgelaufener Session neu an.
Deutet der Rückgabecode auf eine fehlende/abgelaufene Session hin (siehe
_is_auth_error), wird die zwischengespeicherte Session verworfen, via
KeePass neu eingeloggt und der Aufruf einmal wiederholt.
"""
params = params or {}
res = api.call_api(method, params)
if _is_auth_error(res.get('code')):
print("️ Session abgelaufen, melde neu an...")
_clear_cached_session()
if not _do_login(api):
sys.exit(1)
res = api.call_api(method, params)
return res