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

Fix token login #993

Merged
merged 2 commits into from
Aug 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 30 additions & 15 deletions synapse/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,27 +675,18 @@ def get_user_from_macaroon(self, macaroon_str, rights="access"):
try:
macaroon = pymacaroons.Macaroon.deserialize(macaroon_str)

user_prefix = "user_id = "
user = None
user_id = None
guest = False
for caveat in macaroon.caveats:
if caveat.caveat_id.startswith(user_prefix):
user_id = caveat.caveat_id[len(user_prefix):]
user = UserID.from_string(user_id)
elif caveat.caveat_id == "guest = true":
guest = True
user_id = self.get_user_id_from_macaroon(macaroon)
user = UserID.from_string(user_id)

self.validate_macaroon(
macaroon, rights, self.hs.config.expire_access_token,
user_id=user_id,
)

if user is None:
raise AuthError(
self.TOKEN_NOT_FOUND_HTTP_STATUS, "No user caveat in macaroon",
errcode=Codes.UNKNOWN_TOKEN
)
guest = False
for caveat in macaroon.caveats:
if caveat.caveat_id == "guest = true":
guest = True

if guest:
ret = {
Expand Down Expand Up @@ -743,6 +734,29 @@ def get_user_from_macaroon(self, macaroon_str, rights="access"):
errcode=Codes.UNKNOWN_TOKEN
)

def get_user_id_from_macaroon(self, macaroon):
"""Retrieve the user_id given by the caveats on the macaroon.

Does *not* validate the macaroon.

Args:
macaroon (pymacaroons.Macaroon): The macaroon to validate

Returns:
(str) user id

Raises:
AuthError if there is no user_id caveat in the macaroon
"""
user_prefix = "user_id = "
for caveat in macaroon.caveats:
if caveat.caveat_id.startswith(user_prefix):
return caveat.caveat_id[len(user_prefix):]
raise AuthError(
self.TOKEN_NOT_FOUND_HTTP_STATUS, "No user caveat in macaroon",
errcode=Codes.UNKNOWN_TOKEN
)

def validate_macaroon(self, macaroon, type_string, verify_expiry, user_id):
"""
validate that a Macaroon is understood by and was signed by this server.
Expand All @@ -754,6 +768,7 @@ def validate_macaroon(self, macaroon, type_string, verify_expiry, user_id):
verify_expiry(bool): Whether to verify whether the macaroon has expired.
This should really always be True, but no clients currently implement
token refresh, so we can't enforce expiry yet.
user_id (str): The user_id required
"""
v = pymacaroons.Verifier()
v.satisfy_exact("gen = 1")
Expand Down
17 changes: 4 additions & 13 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,10 +720,11 @@ def generate_delete_pusher_token(self, user_id):

def validate_short_term_login_token_and_get_user_id(self, login_token):
try:
macaroon = pymacaroons.Macaroon.deserialize(login_token)
auth_api = self.hs.get_auth()
auth_api.validate_macaroon(macaroon, "login", True)
return self.get_user_from_macaroon(macaroon)
macaroon = pymacaroons.Macaroon.deserialize(login_token)
user_id = auth_api.get_user_id_from_macaroon(macaroon)
auth_api.validate_macaroon(macaroon, "login", True, user_id)
return user_id
except (pymacaroons.exceptions.MacaroonException, TypeError, ValueError):
raise AuthError(401, "Invalid token", errcode=Codes.UNKNOWN_TOKEN)

Expand All @@ -736,16 +737,6 @@ def _generate_base_macaroon(self, user_id):
macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
return macaroon

def get_user_from_macaroon(self, macaroon):
user_prefix = "user_id = "
for caveat in macaroon.caveats:
if caveat.caveat_id.startswith(user_prefix):
return caveat.caveat_id[len(user_prefix):]
raise AuthError(
self.INVALID_TOKEN_HTTP_STATUS, "No user_id found in token",
errcode=Codes.UNKNOWN_TOKEN
)

@defer.inlineCallbacks
def set_password(self, user_id, newpassword, requester=None):
password_hash = self.hash(newpassword)
Expand Down
4 changes: 4 additions & 0 deletions synapse/server.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import synapse.api.auth
import synapse.handlers
import synapse.handlers.auth
import synapse.handlers.device
Expand All @@ -6,6 +7,9 @@ import synapse.storage
import synapse.state

class HomeServer(object):
def get_auth(self) -> synapse.api.auth.Auth:
pass

def get_auth_handler(self) -> synapse.handlers.auth.AuthHandler:
pass

Expand Down
52 changes: 49 additions & 3 deletions tests/handlers/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
# limitations under the License.

import pymacaroons
from twisted.internet import defer

import synapse
import synapse.api.errors
from synapse.handlers.auth import AuthHandler
from tests import unittest
from tests.utils import setup_test_homeserver
from twisted.internet import defer


class AuthHandlers(object):
Expand All @@ -31,11 +33,12 @@ class AuthTestCase(unittest.TestCase):
def setUp(self):
self.hs = yield setup_test_homeserver(handlers=None)
self.hs.handlers = AuthHandlers(self.hs)
self.auth_handler = self.hs.handlers.auth_handler

def test_token_is_a_macaroon(self):
self.hs.config.macaroon_secret_key = "this key is a huge secret"

token = self.hs.handlers.auth_handler.generate_access_token("some_user")
token = self.auth_handler.generate_access_token("some_user")
# Check that we can parse the thing with pymacaroons
macaroon = pymacaroons.Macaroon.deserialize(token)
# The most basic of sanity checks
Expand All @@ -46,7 +49,7 @@ 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.auth_handler.generate_access_token("a_user")
token = self.auth_handler.generate_access_token("a_user")
macaroon = pymacaroons.Macaroon.deserialize(token)

def verify_gen(caveat):
Expand All @@ -67,3 +70,46 @@ def verify_expiry(caveat):
v.satisfy_general(verify_type)
v.satisfy_general(verify_expiry)
v.verify(macaroon, self.hs.config.macaroon_secret_key)

def test_short_term_login_token_gives_user_id(self):
self.hs.clock.now = 1000

token = self.auth_handler.generate_short_term_login_token(
"a_user", 5000
)

self.assertEqual(
"a_user",
self.auth_handler.validate_short_term_login_token_and_get_user_id(
token
)
)

# when we advance the clock, the token should be rejected
self.hs.clock.now = 6000
with self.assertRaises(synapse.api.errors.AuthError):
self.auth_handler.validate_short_term_login_token_and_get_user_id(
token
)

def test_short_term_login_token_cannot_replace_user_id(self):
token = self.auth_handler.generate_short_term_login_token(
"a_user", 5000
)
macaroon = pymacaroons.Macaroon.deserialize(token)

self.assertEqual(
"a_user",
self.auth_handler.validate_short_term_login_token_and_get_user_id(
macaroon.serialize()
)
)

# add another "user_id" caveat, which might allow us to override the
# user_id.
macaroon.add_first_party_caveat("user_id = b_user")

with self.assertRaises(synapse.api.errors.AuthError):
self.auth_handler.validate_short_term_login_token_and_get_user_id(
macaroon.serialize()
)