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

Commit

Permalink
Merge pull request #949 from matrix-org/rav/update_devices
Browse files Browse the repository at this point in the history
Implement updates and deletes for devices
  • Loading branch information
dbkr authored Jul 26, 2016
2 parents 1a54513 + 012b4c1 commit d34e9f9
Show file tree
Hide file tree
Showing 13 changed files with 295 additions and 28 deletions.
22 changes: 20 additions & 2 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def __init__(self, hs):
self.ldap_bind_password = hs.config.ldap_bind_password

self.hs = hs # FIXME better possibility to access registrationHandler later?
self.device_handler = hs.get_device_handler()

@defer.inlineCallbacks
def check_auth(self, flows, clientdict, clientip):
Expand Down Expand Up @@ -374,7 +375,8 @@ def validate_password_login(self, user_id, password):
return self._check_password(user_id, password)

@defer.inlineCallbacks
def get_login_tuple_for_user_id(self, user_id, device_id=None):
def get_login_tuple_for_user_id(self, user_id, device_id=None,
initial_display_name=None):
"""
Gets login tuple for the user with the given user ID.
Expand All @@ -383,9 +385,15 @@ def get_login_tuple_for_user_id(self, user_id, device_id=None):
The user is assumed to have been authenticated by some other
machanism (e.g. CAS), and the user_id converted to the canonical case.
The device will be recorded in the table if it is not there already.
Args:
user_id (str): canonical User ID
device_id (str): the device ID to associate with the access token
device_id (str|None): the device ID to associate with the tokens.
None to leave the tokens unassociated with a device (deprecated:
we should always have a device ID)
initial_display_name (str): display name to associate with the
device if it needs re-registering
Returns:
A tuple of:
The access token for the user's session.
Expand All @@ -397,6 +405,16 @@ def get_login_tuple_for_user_id(self, user_id, device_id=None):
logger.info("Logging in user %s on device %s", user_id, device_id)
access_token = yield self.issue_access_token(user_id, device_id)
refresh_token = yield self.issue_refresh_token(user_id, device_id)

# the device *should* have been registered before we got here; however,
# it's possible we raced against a DELETE operation. The thing we
# really don't want is active access_tokens without a record of the
# device, so we double-check it here.
if device_id is not None:
yield self.device_handler.check_device_registered(
user_id, device_id, initial_display_name
)

defer.returnValue((access_token, refresh_token))

@defer.inlineCallbacks
Expand Down
51 changes: 50 additions & 1 deletion synapse/handlers/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def get_device(self, user_id, device_id):
Args:
user_id (str):
device_id (str)
device_id (str):
Returns:
defer.Deferred: dict[str, X]: info on the device
Expand All @@ -117,6 +117,55 @@ def get_device(self, user_id, device_id):
_update_device_from_client_ips(device, ips)
defer.returnValue(device)

@defer.inlineCallbacks
def delete_device(self, user_id, device_id):
""" Delete the given device
Args:
user_id (str):
device_id (str):
Returns:
defer.Deferred:
"""

try:
yield self.store.delete_device(user_id, device_id)
except errors.StoreError, e:
if e.code == 404:
# no match
pass
else:
raise

yield self.store.user_delete_access_tokens(user_id,
device_id=device_id)

@defer.inlineCallbacks
def update_device(self, user_id, device_id, content):
""" Update the given device
Args:
user_id (str):
device_id (str):
content (dict): body of update request
Returns:
defer.Deferred:
"""

try:
yield self.store.update_device(
user_id,
device_id,
new_display_name=content.get("display_name")
)
except errors.StoreError, e:
if e.code == 404:
raise errors.NotFoundError()
else:
raise


def _update_device_from_client_ips(device, client_ips):
ip = client_ips.get((device["user_id"], device["device_id"]), {})
Expand Down
1 change: 1 addition & 0 deletions synapse/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ def __init__(self, hs, canonical_json=True):

def register_paths(self, method, path_patterns, callback):
for path_pattern in path_patterns:
logger.debug("Registering for %s %s", method, path_pattern.pattern)
self.path_regexs.setdefault(method, []).append(
self._PathEntry(path_pattern, callback)
)
Expand Down
13 changes: 10 additions & 3 deletions synapse/rest/client/v1/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ def do_password_login(self, login_submission):
)
device_id = yield self._register_device(user_id, login_submission)
access_token, refresh_token = (
yield auth_handler.get_login_tuple_for_user_id(user_id, device_id)
yield auth_handler.get_login_tuple_for_user_id(
user_id, device_id,
login_submission.get("initial_device_display_name")
)
)
result = {
"user_id": user_id, # may have changed
Expand All @@ -173,7 +176,10 @@ def do_token_login(self, login_submission):
)
device_id = yield self._register_device(user_id, login_submission)
access_token, refresh_token = (
yield auth_handler.get_login_tuple_for_user_id(user_id, device_id)
yield auth_handler.get_login_tuple_for_user_id(
user_id, device_id,
login_submission.get("initial_device_display_name")
)
)
result = {
"user_id": user_id, # may have changed
Expand Down Expand Up @@ -262,7 +268,8 @@ def do_jwt_login(self, login_submission):
)
access_token, refresh_token = (
yield auth_handler.get_login_tuple_for_user_id(
registered_user_id, device_id
registered_user_id, device_id,
login_submission.get("initial_device_display_name")
)
)
result = {
Expand Down
38 changes: 31 additions & 7 deletions synapse/rest/client/v2_alpha/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from twisted.internet import defer
import logging

from synapse.http.servlet import RestServlet
from twisted.internet import defer

from synapse.http import servlet
from ._base import client_v2_patterns

import logging


logger = logging.getLogger(__name__)


class DevicesRestServlet(RestServlet):
class DevicesRestServlet(servlet.RestServlet):
PATTERNS = client_v2_patterns("/devices$", releases=[], v2_alpha=False)

def __init__(self, hs):
Expand All @@ -47,7 +45,7 @@ def on_GET(self, request):
defer.returnValue((200, {"devices": devices}))


class DeviceRestServlet(RestServlet):
class DeviceRestServlet(servlet.RestServlet):
PATTERNS = client_v2_patterns("/devices/(?P<device_id>[^/]*)$",
releases=[], v2_alpha=False)

Expand All @@ -70,6 +68,32 @@ def on_GET(self, request, device_id):
)
defer.returnValue((200, device))

@defer.inlineCallbacks
def on_DELETE(self, request, device_id):
# XXX: it's not completely obvious we want to expose this endpoint.
# It allows the client to delete access tokens, which feels like a
# thing which merits extra auth. But if we want to do the interactive-
# auth dance, we should really make it possible to delete more than one
# device at a time.
requester = yield self.auth.get_user_by_req(request)
yield self.device_handler.delete_device(
requester.user.to_string(),
device_id,
)
defer.returnValue((200, {}))

@defer.inlineCallbacks
def on_PUT(self, request, device_id):
requester = yield self.auth.get_user_by_req(request)

body = servlet.parse_json_object_from_request(request)
yield self.device_handler.update_device(
requester.user.to_string(),
device_id,
body
)
defer.returnValue((200, {}))


def register_servlets(hs, http_server):
DevicesRestServlet(hs).register(http_server)
Expand Down
10 changes: 5 additions & 5 deletions synapse/rest/client/v2_alpha/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,13 +374,13 @@ def _create_registration_details(self, user_id, params):
"""
device_id = yield self._register_device(user_id, params)

access_token = yield self.auth_handler.issue_access_token(
user_id, device_id=device_id
access_token, refresh_token = (
yield self.auth_handler.get_login_tuple_for_user_id(
user_id, device_id=device_id,
initial_display_name=params.get("initial_device_display_name")
)
)

refresh_token = yield self.auth_handler.issue_refresh_token(
user_id, device_id=device_id
)
defer.returnValue({
"user_id": user_id,
"access_token": access_token,
Expand Down
40 changes: 40 additions & 0 deletions synapse/storage/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,46 @@ def get_device(self, user_id, device_id):
desc="get_device",
)

def delete_device(self, user_id, device_id):
"""Delete a device.
Args:
user_id (str): The ID of the user which owns the device
device_id (str): The ID of the device to delete
Returns:
defer.Deferred
"""
return self._simple_delete_one(
table="devices",
keyvalues={"user_id": user_id, "device_id": device_id},
desc="delete_device",
)

def update_device(self, user_id, device_id, new_display_name=None):
"""Update a device.
Args:
user_id (str): The ID of the user which owns the device
device_id (str): The ID of the device to update
new_display_name (str|None): new displayname for device; None
to leave unchanged
Raises:
StoreError: if the device is not found
Returns:
defer.Deferred
"""
updates = {}
if new_display_name is not None:
updates["display_name"] = new_display_name
if not updates:
return defer.succeed(None)
return self._simple_update_one(
table="devices",
keyvalues={"user_id": user_id, "device_id": device_id},
updatevalues=updates,
desc="update_device",
)

@defer.inlineCallbacks
def get_devices_by_user(self, user_id):
"""Retrieve all of a user's registered devices.
Expand Down
26 changes: 22 additions & 4 deletions synapse/storage/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,31 @@
from twisted.internet import defer

from synapse.api.errors import StoreError, Codes

from ._base import SQLBaseStore
from synapse.storage import background_updates
from synapse.util.caches.descriptors import cached, cachedInlineCallbacks


class RegistrationStore(SQLBaseStore):
class RegistrationStore(background_updates.BackgroundUpdateStore):

def __init__(self, hs):
super(RegistrationStore, self).__init__(hs)

self.clock = hs.get_clock()

self.register_background_index_update(
"access_tokens_device_index",
index_name="access_tokens_device_id",
table="access_tokens",
columns=["user_id", "device_id"],
)

self.register_background_index_update(
"refresh_tokens_device_index",
index_name="refresh_tokens_device_id",
table="refresh_tokens",
columns=["user_id", "device_id"],
)

@defer.inlineCallbacks
def add_access_token_to_user(self, user_id, token, device_id=None):
"""Adds an access token for the given user.
Expand Down Expand Up @@ -238,11 +251,16 @@ def user_set_password_hash(self, user_id, password_hash):
self.get_user_by_id.invalidate((user_id,))

@defer.inlineCallbacks
def user_delete_access_tokens(self, user_id, except_token_ids=[]):
def user_delete_access_tokens(self, user_id, except_token_ids=[],
device_id=None):
def f(txn):
sql = "SELECT token FROM access_tokens WHERE user_id = ?"
clauses = [user_id]

if device_id is not None:
sql += " AND device_id = ?"
clauses.append(device_id)

if except_token_ids:
sql += " AND id NOT IN (%s)" % (
",".join(["?" for _ in except_token_ids]),
Expand Down
17 changes: 17 additions & 0 deletions synapse/storage/schema/delta/33/access_tokens_device_index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* Copyright 2016 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.
*/

INSERT INTO background_updates (update_name, progress_json) VALUES
('access_tokens_device_index', '{}');
17 changes: 17 additions & 0 deletions synapse/storage/schema/delta/33/refreshtoken_device_index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* Copyright 2016 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.
*/

INSERT INTO background_updates (update_name, progress_json) VALUES
('refresh_tokens_device_index', '{}');
Loading

0 comments on commit d34e9f9

Please sign in to comment.