Merge pull request #355 from matrix-org/daniel/anonymouswriting
Allow guest users to join and message rooms
This commit is contained in:
commit
466b4ec01d
|
@ -68,6 +68,7 @@ class EventTypes(object):
|
||||||
RoomHistoryVisibility = "m.room.history_visibility"
|
RoomHistoryVisibility = "m.room.history_visibility"
|
||||||
CanonicalAlias = "m.room.canonical_alias"
|
CanonicalAlias = "m.room.canonical_alias"
|
||||||
RoomAvatar = "m.room.avatar"
|
RoomAvatar = "m.room.avatar"
|
||||||
|
GuestAccess = "m.room.guest_access"
|
||||||
|
|
||||||
# These are used for validation
|
# These are used for validation
|
||||||
Message = "m.room.message"
|
Message = "m.room.message"
|
||||||
|
|
|
@ -175,6 +175,8 @@ class BaseHandler(object):
|
||||||
if not suppress_auth:
|
if not suppress_auth:
|
||||||
self.auth.check(event, auth_events=context.current_state)
|
self.auth.check(event, auth_events=context.current_state)
|
||||||
|
|
||||||
|
yield self.maybe_kick_guest_users(event, context.current_state.values())
|
||||||
|
|
||||||
if event.type == EventTypes.CanonicalAlias:
|
if event.type == EventTypes.CanonicalAlias:
|
||||||
# Check the alias is acually valid (at this time at least)
|
# Check the alias is acually valid (at this time at least)
|
||||||
room_alias_str = event.content.get("alias", None)
|
room_alias_str = event.content.get("alias", None)
|
||||||
|
@ -282,3 +284,58 @@ class BaseHandler(object):
|
||||||
federation_handler.handle_new_event(
|
federation_handler.handle_new_event(
|
||||||
event, destinations=destinations,
|
event, destinations=destinations,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def maybe_kick_guest_users(self, event, current_state):
|
||||||
|
# Technically this function invalidates current_state by changing it.
|
||||||
|
# Hopefully this isn't that important to the caller.
|
||||||
|
if event.type == EventTypes.GuestAccess:
|
||||||
|
guest_access = event.content.get("guest_access", "forbidden")
|
||||||
|
if guest_access != "can_join":
|
||||||
|
yield self.kick_guest_users(current_state)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def kick_guest_users(self, current_state):
|
||||||
|
for member_event in current_state:
|
||||||
|
try:
|
||||||
|
if member_event.type != EventTypes.Member:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not self.hs.is_mine(UserID.from_string(member_event.state_key)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if member_event.content["membership"] not in {
|
||||||
|
Membership.JOIN,
|
||||||
|
Membership.INVITE
|
||||||
|
}:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (
|
||||||
|
"kind" not in member_event.content
|
||||||
|
or member_event.content["kind"] != "guest"
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# We make the user choose to leave, rather than have the
|
||||||
|
# event-sender kick them. This is partially because we don't
|
||||||
|
# need to worry about power levels, and partially because guest
|
||||||
|
# users are a concept which doesn't hugely work over federation,
|
||||||
|
# and having homeservers have their own users leave keeps more
|
||||||
|
# of that decision-making and control local to the guest-having
|
||||||
|
# homeserver.
|
||||||
|
message_handler = self.hs.get_handlers().message_handler
|
||||||
|
yield message_handler.create_and_send_event(
|
||||||
|
{
|
||||||
|
"type": EventTypes.Member,
|
||||||
|
"state_key": member_event.state_key,
|
||||||
|
"content": {
|
||||||
|
"membership": Membership.LEAVE,
|
||||||
|
"kind": "guest"
|
||||||
|
},
|
||||||
|
"room_id": member_event.room_id,
|
||||||
|
"sender": member_event.state_key
|
||||||
|
},
|
||||||
|
ratelimit=False,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warn("Error kicking guest user: %s" % (e,))
|
||||||
|
|
|
@ -1097,8 +1097,6 @@ class FederationHandler(BaseHandler):
|
||||||
context = yield self._prep_event(
|
context = yield self._prep_event(
|
||||||
origin, event,
|
origin, event,
|
||||||
state=state,
|
state=state,
|
||||||
backfilled=backfilled,
|
|
||||||
current_state=current_state,
|
|
||||||
auth_events=auth_events,
|
auth_events=auth_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1121,7 +1119,6 @@ class FederationHandler(BaseHandler):
|
||||||
origin,
|
origin,
|
||||||
ev_info["event"],
|
ev_info["event"],
|
||||||
state=ev_info.get("state"),
|
state=ev_info.get("state"),
|
||||||
backfilled=backfilled,
|
|
||||||
auth_events=ev_info.get("auth_events"),
|
auth_events=ev_info.get("auth_events"),
|
||||||
)
|
)
|
||||||
for ev_info in event_infos
|
for ev_info in event_infos
|
||||||
|
@ -1208,8 +1205,7 @@ class FederationHandler(BaseHandler):
|
||||||
defer.returnValue((event_stream_id, max_stream_id))
|
defer.returnValue((event_stream_id, max_stream_id))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _prep_event(self, origin, event, state=None, backfilled=False,
|
def _prep_event(self, origin, event, state=None, auth_events=None):
|
||||||
current_state=None, auth_events=None):
|
|
||||||
outlier = event.internal_metadata.is_outlier()
|
outlier = event.internal_metadata.is_outlier()
|
||||||
|
|
||||||
context = yield self.state_handler.compute_event_context(
|
context = yield self.state_handler.compute_event_context(
|
||||||
|
@ -1242,6 +1238,10 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
context.rejected = RejectedReason.AUTH_ERROR
|
context.rejected = RejectedReason.AUTH_ERROR
|
||||||
|
|
||||||
|
if event.type == EventTypes.GuestAccess:
|
||||||
|
full_context = yield self.store.get_current_state(room_id=event.room_id)
|
||||||
|
yield self.maybe_kick_guest_users(event, full_context)
|
||||||
|
|
||||||
defer.returnValue(context)
|
defer.returnValue(context)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
|
|
@ -167,7 +167,7 @@ class MessageHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_and_send_event(self, event_dict, ratelimit=True,
|
def create_and_send_event(self, event_dict, ratelimit=True,
|
||||||
token_id=None, txn_id=None):
|
token_id=None, txn_id=None, is_guest=False):
|
||||||
""" Given a dict from a client, create and handle a new event.
|
""" Given a dict from a client, create and handle a new event.
|
||||||
|
|
||||||
Creates an FrozenEvent object, filling out auth_events, prev_events,
|
Creates an FrozenEvent object, filling out auth_events, prev_events,
|
||||||
|
@ -213,7 +213,7 @@ class MessageHandler(BaseHandler):
|
||||||
|
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
member_handler = self.hs.get_handlers().room_member_handler
|
member_handler = self.hs.get_handlers().room_member_handler
|
||||||
yield member_handler.change_membership(event, context)
|
yield member_handler.change_membership(event, context, is_guest=is_guest)
|
||||||
else:
|
else:
|
||||||
yield self.handle_new_client_event(
|
yield self.handle_new_client_event(
|
||||||
event=event,
|
event=event,
|
||||||
|
|
|
@ -950,7 +950,8 @@ class PresenceHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
while len(self._remote_offline_serials) > MAX_OFFLINE_SERIALS:
|
while len(self._remote_offline_serials) > MAX_OFFLINE_SERIALS:
|
||||||
self._remote_offline_serials.pop() # remove the oldest
|
self._remote_offline_serials.pop() # remove the oldest
|
||||||
del self._user_cachemap[user]
|
if user in self._user_cachemap:
|
||||||
|
del self._user_cachemap[user]
|
||||||
else:
|
else:
|
||||||
# Remove the user from remote_offline_serials now that they're
|
# Remove the user from remote_offline_serials now that they're
|
||||||
# no longer offline
|
# no longer offline
|
||||||
|
|
|
@ -369,7 +369,7 @@ class RoomMemberHandler(BaseHandler):
|
||||||
remotedomains.add(member.domain)
|
remotedomains.add(member.domain)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def change_membership(self, event, context, do_auth=True):
|
def change_membership(self, event, context, do_auth=True, is_guest=False):
|
||||||
""" Change the membership status of a user in a room.
|
""" Change the membership status of a user in a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -390,6 +390,20 @@ class RoomMemberHandler(BaseHandler):
|
||||||
# if this HS is not currently in the room, i.e. we have to do the
|
# if this HS is not currently in the room, i.e. we have to do the
|
||||||
# invite/join dance.
|
# invite/join dance.
|
||||||
if event.membership == Membership.JOIN:
|
if event.membership == Membership.JOIN:
|
||||||
|
if is_guest:
|
||||||
|
guest_access = context.current_state.get(
|
||||||
|
(EventTypes.GuestAccess, ""),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
is_guest_access_allowed = (
|
||||||
|
guest_access
|
||||||
|
and guest_access.content
|
||||||
|
and "guest_access" in guest_access.content
|
||||||
|
and guest_access.content["guest_access"] == "can_join"
|
||||||
|
)
|
||||||
|
if not is_guest_access_allowed:
|
||||||
|
raise AuthError(403, "Guest access not allowed")
|
||||||
|
|
||||||
yield self._do_join(event, context, do_auth=do_auth)
|
yield self._do_join(event, context, do_auth=do_auth)
|
||||||
else:
|
else:
|
||||||
if event.membership == Membership.LEAVE:
|
if event.membership == Membership.LEAVE:
|
||||||
|
|
|
@ -175,7 +175,7 @@ class RoomSendEventRestServlet(ClientV1RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, room_id, event_type, txn_id=None):
|
def on_POST(self, request, room_id, event_type, txn_id=None):
|
||||||
user, token_id, _ = yield self.auth.get_user_by_req(request)
|
user, token_id, _ = yield self.auth.get_user_by_req(request, allow_guest=True)
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
||||||
msg_handler = self.handlers.message_handler
|
msg_handler = self.handlers.message_handler
|
||||||
|
@ -220,7 +220,10 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, room_identifier, txn_id=None):
|
def on_POST(self, request, room_identifier, txn_id=None):
|
||||||
user, token_id, _ = yield self.auth.get_user_by_req(request)
|
user, token_id, is_guest = yield self.auth.get_user_by_req(
|
||||||
|
request,
|
||||||
|
allow_guest=True
|
||||||
|
)
|
||||||
|
|
||||||
# the identifier could be a room alias or a room id. Try one then the
|
# the identifier could be a room alias or a room id. Try one then the
|
||||||
# other if it fails to parse, without swallowing other valid
|
# other if it fails to parse, without swallowing other valid
|
||||||
|
@ -242,16 +245,20 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
|
||||||
defer.returnValue((200, ret_dict))
|
defer.returnValue((200, ret_dict))
|
||||||
else: # room id
|
else: # room id
|
||||||
msg_handler = self.handlers.message_handler
|
msg_handler = self.handlers.message_handler
|
||||||
|
content = {"membership": Membership.JOIN}
|
||||||
|
if is_guest:
|
||||||
|
content["kind"] = "guest"
|
||||||
yield msg_handler.create_and_send_event(
|
yield msg_handler.create_and_send_event(
|
||||||
{
|
{
|
||||||
"type": EventTypes.Member,
|
"type": EventTypes.Member,
|
||||||
"content": {"membership": Membership.JOIN},
|
"content": content,
|
||||||
"room_id": identifier.to_string(),
|
"room_id": identifier.to_string(),
|
||||||
"sender": user.to_string(),
|
"sender": user.to_string(),
|
||||||
"state_key": user.to_string(),
|
"state_key": user.to_string(),
|
||||||
},
|
},
|
||||||
token_id=token_id,
|
token_id=token_id,
|
||||||
txn_id=txn_id,
|
txn_id=txn_id,
|
||||||
|
is_guest=is_guest,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((200, {"room_id": identifier.to_string()}))
|
defer.returnValue((200, {"room_id": identifier.to_string()}))
|
||||||
|
|
Loading…
Reference in New Issue