From 1b75d384d7f61cf8af293f19cf6dba662206ea5a Mon Sep 17 00:00:00 2001
From: girlbossceo <june@girlboss.ceo>
Date: Sun, 10 Sep 2023 15:40:00 -0400
Subject: [PATCH] option to control federating device display names

Signed-off-by: girlbossceo <june@girlboss.ceo>
---
 conduit-example.toml          |  6 +++++-
 src/api/client_server/keys.rs | 25 ++++++++++++++++++++-----
 src/api/server_server.rs      | 16 ++++++++++++----
 src/config/mod.rs             |  6 ++++++
 src/service/globals/mod.rs    |  4 ++++
 5 files changed, 47 insertions(+), 10 deletions(-)

diff --git a/conduit-example.toml b/conduit-example.toml
index e551e0227..e8d4b08de 100644
--- a/conduit-example.toml
+++ b/conduit-example.toml
@@ -63,4 +63,8 @@ allow_public_room_directory_over_federation = false
 
 # Set this to true to allow your server's public room directory to be queried without client authentication (access token) through the Client APIs.
 # Set this to false to protect against /publicRooms spiders.
-allow_public_room_directory_without_auth = false
\ No newline at end of file
+allow_public_room_directory_without_auth = false
+
+# Set this to true to allow federating device display names / allow external users to see your device display name.
+# If federation is disabled entirely (`allow_federation`), this is inherently false.
+allow_device_name_federation = false
diff --git a/src/api/client_server/keys.rs b/src/api/client_server/keys.rs
index 7dbe040db..bc0887e3c 100644
--- a/src/api/client_server/keys.rs
+++ b/src/api/client_server/keys.rs
@@ -72,8 +72,13 @@ pub async fn upload_keys_route(
 pub async fn get_keys_route(body: Ruma<get_keys::v3::Request>) -> Result<get_keys::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
-    let response =
-        get_keys_helper(Some(sender_user), &body.device_keys, |u| u == sender_user).await?;
+    let response = get_keys_helper(
+        Some(sender_user),
+        &body.device_keys,
+        |u| u == sender_user,
+        true, // Always allow local users to see device names of other local users
+    )
+    .await?;
 
     Ok(response)
 }
@@ -259,6 +264,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
     sender_user: Option<&UserId>,
     device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
     allowed_signatures: F,
+    include_display_names: bool,
 ) -> Result<get_keys::v3::Response> {
     let mut master_keys = BTreeMap::new();
     let mut self_signing_keys = BTreeMap::new();
@@ -290,8 +296,9 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
                             Error::bad_database("all_device_keys contained nonexistent device.")
                         })?;
 
-                    add_unsigned_device_display_name(&mut keys, metadata)
+                    add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
                         .map_err(|_| Error::bad_database("invalid device keys in database"))?;
+
                     container.insert(device_id, keys);
                 }
             }
@@ -308,7 +315,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
                             "Tried to get keys for nonexistent device.",
                         ))?;
 
-                    add_unsigned_device_display_name(&mut keys, metadata)
+                    add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
                         .map_err(|_| Error::bad_database("invalid device keys in database"))?;
                     container.insert(device_id.to_owned(), keys);
                 }
@@ -446,13 +453,21 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
 fn add_unsigned_device_display_name(
     keys: &mut Raw<ruma::encryption::DeviceKeys>,
     metadata: ruma::api::client::device::Device,
+    include_display_names: bool,
 ) -> serde_json::Result<()> {
     if let Some(display_name) = metadata.display_name {
         let mut object = keys.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
 
         let unsigned = object.entry("unsigned").or_insert_with(|| json!({}));
         if let serde_json::Value::Object(unsigned_object) = unsigned {
-            unsigned_object.insert("device_display_name".to_owned(), display_name.into());
+            if include_display_names {
+                unsigned_object.insert("device_display_name".to_owned(), display_name.into());
+            } else {
+                unsigned_object.insert(
+                    "device_display_name".to_owned(),
+                    Some(metadata.device_id.as_str().to_string()).into(),
+                );
+            }
         }
 
         *keys = Raw::from_json(serde_json::value::to_raw_value(&object)?);
diff --git a/src/api/server_server.rs b/src/api/server_server.rs
index 6ef9073b7..5455d0dc5 100644
--- a/src/api/server_server.rs
+++ b/src/api/server_server.rs
@@ -1848,13 +1848,18 @@ pub async fn get_devices_route(
             .all_devices_metadata(&body.user_id)
             .filter_map(|r| r.ok())
             .filter_map(|metadata| {
+                let device_id_string = metadata.device_id.as_str().to_string();
+                let device_display_name = match services().globals.allow_device_name_federation() {
+                    true => Some(device_id_string.to_string()),
+                    false => metadata.display_name,
+                };
                 Some(UserDevice {
                     keys: services()
                         .users
                         .get_device_keys(&body.user_id, &metadata.device_id)
                         .ok()??,
                     device_id: metadata.device_id,
-                    device_display_name: metadata.display_name,
+                    device_display_name: device_display_name,
                 })
             })
             .collect(),
@@ -1940,9 +1945,12 @@ pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_key
         return Err(Error::bad_config("Federation is disabled."));
     }
 
-    let result = get_keys_helper(None, &body.device_keys, |u| {
-        Some(u.server_name()) == body.sender_servername.as_deref()
-    })
+    let result = get_keys_helper(
+        None,
+        &body.device_keys,
+        |u| Some(u.server_name()) == body.sender_servername.as_deref(),
+        services().globals.allow_device_name_federation(),
+    )
     .await?;
 
     Ok(get_keys::v1::Response {
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 1d881c54e..48efd638f 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -55,6 +55,8 @@ pub struct Config {
     pub allow_public_room_directory_over_federation: bool,
     #[serde(default = "false_fn")]
     pub allow_public_room_directory_without_auth: bool,
+    #[serde(default = "false_fn")]
+    pub allow_device_name_federation: bool,
     #[serde(default = "true_fn")]
     pub allow_room_creation: bool,
     #[serde(default = "true_fn")]
@@ -153,6 +155,10 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
             ),
             ("Allow encryption", &self.allow_encryption.to_string()),
             ("Allow federation", &self.allow_federation.to_string()),
+            (
+                "Allow device name federation",
+                &self.allow_device_name_federation.to_string(),
+            ),
             ("Allow room creation", &self.allow_room_creation.to_string()),
             (
                 "Allow public room directory over federation",
diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs
index 1155f7efb..917897112 100644
--- a/src/service/globals/mod.rs
+++ b/src/service/globals/mod.rs
@@ -307,6 +307,10 @@ pub fn allow_public_room_directory_without_auth(&self) -> bool {
         self.config.allow_public_room_directory_without_auth
     }
 
+    pub fn allow_device_name_federation(&self) -> bool {
+        self.config.allow_device_name_federation
+    }
+
     pub fn allow_room_creation(&self) -> bool {
         self.config.allow_room_creation
     }
-- 
GitLab