1fedd683e0
Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer), Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst. Migrations 0001–0023 deployed auf 192.168.1.137 + .164. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
"""LDAP configuration and sync endpoints.
|
|
|
|
All endpoints require COMPANY_ADMIN or SUPER_ADMIN role.
|
|
"""
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.core.dependencies import require_role
|
|
from app.models.ldap_config import LdapConfig
|
|
from app.models.user import User, UserRole
|
|
|
|
_admin_roles = (UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
|
|
from app.schemas.ldap import (
|
|
LdapConfigCreate,
|
|
LdapConfigOut,
|
|
LdapConfigUpdate,
|
|
LdapSyncRequest,
|
|
LdapSyncResult,
|
|
LdapTestResult,
|
|
LdapUserPreview,
|
|
)
|
|
from app.services.ldap_service import decrypt_password, encrypt_password, ldap_service
|
|
|
|
router = APIRouter(prefix="/ldap", tags=["LDAP"])
|
|
|
|
|
|
@router.get("/config", response_model=LdapConfigOut | None)
|
|
async def get_ldap_config(
|
|
current_user: User = require_role(*_admin_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
return await ldap_service.get_config(current_user.company_id, db)
|
|
|
|
|
|
@router.post("/config", response_model=LdapConfigOut)
|
|
async def create_ldap_config(
|
|
data: LdapConfigCreate,
|
|
current_user: User = require_role(*_admin_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
existing = await ldap_service.get_config(current_user.company_id, db)
|
|
if existing:
|
|
# Update instead of duplicate
|
|
return await _apply_update(existing, data.model_dump(), db)
|
|
|
|
cfg = LdapConfig(
|
|
company_id=current_user.company_id,
|
|
enabled=data.enabled,
|
|
host=data.host,
|
|
port=data.port,
|
|
use_ssl=data.use_ssl,
|
|
use_tls=data.use_tls,
|
|
bind_dn=data.bind_dn,
|
|
bind_password_encrypted=encrypt_password(data.bind_password),
|
|
base_dn=data.base_dn,
|
|
user_search_filter=data.user_search_filter,
|
|
attr_email=data.attr_email,
|
|
attr_firstname=data.attr_firstname,
|
|
attr_lastname=data.attr_lastname,
|
|
attr_username=data.attr_username,
|
|
attr_department=data.attr_department,
|
|
)
|
|
db.add(cfg)
|
|
await db.commit()
|
|
await db.refresh(cfg)
|
|
return cfg
|
|
|
|
|
|
@router.patch("/config", response_model=LdapConfigOut)
|
|
async def update_ldap_config(
|
|
data: LdapConfigUpdate,
|
|
current_user: User = require_role(*_admin_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
cfg = await ldap_service.get_config_or_404(current_user.company_id, db)
|
|
return await _apply_update(cfg, data.model_dump(exclude_none=True), db)
|
|
|
|
|
|
@router.post("/test", response_model=LdapTestResult)
|
|
async def test_ldap_connection(
|
|
current_user: User = require_role(*_admin_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
cfg = await ldap_service.get_config_or_404(current_user.company_id, db)
|
|
result = ldap_service.test_connection(cfg)
|
|
return LdapTestResult(success=result.success, message=result.message)
|
|
|
|
|
|
@router.get("/preview", response_model=list[LdapUserPreview])
|
|
async def preview_ldap_users(
|
|
current_user: User = require_role(*_admin_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Returns first 50 users found in LDAP (for preview before sync)."""
|
|
cfg = await ldap_service.get_config_or_404(current_user.company_id, db)
|
|
raw_users = ldap_service.search_users(cfg)
|
|
previews = []
|
|
for u in raw_users[:50]:
|
|
previews.append(LdapUserPreview(
|
|
dn=u.get("dn", ""),
|
|
email=str(u.get(cfg.attr_email, "") or "").lower(),
|
|
first_name=str(u.get(cfg.attr_firstname, "") or ""),
|
|
last_name=str(u.get(cfg.attr_lastname, "") or ""),
|
|
department=str(u.get(cfg.attr_department, "") or "") if cfg.attr_department else None,
|
|
))
|
|
return previews
|
|
|
|
|
|
@router.post("/sync", response_model=LdapSyncResult)
|
|
async def sync_ldap_users(
|
|
data: LdapSyncRequest,
|
|
current_user: User = require_role(*_admin_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
cfg = await ldap_service.get_config_or_404(current_user.company_id, db)
|
|
result = await ldap_service.sync_users(cfg, db, default_role=data.default_role)
|
|
return LdapSyncResult(
|
|
created=result.created,
|
|
updated=result.updated,
|
|
deactivated=result.deactivated,
|
|
errors=result.errors,
|
|
)
|
|
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
async def _apply_update(cfg: LdapConfig, updates: dict, db: AsyncSession) -> LdapConfig:
|
|
for field, value in updates.items():
|
|
if field == "bind_password" and value:
|
|
cfg.bind_password_encrypted = encrypt_password(value)
|
|
elif hasattr(cfg, field):
|
|
setattr(cfg, field, value)
|
|
await db.commit()
|
|
await db.refresh(cfg)
|
|
return cfg
|