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

Commit

Permalink
Exchange 3pid invites for m.room.member invites
Browse files Browse the repository at this point in the history
  • Loading branch information
illicitonion committed Nov 5, 2015
1 parent 32fc073 commit 2cebe53
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 180 deletions.
73 changes: 38 additions & 35 deletions synapse/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from synapse.api.errors import AuthError, Codes, SynapseError, EventSizeError
from synapse.types import RoomID, UserID, EventID
from synapse.util.logutils import log_function
from synapse.util import third_party_invites
from unpaddedbase64 import decode_base64

import logging
Expand Down Expand Up @@ -318,6 +317,11 @@ def is_membership_change_allowed(self, event, auth_events):
}
)

if Membership.INVITE == membership and "third_party_invite" in event.content:
if not self._verify_third_party_invite(event, auth_events):
raise AuthError(403, "You are not invited to this room.")
return True

if Membership.JOIN != membership:
if (caller_invited
and Membership.LEAVE == membership
Expand Down Expand Up @@ -361,8 +365,7 @@ def is_membership_change_allowed(self, event, auth_events):
pass
elif join_rule == JoinRules.INVITE:
if not caller_in_room and not caller_invited:
if not self._verify_third_party_invite(event, auth_events):
raise AuthError(403, "You are not invited to this room.")
raise AuthError(403, "You are not invited to this room.")
else:
# TODO (erikj): may_join list
# TODO (erikj): private rooms
Expand Down Expand Up @@ -390,10 +393,10 @@ def is_membership_change_allowed(self, event, auth_events):

def _verify_third_party_invite(self, event, auth_events):
"""
Validates that the join event is authorized by a previous third-party invite.
Validates that the invite event is authorized by a previous third-party invite.
Checks that the public key, and keyserver, match those in the invite,
and that the join event has a signature issued using that public key.
Checks that the public key, and keyserver, match those in the third party invite,
and that the invite event has a signature issued using that public key.
Args:
event: The m.room.member join event being validated.
Expand All @@ -404,35 +407,28 @@ def _verify_third_party_invite(self, event, auth_events):
True if the event fulfills the expectations of a previous third party
invite event.
"""
if not third_party_invites.join_has_third_party_invite(event.content):
if "third_party_invite" not in event.content:
return False
if "signed" not in event.content["third_party_invite"]:
return False
join_third_party_invite = event.content["third_party_invite"]
token = join_third_party_invite["token"]
signed = event.content["third_party_invite"]["signed"]
for key in {"mxid", "token"}:
if key not in signed:
return False

token = signed["token"]

invite_event = auth_events.get(
(EventTypes.ThirdPartyInvite, token,)
)
if not invite_event:
logger.info("Failing 3pid invite because no invite found for token %s", token)
return False

if event.user_id != invite_event.user_id:
return False
try:
public_key = join_third_party_invite["public_key"]
key_validity_url = join_third_party_invite["key_validity_url"]
if invite_event.content["public_key"] != public_key:
logger.info(
"Failing 3pid invite because public key invite: %s != join: %s",
invite_event.content["public_key"],
public_key
)
return False
if invite_event.content["key_validity_url"] != key_validity_url:
logger.info(
"Failing 3pid invite because key_validity_url invite: %s != join: %s",
invite_event.content["key_validity_url"],
key_validity_url
)
return False
signed = join_third_party_invite["signed"]
if signed["mxid"] != event.user_id:
public_key = invite_event.content["public_key"]
if signed["mxid"] != event.state_key:
return False
if signed["token"] != token:
return False
Expand All @@ -445,6 +441,11 @@ def _verify_third_party_invite(self, event, auth_events):
decode_base64(public_key)
)
verify_signed_json(signed, server, verify_key)

# We got the public key from the invite, so we know that the
# correct server signed the signed bundle.
# The caller is responsible for checking that the signing
# server has not revoked that public key.
return True
return False
except (KeyError, SignatureVerifyException,):
Expand Down Expand Up @@ -751,17 +752,19 @@ def compute_auth_events(self, event, current_state):
if e_type == Membership.JOIN:
if member_event and not is_public:
auth_ids.append(member_event.event_id)
if third_party_invites.join_has_third_party_invite(event.content):
else:
if member_event:
auth_ids.append(member_event.event_id)

if e_type == Membership.INVITE:
if "third_party_invite" in event.content:
key = (
EventTypes.ThirdPartyInvite,
event.content["third_party_invite"]["token"]
)
invite = current_state.get(key)
if invite:
auth_ids.append(invite.event_id)
else:
if member_event:
auth_ids.append(member_event.event_id)
third_party_invite = current_state.get(key)
if third_party_invite:
auth_ids.append(third_party_invite.event_id)
elif member_event:
if member_event.content["membership"] == Membership.JOIN:
auth_ids.append(member_event.event_id)
Expand Down
33 changes: 25 additions & 8 deletions synapse/federation/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from synapse.util import unwrapFirstError
from synapse.util.caches.expiringcache import ExpiringCache
from synapse.util.logutils import log_function
from synapse.util import third_party_invites
from synapse.events import FrozenEvent
import synapse.metrics

Expand Down Expand Up @@ -358,7 +357,7 @@ def get_event_auth(self, destination, room_id, event_id):
defer.returnValue(signed_auth)

@defer.inlineCallbacks
def make_membership_event(self, destinations, room_id, user_id, membership, content):
def make_membership_event(self, destinations, room_id, user_id, membership):
"""
Creates an m.room.member event, with context, without participating in the room.
Expand Down Expand Up @@ -390,14 +389,9 @@ def make_membership_event(self, destinations, room_id, user_id, membership, cont
if destination == self.server_name:
continue

args = {}
if third_party_invites.join_has_third_party_invite(content):
args = third_party_invites.extract_join_keys(
content["third_party_invite"]
)
try:
ret = yield self.transport_layer.make_membership_event(
destination, room_id, user_id, membership, args
destination, room_id, user_id, membership
)

pdu_dict = ret["event"]
Expand Down Expand Up @@ -704,3 +698,26 @@ def event_from_pdu_json(self, pdu_json, outlier=False):
event.internal_metadata.outlier = outlier

return event

@defer.inlineCallbacks
def forward_third_party_invite(self, destinations, room_id, event_dict):
for destination in destinations:
if destination == self.server_name:
continue

try:
yield self.transport_layer.exchange_third_party_invite(
destination=destination,
room_id=room_id,
event_dict=event_dict,
)
defer.returnValue(None)
except CodeMessageException:
raise
except Exception as e:
logger.exception(
"Failed to send_third_party_invite via %s: %s",
destination, e.message
)

raise RuntimeError("Failed to send to any server.")
31 changes: 15 additions & 16 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@
from synapse.events import FrozenEvent
import synapse.metrics

from synapse.api.errors import FederationError, SynapseError, Codes
from synapse.api.errors import FederationError, SynapseError

from synapse.crypto.event_signing import compute_event_signature

from synapse.util import third_party_invites

import simplejson as json
import logging

Expand Down Expand Up @@ -230,19 +228,8 @@ def on_query_request(self, query_type, args):
)

@defer.inlineCallbacks
def on_make_join_request(self, room_id, user_id, query):
threepid_details = {}
if third_party_invites.has_join_keys(query):
for k in third_party_invites.JOIN_KEYS:
if not isinstance(query[k], list) or len(query[k]) != 1:
raise FederationError(
"FATAL",
Codes.MISSING_PARAM,
"key %s value %s" % (k, query[k],),
None
)
threepid_details[k] = query[k][0]
pdu = yield self.handler.on_make_join_request(room_id, user_id, threepid_details)
def on_make_join_request(self, room_id, user_id):
pdu = yield self.handler.on_make_join_request(room_id, user_id)
time_now = self._clock.time_msec()
defer.returnValue({"event": pdu.get_pdu_json(time_now)})

Expand Down Expand Up @@ -556,3 +543,15 @@ def event_from_pdu_json(self, pdu_json, outlier=False):
event.internal_metadata.outlier = outlier

return event

@defer.inlineCallbacks
def exchange_third_party_invite(self, invite):
ret = yield self.handler.exchange_third_party_invite(invite)
defer.returnValue(ret)

@defer.inlineCallbacks
def on_exchange_third_party_invite_request(self, origin, room_id, event_dict):
ret = yield self.handler.on_exchange_third_party_invite_request(
origin, room_id, event_dict
)
defer.returnValue(ret)
16 changes: 14 additions & 2 deletions synapse/federation/transport/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def make_query(self, destination, query_type, args, retry_on_dns_fail):

@defer.inlineCallbacks
@log_function
def make_membership_event(self, destination, room_id, user_id, membership, args={}):
def make_membership_event(self, destination, room_id, user_id, membership):
valid_memberships = {Membership.JOIN, Membership.LEAVE}
if membership not in valid_memberships:
raise RuntimeError(
Expand All @@ -173,7 +173,6 @@ def make_membership_event(self, destination, room_id, user_id, membership, args=
content = yield self.client.get_json(
destination=destination,
path=path,
args=args,
retry_on_dns_fail=True,
)

Expand Down Expand Up @@ -218,6 +217,19 @@ def send_invite(self, destination, room_id, event_id, content):

defer.returnValue(response)

@defer.inlineCallbacks
@log_function
def exchange_third_party_invite(self, destination, room_id, event_dict):
path = PREFIX + "/exchange_third_party_invite/%s" % (room_id,)

response = yield self.client.put_json(
destination=destination,
path=path,
data=event_dict,
)

defer.returnValue(response)

@defer.inlineCallbacks
@log_function
def get_event_auth(self, destination, room_id, event_id):
Expand Down
39 changes: 38 additions & 1 deletion synapse/federation/transport/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ class FederationMakeJoinServlet(BaseFederationServlet):

@defer.inlineCallbacks
def on_GET(self, origin, content, query, context, user_id):
content = yield self.handler.on_make_join_request(context, user_id, query)
content = yield self.handler.on_make_join_request(context, user_id)
defer.returnValue((200, content))


Expand Down Expand Up @@ -343,6 +343,17 @@ def on_PUT(self, origin, content, query, context, event_id):
defer.returnValue((200, content))


class FederationThirdPartyInviteExchangeServlet(BaseFederationServlet):
PATH = "/exchange_third_party_invite/([^/]*)"

@defer.inlineCallbacks
def on_PUT(self, origin, content, query, room_id):
content = yield self.handler.on_exchange_third_party_invite_request(
origin, room_id, content
)
defer.returnValue((200, content))


class FederationClientKeysQueryServlet(BaseFederationServlet):
PATH = "/user/keys/query"

Expand Down Expand Up @@ -396,6 +407,30 @@ def on_POST(self, origin, content, query, room_id):
defer.returnValue((200, content))


class On3pidBindServlet(BaseFederationServlet):
PATH = "/3pid/onbind"

@defer.inlineCallbacks
def on_POST(self, request):
content_bytes = request.content.read()
content = json.loads(content_bytes)
if "invites" in content:
last_exception = None
for invite in content["invites"]:
try:
yield self.handler.exchange_third_party_invite(invite)
except Exception as e:
last_exception = e
if last_exception:
raise last_exception
defer.returnValue((200, {}))

# Avoid doing remote HS authorization checks which are done by default by
# BaseFederationServlet.
def _wrap(self, code):
return code


SERVLET_CLASSES = (
FederationPullServlet,
FederationEventServlet,
Expand All @@ -413,4 +448,6 @@ def on_POST(self, origin, content, query, room_id):
FederationEventAuthServlet,
FederationClientKeysQueryServlet,
FederationClientKeysClaimServlet,
FederationThirdPartyInviteExchangeServlet,
On3pidBindServlet,
)
11 changes: 0 additions & 11 deletions synapse/handlers/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from synapse.types import UserID, RoomAlias

from synapse.util.logcontext import PreserveLoggingContext
from synapse.util import third_party_invites

import logging

Expand Down Expand Up @@ -192,16 +191,6 @@ def handle_new_client_event(self, event, context, extra_destinations=[],
)
)

if (
event.type == EventTypes.Member and
event.content["membership"] == Membership.JOIN and
third_party_invites.join_has_third_party_invite(event.content)
):
yield third_party_invites.check_key_valid(
self.hs.get_simple_http_client(),
event
)

federation_handler = self.hs.get_handlers().federation_handler

if event.type == EventTypes.Member:
Expand Down
Loading

0 comments on commit 2cebe53

Please sign in to comment.