diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index 67e780864e..62de4b399f 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -32,9 +32,11 @@ class RegistrationConfig(Config):
)
self.registration_shared_secret = config.get("registration_shared_secret")
+ self.macaroon_secret_key = config.get("macaroon_secret_key")
def default_config(self, config_dir, server_name):
registration_shared_secret = random_string_with_symbols(50)
+ macaroon_secret_key = random_string_with_symbols(50)
return """\
## Registration ##
@@ -44,6 +46,8 @@ class RegistrationConfig(Config):
# 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"
+
+ macaroon_secret_key: "%(macaroon_secret_key)s"
""" % locals()
def add_arguments(self, parser):
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index 39392d9fdd..1adc3eebbb 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -25,9 +25,9 @@ import synapse.util.stringutils as stringutils
from synapse.util.async import run_on_reactor
from synapse.http.client import CaptchaServerHttpClient
-import base64
import bcrypt
import logging
+import pymacaroons
import urllib
logger = logging.getLogger(__name__)
@@ -274,11 +274,18 @@ class RegistrationHandler(BaseHandler):
)
def generate_token(self, user_id):
- # urlsafe variant uses _ and - so use . as the separator and replace
- # all =s with .s so http clients don't quote =s when it is used as
- # query params.
- return (base64.urlsafe_b64encode(user_id).replace('=', '.') + '.' +
- stringutils.random_string(18))
+ macaroon = pymacaroons.Macaroon(
+ location=self.hs.config.server_name,
+ identifier="key",
+ key=self.hs.config.macaroon_secret_key)
+ macaroon.add_first_party_caveat("gen = 1")
+ macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
+ macaroon.add_first_party_caveat("type = access")
+ now = self.hs.get_clock().time_msec()
+ expiry = now + (60 * 60 * 1000)
+ macaroon.add_first_party_caveat("time < %d" % (expiry,))
+
+ return macaroon.serialize()
def _generate_user_id(self):
return "-" + stringutils.random_string(18)
diff --git a/synapse/python_dependencies.py b/synapse/python_dependencies.py
index 01fc4ea1e5..a87fdeb2a0 100644
--- a/synapse/python_dependencies.py
+++ b/synapse/python_dependencies.py
@@ -33,6 +33,7 @@ REQUIREMENTS = {
"ujson": ["ujson"],
"blist": ["blist"],
"pysaml2": ["saml2"],
+ "pymacaroons-pynacl": ["pymacaroons"],
}
CONDITIONAL_REQUIREMENTS = {
"web_client": {
diff --git a/tests/handlers/test_register.py b/tests/handlers/test_register.py
new file mode 100644
index 0000000000..91cc90242f
--- /dev/null
+++ b/tests/handlers/test_register.py
@@ -0,0 +1,70 @@
+# -*- 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 pymacaroons
+
+from mock import Mock, NonCallableMock
+from synapse.handlers.register import RegistrationHandler
+from tests import unittest
+from tests.utils import setup_test_homeserver
+from twisted.internet import defer
+
+
+class RegisterHandlers(object):
+ def __init__(self, hs):
+ self.registration_handler = RegistrationHandler(hs)
+
+
+class RegisterTestCase(unittest.TestCase):
+ @defer.inlineCallbacks
+ def setUp(self):
+ self.hs = yield setup_test_homeserver(handlers=None)
+ self.hs.handlers = RegisterHandlers(self.hs)
+
+ def test_token_is_a_macaroon(self):
+ self.hs.config.macaroon_secret_key = "this key is a huge secret"
+
+ token = self.hs.handlers.registration_handler.generate_token("some_user")
+ # Check that we can parse the thing with pymacaroons
+ macaroon = pymacaroons.Macaroon.deserialize(token)
+ # The most basic of sanity checks
+ if "some_user" not in macaroon.inspect():
+ self.fail("some_user was not in %s" % macaroon.inspect())
+
+ def test_macaroon_caveats(self):
+ self.hs.config.macaroon_secret_key = "this key is a massive secret"
+ self.hs.clock.now = 5000
+
+ token = self.hs.handlers.registration_handler.generate_token("a_user")
+ macaroon = pymacaroons.Macaroon.deserialize(token)
+
+ def verify_gen(caveat):
+ return caveat == "gen = 1"
+
+ def verify_user(caveat):
+ return caveat == "user_id = a_user"
+
+ def verify_type(caveat):
+ return caveat == "type = access"
+
+ def verify_expiry(caveat):
+ return caveat == "time < 8600000"
+
+ v = pymacaroons.Verifier()
+ v.satisfy_general(verify_gen)
+ v.satisfy_general(verify_user)
+ v.satisfy_general(verify_type)
+ v.satisfy_general(verify_expiry)
+ v.verify(macaroon, self.hs.config.macaroon_secret_key)
diff --git a/tests/utils.py b/tests/utils.py
index eb035cf48f..80be70b74f 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -44,6 +44,8 @@ def setup_test_homeserver(name="test", datastore=None, config=None, **kargs):
config.signing_key = [MockKey()]
config.event_cache_size = 1
config.disable_registration = False
+ config.macaroon_secret_key = "not even a little secret"
+ config.server_name = "server.under.test"
if "clock" not in kargs:
kargs["clock"] = MockClock()
|