feat: OIDC/LDAP auth, audit log, maintenance windows, migrations
- Add OIDC (OpenID Connect) authentication with discovery URL support, group-based role mapping, and token introspection - Add LDAP authentication with bind credentials, group-based role mapping, and configurable attribute names - Add audit log (model, routes, templates) tracking settings changes, plugin enable/disable, login events - Add migrations 0009 (widget variants), 0010 (maintenance windows), 0011 (audit log) - Add optional dependency groups to pyproject.toml: [ldap], [snmp] - Add Settings → Auth tab with OIDC and LDAP configuration forms Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
"""fabledscryer/core/audit.py
|
||||
|
||||
Helpers for writing audit log entries. Each call opens its own DB
|
||||
session so audit events are committed independently of the calling
|
||||
route's transaction (audit is only written after the main action
|
||||
succeeds, by calling this after the main `async with db.begin()` block).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def log_audit(
|
||||
app,
|
||||
user_id: str | None,
|
||||
username: str,
|
||||
action: str,
|
||||
entity_type: str | None = None,
|
||||
entity_id: str | None = None,
|
||||
detail: dict | None = None,
|
||||
) -> None:
|
||||
"""Write one audit event. Never raises — failures are logged and swallowed."""
|
||||
from fabledscryer.models.audit import AuditEvent
|
||||
try:
|
||||
async with app.db_sessionmaker() as db:
|
||||
async with db.begin():
|
||||
db.add(AuditEvent(
|
||||
user_id=user_id,
|
||||
username=username or "unknown",
|
||||
action=action,
|
||||
entity_type=entity_type,
|
||||
entity_id=entity_id,
|
||||
detail_json=json.dumps(detail) if detail else None,
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
))
|
||||
except Exception:
|
||||
logger.exception("Failed to write audit event action=%r", action)
|
||||
@@ -51,6 +51,35 @@ DEFAULTS: dict[str, Any] = {
|
||||
"ping.threshold.good_ms": 50,
|
||||
"ping.threshold.warn_ms": 200,
|
||||
"plugins.index_url": "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/raw/branch/main/index.yaml",
|
||||
# OIDC single-sign-on
|
||||
"oidc.enabled": False,
|
||||
"oidc.discovery_url": "",
|
||||
"oidc.client_id": "",
|
||||
"oidc.client_secret": "",
|
||||
"oidc.scopes": "openid profile email",
|
||||
"oidc.username_claim": "preferred_username",
|
||||
"oidc.email_claim": "email",
|
||||
"oidc.groups_claim": "groups",
|
||||
"oidc.admin_group": "",
|
||||
"oidc.operator_group": "",
|
||||
# LDAP authentication
|
||||
"ldap.enabled": False,
|
||||
"ldap.host": "",
|
||||
"ldap.port": 389,
|
||||
"ldap.tls": False,
|
||||
"ldap.bind_dn": "",
|
||||
"ldap.bind_password": "",
|
||||
"ldap.base_dn": "",
|
||||
"ldap.user_filter": "(uid={username})",
|
||||
"ldap.admin_group_dn": "",
|
||||
"ldap.operator_group_dn": "",
|
||||
"ldap.attr_username": "uid",
|
||||
"ldap.attr_email": "mail",
|
||||
# Scheduled reports
|
||||
"reports.enabled": False,
|
||||
"reports.schedule_day": 6, # 0=Monday … 6=Sunday
|
||||
"reports.schedule_hour": 8, # UTC hour
|
||||
"reports.last_sent_at": "",
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +152,14 @@ def to_ansible_cfg(settings: dict[str, Any]) -> dict:
|
||||
return {"sources": settings.get("ansible.sources", [])}
|
||||
|
||||
|
||||
def to_oidc_cfg(settings: dict[str, Any]) -> dict:
|
||||
return {k[len("oidc."):]: settings.get(k, DEFAULTS[k]) for k in DEFAULTS if k.startswith("oidc.")}
|
||||
|
||||
|
||||
def to_ldap_cfg(settings: dict[str, Any]) -> dict:
|
||||
return {k[len("ldap."):]: settings.get(k, DEFAULTS[k]) for k in DEFAULTS if k.startswith("ldap.")}
|
||||
|
||||
|
||||
def to_plugins_cfg(settings: dict[str, Any]) -> dict:
|
||||
"""Assemble {plugin_name: {...config}} from all plugin.* keys."""
|
||||
result = {}
|
||||
|
||||
Reference in New Issue
Block a user