Compare commits

...

4 Commits

Author SHA1 Message Date
Andrew Morgan
bd5189c9f7 Hide device displaynames from other users
Some argument finagling was needed as query_local_devices can be called
from requests of both local and remote users, and in the case of remote
users, without a user ID.

In the end, we have an option 'from_local_user_id' which tells
`query_local_devices` both a) whether the request is from a local or
remote user and b) if a local user, which one.
2023-01-28 17:54:48 +01:00
Andrew Morgan
813bab78ce Ignore remote device displaynames if MSC3480 enabled 2023-01-28 17:52:14 +01:00
Andrew Morgan
c601f796fc Don't send device names over federation if msc3480 is enabled 2023-01-28 16:40:48 +01:00
Andrew Morgan
f4d0c6a364 Add experimental config option for MSC3480 2023-01-28 16:30:58 +01:00
4 changed files with 58 additions and 16 deletions

View File

@@ -166,6 +166,18 @@ class ExperimentalConfig(Config):
# MSC3391: Removing account data.
self.msc3391_enabled = experimental.get("msc3391_enabled", False)
# MSC3480: Make device names private.
self.msc3480_enabled = experimental.get("msc3480_enabled", False)
if (
self.msc3480_enabled
and config.get("allow_device_name_lookup_over_federation") is not False
):
raise ConfigError(
"Option 'allow_device_name_lookup_over_federation' must be "
"'false' if experimental MSC3480 support is enabled (option "
"'experimental_features.msc3480_enabled' is 'true')"
)
# MSC3925: do not replace events with their edits
self.msc3925_inhibit_edit = experimental.get("msc3925_inhibit_edit", False)

View File

@@ -1086,7 +1086,7 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
@measure_func("_incoming_device_list_update")
async def _handle_device_updates(self, user_id: str) -> None:
"Actually handle pending updates."
"""Actually handle pending updates."""
async with self._remote_edu_linearizer.queue(user_id):
pending_updates = self._pending_updates.pop(user_id, [])

View File

@@ -118,7 +118,8 @@ class E2eKeysHandler:
Args:
from_user_id: the user making the query. This is used when
adding cross-signing signatures to limit what signatures users
can see.
can see, and to prevent leaking the displayname of devices of
one user to another when experimental MSC3480 support is enabled.
from_device_id: the device making the query. This is used to limit
the number of in-flight queries at a time.
"""
@@ -145,7 +146,7 @@ class E2eKeysHandler:
failures: Dict[str, JsonDict] = {}
results = {}
if local_query:
local_result = await self.query_local_devices(local_query)
local_result = await self.query_local_devices(local_query, from_user_id)
for user_id, keys in local_result.items():
if user_id in local_query:
results[user_id] = keys
@@ -204,8 +205,15 @@ class E2eKeysHandler:
for user_id, devices in remote_results.items():
user_devices = results.setdefault(user_id, {})
for device_id, device in devices.items():
keys = device.get("keys", None)
device_display_name = device.get("device_display_name", None)
keys = device.get("keys")
# Extract the displayname of the remote device
device_display_name = None
if self.config.experimental.msc3480_enabled is not True:
# Ignore remote device names if experimental MSC3480 support
# is enabled.
device_display_name = device.get("device_display_name")
if keys:
result = dict(keys)
unsigned = result.setdefault("unsigned", {})
@@ -446,15 +454,15 @@ class E2eKeysHandler:
async def query_local_devices(
self,
query: Mapping[str, Optional[List[str]]],
include_displaynames: bool = True,
from_local_user_id: Optional[str],
) -> Dict[str, Dict[str, dict]]:
"""Get E2E device keys for local users
Args:
query: map from user_id to a list
of devices to query (None for all devices)
include_displaynames: Whether to include device displaynames in the returned
device details.
from_local_user_id: If the request originates from a local user, their
User ID should be specified here. Otherwise, this should be None.
Returns:
A map from user_id -> device_id -> device details
@@ -487,7 +495,7 @@ class E2eKeysHandler:
result_dict[user_id] = {}
results = await self.store.get_e2e_device_keys_for_cs_api(
local_query, include_displaynames
local_query, from_local_user_id
)
# Build the result structure
@@ -524,9 +532,8 @@ class E2eKeysHandler:
)
res = await self.query_local_devices(
device_keys_query,
include_displaynames=(
self.config.federation.allow_device_name_lookup_over_federation
),
# This is a request originating from a remote user.
from_local_user_id=None,
)
ret = {"device_keys": res}
@@ -928,7 +935,9 @@ class E2eKeysHandler:
# fetch our stored devices. This is used to 1. verify
# signatures on the master key, and 2. to compare with what
# was sent if the device was signed
devices = await self.store.get_e2e_device_keys_for_cs_api([(user_id, None)])
devices = await self.store.get_e2e_device_keys_for_cs_api(
[(user_id, None)], user_id
)
if user_id not in devices:
raise NotFoundError("No device keys found")

View File

@@ -141,13 +141,15 @@ class EndToEndKeyWorkerStore(EndToEndKeyBackgroundStore, CacheInvalidationWorker
async def get_e2e_device_keys_for_cs_api(
self,
query_list: Collection[Tuple[str, Optional[str]]],
include_displaynames: bool = True,
from_local_user_id: Optional[str],
) -> Dict[str, Dict[str, JsonDict]]:
"""Fetch a list of device keys, formatted suitably for the C/S API.
Args:
query_list: List of pairs of user_ids and device_ids.
include_displaynames: Whether to include the displayname of returned devices
(if one exists).
from_local_user_id: If the request originates from a local user, their
User ID should be specified here. Otherwise, this should be None.
Returns:
Dict mapping from user-id to dict mapping from device_id to
key data. The key data will be a dict in the same format as the
@@ -169,6 +171,25 @@ class EndToEndKeyWorkerStore(EndToEndKeyBackgroundStore, CacheInvalidationWorker
if r is None:
continue
# Determine whether the displayname of this device should be shared with
# the user making the request.
include_displaynames = True
if (
from_local_user_id is not None
and user_id != from_local_user_id
and self.hs.config.experimental.msc3480_enabled is True
):
include_displaynames = False
# If this is a request from a remote user, and we've disallowed sharing
# local user device names over federation, strip the device's displayname.
elif (
from_local_user_id is None
and not self._allow_device_name_lookup_over_federation
):
include_displaynames = False
r["unsigned"] = {}
if include_displaynames:
# Include the device's display name in the "unsigned" dictionary