2015-02-09 07:14:15 -07:00
|
|
|
# -*- coding: utf-8 -*-
|
2016-01-05 11:01:18 -07:00
|
|
|
# Copyright 2015 - 2016 OpenMarket Ltd
|
2015-02-09 07:14:15 -07:00
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
from tests import unittest
|
|
|
|
from twisted.internet import defer
|
|
|
|
|
2015-02-09 07:16:36 -07:00
|
|
|
from mock import Mock
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
from synapse.api.auth import Auth
|
|
|
|
from synapse.api.errors import AuthError
|
2015-08-26 06:22:23 -06:00
|
|
|
from synapse.types import UserID
|
|
|
|
from tests.utils import setup_test_homeserver
|
|
|
|
|
|
|
|
import pymacaroons
|
2015-02-09 07:16:36 -07:00
|
|
|
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
class AuthTestCase(unittest.TestCase):
|
|
|
|
|
2015-08-26 06:22:23 -06:00
|
|
|
@defer.inlineCallbacks
|
2015-02-09 07:14:15 -07:00
|
|
|
def setUp(self):
|
|
|
|
self.state_handler = Mock()
|
|
|
|
self.store = Mock()
|
|
|
|
|
2015-08-26 06:22:23 -06:00
|
|
|
self.hs = yield setup_test_homeserver(handlers=None)
|
2015-02-09 07:14:15 -07:00
|
|
|
self.hs.get_datastore = Mock(return_value=self.store)
|
|
|
|
self.auth = Auth(self.hs)
|
|
|
|
|
|
|
|
self.test_user = "@foo:bar"
|
|
|
|
self.test_token = "_test_token_"
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_by_req_user_valid_token(self):
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=None)
|
|
|
|
user_info = {
|
|
|
|
"name": self.test_user,
|
|
|
|
"token_id": "ditto",
|
2016-07-20 08:25:40 -06:00
|
|
|
"device_id": "device",
|
2015-02-09 07:14:15 -07:00
|
|
|
}
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=user_info)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.args["access_token"] = [self.test_token]
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
2016-01-11 08:29:57 -07:00
|
|
|
requester = yield self.auth.get_user_by_req(request)
|
|
|
|
self.assertEquals(requester.user.to_string(), self.test_user)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
def test_get_user_by_req_user_bad_token(self):
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=None)
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=None)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.args["access_token"] = [self.test_token]
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
|
|
|
d = self.auth.get_user_by_req(request)
|
|
|
|
self.failureResultOf(d, AuthError)
|
|
|
|
|
|
|
|
def test_get_user_by_req_user_missing_token(self):
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=None)
|
|
|
|
user_info = {
|
|
|
|
"name": self.test_user,
|
|
|
|
"token_id": "ditto",
|
|
|
|
}
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=user_info)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
|
|
|
d = self.auth.get_user_by_req(request)
|
|
|
|
self.failureResultOf(d, AuthError)
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_by_req_appservice_valid_token(self):
|
|
|
|
app_service = Mock(token="foobar", url="a_url", sender=self.test_user)
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=app_service)
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=None)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.args["access_token"] = [self.test_token]
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
2016-01-11 08:29:57 -07:00
|
|
|
requester = yield self.auth.get_user_by_req(request)
|
|
|
|
self.assertEquals(requester.user.to_string(), self.test_user)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
def test_get_user_by_req_appservice_bad_token(self):
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=None)
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=None)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.args["access_token"] = [self.test_token]
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
|
|
|
d = self.auth.get_user_by_req(request)
|
|
|
|
self.failureResultOf(d, AuthError)
|
|
|
|
|
|
|
|
def test_get_user_by_req_appservice_missing_token(self):
|
|
|
|
app_service = Mock(token="foobar", url="a_url", sender=self.test_user)
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=app_service)
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=None)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
|
|
|
d = self.auth.get_user_by_req(request)
|
|
|
|
self.failureResultOf(d, AuthError)
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_by_req_appservice_valid_token_valid_user_id(self):
|
|
|
|
masquerading_user_id = "@doppelganger:matrix.org"
|
|
|
|
app_service = Mock(token="foobar", url="a_url", sender=self.test_user)
|
|
|
|
app_service.is_interested_in_user = Mock(return_value=True)
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=app_service)
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=None)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.args["access_token"] = [self.test_token]
|
|
|
|
request.args["user_id"] = [masquerading_user_id]
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
2016-01-11 08:29:57 -07:00
|
|
|
requester = yield self.auth.get_user_by_req(request)
|
|
|
|
self.assertEquals(requester.user.to_string(), masquerading_user_id)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
def test_get_user_by_req_appservice_valid_token_bad_user_id(self):
|
|
|
|
masquerading_user_id = "@doppelganger:matrix.org"
|
|
|
|
app_service = Mock(token="foobar", url="a_url", sender=self.test_user)
|
|
|
|
app_service.is_interested_in_user = Mock(return_value=False)
|
|
|
|
self.store.get_app_service_by_token = Mock(return_value=app_service)
|
2015-08-20 09:01:29 -06:00
|
|
|
self.store.get_user_by_access_token = Mock(return_value=None)
|
2015-02-09 07:14:15 -07:00
|
|
|
|
|
|
|
request = Mock(args={})
|
|
|
|
request.args["access_token"] = [self.test_token]
|
|
|
|
request.args["user_id"] = [masquerading_user_id]
|
|
|
|
request.requestHeaders.getRawHeaders = Mock(return_value=[""])
|
|
|
|
d = self.auth.get_user_by_req(request)
|
|
|
|
self.failureResultOf(d, AuthError)
|
2015-08-26 06:22:23 -06:00
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_from_macaroon(self):
|
|
|
|
# TODO(danielwh): Remove this mock when we remove the
|
|
|
|
# get_user_by_access_token fallback.
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
2016-07-20 08:25:40 -06:00
|
|
|
return_value={
|
|
|
|
"name": "@baldrick:matrix.org",
|
|
|
|
"device_id": "device",
|
|
|
|
}
|
2015-08-26 06:22:23 -06:00
|
|
|
)
|
|
|
|
|
2015-09-01 05:41:16 -06:00
|
|
|
user_id = "@baldrick:matrix.org"
|
2015-08-26 06:22:23 -06:00
|
|
|
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("type = access")
|
2015-09-01 05:41:16 -06:00
|
|
|
macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
|
2016-01-05 11:01:18 -07:00
|
|
|
user_info = yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
2015-09-01 05:41:16 -06:00
|
|
|
user = user_info["user"]
|
|
|
|
self.assertEqual(UserID.from_string(user_id), user)
|
2015-08-26 06:22:23 -06:00
|
|
|
|
2016-07-20 08:25:40 -06:00
|
|
|
# TODO: device_id should come from the macaroon, but currently comes
|
|
|
|
# from the db.
|
|
|
|
self.assertEqual(user_info["device_id"], "device")
|
|
|
|
|
2015-11-04 10:29:07 -07:00
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_guest_user_from_macaroon(self):
|
|
|
|
user_id = "@baldrick:matrix.org"
|
|
|
|
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("type = access")
|
|
|
|
macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
|
|
|
|
macaroon.add_first_party_caveat("guest = true")
|
|
|
|
serialized = macaroon.serialize()
|
|
|
|
|
2016-01-05 11:01:18 -07:00
|
|
|
user_info = yield self.auth.get_user_from_macaroon(serialized)
|
2015-11-04 10:29:07 -07:00
|
|
|
user = user_info["user"]
|
|
|
|
is_guest = user_info["is_guest"]
|
|
|
|
self.assertEqual(UserID.from_string(user_id), user)
|
|
|
|
self.assertTrue(is_guest)
|
|
|
|
|
2015-08-26 06:22:23 -06:00
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_from_macaroon_user_db_mismatch(self):
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@percy:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
user = "@baldrick:matrix.org"
|
|
|
|
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("type = access")
|
|
|
|
macaroon.add_first_party_caveat("user_id = %s" % (user,))
|
|
|
|
with self.assertRaises(AuthError) as cm:
|
2016-01-05 11:01:18 -07:00
|
|
|
yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
2015-08-26 06:22:23 -06:00
|
|
|
self.assertEqual(401, cm.exception.code)
|
|
|
|
self.assertIn("User mismatch", cm.exception.msg)
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_from_macaroon_missing_caveat(self):
|
|
|
|
# TODO(danielwh): Remove this mock when we remove the
|
|
|
|
# get_user_by_access_token fallback.
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@baldrick:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
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("type = access")
|
|
|
|
|
|
|
|
with self.assertRaises(AuthError) as cm:
|
2016-01-05 11:01:18 -07:00
|
|
|
yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
2015-08-26 06:22:23 -06:00
|
|
|
self.assertEqual(401, cm.exception.code)
|
|
|
|
self.assertIn("No user caveat", cm.exception.msg)
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_from_macaroon_wrong_key(self):
|
|
|
|
# TODO(danielwh): Remove this mock when we remove the
|
|
|
|
# get_user_by_access_token fallback.
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@baldrick:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
user = "@baldrick:matrix.org"
|
|
|
|
macaroon = pymacaroons.Macaroon(
|
|
|
|
location=self.hs.config.server_name,
|
|
|
|
identifier="key",
|
|
|
|
key=self.hs.config.macaroon_secret_key + "wrong")
|
|
|
|
macaroon.add_first_party_caveat("gen = 1")
|
|
|
|
macaroon.add_first_party_caveat("type = access")
|
|
|
|
macaroon.add_first_party_caveat("user_id = %s" % (user,))
|
|
|
|
|
|
|
|
with self.assertRaises(AuthError) as cm:
|
2016-01-05 11:01:18 -07:00
|
|
|
yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
2015-08-26 06:22:23 -06:00
|
|
|
self.assertEqual(401, cm.exception.code)
|
|
|
|
self.assertIn("Invalid macaroon", cm.exception.msg)
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_from_macaroon_unknown_caveat(self):
|
|
|
|
# TODO(danielwh): Remove this mock when we remove the
|
|
|
|
# get_user_by_access_token fallback.
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@baldrick:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
user = "@baldrick:matrix.org"
|
|
|
|
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("type = access")
|
|
|
|
macaroon.add_first_party_caveat("user_id = %s" % (user,))
|
|
|
|
macaroon.add_first_party_caveat("cunning > fox")
|
|
|
|
|
|
|
|
with self.assertRaises(AuthError) as cm:
|
2016-01-05 11:01:18 -07:00
|
|
|
yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
2015-08-26 06:22:23 -06:00
|
|
|
self.assertEqual(401, cm.exception.code)
|
|
|
|
self.assertIn("Invalid macaroon", cm.exception.msg)
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_from_macaroon_expired(self):
|
|
|
|
# TODO(danielwh): Remove this mock when we remove the
|
|
|
|
# get_user_by_access_token fallback.
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@baldrick:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@baldrick:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
user = "@baldrick:matrix.org"
|
|
|
|
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("type = access")
|
|
|
|
macaroon.add_first_party_caveat("user_id = %s" % (user,))
|
2016-07-08 08:53:18 -06:00
|
|
|
macaroon.add_first_party_caveat("time < -2000") # ms
|
2015-08-26 06:22:23 -06:00
|
|
|
|
2016-02-19 08:34:38 -07:00
|
|
|
self.hs.clock.now = 5000 # seconds
|
2016-04-20 08:21:40 -06:00
|
|
|
self.hs.config.expire_access_token = True
|
|
|
|
# yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
2015-08-26 06:56:01 -06:00
|
|
|
# TODO(daniel): Turn on the check that we validate expiration, when we
|
|
|
|
# validate expiration (and remove the above line, which will start
|
|
|
|
# throwing).
|
2016-04-20 08:21:40 -06:00
|
|
|
with self.assertRaises(AuthError) as cm:
|
|
|
|
yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
|
|
|
self.assertEqual(401, cm.exception.code)
|
|
|
|
self.assertIn("Invalid macaroon", cm.exception.msg)
|
2016-07-08 08:53:18 -06:00
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def test_get_user_from_macaroon_with_valid_duration(self):
|
|
|
|
# TODO(danielwh): Remove this mock when we remove the
|
|
|
|
# get_user_by_access_token fallback.
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@baldrick:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
self.store.get_user_by_access_token = Mock(
|
|
|
|
return_value={"name": "@baldrick:matrix.org"}
|
|
|
|
)
|
|
|
|
|
|
|
|
user_id = "@baldrick:matrix.org"
|
|
|
|
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("type = access")
|
|
|
|
macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
|
|
|
|
macaroon.add_first_party_caveat("time < 900000000") # ms
|
|
|
|
|
|
|
|
self.hs.clock.now = 5000 # seconds
|
|
|
|
self.hs.config.expire_access_token = True
|
|
|
|
|
|
|
|
user_info = yield self.auth.get_user_from_macaroon(macaroon.serialize())
|
|
|
|
user = user_info["user"]
|
|
|
|
self.assertEqual(UserID.from_string(user_id), user)
|