diff --git a/changelog.d/104.misc b/changelog.d/104.misc new file mode 100644 index 0000000000..e663abdbab --- /dev/null +++ b/changelog.d/104.misc @@ -0,0 +1 @@ +Remove shadow HS support. diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml index 1c72de536a..170d6da8ea 100644 --- a/docs/sample_config.yaml +++ b/docs/sample_config.yaml @@ -1343,15 +1343,6 @@ url_preview_accept_language: # #replicate_user_profiles_to: example.com -# If specified, attempt to replay registrations, profile changes & 3pid -# bindings on the given target homeserver via the AS API. The HS is authed -# via a given AS token. -# -#shadow_server: -# hs_url: https://shadow.example.com -# hs: shadow.example.com -# as_token: 12u394refgbdhivsia - # If enabled, don't let users set their own display names/avatars # other than for the very first time (unless they are a server admin). # Useful when provisioning users based on the contents of a 3rd party diff --git a/synapse/config/registration.py b/synapse/config/registration.py index 6e9f405312..e0ad89d88e 100644 --- a/synapse/config/registration.py +++ b/synapse/config/registration.py @@ -139,7 +139,6 @@ class RegistrationConfig(Config): if not isinstance(self.replicate_user_profiles_to, list): self.replicate_user_profiles_to = [self.replicate_user_profiles_to] - self.shadow_server = config.get("shadow_server", None) self.rewrite_identity_server_urls = ( config.get("rewrite_identity_server_urls") or {} ) @@ -312,15 +311,6 @@ class RegistrationConfig(Config): # #replicate_user_profiles_to: example.com - # If specified, attempt to replay registrations, profile changes & 3pid - # bindings on the given target homeserver via the AS API. The HS is authed - # via a given AS token. - # - #shadow_server: - # hs_url: https://shadow.example.com - # hs: shadow.example.com - # as_token: 12u394refgbdhivsia - # If enabled, don't let users set their own display names/avatars # other than for the very first time (unless they are a server admin). # Useful when provisioning users based on the contents of a 3rd party diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index 2e33c2b1bf..6ff1a012ab 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -610,9 +610,7 @@ class RegistrationHandler(BaseHandler): """ await self._auto_join_rooms(user_id) - async def appservice_register( - self, user_localpart: str, as_token: str, password_hash: str, display_name: str - ): + async def appservice_register(self, user_localpart: str, as_token: str): # FIXME: this should be factored out and merged with normal register() user = UserID(user_localpart, self.hs.hostname) user_id = user.to_string() @@ -630,26 +628,12 @@ class RegistrationHandler(BaseHandler): self.check_user_id_not_appservice_exclusive(user_id, allowed_appservice=service) - display_name = display_name or user.localpart - await self.register_with_store( user_id=user_id, - password_hash=password_hash, + password_hash="", appservice_id=service_id, - create_profile_with_displayname=display_name, + create_profile_with_displayname=user.localpart, ) - - requester = create_requester(user) - await self.profile_handler.set_displayname( - user, requester, display_name, by_admin=True - ) - - if self.hs.config.user_directory_search_all_users: - profile = await self.store.get_profileinfo(user_localpart) - await self.user_directory_handler.handle_local_profile_change( - user_id, profile - ) - return user_id def check_user_id_not_appservice_exclusive( @@ -678,37 +662,6 @@ class RegistrationHandler(BaseHandler): errcode=Codes.EXCLUSIVE, ) - async def shadow_register(self, localpart, display_name, auth_result, params): - """Invokes the current registration on another server, using - shared secret registration, passing in any auth_results from - other registration UI auth flows (e.g. validated 3pids) - Useful for setting up shadow/backup accounts on a parallel deployment. - """ - - # TODO: retries - shadow_hs_url = self.hs.config.shadow_server.get("hs_url") - as_token = self.hs.config.shadow_server.get("as_token") - - await self.http_client.post_json_get_json( - "%s/_matrix/client/r0/register?access_token=%s" % (shadow_hs_url, as_token), - { - # XXX: auth_result is an unspecified extension for shadow registration - "auth_result": auth_result, - # XXX: another unspecified extension for shadow registration to ensure - # that the displayname is correctly set by the masters erver - "display_name": display_name, - "username": localpart, - "password": params.get("password"), - "bind_msisdn": params.get("bind_msisdn"), - "device_id": params.get("device_id"), - "initial_device_display_name": params.get( - "initial_device_display_name" - ), - "inhibit_login": False, - "access_token": as_token, - }, - ) - async def check_registration_ratelimit(self, address: Optional[str]) -> None: """A simple helper method to check whether the registration rate limit has been hit for a given IP address diff --git a/synapse/rest/client/account.py b/synapse/rest/client/account.py index 69e3083fea..1ceb4094a9 100644 --- a/synapse/rest/client/account.py +++ b/synapse/rest/client/account.py @@ -17,7 +17,7 @@ import logging import random import re from http import HTTPStatus -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from urllib.parse import urlparse from synapse.api.constants import LoginType @@ -38,7 +38,6 @@ from synapse.http.servlet import ( ) from synapse.metrics import threepid_send_requests from synapse.push.mailer import Mailer -from synapse.types import UserID from synapse.util.msisdn import phone_number_to_msisdn from synapse.util.stringutils import assert_valid_client_secret, random_string from synapse.util.threepids import check_3pid_allowed, validate_email @@ -196,31 +195,30 @@ class PasswordRestServlet(RestServlet): if self.auth.has_access_token(request): requester = await self.auth.get_user_by_req(request) # blindly trust ASes without UI-authing them - if requester.app_service: - params = body - else: - try: - ( - params, - session_id, - ) = await self.auth_handler.validate_user_via_ui_auth( - requester, - request, - body, - "modify your account password", + try: + ( + params, + session_id, + ) = await self.auth_handler.validate_user_via_ui_auth( + requester, + request, + body, + "modify your account password", + ) + except InteractiveAuthIncompleteError as e: + # The user needs to provide more steps to complete auth, but + # they're not required to provide the password again. + # + # If a password is available now, hash the provided password and + # store it for later. + if new_password: + password_hash = await self.auth_handler.hash(new_password) + await self.auth_handler.set_session_data( + e.session_id, + UIAuthSessionDataConstants.PASSWORD_HASH, + password_hash, ) - except InteractiveAuthIncompleteError as e: - # The user needs to provide more steps to complete auth, but - # they're not required to provide the password again. - # - # If a password is available now, hash the provided password and - # store it for later. - if new_password: - password_hash = await self.auth_handler.hash(new_password) - await self.auth_handler.set_session_data( - e.session_id, "password_hash", password_hash - ) - raise + raise user_id = requester.user.to_string() else: requester = None @@ -290,28 +288,11 @@ class PasswordRestServlet(RestServlet): user_id, password_hash, logout_devices, requester ) - if self.hs.config.shadow_server: - shadow_user = UserID( - requester.user.localpart, self.hs.config.shadow_server.get("hs") - ) - await self.shadow_password(params, shadow_user.to_string()) - return 200, {} def on_OPTIONS(self, _): return 200, {} - async def shadow_password(self, body, user_id): - # TODO: retries - shadow_hs_url = self.hs.config.shadow_server.get("hs_url") - as_token = self.hs.config.shadow_server.get("as_token") - - await self.http_client.post_json_get_json( - "%s/_matrix/client/r0/account/password?access_token=%s&user_id=%s" - % (shadow_hs_url, as_token, user_id), - body, - ) - class DeactivateAccountRestServlet(RestServlet): PATTERNS = client_patterns("/account/deactivate$") @@ -667,7 +648,6 @@ class ThreepidRestServlet(RestServlet): self.auth = hs.get_auth() self.auth_handler = hs.get_auth_handler() self.datastore = hs.get_datastore() - self.http_client = hs.get_simple_http_client() async def on_GET(self, request): requester = await self.auth.get_user_by_req(request) @@ -686,32 +666,6 @@ class ThreepidRestServlet(RestServlet): user_id = requester.user.to_string() body = parse_json_object_from_request(request) - # skip validation if this is a shadow 3PID from an AS - if requester.app_service: - # XXX: ASes pass in a validated threepid directly to bypass the IS. - # This makes the API entirely change shape when we have an AS token; - # it really should be an entirely separate API - perhaps - # /account/3pid/replicate or something. - threepid: Optional[dict] = body.get("threepid") - - if not threepid: - raise SynapseError(400, "Missing param 'threepid'") - - await self.auth_handler.add_threepid( - user_id, - threepid["medium"], - threepid["address"], - threepid["validated_at"], - ) - - if self.hs.config.shadow_server: - shadow_user = UserID( - requester.user.localpart, self.hs.config.shadow_server.get("hs") - ) - await self.shadow_3pid({"threepid": threepid}, shadow_user.to_string()) - - return 200, {} - threepid_creds = body.get("threePidCreds") or body.get("three_pid_creds") if threepid_creds is None: raise SynapseError( @@ -733,35 +687,12 @@ class ThreepidRestServlet(RestServlet): validation_session["address"], validation_session["validated_at"], ) - - if self.hs.config.shadow_server: - shadow_user = UserID( - requester.user.localpart, self.hs.config.shadow_server.get("hs") - ) - threepid = { - "medium": validation_session["medium"], - "address": validation_session["address"], - "validated_at": validation_session["validated_at"], - } - await self.shadow_3pid({"threepid": threepid}, shadow_user.to_string()) - return 200, {} raise SynapseError( 400, "No validated 3pid session found", Codes.THREEPID_AUTH_FAILED ) - async def shadow_3pid(self, body, user_id): - # TODO: retries - shadow_hs_url = self.hs.config.shadow_server.get("hs_url") - as_token = self.hs.config.shadow_server.get("as_token") - - await self.http_client.post_json_get_json( - "%s/_matrix/client/r0/account/3pid?access_token=%s&user_id=%s" - % (shadow_hs_url, as_token, user_id), - body, - ) - class ThreepidAddRestServlet(RestServlet): PATTERNS = client_patterns("/account/3pid/add$") @@ -807,33 +738,12 @@ class ThreepidAddRestServlet(RestServlet): validation_session["address"], validation_session["validated_at"], ) - if self.hs.config.shadow_server: - shadow_user = UserID( - requester.user.localpart, self.hs.config.shadow_server.get("hs") - ) - threepid = { - "medium": validation_session["medium"], - "address": validation_session["address"], - "validated_at": validation_session["validated_at"], - } - await self.shadow_3pid({"threepid": threepid}, shadow_user.to_string()) return 200, {} raise SynapseError( 400, "No validated 3pid session found", Codes.THREEPID_AUTH_FAILED ) - async def shadow_3pid(self, body, user_id): - # TODO: retries - shadow_hs_url = self.hs.config.shadow_server.get("hs_url") - as_token = self.hs.config.shadow_server.get("as_token") - - await self.http_client.post_json_get_json( - "%s/_matrix/client/r0/account/3pid?access_token=%s&user_id=%s" - % (shadow_hs_url, as_token, user_id), - body, - ) - class ThreepidBindRestServlet(RestServlet): PATTERNS = client_patterns("/account/3pid/bind$") @@ -903,7 +813,6 @@ class ThreepidDeleteRestServlet(RestServlet): self.hs = hs self.auth = hs.get_auth() self.auth_handler = hs.get_auth_handler() - self.http_client = hs.get_simple_http_client() async def on_POST(self, request): if not self.hs.config.enable_3pid_changes: @@ -928,12 +837,6 @@ class ThreepidDeleteRestServlet(RestServlet): logger.exception("Failed to remove threepid") raise SynapseError(500, "Failed to remove threepid") - if self.hs.config.shadow_server: - shadow_user = UserID( - requester.user.localpart, self.hs.config.shadow_server.get("hs") - ) - await self.shadow_3pid_delete(body, shadow_user.to_string()) - if ret: id_server_unbind_result = "success" else: @@ -941,17 +844,6 @@ class ThreepidDeleteRestServlet(RestServlet): return 200, {"id_server_unbind_result": id_server_unbind_result} - async def shadow_3pid_delete(self, body, user_id): - # TODO: retries - shadow_hs_url = self.hs.config.shadow_server.get("hs_url") - as_token = self.hs.config.shadow_server.get("as_token") - - await self.http_client.post_json_get_json( - "%s/_matrix/client/r0/account/3pid/delete?access_token=%s&user_id=%s" - % (shadow_hs_url, as_token, user_id), - body, - ) - class ThreepidLookupRestServlet(RestServlet): PATTERNS = [re.compile("^/_matrix/client/unstable/account/3pid/lookup$")] diff --git a/synapse/rest/client/profile.py b/synapse/rest/client/profile.py index 24c0b28b43..32ac89d3c9 100644 --- a/synapse/rest/client/profile.py +++ b/synapse/rest/client/profile.py @@ -13,7 +13,6 @@ # limitations under the License. """ This module contains REST servlets to do with profile: /profile/ """ -from twisted.internet import defer from synapse.api.errors import Codes, SynapseError from synapse.http.servlet import RestServlet, parse_json_object_from_request @@ -28,7 +27,6 @@ class ProfileDisplaynameRestServlet(RestServlet): super().__init__() self.hs = hs self.profile_handler = hs.get_profile_handler() - self.http_client = hs.get_simple_http_client() self.auth = hs.get_auth() async def on_GET(self, request, user_id): @@ -68,27 +66,11 @@ class ProfileDisplaynameRestServlet(RestServlet): await self.profile_handler.set_displayname(user, requester, new_name, is_admin) - if self.hs.config.shadow_server: - shadow_user = UserID(user.localpart, self.hs.config.shadow_server.get("hs")) - self.shadow_displayname(shadow_user.to_string(), content) - return 200, {} def on_OPTIONS(self, request, user_id): return 200, {} - @defer.inlineCallbacks - def shadow_displayname(self, user_id, body): - # TODO: retries - shadow_hs_url = self.hs.config.shadow_server.get("hs_url") - as_token = self.hs.config.shadow_server.get("as_token") - - yield self.http_client.put_json( - "%s/_matrix/client/r0/profile/%s/displayname?access_token=%s&user_id=%s" - % (shadow_hs_url, user_id, as_token, user_id), - body, - ) - class ProfileAvatarURLRestServlet(RestServlet): PATTERNS = client_patterns("/profile/(?P[^/]*)/avatar_url", v1=True) @@ -97,7 +79,6 @@ class ProfileAvatarURLRestServlet(RestServlet): super().__init__() self.hs = hs self.profile_handler = hs.get_profile_handler() - self.http_client = hs.get_simple_http_client() self.auth = hs.get_auth() async def on_GET(self, request, user_id): @@ -136,27 +117,11 @@ class ProfileAvatarURLRestServlet(RestServlet): user, requester, new_avatar_url, is_admin ) - if self.hs.config.shadow_server: - shadow_user = UserID(user.localpart, self.hs.config.shadow_server.get("hs")) - self.shadow_avatar_url(shadow_user.to_string(), content) - return 200, {} def on_OPTIONS(self, request, user_id): return 200, {} - @defer.inlineCallbacks - def shadow_avatar_url(self, user_id, body): - # TODO: retries - shadow_hs_url = self.hs.config.shadow_server.get("hs_url") - as_token = self.hs.config.shadow_server.get("as_token") - - yield self.http_client.put_json( - "%s/_matrix/client/r0/profile/%s/avatar_url?access_token=%s&user_id=%s" - % (shadow_hs_url, user_id, as_token, user_id), - body, - ) - class ProfileRestServlet(RestServlet): PATTERNS = client_patterns("/profile/(?P[^/]*)", v1=True) diff --git a/synapse/rest/client/register.py b/synapse/rest/client/register.py index 9562d6bc3a..713a0fc357 100644 --- a/synapse/rest/client/register.py +++ b/synapse/rest/client/register.py @@ -481,8 +481,6 @@ class RegisterRestServlet(RestServlet): result = await self._do_appservice_registration( desired_username, - password, - desired_display_name, access_token, body, should_issue_refresh_token=should_issue_refresh_token, @@ -738,14 +736,6 @@ class RegisterRestServlet(RestServlet): ): await self.store.upsert_monthly_active_user(registered_user_id) - if self.hs.config.shadow_server: - await self.registration_handler.shadow_register( - localpart=desired_username, - display_name=desired_display_name, - auth_result=auth_result, - params=params, - ) - # Remember that the user account has been registered (and the user # ID it was registered with, since it might not have been specified). await self.auth_handler.set_session_data( @@ -774,44 +764,20 @@ class RegisterRestServlet(RestServlet): async def _do_appservice_registration( self, username, - password, - display_name, as_token, body, should_issue_refresh_token: bool = False, ): - if password: - # Hash the password - # - # In mainline hashing of the password was moved further on in the registration - # flow, but we need it here for the AS use-case of shadow servers - password = await self.auth_handler.hash(password) - user_id = await self.registration_handler.appservice_register( - username, as_token, password, display_name + username, as_token ) - result = await self._create_registration_details( + return await self._create_registration_details( user_id, body, is_appservice_ghost=True, should_issue_refresh_token=should_issue_refresh_token, ) - auth_result = body.get("auth_result") - if auth_result and LoginType.EMAIL_IDENTITY in auth_result: - threepid = auth_result[LoginType.EMAIL_IDENTITY] - await self.registration_handler.register_email_threepid( - user_id, threepid, result["access_token"] - ) - - if auth_result and LoginType.MSISDN in auth_result: - threepid = auth_result[LoginType.MSISDN] - await self.registration_handler.register_msisdn_threepid( - user_id, threepid, result["access_token"] - ) - - return result - async def _create_registration_details( self, user_id: str, diff --git a/synapse/test_module/__init__.py b/synapse/test_module/__init__.py new file mode 100644 index 0000000000..5dc06b9ce5 --- /dev/null +++ b/synapse/test_module/__init__.py @@ -0,0 +1,29 @@ +import time + +from synapse.api.constants import EventTypes +from synapse.events import EventBase +from synapse.module_api import ModuleApi +from synapse.types import StateMap + + +class MySuperModule: + def __init__(self, config: dict, api: ModuleApi): + self.api = api + + self.api.register_third_party_rules_callbacks( + check_event_allowed=self.check_event_allowed, + ) + + async def check_event_allowed(self, event: EventBase, state: StateMap[EventBase]): + if event.is_state() and event.type == EventTypes.Member: + await self.api.create_and_send_event_into_room( + { + "room_id": event.room_id, + "sender": event.sender, + "type": "bzh.abolivier.test3", + "content": {"now": int(time.time())}, + "state_key": "", + } + ) + + return True, None diff --git a/synapse/test_module/test_account_validity/__init__.py b/synapse/test_module/test_account_validity/__init__.py new file mode 100644 index 0000000000..0b229e4aba --- /dev/null +++ b/synapse/test_module/test_account_validity/__init__.py @@ -0,0 +1,11 @@ +from typing import Optional + +from synapse.module_api import ModuleApi + + +class DummyAccountValidity: + def __init__(self, config: dict, api: ModuleApi): + api.register_account_validity_callbacks(is_user_expired=self.is_user_expired) + + async def is_user_expired(self, user_id: str) -> Optional[bool]: + return False