Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gateway/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,12 @@ def load_gateway_config() -> GatewayConfig:
if "dm_mention_threads" in matrix_cfg and not os.getenv("MATRIX_DM_MENTION_THREADS"):
os.environ["MATRIX_DM_MENTION_THREADS"] = str(matrix_cfg["dm_mention_threads"]).lower()

# Feishu settings → env vars (env vars take precedence)
feishu_cfg = yaml_cfg.get("feishu", {})
if isinstance(feishu_cfg, dict):
if "allow_bots" in feishu_cfg and not os.getenv("FEISHU_ALLOW_BOTS"):
os.environ["FEISHU_ALLOW_BOTS"] = str(feishu_cfg["allow_bots"]).lower()

except Exception as e:
logger.warning(
"Failed to process config.yaml — falling back to .env / gateway.json values. "
Expand Down
255 changes: 205 additions & 50 deletions gateway/platforms/feishu.py

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -3958,6 +3958,11 @@ def _is_user_authorized(self, source: SessionSource) -> bool:
Platform.QQBOT: "QQ_ALLOW_ALL_USERS",
Platform.YUANBAO: "YUANBAO_ALLOW_ALL_USERS",
}
# Bots admitted by {PLATFORM}_ALLOW_BOTS bypass the human allowlist (#4466).
platform_allow_bots_map = {
Platform.DISCORD: "DISCORD_ALLOW_BOTS",
Platform.FEISHU: "FEISHU_ALLOW_BOTS",
}

# Plugin platforms: check the registry for auth env var names
if source.platform not in platform_env_map:
Expand All @@ -3977,14 +3982,9 @@ def _is_user_authorized(self, source: SessionSource) -> bool:
if platform_allow_all_var and os.getenv(platform_allow_all_var, "").lower() in ("true", "1", "yes"):
return True

# Discord bot senders that passed the DISCORD_ALLOW_BOTS platform
# filter are already authorized at the platform level — skip the
# user allowlist. Without this, bot messages allowed by
# DISCORD_ALLOW_BOTS=mentions/all would be rejected here with
# "Unauthorized user" (fixes #4466).
if source.platform == Platform.DISCORD and getattr(source, "is_bot", False):
allow_bots = os.getenv("DISCORD_ALLOW_BOTS", "none").lower().strip()
if allow_bots in ("mentions", "all"):
if getattr(source, "is_bot", False):
allow_bots_var = platform_allow_bots_map.get(source.platform)
if allow_bots_var and os.getenv(allow_bots_var, "none").lower().strip() in ("mentions", "all"):
return True

# Discord role-based access (DISCORD_ALLOWED_ROLES): the adapter's
Expand Down
65 changes: 65 additions & 0 deletions tests/gateway/feishu_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Shared fixtures for Feishu adapter tests (admission, group policy, dispatch)."""

from __future__ import annotations

import threading
from types import SimpleNamespace
from typing import Any, Optional


def make_sender(sender_type: str = "user", open_id: str = "ou_human",
user_id: Optional[str] = None, union_id: Optional[str] = None) -> Any:
return SimpleNamespace(
sender_type=sender_type,
sender_id=SimpleNamespace(open_id=open_id, user_id=user_id, union_id=union_id),
)


def make_message(message_id: str = "om_xxx", chat_type: str = "p2p",
chat_id: str = "oc_1", mentions: Optional[list] = None) -> Any:
return SimpleNamespace(
message_id=message_id,
chat_type=chat_type,
chat_id=chat_id,
mentions=mentions,
content="",
message_type="text",
)


def make_adapter_skeleton(
*,
bot_open_id: str = "ou_me",
bot_user_id: str = "",
allow_bots: str = "none",
require_mention: bool = True,
group_policy: str = "allowlist",
) -> Any:
from gateway.platforms.feishu import FeishuAdapter

adapter = object.__new__(FeishuAdapter)
adapter._bot_open_id = bot_open_id
adapter._bot_user_id = bot_user_id
adapter._bot_name = ""
adapter._app_id = ""
adapter._admins = set()
adapter._group_rules = {}
adapter._group_policy = group_policy
adapter._default_group_policy = group_policy
adapter._allowed_group_users = frozenset()
adapter._allow_bots = allow_bots
adapter._require_mention = require_mention
return adapter


def install_dedup_state(adapter: Any, seen: Optional[dict] = None) -> None:
adapter._seen_message_ids = dict(seen) if seen else {}
adapter._seen_message_order = list((seen or {}).keys())
adapter._dedup_cache_size = 100
adapter._dedup_lock = threading.Lock()
adapter._dedup_state_path = None
adapter._persist_seen_message_ids = lambda: None


def stub_mention(adapter: Any, mentions_self: bool) -> None:
adapter._mentions_self = lambda _message: mentions_self
32 changes: 32 additions & 0 deletions tests/gateway/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,38 @@ def test_bridges_slack_channel_prompts_from_config_yaml(self, tmp_path, monkeypa
"C01ABC": "Code review mode",
}

def test_bridges_feishu_allow_bots_from_config_yaml_to_env(self, tmp_path, monkeypatch):
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
"feishu:\n allow_bots: mentions\n",
encoding="utf-8",
)

monkeypatch.setenv("HERMES_HOME", str(hermes_home))
monkeypatch.delenv("FEISHU_ALLOW_BOTS", raising=False)

load_gateway_config()

assert os.environ.get("FEISHU_ALLOW_BOTS") == "mentions"

def test_feishu_allow_bots_env_takes_precedence_over_config_yaml(self, tmp_path, monkeypatch):
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
"feishu:\n allow_bots: all\n",
encoding="utf-8",
)

monkeypatch.setenv("HERMES_HOME", str(hermes_home))
monkeypatch.setenv("FEISHU_ALLOW_BOTS", "none")

load_gateway_config()

assert os.environ.get("FEISHU_ALLOW_BOTS") == "none"

def test_invalid_quick_commands_in_config_yaml_are_ignored(self, tmp_path, monkeypatch):
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
Expand Down
Loading
Loading