225 lines
8.1 KiB
Python
225 lines
8.1 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from yuxi.channels.adapters.slack.security import (
|
||
|
|
DmPolicy,
|
||
|
|
GroupPolicy,
|
||
|
|
SecurityConfig,
|
||
|
|
SecurityDecision,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestDmPolicy:
|
||
|
|
def test_values(self):
|
||
|
|
assert DmPolicy.OPEN == "open"
|
||
|
|
assert DmPolicy.PAIRING == "pairing"
|
||
|
|
assert DmPolicy.ALLOWLIST == "allowlist"
|
||
|
|
assert DmPolicy.DISABLED == "disabled"
|
||
|
|
|
||
|
|
|
||
|
|
class TestGroupPolicy:
|
||
|
|
def test_values(self):
|
||
|
|
assert GroupPolicy.OPEN == "open"
|
||
|
|
assert GroupPolicy.ALLOWLIST == "allowlist"
|
||
|
|
assert GroupPolicy.DISABLED == "disabled"
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityConfigDefaults:
|
||
|
|
def test_default_values(self):
|
||
|
|
config = SecurityConfig()
|
||
|
|
assert config.dm_policy == DmPolicy.ALLOWLIST
|
||
|
|
assert config.group_policy == GroupPolicy.ALLOWLIST
|
||
|
|
assert config.require_mention is False
|
||
|
|
assert config.allow_from == set()
|
||
|
|
assert config.allow_from_wildcard is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityConfigFromConfig:
|
||
|
|
def test_empty_config(self):
|
||
|
|
config = SecurityConfig.from_config(None)
|
||
|
|
assert config.dm_policy == DmPolicy.ALLOWLIST
|
||
|
|
|
||
|
|
def test_empty_dict(self):
|
||
|
|
config = SecurityConfig.from_config({})
|
||
|
|
assert config.dm_policy == DmPolicy.ALLOWLIST
|
||
|
|
|
||
|
|
def test_dm_policy_open(self):
|
||
|
|
config = SecurityConfig.from_config({"dm_policy": "open"})
|
||
|
|
assert config.dm_policy == DmPolicy.OPEN
|
||
|
|
|
||
|
|
def test_dm_policy_disabled(self):
|
||
|
|
config = SecurityConfig.from_config({"dm_policy": "disabled"})
|
||
|
|
assert config.dm_policy == DmPolicy.DISABLED
|
||
|
|
|
||
|
|
def test_dm_policy_pairing(self):
|
||
|
|
config = SecurityConfig.from_config({"dm_policy": "pairing"})
|
||
|
|
assert config.dm_policy == DmPolicy.PAIRING
|
||
|
|
|
||
|
|
def test_dm_policy_invalid_fallback(self):
|
||
|
|
config = SecurityConfig.from_config({"dm_policy": "invalid_value"})
|
||
|
|
assert config.dm_policy == DmPolicy.ALLOWLIST
|
||
|
|
|
||
|
|
def test_dm_policy_uppercase_env(self):
|
||
|
|
config = SecurityConfig.from_config({"DM_POLICY": "open"})
|
||
|
|
assert config.dm_policy == DmPolicy.OPEN
|
||
|
|
|
||
|
|
def test_group_policy_from_config(self):
|
||
|
|
config = SecurityConfig.from_config({"group_policy": "open"})
|
||
|
|
assert config.group_policy == GroupPolicy.OPEN
|
||
|
|
|
||
|
|
def test_group_policy_uppercase_env(self):
|
||
|
|
config = SecurityConfig.from_config({"GROUP_POLICY": "disabled"})
|
||
|
|
assert config.group_policy == GroupPolicy.DISABLED
|
||
|
|
|
||
|
|
def test_group_policy_invalid_fallback(self):
|
||
|
|
config = SecurityConfig.from_config({"group_policy": "bad"})
|
||
|
|
assert config.group_policy == GroupPolicy.ALLOWLIST
|
||
|
|
|
||
|
|
def test_require_mention(self):
|
||
|
|
config = SecurityConfig.from_config({"require_mention": True})
|
||
|
|
assert config.require_mention is True
|
||
|
|
|
||
|
|
def test_require_mention_uppercase_env(self):
|
||
|
|
config = SecurityConfig.from_config({"REQUIRE_MENTION": True})
|
||
|
|
assert config.require_mention is True
|
||
|
|
|
||
|
|
def test_allow_from_list(self):
|
||
|
|
config = SecurityConfig.from_config({"allow_from": ["U001", "C001"]})
|
||
|
|
assert config.allow_from == {"U001", "C001"}
|
||
|
|
|
||
|
|
def test_allow_from_comma_string(self):
|
||
|
|
config = SecurityConfig.from_config({"allow_from": "U001, U002, C001"})
|
||
|
|
assert config.allow_from == {"U001", "U002", "C001"}
|
||
|
|
|
||
|
|
def test_allow_from_wildcard(self):
|
||
|
|
config = SecurityConfig.from_config({"allow_from": ["*"]})
|
||
|
|
assert config.allow_from_wildcard is True
|
||
|
|
assert config.allow_from == set()
|
||
|
|
|
||
|
|
def test_allow_from_mixed_wildcard_and_ids(self):
|
||
|
|
config = SecurityConfig.from_config({"allow_from": ["*", "U001"]})
|
||
|
|
assert config.allow_from_wildcard is True
|
||
|
|
assert config.allow_from == {"U001"}
|
||
|
|
|
||
|
|
def test_allow_from_invalid_format_ignored(self):
|
||
|
|
config = SecurityConfig.from_config({"allow_from": ["not_a_slack_id"]})
|
||
|
|
assert "not_a_slack_id" in config.allow_from
|
||
|
|
|
||
|
|
def test_allow_from_empty_entries_filtered(self):
|
||
|
|
config = SecurityConfig.from_config({"allow_from": ["U001", "", " ", "C001"]})
|
||
|
|
assert config.allow_from == {"U001", "C001"}
|
||
|
|
|
||
|
|
def test_allow_from_non_list_scalar(self):
|
||
|
|
config = SecurityConfig.from_config({"allow_from": 123})
|
||
|
|
assert config.allow_from == set()
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityConfigIsAllowed:
|
||
|
|
def test_is_allowed_user_no_allowlist(self):
|
||
|
|
config = SecurityConfig()
|
||
|
|
assert config.is_allowed_user("U001") is False
|
||
|
|
|
||
|
|
def test_is_allowed_user_in_allowlist(self):
|
||
|
|
config = SecurityConfig(allow_from={"U001"})
|
||
|
|
assert config.is_allowed_user("U001") is True
|
||
|
|
assert config.is_allowed_user("U002") is False
|
||
|
|
|
||
|
|
def test_is_allowed_user_wildcard(self):
|
||
|
|
config = SecurityConfig(allow_from_wildcard=True)
|
||
|
|
assert config.is_allowed_user("U001") is True
|
||
|
|
assert config.is_allowed_user("ANYONE") is True
|
||
|
|
|
||
|
|
def test_is_allowed_channel_in_allowlist(self):
|
||
|
|
config = SecurityConfig(allow_from={"C001"})
|
||
|
|
assert config.is_allowed_channel("C001") is True
|
||
|
|
assert config.is_allowed_channel("C002") is False
|
||
|
|
|
||
|
|
def test_is_allowed_channel_wildcard(self):
|
||
|
|
config = SecurityConfig(allow_from_wildcard=True)
|
||
|
|
assert config.is_allowed_channel("C001") is True
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityConfigDmPolicy:
|
||
|
|
def test_dm_open(self):
|
||
|
|
config = SecurityConfig(dm_policy=DmPolicy.OPEN)
|
||
|
|
assert config.is_allowed_dm("U001") is True
|
||
|
|
|
||
|
|
def test_dm_disabled(self):
|
||
|
|
config = SecurityConfig(dm_policy=DmPolicy.DISABLED)
|
||
|
|
assert config.is_allowed_dm("U001") is False
|
||
|
|
|
||
|
|
def test_dm_pairing(self):
|
||
|
|
config = SecurityConfig(dm_policy=DmPolicy.PAIRING)
|
||
|
|
assert config.is_allowed_dm("U001") is True
|
||
|
|
|
||
|
|
def test_dm_allowlist_user_in_list(self):
|
||
|
|
config = SecurityConfig(dm_policy=DmPolicy.ALLOWLIST, allow_from={"U001"})
|
||
|
|
assert config.is_allowed_dm("U001") is True
|
||
|
|
assert config.is_allowed_dm("U002") is False
|
||
|
|
|
||
|
|
def test_dm_allowlist_empty(self):
|
||
|
|
config = SecurityConfig(dm_policy=DmPolicy.ALLOWLIST, allow_from=set())
|
||
|
|
assert config.is_allowed_dm("U001") is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityConfigGroupPolicy:
|
||
|
|
def test_group_open(self):
|
||
|
|
config = SecurityConfig(group_policy=GroupPolicy.OPEN)
|
||
|
|
assert config.is_allowed_group("C001") is True
|
||
|
|
|
||
|
|
def test_group_disabled(self):
|
||
|
|
config = SecurityConfig(group_policy=GroupPolicy.DISABLED)
|
||
|
|
assert config.is_allowed_group("C001") is False
|
||
|
|
|
||
|
|
def test_group_allowlist_channel_in_list(self):
|
||
|
|
config = SecurityConfig(group_policy=GroupPolicy.ALLOWLIST, allow_from={"C001"})
|
||
|
|
assert config.is_allowed_group("C001") is True
|
||
|
|
assert config.is_allowed_group("C002") is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityConfigShouldRequireMention:
|
||
|
|
def test_require_mention_true(self):
|
||
|
|
config = SecurityConfig(require_mention=True)
|
||
|
|
assert config.should_require_mention("C001") is True
|
||
|
|
|
||
|
|
def test_require_mention_false(self):
|
||
|
|
config = SecurityConfig(require_mention=False)
|
||
|
|
assert config.should_require_mention("C001") is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityConfigToDict:
|
||
|
|
def test_to_dict(self):
|
||
|
|
config = SecurityConfig(
|
||
|
|
dm_policy=DmPolicy.OPEN,
|
||
|
|
group_policy=GroupPolicy.ALLOWLIST,
|
||
|
|
require_mention=True,
|
||
|
|
allow_from={"U001", "C001"},
|
||
|
|
allow_from_wildcard=False,
|
||
|
|
)
|
||
|
|
d = config.to_dict()
|
||
|
|
assert d["dm_policy"] == "open"
|
||
|
|
assert d["group_policy"] == "allowlist"
|
||
|
|
assert d["require_mention"] is True
|
||
|
|
assert d["allow_from"] == ["C001", "U001"]
|
||
|
|
assert d["allow_from_wildcard"] is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestSecurityDecision:
|
||
|
|
def test_allowed_decision(self):
|
||
|
|
decision = SecurityDecision(allowed=True, reason="user_allowed")
|
||
|
|
assert decision.allowed is True
|
||
|
|
assert decision.reason == "user_allowed"
|
||
|
|
assert decision.requires_pairing is False
|
||
|
|
assert decision.pairing_code is None
|
||
|
|
|
||
|
|
def test_pairing_required_decision(self):
|
||
|
|
decision = SecurityDecision(
|
||
|
|
allowed=False,
|
||
|
|
reason="pairing_required",
|
||
|
|
requires_pairing=True,
|
||
|
|
pairing_code="ABC123",
|
||
|
|
)
|
||
|
|
assert decision.allowed is False
|
||
|
|
assert decision.requires_pairing is True
|
||
|
|
assert decision.pairing_code == "ABC123"
|