From 16801e2b5ce7d99c2fd157c1228fbbfbad55b668 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Sat, 18 Jan 2020 21:53:35 +0100 Subject: [PATCH] implement knock via non-federation --- synapse/event_auth.py | 19 +++++- synapse/handlers/message.py | 3 +- synapse/handlers/room_member.py | 16 +++++ synapse/handlers/stats.py | 5 ++ synapse/rest/__init__.py | 2 + synapse/rest/client/v2_alpha/knock.py | 61 +++++++++++++++++++ .../delta/58/add_knock_members_to_stats.sql | 17 ++++++ synapse/storage/data_stores/main/stats.py | 1 + synapse/storage/prepare_database.py | 2 +- 9 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 synapse/rest/client/v2_alpha/knock.py create mode 100644 synapse/storage/data_stores/main/schema/delta/58/add_knock_members_to_stats.sql diff --git a/synapse/event_auth.py b/synapse/event_auth.py index 1033e5e121..577f8f166b 100644 --- a/synapse/event_auth.py +++ b/synapse/event_auth.py @@ -225,9 +225,12 @@ def _is_membership_change_allowed(event, auth_events): key = (EventTypes.JoinRules, "") join_rule_event = auth_events.get(key) + print(auth_events) + print(join_rule_event) if join_rule_event: join_rule = join_rule_event.content.get("join_rule", JoinRules.INVITE) else: + print("No such event") join_rule = JoinRules.INVITE user_level = get_user_power_level(event.user_id, auth_events) @@ -235,6 +238,7 @@ def _is_membership_change_allowed(event, auth_events): # FIXME (erikj): What should we do here as the default? ban_level = _get_named_level(auth_events, "ban", 50) + knock_level = _get_named_level(auth_events, "knock", 0) logger.debug( "_is_membership_change_allowed: %s", @@ -257,7 +261,7 @@ def _is_membership_change_allowed(event, auth_events): raise AuthError(403, "%s is banned from the room" % (target_user_id,)) return - if Membership.JOIN != membership: + if Membership.JOIN != membership and Membership.KNOCK != membership: if ( caller_invited and Membership.LEAVE == membership @@ -311,6 +315,17 @@ def _is_membership_change_allowed(event, auth_events): elif Membership.BAN == membership: if user_level < ban_level or user_level <= target_level: raise AuthError(403, "You don't have permission to ban") + elif Membership.KNOCK == membership: + # check that we have the leave event + print("====================") + print(join_rule) + print(user_level, knock_level) + if target and target.membership != Membership.LEAVE: + raise AuthError(403, "You don't have permission to knock") + elif join_rule != JoinRules.INVITE: + raise AuthError(403, "You don't have permission to knock") + elif user_level < knock_level: + raise AuthError(403, "You don't have permission to knock") else: raise AuthError(500, "Unknown membership %s" % membership) @@ -653,7 +668,7 @@ def auth_types_for_event(event) -> Set[Tuple[str]]: if event.type == EventTypes.Member: membership = event.content["membership"] - if membership in [Membership.JOIN, Membership.INVITE]: + if membership in [Membership.JOIN, Membership.INVITE, Membership.KNOCK]: auth_types.add((EventTypes.JoinRules, "")) auth_types.add((EventTypes.Member, event.state_key)) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 8ea3aca2f4..91c582725c 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -2,6 +2,7 @@ # Copyright 2014-2016 OpenMarket Ltd # Copyright 2017-2018 New Vector Ltd # Copyright 2019 The Matrix.org Foundation C.I.C. +# Copyrignt 2020 Sorunome # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -471,7 +472,7 @@ class EventCreationHandler(object): membership = builder.content.get("membership", None) target = UserID.from_string(builder.state_key) - if membership in {Membership.JOIN, Membership.INVITE}: + if membership in {Membership.JOIN, Membership.INVITE, Membership.KNOCK}: # If event doesn't include a display name, add one. profile = self.profile_handler content = builder.content diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py index 15e8aa5249..b3eac39480 100644 --- a/synapse/handlers/room_member.py +++ b/synapse/handlers/room_member.py @@ -2,6 +2,7 @@ # Copyright 2016 OpenMarket Ltd # Copyright 2018 New Vector Ltd # Copyright 2019 The Matrix.org Foundation C.I.C. +# Copyright 2020 Sorunome # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -476,6 +477,21 @@ class RoomMemberHandler(object): requester, remote_room_hosts, room_id, target, content, ) return res + elif effective_membership_state == Membership.KNOCK: + print("===========================") + print("blah") + if not is_host_in_room: + # The knock needs to be send over federation + remote_room_hosts.append(room_id.split(":", 1)[1]) + + content["membership"] = Membership.KNOCK + + profile = self.profile_handler + if not content_specified: + content["displayname"] = yield profile.get_displayname(target) + content["avatar_url"] = yield profile.get_avatar_url(target) + + raise SynapseError(500, "Not yet implemented") res = yield self._local_membership_update( requester=requester, diff --git a/synapse/handlers/stats.py b/synapse/handlers/stats.py index 7f7d56390e..9b4eb4e867 100644 --- a/synapse/handlers/stats.py +++ b/synapse/handlers/stats.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2018 New Vector Ltd +# Copyright 2020 Sorunome # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -232,6 +233,8 @@ class StatsHandler(StateDeltasHandler): room_stats_delta["left_members"] -= 1 elif prev_membership == Membership.BAN: room_stats_delta["banned_members"] -= 1 + elif prev_membership == Membership.KNOCK: + room_stats_delta["knock_members"] -= 1 else: raise ValueError( "%r is not a valid prev_membership" % (prev_membership,) @@ -253,6 +256,8 @@ class StatsHandler(StateDeltasHandler): room_stats_delta["left_members"] += 1 elif membership == Membership.BAN: room_stats_delta["banned_members"] += 1 + elif membership == Membership.KNOCK: + room_stats_delta["knock_members"] += 1 else: raise ValueError("%r is not a valid membership" % (membership,)) diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py index 4a1fc2ec2b..e8d6fe873f 100644 --- a/synapse/rest/__init__.py +++ b/synapse/rest/__init__.py @@ -39,6 +39,7 @@ from synapse.rest.client.v2_alpha import ( filter, groups, keys, + knock, notifications, openid, read_marker, @@ -118,6 +119,7 @@ class ClientRestResource(JsonResource): capabilities.register_servlets(hs, client_resource) account_validity.register_servlets(hs, client_resource) relations.register_servlets(hs, client_resource) + knock.register_servlets(hs, client_resource) # moving to /_synapse/admin synapse.rest.admin.register_servlets_for_client_rest_resource( diff --git a/synapse/rest/client/v2_alpha/knock.py b/synapse/rest/client/v2_alpha/knock.py new file mode 100644 index 0000000000..946aa5a620 --- /dev/null +++ b/synapse/rest/client/v2_alpha/knock.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Sorunome +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from synapse.api.errors import AuthError, SynapseError +from synapse.http.servlet import RestServlet, parse_json_object_from_request + +from ._base import client_patterns + +logger = logging.getLogger(__name__) + + +class KnockServlet(RestServlet): + """ + POST /rooms/{roomId}/knock + """ + + PATTERNS = client_patterns( + "/rooms/(?P[^/]*)/knock" + ) + + def __init__(self, hs): + super(KnockServlet, self).__init__() + self.room_member_handler = hs.get_room_member_handler() + self.auth = hs.get_auth() + + async def on_POST(self, request, room_id, txn_id=None): + requester = await self.auth.get_user_by_req(request) + + content = parse_json_object_from_request(request) + event_content = None + if "reason" in content: + event_content = {"reason": content["reason"]} + + await self.room_member_handler.update_membership( + requester=requester, + target=requester.user, + room_id=room_id, + action="knock", + txn_id=txn_id, + third_party_signed=None, + content=event_content, + ) + + return 200, {} + +def register_servlets(hs, http_server): + KnockServlet(hs).register(http_server) diff --git a/synapse/storage/data_stores/main/schema/delta/58/add_knock_members_to_stats.sql b/synapse/storage/data_stores/main/schema/delta/58/add_knock_members_to_stats.sql new file mode 100644 index 0000000000..0c098fbcbc --- /dev/null +++ b/synapse/storage/data_stores/main/schema/delta/58/add_knock_members_to_stats.sql @@ -0,0 +1,17 @@ +/* Copyright 2020 Sorunome + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +ALTER TABLE room_stats_current ADD knock_members INT NOT NULL DEFAULT '0'; +ALTER TABLE room_stats_historical ADD knock_members BIGINT NOT NULL DEFAULT '0'; diff --git a/synapse/storage/data_stores/main/stats.py b/synapse/storage/data_stores/main/stats.py index 7bc186e9a1..6827b13816 100644 --- a/synapse/storage/data_stores/main/stats.py +++ b/synapse/storage/data_stores/main/stats.py @@ -40,6 +40,7 @@ ABSOLUTE_STATS_FIELDS = { "left_members", "banned_members", "local_users_in_room", + "knock_members", ), "user": ("joined_rooms",), } diff --git a/synapse/storage/prepare_database.py b/synapse/storage/prepare_database.py index e86984cd50..1a06cc0c59 100644 --- a/synapse/storage/prepare_database.py +++ b/synapse/storage/prepare_database.py @@ -29,7 +29,7 @@ logger = logging.getLogger(__name__) # Remember to update this number every time a change is made to database # schema files, so the users will be informed on server restarts. -SCHEMA_VERSION = 57 +SCHEMA_VERSION = 58 dir_path = os.path.abspath(os.path.dirname(__file__))