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
@@ -397,6 +398,43 @@ class RegistrationHandler(BaseHandler):
)
@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:
with (yield self._generate_user_id_linearizer.queue(())):
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(
|