Merge pull request #510 from matrix-org/daniel/nonguestpeeking

Allow non-guests to peek on rooms using /events
This commit is contained in:
Daniel Wagner-Hall 2016-01-20 15:34:18 +00:00
commit a2ae01cc0f
8 changed files with 107 additions and 84 deletions

View File

@ -84,7 +84,7 @@ class BaseHandler(object):
row["event_id"] for rows in forgotten for row in rows row["event_id"] for rows in forgotten for row in rows
) )
def allowed(event, user_id, is_guest): def allowed(event, user_id, is_peeking):
state = event_id_to_state[event.event_id] state = event_id_to_state[event.event_id]
visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""), None) visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""), None)
@ -96,7 +96,7 @@ class BaseHandler(object):
if visibility == "world_readable": if visibility == "world_readable":
return True return True
if is_guest: if is_peeking:
return False return False
membership_event = state.get((EventTypes.Member, user_id), None) membership_event = state.get((EventTypes.Member, user_id), None)
@ -112,7 +112,7 @@ class BaseHandler(object):
return True return True
if event.type == EventTypes.RoomHistoryVisibility: if event.type == EventTypes.RoomHistoryVisibility:
return not is_guest return not is_peeking
if visibility == "shared": if visibility == "shared":
return True return True
@ -127,15 +127,15 @@ class BaseHandler(object):
user_id: [ user_id: [
event event
for event in events for event in events
if allowed(event, user_id, is_guest) if allowed(event, user_id, is_peeking)
] ]
for user_id, is_guest in user_tuples for user_id, is_peeking in user_tuples
}) })
@defer.inlineCallbacks @defer.inlineCallbacks
def _filter_events_for_client(self, user_id, events, is_guest=False): def _filter_events_for_client(self, user_id, events, is_peeking=False):
# Assumes that user has at some point joined the room if not is_guest. # Assumes that user has at some point joined the room if not is_guest.
res = yield self._filter_events_for_clients([(user_id, is_guest)], events) res = yield self._filter_events_for_clients([(user_id, is_peeking)], events)
defer.returnValue(res.get(user_id, [])) defer.returnValue(res.get(user_id, []))
def ratelimit(self, user_id): def ratelimit(self, user_id):

View File

@ -135,7 +135,7 @@ class EventStreamHandler(BaseHandler):
events, tokens = yield self.notifier.get_events_for( events, tokens = yield self.notifier.get_events_for(
auth_user, pagin_config, timeout, auth_user, pagin_config, timeout,
only_room_events=only_room_events, only_room_events=only_room_events,
is_guest=is_guest, guest_room_id=room_id is_guest=is_guest, explicit_room_id=room_id
) )
time_now = self.clock.time_msec() time_now = self.clock.time_msec()

View File

@ -78,21 +78,20 @@ class MessageHandler(BaseHandler):
defer.returnValue(None) defer.returnValue(None)
@defer.inlineCallbacks @defer.inlineCallbacks
def get_messages(self, user_id=None, room_id=None, pagin_config=None, def get_messages(self, requester, room_id=None, pagin_config=None,
as_client_event=True, is_guest=False): as_client_event=True):
"""Get messages in a room. """Get messages in a room.
Args: Args:
user_id (str): The user requesting messages. requester (Requester): The user requesting messages.
room_id (str): The room they want messages from. room_id (str): The room they want messages from.
pagin_config (synapse.api.streams.PaginationConfig): The pagination pagin_config (synapse.api.streams.PaginationConfig): The pagination
config rules to apply, if any. config rules to apply, if any.
as_client_event (bool): True to get events in client-server format. as_client_event (bool): True to get events in client-server format.
is_guest (bool): Whether the requesting user is a guest (as opposed
to a fully registered user).
Returns: Returns:
dict: Pagination API results dict: Pagination API results
""" """
user_id = requester.user.to_string()
data_source = self.hs.get_event_sources().sources["room"] data_source = self.hs.get_event_sources().sources["room"]
if pagin_config.from_token: if pagin_config.from_token:
@ -115,36 +114,33 @@ class MessageHandler(BaseHandler):
source_config = pagin_config.get_source_config("room") source_config = pagin_config.get_source_config("room")
if not is_guest: membership, member_event_id = yield self._check_in_room_or_world_readable(
member_event = yield self.auth.check_user_was_in_room(room_id, user_id) room_id, user_id
if member_event.membership == Membership.LEAVE: )
# If they have left the room then clamp the token to be before if membership == Membership.LEAVE:
# they left the room. # If they have left the room then clamp the token to be before
# If they're a guest, we'll just 403 them if they're asking for # they left the room.
# events they can't see. leave_token = yield self.store.get_topological_token_for_event(
leave_token = yield self.store.get_topological_token_for_event( member_event_id
member_event.event_id )
) leave_token = RoomStreamToken.parse(leave_token)
leave_token = RoomStreamToken.parse(leave_token) if leave_token.topological < room_token.topological:
if leave_token.topological < room_token.topological: source_config.from_key = str(leave_token)
source_config.from_key = str(leave_token)
if source_config.direction == "f": if source_config.direction == "f":
if source_config.to_key is None: if source_config.to_key is None:
source_config.to_key = str(leave_token)
else:
to_token = RoomStreamToken.parse(source_config.to_key)
if leave_token.topological < to_token.topological:
source_config.to_key = str(leave_token) source_config.to_key = str(leave_token)
else:
to_token = RoomStreamToken.parse(source_config.to_key)
if leave_token.topological < to_token.topological:
source_config.to_key = str(leave_token)
yield self.hs.get_handlers().federation_handler.maybe_backfill( yield self.hs.get_handlers().federation_handler.maybe_backfill(
room_id, room_token.topological room_id, room_token.topological
) )
user = UserID.from_string(user_id)
events, next_key = yield data_source.get_pagination_rows( events, next_key = yield data_source.get_pagination_rows(
user, source_config, room_id requester.user, source_config, room_id
) )
next_token = pagin_config.from_token.copy_and_replace( next_token = pagin_config.from_token.copy_and_replace(
@ -158,7 +154,11 @@ class MessageHandler(BaseHandler):
"end": next_token.to_string(), "end": next_token.to_string(),
}) })
events = yield self._filter_events_for_client(user_id, events, is_guest=is_guest) events = yield self._filter_events_for_client(
user_id,
events,
is_peeking=(member_event_id is None),
)
time_now = self.clock.time_msec() time_now = self.clock.time_msec()
@ -289,7 +289,7 @@ class MessageHandler(BaseHandler):
SynapseError if something went wrong. SynapseError if something went wrong.
""" """
membership, membership_event_id = yield self._check_in_room_or_world_readable( membership, membership_event_id = yield self._check_in_room_or_world_readable(
room_id, user_id, is_guest room_id, user_id
) )
if membership == Membership.JOIN: if membership == Membership.JOIN:
@ -306,7 +306,7 @@ class MessageHandler(BaseHandler):
defer.returnValue(data) defer.returnValue(data)
@defer.inlineCallbacks @defer.inlineCallbacks
def _check_in_room_or_world_readable(self, room_id, user_id, is_guest): def _check_in_room_or_world_readable(self, room_id, user_id):
try: try:
# check_user_was_in_room will return the most recent membership # check_user_was_in_room will return the most recent membership
# event for the user if: # event for the user if:
@ -316,7 +316,7 @@ class MessageHandler(BaseHandler):
member_event = yield self.auth.check_user_was_in_room(room_id, user_id) member_event = yield self.auth.check_user_was_in_room(room_id, user_id)
defer.returnValue((member_event.membership, member_event.event_id)) defer.returnValue((member_event.membership, member_event.event_id))
return return
except AuthError, auth_error: except AuthError:
visibility = yield self.state_handler.get_current_state( visibility = yield self.state_handler.get_current_state(
room_id, EventTypes.RoomHistoryVisibility, "" room_id, EventTypes.RoomHistoryVisibility, ""
) )
@ -326,8 +326,6 @@ class MessageHandler(BaseHandler):
): ):
defer.returnValue((Membership.JOIN, None)) defer.returnValue((Membership.JOIN, None))
return return
if not is_guest:
raise auth_error
raise AuthError( raise AuthError(
403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN 403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN
) )
@ -345,7 +343,7 @@ class MessageHandler(BaseHandler):
A list of dicts representing state events. [{}, {}, {}] A list of dicts representing state events. [{}, {}, {}]
""" """
membership, membership_event_id = yield self._check_in_room_or_world_readable( membership, membership_event_id = yield self._check_in_room_or_world_readable(
room_id, user_id, is_guest room_id, user_id
) )
if membership == Membership.JOIN: if membership == Membership.JOIN:
@ -556,13 +554,13 @@ class MessageHandler(BaseHandler):
defer.returnValue(ret) defer.returnValue(ret)
@defer.inlineCallbacks @defer.inlineCallbacks
def room_initial_sync(self, user_id, room_id, pagin_config=None, is_guest=False): def room_initial_sync(self, requester, room_id, pagin_config=None):
"""Capture the a snapshot of a room. If user is currently a member of """Capture the a snapshot of a room. If user is currently a member of
the room this will be what is currently in the room. If the user left the room this will be what is currently in the room. If the user left
the room this will be what was in the room when they left. the room this will be what was in the room when they left.
Args: Args:
user_id(str): The user to get a snapshot for. requester(Requester): The user to get a snapshot for.
room_id(str): The room to get a snapshot of. room_id(str): The room to get a snapshot of.
pagin_config(synapse.streams.config.PaginationConfig): pagin_config(synapse.streams.config.PaginationConfig):
The pagination config used to determine how many messages to The pagination config used to determine how many messages to
@ -573,19 +571,20 @@ class MessageHandler(BaseHandler):
A JSON serialisable dict with the snapshot of the room. A JSON serialisable dict with the snapshot of the room.
""" """
user_id = requester.user.to_string()
membership, member_event_id = yield self._check_in_room_or_world_readable( membership, member_event_id = yield self._check_in_room_or_world_readable(
room_id, room_id, user_id,
user_id,
is_guest
) )
is_peeking = member_event_id is None
if membership == Membership.JOIN: if membership == Membership.JOIN:
result = yield self._room_initial_sync_joined( result = yield self._room_initial_sync_joined(
user_id, room_id, pagin_config, membership, is_guest user_id, room_id, pagin_config, membership, is_peeking
) )
elif membership == Membership.LEAVE: elif membership == Membership.LEAVE:
result = yield self._room_initial_sync_parted( result = yield self._room_initial_sync_parted(
user_id, room_id, pagin_config, membership, member_event_id, is_guest user_id, room_id, pagin_config, membership, member_event_id, is_peeking
) )
account_data_events = [] account_data_events = []
@ -609,7 +608,7 @@ class MessageHandler(BaseHandler):
@defer.inlineCallbacks @defer.inlineCallbacks
def _room_initial_sync_parted(self, user_id, room_id, pagin_config, def _room_initial_sync_parted(self, user_id, room_id, pagin_config,
membership, member_event_id, is_guest): membership, member_event_id, is_peeking):
room_state = yield self.store.get_state_for_events( room_state = yield self.store.get_state_for_events(
[member_event_id], None [member_event_id], None
) )
@ -631,7 +630,7 @@ class MessageHandler(BaseHandler):
) )
messages = yield self._filter_events_for_client( messages = yield self._filter_events_for_client(
user_id, messages, is_guest=is_guest user_id, messages, is_peeking=is_peeking
) )
start_token = StreamToken(token[0], 0, 0, 0, 0) start_token = StreamToken(token[0], 0, 0, 0, 0)
@ -654,7 +653,7 @@ class MessageHandler(BaseHandler):
@defer.inlineCallbacks @defer.inlineCallbacks
def _room_initial_sync_joined(self, user_id, room_id, pagin_config, def _room_initial_sync_joined(self, user_id, room_id, pagin_config,
membership, is_guest): membership, is_peeking):
current_state = yield self.state.get_current_state( current_state = yield self.state.get_current_state(
room_id=room_id, room_id=room_id,
) )
@ -718,7 +717,7 @@ class MessageHandler(BaseHandler):
).addErrback(unwrapFirstError) ).addErrback(unwrapFirstError)
messages = yield self._filter_events_for_client( messages = yield self._filter_events_for_client(
user_id, messages, is_guest=is_guest, user_id, messages, is_peeking=is_peeking,
) )
start_token = now_token.copy_and_replace("room_key", token[0]) start_token = now_token.copy_and_replace("room_key", token[0])
@ -737,7 +736,7 @@ class MessageHandler(BaseHandler):
"presence": presence, "presence": presence,
"receipts": receipts, "receipts": receipts,
} }
if not is_guest: if not is_peeking:
ret["membership"] = membership ret["membership"] = membership
defer.returnValue(ret) defer.returnValue(ret)

View File

@ -936,7 +936,7 @@ class RoomContextHandler(BaseHandler):
return self._filter_events_for_client( return self._filter_events_for_client(
user.to_string(), user.to_string(),
events, events,
is_guest=is_guest) is_peeking=is_guest)
event = yield self.store.get_event(event_id, get_prev_content=True, event = yield self.store.get_event(event_id, get_prev_content=True,
allow_none=True) allow_none=True)

View File

@ -150,7 +150,7 @@ class SyncHandler(BaseHandler):
return self.current_sync_for_user(sync_config, since_token) return self.current_sync_for_user(sync_config, since_token)
result = yield self.notifier.wait_for_events( result = yield self.notifier.wait_for_events(
sync_config.user, timeout, current_sync_callback, sync_config.user.to_string(), timeout, current_sync_callback,
from_token=since_token from_token=since_token
) )
defer.returnValue(result) defer.returnValue(result)
@ -640,7 +640,7 @@ class SyncHandler(BaseHandler):
loaded_recents = yield self._filter_events_for_client( loaded_recents = yield self._filter_events_for_client(
sync_config.user.to_string(), sync_config.user.to_string(),
loaded_recents, loaded_recents,
is_guest=sync_config.is_guest, is_peeking=sync_config.is_guest,
) )
loaded_recents.extend(recents) loaded_recents.extend(recents)
recents = loaded_recents recents = loaded_recents

View File

@ -63,9 +63,9 @@ class _NotifierUserStream(object):
so that it can remove itself from the indexes in the Notifier class. so that it can remove itself from the indexes in the Notifier class.
""" """
def __init__(self, user, rooms, current_token, time_now_ms, def __init__(self, user_id, rooms, current_token, time_now_ms,
appservice=None): appservice=None):
self.user = str(user) self.user_id = user_id
self.appservice = appservice self.appservice = appservice
self.rooms = set(rooms) self.rooms = set(rooms)
self.current_token = current_token self.current_token = current_token
@ -98,7 +98,7 @@ class _NotifierUserStream(object):
lst = notifier.room_to_user_streams.get(room, set()) lst = notifier.room_to_user_streams.get(room, set())
lst.discard(self) lst.discard(self)
notifier.user_to_user_stream.pop(self.user) notifier.user_to_user_stream.pop(self.user_id)
if self.appservice: if self.appservice:
notifier.appservice_to_user_streams.get( notifier.appservice_to_user_streams.get(
@ -271,21 +271,20 @@ class Notifier(object):
logger.exception("Failed to notify listener") logger.exception("Failed to notify listener")
@defer.inlineCallbacks @defer.inlineCallbacks
def wait_for_events(self, user, timeout, callback, room_ids=None, def wait_for_events(self, user_id, timeout, callback, room_ids=None,
from_token=StreamToken("s0", "0", "0", "0", "0")): from_token=StreamToken("s0", "0", "0", "0", "0")):
"""Wait until the callback returns a non empty response or the """Wait until the callback returns a non empty response or the
timeout fires. timeout fires.
""" """
user = str(user) user_stream = self.user_to_user_stream.get(user_id)
user_stream = self.user_to_user_stream.get(user)
if user_stream is None: if user_stream is None:
appservice = yield self.store.get_app_service_by_user_id(user) appservice = yield self.store.get_app_service_by_user_id(user_id)
current_token = yield self.event_sources.get_current_token() current_token = yield self.event_sources.get_current_token()
if room_ids is None: if room_ids is None:
rooms = yield self.store.get_rooms_for_user(user) rooms = yield self.store.get_rooms_for_user(user_id)
room_ids = [room.room_id for room in rooms] room_ids = [room.room_id for room in rooms]
user_stream = _NotifierUserStream( user_stream = _NotifierUserStream(
user=user, user_id=user_id,
rooms=room_ids, rooms=room_ids,
appservice=appservice, appservice=appservice,
current_token=current_token, current_token=current_token,
@ -333,12 +332,17 @@ class Notifier(object):
@defer.inlineCallbacks @defer.inlineCallbacks
def get_events_for(self, user, pagination_config, timeout, def get_events_for(self, user, pagination_config, timeout,
only_room_events=False, only_room_events=False,
is_guest=False, guest_room_id=None): is_guest=False, explicit_room_id=None):
""" For the given user and rooms, return any new events for them. If """ For the given user and rooms, return any new events for them. If
there are no new events wait for up to `timeout` milliseconds for any there are no new events wait for up to `timeout` milliseconds for any
new events to happen before returning. new events to happen before returning.
If `only_room_events` is `True` only room events will be returned. If `only_room_events` is `True` only room events will be returned.
If explicit_room_id is not set, the user's joined rooms will be polled
for events.
If explicit_room_id is set, that room will be polled for events only if
it is world readable or the user has joined the room.
""" """
from_token = pagination_config.from_token from_token = pagination_config.from_token
if not from_token: if not from_token:
@ -346,15 +350,8 @@ class Notifier(object):
limit = pagination_config.limit limit = pagination_config.limit
room_ids = [] room_ids, is_joined = yield self._get_room_ids(user, explicit_room_id)
if is_guest: is_peeking = not is_joined
if guest_room_id:
if not (yield self._is_world_readable(guest_room_id)):
raise AuthError(403, "Guest access not allowed")
room_ids = [guest_room_id]
else:
rooms = yield self.store.get_rooms_for_user(user.to_string())
room_ids = [room.room_id for room in rooms]
@defer.inlineCallbacks @defer.inlineCallbacks
def check_for_updates(before_token, after_token): def check_for_updates(before_token, after_token):
@ -376,7 +373,7 @@ class Notifier(object):
user=user, user=user,
from_key=getattr(from_token, keyname), from_key=getattr(from_token, keyname),
limit=limit, limit=limit,
is_guest=is_guest, is_guest=is_peeking,
room_ids=room_ids, room_ids=room_ids,
) )
@ -385,7 +382,7 @@ class Notifier(object):
new_events = yield room_member_handler._filter_events_for_client( new_events = yield room_member_handler._filter_events_for_client(
user.to_string(), user.to_string(),
new_events, new_events,
is_guest=is_guest, is_peeking=is_peeking,
) )
events.extend(new_events) events.extend(new_events)
@ -396,8 +393,24 @@ class Notifier(object):
else: else:
defer.returnValue(None) defer.returnValue(None)
user_id_for_stream = user.to_string()
if is_peeking:
# Internally, the notifier keeps an event stream per user_id.
# This is used by both /sync and /events.
# We want /events to be used for peeking independently of /sync,
# without polluting its contents. So we invent an illegal user ID
# (which thus cannot clash with any real users) for keying peeking
# over /events.
#
# I am sorry for what I have done.
user_id_for_stream = "_PEEKING_" + user_id_for_stream
result = yield self.wait_for_events( result = yield self.wait_for_events(
user, timeout, check_for_updates, room_ids=room_ids, from_token=from_token user_id_for_stream,
timeout,
check_for_updates,
room_ids=room_ids,
from_token=from_token,
) )
if result is None: if result is None:
@ -405,6 +418,18 @@ class Notifier(object):
defer.returnValue(result) defer.returnValue(result)
@defer.inlineCallbacks
def _get_room_ids(self, user, explicit_room_id):
joined_rooms = yield self.store.get_rooms_for_user(user.to_string())
joined_room_ids = map(lambda r: r.room_id, joined_rooms)
if explicit_room_id:
if explicit_room_id in joined_room_ids:
defer.returnValue(([explicit_room_id], True))
if (yield self._is_world_readable(explicit_room_id)):
defer.returnValue(([explicit_room_id], False))
raise AuthError(403, "Non-joined access not allowed")
defer.returnValue((joined_room_ids, True))
@defer.inlineCallbacks @defer.inlineCallbacks
def _is_world_readable(self, room_id): def _is_world_readable(self, room_id):
state = yield self.hs.get_state_handler().get_current_state( state = yield self.hs.get_state_handler().get_current_state(
@ -432,7 +457,7 @@ class Notifier(object):
@log_function @log_function
def _register_with_keys(self, user_stream): def _register_with_keys(self, user_stream):
self.user_to_user_stream[user_stream.user] = user_stream self.user_to_user_stream[user_stream.user_id] = user_stream
for room in user_stream.rooms: for room in user_stream.rooms:
s = self.room_to_user_streams.setdefault(room, set()) s = self.room_to_user_streams.setdefault(room, set())

View File

@ -43,6 +43,7 @@ class EventStreamRestServlet(ClientV1RestServlet):
if is_guest: if is_guest:
if "room_id" not in request.args: if "room_id" not in request.args:
raise SynapseError(400, "Guest users must specify room_id param") raise SynapseError(400, "Guest users must specify room_id param")
if "room_id" in request.args:
room_id = request.args["room_id"][0] room_id = request.args["room_id"][0]
try: try:
handler = self.handlers.event_stream_handler handler = self.handlers.event_stream_handler

View File

@ -348,8 +348,7 @@ class RoomMessageListRestServlet(ClientV1RestServlet):
handler = self.handlers.message_handler handler = self.handlers.message_handler
msgs = yield handler.get_messages( msgs = yield handler.get_messages(
room_id=room_id, room_id=room_id,
user_id=requester.user.to_string(), requester=requester,
is_guest=requester.is_guest,
pagin_config=pagination_config, pagin_config=pagination_config,
as_client_event=as_client_event as_client_event=as_client_event
) )
@ -384,9 +383,8 @@ class RoomInitialSyncRestServlet(ClientV1RestServlet):
pagination_config = PaginationConfig.from_request(request) pagination_config = PaginationConfig.from_request(request)
content = yield self.handlers.message_handler.room_initial_sync( content = yield self.handlers.message_handler.room_initial_sync(
room_id=room_id, room_id=room_id,
user_id=requester.user.to_string(), requester=requester,
pagin_config=pagination_config, pagin_config=pagination_config,
is_guest=requester.is_guest,
) )
defer.returnValue((200, content)) defer.returnValue((200, content))