diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py
index 51b9772329..a3a7326d94 100644
--- a/synapse/handlers/room_member.py
+++ b/synapse/handlers/room_member.py
@@ -53,6 +53,7 @@ from synapse.metrics import event_processing_positions
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.replication.http.push import ReplicationCopyPusherRestServlet
from synapse.storage.databases.main.state_deltas import StateDelta
+from synapse.storage.invite_rule import InviteRule
from synapse.types import (
JsonDict,
Requester,
@@ -98,7 +99,6 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
self.federation_handler = hs.get_federation_handler()
self.directory_handler = hs.get_directory_handler()
- self.identity_handler = hs.get_identity_handler()
self.registration_handler = hs.get_registration_handler()
self.profile_handler = hs.get_profile_handler()
self.event_creation_handler = hs.get_event_creation_handler()
@@ -122,7 +122,6 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
hs.get_module_api_callbacks().third_party_event_rules
)
self._server_notices_mxid = self.config.servernotices.server_notices_mxid
- self._enable_lookup = hs.config.registration.enable_3pid_lookup
self.allow_per_room_profiles = self.config.server.allow_per_room_profiles
self._join_rate_limiter_local = Ratelimiter(
@@ -158,6 +157,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
store=self.store,
clock=self.clock,
cfg=hs.config.ratelimiting.rc_invites_per_room,
+ ratelimit_callbacks=hs.get_module_api_callbacks().ratelimit,
)
# Ratelimiter for invites, keyed by recipient (across all rooms, all
@@ -166,6 +166,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
store=self.store,
clock=self.clock,
cfg=hs.config.ratelimiting.rc_invites_per_user,
+ ratelimit_callbacks=hs.get_module_api_callbacks().ratelimit,
)
# Ratelimiter for invites, keyed by issuer (across all rooms, all
@@ -174,6 +175,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
store=self.store,
clock=self.clock,
cfg=hs.config.ratelimiting.rc_invites_per_issuer,
+ ratelimit_callbacks=hs.get_module_api_callbacks().ratelimit,
)
self._third_party_invite_limiter = Ratelimiter(
@@ -912,6 +914,21 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
additional_fields=block_invite_result[1],
)
+ # check the invitee's configuration and apply rules. Admins on the server can bypass.
+ if not is_requester_admin:
+ invite_config = await self.store.get_invite_config_for_user(target_id)
+ rule = invite_config.get_invite_rule(requester.user.to_string())
+ if rule == InviteRule.BLOCK:
+ logger.info(
+ f"Automatically rejecting invite from {target_id} due to the the invite filtering rules of {requester.user}"
+ )
+ raise SynapseError(
+ 403,
+ "You are not permitted to invite this user.",
+ errcode=Codes.INVITE_BLOCKED,
+ )
+ # InviteRule.IGNORE is handled at the sync layer.
+
# An empty prev_events list is allowed as long as the auth_event_ids are present
if prev_event_ids is not None:
return await self._local_membership_update(
@@ -1190,6 +1207,26 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
origin_server_ts=origin_server_ts,
)
+ async def check_for_any_membership_in_room(
+ self, *, user_id: str, room_id: str
+ ) -> None:
+ """
+ Check if the user has any membership in the room and raise error if not.
+
+ Args:
+ user_id: The user to check.
+ room_id: The room to check.
+
+ Raises:
+ AuthError if the user doesn't have any membership in the room.
+ """
+ result = await self.store.get_local_current_membership_for_user_in_room(
+ user_id=user_id, room_id=room_id
+ )
+
+ if result is None or result == (None, None):
+ raise AuthError(403, f"User {user_id} has no membership in room {room_id}")
+
async def _should_perform_remote_join(
self,
user_id: str,
@@ -1302,11 +1339,11 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
# If this is going to be a local join, additional information must
# be included in the event content in order to efficiently validate
# the event.
- content[EventContentFields.AUTHORISING_USER] = (
- await self.event_auth_handler.get_user_which_could_invite(
- room_id,
- state_before_join,
- )
+ content[
+ EventContentFields.AUTHORISING_USER
+ ] = await self.event_auth_handler.get_user_which_could_invite(
+ room_id,
+ state_before_join,
)
return False, []
@@ -1415,9 +1452,9 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
if requester is not None:
sender = UserID.from_string(event.sender)
- assert (
- sender == requester.user
- ), "Sender (%s) must be same as requester (%s)" % (sender, requester.user)
+ assert sender == requester.user, (
+ "Sender (%s) must be same as requester (%s)" % (sender, requester.user)
+ )
assert self.hs.is_mine(sender), "Sender must be our own: %s" % (sender,)
else:
requester = types.create_requester(target_user)
@@ -1572,230 +1609,6 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
return UserID.from_string(invite.sender)
return None
- async def do_3pid_invite(
- self,
- room_id: str,
- inviter: UserID,
- medium: str,
- address: str,
- id_server: str,
- requester: Requester,
- txn_id: Optional[str],
- id_access_token: str,
- prev_event_ids: Optional[List[str]] = None,
- depth: Optional[int] = None,
- ) -> Tuple[str, int]:
- """Invite a 3PID to a room.
-
- Args:
- room_id: The room to invite the 3PID to.
- inviter: The user sending the invite.
- medium: The 3PID's medium.
- address: The 3PID's address.
- id_server: The identity server to use.
- requester: The user making the request.
- txn_id: The transaction ID this is part of, or None if this is not
- part of a transaction.
- id_access_token: Identity server access token.
- depth: Override the depth used to order the event in the DAG.
- prev_event_ids: The event IDs to use as the prev events
- Should normally be set to None, which will cause the depth to be calculated
- based on the prev_events.
-
- Returns:
- Tuple of event ID and stream ordering position
-
- Raises:
- ShadowBanError if the requester has been shadow-banned.
- """
- if self.config.server.block_non_admin_invites:
- is_requester_admin = await self.auth.is_server_admin(requester)
- if not is_requester_admin:
- raise SynapseError(
- 403, "Invites have been disabled on this server", Codes.FORBIDDEN
- )
-
- if requester.shadow_banned:
- # We randomly sleep a bit just to annoy the requester.
- await self.clock.sleep(random.randint(1, 10))
- raise ShadowBanError()
-
- # We need to rate limit *before* we send out any 3PID invites, so we
- # can't just rely on the standard ratelimiting of events.
- await self._third_party_invite_limiter.ratelimit(requester)
-
- can_invite = await self._third_party_event_rules.check_threepid_can_be_invited(
- medium, address, room_id
- )
- if not can_invite:
- raise SynapseError(
- 403,
- "This third-party identifier can not be invited in this room",
- Codes.FORBIDDEN,
- )
-
- if not self._enable_lookup:
- raise SynapseError(
- 403, "Looking up third-party identifiers is denied from this server"
- )
-
- invitee = await self.identity_handler.lookup_3pid(
- id_server, medium, address, id_access_token
- )
-
- if invitee:
- # Note that update_membership with an action of "invite" can raise
- # a ShadowBanError, but this was done above already.
- # We don't check the invite against the spamchecker(s) here (through
- # user_may_invite) because we'll do it further down the line anyway (in
- # update_membership_locked).
- event_id, stream_id = await self.update_membership(
- requester, UserID.from_string(invitee), room_id, "invite", txn_id=txn_id
- )
- else:
- # Check if the spamchecker(s) allow this invite to go through.
- spam_check = (
- await self._spam_checker_module_callbacks.user_may_send_3pid_invite(
- inviter_userid=requester.user.to_string(),
- medium=medium,
- address=address,
- room_id=room_id,
- )
- )
- if spam_check != self._spam_checker_module_callbacks.NOT_SPAM:
- raise SynapseError(
- 403,
- "Cannot send threepid invite",
- errcode=spam_check[0],
- additional_fields=spam_check[1],
- )
-
- event, stream_id = await self._make_and_store_3pid_invite(
- requester,
- id_server,
- medium,
- address,
- room_id,
- inviter,
- txn_id=txn_id,
- id_access_token=id_access_token,
- prev_event_ids=prev_event_ids,
- depth=depth,
- )
- event_id = event.event_id
-
- return event_id, stream_id
-
- async def _make_and_store_3pid_invite(
- self,
- requester: Requester,
- id_server: str,
- medium: str,
- address: str,
- room_id: str,
- user: UserID,
- txn_id: Optional[str],
- id_access_token: str,
- prev_event_ids: Optional[List[str]] = None,
- depth: Optional[int] = None,
- ) -> Tuple[EventBase, int]:
- room_state = await self._storage_controllers.state.get_current_state(
- room_id,
- StateFilter.from_types(
- [
- (EventTypes.Member, user.to_string()),
- (EventTypes.CanonicalAlias, ""),
- (EventTypes.Name, ""),
- (EventTypes.Create, ""),
- (EventTypes.JoinRules, ""),
- (EventTypes.RoomAvatar, ""),
- ]
- ),
- )
-
- inviter_display_name = ""
- inviter_avatar_url = ""
- member_event = room_state.get((EventTypes.Member, user.to_string()))
- if member_event:
- inviter_display_name = member_event.content.get("displayname", "")
- inviter_avatar_url = member_event.content.get("avatar_url", "")
-
- # if user has no display name, default to their MXID
- if not inviter_display_name:
- inviter_display_name = user.to_string()
-
- canonical_room_alias = ""
- canonical_alias_event = room_state.get((EventTypes.CanonicalAlias, ""))
- if canonical_alias_event:
- canonical_room_alias = canonical_alias_event.content.get("alias", "")
-
- room_name = ""
- room_name_event = room_state.get((EventTypes.Name, ""))
- if room_name_event:
- room_name = room_name_event.content.get("name", "")
-
- room_type = None
- room_create_event = room_state.get((EventTypes.Create, ""))
- if room_create_event:
- room_type = room_create_event.content.get(EventContentFields.ROOM_TYPE)
-
- room_join_rules = ""
- join_rules_event = room_state.get((EventTypes.JoinRules, ""))
- if join_rules_event:
- room_join_rules = join_rules_event.content.get("join_rule", "")
-
- room_avatar_url = ""
- room_avatar_event = room_state.get((EventTypes.RoomAvatar, ""))
- if room_avatar_event:
- room_avatar_url = room_avatar_event.content.get("url", "")
-
- (
- token,
- public_keys,
- fallback_public_key,
- display_name,
- ) = await self.identity_handler.ask_id_server_for_third_party_invite(
- requester=requester,
- id_server=id_server,
- medium=medium,
- address=address,
- room_id=room_id,
- inviter_user_id=user.to_string(),
- room_alias=canonical_room_alias,
- room_avatar_url=room_avatar_url,
- room_join_rules=room_join_rules,
- room_name=room_name,
- room_type=room_type,
- inviter_display_name=inviter_display_name,
- inviter_avatar_url=inviter_avatar_url,
- id_access_token=id_access_token,
- )
-
- (
- event,
- stream_id,
- ) = await self.event_creation_handler.create_and_send_nonmember_event(
- requester,
- {
- "type": EventTypes.ThirdPartyInvite,
- "content": {
- "display_name": display_name,
- "public_keys": public_keys,
- # For backwards compatibility:
- "key_validity_url": fallback_public_key["key_validity_url"],
- "public_key": fallback_public_key["public_key"],
- },
- "room_id": room_id,
- "sender": user.to_string(),
- "state_key": token,
- },
- ratelimit=False,
- txn_id=txn_id,
- prev_event_ids=prev_event_ids,
- depth=depth,
- )
- return event, stream_id
-
async def _is_host_in_room(self, partial_current_state_ids: StateMap[str]) -> bool:
"""Returns whether the homeserver is in the room based on its current state.
|