summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorMatthew Hodgson <matthew@matrix.org>2018-01-19 00:19:58 +0000
committerMatthew Hodgson <matthew@matrix.org>2018-01-19 00:19:58 +0000
commit28a6ccb49c57cc686761b9e674b501b3b402e616 (patch)
tree66e61588c844e31d868a6155b1393f01b3b3c844 /synapse
parentMerge pull request #2805 from matrix-org/rav/log_state_res (diff)
downloadsynapse-28a6ccb49c57cc686761b9e674b501b3b402e616.tar.xz
add registrations_require_3pid
lets homeservers specify a whitelist for 3PIDs that users are allowed to associate with.
Typically useful for stopping people from registering with non-work emails
Diffstat (limited to '')
-rw-r--r--synapse/api/errors.py1
-rw-r--r--synapse/config/registration.py13
-rw-r--r--synapse/rest/client/v2_alpha/_base.py22
-rw-r--r--synapse/rest/client/v2_alpha/account.py14
-rw-r--r--synapse/rest/client/v2_alpha/register.py73
5 files changed, 110 insertions, 13 deletions
diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index 79b35b3e7c..46b0d7b34c 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -46,6 +46,7 @@ class Codes(object):
     THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
     THREEPID_IN_USE = "M_THREEPID_IN_USE"
     THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
+    THREEPID_DENIED = "M_THREEPID_DENIED"
     INVALID_USERNAME = "M_INVALID_USERNAME"
     SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
 
diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index ef917fc9f2..e5e4f77872 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -31,6 +31,7 @@ class RegistrationConfig(Config):
                 strtobool(str(config["disable_registration"]))
             )
 
+        self.registrations_require_3pid = config.get("registrations_require_3pid", [])
         self.registration_shared_secret = config.get("registration_shared_secret")
 
         self.bcrypt_rounds = config.get("bcrypt_rounds", 12)
@@ -52,6 +53,18 @@ class RegistrationConfig(Config):
         # Enable registration for new users.
         enable_registration: False
 
+        # Mandate that registrations require a 3PID which matches one or more
+        # of these 3PIDs.  N.B. regexp escape backslashes are doubled (once for
+        # YAML and once for the regexp itself)
+        #
+        # registrations_require_3pid:
+        #     - medium: email
+        #       pattern: ".*@matrix\\.org"
+        #     - medium: email
+        #       pattern: ".*@vector\\.im"
+        #     - medium: msisdn
+        #       pattern: "\\+44"
+
         # If set, allows registration by anyone who also has the shared
         # secret, even if registration is otherwise disabled.
         registration_shared_secret: "%(registration_shared_secret)s"
diff --git a/synapse/rest/client/v2_alpha/_base.py b/synapse/rest/client/v2_alpha/_base.py
index 77434937ff..7c46ef7cab 100644
--- a/synapse/rest/client/v2_alpha/_base.py
+++ b/synapse/rest/client/v2_alpha/_base.py
@@ -60,6 +60,28 @@ def set_timeline_upper_limit(filter_json, filter_timeline_limit):
             filter_timeline_limit)
 
 
+def check_3pid_allowed(hs, medium, address):
+    # check whether the HS has whitelisted the given 3PID
+
+    allow = False
+    if hs.config.registrations_require_3pid:
+        for constraint in hs.config.registrations_require_3pid:
+            logger.debug("Checking 3PID %s (%s) against %s (%s)" % (
+                address, medium, constraint['pattern'], constraint['medium']
+                )
+            )
+            if (
+                medium == constraint['medium'] and
+                re.match(constraint['pattern'], address)
+            ):
+                allow = True
+                break
+    else:
+        allow = True
+
+    return allow
+
+
 def interactive_auth_handler(orig):
     """Wraps an on_POST method to handle InteractiveAuthIncompleteErrors
 
diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py
index 385a3ad2ec..66221e8f00 100644
--- a/synapse/rest/client/v2_alpha/account.py
+++ b/synapse/rest/client/v2_alpha/account.py
@@ -26,7 +26,7 @@ from synapse.http.servlet import (
 )
 from synapse.util.async import run_on_reactor
 from synapse.util.msisdn import phone_number_to_msisdn
-from ._base import client_v2_patterns, interactive_auth_handler
+from ._base import client_v2_patterns, interactive_auth_handler, check_3pid_allowed
 
 logger = logging.getLogger(__name__)
 
@@ -47,6 +47,9 @@ class EmailPasswordRequestTokenRestServlet(RestServlet):
             'id_server', 'client_secret', 'email', 'send_attempt'
         ])
 
+        if not check_3pid_allowed(self.hs, "email", body['email']):
+            raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+
         existingUid = yield self.hs.get_datastore().get_user_id_by_threepid(
             'email', body['email']
         )
@@ -78,6 +81,9 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet):
 
         msisdn = phone_number_to_msisdn(body['country'], body['phone_number'])
 
+        if not check_3pid_allowed(self.hs, "msisdn", msisdn):
+            raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+
         existingUid = yield self.datastore.get_user_id_by_threepid(
             'msisdn', msisdn
         )
@@ -217,6 +223,9 @@ class EmailThreepidRequestTokenRestServlet(RestServlet):
         if absent:
             raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM)
 
+        if not check_3pid_allowed(self.hs, "email", body['email']):
+            raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+
         existingUid = yield self.datastore.get_user_id_by_threepid(
             'email', body['email']
         )
@@ -255,6 +264,9 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet):
 
         msisdn = phone_number_to_msisdn(body['country'], body['phone_number'])
 
+        if not check_3pid_allowed(self.hs, "msisdn", msisdn):
+            raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+
         existingUid = yield self.datastore.get_user_id_by_threepid(
             'msisdn', msisdn
         )
diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py
index e9d88a8895..762782c1f0 100644
--- a/synapse/rest/client/v2_alpha/register.py
+++ b/synapse/rest/client/v2_alpha/register.py
@@ -27,9 +27,10 @@ from synapse.http.servlet import (
 )
 from synapse.util.msisdn import phone_number_to_msisdn
 
-from ._base import client_v2_patterns, interactive_auth_handler
+from ._base import client_v2_patterns, interactive_auth_handler, check_3pid_allowed
 
 import logging
+import re
 import hmac
 from hashlib import sha1
 from synapse.util.async import run_on_reactor
@@ -70,6 +71,9 @@ class EmailRegisterRequestTokenRestServlet(RestServlet):
             'id_server', 'client_secret', 'email', 'send_attempt'
         ])
 
+        if not check_3pid_allowed(self.hs, "email", body['email']):
+            raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+
         existingUid = yield self.hs.get_datastore().get_user_id_by_threepid(
             'email', body['email']
         )
@@ -105,6 +109,9 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet):
 
         msisdn = phone_number_to_msisdn(body['country'], body['phone_number'])
 
+        if not check_3pid_allowed(self.hs, "msisdn", msisdn):
+            raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+
         existingUid = yield self.hs.get_datastore().get_user_id_by_threepid(
             'msisdn', msisdn
         )
@@ -305,31 +312,73 @@ class RegisterRestServlet(RestServlet):
         if 'x_show_msisdn' in body and body['x_show_msisdn']:
             show_msisdn = True
 
+        require_email = False
+        require_msisdn = False
+        for constraint in self.hs.config.registrations_require_3pid:
+            if constraint['medium'] == 'email':
+                require_email = True
+            elif constraint['medium'] == 'msisdn':
+                require_msisdn = True
+            else:
+                logger.warn(
+                    "Unrecognised 3PID medium %s in registrations_require_3pid" %
+                    constraint['medium']
+                )
+
+        flows = []
         if self.hs.config.enable_registration_captcha:
-            flows = [
-                [LoginType.RECAPTCHA],
-                [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA],
-            ]
+            if not require_email and not require_msisdn:
+                flows.extend([[LoginType.RECAPTCHA]])
+            if require_email or not require_msisdn:
+                flows.extend([[LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA]])
+
             if show_msisdn:
+                if not require_email or require_msisdn:
+                    flows.extend([[LoginType.MSISDN, LoginType.RECAPTCHA]])
                 flows.extend([
-                    [LoginType.MSISDN, LoginType.RECAPTCHA],
                     [LoginType.MSISDN, LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA],
                 ])
         else:
-            flows = [
-                [LoginType.DUMMY],
-                [LoginType.EMAIL_IDENTITY],
-            ]
+            if not require_email and not require_msisdn:
+                flows.extend([[LoginType.DUMMY]])
+            if require_email or not require_msisdn:
+                flows.extend([[LoginType.EMAIL_IDENTITY]])
+
             if show_msisdn:
+                if not require_email or require_msisdn:
+                    flows.extend([[LoginType.MSISDN]])
                 flows.extend([
-                    [LoginType.MSISDN],
-                    [LoginType.MSISDN, LoginType.EMAIL_IDENTITY],
+                    [LoginType.MSISDN, LoginType.EMAIL_IDENTITY]
                 ])
 
         auth_result, params, session_id = yield self.auth_handler.check_auth(
             flows, body, self.hs.get_ip_from_request(request)
         )
 
+        # doublecheck that we're not trying to register an denied 3pid.
+        # the user-facing checks should already have happened when we requested
+        # a 3PID token to validate them in /register/email/requestToken etc
+
+        for constraint in self.hs.config.registrations_require_3pid:
+            if (
+                constraint['medium'] == 'email' and
+                auth_result and LoginType.EMAIL_IDENTITY in auth_result and
+                re.match(
+                    constraint['pattern'],
+                    auth_result[LoginType.EMAIL_IDENTITY].threepid.address
+                )
+            ):
+                raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+            elif (
+                constraint['medium'] == 'msisdn' and
+                auth_result and LoginType.MSISDN in auth_result and
+                re.match(
+                    constraint['pattern'],
+                    auth_result[LoginType.MSISDN].threepid.address
+                )
+            ):
+                raise SynapseError(403, "3PID denied", Codes.THREEPID_DENIED)
+
         if registered_user_id is not None:
             logger.info(
                 "Already registered user ID %r for this session",