手冊首頁 / 系統設定 / LDAP 設定

LDAP 設定(LDAP Configuration)

功能群:系統管理system_configs key-value 慣例先讀 功能群總覽 §4

事實基準:2026-07-05 從 FE src/views/ldap-config/LdapConfigForm.vue + BE api/system_config/ / api/user_auth_provider/ + jedi-system-config / jedi-login 套件 + routes.json / db_schema.json 掃出。

1. 功能描述

LDAP 設定頁是第三方登入(LDAP / Active Directory)的連線參數維護頁:管理員在此設定企業 LDAP 或 AD 伺服器資訊,設定值同時是登入流程app/auth/service/login_service.py::init_config())在使用者選擇 LDAP 登入時實際套用的參數來源——不是獨立於登入之外的靜態設定。頁面只有單一表單,無列表、無分頁。

主要使用者:系統管理員(頁面本身無角色守門,見 §3)。

角色速覽

角色 一句話
系統管理員(可見本選單者) 維護 LDAP/AD 連線設定、測試連線
一般使用者 無此選單(ui_routes 可見性未命中);但可直接打 API(見 §3 ⚠️)

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 載入既有設定 進頁自動 GET 目前的 LDAP 設定回填表單 整頁(onMounted §6.1
2 伺服器欄位 必填,LDAP/AD 伺服器 IP 或 FQDN 表單 §6.1
3 埠號欄位 必填,數字輸入,預設 389 表單 §6.1
4 Base DN 欄位 必填,LDAP 查詢起點 DN 表單 §6.1
5 LDAP 類型下拉 二選一:Microsoft AD / LDAP(即 OpenLDAP),對應 is_ad 布林 表單 §9.3
6 加密方式下拉 三選一:無 / SSL / STARTTLS 表單 §9.3
7 認證帳號欄位 選填,LDAP bind 用的服務帳號 表單 §6.1
8 密碼欄位 選填,input type="password";留白送出=沿用舊密碼(見 §6.1 changePwd 表單 §6.1
9 連線測試 用表單目前值即時測試 LDAP/AD bind,不落地儲存 表單底部按鈕 UC-LDAP-02
10 儲存設定 寫回 system_configs(THIRD_PARTY_LOGIN/LDAP) 表單底部按鈕 UC-LDAP-01
11 離開頁面確認 表單有異動時離開觸發確認 dialog(onBeforeRouteLeave 全域

UC(§2)展開 1(載入)已併入儲存流程圖;核心路徑為 UC-LDAP-01(儲存)與 UC-LDAP-02(連線測試),兩者共用同一張流程圖。

2. Use Case

角色速覽

角色 一句話
系統管理員(可見本選單者) 維護 LDAP/AD 連線設定、測試連線
一般使用者 無此選單(ui_routes 可見性未命中);但可直接打 API(見 §3 ⚠️)

視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點。

UC-LDAP-01 儲存 LDAP 設定

項目 內容
角色 系統管理員(BE 僅檢查登入,見 §3)
前置條件 已登入;server / port / base_dn 已填
產出 / 後置條件 public.system_configs(group=THIRD_PARTY_LOGIN, key=LDAP)新增或更新一列;密碼欄未勾異動則保留舊值

UC-LDAP-02 連線測試

項目 內容
角色 系統管理員(BE 僅檢查登入)
前置條件 已登入;表單必填欄位已填(不需先儲存)
產出 / 後置條件 無資料異動,純測試 bind;成功回 200、失敗回 401(LOGIN_401014
ldap_save_test start 進入 LDAP 設定頁 load 載入既有設定 GET /system/config/THIRD_PARTY_LOGIN/LDAP (secret 欄位回傳前先剔除) start->load fill 填寫伺服器 / 埠號 / Base DN(必填) 選填:LDAP 類型 / 加密方式 / 認證帳號 / 密碼 load->fill valid 前端表單驗證:server / port / base_dn 皆已填? fill->valid invalid 標紅對應欄位並 focus,不送出 valid->invalid choice 使用者按哪個按鈕? valid->choice invalid->fill test 連線測試 POST /ldap/connect-test body = 表單目前值 choice->test 連線測試 save 儲存 PUT /system/config/THIRD_PARTY_LOGIN/LDAP body = 表單值 + changePwd(密碼欄是否手動改過) choice->save 儲存 testbe BE:test_connect() 若密碼欄空但曾填過帳號 → 沿用舊 secret test->testbe testldap LdapAdapter.connect() 建 Server → bind(AD 用 NTLM) STARTTLS/SSL 依 encryption 欄位 testbe->testldap testok bind 成功 → 200 Toast「連線成功」 testldap->testok bind 成功 testfail bind 失敗 / 例外 401 LOGIN_401014 Toast「連線失敗」 testldap->testfail bind 失敗 / 逾時 / 格式錯 savebe BE:update_config_by_group_key() changePwd=false → 用舊 secret 覆蓋 payload.secret (找不到 → 新增一筆) save->savebe saveok 200 → 重新 GET 設定回填 Toast「更新成功」 savebe->saveok
LDAP 儲存與連線測試流程:載入回填→填寫→前端必填驗證→儲存(changePwd 決定密碼是否覆蓋)/連線測試(密碼留白沿用舊值)→BE LdapAdapter bind

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
進入本頁 ui_routes web-menu 可見性 無端點層強制(僅 @jwt_required api/system_config/routes/system_config_route.py:42,109
讀取設定 有選單即可 ⚠️ 僅 @jwt_required無角色 / 能力守門 SystemConfigGroupRoute.getsystem_config_route.py:96-104
儲存設定 有選單即可 ⚠️ 僅 @jwt_required無角色 / 能力守門 SystemConfigGroupRoute.putsystem_config_route.py:106-119
連線測試 有選單即可 ⚠️ 僅 @jwt_required無角色 / 能力守門 LdapConnectTestRoute.postapi/user_auth_provider/routes/ldap_route.py:12-24

⚠️ 本頁三個端點全部僅 @jwt_required(),無任何角色 / 平台管理員守門——任何已登入使用者(不限系統管理員)直接打 API 都能讀取 / 覆寫全系統 LDAP 連線設定(含觸發連線測試)。與 _overview §5 一致(本群多頁通病)。本 spec 只記錄不修,收進 §12。

4. 狀態機與前置條件

本頁無狀態機,僅表單前端驗證與後端密碼保留邏輯:

條件 效果 出處
server / port / base_dn 任一為空 前端阻擋送出(連線測試與儲存皆同一驗證),focus 錯誤欄位 LdapConfigForm.vue:30-42, 95-107, 128-139
儲存時 changePwd=false(密碼欄未手動改過) BE 用資料庫既有 secret 覆蓋 payload,不寫入前端傳來的空值 jedi-system-config system_config_domain_service.py:118-132update_config_by_group_key
連線測試時 payload 無 secret 但有 user BE 沿用資料庫既有 secret 做測試(避免每次測試都要重打密碼) app/user_auth_provider/service/ldap_service.py:32-45init_config
設定不存在(首次進頁) BE get_system_config_by_key 找不到會 raise NotFound;FE getConfig() 未特別 catch(見 §12 坑 1) system_config_service.py:31-39

5. UI 設計

📸 實機截圖待補(亮色模式,另有專責 session)。目前以版面骨架圖示意。待補清單:初次進頁空表單狀態、已有設定回填狀態、連線測試成功 / 失敗 toast、必填欄位驗證錯誤紅框狀態。

版面骨架(單卡片表單,無 tab、無列表):

單卡片表單(無 tab、無列表):伺服器/埠號/Base DN/類型/加密方式/認證帳號/密碼 表單欄位 伺服器*/埠號*(預設 389)/Base DN*/LDAP 類型(AD / OpenLDAP)/加密方式(無/SSL/STARTTLS) 認證帳號(選填)/密碼(選填,留白=沿用舊值) 進頁 GET 回填(secret 恆不回傳);離開有異動觸發確認 dialog 開啟:UC-LDAP-01 儲存 LDAP 設定 儲存設定 PUT /system/config/THIRD_PARTY_LOGIN/LDAP(+changePwd) → UC-LDAP-01(點我開啟) 開啟:UC-LDAP-02 連線測試 連線測試(不落地,用表單當前值) POST /ldap/connect-test(失敗 → LOGIN_401014) → UC-LDAP-02(點我開啟) 彩色=可點跳轉本頁 UC 的區塊|灰=本頁結構描述(表單欄位) API 僅列代表性 1 條,完整規格見 §6;三端點皆僅 @jwt_required,無角色守門
LDAP 設定頁版面骨架:伺服器/埠號/Base DN 必填 + LDAP類型/加密方式下拉 + 認證帳號/密碼選填(灰)、儲存(連 UC-LDAP-01)與連線測試(連 UC-LDAP-02)按鈕

狀態呈現loading 期間顯示 LoadingState(page size)取代表單;密碼欄為原生 <input type="password">(非 PrimeVue Password 元件),change 時同時標記 hasChangepasswordHasChange;儲存 / 測試按鈕在送出期間顯示 loading spinner(submitted state)。無 socket、無多人協作提示。

6. API 規格

Envelope:成功 {"code": 1, "data": {...}}、失敗 {"code": 0, "msg": "...", "data": {...}}(見 CLAUDE.md Response Format)。

6.1 Endpoint 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 詳述
讀取 GET /api/1.0/system/config/<group>/<key> 讀取 LDAP 設定(group=THIRD_PARTY_LOGIN, key=LDAP),回應剔除 secret §6.2
儲存 PUT /api/1.0/system/config/<group>/<key> 更新 LDAP 設定(不存在則新增) §6.2
連線測試 POST /api/1.0/ldap/connect-test 用送入的參數即時測試 LDAP/AD bind,不落地 §6.3

endpoint 存在性對 routes.json 核實(system-config.systemconfiggrouproute / user_auth_provider.ldapconnecttestroute)。group / key 常數值取自 FE 呼叫點 LdapConfigForm.vue:86, 115API.SYSTEM_CONFIG + '/THIRD_PARTY_LOGIN/LDAP'),非泛用 /system/config 全量端點。

6.2 讀取 / 儲存(system_config/<group>/<key>

GET Response dataSystemConfigResponseapi/system_config/serializers/system_config.py:9-15):

欄位 型別 說明
uid string 該設定列的 UUID
group string 固定 THIRD_PARTY_LOGIN
key string 固定 LDAP
value dict LDAP 參數本體(見下表),回應前若含 secret 一律剔除system_config_route.py:18,47-49 HIDDEN_SECRET 清單含 THIRD_PARTY_LOGIN
created_at / updated_at datetime %Y-%m-%d %H:%M:%S

value 內部欄位(對照 LdapConfigRequestapi/user_auth_provider/serializers/ldap_config.py:4-11,及 FE currData 初值 LdapConfigForm.vue:23-29):

欄位 型別 必填 說明
server string LDAP/AD 伺服器
port int 埠號,FE 預設 389
base_dn string Base DN
is_ad bool true=Microsoft AD(NTLM 認證)、false=OpenLDAP
encryption string '' / SSL / STARTTLS
user string bind 用認證帳號
secret string bind 密碼(GET 回應永遠不含此欄;只在 PUT/POST 送入)

PUT Request:同上 value 欄位 + 頂層 changePwd(bool,FE 送 passwordHasChange 的值,LdapConfigForm.vue:114)——changePwd=false 時 BE 用既有 secret 覆蓋,changePwd=true 才採用送入的新密碼。出處:jedi-system-config system_config_domain_service.py:118-132update_config_by_group_key)。

BE 鏈路SystemConfigGroupRoute.get/putapi/system_config/routes/system_config_route.py:92-119)→ SystemConfigService.get_system_config_by_key / update_config_by_group_key(jedi-system-config app/service/system_config_service.py:32-39,142-149)→ SystemConfigDomainService.update_config_by_group_key(同套件 domain/service/system_config_domain_service.py:118-132,密碼保留邏輯 + 找不到自動新增)→ repo 寫 public.system_configs

6.3 連線測試(POST /ldap/connect-test

RequestLdapConfigRequest,同 §6.2 value 欄位形狀,無 changePwd):

欄位 型別 必填 說明
server string LDAP/AD 伺服器
port int 埠號
base_dn string Base DN
is_ad bool true=Microsoft AD(NTLM 認證)、false=OpenLDAP
encryption string '' / SSL / STARTTLS
user string bind 用認證帳號
secret string bind 密碼

ResponseBooleanResponse(成功恆 true;失敗直接拋例外變成非 2xx,見下)。

BE 鏈路LdapConnectTestRoute.postapi/user_auth_provider/routes/ldap_route.py:12-24)→ LdapService.test_connectapp/user_auth_provider/service/ldap_service.py:21-30):先 init_config() 組出 LoginConfigDTO(若送入 payload 無 secret 但有 user,沿用資料庫既有 secretldap_service.py:38-45)→ jedi-login get_authenticate_adapter(LDAP, config)LdapAdapter.test_connect()~/Projects/Jedicogy/module/jedi-python-package/jedi-login/jedi_login/infra/adapter/authenticate_adapter/ldap/ldap_adapter.py:81-91):建立 ldap3.Server(依 encryption 決定 ldaps:// 前綴 / use_ssl)→ Connection.bind()(AD 用 NTLM、STARTTLS 額外呼叫 start_tls())→ bind 成功回 True,失敗(含逾時、憑證錯、伺服器不可達)一律包成 UnauthorizedError(LOGIN_401014) → HTTP 401。

已知例外碼(jedi-login common/enum/error_code.py):LOGIN_404006LDAP_SERVER_NOT_CONFIGUREDserver 空字串)、LOGIN_405003LDAP_ACCOUNT_FORMAT_ERROR,AD 帳號非 DOMAIN\username 格式)、LOGIN_401014LDAP_CONNECT_FAILEDtest_connect() 統一捕捉的失敗碼)。error code 前綴為 LOGIN_(jedi-login 套件自有前綴,非主專案 GRC_/AUTH_,查證方式:grep ~/Projects/Jedicogy/module/jedi-python-package/jedi-login/jedi_login/common/enum/error_code.py,非 common/code/*_error_code.py——本設定的錯誤碼定義不在主專案倉庫內)。

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/ldap-config/LdapConfigForm.vue 唯一檢視:表單載入 / 驗證 / 儲存 / 連線測試 / 離開確認
src/config/router/index.js:317-328 route ldap-config/system/ldap-config
src/config/api/api.js:328,339 SYSTEM_CONFIG / LDAP_CONNECT_TEST 端點常數
src/service/BaseService.js HTTP 包裝(get/put/post
src/config/locales/i18n/zh-tw/pages.json:379-406 ldap_config 命名空間(欄位標籤 / placeholder / 驗證訊息)

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
讀取 / 儲存設定 api/system_config/routes/system_config_route.py::SystemConfigGroupRoute jedi-system-config SystemConfigService.get_system_config_by_key / update_config_by_group_key jedi-system-config SystemConfigDomainServiceSystemConfigRepoImplpublic.system_configs
連線測試 api/user_auth_provider/routes/ldap_route.py::LdapConnectTestRoute app/user_auth_provider/service/ldap_service.py::LdapService.test_connect jedi-login get_authenticate_adapterLdapAdapterldap3 套件)
登入時實際套用 (非本頁 route,登入頁觸發) app/auth/service/login_service.py::LoginService.init_config(讀同一筆 THIRD_PARTY_LOGIN/LDAP 設定) jedi-login LoginService.process_loginLdapAdapter.authenticate

DDD 提醒:路由層(SystemConfigGroupRoute / LdapConnectTestRoute)皆不碰 DB,委派 app service / jedi-system-config service,符合分層規範。但三端點皆無角色守門(僅 @jwt_required)——新增本頁 API 或提升守門強度時,比照 §3 現況先跟 user 確認是否要補角色檢查,勿在不知情下複製「僅 JWT」寫法到有敏感度更高的設定頁。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 詳述
public.system_configs 讀 / LDAP 設定實際儲存位置:group='THIRD_PARTY_LOGIN', key='LDAP', value 為 jsonb(欄位見 §6.2) §9.3
public.user_auth_providers 讀(間接,登入時) 使用者 ↔︎ LDAP 第三方登入綁定關係(provider='LDAP');本頁不直接讀寫此表,但本頁設定的伺服器參數是登入時解析此表 provider_user_id 的前提 §9.3

表結構 / 欄位 / comment 取自 scripts/deliverables/out/db_schema.json(2026-07-05 dump)。

9.2 ER 圖

本頁只涉及單表寫入(system_configs 為通用 key-value 表,無專屬 FK),關聯示意見 _overview §6 核心資料模型圖system_configs 節點)。user_auth_providers 與 LDAP 設定為邏輯關聯(同一套登入流程共用參數),非資料庫層 FK 關聯,故不另畫 ER。

9.3 核心表欄位卡

public.system_configs(comment:「系統配置表:儲存系統設定參數,以群組和鍵值對方式組織」):

欄位 型別 說明
id integer 主鍵
uid varchar(50) 配置唯一識別碼 UUID
group varchar(255) 配置群組(本頁固定 THIRD_PARTY_LOGIN
key varchar(255) 配置鍵名(本頁固定 LDAP
value jsonb 配置值(本頁形狀見 §6.2,含 server/port/base_dn/is_ad/encryption/user/secret
tenant_id integer|null 設定歸屬租戶(本頁未見 FE 傳入,實務為全域設定)
org_unit_id integer|null 設定歸屬部門(同上,本頁未使用)
created_at / updated_at timestamp 審計時間戳(表本身無 created_user/updated_user 欄位)

public.user_auth_providers(comment:「使用者第三方登入驗證表:記錄使用者與第三方身份提供者的綁定關係」,本頁不寫,僅背景關聯說明):uid / user_id(FK→users.id CASCADE)/ provider(如 LDAP)/ provider_user_id(LDAP entryUUID 或 AD objectGUID)/ created_at / updated_at / created_user / updated_user

10. 頁面邏輯與資料對應

載入時序onMountedgetConfig()GET /system/config/THIRD_PARTY_LOGIN/LDAP → 成功則 currData.value = res.valuesecret 因 BE 剔除而為 undefined,表單密碼欄顯示空白)→ loading=false

欄位對應(畫面 ↔︎ FE state ↔︎ API 欄位):

畫面元素 FE state API 欄位
伺服器 currData.server value.server
埠號 currData.port value.port
Base DN currData.base_dn value.base_dn
LDAP 類型下拉 currData.is_ad value.is_ad
加密方式下拉 currData.encryption value.encryption
認證帳號 currData.user value.user
密碼 currData.secret + passwordHasChange value.secret + 頂層 changePwd

儲存流程onSaveClick()v$.value.$touch() 驗證必填三欄 → 通過則 update():組 payload = {...currData.value, changePwd: passwordHasChange.value}PUT /system/config/THIRD_PARTY_LOGIN/LDAP → 成功後重新 getConfig() 回填(不是直接用 response 蓋,而是整個重抓)、showSuccess、重置 hasChange / passwordHasChange

連線測試流程onTestConnectClick() → 同樣先驗證必填三欄 → POST /ldap/connect-test,body 為當前表單值(非已儲存值,允許先測後存)→ 成功 showSuccess('連線成功');失敗(.finally 只重置 submitted,無專屬 .catch,錯誤走全域攔截器 toast,見 §12 坑 2)。

錯誤對應:全域 axios 攔截器(BaseService)catch 非 2xx 轉 toast;本頁未見自訂錯誤碼對應文案,因此 LOGIN_401014 等碼會顯示 BE 給的通用 msg(中文「LDAP連線失敗」),非 i18n key 對照。

11. 背景行為與外部依賴

類型 內容
通知
Job / 排程
Socket 無:純表單 CRUD,資料更新靠操作後重新 GET
jedi-* 套件 jedi-system-configsystem_configs 表 CRUD + 密碼保留邏輯)、jedi-loginLdapAdapter 實際 bind 測試 / 登入時驗證,ldap3 第三方套件)、jedi-auth(JWT)
系統參數 本頁本身即系統參數維護頁;設定值被 app/auth/service/login_service.py::LoginService.init_config() 在使用者選擇 LDAP 登入方式時讀取套用(非本頁 route,跨頁依賴)

12. 邊界情況與已知坑

  1. 三端點皆無角色守門:讀取 / 儲存 / 連線測試僅 @jwt_required(),任何登入使用者都能改動全系統 LDAP 設定或觸發連線測試(可能被用作內部網路探測工具)。與 _overview §5 一致,本 spec 只記錄不修(§3)。
  2. 連線測試失敗無專屬錯誤處理:FE onTestConnectClick() 只有 .finally(重置 loading),沒有 .catch 顯示失敗訊息或聚焦欄位,錯誤 toast 完全依賴 axios 全域攔截器的通用文案;LOGIN_401014 等具體原因不會被特別標示在表單上。
  3. 密碼「留白=沿用舊值」的判斷靠 FE flag,不是靠是否為空字串:儲存走 changePwdpasswordHasChange)欄位判斷;連線測試走「payload 無 secret 但有 user」判斷(ldap_service.py:43-45)——兩條路徑保留舊密碼的條件不同,若日後改動任一邏輯需同步檢視另一條。
  4. is_ad 決定認證機制而非單純顯示文字:AD 用 NTLM 認證且要求帳號格式 DOMAIN\usernameLOGIN_405003),OpenLDAP 走匿名或 simple bind 搜尋 cn=;切換下拉不會清空既有帳號欄位,格式不符會在測試 / 登入時才報錯,非選擇當下即時驗證。
  5. encryption 大小寫正規化LdapAdapter 內部一律 .upper() 比對,FE 下拉值固定送 'SSL'/'STARTTLS'(大寫),與 BE 假設一致;但若日後有其他來源寫入小寫值(如手動改 DB),連線行為(是否啟用 STARTTLS)會因正規化前的舊版本差異而有雷(見套件內註解 ldap_adapter.py:41-42,73,已修過一次大小寫不一致的坑)。
  6. 設定不存在時的行為不對稱GETget_system_config_by_key 找不到會 NotFoundSYSTEM_CONFIG_404001);PUTupdate_config_by_group_key 找不到則自動新增(非報錯)。首次使用本頁時,若 GET 400/404,FE getConfig() 沒有特別 catch,會直接留預設值(server:'' / port:389 / ...),使用者未必會注意到「這是全新設定而非讀取失敗」。
  7. system_configs 表本身無 created_user/updated_user 欄位:不同於本專案「審計欄位回傳規範」(CLAUDE.md),本表無操作者留痕,異動追蹤需另查 api_logs / system_logs(見操作日誌頁)。
  8. tenant_id / org_unit_id 欄位存在但本頁未使用:LDAP 設定實務上是全域(root)層級參數,FE 從未傳入這兩欄,隱含「只有一份全租戶共用的 LDAP 設定」的假設;若未來要做多租戶各自 LDAP,需連同 §6 / §9 一起重新設計(非本頁現況涵蓋範圍)。

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)。
  • 測試帳號:見 memory reference_dev_login / .env(憑證不寫入本文件);本頁無角色門檻,任一登入帳號皆可進頁測試。
  • 導航路徑:登入 → 側選單「系統管理」→「LDAP 設定」(/system/ldap-config)。
  • 前置資料:無強制前置資料;首次進頁 system_configs 若無 THIRD_PARTY_LOGIN/LDAP 列,GET 會 404(見 §12 坑 6),儲存後才會建立該列。
  • 實機驗證連線測試:需要一台可連通的 LDAP/AD 測試伺服器;DEV 環境是否已備測試伺服器需另行確認(存疑項,見報告)。
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);本頁相關 feature 檔以 ldap 搜尋(若尚無則屬待補)。
  • 相關文件docs/claude/frontend-overview.md(BE 視角速查);GAI-SD-02/03 涵蓋常規 CRUD 端點細節。