Merge branch 'develop' into rav/get_devices_api
(pick up PR #938 in the hope of fixing the UTs)
This commit is contained in:
commit
7314bf4682
|
@ -586,6 +586,10 @@ class Auth(object):
|
||||||
token_id = user_info["token_id"]
|
token_id = user_info["token_id"]
|
||||||
is_guest = user_info["is_guest"]
|
is_guest = user_info["is_guest"]
|
||||||
|
|
||||||
|
# device_id may not be present if get_user_by_access_token has been
|
||||||
|
# stubbed out.
|
||||||
|
device_id = user_info.get("device_id")
|
||||||
|
|
||||||
ip_addr = self.hs.get_ip_from_request(request)
|
ip_addr = self.hs.get_ip_from_request(request)
|
||||||
user_agent = request.requestHeaders.getRawHeaders(
|
user_agent = request.requestHeaders.getRawHeaders(
|
||||||
"User-Agent",
|
"User-Agent",
|
||||||
|
@ -597,7 +601,8 @@ class Auth(object):
|
||||||
user=user,
|
user=user,
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
ip=ip_addr,
|
ip=ip_addr,
|
||||||
user_agent=user_agent
|
user_agent=user_agent,
|
||||||
|
device_id=device_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_guest and not allow_guest:
|
if is_guest and not allow_guest:
|
||||||
|
@ -695,6 +700,7 @@ class Auth(object):
|
||||||
"user": user,
|
"user": user,
|
||||||
"is_guest": True,
|
"is_guest": True,
|
||||||
"token_id": None,
|
"token_id": None,
|
||||||
|
"device_id": None,
|
||||||
}
|
}
|
||||||
elif rights == "delete_pusher":
|
elif rights == "delete_pusher":
|
||||||
# We don't store these tokens in the database
|
# We don't store these tokens in the database
|
||||||
|
@ -702,13 +708,20 @@ class Auth(object):
|
||||||
"user": user,
|
"user": user,
|
||||||
"is_guest": False,
|
"is_guest": False,
|
||||||
"token_id": None,
|
"token_id": None,
|
||||||
|
"device_id": None,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# This codepath exists so that we can actually return a
|
# This codepath exists for several reasons:
|
||||||
# token ID, because we use token IDs in place of device
|
# * so that we can actually return a token ID, which is used
|
||||||
# identifiers throughout the codebase.
|
# in some parts of the schema (where we probably ought to
|
||||||
# TODO(daniel): Remove this fallback when device IDs are
|
# use device IDs instead)
|
||||||
# properly implemented.
|
# * the only way we currently have to invalidate an
|
||||||
|
# access_token is by removing it from the database, so we
|
||||||
|
# have to check here that it is still in the db
|
||||||
|
# * some attributes (notably device_id) aren't stored in the
|
||||||
|
# macaroon. They probably should be.
|
||||||
|
# TODO: build the dictionary from the macaroon once the
|
||||||
|
# above are fixed
|
||||||
ret = yield self._look_up_user_by_access_token(macaroon_str)
|
ret = yield self._look_up_user_by_access_token(macaroon_str)
|
||||||
if ret["user"] != user:
|
if ret["user"] != user:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
@ -782,10 +795,14 @@ class Auth(object):
|
||||||
self.TOKEN_NOT_FOUND_HTTP_STATUS, "Unrecognised access token.",
|
self.TOKEN_NOT_FOUND_HTTP_STATUS, "Unrecognised access token.",
|
||||||
errcode=Codes.UNKNOWN_TOKEN
|
errcode=Codes.UNKNOWN_TOKEN
|
||||||
)
|
)
|
||||||
|
# we use ret.get() below because *lots* of unit tests stub out
|
||||||
|
# get_user_by_access_token in a way where it only returns a couple of
|
||||||
|
# the fields.
|
||||||
user_info = {
|
user_info = {
|
||||||
"user": UserID.from_string(ret.get("name")),
|
"user": UserID.from_string(ret.get("name")),
|
||||||
"token_id": ret.get("token_id", None),
|
"token_id": ret.get("token_id", None),
|
||||||
"is_guest": False,
|
"is_guest": False,
|
||||||
|
"device_id": ret.get("device_id"),
|
||||||
}
|
}
|
||||||
defer.returnValue(user_info)
|
defer.returnValue(user_info)
|
||||||
|
|
||||||
|
|
|
@ -180,6 +180,9 @@ class MemoryUsageMetric(object):
|
||||||
self.memory_snapshots[:] = self.memory_snapshots[-max_size:]
|
self.memory_snapshots[:] = self.memory_snapshots[-max_size:]
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
|
if not self.memory_snapshots:
|
||||||
|
return []
|
||||||
|
|
||||||
max_rss = max(self.memory_snapshots)
|
max_rss = max(self.memory_snapshots)
|
||||||
min_rss = min(self.memory_snapshots)
|
min_rss = min(self.memory_snapshots)
|
||||||
sum_rss = sum(self.memory_snapshots)
|
sum_rss = sum(self.memory_snapshots)
|
||||||
|
|
|
@ -93,6 +93,7 @@ class RegisterRestServlet(RestServlet):
|
||||||
self.auth_handler = hs.get_auth_handler()
|
self.auth_handler = hs.get_auth_handler()
|
||||||
self.registration_handler = hs.get_handlers().registration_handler
|
self.registration_handler = hs.get_handlers().registration_handler
|
||||||
self.identity_handler = hs.get_handlers().identity_handler
|
self.identity_handler = hs.get_handlers().identity_handler
|
||||||
|
self.device_handler = hs.get_device_handler()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request):
|
def on_POST(self, request):
|
||||||
|
@ -145,7 +146,7 @@ class RegisterRestServlet(RestServlet):
|
||||||
|
|
||||||
if isinstance(desired_username, basestring):
|
if isinstance(desired_username, basestring):
|
||||||
result = yield self._do_appservice_registration(
|
result = yield self._do_appservice_registration(
|
||||||
desired_username, request.args["access_token"][0]
|
desired_username, request.args["access_token"][0], body
|
||||||
)
|
)
|
||||||
defer.returnValue((200, result)) # we throw for non 200 responses
|
defer.returnValue((200, result)) # we throw for non 200 responses
|
||||||
return
|
return
|
||||||
|
@ -155,7 +156,7 @@ class RegisterRestServlet(RestServlet):
|
||||||
# FIXME: Should we really be determining if this is shared secret
|
# FIXME: Should we really be determining if this is shared secret
|
||||||
# auth based purely on the 'mac' key?
|
# auth based purely on the 'mac' key?
|
||||||
result = yield self._do_shared_secret_registration(
|
result = yield self._do_shared_secret_registration(
|
||||||
desired_username, desired_password, body["mac"]
|
desired_username, desired_password, body
|
||||||
)
|
)
|
||||||
defer.returnValue((200, result)) # we throw for non 200 responses
|
defer.returnValue((200, result)) # we throw for non 200 responses
|
||||||
return
|
return
|
||||||
|
@ -236,7 +237,7 @@ class RegisterRestServlet(RestServlet):
|
||||||
add_email = True
|
add_email = True
|
||||||
|
|
||||||
result = yield self._create_registration_details(
|
result = yield self._create_registration_details(
|
||||||
registered_user_id
|
registered_user_id, body
|
||||||
)
|
)
|
||||||
|
|
||||||
if add_email and result and LoginType.EMAIL_IDENTITY in result:
|
if add_email and result and LoginType.EMAIL_IDENTITY in result:
|
||||||
|
@ -252,14 +253,14 @@ class RegisterRestServlet(RestServlet):
|
||||||
return 200, {}
|
return 200, {}
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _do_appservice_registration(self, username, as_token):
|
def _do_appservice_registration(self, username, as_token, body):
|
||||||
user_id = yield self.registration_handler.appservice_register(
|
user_id = yield self.registration_handler.appservice_register(
|
||||||
username, as_token
|
username, as_token
|
||||||
)
|
)
|
||||||
defer.returnValue((yield self._create_registration_details(user_id)))
|
defer.returnValue((yield self._create_registration_details(user_id, body)))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _do_shared_secret_registration(self, username, password, mac):
|
def _do_shared_secret_registration(self, username, password, body):
|
||||||
if not self.hs.config.registration_shared_secret:
|
if not self.hs.config.registration_shared_secret:
|
||||||
raise SynapseError(400, "Shared secret registration is not enabled")
|
raise SynapseError(400, "Shared secret registration is not enabled")
|
||||||
|
|
||||||
|
@ -267,7 +268,7 @@ class RegisterRestServlet(RestServlet):
|
||||||
|
|
||||||
# str() because otherwise hmac complains that 'unicode' does not
|
# str() because otherwise hmac complains that 'unicode' does not
|
||||||
# have the buffer interface
|
# have the buffer interface
|
||||||
got_mac = str(mac)
|
got_mac = str(body["mac"])
|
||||||
|
|
||||||
want_mac = hmac.new(
|
want_mac = hmac.new(
|
||||||
key=self.hs.config.registration_shared_secret,
|
key=self.hs.config.registration_shared_secret,
|
||||||
|
@ -284,7 +285,7 @@ class RegisterRestServlet(RestServlet):
|
||||||
localpart=username, password=password, generate_token=False,
|
localpart=username, password=password, generate_token=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = yield self._create_registration_details(user_id)
|
result = yield self._create_registration_details(user_id, body)
|
||||||
defer.returnValue(result)
|
defer.returnValue(result)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -358,35 +359,58 @@ class RegisterRestServlet(RestServlet):
|
||||||
defer.returnValue()
|
defer.returnValue()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _create_registration_details(self, user_id):
|
def _create_registration_details(self, user_id, body):
|
||||||
"""Complete registration of newly-registered user
|
"""Complete registration of newly-registered user
|
||||||
|
|
||||||
Issues access_token and refresh_token, and builds the success response
|
Allocates device_id if one was not given; also creates access_token
|
||||||
body.
|
and refresh_token.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
(str) user_id: full canonical @user:id
|
(str) user_id: full canonical @user:id
|
||||||
|
(object) body: dictionary supplied to /register call, from
|
||||||
|
which we pull device_id and initial_device_name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
defer.Deferred: (object) dictionary for response from /register
|
defer.Deferred: (object) dictionary for response from /register
|
||||||
"""
|
"""
|
||||||
|
device_id = yield self._register_device(user_id, body)
|
||||||
|
|
||||||
access_token = yield self.auth_handler.issue_access_token(
|
access_token = yield self.auth_handler.issue_access_token(
|
||||||
user_id
|
user_id, device_id=device_id
|
||||||
)
|
)
|
||||||
|
|
||||||
refresh_token = yield self.auth_handler.issue_refresh_token(
|
refresh_token = yield self.auth_handler.issue_refresh_token(
|
||||||
user_id
|
user_id, device_id=device_id
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue({
|
defer.returnValue({
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"access_token": access_token,
|
"access_token": access_token,
|
||||||
"home_server": self.hs.hostname,
|
"home_server": self.hs.hostname,
|
||||||
"refresh_token": refresh_token,
|
"refresh_token": refresh_token,
|
||||||
|
"device_id": device_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def _register_device(self, user_id, body):
|
||||||
|
"""Register a device for a user.
|
||||||
|
|
||||||
|
This is called after the user's credentials have been validated, but
|
||||||
|
before the access token has been issued.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
(str) user_id: full canonical @user:id
|
||||||
|
(object) body: dictionary supplied to /register call, from
|
||||||
|
which we pull device_id and initial_device_name
|
||||||
|
Returns:
|
||||||
|
defer.Deferred: (str) device_id
|
||||||
|
"""
|
||||||
|
# register the user's device
|
||||||
|
device_id = body.get("device_id")
|
||||||
|
initial_display_name = body.get("initial_device_display_name")
|
||||||
|
device_id = self.device_handler.check_device_registered(
|
||||||
|
user_id, device_id, initial_display_name
|
||||||
|
)
|
||||||
|
return device_id
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _do_guest_registration(self):
|
def _do_guest_registration(self):
|
||||||
if not self.hs.config.allow_guest_access:
|
if not self.hs.config.allow_guest_access:
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ClientIpStore(SQLBaseStore):
|
||||||
super(ClientIpStore, self).__init__(hs)
|
super(ClientIpStore, self).__init__(hs)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def insert_client_ip(self, user, access_token, ip, user_agent):
|
def insert_client_ip(self, user, access_token, ip, user_agent, device_id):
|
||||||
now = int(self._clock.time_msec())
|
now = int(self._clock.time_msec())
|
||||||
key = (user.to_string(), access_token, ip)
|
key = (user.to_string(), access_token, ip)
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ class ClientIpStore(SQLBaseStore):
|
||||||
"access_token": access_token,
|
"access_token": access_token,
|
||||||
"ip": ip,
|
"ip": ip,
|
||||||
"user_agent": user_agent,
|
"user_agent": user_agent,
|
||||||
|
"device_id": device_id,
|
||||||
},
|
},
|
||||||
values={
|
values={
|
||||||
"last_seen": now,
|
"last_seen": now,
|
||||||
|
|
|
@ -45,6 +45,7 @@ class AuthTestCase(unittest.TestCase):
|
||||||
user_info = {
|
user_info = {
|
||||||
"name": self.test_user,
|
"name": self.test_user,
|
||||||
"token_id": "ditto",
|
"token_id": "ditto",
|
||||||
|
"device_id": "device",
|
||||||
}
|
}
|
||||||
self.store.get_user_by_access_token = Mock(return_value=user_info)
|
self.store.get_user_by_access_token = Mock(return_value=user_info)
|
||||||
|
|
||||||
|
@ -143,7 +144,10 @@ class AuthTestCase(unittest.TestCase):
|
||||||
# TODO(danielwh): Remove this mock when we remove the
|
# TODO(danielwh): Remove this mock when we remove the
|
||||||
# get_user_by_access_token fallback.
|
# get_user_by_access_token fallback.
|
||||||
self.store.get_user_by_access_token = Mock(
|
self.store.get_user_by_access_token = Mock(
|
||||||
return_value={"name": "@baldrick:matrix.org"}
|
return_value={
|
||||||
|
"name": "@baldrick:matrix.org",
|
||||||
|
"device_id": "device",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
user_id = "@baldrick:matrix.org"
|
user_id = "@baldrick:matrix.org"
|
||||||
|
@ -158,6 +162,10 @@ class AuthTestCase(unittest.TestCase):
|
||||||
user = user_info["user"]
|
user = user_info["user"]
|
||||||
self.assertEqual(UserID.from_string(user_id), user)
|
self.assertEqual(UserID.from_string(user_id), user)
|
||||||
|
|
||||||
|
# TODO: device_id should come from the macaroon, but currently comes
|
||||||
|
# from the db.
|
||||||
|
self.assertEqual(user_info["device_id"], "device")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_guest_user_from_macaroon(self):
|
def test_get_guest_user_from_macaroon(self):
|
||||||
user_id = "@baldrick:matrix.org"
|
user_id = "@baldrick:matrix.org"
|
||||||
|
|
|
@ -30,6 +30,7 @@ class RegisterRestServletTestCase(unittest.TestCase):
|
||||||
self.registration_handler = Mock()
|
self.registration_handler = Mock()
|
||||||
self.identity_handler = Mock()
|
self.identity_handler = Mock()
|
||||||
self.login_handler = Mock()
|
self.login_handler = Mock()
|
||||||
|
self.device_handler = Mock()
|
||||||
|
|
||||||
# do the dance to hook it up to the hs global
|
# do the dance to hook it up to the hs global
|
||||||
self.handlers = Mock(
|
self.handlers = Mock(
|
||||||
|
@ -42,6 +43,7 @@ class RegisterRestServletTestCase(unittest.TestCase):
|
||||||
self.hs.get_auth = Mock(return_value=self.auth)
|
self.hs.get_auth = Mock(return_value=self.auth)
|
||||||
self.hs.get_handlers = Mock(return_value=self.handlers)
|
self.hs.get_handlers = Mock(return_value=self.handlers)
|
||||||
self.hs.get_auth_handler = Mock(return_value=self.auth_handler)
|
self.hs.get_auth_handler = Mock(return_value=self.auth_handler)
|
||||||
|
self.hs.get_device_handler = Mock(return_value=self.device_handler)
|
||||||
self.hs.config.enable_registration = True
|
self.hs.config.enable_registration = True
|
||||||
|
|
||||||
# init the thing we're testing
|
# init the thing we're testing
|
||||||
|
@ -107,9 +109,11 @@ class RegisterRestServletTestCase(unittest.TestCase):
|
||||||
def test_POST_user_valid(self):
|
def test_POST_user_valid(self):
|
||||||
user_id = "@kermit:muppet"
|
user_id = "@kermit:muppet"
|
||||||
token = "kermits_access_token"
|
token = "kermits_access_token"
|
||||||
|
device_id = "frogfone"
|
||||||
self.request_data = json.dumps({
|
self.request_data = json.dumps({
|
||||||
"username": "kermit",
|
"username": "kermit",
|
||||||
"password": "monkey"
|
"password": "monkey",
|
||||||
|
"device_id": device_id,
|
||||||
})
|
})
|
||||||
self.registration_handler.check_username = Mock(return_value=True)
|
self.registration_handler.check_username = Mock(return_value=True)
|
||||||
self.auth_result = (True, None, {
|
self.auth_result = (True, None, {
|
||||||
|
@ -118,18 +122,21 @@ class RegisterRestServletTestCase(unittest.TestCase):
|
||||||
}, None)
|
}, None)
|
||||||
self.registration_handler.register = Mock(return_value=(user_id, None))
|
self.registration_handler.register = Mock(return_value=(user_id, None))
|
||||||
self.auth_handler.issue_access_token = Mock(return_value=token)
|
self.auth_handler.issue_access_token = Mock(return_value=token)
|
||||||
|
self.device_handler.check_device_registered = \
|
||||||
|
Mock(return_value=device_id)
|
||||||
|
|
||||||
(code, result) = yield self.servlet.on_POST(self.request)
|
(code, result) = yield self.servlet.on_POST(self.request)
|
||||||
self.assertEquals(code, 200)
|
self.assertEquals(code, 200)
|
||||||
det_data = {
|
det_data = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"access_token": token,
|
"access_token": token,
|
||||||
"home_server": self.hs.hostname
|
"home_server": self.hs.hostname,
|
||||||
|
"device_id": device_id,
|
||||||
}
|
}
|
||||||
self.assertDictContainsSubset(det_data, result)
|
self.assertDictContainsSubset(det_data, result)
|
||||||
self.assertIn("refresh_token", result)
|
self.assertIn("refresh_token", result)
|
||||||
self.auth_handler.issue_access_token.assert_called_once_with(
|
self.auth_handler.issue_access_token.assert_called_once_with(
|
||||||
user_id)
|
user_id, device_id=device_id)
|
||||||
|
|
||||||
def test_POST_disabled_registration(self):
|
def test_POST_disabled_registration(self):
|
||||||
self.hs.config.enable_registration = False
|
self.hs.config.enable_registration = False
|
||||||
|
|
Loading…
Reference in New Issue