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

Commit 8ae42ab

Browse files
authored
Support enabling/disabling pushers (from MSC3881) (#13799)
Partial implementation of MSC3881
1 parent 6bd8763 commit 8ae42ab

File tree

15 files changed

+294
-71
lines changed

15 files changed

+294
-71
lines changed

changelog.d/13799.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add experimental support for [MSC3881: Remotely toggle push notifications for another client](https://github.com/matrix-org/matrix-spec-proposals/pull/3881).

synapse/_scripts/synapse_port_db.py

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
"e2e_fallback_keys_json": ["used"],
112112
"access_tokens": ["used"],
113113
"device_lists_changes_in_room": ["converted_to_destinations"],
114+
"pushers": ["enabled"],
114115
}
115116

116117

synapse/config/experimental.py

+3
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,6 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
9393

9494
# MSC3852: Expose last seen user agent field on /_matrix/client/v3/devices.
9595
self.msc3852_enabled: bool = experimental.get("msc3852_enabled", False)
96+
97+
# MSC3881: Remotely toggle push notifications for another client
98+
self.msc3881_enabled: bool = experimental.get("msc3881_enabled", False)

synapse/handlers/register.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -997,15 +997,15 @@ async def _register_email_threepid(
997997
assert user_tuple
998998
token_id = user_tuple.token_id
999999

1000-
await self.pusher_pool.add_pusher(
1000+
await self.pusher_pool.add_or_update_pusher(
10011001
user_id=user_id,
10021002
access_token=token_id,
10031003
kind="email",
10041004
app_id="m.email",
10051005
app_display_name="Email Notifications",
10061006
device_display_name=threepid["address"],
10071007
pushkey=threepid["address"],
1008-
lang=None, # We don't know a user's language here
1008+
lang=None,
10091009
data={},
10101010
)
10111011

synapse/push/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class PusherConfig:
116116
last_stream_ordering: int
117117
last_success: Optional[int]
118118
failing_since: Optional[int]
119+
enabled: bool
119120

120121
def as_dict(self) -> Dict[str, Any]:
121122
"""Information that can be retrieved about a pusher after creation."""
@@ -128,6 +129,7 @@ def as_dict(self) -> Dict[str, Any]:
128129
"lang": self.lang,
129130
"profile_tag": self.profile_tag,
130131
"pushkey": self.pushkey,
132+
"enabled": self.enabled,
131133
}
132134

133135

synapse/push/pusherpool.py

+61-20
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def start(self) -> None:
9494
return
9595
run_as_background_process("start_pushers", self._start_pushers)
9696

97-
async def add_pusher(
97+
async def add_or_update_pusher(
9898
self,
9999
user_id: str,
100100
access_token: Optional[int],
@@ -106,6 +106,7 @@ async def add_pusher(
106106
lang: Optional[str],
107107
data: JsonDict,
108108
profile_tag: str = "",
109+
enabled: bool = True,
109110
) -> Optional[Pusher]:
110111
"""Creates a new pusher and adds it to the pool
111112
@@ -147,9 +148,20 @@ async def add_pusher(
147148
last_stream_ordering=last_stream_ordering,
148149
last_success=None,
149150
failing_since=None,
151+
enabled=enabled,
150152
)
151153
)
152154

155+
# Before we actually persist the pusher, we check if the user already has one
156+
# for this app ID and pushkey. If so, we want to keep the access token in place,
157+
# since this could be one device modifying (e.g. enabling/disabling) another
158+
# device's pusher.
159+
existing_config = await self._get_pusher_config_for_user_by_app_id_and_pushkey(
160+
user_id, app_id, pushkey
161+
)
162+
if existing_config:
163+
access_token = existing_config.access_token
164+
153165
await self.store.add_pusher(
154166
user_id=user_id,
155167
access_token=access_token,
@@ -163,8 +175,9 @@ async def add_pusher(
163175
data=data,
164176
last_stream_ordering=last_stream_ordering,
165177
profile_tag=profile_tag,
178+
enabled=enabled,
166179
)
167-
pusher = await self.start_pusher_by_id(app_id, pushkey, user_id)
180+
pusher = await self.process_pusher_change_by_id(app_id, pushkey, user_id)
168181

169182
return pusher
170183

@@ -276,10 +289,25 @@ async def on_new_receipts(
276289
except Exception:
277290
logger.exception("Exception in pusher on_new_receipts")
278291

279-
async def start_pusher_by_id(
292+
async def _get_pusher_config_for_user_by_app_id_and_pushkey(
293+
self, user_id: str, app_id: str, pushkey: str
294+
) -> Optional[PusherConfig]:
295+
resultlist = await self.store.get_pushers_by_app_id_and_pushkey(app_id, pushkey)
296+
297+
pusher_config = None
298+
for r in resultlist:
299+
if r.user_name == user_id:
300+
pusher_config = r
301+
302+
return pusher_config
303+
304+
async def process_pusher_change_by_id(
280305
self, app_id: str, pushkey: str, user_id: str
281306
) -> Optional[Pusher]:
282-
"""Look up the details for the given pusher, and start it
307+
"""Look up the details for the given pusher, and either start it if its
308+
"enabled" flag is True, or try to stop it otherwise.
309+
310+
If the pusher is new and its "enabled" flag is False, the stop is a noop.
283311
284312
Returns:
285313
The pusher started, if any
@@ -290,12 +318,13 @@ async def start_pusher_by_id(
290318
if not self._pusher_shard_config.should_handle(self._instance_name, user_id):
291319
return None
292320

293-
resultlist = await self.store.get_pushers_by_app_id_and_pushkey(app_id, pushkey)
321+
pusher_config = await self._get_pusher_config_for_user_by_app_id_and_pushkey(
322+
user_id, app_id, pushkey
323+
)
294324

295-
pusher_config = None
296-
for r in resultlist:
297-
if r.user_name == user_id:
298-
pusher_config = r
325+
if pusher_config and not pusher_config.enabled:
326+
self.maybe_stop_pusher(app_id, pushkey, user_id)
327+
return None
299328

300329
pusher = None
301330
if pusher_config:
@@ -305,7 +334,7 @@ async def start_pusher_by_id(
305334

306335
async def _start_pushers(self) -> None:
307336
"""Start all the pushers"""
308-
pushers = await self.store.get_all_pushers()
337+
pushers = await self.store.get_enabled_pushers()
309338

310339
# Stagger starting up the pushers so we don't completely drown the
311340
# process on start up.
@@ -363,6 +392,8 @@ async def _start_pusher(self, pusher_config: PusherConfig) -> Optional[Pusher]:
363392

364393
synapse_pushers.labels(type(pusher).__name__, pusher.app_id).inc()
365394

395+
logger.info("Starting pusher %s / %s", pusher.user_id, appid_pushkey)
396+
366397
# Check if there *may* be push to process. We do this as this check is a
367398
# lot cheaper to do than actually fetching the exact rows we need to
368399
# push.
@@ -382,16 +413,7 @@ async def _start_pusher(self, pusher_config: PusherConfig) -> Optional[Pusher]:
382413
return pusher
383414

384415
async def remove_pusher(self, app_id: str, pushkey: str, user_id: str) -> None:
385-
appid_pushkey = "%s:%s" % (app_id, pushkey)
386-
387-
byuser = self.pushers.get(user_id, {})
388-
389-
if appid_pushkey in byuser:
390-
logger.info("Stopping pusher %s / %s", user_id, appid_pushkey)
391-
pusher = byuser.pop(appid_pushkey)
392-
pusher.on_stop()
393-
394-
synapse_pushers.labels(type(pusher).__name__, pusher.app_id).dec()
416+
self.maybe_stop_pusher(app_id, pushkey, user_id)
395417

396418
# We can only delete pushers on master.
397419
if self._remove_pusher_client:
@@ -402,3 +424,22 @@ async def remove_pusher(self, app_id: str, pushkey: str, user_id: str) -> None:
402424
await self.store.delete_pusher_by_app_id_pushkey_user_id(
403425
app_id, pushkey, user_id
404426
)
427+
428+
def maybe_stop_pusher(self, app_id: str, pushkey: str, user_id: str) -> None:
429+
"""Stops a pusher with the given app ID and push key if one is running.
430+
431+
Args:
432+
app_id: the pusher's app ID.
433+
pushkey: the pusher's push key.
434+
user_id: the user the pusher belongs to. Only used for logging.
435+
"""
436+
appid_pushkey = "%s:%s" % (app_id, pushkey)
437+
438+
byuser = self.pushers.get(user_id, {})
439+
440+
if appid_pushkey in byuser:
441+
logger.info("Stopping pusher %s / %s", user_id, appid_pushkey)
442+
pusher = byuser.pop(appid_pushkey)
443+
pusher.on_stop()
444+
445+
synapse_pushers.labels(type(pusher).__name__, pusher.app_id).dec()

synapse/replication/tcp/client.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ async def on_rdata(
189189
if row.deleted:
190190
self.stop_pusher(row.user_id, row.app_id, row.pushkey)
191191
else:
192-
await self.start_pusher(row.user_id, row.app_id, row.pushkey)
192+
await self.process_pusher_change(
193+
row.user_id, row.app_id, row.pushkey
194+
)
193195
elif stream_name == EventsStream.NAME:
194196
# We shouldn't get multiple rows per token for events stream, so
195197
# we don't need to optimise this for multiple rows.
@@ -334,13 +336,15 @@ def stop_pusher(self, user_id: str, app_id: str, pushkey: str) -> None:
334336
logger.info("Stopping pusher %r / %r", user_id, key)
335337
pusher.on_stop()
336338

337-
async def start_pusher(self, user_id: str, app_id: str, pushkey: str) -> None:
339+
async def process_pusher_change(
340+
self, user_id: str, app_id: str, pushkey: str
341+
) -> None:
338342
if not self._notify_pushers:
339343
return
340344

341345
key = "%s:%s" % (app_id, pushkey)
342346
logger.info("Starting pusher %r / %r", user_id, key)
343-
await self._pusher_pool.start_pusher_by_id(app_id, pushkey, user_id)
347+
await self._pusher_pool.process_pusher_change_by_id(app_id, pushkey, user_id)
344348

345349

346350
class FederationSenderHandler:

synapse/rest/admin/users.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -375,15 +375,15 @@ async def on_PUT(
375375
and self.hs.config.email.email_notif_for_new_users
376376
and medium == "email"
377377
):
378-
await self.pusher_pool.add_pusher(
378+
await self.pusher_pool.add_or_update_pusher(
379379
user_id=user_id,
380380
access_token=None,
381381
kind="email",
382382
app_id="m.email",
383383
app_display_name="Email Notifications",
384384
device_display_name=address,
385385
pushkey=address,
386-
lang=None, # We don't know a user's language here
386+
lang=None,
387387
data={},
388388
)
389389

synapse/rest/client/pusher.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(self, hs: "HomeServer"):
4242
super().__init__()
4343
self.hs = hs
4444
self.auth = hs.get_auth()
45+
self._msc3881_enabled = self.hs.config.experimental.msc3881_enabled
4546

4647
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
4748
requester = await self.auth.get_user_by_req(request)
@@ -51,9 +52,14 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
5152
user.to_string()
5253
)
5354

54-
filtered_pushers = [p.as_dict() for p in pushers]
55+
pusher_dicts = [p.as_dict() for p in pushers]
5556

56-
return 200, {"pushers": filtered_pushers}
57+
for pusher in pusher_dicts:
58+
if self._msc3881_enabled:
59+
pusher["org.matrix.msc3881.enabled"] = pusher["enabled"]
60+
del pusher["enabled"]
61+
62+
return 200, {"pushers": pusher_dicts}
5763

5864

5965
class PushersSetRestServlet(RestServlet):
@@ -65,6 +71,7 @@ def __init__(self, hs: "HomeServer"):
6571
self.auth = hs.get_auth()
6672
self.notifier = hs.get_notifier()
6773
self.pusher_pool = self.hs.get_pusherpool()
74+
self._msc3881_enabled = self.hs.config.experimental.msc3881_enabled
6875

6976
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
7077
requester = await self.auth.get_user_by_req(request)
@@ -103,6 +110,10 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
103110
if "append" in content:
104111
append = content["append"]
105112

113+
enabled = True
114+
if self._msc3881_enabled and "org.matrix.msc3881.enabled" in content:
115+
enabled = content["org.matrix.msc3881.enabled"]
116+
106117
if not append:
107118
await self.pusher_pool.remove_pushers_by_app_id_and_pushkey_not_user(
108119
app_id=content["app_id"],
@@ -111,7 +122,7 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
111122
)
112123

113124
try:
114-
await self.pusher_pool.add_pusher(
125+
await self.pusher_pool.add_or_update_pusher(
115126
user_id=user.to_string(),
116127
access_token=requester.access_token_id,
117128
kind=content["kind"],
@@ -122,6 +133,7 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
122133
lang=content["lang"],
123134
data=content["data"],
124135
profile_tag=content.get("profile_tag", ""),
136+
enabled=enabled,
125137
)
126138
except PusherConfigException as pce:
127139
raise SynapseError(

0 commit comments

Comments
 (0)