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

Implement updates and deletes for devices #949

Merged
merged 3 commits into from
Jul 26, 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
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