Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #109 from matrix-org/default_registration
Browse files Browse the repository at this point in the history
Disable registration by default. Add script to register new users.
  • Loading branch information
erikjohnston committed Mar 18, 2015
2 parents bb24609 + f88db7a commit ed4d44d
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 17 deletions.
11 changes: 11 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ To set up your homeserver, run (in your virtualenv, as before)::

Substituting your host and domain name as appropriate.

By default, registration of new users is disabled. You can either enable
registration in the config (it is then recommended to also set up CAPTCHA), or
you can use the command line to register new users::

$ source ~/.synapse/bin/activate
$ register_new_matrix_user -c homeserver.yaml https://localhost:8448
New user localpart: erikj
Password:
Confirm password:
Success!

For reliable VoIP calls to be routed via this homeserver, you MUST configure
a TURN server. See docs/turn-howto.rst for details.

Expand Down
149 changes: 149 additions & 0 deletions register_new_matrix_user
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2015 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import argparse
import getpass
import hashlib
import hmac
import json
import sys
import urllib2
import yaml


def request_registration(user, password, server_location, shared_secret):
mac = hmac.new(
key=shared_secret,
msg=user,
digestmod=hashlib.sha1,
).hexdigest()

data = {
"user": user,
"password": password,
"mac": mac,
"type": "org.matrix.login.shared_secret",
}

server_location = server_location.rstrip("/")

print "Sending registration request..."

req = urllib2.Request(
"%s/_matrix/client/api/v1/register" % (server_location,),
data=json.dumps(data),
headers={'Content-Type': 'application/json'}
)
try:
f = urllib2.urlopen(req)
f.read()
f.close()
print "Success."
except urllib2.HTTPError as e:
print "ERROR! Received %d %s" % (e.code, e.reason,)
if 400 <= e.code < 500:
if e.info().type == "application/json":
resp = json.load(e)
if "error" in resp:
print resp["error"]
sys.exit(1)


def register_new_user(user, password, server_location, shared_secret):
if not user:
try:
default_user = getpass.getuser()
except:
default_user = None

if default_user:
user = raw_input("New user localpart [%s]: " % (default_user,))
if not user:
user = default_user
else:
user = raw_input("New user localpart: ")

if not user:
print "Invalid user name"
sys.exit(1)

if not password:
password = getpass.getpass("Password: ")

if not password:
print "Password cannot be blank."
sys.exit(1)

confirm_password = getpass.getpass("Confirm password: ")

if password != confirm_password:
print "Passwords do not match"
sys.exit(1)

request_registration(user, password, server_location, shared_secret)


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Used to register new users with a given home server when"
" registration has been disabled. The home server must be"
" configured with the 'registration_shared_secret' option"
" set.",
)
parser.add_argument(
"-u", "--user",
default=None,
help="Local part of the new user. Will prompt if omitted.",
)
parser.add_argument(
"-p", "--password",
default=None,
help="New password for user. Will prompt if omitted.",
)

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-c", "--config",
type=argparse.FileType('r'),
help="Path to server config file. Used to read in shared secret.",
)

group.add_argument(
"-k", "--shared-secret",
help="Shared secret as defined in server config file.",
)

parser.add_argument(
"server_url",
default="https://localhost:8448",
nargs='?',
help="URL to use to talk to the home server. Defaults to "
" 'https://localhost:8448'.",
)

args = parser.parse_args()

if "config" in args and args.config:
config = yaml.safe_load(args.config)
secret = config.get("registration_shared_secret", None)
if not secret:
print "No 'registration_shared_secret' defined in config."
sys.exit(1)
else:
secret = args.shared_secret

register_new_user(args.user, args.password, args.server_url, secret)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ def exec_file(path_segments):
include_package_data=True,
zip_safe=False,
long_description=long_description,
scripts=["synctl"],
scripts=["synctl", "register_new_matrix_user"],
)
1 change: 1 addition & 0 deletions synapse/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
33 changes: 30 additions & 3 deletions synapse/config/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
8 changes: 8 additions & 0 deletions synapse/handlers/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import bcrypt
import json
import logging
import urllib

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -63,6 +64,13 @@ def register(self, localpart=None, password=None):
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()

Expand Down
4 changes: 2 additions & 2 deletions synapse/http/servlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def register(self, http_server):
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.")
Expand Down
71 changes: 60 additions & 11 deletions synapse/rest/client/v1/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import hmac
import simplejson as json
import logging
import urllib

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -110,14 +109,22 @@ def on_POST(self, request):
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)
Expand Down Expand Up @@ -255,14 +262,11 @@ def _do_password(self, request, register_json, session):
)

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,
Expand Down Expand Up @@ -304,6 +308,51 @@ def _do_app_service(self, request, register_json, session):
"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:
Expand Down
10 changes: 10 additions & 0 deletions synapse/util/stringutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@
import random
import string

_string_with_symbols = (
string.digits + string.ascii_letters + ".,;:^&*-_+=#~@"
)


def origin_from_ucid(ucid):
return ucid.split("@", 1)[1]


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)
)

0 comments on commit ed4d44d

Please sign in to comment.