diff --git a/synapse/config/registration.py b/synapse/config/registration.py index 685c78bc7f..a9bb13c934 100644 --- a/synapse/config/registration.py +++ b/synapse/config/registration.py @@ -60,6 +60,8 @@ class RegistrationConfig(Config): if not isinstance(self.replicate_user_profiles_to, list): self.replicate_user_profiles_to = [self.replicate_user_profiles_to, ] + self.chain_register = config.get("chain_register", None) + def default_config(self, **kwargs): registration_shared_secret = random_string_with_symbols(50) @@ -137,6 +139,13 @@ class RegistrationConfig(Config): # cross-homeserver user directories. # replicate_user_profiles_to: example.com + # If specified, attempt to replay registrations on the given target + # homeserver and identity server. The HS is authed via a given shared secret + # chain_register: + # hs: https://shadow.example.com + # hs_shared_secret: 12u394refgbdhivsia + # is: https://shadow-is.example.com + # 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 3e061c89dc..c6580653c0 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -51,6 +51,7 @@ class RegistrationHandler(BaseHandler): self.profile_handler = hs.get_profile_handler() self.user_directory_handler = hs.get_user_directory_handler() self.captcha_client = CaptchaServerHttpClient(hs) + self.http_client = hs.get_simple_http_client() self._next_generated_user_id = None @@ -396,6 +397,43 @@ class RegistrationHandler(BaseHandler): errcode=Codes.EXCLUSIVE ) + @defer.inlineCallbacks + def chain_register(self, localpart, 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 + + chained_hs = self.hs.config.chain_register.get("hs") + + user = localpart.encode("utf-8") + mac = hmac.new( + key=self.hs.config.chain_register.get("hs_shared_secret").encode(), + msg=user, + digestmod=sha1, + ).hexdigest() + + data = yield self.http_client.post_urlencoded_get_json( + "https://%s%s" % ( + chained_hs, "/_matrix/client/r0/register" + ), + { + # XXX: auth_result is an unspecified extension for chained registration + 'auth_result': auth_result, + 'username': localpart, + 'password': params.get("password"), + 'bind_email': params.get("bind_email"), + 'bind_msisdn': params.get("bind_msisdn"), + 'device_id': params.get("device_id"), + 'initial_device_display_name': params.get("initial_device_display_name"), + 'inhibit_login': True, + 'mac': mac, + } + ) + @defer.inlineCallbacks def _generate_user_id(self, reseed=False): if reseed or self._next_generated_user_id is None: diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 7bbebd54ab..b80855b71f 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -467,7 +467,6 @@ class RegisterRestServlet(RestServlet): pass guest_access_token = params.get("guest_access_token", None) - new_password = params.get("password", None) # XXX: don't we need to validate these for length etc like we did on # the ones from the JSON body earlier on in the method? @@ -477,12 +476,19 @@ class RegisterRestServlet(RestServlet): (registered_user_id, _) = yield self.registration_handler.register( localpart=desired_username, - password=new_password, + password=params.get("password", None), guest_access_token=guest_access_token, generate_token=False, display_name=desired_display_name, ) + if self.hs.config.chain_register: + yield self.registration_handler.chain_register( + localpart=desired_username, + auth_result=auth_result, + params=params, + ) + # remember that we've now registered that user account, and with # what user ID (since the user may not have specified) self.auth_handler.set_session_data(