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