fix(gateway): bridge nested DingTalk allowed_users into auth env

The DingTalk docs offer gateway.platforms.dingtalk.extra.allowed_users
as the config.yaml alternative to DINGTALK_ALLOWED_USERS. The adapter
honors it (_load_allowed_users reads PlatformConfig.extra), but gateway
authorization (_is_user_authorized in gateway/authz_mixin.py) only
consults the env var, and load_gateway_config() bridged the allowlist
to the env var only from a top-level dingtalk: block. A nested-only
allowlist therefore passed the adapter and was then denied at the
gateway - listed users fell through to pairing/default-deny in DMs.

Extend the DingTalk YAML->env bridge to fall back to the merged nested
platform config (gateway.platforms / platforms), mirroring the existing
platforms.discord.extra.allow_from precedent. Precedence is unchanged:
an explicit DINGTALK_ALLOWED_USERS env var still wins, then the
top-level dingtalk: block, then the nested extra.

Also correct the docs' claim that the two allowlists are "merged" when
both are set - that behavior never existed (the doc line came from a
docs-only sweep); the effective result is the intersection of the two
gates, so the docs now recommend configuring one or the other.

Repro (before): config.yaml containing only the nested allowlist ->
adapter._is_user_allowed("user-id-1") is True but
runner._is_user_authorized(...) is False. After: both True; unlisted
users are still denied.
This commit is contained in:
Frowtek 2026-06-12 16:35:16 +03:00 committed by Teknium
parent 330b224525
commit 222772ad61
4 changed files with 185 additions and 2 deletions

View file

@ -1660,6 +1660,31 @@ def _apply_yaml_config(yaml_cfg: dict, dingtalk_cfg: dict) -> dict | None:
ac = ",".join(str(v) for v in ac)
os.environ["DINGTALK_ALLOWED_CHATS"] = str(ac)
allowed = dingtalk_cfg.get("allowed_users")
if allowed is None:
# Fall back to the documented nested paths (#44928). The docs
# (website/docs/user-guide/messaging/dingtalk.md) configure the
# allowlist at gateway.platforms.dingtalk.extra.allowed_users; the
# adapter reads it from PlatformConfig.extra, but gateway
# authorization (_is_user_authorized in gateway/authz_mixin.py)
# only consults DINGTALK_ALLOWED_USERS — without this bridge a
# nested-only allowlist passes the adapter and is then denied at
# the gateway. Check this block's own extra first (the dispatch
# loop passes the platforms block here when no top-level
# ``dingtalk:`` section exists), then both nested containers.
_extra = dingtalk_cfg.get("extra")
if isinstance(_extra, dict):
allowed = _extra.get("allowed_users")
if allowed is None:
_gw = yaml_cfg.get("gateway")
_gw_platforms = _gw.get("platforms") if isinstance(_gw, dict) else None
for _container in (_gw_platforms, yaml_cfg.get("platforms")):
if not isinstance(_container, dict):
continue
_dt = _container.get("dingtalk")
_dt_extra = _dt.get("extra") if isinstance(_dt, dict) else None
if isinstance(_dt_extra, dict) and _dt_extra.get("allowed_users") is not None:
allowed = _dt_extra.get("allowed_users")
break
if allowed is not None and not os.getenv("DINGTALK_ALLOWED_USERS"):
if isinstance(allowed, list):
allowed = ",".join(str(v) for v in allowed)