Compare commits

...

37 Commits

Author SHA1 Message Date
Will Hunt
3bab937ae3 Add ability for ASes to login 2020-09-15 17:24:25 +01:00
Will Hunt
023e21f9ef Merge remote-tracking branch 'origin/develop' into hs/shared-with-api 2020-09-01 14:03:38 +01:00
Will Hunt
837cf31838 Update synapse/storage/databases/main/user_directory.py
Co-authored-by: Tulir Asokan <tulir@maunium.net>
2020-08-31 16:11:18 +01:00
Patrick Cloke
a444ab01d9 Fix style by adding a blank line at EOF. 2020-08-31 07:04:57 -04:00
Will Hunt
783f8f5481 a line 2020-08-29 10:02:57 +01:00
Will Hunt
1fa8b84cc1 Add upgrade node 2020-08-29 10:01:36 +01:00
Will Hunt
7554836b23 Seperate unstable endpoint from v2 2020-08-29 10:01:22 +01:00
Will Hunt
e86f4062ce Feedback 2020-08-29 10:01:15 +01:00
Will Hunt
49f14c0af3 Merge remote-tracking branch 'origin/develop' into hs/shared-with-api 2020-08-28 16:19:25 +01:00
Will Hunt
8a4c5abe72 Add tests 2020-08-28 16:16:00 +01:00
Will Hunt
e785cc641e Switch to using runInteraction 2020-08-28 15:27:20 +01:00
Will Hunt
e135cd515e Handle the easy feedback 2020-08-28 15:24:39 +01:00
Will Hunt
c120fd3740 trailing whitespace 2020-08-19 09:34:31 +01:00
Will Hunt
130d2f3e92 a comma 2020-08-18 19:39:59 +01:00
Will Hunt
7905978cf3 Implement get_shared_rooms_for_users 2020-08-18 19:34:24 +01:00
Will Hunt
cc4fb1695d Merge remote-tracking branch 'origin/develop' into hs/shared-with-api 2020-08-18 18:54:18 +01:00
Will Hunt
42a09b999f Update changelog.d/7785.feature
Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
2020-08-18 18:51:37 +01:00
Will Hunt
ce49d19bf6 Use FORBIDDEN 2020-08-18 18:33:33 +01:00
Will Hunt
a281322648 Merge remote-tracking branch 'origin/develop' into hs/shared-with-api 2020-08-02 23:36:03 +01:00
Will Hunt
ebfe6c4e1b Merge branch 'hs/shared-with-api' of github.com:matrix-org/synapse into hs/shared-with-api 2020-08-02 23:35:23 +01:00
Will Hunt
72e90a2196 import Codes 2020-08-02 23:35:15 +01:00
Will Hunt
9cd5116be0 import Codes 2020-07-31 14:43:01 +01:00
Will Hunt
256e0172ac Fix to match spec 2020-07-31 14:41:43 +01:00
Half-Shot
dde46cf6af Vendor prefix unstable API 2020-07-10 11:59:54 +01:00
Will Hunt
c399292142 Merge remote-tracking branch 'origin/develop' into hs/shared-with-api 2020-07-05 16:44:12 +01:00
Will Hunt
676edeaeac fix linting again for isort 2020-07-05 14:35:53 +01:00
Will Hunt
c143e6294e Merge branch 'hs/isort-5' into hs/shared-with-api 2020-07-05 14:33:53 +01:00
Will Hunt
09e09a3cf9 Update tox.ini file with correct invocation 2020-07-05 14:33:00 +01:00
Will Hunt
6bd34c7e51 Add changelog 2020-07-05 14:30:08 +01:00
Will Hunt
3fa92f545f Update imports to make isort happy 2020-07-05 14:27:08 +01:00
Will Hunt
90a5975c7b Remove options from isort that aren't part of 5.x
`-y` and `-rc` are now default behaviour and no longer exist.

`dont-skip` is no longer required

https://timothycrosley.github.io/isort/CHANGELOG/#500-penny-july-4-2020
2020-07-05 14:25:44 +01:00
Will Hunt
208d3cfa8a Add unstable_features key 2020-07-05 14:17:13 +01:00
Will Hunt
9c1e934d94 linting 2020-07-05 13:52:50 +01:00
Will Hunt
f43bf0cc6b Wrap response in {"rooms": } 2020-07-05 13:46:53 +01:00
Will Hunt
984c52b4e6 Add . 2020-07-05 13:01:00 +01:00
Will Hunt
0f6800f45b Add changelog 2020-07-05 13:00:33 +01:00
Will Hunt
26634cb0d9 Add shared_rooms api 2020-07-05 12:57:55 +01:00
10 changed files with 288 additions and 3 deletions

View File

@@ -1,3 +1,16 @@
Upgrading to v1.20.0
====================
Shared rooms endpoint (MSC2666)
------------------------------
This release contains a new unstable endpoint `/_matrix/client/uk.half-shot.msc2666/user/shared_rooms/.*`
for fetching rooms one user has in common with another. This feature requires the
`update_user_directory` config flag to be `True`. If you are you are using a `synapse.app.user_dir`
worker, requests to this endpoint must be handled by that worker.
See `docs/workers.md <docs/workers.md>`_ for more details.
Upgrading Synapse Upgrading Synapse
================= =================

1
changelog.d/7785.feature Normal file
View File

@@ -0,0 +1 @@
Add an endpoint to query your shared rooms with another user as an implementation of [MSC2666](https://github.com/matrix-org/matrix-doc/pull/2666).

View File

@@ -380,6 +380,7 @@ Handles searches in the user directory. It can handle REST endpoints matching
the following regular expressions: the following regular expressions:
^/_matrix/client/(api/v1|r0|unstable)/user_directory/search$ ^/_matrix/client/(api/v1|r0|unstable)/user_directory/search$
^/_matrix/client/uk.half-shot.msc2666/user/shared_rooms/.*$
When using this worker you must also set `update_user_directory: False` in the When using this worker you must also set `update_user_directory: False` in the
shared configuration file to stop the main synapse running background shared configuration file to stop the main synapse running background

View File

@@ -57,6 +57,7 @@ class JoinRules(object):
class LoginType(object): class LoginType(object):
PASSWORD = "m.login.password" PASSWORD = "m.login.password"
APPSERVICE = "uk.half-shot.unstable.login.appservice"
EMAIL_IDENTITY = "m.login.email.identity" EMAIL_IDENTITY = "m.login.email.identity"
MSISDN = "m.login.msisdn" MSISDN = "m.login.msisdn"
RECAPTCHA = "m.login.recaptcha" RECAPTCHA = "m.login.recaptcha"

View File

@@ -50,6 +50,7 @@ from synapse.rest.client.v2_alpha import (
room_keys, room_keys,
room_upgrade_rest_servlet, room_upgrade_rest_servlet,
sendtodevice, sendtodevice,
shared_rooms,
sync, sync,
tags, tags,
thirdparty, thirdparty,
@@ -125,3 +126,6 @@ class ClientRestResource(JsonResource):
synapse.rest.admin.register_servlets_for_client_rest_resource( synapse.rest.admin.register_servlets_for_client_rest_resource(
hs, client_resource hs, client_resource
) )
# unstable
shared_rooms.register_servlets(hs, client_resource)

View File

@@ -16,8 +16,10 @@
import logging import logging
from typing import Awaitable, Callable, Dict, Optional from typing import Awaitable, Callable, Dict, Optional
from synapse.api.constants import LoginType
from synapse.api.errors import Codes, LoginError, SynapseError from synapse.api.errors import Codes, LoginError, SynapseError
from synapse.api.ratelimiting import Ratelimiter from synapse.api.ratelimiting import Ratelimiter
from synapse.appservice import ApplicationService
from synapse.handlers.auth import ( from synapse.handlers.auth import (
convert_client_dict_legacy_fields_to_identifier, convert_client_dict_legacy_fields_to_identifier,
login_id_phone_to_thirdparty, login_id_phone_to_thirdparty,
@@ -61,6 +63,8 @@ class LoginRestServlet(RestServlet):
self.cas_enabled = hs.config.cas_enabled self.cas_enabled = hs.config.cas_enabled
self.oidc_enabled = hs.config.oidc_enabled self.oidc_enabled = hs.config.oidc_enabled
self.auth = hs.get_auth()
self.auth_handler = self.hs.get_auth_handler() self.auth_handler = self.hs.get_auth_handler()
self.registration_handler = hs.get_registration_handler() self.registration_handler = hs.get_registration_handler()
self.handlers = hs.get_handlers() self.handlers = hs.get_handlers()
@@ -116,6 +120,11 @@ class LoginRestServlet(RestServlet):
self._address_ratelimiter.ratelimit(request.getClientIP()) self._address_ratelimiter.ratelimit(request.getClientIP())
login_submission = parse_json_object_from_request(request) login_submission = parse_json_object_from_request(request)
appservice = None
if self.auth.has_access_token(request):
appservice = self.auth.get_appservice_by_req(request)
try: try:
if self.jwt_enabled and ( if self.jwt_enabled and (
login_submission["type"] == LoginRestServlet.JWT_TYPE login_submission["type"] == LoginRestServlet.JWT_TYPE
@@ -125,7 +134,7 @@ class LoginRestServlet(RestServlet):
elif login_submission["type"] == LoginRestServlet.TOKEN_TYPE: elif login_submission["type"] == LoginRestServlet.TOKEN_TYPE:
result = await self._do_token_login(login_submission) result = await self._do_token_login(login_submission)
else: else:
result = await self._do_other_login(login_submission) result = await self._do_other_login(login_submission, appservice)
except KeyError: except KeyError:
raise SynapseError(400, "Missing JSON keys.") raise SynapseError(400, "Missing JSON keys.")
@@ -134,7 +143,9 @@ class LoginRestServlet(RestServlet):
result["well_known"] = well_known_data result["well_known"] = well_known_data
return 200, result return 200, result
async def _do_other_login(self, login_submission: JsonDict) -> Dict[str, str]: async def _do_other_login(
self, login_submission: JsonDict, appservice: ApplicationService
) -> Dict[str, str]:
"""Handle non-token/saml/jwt logins """Handle non-token/saml/jwt logins
Args: Args:
@@ -229,6 +240,10 @@ class LoginRestServlet(RestServlet):
else: else:
qualified_user_id = UserID(identifier["user"], self.hs.hostname).to_string() qualified_user_id = UserID(identifier["user"], self.hs.hostname).to_string()
if login_submission["type"] == LoginType.APPSERVICE and appservice is not None:
result = await self._complete_login(qualified_user_id, login_submission)
return result
# Check if we've hit the failed ratelimit (but don't update it) # Check if we've hit the failed ratelimit (but don't update it)
self._failed_attempts_ratelimiter.ratelimit( self._failed_attempts_ratelimiter.ratelimit(
qualified_user_id.lower(), update=False qualified_user_id.lower(), update=False

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright 2020 Half-Shot
#
# 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.
import logging
from synapse.api.errors import Codes, SynapseError
from synapse.http.servlet import RestServlet
from synapse.types import UserID
from ._base import client_patterns
logger = logging.getLogger(__name__)
class UserSharedRoomsServlet(RestServlet):
"""
GET /uk.half-shot.msc2666/user/shared_rooms/{user_id} HTTP/1.1
"""
PATTERNS = client_patterns(
"/uk.half-shot.msc2666/user/shared_rooms/(?P<user_id>[^/]*)",
releases=(), # This is an unstable feature
)
def __init__(self, hs):
super(UserSharedRoomsServlet, self).__init__()
self.auth = hs.get_auth()
self.store = hs.get_datastore()
self.user_directory_active = hs.config.update_user_directory
async def on_GET(self, request, user_id):
if not self.user_directory_active:
raise SynapseError(
code=400,
msg="The user directory is disabled on this server. Cannot determine shared rooms.",
errcode=Codes.FORBIDDEN,
)
UserID.from_string(user_id)
requester = await self.auth.get_user_by_req(request)
if user_id == requester.user.to_string():
raise SynapseError(
code=400,
msg="You cannot request a list of shared rooms with yourself",
errcode=Codes.FORBIDDEN,
)
rooms = await self.store.get_shared_rooms_for_users(
requester.user.to_string(), user_id
)
return 200, {"joined": list(rooms)}
def register_servlets(hs, http_server):
UserSharedRoomsServlet(hs).register(http_server)

View File

@@ -60,6 +60,8 @@ class VersionsRestServlet(RestServlet):
"org.matrix.e2e_cross_signing": True, "org.matrix.e2e_cross_signing": True,
# Implements additional endpoints as described in MSC2432 # Implements additional endpoints as described in MSC2432
"org.matrix.msc2432": True, "org.matrix.msc2432": True,
# Implements additional endpoints as described in MSC2666
"uk.half-shot.msc2666": True,
}, },
}, },
) )

View File

@@ -15,7 +15,7 @@
import logging import logging
import re import re
from typing import Any, Dict, Optional from typing import Any, Dict, Optional, Set
from synapse.api.constants import EventTypes, JoinRules from synapse.api.constants import EventTypes, JoinRules
from synapse.storage.database import DatabasePool from synapse.storage.database import DatabasePool
@@ -664,6 +664,48 @@ class UserDirectoryStore(UserDirectoryBackgroundUpdateStore):
users.update(rows) users.update(rows)
return list(users) return list(users)
@cached()
async def get_shared_rooms_for_users(
self, user_id: str, other_user_id: str
) -> Set[str]:
"""
Returns the rooms that a local user shares with another local or remote user.
Args:
user_id: The MXID of a local user
other_user_id: The MXID of the other user
Returns:
A set of room ID's that the users share.
"""
def _get_shared_rooms_for_users_txn(txn):
txn.execute(
"""
SELECT p1.room_id
FROM users_in_public_rooms as p1
INNER JOIN users_in_public_rooms as p2
ON p1.room_id = p2.room_id
AND p1.user_id = ?
AND p2.user_id = ?
UNION
SELECT room_id
FROM users_who_share_private_rooms
WHERE
user_id = ?
AND other_user_id = ?;
""",
(user_id, other_user_id, user_id, other_user_id),
)
rows = self.db_pool.cursor_to_dict(txn)
return rows
rows = await self.db_pool.runInteraction(
"get_shared_rooms_for_users", _get_shared_rooms_for_users_txn
)
return {row["room_id"] for row in rows}
async def get_user_directory_stream_pos(self) -> int: async def get_user_directory_stream_pos(self) -> int:
return await self.db_pool.simple_select_one_onecol( return await self.db_pool.simple_select_one_onecol(
table="user_directory_stream_pos", table="user_directory_stream_pos",

View File

@@ -0,0 +1,138 @@
# -*- coding: utf-8 -*-
# Copyright 2020 Half-Shot
#
# 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.
import synapse.rest.admin
from synapse.rest.client.v1 import login, room
from synapse.rest.client.v2_alpha import shared_rooms
from tests import unittest
class UserSharedRoomsTest(unittest.HomeserverTestCase):
"""
Tests the UserSharedRoomsServlet.
"""
servlets = [
login.register_servlets,
synapse.rest.admin.register_servlets_for_client_rest_resource,
room.register_servlets,
shared_rooms.register_servlets,
]
def make_homeserver(self, reactor, clock):
config = self.default_config()
config["update_user_directory"] = True
return self.setup_test_homeserver(config=config)
def prepare(self, reactor, clock, hs):
self.store = hs.get_datastore()
self.handler = hs.get_user_directory_handler()
def _get_shared_rooms(self, token, other_user):
request, channel = self.make_request(
"GET",
"/_matrix/client/unstable/uk.half-shot.msc2666/user/shared_rooms/%s"
% other_user,
access_token=token,
)
self.render(request)
return request, channel
def test_shared_room_list_public(self):
"""
A room should show up in the shared list of rooms between two users
if it is public.
"""
u1 = self.register_user("user1", "pass")
u1_token = self.login(u1, "pass")
u2 = self.register_user("user2", "pass")
u2_token = self.login(u2, "pass")
room = self.helper.create_room_as(u1, is_public=True, tok=u1_token)
self.helper.invite(room, src=u1, targ=u2, tok=u1_token)
self.helper.join(room, user=u2, tok=u2_token)
request, channel = self._get_shared_rooms(u1_token, u2)
self.assertEquals(200, channel.code, channel.result)
self.assertEquals(len(channel.json_body["joined"]), 1)
self.assertEquals(channel.json_body["joined"][0], room)
def test_shared_room_list_private(self):
"""
A room should show up in the shared list of rooms between two users
if it is private.
"""
u1 = self.register_user("user1", "pass")
u1_token = self.login(u1, "pass")
u2 = self.register_user("user2", "pass")
u2_token = self.login(u2, "pass")
room = self.helper.create_room_as(u1, is_public=False, tok=u1_token)
self.helper.invite(room, src=u1, targ=u2, tok=u1_token)
self.helper.join(room, user=u2, tok=u2_token)
request, channel = self._get_shared_rooms(u1_token, u2)
self.assertEquals(200, channel.code, channel.result)
self.assertEquals(len(channel.json_body["joined"]), 1)
self.assertEquals(channel.json_body["joined"][0], room)
def test_shared_room_list_mixed(self):
"""
The shared room list between two users should contain both public and private
rooms.
"""
u1 = self.register_user("user1", "pass")
u1_token = self.login(u1, "pass")
u2 = self.register_user("user2", "pass")
u2_token = self.login(u2, "pass")
room_public = self.helper.create_room_as(u1, is_public=True, tok=u1_token)
room_private = self.helper.create_room_as(u2, is_public=False, tok=u2_token)
self.helper.invite(room_public, src=u1, targ=u2, tok=u1_token)
self.helper.invite(room_private, src=u2, targ=u1, tok=u2_token)
self.helper.join(room_public, user=u2, tok=u2_token)
self.helper.join(room_private, user=u1, tok=u1_token)
request, channel = self._get_shared_rooms(u1_token, u2)
self.assertEquals(200, channel.code, channel.result)
self.assertEquals(len(channel.json_body["joined"]), 2)
self.assertTrue(room_public in channel.json_body["joined"])
self.assertTrue(room_private in channel.json_body["joined"])
def test_shared_room_list_after_leave(self):
"""
A room should no longer be considered shared if the other
user has left it.
"""
u1 = self.register_user("user1", "pass")
u1_token = self.login(u1, "pass")
u2 = self.register_user("user2", "pass")
u2_token = self.login(u2, "pass")
room = self.helper.create_room_as(u1, is_public=True, tok=u1_token)
self.helper.invite(room, src=u1, targ=u2, tok=u1_token)
self.helper.join(room, user=u2, tok=u2_token)
# Assert user directory is not empty
request, channel = self._get_shared_rooms(u1_token, u2)
self.assertEquals(200, channel.code, channel.result)
self.assertEquals(len(channel.json_body["joined"]), 1)
self.assertEquals(channel.json_body["joined"][0], room)
self.helper.leave(room, user=u1, tok=u1_token)
request, channel = self._get_shared_rooms(u2_token, u1)
self.assertEquals(200, channel.code, channel.result)
self.assertEquals(len(channel.json_body["joined"]), 0)