diff --git a/backend/app.py b/backend/app.py index e58c4ed..a46f2ce 100644 --- a/backend/app.py +++ b/backend/app.py @@ -29,10 +29,10 @@ templates = Jinja2Templates(directory="backend/templates") def get_oidc_client(settings: Settings = Depends(get_settings)) -> OIDCClient: - if settings.auth_mode != "oidc": + if not settings.oidc_enabled: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="OIDC auth not enabled", + detail="OIDC not configured", ) try: return OIDCClient(settings) @@ -59,7 +59,8 @@ def whoami( current_user: str = Depends(get_current_admin), settings: Settings = Depends(get_settings), ) -> dict: - return {"user": current_user, "auth_mode": settings.auth_mode} + auth_mode = "pam+oidc" if settings.oidc_enabled else "pam" + return {"user": current_user, "auth_mode": auth_mode} @app.post("/login", response_model=LoginResponse) @@ -115,7 +116,7 @@ def oidc_callback( username = oidc.extract_username(claims) if not username: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing username claim") - if not is_authorized_admin(username, settings): + if not is_authorized_admin(username, settings, mode="oidc"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not authorized to log in") token = issue_token(username, settings) @@ -132,6 +133,11 @@ def oidc_callback( return redirect +@app.get("/login/oidc/status") +def oidc_status(settings: Settings = Depends(get_settings)) -> dict: + return {"enabled": settings.oidc_enabled} + + @app.get("/users", response_model=List[UserStatus], dependencies=[Depends(get_current_admin)]) def users(settings: Settings = Depends(get_settings)) -> List[UserStatus]: logged_in = set(actions.list_logged_in_users()) diff --git a/backend/auth.py b/backend/auth.py index a402bd4..b012197 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -1,7 +1,7 @@ import datetime as dt import grp import pwd -from typing import List, Set +from typing import List, Set, Optional import jwt import pam @@ -23,8 +23,8 @@ def _is_member_of(username: str, groups: Set[str]) -> bool: return bool(user_groups & groups) -def is_authorized_admin(username: str, settings: Settings) -> bool: - if settings.auth_mode == "oidc": +def is_authorized_admin(username: str, settings: Settings, mode: Optional[str] = None) -> bool: + if mode == "oidc": allowed_users = set(settings.auth_allowed_users) if allowed_users and username not in allowed_users: return False @@ -46,12 +46,7 @@ def is_authorized_admin(username: str, settings: Settings) -> bool: def authenticate_admin_user(username: str, password: str, settings: Settings) -> None: - if settings.auth_mode != "pam": - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="Password login disabled; OIDC is configured", - ) - if not is_authorized_admin(username, settings): + if not is_authorized_admin(username, settings, mode="pam"): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User not authorized to log in", @@ -82,7 +77,12 @@ def decode_token(token: str, settings: Settings) -> str: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") from exc username = payload.get("sub") - if not username or not is_authorized_admin(username, settings): + if not username: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") + if not ( + is_authorized_admin(username, settings, mode="pam") + or is_authorized_admin(username, settings, mode="oidc") + ): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Unauthorized user") return username diff --git a/backend/settings.py b/backend/settings.py index 4a7ebbf..e4b1165 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -32,6 +32,9 @@ class Settings: self.oidc_state_cookie_name: str = os.getenv( "SKD_OIDC_STATE_COOKIE_NAME", "skd_oidc_state" ) + self.oidc_enabled: bool = bool( + self.oidc_issuer and self.oidc_client_id and self.oidc_client_secret + ) self.default_countdown: int = int(os.getenv("SKD_DEFAULT_COUNTDOWN", "60")) self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true" self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5")) diff --git a/backend/templates/index.html b/backend/templates/index.html index c7011dc..257941c 100644 --- a/backend/templates/index.html +++ b/backend/templates/index.html @@ -18,11 +18,11 @@
-

Login (nur Root-User)

+

Login

Bevorzugt OIDC nutzen, falls konfiguriert. Die Anmeldung öffnet den Identity Provider und setzt eine Session-Cookie.


-

Lokale Anmeldung (PAM) nur falls OIDC nicht verfügbar:

+

Lokale Anmeldung (PAM) ist immer moeglich:

@@ -84,7 +84,7 @@
-