This repository was archived by the owner on Apr 26, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add an admin API endpoint to support per-user feature flags #15344
Merged
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
5a1f946
add an experimental features table and a class to access it
H-Shay ec04542
add an admin endpoint and handler class to access table
H-Shay 0841f79
add some tests of basic functionality
H-Shay 28f835a
newsframent
H-Shay 1b68698
Merge branch 'develop' into shay/experimental_flags
H-Shay daf8a9f
fix schema delta
H-Shay dc6207a
fix bad import (thanks pycharm)
H-Shay 845c07e
drop unnecessary handler class and user enum for verification
H-Shay c999fea
rewrite table
H-Shay 5c9c4b7
allow for bulk setting/getting of features, cache result of getting
H-Shay 6280124
update tests
H-Shay d3f7032
Merge branch 'develop' into shay/experimental_flags
H-Shay aa74c4a
update schema
H-Shay 985eba9
Merge branch 'develop' into shay/experimental_flags
H-Shay 2c1191d
update comments and schema number
H-Shay 4847df7
add new boolean column to `port_db` script and capitalize FALSE
H-Shay 1ca2989
properly handle boolean column in sqlite
H-Shay e86e39c
it's postgres, not postgre
H-Shay fadb5e6
make PUT and GET more symmetrical for ease of use
H-Shay 0496d1d
Update docs/admin_api/experimental_features.md
H-Shay 2044c14
update docs
H-Shay dcc9c41
fix the urls
H-Shay 37e7f30
get rid of sqlite-specific table
H-Shay 401cb10
schema version is now 75 so move file
H-Shay 8927eae
Apply suggestions from code review
H-Shay 17e48be
Merge branch 'develop' into shay/experimental_flags
H-Shay 427d65f
fix schema drift
H-Shay 80d3f69
Merge branch 'shay/experimental_flags' of https://github.com/matrix-o…
H-Shay 6a994dd
update test to accompdate new error message
H-Shay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add an admin API endpoint to support per-user feature flags. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Experimental Features API | ||
|
||
This API allows a server administrator to enable or disable some experimental features on a per-user | ||
basis. Currently supported features are [msc3026](https://github.com/matrix-org/matrix-spec-proposals/pull/3026): busy | ||
presence state enabled, [msc2654](https://github.com/matrix-org/matrix-spec-proposals/pull/2654): enable unread counts, | ||
[msc3881](https://github.com/matrix-org/matrix-spec-proposals/pull/3881): enable remotely toggling push notifications | ||
for another client, and [msc3967](https://github.com/matrix-org/matrix-spec-proposals/pull/3967): do not require | ||
UIA when first uploading cross-signing keys. | ||
|
||
|
||
To use it, you will need to authenticate by providing an `access_token` | ||
for a server admin: see [Admin API](../usage/administration/admin_api/). | ||
|
||
## Enabling/Disabling Features | ||
|
||
This API allows a server administrator to enable experimental features for a given user. The request must | ||
provide a body containing the user id and listing the features to enable/disable in the following format: | ||
```json | ||
{ | ||
"features": { | ||
"msc3026":true, | ||
"msc2654":true | ||
} | ||
} | ||
``` | ||
where true is used to enable the feature, and false is used to disable the feature. | ||
|
||
|
||
The API is: | ||
|
||
``` | ||
PUT /_synapse/admin/v1/experimental_features/<user_id> | ||
``` | ||
|
||
## Listing Enabled Features | ||
|
||
To list which features are enabled/disabled for a given user send a request to the following API: | ||
|
||
``` | ||
GET /_synapse/admin/v1/experimental_features/<user_id> | ||
``` | ||
|
||
It will return a list of possible features and indicate whether they are enabled or disabled for the | ||
user like so: | ||
```json | ||
{ | ||
"features": { | ||
"msc3026": true, | ||
"msc2654": true, | ||
"msc3881": false, | ||
"msc3967": false | ||
} | ||
} | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# Copyright 2023 The Matrix.org Foundation C.I.C | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
from enum import Enum | ||
from http import HTTPStatus | ||
from typing import TYPE_CHECKING, Dict, Tuple | ||
|
||
from synapse.api.errors import SynapseError | ||
from synapse.http.servlet import RestServlet, parse_json_object_from_request | ||
from synapse.http.site import SynapseRequest | ||
from synapse.rest.admin import admin_patterns, assert_requester_is_admin | ||
from synapse.types import JsonDict, UserID | ||
|
||
if TYPE_CHECKING: | ||
from synapse.server import HomeServer | ||
|
||
|
||
class ExperimentalFeature(str, Enum): | ||
""" | ||
Currently supported per-user features | ||
""" | ||
|
||
MSC3026 = "msc3026" | ||
MSC2654 = "msc2654" | ||
MSC3881 = "msc3881" | ||
MSC3967 = "msc3967" | ||
|
||
|
||
class ExperimentalFeaturesRestServlet(RestServlet): | ||
""" | ||
Enable or disable experimental features for a user or determine which features are enabled | ||
for a given user | ||
""" | ||
|
||
PATTERNS = admin_patterns("/experimental_features/(?P<user_id>[^/]*)") | ||
|
||
def __init__(self, hs: "HomeServer"): | ||
super().__init__() | ||
self.auth = hs.get_auth() | ||
self.store = hs.get_datastores().main | ||
self.is_mine = hs.is_mine | ||
|
||
async def on_GET( | ||
self, | ||
request: SynapseRequest, | ||
user_id: str, | ||
) -> Tuple[int, JsonDict]: | ||
""" | ||
List which features are enabled for a given user | ||
""" | ||
await assert_requester_is_admin(self.auth, request) | ||
|
||
target_user = UserID.from_string(user_id) | ||
if not self.is_mine(target_user): | ||
raise SynapseError( | ||
HTTPStatus.BAD_REQUEST, | ||
"User must be local to check what experimental features are enabled.", | ||
) | ||
|
||
enabled_features = await self.store.list_enabled_features(user_id) | ||
|
||
user_features = {} | ||
for feature in ExperimentalFeature: | ||
H-Shay marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if feature in enabled_features: | ||
user_features[feature] = True | ||
else: | ||
user_features[feature] = False | ||
return HTTPStatus.OK, {"features": user_features} | ||
|
||
async def on_PUT( | ||
self, request: SynapseRequest, user_id: str | ||
) -> Tuple[HTTPStatus, Dict]: | ||
""" | ||
Enable or disable the provided features for the requester | ||
""" | ||
await assert_requester_is_admin(self.auth, request) | ||
|
||
body = parse_json_object_from_request(request) | ||
|
||
target_user = UserID.from_string(user_id) | ||
if not self.is_mine(target_user): | ||
raise SynapseError( | ||
HTTPStatus.BAD_REQUEST, | ||
"User must be local to enable experimental features.", | ||
) | ||
|
||
features = body.get("features") | ||
if not features: | ||
raise SynapseError( | ||
HTTPStatus.BAD_REQUEST, "You must provide features to set." | ||
) | ||
|
||
# validate the provided features | ||
validated_features = {} | ||
for feature, enabled in features.items(): | ||
try: | ||
validated_feature = ExperimentalFeature(feature) | ||
validated_features[validated_feature] = enabled | ||
except ValueError: | ||
raise SynapseError( | ||
HTTPStatus.BAD_REQUEST, | ||
f"{feature!r} is not recognised as a valid experimental feature.", | ||
) | ||
|
||
await self.store.set_features_for_user(user_id, validated_features) | ||
|
||
return HTTPStatus.OK, {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# Copyright 2023 The Matrix.org Foundation C.I.C | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from typing import TYPE_CHECKING, Dict | ||
|
||
from synapse.storage.database import DatabasePool, LoggingDatabaseConnection | ||
from synapse.storage.databases.main import CacheInvalidationWorkerStore | ||
from synapse.types import StrCollection | ||
from synapse.util.caches.descriptors import cached | ||
|
||
if TYPE_CHECKING: | ||
from synapse.rest.admin.experimental_features import ExperimentalFeature | ||
from synapse.server import HomeServer | ||
|
||
|
||
class ExperimentalFeaturesStore(CacheInvalidationWorkerStore): | ||
def __init__( | ||
self, | ||
database: DatabasePool, | ||
db_conn: LoggingDatabaseConnection, | ||
hs: "HomeServer", | ||
) -> None: | ||
super().__init__(database, db_conn, hs) | ||
|
||
@cached() | ||
async def list_enabled_features(self, user_id: str) -> StrCollection: | ||
""" | ||
Checks to see what features are enabled for a given user | ||
Args: | ||
user: | ||
the user to be queried on | ||
Returns: | ||
the features currently enabled for the user | ||
""" | ||
enabled = await self.db_pool.simple_select_list( | ||
"per_user_experimental_features", | ||
{"user_id": user_id, "enabled": True}, | ||
["feature"], | ||
) | ||
|
||
return [feature["feature"] for feature in enabled] | ||
|
||
async def set_features_for_user( | ||
self, | ||
user: str, | ||
features: Dict["ExperimentalFeature", bool], | ||
) -> None: | ||
""" | ||
Enables or disables features for a given user | ||
Args: | ||
user: | ||
the user for whom to enable/disable the features | ||
features: | ||
pairs of features and True/False for whether the feature should be enabled | ||
""" | ||
for feature, enabled in features.items(): | ||
await self.db_pool.simple_upsert( | ||
table="per_user_experimental_features", | ||
keyvalues={"feature": feature, "user_id": user}, | ||
values={"enabled": enabled}, | ||
insertion_values={"user_id": user, "feature": feature}, | ||
) | ||
|
||
await self.invalidate_cache_and_stream("list_enabled_features", (user,)) |
27 changes: 27 additions & 0 deletions
27
synapse/storage/schema/main/delta/76/03_per_user_experimental_features.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* Copyright 2023 The Matrix.org Foundation C.I.C | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
-- Table containing experimental features and whether they are enabled for a given user | ||
CREATE TABLE per_user_experimental_features ( | ||
-- The User ID to check/set the feature for | ||
user_id TEXT NOT NULL, | ||
-- Contains features to be enabled/disabled | ||
feature TEXT NOT NULL, | ||
-- whether the feature is enabled/disabled for a given user, defaults to disabled | ||
enabled BOOLEAN DEFAULT FALSE, | ||
FOREIGN KEY (user_id) REFERENCES users(name), | ||
PRIMARY KEY (user_id, feature) | ||
); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.