mirror of
https://github.com/element-hq/synapse.git
synced 2025-03-31 03:45:13 +00:00
Merge 9edc201a7b
into 3c188231c7
This commit is contained in:
commit
b529ee82a6
7 changed files with 112 additions and 2 deletions
1
changelog.d/18288.feature
Normal file
1
changelog.d/18288.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add support for MSC4155 Invite Filtering.
|
|
@ -280,6 +280,8 @@ class AccountDataTypes:
|
|||
IGNORED_USER_LIST: Final = "m.ignored_user_list"
|
||||
TAG: Final = "m.tag"
|
||||
PUSH_RULES: Final = "m.push_rules"
|
||||
# MSC4155: Invite filtering
|
||||
INVITE_PERMISSION_CONFIG: Final = "org.matrix.msc4155.invite_permission_config"
|
||||
|
||||
|
||||
class HistoryVisibility:
|
||||
|
|
|
@ -560,3 +560,6 @@ class ExperimentalConfig(Config):
|
|||
|
||||
# MSC4076: Add `disable_badge_count`` to pusher configuration
|
||||
self.msc4076_enabled: bool = experimental.get("msc4076_enabled", False)
|
||||
|
||||
# MSC4076: Invite filtering
|
||||
self.msc4155_enabled: bool = experimental.get("msc4155_enabled", False)
|
||||
|
|
|
@ -78,7 +78,7 @@ from synapse.replication.http.federation import (
|
|||
ReplicationStoreRoomOnOutlierMembershipRestServlet,
|
||||
)
|
||||
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
|
||||
from synapse.types import JsonDict, StrCollection, get_domain_from_id
|
||||
from synapse.types import JsonDict, StrCollection, UserID, get_domain_from_id
|
||||
from synapse.types.state import StateFilter
|
||||
from synapse.util.async_helpers import Linearizer
|
||||
from synapse.util.retryutils import NotRetryingDestination
|
||||
|
@ -1089,6 +1089,18 @@ class FederationHandler:
|
|||
if event.state_key == self._server_notices_mxid:
|
||||
raise SynapseError(HTTPStatus.FORBIDDEN, "Cannot invite this user")
|
||||
|
||||
if self.config.experimental.msc4155_enabled:
|
||||
invite_config = await self.store.get_invite_config_for_user(event.state_key)
|
||||
if not invite_config.invite_allowed(UserID.from_string(event.sender)):
|
||||
logger.info(
|
||||
f"User {event.state_key} rejected invite from {event.sender}"
|
||||
)
|
||||
raise SynapseError(
|
||||
403,
|
||||
"You are not permitted to invite this user.",
|
||||
errcode=Codes.FORBIDDEN,
|
||||
)
|
||||
|
||||
# We retrieve the room member handler here as to not cause a cyclic dependency
|
||||
member_handler = self.hs.get_room_member_handler()
|
||||
# We don't rate limit based on room ID, as that should be done by
|
||||
|
|
|
@ -904,6 +904,18 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
|||
logger.info("Blocking invite due to spam checker")
|
||||
block_invite_result = spam_check
|
||||
|
||||
if self.config.experimental.msc4155_enabled:
|
||||
invite_config = await self.store.get_invite_config_for_user(target_id)
|
||||
if not invite_config.invite_allowed(requester.user):
|
||||
logger.info(
|
||||
f"User {target_id} rejected invite from {requester.user}"
|
||||
)
|
||||
raise SynapseError(
|
||||
403,
|
||||
"You are not permitted to invite this user.",
|
||||
errcode=Codes.FORBIDDEN,
|
||||
)
|
||||
|
||||
if block_invite_result is not None:
|
||||
raise SynapseError(
|
||||
403,
|
||||
|
|
|
@ -174,6 +174,8 @@ class VersionsRestServlet(RestServlet):
|
|||
"org.matrix.simplified_msc3575": msc3575_enabled,
|
||||
# Arbitrary key-value profile fields.
|
||||
"uk.tcpip.msc4133": self.config.experimental.msc4133_enabled,
|
||||
# MSC4155: Invite filtering
|
||||
"org.matrix.msc4155": self.config.experimental.msc4155_enabled,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#
|
||||
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
|
@ -44,7 +45,7 @@ from synapse.storage.database import (
|
|||
from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
|
||||
from synapse.storage.databases.main.push_rule import PushRulesWorkerStore
|
||||
from synapse.storage.util.id_generators import MultiWriterIdGenerator
|
||||
from synapse.types import JsonDict, JsonMapping
|
||||
from synapse.types import JsonDict, JsonMapping, UserID
|
||||
from synapse.util import json_encoder
|
||||
from synapse.util.caches.descriptors import cached
|
||||
from synapse.util.caches.stream_change_cache import StreamChangeCache
|
||||
|
@ -55,6 +56,64 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InviteRule(Enum):
|
||||
"""Enum to define the sorting method used when returning destinations."""
|
||||
|
||||
ALLOW = "allow"
|
||||
BLOCK = "block"
|
||||
|
||||
class InviteRulesConfig:
|
||||
default: InviteRule
|
||||
user_exceptions: Dict[UserID, InviteRule]
|
||||
server_exceptions: Dict[str, InviteRule]
|
||||
|
||||
def __init__(self, account_data: Optional[JsonMapping]):
|
||||
account_data_safe = account_data or {}
|
||||
|
||||
default_str = account_data_safe.get("default", "allow")
|
||||
self.default = (
|
||||
(InviteRule(default_str) or InviteRule.ALLOW)
|
||||
if isinstance(default_str, str)
|
||||
else InviteRule.ALLOW
|
||||
)
|
||||
self.user_exceptions = {}
|
||||
self.server_exceptions = {}
|
||||
|
||||
for user_id, rule in account_data_safe.get("user_exceptions", {}):
|
||||
if not UserID.is_valid(user_id):
|
||||
continue
|
||||
if InviteRule(rule) is None:
|
||||
continue
|
||||
self.user_exceptions[UserID.from_string(user_id)] = InviteRule(rule)
|
||||
|
||||
for server_name, rule in account_data_safe.get("server_exceptions", {}):
|
||||
if not isinstance(server_name, str) or len(server_name) < 1:
|
||||
continue
|
||||
if InviteRule(rule) is None:
|
||||
continue
|
||||
self.server_exceptions[server_name] = InviteRule(rule)
|
||||
|
||||
def invite_allowed(self, user_id: UserID) -> bool:
|
||||
user_rule = self.user_exceptions.get(user_id)
|
||||
if user_rule:
|
||||
logger.debug(
|
||||
"invite_allowed user_rule %s => %s", user_id.to_string(), user_rule
|
||||
)
|
||||
return user_rule == InviteRule.ALLOW
|
||||
|
||||
server_rule = self.server_exceptions.get(user_id.domain)
|
||||
if server_rule:
|
||||
logger.debug(
|
||||
"invite_allowed server_rule %s => %s", user_id.to_string(), server_rule
|
||||
)
|
||||
return server_rule == InviteRule.ALLOW
|
||||
|
||||
logger.debug(
|
||||
"invite_allowed default %s => %s", user_id.to_string(), self.default
|
||||
)
|
||||
return self.default == InviteRule.ALLOW
|
||||
|
||||
|
||||
class AccountDataWorkerStore(PushRulesWorkerStore, CacheInvalidationWorkerStore):
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -1016,6 +1075,25 @@ class AccountDataWorkerStore(PushRulesWorkerStore, CacheInvalidationWorkerStore)
|
|||
|
||||
return number_deleted
|
||||
|
||||
async def get_invite_config_for_user(self, user_id: str) -> InviteRulesConfig:
|
||||
"""
|
||||
Get the invite configuration for the current user.
|
||||
|
||||
If experimental MSC3391 support is enabled, any entries with an empty
|
||||
content body are excluded; as this means they have been deleted.
|
||||
|
||||
Args:
|
||||
user_id: The user to get the account_data for.
|
||||
|
||||
Returns:
|
||||
The global account_data.
|
||||
"""
|
||||
|
||||
data = await self.get_global_account_data_by_type_for_user(
|
||||
user_id, AccountDataTypes.INVITE_PERMISSION_CONFIG
|
||||
)
|
||||
return InviteRulesConfig(data)
|
||||
|
||||
|
||||
class AccountDataStore(AccountDataWorkerStore):
|
||||
pass
|
||||
|
|
Loading…
Add table
Reference in a new issue