Skip to content

Commit ff16dd5

Browse files
committed
Use per-user feature flags for MSC3881
1 parent 8ce087d commit ff16dd5

File tree

3 files changed

+111
-17
lines changed

3 files changed

+111
-17
lines changed

synapse/rest/client/pusher.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
)
3333
from synapse.http.site import SynapseRequest
3434
from synapse.push import PusherConfigException
35+
from synapse.rest.admin.experimental_features import ExperimentalFeature
3536
from synapse.rest.client._base import client_patterns
3637
from synapse.rest.synapse.client.unsubscribe import UnsubscribeResource
3738
from synapse.types import JsonDict
@@ -49,20 +50,22 @@ def __init__(self, hs: "HomeServer"):
4950
super().__init__()
5051
self.hs = hs
5152
self.auth = hs.get_auth()
52-
self._msc3881_enabled = self.hs.config.experimental.msc3881_enabled
53+
self._store = hs.get_datastores().main
5354

5455
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
5556
requester = await self.auth.get_user_by_req(request)
56-
user = requester.user
57+
user_id = requester.user.to_string()
5758

58-
pushers = await self.hs.get_datastores().main.get_pushers_by_user_id(
59-
user.to_string()
59+
msc3881_enabled = await self._store.is_feature_enabled(
60+
user_id, ExperimentalFeature.MSC3881
6061
)
6162

63+
pushers = await self.hs.get_datastores().main.get_pushers_by_user_id(user_id)
64+
6265
pusher_dicts = [p.as_dict() for p in pushers]
6366

6467
for pusher in pusher_dicts:
65-
if self._msc3881_enabled:
68+
if msc3881_enabled:
6669
pusher["org.matrix.msc3881.enabled"] = pusher["enabled"]
6770
pusher["org.matrix.msc3881.device_id"] = pusher["device_id"]
6871
del pusher["enabled"]
@@ -80,11 +83,15 @@ def __init__(self, hs: "HomeServer"):
8083
self.auth = hs.get_auth()
8184
self.notifier = hs.get_notifier()
8285
self.pusher_pool = self.hs.get_pusherpool()
83-
self._msc3881_enabled = self.hs.config.experimental.msc3881_enabled
86+
self._store = hs.get_datastores().main
8487

8588
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
8689
requester = await self.auth.get_user_by_req(request)
87-
user = requester.user
90+
user_id = requester.user.to_string()
91+
92+
msc3881_enabled = await self._store.is_feature_enabled(
93+
user_id, ExperimentalFeature.MSC3881
94+
)
8895

8996
content = parse_json_object_from_request(request)
9097

@@ -95,7 +102,7 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
95102
and content["kind"] is None
96103
):
97104
await self.pusher_pool.remove_pusher(
98-
content["app_id"], content["pushkey"], user_id=user.to_string()
105+
content["app_id"], content["pushkey"], user_id=user_id
99106
)
100107
return 200, {}
101108

@@ -120,19 +127,19 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
120127
append = content["append"]
121128

122129
enabled = True
123-
if self._msc3881_enabled and "org.matrix.msc3881.enabled" in content:
130+
if msc3881_enabled and "org.matrix.msc3881.enabled" in content:
124131
enabled = content["org.matrix.msc3881.enabled"]
125132

126133
if not append:
127134
await self.pusher_pool.remove_pushers_by_app_id_and_pushkey_not_user(
128135
app_id=content["app_id"],
129136
pushkey=content["pushkey"],
130-
not_user_id=user.to_string(),
137+
not_user_id=user_id,
131138
)
132139

133140
try:
134141
await self.pusher_pool.add_or_update_pusher(
135-
user_id=user.to_string(),
142+
user_id=user_id,
136143
kind=content["kind"],
137144
app_id=content["app_id"],
138145
app_display_name=content["app_display_name"],

synapse/rest/client/versions.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
import re
2626
from typing import TYPE_CHECKING, Tuple
2727

28-
from twisted.web.server import Request
29-
3028
from synapse.api.constants import RoomCreationPreset
3129
from synapse.http.server import HttpServer
3230
from synapse.http.servlet import RestServlet
31+
from synapse.http.site import SynapseRequest
32+
from synapse.rest.admin.experimental_features import ExperimentalFeature
3333
from synapse.types import JsonDict
3434

3535
if TYPE_CHECKING:
@@ -46,6 +46,7 @@ def __init__(self, hs: "HomeServer"):
4646
super().__init__()
4747
self.config = hs.config
4848
self.auth = hs.get_auth()
49+
self.store = hs.get_datastores().main
4950

5051
# Calculate these once since they shouldn't change after start-up.
5152
self.e2ee_forced_public = (
@@ -61,10 +62,16 @@ def __init__(self, hs: "HomeServer"):
6162
in self.config.room.encryption_enabled_by_default_for_room_presets
6263
)
6364

64-
async def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
65-
requester = None
65+
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
66+
msc3881_enabled = self.config.experimental.msc3881_enabled
67+
6668
if self.auth.has_access_token(request):
6769
requester = await self.auth.get_user_by_req(request)
70+
user_id = requester.user.to_string()
71+
72+
msc3881_enabled = await self.store.is_feature_enabled(
73+
user_id, ExperimentalFeature.MSC3881
74+
)
6875

6976
return (
7077
200,
@@ -129,7 +136,7 @@ async def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
129136
# TODO: this is no longer needed once unstable MSC3882 does not need to be supported:
130137
"org.matrix.msc3882": self.config.auth.login_via_existing_enabled,
131138
# Adds support for remotely enabling/disabling pushers, as per MSC3881
132-
"org.matrix.msc3881": self.config.experimental.msc3881_enabled,
139+
"org.matrix.msc3881": msc3881_enabled,
133140
# Adds support for filtering /messages by event relation.
134141
"org.matrix.msc3874": self.config.experimental.msc3874_enabled,
135142
# Adds support for simple HTTP rendezvous as per MSC3886

tests/push/test_http.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
import synapse.rest.admin
2727
from synapse.logging.context import make_deferred_yieldable
2828
from synapse.push import PusherConfig, PusherConfigException
29-
from synapse.rest.client import login, push_rule, pusher, receipts, room
29+
from synapse.rest.admin.experimental_features import ExperimentalFeature
30+
from synapse.rest.client import login, push_rule, pusher, receipts, room, versions
3031
from synapse.server import HomeServer
3132
from synapse.types import JsonDict
3233
from synapse.util import Clock
@@ -42,6 +43,7 @@ class HTTPPusherTests(HomeserverTestCase):
4243
receipts.register_servlets,
4344
push_rule.register_servlets,
4445
pusher.register_servlets,
46+
versions.register_servlets,
4547
]
4648
user_id = True
4749
hijack_auth = False
@@ -969,6 +971,84 @@ def test_device_id(self) -> None:
969971
lookup_result.device_id,
970972
)
971973

974+
def test_device_id_feature_flag(self) -> None:
975+
"""Tests that a pusher created with a given device ID shows that device ID in
976+
GET /pushers requests when feature is enabled for the user
977+
"""
978+
user_id = self.register_user("user", "pass")
979+
access_token = self.login("user", "pass")
980+
981+
# We create the pusher with an HTTP request rather than with
982+
# _make_user_with_pusher so that we can test the device ID is correctly set when
983+
# creating a pusher via an API call.
984+
self.make_request(
985+
method="POST",
986+
path="/pushers/set",
987+
content={
988+
"kind": "http",
989+
"app_id": "m.http",
990+
"app_display_name": "HTTP Push Notifications",
991+
"device_display_name": "pushy push",
992+
"pushkey": "[email protected]",
993+
"lang": "en",
994+
"data": {"url": "http://example.com/_matrix/push/v1/notify"},
995+
},
996+
access_token=access_token,
997+
)
998+
999+
# Look up the user info for the access token so we can compare the device ID.
1000+
store = self.hs.get_datastores().main
1001+
lookup_result = self.get_success(store.get_user_by_access_token(access_token))
1002+
assert lookup_result is not None
1003+
1004+
# Check field is not there before we enable the feature flag
1005+
channel = self.make_request("GET", "/pushers", access_token=access_token)
1006+
self.assertEqual(channel.code, 200)
1007+
self.assertEqual(len(channel.json_body["pushers"]), 1)
1008+
self.assertNotIn(
1009+
"org.matrix.msc3881.device_id", channel.json_body["pushers"][0]
1010+
)
1011+
1012+
self.get_success(
1013+
store.set_features_for_user(user_id, {ExperimentalFeature.MSC3881: True})
1014+
)
1015+
1016+
# Get the user's devices and check it has the correct device ID.
1017+
channel = self.make_request("GET", "/pushers", access_token=access_token)
1018+
self.assertEqual(channel.code, 200)
1019+
self.assertEqual(len(channel.json_body["pushers"]), 1)
1020+
self.assertEqual(
1021+
channel.json_body["pushers"][0]["org.matrix.msc3881.device_id"],
1022+
lookup_result.device_id,
1023+
)
1024+
1025+
def test_msc3881_client_versions_flag(self) -> None:
1026+
"""Tests that MSC3881 only appears in /versions if user has it enabled."""
1027+
1028+
user_id = self.register_user("user", "pass")
1029+
access_token = self.login("user", "pass")
1030+
1031+
# Check feature is disabled in /versions
1032+
channel = self.make_request(
1033+
"GET", "/_matrix/client/versions", access_token=access_token
1034+
)
1035+
self.assertEqual(channel.code, 200)
1036+
self.assertFalse(channel.json_body["unstable_features"]["org.matrix.msc3881"])
1037+
1038+
# Enable feature for user
1039+
self.get_success(
1040+
self.hs.get_datastores().main.set_features_for_user(
1041+
user_id, {ExperimentalFeature.MSC3881: True}
1042+
)
1043+
)
1044+
1045+
# Check feature is now enabled in /versions for user
1046+
channel = self.make_request(
1047+
"GET", "/_matrix/client/versions", access_token=access_token
1048+
)
1049+
self.assertEqual(channel.code, 200)
1050+
self.assertTrue(channel.json_body["unstable_features"]["org.matrix.msc3881"])
1051+
9721052
@override_config({"push": {"jitter_delay": "10s"}})
9731053
def test_jitter(self) -> None:
9741054
"""Tests that enabling jitter actually delays sending push."""

0 commit comments

Comments
 (0)