Merge pull request #229 from matrix-org/auth

Issue macaroons as opaque auth tokens
This commit is contained in:
Daniel Wagner-Hall 2015-08-20 17:25:42 +01:00
commit f483340b3e
5 changed files with 90 additions and 6 deletions

View file

@ -32,9 +32,11 @@ class RegistrationConfig(Config):
) )
self.registration_shared_secret = config.get("registration_shared_secret") 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): def default_config(self, config_dir, server_name):
registration_shared_secret = random_string_with_symbols(50) registration_shared_secret = random_string_with_symbols(50)
macaroon_secret_key = random_string_with_symbols(50)
return """\ return """\
## Registration ## ## Registration ##
@ -44,6 +46,8 @@ class RegistrationConfig(Config):
# If set, allows registration by anyone who also has the shared # If set, allows registration by anyone who also has the shared
# secret, even if registration is otherwise disabled. # secret, even if registration is otherwise disabled.
registration_shared_secret: "%(registration_shared_secret)s" registration_shared_secret: "%(registration_shared_secret)s"
macaroon_secret_key: "%(macaroon_secret_key)s"
""" % locals() """ % locals()
def add_arguments(self, parser): def add_arguments(self, parser):

View file

@ -25,9 +25,9 @@ import synapse.util.stringutils as stringutils
from synapse.util.async import run_on_reactor from synapse.util.async import run_on_reactor
from synapse.http.client import CaptchaServerHttpClient from synapse.http.client import CaptchaServerHttpClient
import base64
import bcrypt import bcrypt
import logging import logging
import pymacaroons
import urllib import urllib
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -274,11 +274,18 @@ class RegistrationHandler(BaseHandler):
) )
def generate_token(self, user_id): def generate_token(self, user_id):
# urlsafe variant uses _ and - so use . as the separator and replace macaroon = pymacaroons.Macaroon(
# all =s with .s so http clients don't quote =s when it is used as location=self.hs.config.server_name,
# query params. identifier="key",
return (base64.urlsafe_b64encode(user_id).replace('=', '.') + '.' + key=self.hs.config.macaroon_secret_key)
stringutils.random_string(18)) 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): def _generate_user_id(self):
return "-" + stringutils.random_string(18) return "-" + stringutils.random_string(18)

View file

@ -33,6 +33,7 @@ REQUIREMENTS = {
"ujson": ["ujson"], "ujson": ["ujson"],
"blist": ["blist"], "blist": ["blist"],
"pysaml2": ["saml2"], "pysaml2": ["saml2"],
"pymacaroons-pynacl": ["pymacaroons"],
} }
CONDITIONAL_REQUIREMENTS = { CONDITIONAL_REQUIREMENTS = {
"web_client": { "web_client": {

View file

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

View file

@ -44,6 +44,8 @@ def setup_test_homeserver(name="test", datastore=None, config=None, **kargs):
config.signing_key = [MockKey()] config.signing_key = [MockKey()]
config.event_cache_size = 1 config.event_cache_size = 1
config.disable_registration = False config.disable_registration = False
config.macaroon_secret_key = "not even a little secret"
config.server_name = "server.under.test"
if "clock" not in kargs: if "clock" not in kargs:
kargs["clock"] = MockClock() kargs["clock"] = MockClock()