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

Add /user/{user_id}/shared_rooms/ api #7785

Merged
merged 40 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
26634cb
Add shared_rooms api
Half-Shot Jul 5, 2020
0f6800f
Add changelog
Half-Shot Jul 5, 2020
984c52b
Add .
Half-Shot Jul 5, 2020
f43bf0c
Wrap response in {"rooms": }
Half-Shot Jul 5, 2020
9c1e934
linting
Half-Shot Jul 5, 2020
208d3cf
Add unstable_features key
Half-Shot Jul 5, 2020
90a5975
Remove options from isort that aren't part of 5.x
Half-Shot Jul 5, 2020
3fa92f5
Update imports to make isort happy
Half-Shot Jul 5, 2020
6bd34c7
Add changelog
Half-Shot Jul 5, 2020
09e09a3
Update tox.ini file with correct invocation
Half-Shot Jul 5, 2020
c143e62
Merge branch 'hs/isort-5' into hs/shared-with-api
Half-Shot Jul 5, 2020
676edea
fix linting again for isort
Half-Shot Jul 5, 2020
c399292
Merge remote-tracking branch 'origin/develop' into hs/shared-with-api
Half-Shot Jul 5, 2020
dde46cf
Vendor prefix unstable API
Half-Shot Jul 10, 2020
256e017
Fix to match spec
Half-Shot Jul 31, 2020
9cd5116
import Codes
Half-Shot Jul 31, 2020
72e90a2
import Codes
Half-Shot Jul 31, 2020
ebfe6c4
Merge branch 'hs/shared-with-api' of github.com:matrix-org/synapse in…
Half-Shot Aug 2, 2020
a281322
Merge remote-tracking branch 'origin/develop' into hs/shared-with-api
Half-Shot Aug 2, 2020
ce49d19
Use FORBIDDEN
Half-Shot Aug 18, 2020
42a09b9
Update changelog.d/7785.feature
Half-Shot Aug 18, 2020
cc4fb16
Merge remote-tracking branch 'origin/develop' into hs/shared-with-api
Half-Shot Aug 18, 2020
7905978
Implement get_shared_rooms_for_users
Half-Shot Aug 18, 2020
130d2f3
a comma
Half-Shot Aug 18, 2020
c120fd3
trailing whitespace
Half-Shot Aug 19, 2020
e135cd5
Handle the easy feedback
Half-Shot Aug 28, 2020
e785cc6
Switch to using runInteraction
Half-Shot Aug 28, 2020
8a4c5ab
Add tests
Half-Shot Aug 28, 2020
49f14c0
Merge remote-tracking branch 'origin/develop' into hs/shared-with-api
Half-Shot Aug 28, 2020
e86f406
Feedback
Half-Shot Aug 29, 2020
7554836
Seperate unstable endpoint from v2
Half-Shot Aug 29, 2020
1fa8b84
Add upgrade node
Half-Shot Aug 29, 2020
783f8f5
a line
Half-Shot Aug 29, 2020
a444ab0
Fix style by adding a blank line at EOF.
clokep Aug 31, 2020
837cf31
Update synapse/storage/databases/main/user_directory.py
Half-Shot Aug 31, 2020
023e21f
Merge remote-tracking branch 'origin/develop' into hs/shared-with-api
Half-Shot Sep 1, 2020
679810a
Update synapse/storage/databases/main/user_directory.py
Half-Shot Sep 2, 2020
0578bb9
Update UPGRADE.rst
Half-Shot Sep 2, 2020
5acf573
Merge branch 'develop' into hs/shared-with-api
Half-Shot Sep 2, 2020
078b3e3
Fix UPGRADE/CHANGELOG unstable paths
Half-Shot Sep 2, 2020
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
13 changes: 13 additions & 0 deletions UPGRADE.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
Upgrading to v1.20.0
====================

Shared rooms endpoint (MSC2666)
------------------------------
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved

This release contains a new unstable endpoint `/_matrix/client/uk.half-shot.msc2666/user/shared_rooms/.*`
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
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
=================

Expand Down
1 change: 1 addition & 0 deletions changelog.d/7785.feature
Original file line number Diff line number Diff line change
@@ -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).
1 change: 1 addition & 0 deletions docs/workers.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ Handles searches in the user directory. It can handle REST endpoints matching
the following regular expressions:

^/_matrix/client/(api/v1|r0|unstable)/user_directory/search$
^/_matrix/client/uk.half-shot.msc2666/user/shared_rooms/.*$
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved

When using this worker you must also set `update_user_directory: False` in the
shared configuration file to stop the main synapse running background
Expand Down
4 changes: 4 additions & 0 deletions synapse/rest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
room_keys,
room_upgrade_rest_servlet,
sendtodevice,
shared_rooms,
sync,
tags,
thirdparty,
Expand Down Expand Up @@ -125,3 +126,6 @@ def register_servlets(client_resource, hs):
synapse.rest.admin.register_servlets_for_client_rest_resource(
hs, client_resource
)

# unstable
shared_rooms.register_servlets(hs, client_resource)
clokep marked this conversation as resolved.
Show resolved Hide resolved
68 changes: 68 additions & 0 deletions synapse/rest/client/v2_alpha/shared_rooms.py
Original file line number Diff line number Diff line change
@@ -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>[^/]*)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just noticed this and your MSC don't match up. You'll want to put /unstable/ before /uk.half-shot.msc2666. This matches the unstable endpoint definitions laid out in MSC2324.

You can do so by adding unstable=True to this function call (and at that point the # This is an unstable feature comment is likely redundant).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't need to. unstable is True by default. I don't think I need to stick /unstable/ before the path either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! My bad. Just a couple doc updates then and we're good to go!

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,
)
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
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)
2 changes: 2 additions & 0 deletions synapse/rest/client/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def on_GET(self, request):
"org.matrix.e2e_cross_signing": True,
# Implements additional endpoints as described in MSC2432
"org.matrix.msc2432": True,
# Implements additional endpoints as described in MSC2666
"uk.half-shot.msc2666": True,
},
},
)
Expand Down
44 changes: 43 additions & 1 deletion synapse/storage/databases/main/user_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import logging
import re
from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Set

from synapse.api.constants import EventTypes, JoinRules
from synapse.storage.database import DatabasePool
Expand Down Expand Up @@ -664,6 +664,48 @@ async def get_user_dir_rooms_user_is_in(self, user_id):
users.update(rows)
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

Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
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 = ?;
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
""",
(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 set(row["room_id"] for row in rows)
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved

async def get_user_directory_stream_pos(self) -> int:
return await self.db_pool.simple_select_one_onecol(
table="user_directory_stream_pos",
Expand Down
139 changes: 139 additions & 0 deletions tests/rest/client/v2_alpha/test_shared_rooms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# -*- 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):

Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
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)