"""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