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

Commit 4b4e0dc

Browse files
authored
Error if attempting to set m.push_rules account data, per MSC4010. (#15555)
m.push_rules, like m.fully_read, is a special account data type that cannot be set using the normal /account_data endpoint. Return an error instead of allowing data that will not be used to be stored.
1 parent 2bfe3f0 commit 4b4e0dc

File tree

9 files changed

+95
-35
lines changed

9 files changed

+95
-35
lines changed

changelog.d/15554.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Experimental support for [MSC4010](https://github.com/matrix-org/matrix-spec-proposals/pull/4010) which rejects setting the `"m.push_rules"` via account data.

changelog.d/15554.misc

-1
This file was deleted.

changelog.d/15555.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Experimental support for [MSC4010](https://github.com/matrix-org/matrix-spec-proposals/pull/4010) which rejects setting the `"m.push_rules"` via account data.

synapse/config/experimental.py

+5
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,8 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
202202

203203
# MSC4009: E.164 Matrix IDs
204204
self.msc4009_e164_mxids = experimental.get("msc4009_e164_mxids", False)
205+
206+
# MSC4010: Do not allow setting m.push_rules account data.
207+
self.msc4010_push_rules_account_data = experimental.get(
208+
"msc4010_push_rules_account_data", False
209+
)

synapse/handlers/push_rules.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
from typing import TYPE_CHECKING, List, Optional, Union
14+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
1515

1616
import attr
1717

1818
from synapse.api.errors import SynapseError, UnrecognizedRequestError
19+
from synapse.push.clientformat import format_push_rules_for_user
1920
from synapse.storage.push_rule import RuleNotFoundException
2021
from synapse.synapse_rust.push import get_base_rule_ids
21-
from synapse.types import JsonDict
22+
from synapse.types import JsonDict, UserID
2223

2324
if TYPE_CHECKING:
2425
from synapse.server import HomeServer
@@ -115,6 +116,17 @@ def notify_user(self, user_id: str) -> None:
115116
stream_id = self._main_store.get_max_push_rules_stream_id()
116117
self._notifier.on_new_event("push_rules_key", stream_id, users=[user_id])
117118

119+
async def push_rules_for_user(
120+
self, user: UserID
121+
) -> Dict[str, Dict[str, List[Dict[str, Any]]]]:
122+
"""
123+
Push rules aren't really account data, but get formatted as such for /sync.
124+
"""
125+
user_id = user.to_string()
126+
rules_raw = await self._main_store.get_push_rules_for_user(user_id)
127+
rules = format_push_rules_for_user(user, rules_raw)
128+
return rules
129+
118130

119131
def check_actions(actions: List[Union[str, JsonDict]]) -> None:
120132
"""Check if the given actions are spec compliant.

synapse/handlers/sync.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
start_active_span,
5151
trace,
5252
)
53-
from synapse.push.clientformat import format_push_rules_for_user
5453
from synapse.storage.databases.main.event_push_actions import RoomNotifCounts
5554
from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
5655
from synapse.storage.roommember import MemberSummary
@@ -261,6 +260,7 @@ def __init__(self, hs: "HomeServer"):
261260
self.notifier = hs.get_notifier()
262261
self.presence_handler = hs.get_presence_handler()
263262
self._relations_handler = hs.get_relations_handler()
263+
self._push_rules_handler = hs.get_push_rules_handler()
264264
self.event_sources = hs.get_event_sources()
265265
self.clock = hs.get_clock()
266266
self.state = hs.get_state_handler()
@@ -428,12 +428,6 @@ async def current_sync_for_user(
428428
set_tag(SynapseTags.SYNC_RESULT, bool(sync_result))
429429
return sync_result
430430

431-
async def push_rules_for_user(self, user: UserID) -> Dict[str, Dict[str, list]]:
432-
user_id = user.to_string()
433-
rules_raw = await self.store.get_push_rules_for_user(user_id)
434-
rules = format_push_rules_for_user(user, rules_raw)
435-
return rules
436-
437431
async def ephemeral_by_room(
438432
self,
439433
sync_result_builder: "SyncResultBuilder",
@@ -1779,7 +1773,7 @@ async def _generate_sync_entry_for_account_data(
17791773
global_account_data = dict(global_account_data)
17801774
global_account_data[
17811775
AccountDataTypes.PUSH_RULES
1782-
] = await self.push_rules_for_user(sync_config.user)
1776+
] = await self._push_rules_handler.push_rules_for_user(sync_config.user)
17831777
else:
17841778
all_global_account_data = await self.store.get_global_account_data_for_user(
17851779
user_id
@@ -1788,7 +1782,7 @@ async def _generate_sync_entry_for_account_data(
17881782
global_account_data = dict(all_global_account_data)
17891783
global_account_data[
17901784
AccountDataTypes.PUSH_RULES
1791-
] = await self.push_rules_for_user(sync_config.user)
1785+
] = await self._push_rules_handler.push_rules_for_user(sync_config.user)
17921786

17931787
account_data_for_user = (
17941788
await sync_config.filter_collection.filter_global_account_data(

synapse/push/clientformat.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
def format_push_rules_for_user(
2424
user: UserID, ruleslist: FilteredPushRules
25-
) -> Dict[str, Dict[str, list]]:
25+
) -> Dict[str, Dict[str, List[Dict[str, Any]]]]:
2626
"""Converts a list of rawrules and a enabled map into nested dictionaries
2727
to match the Matrix client-server format for push rules"""
2828

synapse/rest/client/account_data.py

+68-17
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
# limitations under the License.
1414

1515
import logging
16-
from typing import TYPE_CHECKING, Tuple
16+
from typing import TYPE_CHECKING, Optional, Tuple
1717

18-
from synapse.api.constants import ReceiptTypes
18+
from synapse.api.constants import AccountDataTypes, ReceiptTypes
1919
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
2020
from synapse.http.server import HttpServer
2121
from synapse.http.servlet import RestServlet, parse_json_object_from_request
@@ -30,6 +30,23 @@
3030
logger = logging.getLogger(__name__)
3131

3232

33+
def _check_can_set_account_data_type(account_data_type: str) -> None:
34+
"""The fully read marker and push rules cannot be directly set via /account_data."""
35+
if account_data_type == ReceiptTypes.FULLY_READ:
36+
raise SynapseError(
37+
405,
38+
"Cannot set m.fully_read through this API."
39+
" Use /rooms/!roomId:server.name/read_markers",
40+
Codes.BAD_JSON,
41+
)
42+
elif account_data_type == AccountDataTypes.PUSH_RULES:
43+
raise SynapseError(
44+
405,
45+
"Cannot set m.push_rules through this API. Use /pushrules",
46+
Codes.BAD_JSON,
47+
)
48+
49+
3350
class AccountDataServlet(RestServlet):
3451
"""
3552
PUT /user/{user_id}/account_data/{account_dataType} HTTP/1.1
@@ -47,6 +64,7 @@ def __init__(self, hs: "HomeServer"):
4764
self.auth = hs.get_auth()
4865
self.store = hs.get_datastores().main
4966
self.handler = hs.get_account_data_handler()
67+
self._push_rules_handler = hs.get_push_rules_handler()
5068

5169
async def on_PUT(
5270
self, request: SynapseRequest, user_id: str, account_data_type: str
@@ -55,6 +73,10 @@ async def on_PUT(
5573
if user_id != requester.user.to_string():
5674
raise AuthError(403, "Cannot add account data for other users.")
5775

76+
# Raise an error if the account data type cannot be set directly.
77+
if self._hs.config.experimental.msc4010_push_rules_account_data:
78+
_check_can_set_account_data_type(account_data_type)
79+
5880
body = parse_json_object_from_request(request)
5981

6082
# If experimental support for MSC3391 is enabled, then providing an empty dict
@@ -78,19 +100,28 @@ async def on_GET(
78100
if user_id != requester.user.to_string():
79101
raise AuthError(403, "Cannot get account data for other users.")
80102

81-
event = await self.store.get_global_account_data_by_type_for_user(
82-
user_id, account_data_type
83-
)
103+
# Push rules are stored in a separate table and must be queried separately.
104+
if (
105+
self._hs.config.experimental.msc4010_push_rules_account_data
106+
and account_data_type == AccountDataTypes.PUSH_RULES
107+
):
108+
account_data: Optional[
109+
JsonDict
110+
] = await self._push_rules_handler.push_rules_for_user(requester.user)
111+
else:
112+
account_data = await self.store.get_global_account_data_by_type_for_user(
113+
user_id, account_data_type
114+
)
84115

85-
if event is None:
116+
if account_data is None:
86117
raise NotFoundError("Account data not found")
87118

88119
# If experimental support for MSC3391 is enabled, then this endpoint should
89120
# return a 404 if the content for an account data type is an empty dict.
90-
if self._hs.config.experimental.msc3391_enabled and event == {}:
121+
if self._hs.config.experimental.msc3391_enabled and account_data == {}:
91122
raise NotFoundError("Account data not found")
92123

93-
return 200, event
124+
return 200, account_data
94125

95126

96127
class UnstableAccountDataServlet(RestServlet):
@@ -109,6 +140,7 @@ class UnstableAccountDataServlet(RestServlet):
109140

110141
def __init__(self, hs: "HomeServer"):
111142
super().__init__()
143+
self._hs = hs
112144
self.auth = hs.get_auth()
113145
self.handler = hs.get_account_data_handler()
114146

@@ -122,6 +154,10 @@ async def on_DELETE(
122154
if user_id != requester.user.to_string():
123155
raise AuthError(403, "Cannot delete account data for other users.")
124156

157+
# Raise an error if the account data type cannot be set directly.
158+
if self._hs.config.experimental.msc4010_push_rules_account_data:
159+
_check_can_set_account_data_type(account_data_type)
160+
125161
await self.handler.remove_account_data_for_user(user_id, account_data_type)
126162

127163
return 200, {}
@@ -165,16 +201,19 @@ async def on_PUT(
165201
Codes.INVALID_PARAM,
166202
)
167203

168-
body = parse_json_object_from_request(request)
169-
170-
if account_data_type == ReceiptTypes.FULLY_READ:
204+
# Raise an error if the account data type cannot be set directly.
205+
if self._hs.config.experimental.msc4010_push_rules_account_data:
206+
_check_can_set_account_data_type(account_data_type)
207+
elif account_data_type == ReceiptTypes.FULLY_READ:
171208
raise SynapseError(
172209
405,
173210
"Cannot set m.fully_read through this API."
174211
" Use /rooms/!roomId:server.name/read_markers",
175212
Codes.BAD_JSON,
176213
)
177214

215+
body = parse_json_object_from_request(request)
216+
178217
# If experimental support for MSC3391 is enabled, then providing an empty dict
179218
# as the value for an account data type should be functionally equivalent to
180219
# calling the DELETE method on the same type.
@@ -209,19 +248,26 @@ async def on_GET(
209248
Codes.INVALID_PARAM,
210249
)
211250

212-
event = await self.store.get_account_data_for_room_and_type(
213-
user_id, room_id, account_data_type
214-
)
251+
# Room-specific push rules are not currently supported.
252+
if (
253+
self._hs.config.experimental.msc4010_push_rules_account_data
254+
and account_data_type == AccountDataTypes.PUSH_RULES
255+
):
256+
account_data: Optional[JsonDict] = {}
257+
else:
258+
account_data = await self.store.get_account_data_for_room_and_type(
259+
user_id, room_id, account_data_type
260+
)
215261

216-
if event is None:
262+
if account_data is None:
217263
raise NotFoundError("Room account data not found")
218264

219265
# If experimental support for MSC3391 is enabled, then this endpoint should
220266
# return a 404 if the content for an account data type is an empty dict.
221-
if self._hs.config.experimental.msc3391_enabled and event == {}:
267+
if self._hs.config.experimental.msc3391_enabled and account_data == {}:
222268
raise NotFoundError("Room account data not found")
223269

224-
return 200, event
270+
return 200, account_data
225271

226272

227273
class UnstableRoomAccountDataServlet(RestServlet):
@@ -241,6 +287,7 @@ class UnstableRoomAccountDataServlet(RestServlet):
241287

242288
def __init__(self, hs: "HomeServer"):
243289
super().__init__()
290+
self._hs = hs
244291
self.auth = hs.get_auth()
245292
self.handler = hs.get_account_data_handler()
246293

@@ -262,6 +309,10 @@ async def on_DELETE(
262309
Codes.INVALID_PARAM,
263310
)
264311

312+
# Raise an error if the account data type cannot be set directly.
313+
if self._hs.config.experimental.msc4010_push_rules_account_data:
314+
_check_can_set_account_data_type(account_data_type)
315+
265316
await self.handler.remove_account_data_for_room(
266317
user_id, room_id, account_data_type
267318
)

synapse/rest/client/push_rule.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
parse_string,
2929
)
3030
from synapse.http.site import SynapseRequest
31-
from synapse.push.clientformat import format_push_rules_for_user
3231
from synapse.push.rulekinds import PRIORITY_CLASS_MAP
3332
from synapse.rest.client._base import client_patterns
3433
from synapse.storage.push_rule import InconsistentRuleException, RuleNotFoundException
@@ -146,14 +145,12 @@ async def on_DELETE(
146145

147146
async def on_GET(self, request: SynapseRequest, path: str) -> Tuple[int, JsonDict]:
148147
requester = await self.auth.get_user_by_req(request)
149-
user_id = requester.user.to_string()
148+
requester.user.to_string()
150149

151150
# we build up the full structure and then decide which bits of it
152151
# to send which means doing unnecessary work sometimes but is
153152
# is probably not going to make a whole lot of difference
154-
rules_raw = await self.store.get_push_rules_for_user(user_id)
155-
156-
rules = format_push_rules_for_user(requester.user, rules_raw)
153+
rules = await self._push_rules_handler.push_rules_for_user(requester.user)
157154

158155
path_parts = path.split("/")[1:]
159156

0 commit comments

Comments
 (0)