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

Commit 84ddcd7

Browse files
authored
Drop support for calling /_matrix/client/v3/rooms/{roomId}/invite without an id_access_token (#13241)
Fixes #13206 Signed-off-by: Jacek Kusnierz [email protected]
1 parent 42b11d5 commit 84ddcd7

File tree

9 files changed

+81
-137
lines changed

9 files changed

+81
-137
lines changed

changelog.d/13241.removal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Drop support for calling `/_matrix/client/v3/rooms/{roomId}/invite` without an `id_access_token`, which was not permitted by the spec. Contributed by @Vetchu.

synapse/handlers/identity.py

Lines changed: 23 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,7 @@ async def proxy_msisdn_submit_token(
538538
raise SynapseError(400, "Error contacting the identity server")
539539

540540
async def lookup_3pid(
541-
self,
542-
id_server: str,
543-
medium: str,
544-
address: str,
545-
id_access_token: Optional[str] = None,
541+
self, id_server: str, medium: str, address: str, id_access_token: str
546542
) -> Optional[str]:
547543
"""Looks up a 3pid in the passed identity server.
548544
@@ -557,60 +553,15 @@ async def lookup_3pid(
557553
Returns:
558554
the matrix ID of the 3pid, or None if it is not recognized.
559555
"""
560-
if id_access_token is not None:
561-
try:
562-
results = await self._lookup_3pid_v2(
563-
id_server, id_access_token, medium, address
564-
)
565-
return results
566-
567-
except Exception as e:
568-
# Catch HttpResponseExcept for a non-200 response code
569-
# Check if this identity server does not know about v2 lookups
570-
if isinstance(e, HttpResponseException) and e.code == 404:
571-
# This is an old identity server that does not yet support v2 lookups
572-
logger.warning(
573-
"Attempted v2 lookup on v1 identity server %s. Falling "
574-
"back to v1",
575-
id_server,
576-
)
577-
else:
578-
logger.warning("Error when looking up hashing details: %s", e)
579-
return None
580-
581-
return await self._lookup_3pid_v1(id_server, medium, address)
582-
583-
async def _lookup_3pid_v1(
584-
self, id_server: str, medium: str, address: str
585-
) -> Optional[str]:
586-
"""Looks up a 3pid in the passed identity server using v1 lookup.
587556

588-
Args:
589-
id_server: The server name (including port, if required)
590-
of the identity server to use.
591-
medium: The type of the third party identifier (e.g. "email").
592-
address: The third party identifier (e.g. "[email protected]").
593-
594-
Returns:
595-
the matrix ID of the 3pid, or None if it is not recognized.
596-
"""
597557
try:
598-
data = await self.blacklisting_http_client.get_json(
599-
"%s%s/_matrix/identity/api/v1/lookup" % (id_server_scheme, id_server),
600-
{"medium": medium, "address": address},
558+
results = await self._lookup_3pid_v2(
559+
id_server, id_access_token, medium, address
601560
)
602-
603-
if "mxid" in data:
604-
# note: we used to verify the identity server's signature here, but no longer
605-
# require or validate it. See the following for context:
606-
# https://github.com/matrix-org/synapse/issues/5253#issuecomment-666246950
607-
return data["mxid"]
608-
except RequestTimedOutError:
609-
raise SynapseError(500, "Timed out contacting identity server")
610-
except OSError as e:
611-
logger.warning("Error from v1 identity server lookup: %s" % (e,))
612-
613-
return None
561+
return results
562+
except Exception as e:
563+
logger.warning("Error when looking up hashing details: %s", e)
564+
return None
614565

615566
async def _lookup_3pid_v2(
616567
self, id_server: str, id_access_token: str, medium: str, address: str
@@ -739,7 +690,7 @@ async def ask_id_server_for_third_party_invite(
739690
room_type: Optional[str],
740691
inviter_display_name: str,
741692
inviter_avatar_url: str,
742-
id_access_token: Optional[str] = None,
693+
id_access_token: str,
743694
) -> Tuple[str, List[Dict[str, str]], Dict[str, str], str]:
744695
"""
745696
Asks an identity server for a third party invite.
@@ -760,7 +711,7 @@ async def ask_id_server_for_third_party_invite(
760711
inviter_display_name: The current display name of the
761712
inviter.
762713
inviter_avatar_url: The URL of the inviter's avatar.
763-
id_access_token (str|None): The access token to authenticate to the identity
714+
id_access_token (str): The access token to authenticate to the identity
764715
server with
765716
766717
Returns:
@@ -792,71 +743,24 @@ async def ask_id_server_for_third_party_invite(
792743
invite_config["org.matrix.web_client_location"] = self._web_client_location
793744

794745
# Add the identity service access token to the JSON body and use the v2
795-
# Identity Service endpoints if id_access_token is present
746+
# Identity Service endpoints
796747
data = None
797-
base_url = "%s%s/_matrix/identity" % (id_server_scheme, id_server)
798748

799-
if id_access_token:
800-
key_validity_url = "%s%s/_matrix/identity/v2/pubkey/isvalid" % (
801-
id_server_scheme,
802-
id_server,
803-
)
749+
key_validity_url = "%s%s/_matrix/identity/v2/pubkey/isvalid" % (
750+
id_server_scheme,
751+
id_server,
752+
)
804753

805-
# Attempt a v2 lookup
806-
url = base_url + "/v2/store-invite"
807-
try:
808-
data = await self.blacklisting_http_client.post_json_get_json(
809-
url,
810-
invite_config,
811-
{"Authorization": create_id_access_token_header(id_access_token)},
812-
)
813-
except RequestTimedOutError:
814-
raise SynapseError(500, "Timed out contacting identity server")
815-
except HttpResponseException as e:
816-
if e.code != 404:
817-
logger.info("Failed to POST %s with JSON: %s", url, e)
818-
raise e
819-
820-
if data is None:
821-
key_validity_url = "%s%s/_matrix/identity/api/v1/pubkey/isvalid" % (
822-
id_server_scheme,
823-
id_server,
754+
url = "%s%s/_matrix/identity/v2/store-invite" % (id_server_scheme, id_server)
755+
try:
756+
data = await self.blacklisting_http_client.post_json_get_json(
757+
url,
758+
invite_config,
759+
{"Authorization": create_id_access_token_header(id_access_token)},
824760
)
825-
url = base_url + "/api/v1/store-invite"
826-
827-
try:
828-
data = await self.blacklisting_http_client.post_json_get_json(
829-
url, invite_config
830-
)
831-
except RequestTimedOutError:
832-
raise SynapseError(500, "Timed out contacting identity server")
833-
except HttpResponseException as e:
834-
logger.warning(
835-
"Error trying to call /store-invite on %s%s: %s",
836-
id_server_scheme,
837-
id_server,
838-
e,
839-
)
840-
841-
if data is None:
842-
# Some identity servers may only support application/x-www-form-urlencoded
843-
# types. This is especially true with old instances of Sydent, see
844-
# https://github.com/matrix-org/sydent/pull/170
845-
try:
846-
data = await self.blacklisting_http_client.post_urlencoded_get_json(
847-
url, invite_config
848-
)
849-
except HttpResponseException as e:
850-
logger.warning(
851-
"Error calling /store-invite on %s%s with fallback "
852-
"encoding: %s",
853-
id_server_scheme,
854-
id_server,
855-
e,
856-
)
857-
raise e
858-
859-
# TODO: Check for success
761+
except RequestTimedOutError:
762+
raise SynapseError(500, "Timed out contacting identity server")
763+
860764
token = data["token"]
861765
public_keys = data.get("public_keys", [])
862766
if "public_key" in data:

synapse/handlers/room.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import random
2020
import string
2121
from collections import OrderedDict
22+
from http import HTTPStatus
2223
from typing import (
2324
TYPE_CHECKING,
2425
Any,
@@ -704,8 +705,8 @@ async def create_room(
704705
was, requested, `room_alias`. Secondly, the stream_id of the
705706
last persisted event.
706707
Raises:
707-
SynapseError if the room ID couldn't be stored, or something went
708-
horribly wrong.
708+
SynapseError if the room ID couldn't be stored, 3pid invitation config
709+
validation failed, or something went horribly wrong.
709710
ResourceLimitError if server is blocked to some resource being
710711
exceeded
711712
"""
@@ -731,6 +732,19 @@ async def create_room(
731732
invite_3pid_list = config.get("invite_3pid", [])
732733
invite_list = config.get("invite", [])
733734

735+
# validate each entry for correctness
736+
for invite_3pid in invite_3pid_list:
737+
if not all(
738+
key in invite_3pid
739+
for key in ("medium", "address", "id_server", "id_access_token")
740+
):
741+
raise SynapseError(
742+
HTTPStatus.BAD_REQUEST,
743+
"all of `medium`, `address`, `id_server` and `id_access_token` "
744+
"are required when making a 3pid invite",
745+
Codes.MISSING_PARAM,
746+
)
747+
734748
if not is_requester_admin:
735749
spam_check = await self.spam_checker.user_may_create_room(user_id)
736750
if spam_check != NOT_SPAM:
@@ -978,7 +992,7 @@ async def create_room(
978992

979993
for invite_3pid in invite_3pid_list:
980994
id_server = invite_3pid["id_server"]
981-
id_access_token = invite_3pid.get("id_access_token") # optional
995+
id_access_token = invite_3pid["id_access_token"]
982996
address = invite_3pid["address"]
983997
medium = invite_3pid["medium"]
984998
# Note that do_3pid_invite can raise a ShadowBanError, but this was

synapse/handlers/room_member.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,7 @@ async def do_3pid_invite(
13821382
id_server: str,
13831383
requester: Requester,
13841384
txn_id: Optional[str],
1385-
id_access_token: Optional[str] = None,
1385+
id_access_token: str,
13861386
prev_event_ids: Optional[List[str]] = None,
13871387
depth: Optional[int] = None,
13881388
) -> Tuple[str, int]:
@@ -1397,7 +1397,7 @@ async def do_3pid_invite(
13971397
requester: The user making the request.
13981398
txn_id: The transaction ID this is part of, or None if this is not
13991399
part of a transaction.
1400-
id_access_token: The optional identity server access token.
1400+
id_access_token: Identity server access token.
14011401
depth: Override the depth used to order the event in the DAG.
14021402
prev_event_ids: The event IDs to use as the prev events
14031403
Should normally be set to None, which will cause the depth to be calculated
@@ -1494,7 +1494,7 @@ async def _make_and_store_3pid_invite(
14941494
room_id: str,
14951495
user: UserID,
14961496
txn_id: Optional[str],
1497-
id_access_token: Optional[str] = None,
1497+
id_access_token: str,
14981498
prev_event_ids: Optional[List[str]] = None,
14991499
depth: Optional[int] = None,
15001500
) -> Tuple[EventBase, int]:

synapse/rest/client/room.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import logging
1818
import re
1919
from enum import Enum
20+
from http import HTTPStatus
2021
from typing import TYPE_CHECKING, Awaitable, Dict, List, Optional, Tuple
2122
from urllib import parse as urlparse
2223

@@ -947,7 +948,16 @@ async def on_POST(
947948
# cheekily send invalid bodies.
948949
content = {}
949950

950-
if membership_action == "invite" and self._has_3pid_invite_keys(content):
951+
if membership_action == "invite" and all(
952+
key in content for key in ("medium", "address")
953+
):
954+
if not all(key in content for key in ("id_server", "id_access_token")):
955+
raise SynapseError(
956+
HTTPStatus.BAD_REQUEST,
957+
"`id_server` and `id_access_token` are required when doing 3pid invite",
958+
Codes.MISSING_PARAM,
959+
)
960+
951961
try:
952962
await self.room_member_handler.do_3pid_invite(
953963
room_id,
@@ -957,7 +967,7 @@ async def on_POST(
957967
content["id_server"],
958968
requester,
959969
txn_id,
960-
content.get("id_access_token"),
970+
content["id_access_token"],
961971
)
962972
except ShadowBanError:
963973
# Pretend the request succeeded.
@@ -994,12 +1004,6 @@ async def on_POST(
9941004

9951005
return 200, return_value
9961006

997-
def _has_3pid_invite_keys(self, content: JsonDict) -> bool:
998-
for key in {"id_server", "medium", "address"}:
999-
if key not in content:
1000-
return False
1001-
return True
1002-
10031007
def on_PUT(
10041008
self, request: SynapseRequest, room_id: str, membership_action: str, txn_id: str
10051009
) -> Awaitable[Tuple[int, JsonDict]]:

synapse/rest/media/v1/media_repository.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464

6565
logger = logging.getLogger(__name__)
6666

67-
6867
# How often to run the background job to update the "recently accessed"
6968
# attribute of local and remote media.
7069
UPDATE_RECENTLY_ACCESSED_TS = 60 * 1000 # 1 minute

tests/rest/client/test_identity.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,13 @@
2525

2626

2727
class IdentityTestCase(unittest.HomeserverTestCase):
28-
2928
servlets = [
3029
synapse.rest.admin.register_servlets_for_client_rest_resource,
3130
room.register_servlets,
3231
login.register_servlets,
3332
]
3433

3534
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
36-
3735
config = self.default_config()
3836
config["enable_3pid_lookup"] = False
3937
self.hs = self.setup_test_homeserver(config=config)
@@ -54,6 +52,7 @@ def test_3pid_lookup_disabled(self) -> None:
5452
"id_server": "testis",
5553
"medium": "email",
5654
"address": "[email protected]",
55+
"id_access_token": tok,
5756
}
5857
request_url = ("/rooms/%s/invite" % (room_id)).encode("ascii")
5958
channel = self.make_request(

tests/rest/client/test_rooms.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3461,3 +3461,21 @@ def test_threepid_invite_spamcheck(self) -> None:
34613461

34623462
# Also check that it stopped before calling _make_and_store_3pid_invite.
34633463
make_invite_mock.assert_called_once()
3464+
3465+
def test_400_missing_param_without_id_access_token(self) -> None:
3466+
"""
3467+
Test that a 3pid invite request returns 400 M_MISSING_PARAM
3468+
if we do not include id_access_token.
3469+
"""
3470+
channel = self.make_request(
3471+
method="POST",
3472+
path="/rooms/" + self.room_id + "/invite",
3473+
content={
3474+
"id_server": "example.com",
3475+
"medium": "email",
3476+
"address": "[email protected]",
3477+
},
3478+
access_token=self.tok,
3479+
)
3480+
self.assertEqual(channel.code, 400)
3481+
self.assertEqual(channel.json_body["errcode"], "M_MISSING_PARAM")

tests/rest/client/test_shadow_banned.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,12 @@ def test_invite_3pid(self) -> None:
9797
channel = self.make_request(
9898
"POST",
9999
"/rooms/%s/invite" % (room_id,),
100-
{"id_server": "test", "medium": "email", "address": "[email protected]"},
100+
{
101+
"id_server": "test",
102+
"medium": "email",
103+
"address": "[email protected]",
104+
"id_access_token": "anytoken",
105+
},
101106
access_token=self.banned_access_token,
102107
)
103108
self.assertEqual(200, channel.code, channel.result)

0 commit comments

Comments
 (0)