update
This commit is contained in:
parent
e60f568746
commit
7ce2bcb034
|
|
@ -12,6 +12,7 @@
|
|||
- 只处理当前在职用户。
|
||||
- 只更新 AD 中已存在的 `sAMAccountName` 用户,不自动创建 AD 账号。
|
||||
- 更新前会比对 AD 当前值与 EHR 目标值;一致则跳过,不一致才更新差异字段。
|
||||
- 默认写入后立即回读校验;如果 LDAP 返回成功但字段未落地,Job 会报失败并记录未生效字段。
|
||||
- EHR 目标值为空时不会清空 AD 字段。
|
||||
- 如果配置了 `target_sam_accounts`,只对列表中的 AD 账号执行同步。
|
||||
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
"ldap_user_filter": "(sAMAccountName={sAMAccountName})",
|
||||
"ldap_verify_tls": false,
|
||||
"dry_run": true,
|
||||
"verify_after_write": true,
|
||||
"target_sam_accounts": ["fchen", "jqian"],
|
||||
"proxy_alias_domain": "vastaitech.com",
|
||||
"default_company": "Vastai Technologies",
|
||||
|
|
@ -56,6 +58,7 @@
|
|||
|
||||
- `target_sam_accounts`: 可选,字符串数组。传入后只同步这些 AD 账号;不传则同步所有当前用户。
|
||||
- `dry_run`: 可选,默认 `false`。建议首次配置为 `true`,只记录会变更的字段,不写入 AD。
|
||||
- `verify_after_write`: 可选,默认 `true`。真实写入后回读 AD 字段,确认修改已落地。
|
||||
- `ldap_uri`: AD LDAP 地址,如 `ldaps://dc01.vastai.com:636`。
|
||||
- `ldap_base_dn`: AD 用户搜索根 DN。
|
||||
- `ldap_user_filter`: 用户搜索过滤器,默认 `(sAMAccountName={sAMAccountName})`。
|
||||
|
|
@ -75,6 +78,7 @@
|
|||
"ldap_base_dn": "DC=vastai,DC=com",
|
||||
"ldap_verify_tls": false,
|
||||
"dry_run": true,
|
||||
"verify_after_write": true,
|
||||
"target_sam_accounts": ["fchen"],
|
||||
"proxy_alias_domain": "vastaitech.com",
|
||||
"default_company": "Vastai Technologies"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import logging
|
|||
import ssl
|
||||
from typing import Any
|
||||
|
||||
from ldap3 import ALL, MODIFY_REPLACE, SUBTREE, Connection, Server, Tls
|
||||
from ldap3 import ALL, BASE, MODIFY_REPLACE, SUBTREE, Connection, Server, Tls
|
||||
from ldap3.utils.conv import escape_filter_chars
|
||||
|
||||
from extensions.sync_ehr_to_oa.api import SyncEhrToOaApi
|
||||
|
|
@ -123,6 +123,25 @@ class ActiveDirectoryClient:
|
|||
finally:
|
||||
conn.unbind()
|
||||
|
||||
def read_user_by_dn(self, dn: str, *, attributes: list[str] | None = None) -> dict[str, Any] | None:
|
||||
clean_dn = str(dn or "").strip()
|
||||
if not clean_dn:
|
||||
return None
|
||||
conn = self._connect()
|
||||
try:
|
||||
conn.search(
|
||||
clean_dn,
|
||||
"(objectClass=*)",
|
||||
search_scope=BASE,
|
||||
attributes=attributes or ["distinguishedName", "sAMAccountName"],
|
||||
)
|
||||
if not conn.entries:
|
||||
return None
|
||||
entry = conn.entries[0]
|
||||
return {"dn": entry.entry_dn, "attributes": dict(entry.entry_attributes_as_dict)}
|
||||
finally:
|
||||
conn.unbind()
|
||||
|
||||
@staticmethod
|
||||
def _normalize_change_value(value: Any) -> list[Any]:
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
|
|
@ -155,6 +174,7 @@ class ActiveDirectoryClient:
|
|||
ok = bool(conn.modify(clean_dn, changes))
|
||||
if not ok:
|
||||
raise RuntimeError(f"AD modify failed dn={clean_dn!r} result={conn.result!r}")
|
||||
logger.info("AD modify success: dn=%s result=%s changed_attrs=%s", clean_dn, conn.result, sorted(changes.keys()))
|
||||
return True
|
||||
finally:
|
||||
conn.unbind()
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ class SyncEhrToAdUserJob(BaseJob):
|
|||
ldap_verify_tls = _to_bool_or_none(params.get("ldap_verify_tls"))
|
||||
dry_run = _to_bool_or_none(params.get("dry_run"))
|
||||
verbose_trace = _to_bool_or_none(params.get("verbose_trace"))
|
||||
verify_after_write = _to_bool_or_none(params.get("verify_after_write"))
|
||||
if ldap_use_starttls is None:
|
||||
ldap_use_starttls = False
|
||||
if ldap_verify_tls is None:
|
||||
|
|
@ -355,6 +356,8 @@ class SyncEhrToAdUserJob(BaseJob):
|
|||
dry_run = False
|
||||
if verbose_trace is None:
|
||||
verbose_trace = True
|
||||
if verify_after_write is None:
|
||||
verify_after_write = True
|
||||
|
||||
stop_time = params.get("stop_time")
|
||||
capacity = int(params.get("capacity") or 300)
|
||||
|
|
@ -586,6 +589,16 @@ class SyncEhrToAdUserJob(BaseJob):
|
|||
|
||||
changed = ad.modify_user(str(ad_user["dn"]), diff_attributes, dry_run=dry_run)
|
||||
if changed:
|
||||
if verify_after_write and not dry_run:
|
||||
verify_attrs = list(diff_attributes.keys())
|
||||
readback_user = ad.read_user_by_dn(str(ad_user["dn"]), attributes=verify_attrs)
|
||||
readback_attrs = (readback_user or {}).get("attributes") or {}
|
||||
remaining_diff = _diff_ad_attributes(readback_attrs, diff_attributes)
|
||||
if remaining_diff:
|
||||
raise RuntimeError(
|
||||
"AD modify returned success but readback still differs: "
|
||||
f"sam={sam!r} dn={ad_user['dn']!r} attrs={sorted(remaining_diff.keys())}"
|
||||
)
|
||||
updated += 1
|
||||
if verbose_trace:
|
||||
logger.info(
|
||||
|
|
|
|||
Loading…
Reference in New Issue