summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
Diffstat (limited to 'synapse')
-rw-r--r--synapse/api/constants.py1
-rw-r--r--synapse/config/registration.py33
-rw-r--r--synapse/handlers/register.py8
-rw-r--r--synapse/http/servlet.py4
-rw-r--r--synapse/rest/client/v1/register.py71
-rw-r--r--synapse/util/stringutils.py10
6 files changed, 111 insertions, 16 deletions
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index 420f963d91..b16bf4247d 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -60,6 +60,7 @@ class LoginType(object):
     EMAIL_IDENTITY = u"m.login.email.identity"
     RECAPTCHA = u"m.login.recaptcha"
     APPLICATION_SERVICE = u"m.login.application_service"
+    SHARED_SECRET = u"org.matrix.login.shared_secret"
 
 
 class EventTypes(object):
diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index cca8ab5676..4401e774d1 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -15,19 +15,46 @@
 
 from ._base import Config
 
+from synapse.util.stringutils import random_string_with_symbols
+
+import distutils.util
+
 
 class RegistrationConfig(Config):
 
     def __init__(self, args):
         super(RegistrationConfig, self).__init__(args)
-        self.disable_registration = args.disable_registration
+
+        # `args.disable_registration` may either be a bool or a string depending
+        # on if the option was given a value (e.g. --disable-registration=false
+        # would set `args.disable_registration` to "false" not False.)
+        self.disable_registration = bool(
+            distutils.util.strtobool(str(args.disable_registration))
+        )
+        self.registration_shared_secret = args.registration_shared_secret
 
     @classmethod
     def add_arguments(cls, parser):
         super(RegistrationConfig, cls).add_arguments(parser)
         reg_group = parser.add_argument_group("registration")
+
         reg_group.add_argument(
             "--disable-registration",
-            action='store_true',
-            help="Disable registration of new users."
+            const=True,
+            default=True,
+            nargs='?',
+            help="Disable registration of new users.",
         )
+        reg_group.add_argument(
+            "--registration-shared-secret", type=str,
+            help="If set, allows registration by anyone who also has the shared"
+                 " secret, even if registration is otherwise disabled.",
+        )
+
+    @classmethod
+    def generate_config(cls, args, config_dir_path):
+        if args.disable_registration is None:
+            args.disable_registration = True
+
+        if args.registration_shared_secret is None:
+            args.registration_shared_secret = random_string_with_symbols(50)
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index cda4a8502a..c25e321099 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -31,6 +31,7 @@ import base64
 import bcrypt
 import json
 import logging
+import urllib
 
 logger = logging.getLogger(__name__)
 
@@ -63,6 +64,13 @@ class RegistrationHandler(BaseHandler):
             password_hash = bcrypt.hashpw(password, bcrypt.gensalt())
 
         if localpart:
+            if localpart and urllib.quote(localpart) != localpart:
+                raise SynapseError(
+                    400,
+                    "User ID must only contain characters which do not"
+                    " require URL encoding."
+                )
+
             user = UserID(localpart, self.hs.hostname)
             user_id = user.to_string()
 
diff --git a/synapse/http/servlet.py b/synapse/http/servlet.py
index a4eb6c817c..265559a3ea 100644
--- a/synapse/http/servlet.py
+++ b/synapse/http/servlet.py
@@ -51,8 +51,8 @@ class RestServlet(object):
             pattern = self.PATTERN
 
             for method in ("GET", "PUT", "POST", "OPTIONS", "DELETE"):
-                if hasattr(self, "on_%s" % (method)):
-                    method_handler = getattr(self, "on_%s" % (method))
+                if hasattr(self, "on_%s" % (method,)):
+                    method_handler = getattr(self, "on_%s" % (method,))
                     http_server.register_path(method, pattern, method_handler)
         else:
             raise NotImplementedError("RestServlet must register something.")
diff --git a/synapse/rest/client/v1/register.py b/synapse/rest/client/v1/register.py
index f5acfb945f..a56834e365 100644
--- a/synapse/rest/client/v1/register.py
+++ b/synapse/rest/client/v1/register.py
@@ -27,7 +27,6 @@ from hashlib import sha1
 import hmac
 import simplejson as json
 import logging
-import urllib
 
 logger = logging.getLogger(__name__)
 
@@ -110,14 +109,22 @@ class RegisterRestServlet(ClientV1RestServlet):
             login_type = register_json["type"]
 
             is_application_server = login_type == LoginType.APPLICATION_SERVICE
-            if self.disable_registration and not is_application_server:
+            is_using_shared_secret = login_type == LoginType.SHARED_SECRET
+
+            can_register = (
+                not self.disable_registration
+                or is_application_server
+                or is_using_shared_secret
+            )
+            if not can_register:
                 raise SynapseError(403, "Registration has been disabled")
 
             stages = {
                 LoginType.RECAPTCHA: self._do_recaptcha,
                 LoginType.PASSWORD: self._do_password,
                 LoginType.EMAIL_IDENTITY: self._do_email_identity,
-                LoginType.APPLICATION_SERVICE: self._do_app_service
+                LoginType.APPLICATION_SERVICE: self._do_app_service,
+                LoginType.SHARED_SECRET: self._do_shared_secret,
             }
 
             session_info = self._get_session_info(request, session)
@@ -255,14 +262,11 @@ class RegisterRestServlet(ClientV1RestServlet):
             )
 
         password = register_json["password"].encode("utf-8")
-        desired_user_id = (register_json["user"].encode("utf-8")
-                           if "user" in register_json else None)
-        if (desired_user_id
-                and urllib.quote(desired_user_id) != desired_user_id):
-            raise SynapseError(
-                400,
-                "User ID must only contain characters which do not " +
-                "require URL encoding.")
+        desired_user_id = (
+            register_json["user"].encode("utf-8")
+            if "user" in register_json else None
+        )
+
         handler = self.handlers.registration_handler
         (user_id, token) = yield handler.register(
             localpart=desired_user_id,
@@ -304,6 +308,51 @@ class RegisterRestServlet(ClientV1RestServlet):
             "home_server": self.hs.hostname,
         })
 
+    @defer.inlineCallbacks
+    def _do_shared_secret(self, request, register_json, session):
+        yield run_on_reactor()
+
+        if not isinstance(register_json.get("mac", None), basestring):
+            raise SynapseError(400, "Expected mac.")
+        if not isinstance(register_json.get("user", None), basestring):
+            raise SynapseError(400, "Expected 'user' key.")
+        if not isinstance(register_json.get("password", None), basestring):
+            raise SynapseError(400, "Expected 'password' key.")
+
+        if not self.hs.config.registration_shared_secret:
+            raise SynapseError(400, "Shared secret registration is not enabled")
+
+        user = register_json["user"].encode("utf-8")
+
+        # str() because otherwise hmac complains that 'unicode' does not
+        # have the buffer interface
+        got_mac = str(register_json["mac"])
+
+        want_mac = hmac.new(
+            key=self.hs.config.registration_shared_secret,
+            msg=user,
+            digestmod=sha1,
+        ).hexdigest()
+
+        password = register_json["password"].encode("utf-8")
+
+        if compare_digest(want_mac, got_mac):
+            handler = self.handlers.registration_handler
+            user_id, token = yield handler.register(
+                localpart=user,
+                password=password,
+            )
+            self._remove_session(session)
+            defer.returnValue({
+                "user_id": user_id,
+                "access_token": token,
+                "home_server": self.hs.hostname,
+            })
+        else:
+            raise SynapseError(
+                403, "HMAC incorrect",
+            )
+
 
 def _parse_json(request):
     try:
diff --git a/synapse/util/stringutils.py b/synapse/util/stringutils.py
index ea53a8085c..52e66beaee 100644
--- a/synapse/util/stringutils.py
+++ b/synapse/util/stringutils.py
@@ -16,6 +16,10 @@
 import random
 import string
 
+_string_with_symbols = (
+    string.digits + string.ascii_letters + ".,;:^&*-_+=#~@"
+)
+
 
 def origin_from_ucid(ucid):
     return ucid.split("@", 1)[1]
@@ -23,3 +27,9 @@ def origin_from_ucid(ucid):
 
 def random_string(length):
     return ''.join(random.choice(string.ascii_letters) for _ in xrange(length))
+
+
+def random_string_with_symbols(length):
+    return ''.join(
+        random.choice(_string_with_symbols) for _ in xrange(length)
+    )