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

Make room alias lists peekable #6949

Merged
merged 4 commits into from
Feb 19, 2020
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
1 change: 1 addition & 0 deletions changelog.d/6949.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement `GET /_matrix/client/r0/rooms/{roomId}/aliases` endpoint as per [MSC2432](https://github.com/matrix-org/matrix-doc/pull/2432).
94 changes: 49 additions & 45 deletions synapse/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# limitations under the License.

import logging
from typing import Optional

from six import itervalues

Expand All @@ -35,6 +36,7 @@
)
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.config.server import is_threepid_reserved
from synapse.events import EventBase
from synapse.types import StateMap, UserID
from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
from synapse.util.caches.lrucache import LruCache
Expand Down Expand Up @@ -92,71 +94,61 @@ def check_from_context(self, room_version: str, event, context, do_sig_check=Tru
)

@defer.inlineCallbacks
def check_joined_room(self, room_id, user_id, current_state=None):
"""Check if the user is currently joined in the room
def check_user_in_room(
self,
room_id: str,
user_id: str,
current_state: Optional[StateMap[EventBase]] = None,
allow_departed_users: bool = False,
):
"""Check if the user is in the room, or was at some point.
Args:
room_id(str): The room to check.
user_id(str): The user to check.
current_state(dict): Optional map of the current state of the room.
room_id: The room to check.

user_id: The user to check.

current_state: Optional map of the current state of the room.
If provided then that map is used to check whether they are a
member of the room. Otherwise the current membership is
loaded from the database.

allow_departed_users: if True, accept users that were previously
members but have now departed.

Raises:
AuthError if the user is not in the room.
AuthError if the user is/was not in the room.
Returns:
A deferred membership event for the user if the user is in
the room.
Deferred[Optional[EventBase]]:
Membership event for the user if the user was in the
room. This will be the join event if they are currently joined to
the room. This will be the leave event if they have left the room.
"""
if current_state:
member = current_state.get((EventTypes.Member, user_id), None)
else:
member = yield self.state.get_current_state(
room_id=room_id, event_type=EventTypes.Member, state_key=user_id
)

self._check_joined_room(member, user_id, room_id)
return member

@defer.inlineCallbacks
def check_user_was_in_room(self, room_id, user_id):
"""Check if the user was in the room at some point.
Args:
room_id(str): The room to check.
user_id(str): The user to check.
Raises:
AuthError if the user was never in the room.
Returns:
A deferred membership event for the user if the user was in the
room. This will be the join event if they are currently joined to
the room. This will be the leave event if they have left the room.
"""
member = yield self.state.get_current_state(
room_id=room_id, event_type=EventTypes.Member, state_key=user_id
)
membership = member.membership if member else None

if membership not in (Membership.JOIN, Membership.LEAVE):
raise AuthError(403, "User %s not in room %s" % (user_id, room_id))
if membership == Membership.JOIN:
return member

if membership == Membership.LEAVE:
# XXX this looks totally bogus. Why do we not allow users who have been banned,
# or those who were members previously and have been re-invited?
if allow_departed_users and membership == Membership.LEAVE:
forgot = yield self.store.did_forget(user_id, room_id)
if forgot:
raise AuthError(403, "User %s not in room %s" % (user_id, room_id))
if not forgot:
return member

return member
raise AuthError(403, "User %s not in room %s" % (user_id, room_id))

@defer.inlineCallbacks
def check_host_in_room(self, room_id, host):
with Measure(self.clock, "check_host_in_room"):
latest_event_ids = yield self.store.is_host_joined(room_id, host)
return latest_event_ids

def _check_joined_room(self, member, user_id, room_id):
if not member or member.membership != Membership.JOIN:
raise AuthError(
403, "User %s not in room %s (%s)" % (user_id, room_id, repr(member))
)

def can_federate(self, event, auth_events):
creation_event = auth_events.get((EventTypes.Create, ""))

Expand Down Expand Up @@ -560,7 +552,7 @@ def check_can_change_room_list(self, room_id, user):
return True

user_id = user.to_string()
yield self.check_joined_room(room_id, user_id)
yield self.check_user_in_room(room_id, user_id)

# We currently require the user is a "moderator" in the room. We do this
# by checking if they would (theoretically) be able to change the
Expand Down Expand Up @@ -633,10 +625,18 @@ def get_access_token_from_request(request):
return query_params[0].decode("ascii")

@defer.inlineCallbacks
def check_in_room_or_world_readable(self, room_id, user_id):
def check_user_in_room_or_world_readable(
self, room_id: str, user_id: str, allow_departed_users: bool = False
Copy link
Member Author

Choose a reason for hiding this comment

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

this defaults to False for consistency with check_user_in_room, and for safety (if you forget to set it, it's safer if we deny access to users that should have it than grant access to users that should).

):
"""Checks that the user is or was in the room or the room is world
readable. If it isn't then an exception is raised.

Args:
room_id: room to check
user_id: user to check
allow_departed_users: if True, accept users that were previously
members but have now departed

Returns:
Deferred[tuple[str, str|None]]: Resolves to the current membership of
the user in the room and the membership event ID of the user. If
Expand All @@ -645,12 +645,14 @@ def check_in_room_or_world_readable(self, room_id, user_id):
"""

try:
# check_user_was_in_room will return the most recent membership
# check_user_in_room will return the most recent membership
# event for the user if:
# * The user is a non-guest user, and was ever in the room
# * The user is a guest user, and has joined the room
# else it will throw.
member_event = yield self.check_user_was_in_room(room_id, user_id)
member_event = yield self.check_user_in_room(
room_id, user_id, allow_departed_users=allow_departed_users
)
return member_event.membership, member_event.event_id
except AuthError:
visibility = yield self.state.get_current_state(
Expand All @@ -662,7 +664,9 @@ def check_in_room_or_world_readable(self, room_id, user_id):
):
return Membership.JOIN, None
raise AuthError(
403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN
Copy link
Member Author

Choose a reason for hiding this comment

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

this was never anything to do with guest access, so the error message was wrong and confusing; I've fixed it up while I'm here.

403,
"User %s not in room %s, and room previews are disabled"
% (user_id, room_id),
)

@defer.inlineCallbacks
Expand Down
4 changes: 3 additions & 1 deletion synapse/handlers/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,9 @@ async def get_aliases_for_room(
# allow access to server admins and current members of the room
is_admin = await self.auth.is_server_admin(requester.user)
if not is_admin:
await self.auth.check_joined_room(room_id, requester.user.to_string())
await self.auth.check_user_in_room_or_world_readable(
room_id, requester.user.to_string()
)

aliases = await self.store.get_aliases_for_room(room_id)
return aliases
31 changes: 6 additions & 25 deletions synapse/handlers/initial_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from twisted.internet import defer

from synapse.api.constants import EventTypes, Membership
from synapse.api.errors import AuthError, Codes, SynapseError
from synapse.api.errors import SynapseError
from synapse.events.validator import EventValidator
from synapse.handlers.presence import format_user_presence_state
from synapse.logging.context import make_deferred_yieldable, run_in_background
Expand Down Expand Up @@ -274,8 +274,11 @@ async def room_initial_sync(self, requester, room_id, pagin_config=None):

user_id = requester.user.to_string()

membership, member_event_id = await self._check_in_room_or_world_readable(
room_id, user_id
(
membership,
member_event_id,
) = await self.auth.check_user_in_room_or_world_readable(
room_id, user_id, allow_departed_users=True,
)
is_peeking = member_event_id is None

Expand Down Expand Up @@ -433,25 +436,3 @@ async def get_receipts():
ret["membership"] = membership

return ret

async def _check_in_room_or_world_readable(self, room_id, user_id):
Copy link
Member Author

Choose a reason for hiding this comment

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

this was an exact cut-and-paste of Auth.check_in_room_or_world_readable; removed for sanity.

try:
# check_user_was_in_room will return the most recent membership
# event for the user if:
# * The user is a non-guest user, and was ever in the room
# * The user is a guest user, and has joined the room
# else it will throw.
member_event = await self.auth.check_user_was_in_room(room_id, user_id)
return member_event.membership, member_event.event_id
except AuthError:
visibility = await self.state_handler.get_current_state(
room_id, EventTypes.RoomHistoryVisibility, ""
)
if (
visibility
and visibility.content["history_visibility"] == "world_readable"
):
return Membership.JOIN, None
raise AuthError(
403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN
)
12 changes: 8 additions & 4 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ def get_room_data(
(
membership,
membership_event_id,
) = yield self.auth.check_in_room_or_world_readable(room_id, user_id)
) = yield self.auth.check_user_in_room_or_world_readable(
room_id, user_id, allow_departed_users=True
)

if membership == Membership.JOIN:
data = yield self.state.get_current_state(room_id, event_type, state_key)
Expand Down Expand Up @@ -177,7 +179,9 @@ def get_state_events(
(
membership,
membership_event_id,
) = yield self.auth.check_in_room_or_world_readable(room_id, user_id)
) = yield self.auth.check_user_in_room_or_world_readable(
room_id, user_id, allow_departed_users=True
)

if membership == Membership.JOIN:
state_ids = yield self.store.get_filtered_current_state_ids(
Expand Down Expand Up @@ -216,8 +220,8 @@ def get_joined_members(self, requester, room_id):
if not requester.app_service:
# We check AS auth after fetching the room membership, as it
# requires us to pull out all joined members anyway.
membership, _ = yield self.auth.check_in_room_or_world_readable(
room_id, user_id
membership, _ = yield self.auth.check_user_in_room_or_world_readable(
room_id, user_id, allow_departed_users=True
)
if membership != Membership.JOIN:
raise NotImplementedError(
Expand Down
4 changes: 3 additions & 1 deletion synapse/handlers/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,9 @@ async def get_messages(
(
membership,
member_event_id,
) = await self.auth.check_in_room_or_world_readable(room_id, user_id)
) = await self.auth.check_user_in_room_or_world_readable(
room_id, user_id, allow_departed_users=True
)

if source_config.direction == "b":
# if we're going backwards, we might need to backfill. This
Expand Down
4 changes: 2 additions & 2 deletions synapse/handlers/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def started_typing(self, target_user, auth_user, room_id, timeout):
if target_user_id != auth_user_id:
raise AuthError(400, "Cannot set another user's typing state")

yield self.auth.check_joined_room(room_id, target_user_id)
yield self.auth.check_user_in_room(room_id, target_user_id)

logger.debug("%s has started typing in %s", target_user_id, room_id)

Expand Down Expand Up @@ -155,7 +155,7 @@ def stopped_typing(self, target_user, auth_user, room_id):
if target_user_id != auth_user_id:
raise AuthError(400, "Cannot set another user's typing state")

yield self.auth.check_joined_room(room_id, target_user_id)
yield self.auth.check_user_in_room(room_id, target_user_id)

logger.debug("%s has stopped typing in %s", target_user_id, room_id)

Expand Down
12 changes: 6 additions & 6 deletions synapse/rest/client/v2_alpha/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ async def on_GET(
):
requester = await self.auth.get_user_by_req(request, allow_guest=True)

await self.auth.check_in_room_or_world_readable(
room_id, requester.user.to_string()
await self.auth.check_user_in_room_or_world_readable(
room_id, requester.user.to_string(), allow_departed_users=True
)

# This gets the original event and checks that a) the event exists and
Expand Down Expand Up @@ -235,8 +235,8 @@ async def on_GET(
):
requester = await self.auth.get_user_by_req(request, allow_guest=True)

await self.auth.check_in_room_or_world_readable(
room_id, requester.user.to_string()
await self.auth.check_user_in_room_or_world_readable(
room_id, requester.user.to_string(), allow_departed_users=True,
)

# This checks that a) the event exists and b) the user is allowed to
Expand Down Expand Up @@ -313,8 +313,8 @@ def __init__(self, hs):
async def on_GET(self, request, room_id, parent_id, relation_type, event_type, key):
requester = await self.auth.get_user_by_req(request, allow_guest=True)

await self.auth.check_in_room_or_world_readable(
room_id, requester.user.to_string()
await self.auth.check_user_in_room_or_world_readable(
room_id, requester.user.to_string(), allow_departed_users=True,
)

# This checks that a) the event exists and b) the user is allowed to
Expand Down
4 changes: 2 additions & 2 deletions tests/handlers/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,11 @@ def get_received_txn_response(*args):

self.room_members = []

def check_joined_room(room_id, user_id):
def check_user_in_room(room_id, user_id):
if user_id not in [u.to_string() for u in self.room_members]:
raise AuthError(401, "User is not in the room")

hs.get_auth().check_joined_room = check_joined_room
hs.get_auth().check_user_in_room = check_user_in_room

def get_joined_hosts_for_room(room_id):
return set(member.domain for member in self.room_members)
Expand Down
17 changes: 17 additions & 0 deletions tests/rest/client/v1/test_rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,23 @@ def test_with_aliases(self):
res = self._get_aliases(self.room_owner_tok)
self.assertEqual(set(res["aliases"]), {alias1, alias2})

def test_peekable_room(self):
alias1 = self._random_alias()
self._set_alias_via_directory(alias1)

self.helper.send_state(
self.room_id,
EventTypes.RoomHistoryVisibility,
body={"history_visibility": "world_readable"},
tok=self.room_owner_tok,
)

self.register_user("user", "test")
user_tok = self.login("user", "test")

res = self._get_aliases(user_tok)
self.assertEqual(res["aliases"], [alias1])

def _get_aliases(self, access_token: str, expected_code: int = 200) -> JsonDict:
"""Calls the endpoint under test. returns the json response object."""
request, channel = self.make_request(
Expand Down