diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index 3364a5de14..3f0cde56f0 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -22,11 +22,16 @@ from synapse.types import UserID, RoomAlias, RoomID
from synapse.api.constants import (
EventTypes, Membership, JoinRules, RoomCreationPreset,
)
-from synapse.api.errors import StoreError, SynapseError
+from synapse.api.errors import AuthError, StoreError, SynapseError
from synapse.util import stringutils, unwrapFirstError
from synapse.util.async import run_on_reactor
+from signedjson.sign import verify_signed_json
+from signedjson.key import decode_verify_key_bytes
+
from collections import OrderedDict
+from unpaddedbase64 import decode_base64
+
import logging
import string
@@ -483,6 +488,13 @@ class RoomMemberHandler(BaseHandler):
should_do_dance = not self.hs.is_mine(inviter)
room_hosts = [inviter.domain]
+ elif "third_party_invite" in event.content:
+ if "sender" in event.content["third_party_invite"]:
+ inviter = UserID.from_string(
+ event.content["third_party_invite"]["sender"]
+ )
+ should_do_dance = not self.hs.is_mine(inviter)
+ room_hosts = [inviter.domain]
else:
# return the same error as join_room_alias does
raise SynapseError(404, "No known servers")
@@ -540,6 +552,160 @@ class RoomMemberHandler(BaseHandler):
suppress_auth=(not do_auth),
)
+ @defer.inlineCallbacks
+ def do_3pid_invite(
+ self,
+ room_id,
+ inviter,
+ medium,
+ address,
+ id_server,
+ display_name,
+ token_id,
+ txn_id
+ ):
+ invitee = yield self._lookup_3pid(
+ id_server, medium, address
+ )
+
+ if invitee:
+ # make sure it looks like a user ID; it'll throw if it's invalid.
+ UserID.from_string(invitee)
+ yield self.hs.get_handlers().message_handler.create_and_send_event(
+ {
+ "type": EventTypes.Member,
+ "content": {
+ "membership": unicode("invite")
+ },
+ "room_id": room_id,
+ "sender": inviter.to_string(),
+ "state_key": invitee,
+ },
+ token_id=token_id,
+ txn_id=txn_id,
+ )
+ else:
+ yield self._make_and_store_3pid_invite(
+ id_server,
+ display_name,
+ medium,
+ address,
+ room_id,
+ inviter,
+ token_id,
+ txn_id=txn_id
+ )
+
+ @defer.inlineCallbacks
+ def _lookup_3pid(self, id_server, medium, address):
+ """Looks up a 3pid in the passed identity server.
+
+ Args:
+ id_server (str): The server name (including port, if required)
+ of the identity server to use.
+ medium (str): The type of the third party identifier (e.g. "email").
+ address (str): The third party identifier (e.g. "foo@example.com").
+
+ Returns:
+ (str) the matrix ID of the 3pid, or None if it is not recognized.
+ """
+ try:
+ data = yield self.hs.get_simple_http_client().get_json(
+ "https://%s/_matrix/identity/api/v1/lookup" % (id_server,),
+ {
+ "medium": medium,
+ "address": address,
+ }
+ )
+
+ if "mxid" in data:
+ if "signatures" not in data:
+ raise AuthError(401, "No signatures on 3pid binding")
+ self.verify_any_signature(data, id_server)
+ defer.returnValue(data["mxid"])
+
+ except IOError as e:
+ logger.warn("Error from identity server lookup: %s" % (e,))
+ defer.returnValue(None)
+
+ @defer.inlineCallbacks
+ def verify_any_signature(self, data, server_hostname):
+ if server_hostname not in data["signatures"]:
+ raise AuthError(401, "No signature from server %s" % (server_hostname,))
+ for key_name, signature in data["signatures"][server_hostname].items():
+ key_data = yield self.hs.get_simple_http_client().get_json(
+ "https://%s/_matrix/identity/api/v1/pubkey/%s" %
+ (server_hostname, key_name,),
+ )
+ if "public_key" not in key_data:
+ raise AuthError(401, "No public key named %s from %s" %
+ (key_name, server_hostname,))
+ verify_signed_json(
+ data,
+ server_hostname,
+ decode_verify_key_bytes(key_name, decode_base64(key_data["public_key"]))
+ )
+ return
+
+ @defer.inlineCallbacks
+ def _make_and_store_3pid_invite(
+ self,
+ id_server,
+ display_name,
+ medium,
+ address,
+ room_id,
+ user,
+ token_id,
+ txn_id
+ ):
+ token, public_key, key_validity_url = (
+ yield self._ask_id_server_for_third_party_invite(
+ id_server,
+ medium,
+ address,
+ room_id,
+ user.to_string()
+ )
+ )
+ msg_handler = self.hs.get_handlers().message_handler
+ yield msg_handler.create_and_send_event(
+ {
+ "type": EventTypes.ThirdPartyInvite,
+ "content": {
+ "display_name": display_name,
+ "key_validity_url": key_validity_url,
+ "public_key": public_key,
+ },
+ "room_id": room_id,
+ "sender": user.to_string(),
+ "state_key": token,
+ },
+ token_id=token_id,
+ txn_id=txn_id,
+ )
+
+ @defer.inlineCallbacks
+ def _ask_id_server_for_third_party_invite(
+ self, id_server, medium, address, room_id, sender):
+ is_url = "https://%s/_matrix/identity/api/v1/store-invite" % (id_server,)
+ data = yield self.hs.get_simple_http_client().post_urlencoded_get_json(
+ is_url,
+ {
+ "medium": medium,
+ "address": address,
+ "room_id": room_id,
+ "sender": sender,
+ }
+ )
+ # TODO: Check for success
+ token = data["token"]
+ public_key = data["public_key"]
+ key_validity_url = "https://%s/_matrix/identity/api/v1/pubkey/isvalid" % (
+ id_server,
+ )
+ defer.returnValue((token, public_key, key_validity_url))
+
class RoomListHandler(BaseHandler):
|