0
0
Fork 1
mirror of https://mau.dev/maunium/synapse.git synced 2024-12-15 11:03:51 +01:00

Merge pull request #584 from matrix-org/daniel/ephemeralthreepids

Allow third_party_signed to be specified on /join
This commit is contained in:
Daniel Wagner-Hall 2016-02-23 15:11:35 +00:00
commit 28ad246bb4
7 changed files with 196 additions and 54 deletions

View file

@ -434,16 +434,19 @@ class Auth(object):
if event.user_id != invite_event.user_id: if event.user_id != invite_event.user_id:
return False return False
try:
public_key = invite_event.content["public_key"]
if signed["mxid"] != event.state_key: if signed["mxid"] != event.state_key:
return False return False
if signed["token"] != token: if signed["token"] != token:
return False return False
for public_key_object in self.get_public_keys(invite_event):
public_key = public_key_object["public_key"]
try:
for server, signature_block in signed["signatures"].items(): for server, signature_block in signed["signatures"].items():
for key_name, encoded_signature in signature_block.items(): for key_name, encoded_signature in signature_block.items():
if not key_name.startswith("ed25519:"): if not key_name.startswith("ed25519:"):
return False continue
verify_key = decode_verify_key_bytes( verify_key = decode_verify_key_bytes(
key_name, key_name,
decode_base64(public_key) decode_base64(public_key)
@ -455,10 +458,22 @@ class Auth(object):
# The caller is responsible for checking that the signing # The caller is responsible for checking that the signing
# server has not revoked that public key. # server has not revoked that public key.
return True return True
return False
except (KeyError, SignatureVerifyException,): except (KeyError, SignatureVerifyException,):
continue
return False return False
def get_public_keys(self, invite_event):
public_keys = []
if "public_key" in invite_event.content:
o = {
"public_key": invite_event.content["public_key"],
}
if "key_validity_url" in invite_event.content:
o["key_validity_url"] = invite_event.content["key_validity_url"]
public_keys.append(o)
public_keys.extend(invite_event.content.get("public_keys", []))
return public_keys
def _get_power_level_event(self, auth_events): def _get_power_level_event(self, auth_events):
key = (EventTypes.PowerLevels, "", ) key = (EventTypes.PowerLevels, "", )
return auth_events.get(key) return auth_events.get(key)

View file

@ -543,8 +543,19 @@ class FederationServer(FederationBase):
return event return event
@defer.inlineCallbacks @defer.inlineCallbacks
def exchange_third_party_invite(self, invite): def exchange_third_party_invite(
ret = yield self.handler.exchange_third_party_invite(invite) self,
sender_user_id,
target_user_id,
room_id,
signed,
):
ret = yield self.handler.exchange_third_party_invite(
sender_user_id,
target_user_id,
room_id,
signed,
)
defer.returnValue(ret) defer.returnValue(ret)
@defer.inlineCallbacks @defer.inlineCallbacks

View file

@ -425,7 +425,17 @@ class On3pidBindServlet(BaseFederationServlet):
last_exception = None last_exception = None
for invite in content["invites"]: for invite in content["invites"]:
try: try:
yield self.handler.exchange_third_party_invite(invite) if "signed" not in invite or "token" not in invite["signed"]:
message = ("Rejecting received notification of third-"
"party invite without signed: %s" % (invite,))
logger.info(message)
raise SynapseError(400, message)
yield self.handler.exchange_third_party_invite(
invite["sender"],
invite["mxid"],
invite["room_id"],
invite["signed"],
)
except Exception as e: except Exception as e:
last_exception = e last_exception = e
if last_exception: if last_exception:

View file

@ -14,6 +14,9 @@
# limitations under the License. # limitations under the License.
"""Contains handlers for federation events.""" """Contains handlers for federation events."""
from signedjson.key import decode_verify_key_bytes
from signedjson.sign import verify_signed_json
from unpaddedbase64 import decode_base64
from ._base import BaseHandler from ._base import BaseHandler
@ -1620,19 +1623,15 @@ class FederationHandler(BaseHandler):
@defer.inlineCallbacks @defer.inlineCallbacks
@log_function @log_function
def exchange_third_party_invite(self, invite): def exchange_third_party_invite(
sender = invite["sender"] self,
room_id = invite["room_id"] sender_user_id,
target_user_id,
if "signed" not in invite or "token" not in invite["signed"]: room_id,
logger.info( signed,
"Discarding received notification of third party invite " ):
"without signed: %s" % (invite,)
)
return
third_party_invite = { third_party_invite = {
"signed": invite["signed"], "signed": signed,
} }
event_dict = { event_dict = {
@ -1642,8 +1641,8 @@ class FederationHandler(BaseHandler):
"third_party_invite": third_party_invite, "third_party_invite": third_party_invite,
}, },
"room_id": room_id, "room_id": room_id,
"sender": sender, "sender": sender_user_id,
"state_key": invite["mxid"], "state_key": target_user_id,
} }
if (yield self.auth.check_host_in_room(room_id, self.hs.hostname)): if (yield self.auth.check_host_in_room(room_id, self.hs.hostname)):
@ -1656,11 +1655,11 @@ class FederationHandler(BaseHandler):
) )
self.auth.check(event, context.current_state) self.auth.check(event, context.current_state)
yield self._validate_keyserver(event, auth_events=context.current_state) yield self._check_signature(event, auth_events=context.current_state)
member_handler = self.hs.get_handlers().room_member_handler member_handler = self.hs.get_handlers().room_member_handler
yield member_handler.send_membership_event(event, context, from_client=False) yield member_handler.send_membership_event(event, context, from_client=False)
else: else:
destinations = set([x.split(":", 1)[-1] for x in (sender, room_id)]) destinations = set(x.split(":", 1)[-1] for x in (sender_user_id, room_id))
yield self.replication_layer.forward_third_party_invite( yield self.replication_layer.forward_third_party_invite(
destinations, destinations,
room_id, room_id,
@ -1681,7 +1680,7 @@ class FederationHandler(BaseHandler):
) )
self.auth.check(event, auth_events=context.current_state) self.auth.check(event, auth_events=context.current_state)
yield self._validate_keyserver(event, auth_events=context.current_state) yield self._check_signature(event, auth_events=context.current_state)
returned_invite = yield self.send_invite(origin, event) returned_invite = yield self.send_invite(origin, event)
# TODO: Make sure the signatures actually are correct. # TODO: Make sure the signatures actually are correct.
@ -1711,17 +1710,69 @@ class FederationHandler(BaseHandler):
defer.returnValue((event, context)) defer.returnValue((event, context))
@defer.inlineCallbacks @defer.inlineCallbacks
def _validate_keyserver(self, event, auth_events): def _check_signature(self, event, auth_events):
token = event.content["third_party_invite"]["signed"]["token"] """
Checks that the signature in the event is consistent with its invite.
:param event (Event): The m.room.member event to check
:param auth_events (dict<(event type, state_key), event>)
:raises
AuthError if signature didn't match any keys, or key has been
revoked,
SynapseError if a transient error meant a key couldn't be checked
for revocation.
"""
signed = event.content["third_party_invite"]["signed"]
token = signed["token"]
invite_event = auth_events.get( invite_event = auth_events.get(
(EventTypes.ThirdPartyInvite, token,) (EventTypes.ThirdPartyInvite, token,)
) )
if not invite_event:
raise AuthError(403, "Could not find invite")
last_exception = None
for public_key_object in self.hs.get_auth().get_public_keys(invite_event):
try:
for server, signature_block in signed["signatures"].items():
for key_name, encoded_signature in signature_block.items():
if not key_name.startswith("ed25519:"):
continue
public_key = public_key_object["public_key"]
verify_key = decode_verify_key_bytes(
key_name,
decode_base64(public_key)
)
verify_signed_json(signed, server, verify_key)
if "key_validity_url" in public_key_object:
yield self._check_key_revocation(
public_key,
public_key_object["key_validity_url"]
)
return
except Exception as e:
last_exception = e
raise last_exception
@defer.inlineCallbacks
def _check_key_revocation(self, public_key, url):
"""
Checks whether public_key has been revoked.
:param public_key (str): base-64 encoded public key.
:param url (str): Key revocation URL.
:raises
AuthError if they key has been revoked.
SynapseError if a transient error meant a key couldn't be checked
for revocation.
"""
try: try:
response = yield self.hs.get_simple_http_client().get_json( response = yield self.hs.get_simple_http_client().get_json(
invite_event.content["key_validity_url"], url,
{"public_key": invite_event.content["public_key"]} {"public_key": public_key}
) )
except Exception: except Exception:
raise SynapseError( raise SynapseError(

View file

@ -398,6 +398,7 @@ class RoomMemberHandler(BaseHandler):
action, action,
txn_id=None, txn_id=None,
remote_room_hosts=None, remote_room_hosts=None,
third_party_signed=None,
ratelimit=True, ratelimit=True,
): ):
effective_membership_state = action effective_membership_state = action
@ -406,6 +407,15 @@ class RoomMemberHandler(BaseHandler):
elif action == "forget": elif action == "forget":
effective_membership_state = "leave" effective_membership_state = "leave"
if third_party_signed is not None:
replication = self.hs.get_replication_layer()
yield replication.exchange_third_party_invite(
third_party_signed["sender"],
target.to_string(),
room_id,
third_party_signed,
)
msg_handler = self.hs.get_handlers().message_handler msg_handler = self.hs.get_handlers().message_handler
content = {"membership": effective_membership_state} content = {"membership": effective_membership_state}
@ -759,7 +769,7 @@ class RoomMemberHandler(BaseHandler):
if room_avatar_event: if room_avatar_event:
room_avatar_url = room_avatar_event.content.get("url", "") room_avatar_url = room_avatar_event.content.get("url", "")
token, public_key, key_validity_url, display_name = ( token, public_keys, fallback_public_key, display_name = (
yield self._ask_id_server_for_third_party_invite( yield self._ask_id_server_for_third_party_invite(
id_server=id_server, id_server=id_server,
medium=medium, medium=medium,
@ -774,14 +784,18 @@ class RoomMemberHandler(BaseHandler):
inviter_avatar_url=inviter_avatar_url inviter_avatar_url=inviter_avatar_url
) )
) )
msg_handler = self.hs.get_handlers().message_handler msg_handler = self.hs.get_handlers().message_handler
yield msg_handler.create_and_send_nonmember_event( yield msg_handler.create_and_send_nonmember_event(
{ {
"type": EventTypes.ThirdPartyInvite, "type": EventTypes.ThirdPartyInvite,
"content": { "content": {
"display_name": display_name, "display_name": display_name,
"key_validity_url": key_validity_url, "public_keys": public_keys,
"public_key": public_key,
# For backwards compatibility:
"key_validity_url": fallback_public_key["key_validity_url"],
"public_key": fallback_public_key["public_key"],
}, },
"room_id": room_id, "room_id": room_id,
"sender": user.to_string(), "sender": user.to_string(),
@ -806,6 +820,34 @@ class RoomMemberHandler(BaseHandler):
inviter_display_name, inviter_display_name,
inviter_avatar_url inviter_avatar_url
): ):
"""
Asks an identity server for a third party invite.
:param id_server (str): hostname + optional port for the identity server.
:param medium (str): The literal string "email".
:param address (str): The third party address being invited.
:param room_id (str): The ID of the room to which the user is invited.
:param inviter_user_id (str): The user ID of the inviter.
:param room_alias (str): An alias for the room, for cosmetic
notifications.
:param room_avatar_url (str): The URL of the room's avatar, for cosmetic
notifications.
:param room_join_rules (str): The join rules of the email
(e.g. "public").
:param room_name (str): The m.room.name of the room.
:param inviter_display_name (str): The current display name of the
inviter.
:param inviter_avatar_url (str): The URL of the inviter's avatar.
:return: A deferred tuple containing:
token (str): The token which must be signed to prove authenticity.
public_keys ([{"public_key": str, "key_validity_url": str}]):
public_key is a base64-encoded ed25519 public key.
fallback_public_key: One element from public_keys.
display_name (str): A user-friendly name to represent the invited
user.
"""
is_url = "%s%s/_matrix/identity/api/v1/store-invite" % ( is_url = "%s%s/_matrix/identity/api/v1/store-invite" % (
id_server_scheme, id_server, id_server_scheme, id_server,
) )
@ -826,12 +868,21 @@ class RoomMemberHandler(BaseHandler):
) )
# TODO: Check for success # TODO: Check for success
token = data["token"] token = data["token"]
public_key = data["public_key"] public_keys = data.get("public_keys", [])
display_name = data["display_name"] if "public_key" in data:
key_validity_url = "%s%s/_matrix/identity/api/v1/pubkey/isvalid" % ( fallback_public_key = {
"public_key": data["public_key"],
"key_validity_url": "%s%s/_matrix/identity/api/v1/pubkey/isvalid" % (
id_server_scheme, id_server, id_server_scheme, id_server,
) ),
defer.returnValue((token, public_key, key_validity_url, display_name)) }
else:
fallback_public_key = public_keys[0]
if not public_keys:
public_keys.append(fallback_public_key)
display_name = data["display_name"]
defer.returnValue((token, public_keys, fallback_public_key, display_name))
def forget(self, user, room_id): def forget(self, user, room_id):
return self.store.forget(user.to_string(), room_id) return self.store.forget(user.to_string(), room_id)

View file

@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
REQUIREMENTS = { REQUIREMENTS = {
"frozendict>=0.4": ["frozendict"], "frozendict>=0.4": ["frozendict"],
"unpaddedbase64>=1.0.1": ["unpaddedbase64>=1.0.1"], "unpaddedbase64>=1.1.0": ["unpaddedbase64>=1.1.0"],
"canonicaljson>=1.0.0": ["canonicaljson>=1.0.0"], "canonicaljson>=1.0.0": ["canonicaljson>=1.0.0"],
"signedjson>=1.0.0": ["signedjson>=1.0.0"], "signedjson>=1.0.0": ["signedjson>=1.0.0"],
"pynacl==0.3.0": ["nacl==0.3.0", "nacl.bindings"], "pynacl==0.3.0": ["nacl==0.3.0", "nacl.bindings"],

View file

@ -228,6 +228,8 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
allow_guest=True, allow_guest=True,
) )
content = _parse_json(request)
if RoomID.is_valid(room_identifier): if RoomID.is_valid(room_identifier):
room_id = room_identifier room_id = room_identifier
remote_room_hosts = None remote_room_hosts = None
@ -248,6 +250,7 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
action="join", action="join",
txn_id=txn_id, txn_id=txn_id,
remote_room_hosts=remote_room_hosts, remote_room_hosts=remote_room_hosts,
third_party_signed=content.get("third_party_signed", None),
) )
defer.returnValue((200, {"room_id": room_id})) defer.returnValue((200, {"room_id": room_id}))
@ -451,6 +454,7 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
room_id=room_id, room_id=room_id,
action=membership_action, action=membership_action,
txn_id=txn_id, txn_id=txn_id,
third_party_signed=content.get("third_party_signed", None),
) )
defer.returnValue((200, {})) defer.returnValue((200, {}))