Release / verify (push) Canceled after 0s
CI / checks (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / backend (push) Canceled after 0s
Release / test (push) Canceled after 0s
Release / publish (backend) (push) Canceled after 0s
Release / publish (frontend) (push) Canceled after 0s
Release / publish (proxy) (push) Canceled after 0s
Release / publish (updater) (push) Canceled after 0s
- **Single Sign-on (OIDC)**: Anmeldung über Authentik, Keycloak, Entra ID, Google u. a. (Authorization Code mit PKCE). Die Rolle kommt bei jeder Anmeldung aus den Gruppen des Anbieters, ohne zugeordnete Gruppe kommt niemand hinein; SSO-Konten werden nie mit lokalen Konten verknüpft; lokale Anmeldung bleibt als Notzugang. ID-Token werden streng geprüft (Signatur nur asymmetrisch, Aussteller, Zielgruppe, Ablauf, Nonce, einmaliger State), gegen einen nachgebauten Anbieter getestet (inkl. Fälschungsversuche). Neue Tabellen `oidc_settings`, `oidc_login_states`, Spalten `admin_users.auth_source/oidc_subject` (Migration `0013`). Neue Abhängigkeit: PyJWT. - **Behoben:** `opnexus backup --full` und `opnexus restore` (Vollsicherung, in 1.19.0 beschrieben) fehlten im 1.19.0-Bundle und -Updater-Image, weil die Änderung am Skript nicht mit eingecheckt war; sie sind erst ab dieser Version enthalten. - **Aufgabentrennung**: Schalter, mit dem nur Admins ausrollen dürfen, Operatoren aber vorbereiten (Migration `0014`).
40 lines
1.5 KiB
Python
40 lines
1.5 KiB
Python
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
|
|
|
class OidcSettingsRead(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
enabled: bool
|
|
display_name: str
|
|
issuer: str | None
|
|
client_id: str | None
|
|
client_secret_set: bool
|
|
scopes: str
|
|
groups_claim: str
|
|
admin_group: str | None
|
|
operator_group: str | None
|
|
read_only_group: str | None
|
|
|
|
|
|
class OidcSettingsUpdate(BaseModel):
|
|
"""Partial update; the client secret is write-only (set it or clear it)."""
|
|
|
|
enabled: bool | None = None
|
|
display_name: str | None = Field(default=None, min_length=1, max_length=60)
|
|
issuer: str | None = Field(default=None, max_length=500)
|
|
client_id: str | None = Field(default=None, max_length=255)
|
|
client_secret: str | None = Field(default=None, min_length=1, max_length=512)
|
|
clear_client_secret: bool = False
|
|
scopes: str | None = Field(default=None, min_length=1, max_length=255)
|
|
groups_claim: str | None = Field(default=None, min_length=1, max_length=64)
|
|
admin_group: str | None = Field(default=None, max_length=255)
|
|
operator_group: str | None = Field(default=None, max_length=255)
|
|
read_only_group: str | None = Field(default=None, max_length=255)
|
|
|
|
@field_validator("issuer")
|
|
@classmethod
|
|
def https_only(cls, value):
|
|
if value and not value.startswith("https://"):
|
|
raise ValueError("issuer must be an https:// URL")
|
|
return value.strip().rstrip("/") if value else value
|