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

Commit 502f075

Browse files
authored
Implement MSC3848: Introduce errcodes for specific event sending failures (#13343)
Implements MSC3848
1 parent 39be5bc commit 502f075

File tree

11 files changed

+144
-36
lines changed

11 files changed

+144
-36
lines changed

changelog.d/13343.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add new unstable error codes `ORG.MATRIX.MSC3848.ALREADY_JOINED`, `ORG.MATRIX.MSC3848.NOT_JOINED`, and `ORG.MATRIX.MSC3848.INSUFFICIENT_POWER` described in MSC3848.

synapse/api/auth.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
Codes,
2727
InvalidClientTokenError,
2828
MissingClientTokenError,
29+
UnstableSpecAuthError,
2930
)
3031
from synapse.appservice import ApplicationService
3132
from synapse.http import get_request_user_agent
@@ -106,8 +107,11 @@ async def check_user_in_room(
106107
forgot = await self.store.did_forget(user_id, room_id)
107108
if not forgot:
108109
return membership, member_event_id
109-
110-
raise AuthError(403, "User %s not in room %s" % (user_id, room_id))
110+
raise UnstableSpecAuthError(
111+
403,
112+
"User %s not in room %s" % (user_id, room_id),
113+
errcode=Codes.NOT_JOINED,
114+
)
111115

112116
async def get_user_by_req(
113117
self,
@@ -600,8 +604,9 @@ async def check_user_in_room_or_world_readable(
600604
== HistoryVisibility.WORLD_READABLE
601605
):
602606
return Membership.JOIN, None
603-
raise AuthError(
607+
raise UnstableSpecAuthError(
604608
403,
605609
"User %s not in room %s, and room previews are disabled"
606610
% (user_id, room_id),
611+
errcode=Codes.NOT_JOINED,
607612
)

synapse/api/errors.py

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from synapse.util import json_decoder
2727

2828
if typing.TYPE_CHECKING:
29+
from synapse.config.homeserver import HomeServerConfig
2930
from synapse.types import JsonDict
3031

3132
logger = logging.getLogger(__name__)
@@ -80,6 +81,12 @@ class Codes(str, Enum):
8081
INVALID_SIGNATURE = "M_INVALID_SIGNATURE"
8182
USER_DEACTIVATED = "M_USER_DEACTIVATED"
8283

84+
# Part of MSC3848
85+
# https://github.com/matrix-org/matrix-spec-proposals/pull/3848
86+
ALREADY_JOINED = "ORG.MATRIX.MSC3848.ALREADY_JOINED"
87+
NOT_JOINED = "ORG.MATRIX.MSC3848.NOT_JOINED"
88+
INSUFFICIENT_POWER = "ORG.MATRIX.MSC3848.INSUFFICIENT_POWER"
89+
8390
# The account has been suspended on the server.
8491
# By opposition to `USER_DEACTIVATED`, this is a reversible measure
8592
# that can possibly be appealed and reverted.
@@ -167,7 +174,7 @@ def __init__(
167174
else:
168175
self._additional_fields = dict(additional_fields)
169176

170-
def error_dict(self) -> "JsonDict":
177+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
171178
return cs_error(self.msg, self.errcode, **self._additional_fields)
172179

173180

@@ -213,7 +220,7 @@ def __init__(self, msg: str, consent_uri: str):
213220
)
214221
self._consent_uri = consent_uri
215222

216-
def error_dict(self) -> "JsonDict":
223+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
217224
return cs_error(self.msg, self.errcode, consent_uri=self._consent_uri)
218225

219226

@@ -307,6 +314,37 @@ def __init__(
307314
super().__init__(code, msg, errcode, additional_fields)
308315

309316

317+
class UnstableSpecAuthError(AuthError):
318+
"""An error raised when a new error code is being proposed to replace a previous one.
319+
This error will return a "org.matrix.unstable.errcode" property with the new error code,
320+
with the previous error code still being defined in the "errcode" property.
321+
322+
This error will include `org.matrix.msc3848.unstable.errcode` in the C-S error body.
323+
"""
324+
325+
def __init__(
326+
self,
327+
code: int,
328+
msg: str,
329+
errcode: str,
330+
previous_errcode: str = Codes.FORBIDDEN,
331+
additional_fields: Optional[dict] = None,
332+
):
333+
self.previous_errcode = previous_errcode
334+
super().__init__(code, msg, errcode, additional_fields)
335+
336+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
337+
fields = {}
338+
if config is not None and config.experimental.msc3848_enabled:
339+
fields["org.matrix.msc3848.unstable.errcode"] = self.errcode
340+
return cs_error(
341+
self.msg,
342+
self.previous_errcode,
343+
**fields,
344+
**self._additional_fields,
345+
)
346+
347+
310348
class InvalidClientCredentialsError(SynapseError):
311349
"""An error raised when there was a problem with the authorisation credentials
312350
in a client request.
@@ -338,8 +376,8 @@ def __init__(
338376
super().__init__(msg=msg, errcode="M_UNKNOWN_TOKEN")
339377
self._soft_logout = soft_logout
340378

341-
def error_dict(self) -> "JsonDict":
342-
d = super().error_dict()
379+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
380+
d = super().error_dict(config)
343381
d["soft_logout"] = self._soft_logout
344382
return d
345383

@@ -362,7 +400,7 @@ def __init__(
362400
self.limit_type = limit_type
363401
super().__init__(code, msg, errcode=errcode)
364402

365-
def error_dict(self) -> "JsonDict":
403+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
366404
return cs_error(
367405
self.msg,
368406
self.errcode,
@@ -397,7 +435,7 @@ def __init__(
397435
super().__init__(code, msg, errcode)
398436
self.error_url = error_url
399437

400-
def error_dict(self) -> "JsonDict":
438+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
401439
return cs_error(self.msg, self.errcode, error_url=self.error_url)
402440

403441

@@ -414,7 +452,7 @@ def __init__(
414452
super().__init__(code, msg, errcode)
415453
self.retry_after_ms = retry_after_ms
416454

417-
def error_dict(self) -> "JsonDict":
455+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
418456
return cs_error(self.msg, self.errcode, retry_after_ms=self.retry_after_ms)
419457

420458

@@ -429,7 +467,7 @@ def __init__(self, current_version: str):
429467
super().__init__(403, "Wrong room_keys version", Codes.WRONG_ROOM_KEYS_VERSION)
430468
self.current_version = current_version
431469

432-
def error_dict(self) -> "JsonDict":
470+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
433471
return cs_error(self.msg, self.errcode, current_version=self.current_version)
434472

435473

@@ -469,7 +507,7 @@ def __init__(self, room_version: str):
469507

470508
self._room_version = room_version
471509

472-
def error_dict(self) -> "JsonDict":
510+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
473511
return cs_error(self.msg, self.errcode, room_version=self._room_version)
474512

475513

@@ -515,7 +553,7 @@ def __init__(self, content_keep_ms: Optional[int] = None):
515553
)
516554
self.content_keep_ms = content_keep_ms
517555

518-
def error_dict(self) -> "JsonDict":
556+
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
519557
extra = {}
520558
if self.content_keep_ms is not None:
521559
extra = {"fi.mau.msc2815.content_keep_ms": self.content_keep_ms}

synapse/config/experimental.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,6 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
9090

9191
# MSC3827: Filtering of /publicRooms by room type
9292
self.msc3827_enabled: bool = experimental.get("msc3827_enabled", False)
93+
94+
# MSC3848: Introduce errcodes for specific event sending failures
95+
self.msc3848_enabled: bool = experimental.get("msc3848_enabled", False)

synapse/event_auth.py

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@
3030
JoinRules,
3131
Membership,
3232
)
33-
from synapse.api.errors import AuthError, EventSizeError, SynapseError
33+
from synapse.api.errors import (
34+
AuthError,
35+
Codes,
36+
EventSizeError,
37+
SynapseError,
38+
UnstableSpecAuthError,
39+
)
3440
from synapse.api.room_versions import (
3541
KNOWN_ROOM_VERSIONS,
3642
EventFormatVersions,
@@ -291,7 +297,11 @@ def check_state_dependent_auth_rules(
291297
invite_level = get_named_level(auth_dict, "invite", 0)
292298

293299
if user_level < invite_level:
294-
raise AuthError(403, "You don't have permission to invite users")
300+
raise UnstableSpecAuthError(
301+
403,
302+
"You don't have permission to invite users",
303+
errcode=Codes.INSUFFICIENT_POWER,
304+
)
295305
else:
296306
logger.debug("Allowing! %s", event)
297307
return
@@ -474,7 +484,11 @@ def _is_membership_change_allowed(
474484
return
475485

476486
if not caller_in_room: # caller isn't joined
477-
raise AuthError(403, "%s not in room %s." % (event.user_id, event.room_id))
487+
raise UnstableSpecAuthError(
488+
403,
489+
"%s not in room %s." % (event.user_id, event.room_id),
490+
errcode=Codes.NOT_JOINED,
491+
)
478492

479493
if Membership.INVITE == membership:
480494
# TODO (erikj): We should probably handle this more intelligently
@@ -484,10 +498,18 @@ def _is_membership_change_allowed(
484498
if target_banned:
485499
raise AuthError(403, "%s is banned from the room" % (target_user_id,))
486500
elif target_in_room: # the target is already in the room.
487-
raise AuthError(403, "%s is already in the room." % target_user_id)
501+
raise UnstableSpecAuthError(
502+
403,
503+
"%s is already in the room." % target_user_id,
504+
errcode=Codes.ALREADY_JOINED,
505+
)
488506
else:
489507
if user_level < invite_level:
490-
raise AuthError(403, "You don't have permission to invite users")
508+
raise UnstableSpecAuthError(
509+
403,
510+
"You don't have permission to invite users",
511+
errcode=Codes.INSUFFICIENT_POWER,
512+
)
491513
elif Membership.JOIN == membership:
492514
# Joins are valid iff caller == target and:
493515
# * They are not banned.
@@ -549,15 +571,27 @@ def _is_membership_change_allowed(
549571
elif Membership.LEAVE == membership:
550572
# TODO (erikj): Implement kicks.
551573
if target_banned and user_level < ban_level:
552-
raise AuthError(403, "You cannot unban user %s." % (target_user_id,))
574+
raise UnstableSpecAuthError(
575+
403,
576+
"You cannot unban user %s." % (target_user_id,),
577+
errcode=Codes.INSUFFICIENT_POWER,
578+
)
553579
elif target_user_id != event.user_id:
554580
kick_level = get_named_level(auth_events, "kick", 50)
555581

556582
if user_level < kick_level or user_level <= target_level:
557-
raise AuthError(403, "You cannot kick user %s." % target_user_id)
583+
raise UnstableSpecAuthError(
584+
403,
585+
"You cannot kick user %s." % target_user_id,
586+
errcode=Codes.INSUFFICIENT_POWER,
587+
)
558588
elif Membership.BAN == membership:
559589
if user_level < ban_level or user_level <= target_level:
560-
raise AuthError(403, "You don't have permission to ban")
590+
raise UnstableSpecAuthError(
591+
403,
592+
"You don't have permission to ban",
593+
errcode=Codes.INSUFFICIENT_POWER,
594+
)
561595
elif room_version.msc2403_knocking and Membership.KNOCK == membership:
562596
if join_rule != JoinRules.KNOCK and (
563597
not room_version.msc3787_knock_restricted_join_rule
@@ -567,7 +601,11 @@ def _is_membership_change_allowed(
567601
elif target_user_id != event.user_id:
568602
raise AuthError(403, "You cannot knock for other users")
569603
elif target_in_room:
570-
raise AuthError(403, "You cannot knock on a room you are already in")
604+
raise UnstableSpecAuthError(
605+
403,
606+
"You cannot knock on a room you are already in",
607+
errcode=Codes.ALREADY_JOINED,
608+
)
571609
elif caller_invited:
572610
raise AuthError(403, "You are already invited to this room")
573611
elif target_banned:
@@ -638,10 +676,11 @@ def _can_send_event(event: "EventBase", auth_events: StateMap["EventBase"]) -> b
638676
user_level = get_user_power_level(event.user_id, auth_events)
639677

640678
if user_level < send_level:
641-
raise AuthError(
679+
raise UnstableSpecAuthError(
642680
403,
643681
"You don't have permission to post that to the room. "
644682
+ "user_level (%d) < send_level (%d)" % (user_level, send_level),
683+
errcode=Codes.INSUFFICIENT_POWER,
645684
)
646685

647686
# Check state_key
@@ -716,9 +755,10 @@ def check_historical(
716755
historical_level = get_named_level(auth_events, "historical", 100)
717756

718757
if user_level < historical_level:
719-
raise AuthError(
758+
raise UnstableSpecAuthError(
720759
403,
721760
'You don\'t have permission to send send historical related events ("insertion", "batch", and "marker")',
761+
errcode=Codes.INSUFFICIENT_POWER,
722762
)
723763

724764

synapse/federation/federation_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ async def process_pdus_for_room(room_id: str) -> None:
469469
)
470470
for pdu in pdus_by_room[room_id]:
471471
event_id = pdu.event_id
472-
pdu_results[event_id] = e.error_dict()
472+
pdu_results[event_id] = e.error_dict(self.hs.config)
473473
return
474474

475475
for pdu in pdus_by_room[room_id]:

synapse/handlers/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ async def check_ui_auth(
565565
except LoginError as e:
566566
# this step failed. Merge the error dict into the response
567567
# so that the client can have another go.
568-
errordict = e.error_dict()
568+
errordict = e.error_dict(self.hs.config)
569569

570570
creds = await self.store.get_completed_ui_auth_stages(session.session_id)
571571
for f in flows:

synapse/handlers/message.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
NotFoundError,
4242
ShadowBanError,
4343
SynapseError,
44+
UnstableSpecAuthError,
4445
UnsupportedRoomVersionError,
4546
)
4647
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
@@ -149,7 +150,11 @@ async def get_room_data(
149150
"Attempted to retrieve data from a room for a user that has never been in it. "
150151
"This should not have happened."
151152
)
152-
raise SynapseError(403, "User not in room", errcode=Codes.FORBIDDEN)
153+
raise UnstableSpecAuthError(
154+
403,
155+
"User not in room",
156+
errcode=Codes.NOT_JOINED,
157+
)
153158

154159
return data
155160

@@ -334,7 +339,11 @@ async def get_joined_members(self, requester: Requester, room_id: str) -> dict:
334339
break
335340
else:
336341
# Loop fell through, AS has no interested users in room
337-
raise AuthError(403, "Appservice not in room")
342+
raise UnstableSpecAuthError(
343+
403,
344+
"Appservice not in room",
345+
errcode=Codes.NOT_JOINED,
346+
)
338347

339348
return {
340349
user_id: {

synapse/handlers/room_summary.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
RoomTypes,
2929
)
3030
from synapse.api.errors import (
31-
AuthError,
3231
Codes,
3332
NotFoundError,
3433
StoreError,
3534
SynapseError,
35+
UnstableSpecAuthError,
3636
UnsupportedRoomVersionError,
3737
)
3838
from synapse.api.ratelimiting import Ratelimiter
@@ -175,10 +175,11 @@ async def _get_room_hierarchy(
175175

176176
# First of all, check that the room is accessible.
177177
if not await self._is_local_room_accessible(requested_room_id, requester):
178-
raise AuthError(
178+
raise UnstableSpecAuthError(
179179
403,
180180
"User %s not in room %s, and room previews are disabled"
181181
% (requester, requested_room_id),
182+
errcode=Codes.NOT_JOINED,
182183
)
183184

184185
# If this is continuing a previous session, pull the persisted data.

0 commit comments

Comments
 (0)