diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 9bfd25c86e70bdc3e51f4de4d0bd414630944a42..6f8146ec3a0aeef76ca17c1d9932dfe5bd6ee066 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 4935e323d907b8212afd148a8eecdb0d11294291..804117ee09920cf822b0baccf80d87c6f08d0fe5 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 cdea49e6abeeb4e6ceb631ab1583ede7c457b5ed..e5b048ede0f5ce987832793612321724f6aabdea 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 15919eb58021b3b894402e566f2b173259ccb612..d53c090a91b4270cfc3b2d2027a63af0d1a1e9c3 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 0000000000000000000000000000000000000000..380eec6f354ccebb97d7aa67e474d96d372eb102 --- /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 2519702971bb9ee852d8e548845ee9d5815eb0a6..89eab8babe46fbc1f61ba1efcce1e5457f2ed72c 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 ea3478ac5d85e276697bfbf80327f1d79011641b..1b3e6759c2f008ad1d92e06c7030e50246a90994 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 e6e51f6dd09a64ef322a4b44de8b5329327b40e1..b0f48e7fd8bf19215a2d9ebca760c50bb0767e81 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 bb8e9964dd1e1074f5bce5b208b3e8448c81bc09..ae9762114760b7ecc419fdcf8277b3af97f1dbba 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(