summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorBrendan Abolivier <babolivier@matrix.org>2019-07-04 11:12:47 +0100
committerGitHub <noreply@github.com>2019-07-04 11:12:47 +0100
commit5fe0cea37e8d4c23c08a22d7d45b0d35bcf4a4bb (patch)
treebf4b9d1a31b36315e512414a9b14049d8646e44e /synapse
parentMerge pull request #5577 from matrix-org/babolivier/3pid-invite-ratelimit (diff)
parentMerge branch 'babolivier/power-levels' of github.com:matrix-org/synapse into ... (diff)
downloadsynapse-5fe0cea37e8d4c23c08a22d7d45b0d35bcf4a4bb.tar.xz
Merge pull request #5610 from matrix-org/babolivier/power-levels dinsic_2019-07-04
Implement new restrictions on power levels
Diffstat (limited to 'synapse')
-rw-r--r--synapse/third_party_rules/access_rules.py154
1 files changed, 120 insertions, 34 deletions
diff --git a/synapse/third_party_rules/access_rules.py b/synapse/third_party_rules/access_rules.py

index d13f8de888..c8698c66cc 100644 --- a/synapse/third_party_rules/access_rules.py +++ b/synapse/third_party_rules/access_rules.py
@@ -33,6 +33,21 @@ VALID_ACCESS_RULES = ( ACCESS_RULE_UNRESTRICTED, ) +# Rules to which we need to apply the power levels restrictions. +# +# These are all of the rules that neither: +# * forbid users from joining based on a server blacklist (which means that there +# is no need to apply power level restrictions), nor +# * target direct chats (since we allow both users to be room admins in this case). +# +# The power-level restrictions, when they are applied, prevent the following: +# * the default power level for users (users_default) being set to anything other than 0. +# * a non-default power level being assigned to any user which would be forbidden from +# joining a restricted room. +RULES_WITH_RESTRICTED_POWER_LEVELS = ( + ACCESS_RULE_UNRESTRICTED, +) + class RoomAccessRules(object): """Implementation of the ThirdPartyEventRules module API that allows federation admins @@ -79,34 +94,32 @@ class RoomAccessRules(object): default rule to the initial state. """ is_direct = config.get("is_direct") - rules_in_initial_state = False + rule = None # If there's a rules event in the initial state, check if it complies with the # spec for im.vector.room.access_rules and deny the request if not. for event in config.get("initial_state", []): if event["type"] == ACCESS_RULES_TYPE: - rules_in_initial_state = True - rule = event["content"].get("rule") # Make sure the event has a valid content. if rule is None: - raise SynapseError(400, "Invalid access rule",) + raise SynapseError(400, "Invalid access rule") # Make sure the rule name is valid. if rule not in VALID_ACCESS_RULES: - raise SynapseError(400, "Invalid access rule", ) + raise SynapseError(400, "Invalid access rule") # Make sure the rule is "direct" if the room is a direct chat. if ( (is_direct and rule != ACCESS_RULE_DIRECT) or (rule == ACCESS_RULE_DIRECT and not is_direct) ): - raise SynapseError(400, "Invalid access rule",) + raise SynapseError(400, "Invalid access rule") # If there's no rules event in the initial state, create one with the default # setting. - if not rules_in_initial_state: + if not rule: if is_direct: default_rule = ACCESS_RULE_DIRECT else: @@ -123,6 +136,22 @@ class RoomAccessRules(object): } }) + rule = default_rule + + # Check if the creator can override values for the power levels. + allowed = self._is_power_level_content_allowed( + config.get("power_level_content_override", {}), rule, + ) + if not allowed: + raise SynapseError(400, "Invalid power levels content override") + + # Second loop for events we need to know the current rule to process. + for event in config.get("initial_state", []): + if event["type"] == EventTypes.PowerLevels: + allowed = self._is_power_level_content_allowed(event["content"], rule) + if not allowed: + raise SynapseError(400, "Invalid power levels content") + @defer.inlineCallbacks def check_threepid_can_be_invited(self, medium, address, state_events): """Implements synapse.events.ThirdPartyEventRules.check_threepid_can_be_invited @@ -172,24 +201,19 @@ class RoomAccessRules(object): Checks the event's type and the current rule and calls the right function to determine whether the event can be allowed. """ - # Special-case the access rules event. if event.type == ACCESS_RULES_TYPE: return self._on_rules_change(event, state_events) + # We need to know the rule to apply when processing the event types below. rule = self._get_rule_from_state(state_events) - if rule == ACCESS_RULE_RESTRICTED: - ret = self._apply_restricted(event) - elif rule == ACCESS_RULE_UNRESTRICTED: - ret = self._apply_unrestricted() - elif rule == ACCESS_RULE_DIRECT: - ret = self._apply_direct(event, state_events) - else: - # We currently apply the default (restricted) if we don't know the rule, we - # might want to change that in the future. - ret = self._apply_restricted(event) + if event.type == EventTypes.PowerLevels: + return self._is_power_level_content_allowed(event.content, rule) - return ret + if event.type == EventTypes.Member or event.type == EventTypes.ThirdPartyInvite: + return self._on_membership_or_invite(event, rule, state_events) + + return True def _on_rules_change(self, event, state_events): """Implement the checks and behaviour specified on allowing or forbidding a new @@ -232,35 +256,67 @@ class RoomAccessRules(object): return False - def _apply_restricted(self, event): + def _on_membership_or_invite(self, event, rule, state_events): + """Applies the correct rule for incoming m.room.member and + m.room.third_party_invite events. + + Args: + event (synapse.events.EventBase): The event to check. + rule (str): The name of the rule to apply. + state_events (dict[tuple[event type, state key], EventBase]): The state of the + room before the event was sent. + Returns: + bool, True if the event can be allowed, False otherwise. + """ + if rule == ACCESS_RULE_RESTRICTED: + ret = self._on_membership_or_invite_restricted(event) + elif rule == ACCESS_RULE_UNRESTRICTED: + ret = self._on_membership_or_invite_unrestricted() + elif rule == ACCESS_RULE_DIRECT: + ret = self._on_membership_or_invite_direct(event, state_events) + else: + # We currently apply the default (restricted) if we don't know the rule, we + # might want to change that in the future. + ret = self._on_membership_or_invite_restricted(event) + + return ret + + def _on_membership_or_invite_restricted(self, event): """Implements the checks and behaviour specified for the "restricted" rule. + "restricted" currently means that users can only invite users if their server is + included in a limited list of domains. + Args: event (synapse.events.EventBase): The event to check. Returns: bool, True if the event can be allowed, False otherwise. """ - # "restricted" currently means that users can only invite users if their server is - # included in a limited list of domains. - # We're not filtering on m.room.third_party_member events here because the - # filtering on threepids is done in check_threepid_can_be_invited. - if event.type != EventTypes.Member: + # We're not applying the rules on m.room.third_party_member events here because + # the filtering on threepids is done in check_threepid_can_be_invited, which is + # called before check_event_allowed. + if event.type == EventTypes.ThirdPartyInvite: return True invitee_domain = get_domain_from_id(event.state_key) return invitee_domain not in self.domains_forbidden_when_restricted - def _apply_unrestricted(self): + def _on_membership_or_invite_unrestricted(self): """Implements the checks and behaviour specified for the "unrestricted" rule. + "unrestricted" currently means that every event is allowed. + Returns: bool, True if the event can be allowed, False otherwise. """ - # "unrestricted" currently means that every event is allowed. return True - def _apply_direct(self, event, state_events): + def _on_membership_or_invite_direct(self, event, state_events): """Implements the checks and behaviour specified for the "direct" rule. + "direct" currently means that no member is allowed apart from the two initial + members the room was created for (i.e. the room's creator and their first + invitee). + Args: event (synapse.events.EventBase): The event to check. state_events (dict[tuple[event type, state key], EventBase]): The state of the @@ -268,12 +324,6 @@ class RoomAccessRules(object): Returns: bool, True if the event can be allowed, False otherwise. """ - # "direct" currently means that no member is allowed apart from the two initial - # members the room was created for (i.e. the room's creator and their first - # invitee). - if event.type != EventTypes.Member and event.type != EventTypes.ThirdPartyInvite: - return True - # Get the room memberships and 3PID invite tokens from the room's state. existing_members, threepid_tokens = self._get_members_and_tokens_from_state( state_events, @@ -319,6 +369,42 @@ class RoomAccessRules(object): return True + def _is_power_level_content_allowed(self, content, access_rule): + """Check if a given power levels event is permitted under the given access rule. + + It shouldn't be allowed if it either changes the default PL to a non-0 value or + gives a non-0 PL to a user that would have been forbidden from joining the room + under a more restrictive access rule. + + Args: + content (dict[]): The content of the m.room.power_levels event to check. + access_rule (str): The access rule in place in this room. + Returns: + bool, True if the event can be allowed, False otherwise. + + """ + # Check if we need to apply the restrictions with the current rule. + if access_rule not in RULES_WITH_RESTRICTED_POWER_LEVELS: + return True + + # If users_default is explicitly set to a non-0 value, deny the event. + users_default = content.get('users_default', 0) + if users_default: + return False + + users = content.get('users', {}) + for user_id, power_level in users.items(): + server_name = get_domain_from_id(user_id) + # Check the domain against the blacklist. If found, and the PL isn't 0, deny + # the event. + if ( + server_name in self.domains_forbidden_when_restricted + and power_level != 0 + ): + return False + + return True + @staticmethod def _get_rule_from_state(state_events): """Extract the rule to be applied from the given set of state events.