From e3117a2a236220f89eb73f0355d00b3b3e83ce35 Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Tue, 23 Sep 2014 18:40:59 +0100 Subject: Add a _matrix/key/v1 resource with the verification keys of the local server --- synapse/api/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'synapse/api') diff --git a/synapse/api/urls.py b/synapse/api/urls.py index 6314f31f7a..6dc19305b7 100644 --- a/synapse/api/urls.py +++ b/synapse/api/urls.py @@ -18,4 +18,5 @@ CLIENT_PREFIX = "/_matrix/client/api/v1" FEDERATION_PREFIX = "/_matrix/federation/v1" WEB_CLIENT_PREFIX = "/_matrix/client" -CONTENT_REPO_PREFIX = "/_matrix/content" \ No newline at end of file +CONTENT_REPO_PREFIX = "/_matrix/content" +SERVER_KEY_PREFIX = "/_matrix/key/v1" -- cgit 1.5.1 From 0fdf3088743b25e9fcc39b7b3b4c7f6e8332e26c Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 26 Sep 2014 16:36:24 +0100 Subject: Track the IP users connect with. Add an admin column to users table. --- synapse/api/auth.py | 10 +++++++++- synapse/rest/register.py | 8 +------- synapse/server.py | 12 ++++++++++++ synapse/storage/__init__.py | 12 +++++++++++- synapse/storage/schema/delta/v5.sql | 13 +++++++++++++ synapse/storage/schema/users.sql | 11 +++++++++++ tests/rest/test_presence.py | 6 +++++- tests/rest/test_profile.py | 4 ++-- tests/utils.py | 3 +++ 9 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 synapse/storage/schema/delta/v5.sql (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 9bfd25c86e..6f8146ec3a 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -206,6 +206,7 @@ class Auth(object): defer.returnValue(True) + @defer.inlineCallbacks def get_user_by_req(self, request): """ Get a registered user's ID. @@ -218,7 +219,14 @@ class Auth(object): """ # Can optionally look elsewhere in the request (e.g. headers) try: - return self.get_user_by_token(request.args["access_token"][0]) + access_token = request.args["access_token"][0] + user = yield self.get_user_by_token(access_token) + + ip_addr = self.hs.get_ip_from_request(request) + if user and access_token and ip_addr: + self.store.insert_client_ip(user, access_token, ip_addr) + + defer.returnValue(user) except KeyError: raise AuthError(403, "Missing access token.") diff --git a/synapse/rest/register.py b/synapse/rest/register.py index 4935e323d9..804117ee09 100644 --- a/synapse/rest/register.py +++ b/synapse/rest/register.py @@ -195,13 +195,7 @@ class RegisterRestServlet(RestServlet): raise SynapseError(400, "Captcha response is required", errcode=Codes.CAPTCHA_NEEDED) - # May be an X-Forwarding-For header depending on config - ip_addr = request.getClientIP() - if self.hs.config.captcha_ip_origin_is_x_forwarded: - # use the header - if request.requestHeaders.hasHeader("X-Forwarded-For"): - ip_addr = request.requestHeaders.getRawHeaders( - "X-Forwarded-For")[0] + ip_addr = self.hs.get_ip_from_request(request) handler = self.handlers.registration_handler yield handler.check_recaptcha( diff --git a/synapse/server.py b/synapse/server.py index cdea49e6ab..e5b048ede0 100644 --- a/synapse/server.py +++ b/synapse/server.py @@ -143,6 +143,18 @@ class BaseHomeServer(object): def serialize_event(self, e): return serialize_event(self, e) + def get_ip_from_request(self, request): + # May be an X-Forwarding-For header depending on config + ip_addr = request.getClientIP() + if self.config.captcha_ip_origin_is_x_forwarded: + # use the header + if request.requestHeaders.hasHeader("X-Forwarded-For"): + ip_addr = request.requestHeaders.getRawHeaders( + "X-Forwarded-For" + )[0] + + return ip_addr + # Build magic accessors for every dependency for depname in BaseHomeServer.DEPENDENCIES: BaseHomeServer._make_dependency_method(depname) diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 15919eb580..d53c090a91 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -63,7 +63,7 @@ SCHEMAS = [ # Remember to update this number every time an incompatible change is made to # database schema files, so the users will be informed on server restarts. -SCHEMA_VERSION = 4 +SCHEMA_VERSION = 5 class _RollbackButIsFineException(Exception): @@ -294,6 +294,16 @@ class DataStore(RoomMemberStore, RoomStore, defer.returnValue(self.min_token) + def insert_client_ip(self, user, access_token, ip): + return self._simple_insert( + "user_ips", + { + "user": user.to_string(), + "access_token": access_token, + "ip": ip + } + ) + def snapshot_room(self, room_id, user_id, state_type=None, state_key=None): """Snapshot the room for an update by a user Args: diff --git a/synapse/storage/schema/delta/v5.sql b/synapse/storage/schema/delta/v5.sql new file mode 100644 index 0000000000..380eec6f35 --- /dev/null +++ b/synapse/storage/schema/delta/v5.sql @@ -0,0 +1,13 @@ + +CREATE TABLE IF NOT EXISTS user_ips ( + user TEXT NOT NULL, + access_token TEXT NOT NULL, + ip TEXT NOT NULL, + CONSTRAINT user_ip UNIQUE (user, access_token, ip) ON CONFLICT IGNORE +); + +CREATE INDEX IF NOT EXISTS user_ips_user ON user_ips(user); + +ALTER TABLE users ADD COLUMN admin BOOL DEFAULT 0 NOT NULL; + +PRAGMA user_version = 5; diff --git a/synapse/storage/schema/users.sql b/synapse/storage/schema/users.sql index 2519702971..89eab8babe 100644 --- a/synapse/storage/schema/users.sql +++ b/synapse/storage/schema/users.sql @@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS users( name TEXT, password_hash TEXT, creation_ts INTEGER, + admin BOOL DEFAULT 0 NOT NULL, UNIQUE(name) ON CONFLICT ROLLBACK ); @@ -29,3 +30,13 @@ CREATE TABLE IF NOT EXISTS access_tokens( FOREIGN KEY(user_id) REFERENCES users(id), UNIQUE(token) ON CONFLICT ROLLBACK ); + +CREATE TABLE IF NOT EXISTS user_ips ( + user TEXT NOT NULL, + access_token TEXT NOT NULL, + ip TEXT NOT NULL, + CONSTRAINT user_ip UNIQUE (user, access_token, ip) ON CONFLICT IGNORE +); + +CREATE INDEX IF NOT EXISTS user_ips_user ON user_ips(user); + diff --git a/tests/rest/test_presence.py b/tests/rest/test_presence.py index ea3478ac5d..1b3e6759c2 100644 --- a/tests/rest/test_presence.py +++ b/tests/rest/test_presence.py @@ -51,10 +51,12 @@ class PresenceStateTestCase(unittest.TestCase): datastore=Mock(spec=[ "get_presence_state", "set_presence_state", + "insert_client_ip", ]), http_client=None, resource_for_client=self.mock_resource, resource_for_federation=self.mock_resource, + config=Mock(), ) hs.handlers = JustPresenceHandlers(hs) @@ -131,10 +133,12 @@ class PresenceListTestCase(unittest.TestCase): "set_presence_list_accepted", "del_presence_list", "get_presence_list", + "insert_client_ip", ]), http_client=None, resource_for_client=self.mock_resource, - resource_for_federation=self.mock_resource + resource_for_federation=self.mock_resource, + config=Mock(), ) hs.handlers = JustPresenceHandlers(hs) diff --git a/tests/rest/test_profile.py b/tests/rest/test_profile.py index e6e51f6dd0..b0f48e7fd8 100644 --- a/tests/rest/test_profile.py +++ b/tests/rest/test_profile.py @@ -50,10 +50,10 @@ class ProfileTestCase(unittest.TestCase): datastore=None, ) - def _get_user_by_token(token=None): + def _get_user_by_req(request=None): return hs.parse_userid(myid) - hs.get_auth().get_user_by_token = _get_user_by_token + hs.get_auth().get_user_by_req = _get_user_by_req hs.get_handlers().profile_handler = self.mock_handler diff --git a/tests/utils.py b/tests/utils.py index bb8e9964dd..ae97621147 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -264,6 +264,9 @@ class MemoryDataStore(object): def get_ops_levels(self, room_id): return defer.succeed((5, 5, 5)) + def insert_client_ip(self, user, access_token, ip_addr): + return defer.succeed(None) + def _format_call(args, kwargs): return ", ".join( -- cgit 1.5.1 From f7d80930f21190355733da9ba7ab5068ce0702a8 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 29 Sep 2014 13:35:15 +0100 Subject: SYN-48: Track User-Agents as well as IPs for client devices. --- synapse/api/auth.py | 11 ++++++++++- synapse/storage/__init__.py | 6 ++++-- synapse/storage/schema/delta/v5.sql | 4 +++- synapse/storage/schema/users.sql | 4 +++- 4 files changed, 20 insertions(+), 5 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 6f8146ec3a..739f77afd4 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -223,8 +223,17 @@ class Auth(object): user = yield self.get_user_by_token(access_token) ip_addr = self.hs.get_ip_from_request(request) + user_agent = request.requestHeaders.getRawHeaders( + "User-Agent", + default=[""] + )[0] if user and access_token and ip_addr: - self.store.insert_client_ip(user, access_token, ip_addr) + self.store.insert_client_ip( + user, + access_token, + ip_addr, + user_agent + ) defer.returnValue(user) except KeyError: diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index d53c090a91..169a80dce4 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -294,13 +294,15 @@ class DataStore(RoomMemberStore, RoomStore, defer.returnValue(self.min_token) - def insert_client_ip(self, user, access_token, ip): + def insert_client_ip(self, user, access_token, ip, user_agent): return self._simple_insert( "user_ips", { "user": user.to_string(), "access_token": access_token, - "ip": ip + "ip": ip, + "user_agent": user_agent, + "last_used": int(self._clock.time()), } ) diff --git a/synapse/storage/schema/delta/v5.sql b/synapse/storage/schema/delta/v5.sql index 380eec6f35..f5a667a250 100644 --- a/synapse/storage/schema/delta/v5.sql +++ b/synapse/storage/schema/delta/v5.sql @@ -3,7 +3,9 @@ CREATE TABLE IF NOT EXISTS user_ips ( user TEXT NOT NULL, access_token TEXT NOT NULL, ip TEXT NOT NULL, - CONSTRAINT user_ip UNIQUE (user, access_token, ip) ON CONFLICT IGNORE + user_agent TEXT NOT NULL, + last_used INTEGER NOT NULL, + CONSTRAINT user_ip UNIQUE (user, access_token, ip, user_agent) ON CONFLICT REPLACE ); CREATE INDEX IF NOT EXISTS user_ips_user ON user_ips(user); diff --git a/synapse/storage/schema/users.sql b/synapse/storage/schema/users.sql index 89eab8babe..d96dd9f075 100644 --- a/synapse/storage/schema/users.sql +++ b/synapse/storage/schema/users.sql @@ -35,7 +35,9 @@ CREATE TABLE IF NOT EXISTS user_ips ( user TEXT NOT NULL, access_token TEXT NOT NULL, ip TEXT NOT NULL, - CONSTRAINT user_ip UNIQUE (user, access_token, ip) ON CONFLICT IGNORE + user_agent TEXT NOT NULL, + last_used INTEGER NOT NULL, + CONSTRAINT user_ip UNIQUE (user, access_token, ip, user_agent) ON CONFLICT REPLACE ); CREATE INDEX IF NOT EXISTS user_ips_user ON user_ips(user); -- cgit 1.5.1 From c65306f8779d480a10b6ce318e73b458538e1eef Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 29 Sep 2014 13:35:38 +0100 Subject: Add auth check to test if a user is an admin or not. --- synapse/api/auth.py | 3 +++ synapse/storage/registration.py | 8 ++++++++ 2 files changed, 11 insertions(+) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 739f77afd4..5e3ea5b8c5 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -259,6 +259,9 @@ class Auth(object): raise AuthError(403, "Unrecognised access token.", errcode=Codes.UNKNOWN_TOKEN) + def is_server_admin(self, user): + return self.store.is_server_admin(user) + @defer.inlineCallbacks @log_function def _can_send_event(self, event): diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py index db20b1daa0..f32b000cb6 100644 --- a/synapse/storage/registration.py +++ b/synapse/storage/registration.py @@ -103,6 +103,14 @@ class RegistrationStore(SQLBaseStore): token) defer.returnValue(user_id) + @defer.inlineCallbacks + def is_server_admin(self, user): + return self._simple_select_one_onecol( + table="users", + keyvalues={"name": user.to_string()}, + retcol="admin", + ) + def _query_for_auth(self, txn, token): txn.execute("SELECT users.name FROM access_tokens LEFT JOIN users" + " ON users.id = access_tokens.user_id WHERE token = ?", -- cgit 1.5.1 From 3ccb17ce592d7e75e0bd0237c347d64f63d5eb10 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 29 Sep 2014 14:59:52 +0100 Subject: SYN-48: Implement WHOIS rest servlet --- synapse/api/auth.py | 28 +++++++++++------ synapse/handlers/__init__.py | 2 ++ synapse/handlers/admin.py | 62 +++++++++++++++++++++++++++++++++++++ synapse/rest/__init__.py | 4 ++- synapse/rest/admin.py | 47 ++++++++++++++++++++++++++++ synapse/storage/__init__.py | 40 ++++++++++++++++++++++-- synapse/storage/registration.py | 26 +++++++++------- synapse/storage/schema/delta/v5.sql | 3 +- synapse/storage/schema/users.sql | 3 +- 9 files changed, 190 insertions(+), 25 deletions(-) create mode 100644 synapse/handlers/admin.py create mode 100644 synapse/rest/admin.py (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 5e3ea5b8c5..8f7982c7fa 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -220,7 +220,8 @@ class Auth(object): # Can optionally look elsewhere in the request (e.g. headers) try: access_token = request.args["access_token"][0] - user = yield self.get_user_by_token(access_token) + user_info = yield self.get_user_by_token(access_token) + user = user_info["user"] ip_addr = self.hs.get_ip_from_request(request) user_agent = request.requestHeaders.getRawHeaders( @@ -229,10 +230,11 @@ class Auth(object): )[0] if user and access_token and ip_addr: self.store.insert_client_ip( - user, - access_token, - ip_addr, - user_agent + user=user, + access_token=access_token, + device_id=user_info["device_id"], + ip=ip_addr, + user_agent=user_agent ) defer.returnValue(user) @@ -246,15 +248,23 @@ class Auth(object): Args: token (str)- The access token to get the user by. Returns: - UserID : User ID object of the user who has that access token. + dict : dict that includes the user, device_id, and whether the + user is a server admin. Raises: AuthError if no user by that token exists or the token is invalid. """ try: - user_id = yield self.store.get_user_by_token(token=token) - if not user_id: + ret = yield self.store.get_user_by_token(token=token) + if not ret: raise StoreError() - defer.returnValue(self.hs.parse_userid(user_id)) + + user_info = { + "admin": bool(ret.get("admin", False)), + "device_id": ret.get("device_id"), + "user": self.hs.parse_userid(ret.get("name")), + } + + defer.returnValue(user_info) except StoreError: raise AuthError(403, "Unrecognised access token.", errcode=Codes.UNKNOWN_TOKEN) diff --git a/synapse/handlers/__init__.py b/synapse/handlers/__init__.py index 5308e2c8e1..d5df3c630b 100644 --- a/synapse/handlers/__init__.py +++ b/synapse/handlers/__init__.py @@ -25,6 +25,7 @@ from .profile import ProfileHandler from .presence import PresenceHandler from .directory import DirectoryHandler from .typing import TypingNotificationHandler +from .admin import AdminHandler class Handlers(object): @@ -49,3 +50,4 @@ class Handlers(object): self.login_handler = LoginHandler(hs) self.directory_handler = DirectoryHandler(hs) self.typing_notification_handler = TypingNotificationHandler(hs) + self.admin_handler = AdminHandler(hs) diff --git a/synapse/handlers/admin.py b/synapse/handlers/admin.py new file mode 100644 index 0000000000..687b343a1d --- /dev/null +++ b/synapse/handlers/admin.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 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. + +from twisted.internet import defer + +from ._base import BaseHandler + +import logging + + +logger = logging.getLogger(__name__) + + +class AdminHandler(BaseHandler): + + def __init__(self, hs): + super(AdminHandler, self).__init__(hs) + + @defer.inlineCallbacks + def get_whois(self, user): + res = yield self.store.get_user_ip_and_agents(user) + + d = {} + for r in res: + device = d.setdefault(r["device_id"], {}) + session = device.setdefault(r["access_token"], []) + session.append({ + "ip": r["ip"], + "user_agent": r["user_agent"], + "last_seen": r["last_seen"], + }) + + ret = { + "user_id": user.to_string(), + "devices": [ + { + "device_id": k, + "sessions": [ + { + # "access_token": x, TODO (erikj) + "connections": y, + } + for x, y in v.items() + ] + } + for k, v in d.items() + ], + } + + defer.returnValue(ret) diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py index 3b9aa59733..e391e5678d 100644 --- a/synapse/rest/__init__.py +++ b/synapse/rest/__init__.py @@ -15,7 +15,8 @@ from . import ( - room, events, register, login, profile, presence, initial_sync, directory, voip + room, events, register, login, profile, presence, initial_sync, directory, + voip, admin, ) @@ -43,3 +44,4 @@ class RestServletFactory(object): initial_sync.register_servlets(hs, client_resource) directory.register_servlets(hs, client_resource) voip.register_servlets(hs, client_resource) + admin.register_servlets(hs, client_resource) diff --git a/synapse/rest/admin.py b/synapse/rest/admin.py new file mode 100644 index 0000000000..97eb1954e0 --- /dev/null +++ b/synapse/rest/admin.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 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. + +from twisted.internet import defer + +from synapse.api.errors import AuthError, SynapseError +from base import RestServlet, client_path_pattern + +import logging + +logger = logging.getLogger(__name__) + + +class WhoisRestServlet(RestServlet): + PATTERN = client_path_pattern("/admin/whois/(?P[^/]*)") + + @defer.inlineCallbacks + def on_GET(self, request, user_id): + target_user = self.hs.parse_userid(user_id) + auth_user = yield self.auth.get_user_by_req(request) + is_admin = yield self.auth.is_server_admin(auth_user) + + if not is_admin and target_user != auth_user: + raise AuthError(403, "You are not a server admin") + + if not target_user.is_mine: + raise SynapseError(400, "Can only whois a local user") + + ret = yield self.handlers.admin_handler.get_whois(auth_user) + + defer.returnValue((200, ret)) + + +def register_servlets(hs, http_server): + WhoisRestServlet(hs).register(http_server) diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 169a80dce4..749347d5a8 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -294,18 +294,54 @@ class DataStore(RoomMemberStore, RoomStore, defer.returnValue(self.min_token) - def insert_client_ip(self, user, access_token, ip, user_agent): + def insert_client_ip(self, user, access_token, device_id, ip, user_agent): return self._simple_insert( "user_ips", { "user": user.to_string(), "access_token": access_token, + "device_id": device_id, "ip": ip, "user_agent": user_agent, - "last_used": int(self._clock.time()), + "last_seen": int(self._clock.time_msec()), } ) + def get_user_ip_and_agents(self, user): + return self._simple_select_list( + table="user_ips", + keyvalues={"user": user.to_string()}, + retcols=[ + "device_id", "access_token", "ip", "user_agent", "last_seen" + ], + ) + + d = {} + for r in res: + device = d.setdefault(r["device_id"], {}) + session = device.setdefault(r["access_token"], []) + session.append({ + "ip": r["ip"], + "user_agent": r["user_agent"], + "last_seen": r["last_seen"], + }) + + defer.returnValue( + [ + { + "device_id": k, + "sessions": [ + { + "access_token": x, + "connections": y, + } + for x, y in v.items() + ] + } + for k, v in d.items() + ] + ) + def snapshot_room(self, room_id, user_id, state_type=None, state_key=None): """Snapshot the room for an update by a user Args: diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py index f32b000cb6..cf43209367 100644 --- a/synapse/storage/registration.py +++ b/synapse/storage/registration.py @@ -88,7 +88,6 @@ class RegistrationStore(SQLBaseStore): query, user_id ) - @defer.inlineCallbacks def get_user_by_token(self, token): """Get a user from the given access token. @@ -99,11 +98,11 @@ class RegistrationStore(SQLBaseStore): Raises: StoreError if no user was found. """ - user_id = yield self.runInteraction(self._query_for_auth, - token) - defer.returnValue(user_id) + return self.runInteraction( + self._query_for_auth, + token + ) - @defer.inlineCallbacks def is_server_admin(self, user): return self._simple_select_one_onecol( table="users", @@ -112,11 +111,16 @@ class RegistrationStore(SQLBaseStore): ) def _query_for_auth(self, txn, token): - txn.execute("SELECT users.name FROM access_tokens LEFT JOIN users" + - " ON users.id = access_tokens.user_id WHERE token = ?", - [token]) - row = txn.fetchone() - if row: - return row[0] + sql = ( + "SELECT users.name, users.admin, access_tokens.device_id " + "FROM users " + "INNER JOIN access_tokens on users.id = access_tokens.user_id " + "WHERE token = ?" + ) + + cursor = txn.execute(sql, (token,)) + rows = self.cursor_to_dict(cursor) + if rows: + return rows[0] raise StoreError(404, "Token not found.") diff --git a/synapse/storage/schema/delta/v5.sql b/synapse/storage/schema/delta/v5.sql index f5a667a250..af9df11aa9 100644 --- a/synapse/storage/schema/delta/v5.sql +++ b/synapse/storage/schema/delta/v5.sql @@ -2,9 +2,10 @@ CREATE TABLE IF NOT EXISTS user_ips ( user TEXT NOT NULL, access_token TEXT NOT NULL, + device_id TEXT, ip TEXT NOT NULL, user_agent TEXT NOT NULL, - last_used INTEGER NOT NULL, + last_seen INTEGER NOT NULL, CONSTRAINT user_ip UNIQUE (user, access_token, ip, user_agent) ON CONFLICT REPLACE ); diff --git a/synapse/storage/schema/users.sql b/synapse/storage/schema/users.sql index d96dd9f075..8244f733bd 100644 --- a/synapse/storage/schema/users.sql +++ b/synapse/storage/schema/users.sql @@ -34,9 +34,10 @@ CREATE TABLE IF NOT EXISTS access_tokens( CREATE TABLE IF NOT EXISTS user_ips ( user TEXT NOT NULL, access_token TEXT NOT NULL, + device_id TEXT, ip TEXT NOT NULL, user_agent TEXT NOT NULL, - last_used INTEGER NOT NULL, + last_seen INTEGER NOT NULL, CONSTRAINT user_ip UNIQUE (user, access_token, ip, user_agent) ON CONFLICT REPLACE ); -- cgit 1.5.1 From 7151615260d50c40341fb168bfe804be0d94ec24 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 29 Sep 2014 15:35:54 +0100 Subject: Update docstring --- synapse/api/auth.py | 2 +- synapse/storage/registration.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 8f7982c7fa..e1b1823cd7 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -246,7 +246,7 @@ class Auth(object): """ Get a registered user's ID. Args: - token (str)- The access token to get the user by. + token (str): The access token to get the user by. Returns: dict : dict that includes the user, device_id, and whether the user is a server admin. diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py index cf43209367..719806f82b 100644 --- a/synapse/storage/registration.py +++ b/synapse/storage/registration.py @@ -94,7 +94,8 @@ class RegistrationStore(SQLBaseStore): Args: token (str): The access token of a user. Returns: - str: The user ID of the user. + dict: Including the name (user_id), device_id and whether they are + an admin. Raises: StoreError if no user was found. """ -- cgit 1.5.1 From 07639c79d9536cf293c550e5849ce6b5dd82189e Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Mon, 13 Oct 2014 16:39:15 +0100 Subject: Respond with more helpful error messages for unsigned requests --- synapse/api/errors.py | 1 + synapse/crypto/keyclient.py | 4 ++-- synapse/crypto/keyring.py | 33 +++++++++++++++++++++++++++++++-- synapse/federation/transport.py | 4 ++-- synapse/storage/_base.py | 11 +++++++---- synapse/storage/keys.py | 2 ++ 6 files changed, 45 insertions(+), 10 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/errors.py b/synapse/api/errors.py index 88175602c4..6d7d499fea 100644 --- a/synapse/api/errors.py +++ b/synapse/api/errors.py @@ -19,6 +19,7 @@ import logging class Codes(object): + UNAUTHORIZED = "M_UNAUTHORIZED" FORBIDDEN = "M_FORBIDDEN" BAD_JSON = "M_BAD_JSON" NOT_JSON = "M_NOT_JSON" diff --git a/synapse/crypto/keyclient.py b/synapse/crypto/keyclient.py index c26f16a038..5949ea0573 100644 --- a/synapse/crypto/keyclient.py +++ b/synapse/crypto/keyclient.py @@ -43,7 +43,7 @@ def fetch_server_key(server_name, ssl_context_factory): return except Exception as e: logger.exception(e) - raise IOError("Cannot get key for " % server_name) + raise IOError("Cannot get key for %s" % server_name) class SynapseKeyClientError(Exception): @@ -93,7 +93,7 @@ class SynapseKeyClientProtocol(HTTPClient): def on_timeout(self): logger.debug("Timeout waiting for response from %s", self.transport.getHost()) - self.on_remote_key.errback(IOError("Timeout waiting for response")) + self.remote_key.errback(IOError("Timeout waiting for response")) self.transport.abortConnection() diff --git a/synapse/crypto/keyring.py b/synapse/crypto/keyring.py index ce19c69bd5..3c85295274 100644 --- a/synapse/crypto/keyring.py +++ b/synapse/crypto/keyring.py @@ -20,6 +20,7 @@ from syutil.crypto.signing_key import ( is_signing_algorithm_supported, decode_verify_key_bytes ) from syutil.base64util import decode_base64, encode_base64 +from synapse.api.errors import SynapseError, Codes from OpenSSL import crypto @@ -38,8 +39,36 @@ class Keyring(object): @defer.inlineCallbacks def verify_json_for_server(self, server_name, json_object): key_ids = signature_ids(json_object, server_name) - verify_key = yield self.get_server_verify_key(server_name, key_ids) - verify_signed_json(json_object, server_name, verify_key) + if not key_ids: + raise SynapseError( + 400, + "No supported algorithms in signing keys", + Codes.UNAUTHORIZED, + ) + try: + verify_key = yield self.get_server_verify_key(server_name, key_ids) + except IOError: + raise SynapseError( + 502, + "Error downloading keys for %s" % (server_name,), + Codes.UNAUTHORIZED, + ) + except: + raise SynapseError( + 401, + "No key for %s with id %s" % (server_name, key_ids), + Codes.UNAUTHORIZED, + ) + try: + verify_signed_json(json_object, server_name, verify_key) + except: + raise SynapseError( + 401, + "Invalid signature for server %s with key %s:%s" % ( + server_name, verify_key.alg, verify_key.version + ), + Codes.UNAUTHORIZED, + ) @defer.inlineCallbacks def get_server_verify_key(self, server_name, key_ids): diff --git a/synapse/federation/transport.py b/synapse/federation/transport.py index 7a4c1f6443..755eee8cf6 100644 --- a/synapse/federation/transport.py +++ b/synapse/federation/transport.py @@ -233,7 +233,7 @@ class TransportLayer(object): return (origin, key, sig) except: raise SynapseError( - 400, "Malformed Authorization Header", Codes.FORBIDDEN + 400, "Malformed Authorization header", Codes.UNAUTHORIZED ) auth_headers = request.requestHeaders.getRawHeaders(b"Authorization") @@ -246,7 +246,7 @@ class TransportLayer(object): if not json_request["signatures"]: raise SynapseError( - 401, "Missing Authorization headers", Codes.FORBIDDEN, + 401, "Missing Authorization headers", Codes.UNAUTHORIZED, ) yield self.keyring.verify_json_for_server(origin, json_request) diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py index 889de2bedc..dba50f1213 100644 --- a/synapse/storage/_base.py +++ b/synapse/storage/_base.py @@ -121,7 +121,7 @@ class SQLBaseStore(object): # "Simple" SQL API methods that operate on a single table with no JOINs, # no complex WHERE clauses, just a dict of values for columns. - def _simple_insert(self, table, values, or_replace=False): + def _simple_insert(self, table, values, or_replace=False, or_ignore=False): """Executes an INSERT query on the named table. Args: @@ -130,13 +130,16 @@ class SQLBaseStore(object): or_replace : bool; if True performs an INSERT OR REPLACE """ return self.runInteraction( - self._simple_insert_txn, table, values, or_replace=or_replace + self._simple_insert_txn, table, values, or_replace=or_replace, + or_ignore=or_ignore, ) @log_function - def _simple_insert_txn(self, txn, table, values, or_replace=False): + def _simple_insert_txn(self, txn, table, values, or_replace=False, + or_ignore=False): sql = "%s INTO %s (%s) VALUES(%s)" % ( - ("INSERT OR REPLACE" if or_replace else "INSERT"), + ("INSERT OR REPLACE" if or_replace else + "INSERT OR IGNORE" if or_ignore else "INSERT"), table, ", ".join(k for k in values), ", ".join("?" for k in values) diff --git a/synapse/storage/keys.py b/synapse/storage/keys.py index 253dc17be2..8189e071a3 100644 --- a/synapse/storage/keys.py +++ b/synapse/storage/keys.py @@ -65,6 +65,7 @@ class KeyStore(SQLBaseStore): "ts_added_ms": time_now_ms, "tls_certificate": buffer(tls_certificate_bytes), }, + or_ignore=True, ) @defer.inlineCallbacks @@ -113,4 +114,5 @@ class KeyStore(SQLBaseStore): "ts_added_ms": time_now_ms, "verify_key": buffer(verify_key.encode()), }, + or_ignore=True, ) -- cgit 1.5.1 From f5cf7ac25b311fda8a2d553f07437b3648c66f6c Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Fri, 17 Oct 2014 17:12:25 +0100 Subject: SPEC-7: Rename 'ts' to 'origin_server_ts' --- synapse/api/events/factory.py | 4 ++-- synapse/federation/pdu_codec.py | 4 ++-- synapse/federation/persistence.py | 2 +- synapse/federation/replication.py | 4 ++-- synapse/federation/units.py | 16 ++++++++-------- synapse/storage/_base.py | 2 +- synapse/storage/pdu.py | 2 +- synapse/storage/schema/pdu.sql | 2 +- synapse/storage/schema/transactions.sql | 4 ++-- synapse/storage/transactions.py | 14 +++++++------- tests/federation/test_federation.py | 14 +++++++------- tests/federation/test_pdu_codec.py | 4 ++-- tests/handlers/test_federation.py | 6 +++--- tests/handlers/test_presence.py | 2 +- tests/handlers/test_typing.py | 2 +- tests/test_state.py | 2 +- 16 files changed, 42 insertions(+), 42 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py index 0d94850cec..74d0ef77f4 100644 --- a/synapse/api/events/factory.py +++ b/synapse/api/events/factory.py @@ -58,8 +58,8 @@ class EventFactory(object): random_string(10), self.hs.hostname ) - if "ts" not in kwargs: - kwargs["ts"] = int(self.clock.time_msec()) + if "origin_server_ts" not in kwargs: + kwargs["origin_server_ts"] = int(self.clock.time_msec()) # The "age" key is a delta timestamp that should be converted into an # absolute timestamp the minute we see it. diff --git a/synapse/federation/pdu_codec.py b/synapse/federation/pdu_codec.py index cef61108dd..e8180d94fd 100644 --- a/synapse/federation/pdu_codec.py +++ b/synapse/federation/pdu_codec.py @@ -96,7 +96,7 @@ class PduCodec(object): if k not in ["event_id", "room_id", "type", "prev_events"] }) - if "ts" not in kwargs: - kwargs["ts"] = int(self.clock.time_msec()) + if "origin_server_ts" not in kwargs: + kwargs["origin_server_ts"] = int(self.clock.time_msec()) return Pdu(**kwargs) diff --git a/synapse/federation/persistence.py b/synapse/federation/persistence.py index de36a80e41..7043fcc504 100644 --- a/synapse/federation/persistence.py +++ b/synapse/federation/persistence.py @@ -157,7 +157,7 @@ class TransactionActions(object): transaction.prev_ids = yield self.store.prep_send_transaction( transaction.transaction_id, transaction.destination, - transaction.ts, + transaction.origin_server_ts, [(p["pdu_id"], p["origin"]) for p in transaction.pdus] ) diff --git a/synapse/federation/replication.py b/synapse/federation/replication.py index 9363ac7300..092411eaf9 100644 --- a/synapse/federation/replication.py +++ b/synapse/federation/replication.py @@ -421,7 +421,7 @@ class ReplicationLayer(object): return Transaction( origin=self.server_name, pdus=pdus, - ts=int(self._clock.time_msec()), + origin_server_ts=int(self._clock.time_msec()), destination=None, ) @@ -589,7 +589,7 @@ class _TransactionQueue(object): logger.debug("TX [%s] Persisting transaction...", destination) transaction = Transaction.create_new( - ts=self._clock.time_msec(), + origin_server_ts=self._clock.time_msec(), transaction_id=str(self._next_txn_id), origin=self.server_name, destination=destination, diff --git a/synapse/federation/units.py b/synapse/federation/units.py index d97aeb698e..dccac2aca7 100644 --- a/synapse/federation/units.py +++ b/synapse/federation/units.py @@ -40,7 +40,7 @@ class Pdu(JsonEncodedObject): { "pdu_id": "78c", - "ts": 1404835423000, + "origin_server_ts": 1404835423000, "origin": "bar", "prev_ids": [ ["23b", "foo"], @@ -55,7 +55,7 @@ class Pdu(JsonEncodedObject): "pdu_id", "context", "origin", - "ts", + "origin_server_ts", "pdu_type", "destinations", "transaction_id", @@ -82,7 +82,7 @@ class Pdu(JsonEncodedObject): "pdu_id", "context", "origin", - "ts", + "origin_server_ts", "pdu_type", "content", ] @@ -186,7 +186,7 @@ class Transaction(JsonEncodedObject): "transaction_id", "origin", "destination", - "ts", + "origin_server_ts", "previous_ids", "pdus", "edus", @@ -203,7 +203,7 @@ class Transaction(JsonEncodedObject): "transaction_id", "origin", "destination", - "ts", + "origin_server_ts", "pdus", ] @@ -225,10 +225,10 @@ class Transaction(JsonEncodedObject): @staticmethod def create_new(pdus, **kwargs): """ Used to create a new transaction. Will auto fill out - transaction_id and ts keys. + transaction_id and origin_server_ts keys. """ - if "ts" not in kwargs: - raise KeyError("Require 'ts' to construct a Transaction") + if "origin_server_ts" not in kwargs: + raise KeyError("Require 'origin_server_ts' to construct a Transaction") if "transaction_id" not in kwargs: raise KeyError( "Require 'transaction_id' to construct a Transaction" diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py index dba50f1213..30c5103cdd 100644 --- a/synapse/storage/_base.py +++ b/synapse/storage/_base.py @@ -361,7 +361,7 @@ class SQLBaseStore(object): if "age_ts" not in d: # For compatibility - d["age_ts"] = d["ts"] if "ts" in d else 0 + d["age_ts"] = d["origin_server_ts"] if "origin_server_ts" in d else 0 return self.event_factory.create_event( etype=d["type"], diff --git a/synapse/storage/pdu.py b/synapse/storage/pdu.py index d70467dcd6..61ea979b8a 100644 --- a/synapse/storage/pdu.py +++ b/synapse/storage/pdu.py @@ -789,7 +789,7 @@ class PdusTable(Table): "origin", "context", "pdu_type", - "ts", + "origin_server_ts", "depth", "is_state", "content_json", diff --git a/synapse/storage/schema/pdu.sql b/synapse/storage/schema/pdu.sql index 16e111a56c..5cc8669912 100644 --- a/synapse/storage/schema/pdu.sql +++ b/synapse/storage/schema/pdu.sql @@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS pdus( origin TEXT, context TEXT, pdu_type TEXT, - ts INTEGER, + origin_server_ts INTEGER, depth INTEGER DEFAULT 0 NOT NULL, is_state BOOL, content_json TEXT, diff --git a/synapse/storage/schema/transactions.sql b/synapse/storage/schema/transactions.sql index 88e3e4e04d..5f8d01327a 100644 --- a/synapse/storage/schema/transactions.sql +++ b/synapse/storage/schema/transactions.sql @@ -16,7 +16,7 @@ CREATE TABLE IF NOT EXISTS received_transactions( transaction_id TEXT, origin TEXT, - ts INTEGER, + origin_server_ts INTEGER, response_code INTEGER, response_json TEXT, has_been_referenced BOOL default 0, -- Whether thishas been referenced by a prev_tx @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS sent_transactions( destination TEXT, response_code INTEGER DEFAULT 0, response_json TEXT, - ts INTEGER + origin_server_ts INTEGER ); CREATE INDEX IF NOT EXISTS sent_transaction_dest ON sent_transactions(destination); diff --git a/synapse/storage/transactions.py b/synapse/storage/transactions.py index ab4599b468..a9fa959d49 100644 --- a/synapse/storage/transactions.py +++ b/synapse/storage/transactions.py @@ -87,7 +87,7 @@ class TransactionStore(SQLBaseStore): txn.execute(query, (code, response_json, transaction_id, origin)) - def prep_send_transaction(self, transaction_id, destination, ts, pdu_list): + def prep_send_transaction(self, transaction_id, destination, origin_server_ts, pdu_list): """Persists an outgoing transaction and calculates the values for the previous transaction id list. @@ -97,7 +97,7 @@ class TransactionStore(SQLBaseStore): Args: transaction_id (str) destination (str) - ts (int) + origin_server_ts (int) pdu_list (list) Returns: @@ -106,10 +106,10 @@ class TransactionStore(SQLBaseStore): return self.runInteraction( self._prep_send_transaction, - transaction_id, destination, ts, pdu_list + transaction_id, destination, origin_server_ts, pdu_list ) - def _prep_send_transaction(self, txn, transaction_id, destination, ts, + def _prep_send_transaction(self, txn, transaction_id, destination, origin_server_ts, pdu_list): # First we find out what the prev_txs should be. @@ -131,7 +131,7 @@ class TransactionStore(SQLBaseStore): None, transaction_id=transaction_id, destination=destination, - ts=ts, + origin_server_ts=origin_server_ts, response_code=0, response_json=None )) @@ -251,7 +251,7 @@ class ReceivedTransactionsTable(Table): fields = [ "transaction_id", "origin", - "ts", + "origin_server_ts", "response_code", "response_json", "has_been_referenced", @@ -267,7 +267,7 @@ class SentTransactions(Table): "id", "transaction_id", "destination", - "ts", + "origin_server_ts", "response_code", "response_json", ] diff --git a/tests/federation/test_federation.py b/tests/federation/test_federation.py index d86ce83b28..8b1202f6e4 100644 --- a/tests/federation/test_federation.py +++ b/tests/federation/test_federation.py @@ -99,7 +99,7 @@ class FederationTestCase(unittest.TestCase): origin="red", context="my-context", pdu_type="m.topic", - ts=123456789000, + origin_server_ts=123456789000, depth=1, is_state=True, content_json='{"topic":"The topic"}', @@ -134,7 +134,7 @@ class FederationTestCase(unittest.TestCase): origin="red", context="my-context", pdu_type="m.text", - ts=123456789001, + origin_server_ts=123456789001, depth=1, content_json='{"text":"Here is the message"}', ) @@ -158,7 +158,7 @@ class FederationTestCase(unittest.TestCase): origin="red", destinations=["remote"], context="my-context", - ts=123456789002, + origin_server_ts=123456789002, pdu_type="m.test", content={"testing": "content here"}, depth=1, @@ -170,14 +170,14 @@ class FederationTestCase(unittest.TestCase): "remote", path="/_matrix/federation/v1/send/1000000/", data={ - "ts": 1000000, + "origin_server_ts": 1000000, "origin": "test", "pdus": [ { "origin": "red", "pdu_id": "abc123def456", "prev_pdus": [], - "ts": 123456789002, + "origin_server_ts": 123456789002, "context": "my-context", "pdu_type": "m.test", "is_state": False, @@ -207,7 +207,7 @@ class FederationTestCase(unittest.TestCase): path="/_matrix/federation/v1/send/1000000/", data={ "origin": "test", - "ts": 1000000, + "origin_server_ts": 1000000, "pdus": [], "edus": [ { @@ -234,7 +234,7 @@ class FederationTestCase(unittest.TestCase): "/_matrix/federation/v1/send/1001000/", """{ "origin": "remote", - "ts": 1001000, + "origin_server_ts": 1001000, "pdus": [], "edus": [ { diff --git a/tests/federation/test_pdu_codec.py b/tests/federation/test_pdu_codec.py index 344e1baf60..0754ef92e8 100644 --- a/tests/federation/test_pdu_codec.py +++ b/tests/federation/test_pdu_codec.py @@ -68,7 +68,7 @@ class PduCodecTestCase(unittest.TestCase): context="rooooom", pdu_type="m.room.message", origin="bar.com", - ts=12345, + origin_server_ts=12345, depth=5, prev_pdus=[("alice", "bob.com")], is_state=False, @@ -123,7 +123,7 @@ class PduCodecTestCase(unittest.TestCase): context="rooooom", pdu_type="m.room.topic", origin="bar.com", - ts=12345, + origin_server_ts=12345, depth=5, prev_pdus=[("alice", "bob.com")], is_state=True, diff --git a/tests/handlers/test_federation.py b/tests/handlers/test_federation.py index 35c3a4df7b..219b2c4c5e 100644 --- a/tests/handlers/test_federation.py +++ b/tests/handlers/test_federation.py @@ -68,7 +68,7 @@ class FederationTestCase(unittest.TestCase): pdu_type=MessageEvent.TYPE, context="foo", content={"msgtype": u"fooo"}, - ts=0, + origin_server_ts=0, pdu_id="a", origin="b", ) @@ -95,7 +95,7 @@ class FederationTestCase(unittest.TestCase): target_host=self.hostname, context=room_id, content={}, - ts=0, + origin_server_ts=0, pdu_id="a", origin="b", ) @@ -127,7 +127,7 @@ class FederationTestCase(unittest.TestCase): state_key="@red:not%s" % self.hostname, context=room_id, content={}, - ts=0, + origin_server_ts=0, pdu_id="a", origin="b", ) diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py index 84985a8066..1850deacf5 100644 --- a/tests/handlers/test_presence.py +++ b/tests/handlers/test_presence.py @@ -39,7 +39,7 @@ ONLINE = PresenceState.ONLINE def _expect_edu(destination, edu_type, content, origin="test"): return { "origin": origin, - "ts": 1000000, + "origin_server_ts": 1000000, "pdus": [], "edus": [ { diff --git a/tests/handlers/test_typing.py b/tests/handlers/test_typing.py index b685373deb..f1d3b27f74 100644 --- a/tests/handlers/test_typing.py +++ b/tests/handlers/test_typing.py @@ -29,7 +29,7 @@ from synapse.handlers.typing import TypingNotificationHandler def _expect_edu(destination, edu_type, content, origin="test"): return { "origin": origin, - "ts": 1000000, + "origin_server_ts": 1000000, "pdus": [], "edus": [ { diff --git a/tests/test_state.py b/tests/test_state.py index b1624f0b25..4b1feaf410 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -599,7 +599,7 @@ def new_fake_pdu(pdu_id, context, pdu_type, state_key, prev_state_id, prev_state_id=prev_state_id, origin="example.com", context="context", - ts=1405353060021, + origin_server_ts=1405353060021, depth=depth, content_json="{}", unrecognized_keys="{}", -- cgit 1.5.1