import json from typing import Optional from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.crypto import encrypt_password, decrypt_password from app.core.database import get_db from app.core.security import require_role from app.models.user import User from app.models.ldap_config import LdapConfig from app.schemas.ldap_config import ( LdapConfigCreate, LdapConfigUpdate, LdapConfigResponse, LdapTestRequest, ) router = APIRouter(prefix="/ldap-config", tags=["LDAP配置"]) def _serialize_ldap(ldap: LdapConfig) -> dict: """Serialize LdapConfig to response dict with masked password.""" user_mapping = json.loads(ldap.user_mapping) if ldap.user_mapping else None return { "id": ldap.id, "ldap_host": ldap.ldap_host, "ldap_port": ldap.ldap_port, "use_ldaps": ldap.use_ldaps, "admin_dn": ldap.admin_dn, "admin_password": "******", "search_filter": ldap.search_filter, "base_dn": ldap.base_dn, "user_mapping": user_mapping, "created_at": ldap.created_at, "updated_at": ldap.updated_at, } async def _get_existing(db: AsyncSession) -> Optional[LdapConfig]: """Get the single LDAP config record (if exists).""" result = await db.execute(select(LdapConfig).limit(1)) return result.scalar_one_or_none() @router.get("", response_model=LdapConfigResponse) async def get_ldap_config( db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): """管理员获取 LDAP 配置""" ldap = await _get_existing(db) if not ldap: raise HTTPException(status_code=404, detail="LDAP 配置不存在,请先创建") return _serialize_ldap(ldap) @router.put("", response_model=LdapConfigResponse) async def upsert_ldap_config( data: LdapConfigCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): """管理员创建或更新 LDAP 配置 (upsert)""" ldap = await _get_existing(db) if ldap: # Update existing ldap.ldap_host = data.ldap_host ldap.ldap_port = data.ldap_port ldap.use_ldaps = data.use_ldaps ldap.admin_dn = data.admin_dn ldap.admin_password = encrypt_password(data.admin_password) ldap.search_filter = data.search_filter ldap.base_dn = data.base_dn ldap.user_mapping = json.dumps(data.user_mapping, ensure_ascii=False) if data.user_mapping else None else: # Create new ldap = LdapConfig( ldap_host=data.ldap_host, ldap_port=data.ldap_port, use_ldaps=data.use_ldaps, admin_dn=data.admin_dn, admin_password=encrypt_password(data.admin_password), search_filter=data.search_filter, base_dn=data.base_dn, user_mapping=json.dumps(data.user_mapping, ensure_ascii=False) if data.user_mapping else None, ) db.add(ldap) await db.commit() await db.refresh(ldap) return _serialize_ldap(ldap) @router.post("/test") async def test_ldap_connection( data: LdapTestRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): """测试 LDAP 连接""" ldap = await _get_existing(db) if not ldap: raise HTTPException(status_code=404, detail="LDAP 配置不存在,请先创建") try: import ldap as _ldap # noqa # Real LDAP test path protocol = "ldaps" if ldap.use_ldaps else "ldap" uri = f"{protocol}://{ldap.ldap_host}:{ldap.ldap_port}" conn = _ldap.initialize(uri) conn.protocol_version = 3 conn.simple_bind_s(ldap.admin_dn, decrypt_password(ldap.admin_password)) # Try binding with test user search_filter = ldap.search_filter or "(uid=%s)" results = conn.search_s( ldap.base_dn, _ldap.SCOPE_SUBTREE, search_filter.replace("%s", data.test_user), ) if not results: return {"success": False, "message": f"未找到用户: {data.test_user}"} user_dn = results[0][0] conn.simple_bind_s(user_dn, data.test_password) conn.unbind_s() return {"success": True, "message": f"LDAP 连接成功,用户 {data.test_user} 验证通过"} except ImportError: return { "success": False, "message": "python-ldap 未安装,无法测试 LDAP 连接。请执行: pip install python-ldap", } except Exception as e: return {"success": False, "message": f"LDAP 连接失败: {str(e)}"}