From 27c44f0ee1f9e8898ede3974106139b09f404cec Mon Sep 17 00:00:00 2001 From: iequidoo Date: Sun, 22 Jun 2025 08:11:15 -0300 Subject: [PATCH 1/3] api: Add chat::create_group_ex(), deprecate create_group_chat() (#6927) `chat::create_group_ex()` gains an `encryption: Option` parameter to support unencrypted chats. --- src/chat.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 1e7c6d1927..b24ce6bc95 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -3629,15 +3629,31 @@ pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Resul } /// Creates a group chat with a given `name`. +/// Deprecated on 2025-06-21, use `create_group_ex()`. pub async fn create_group_chat( context: &Context, protect: ProtectionStatus, - chat_name: &str, + name: &str, ) -> Result { - let chat_name = sanitize_single_line(chat_name); + create_group_ex(context, Some(protect), name).await +} + +/// Creates a group chat. +/// +/// * `encryption` - If `Some`, the chat is encrypted (with key-contacts) and can be protected. +/// * `name` - Chat name. +pub async fn create_group_ex( + context: &Context, + encryption: Option, + name: &str, +) -> Result { + let chat_name = sanitize_single_line(name); ensure!(!chat_name.is_empty(), "Invalid chat name"); - let grpid = create_id(); + let grpid = match encryption { + Some(_) => create_id(), + None => String::new(), + }; let timestamp = create_smeared_timestamp(context); let row_id = context @@ -3657,7 +3673,8 @@ pub async fn create_group_chat( chatlist_events::emit_chatlist_changed(context); chatlist_events::emit_chatlist_item_changed(context, chat_id); - if protect == ProtectionStatus::Protected { + if encryption == Some(ProtectionStatus::Protected) { + let protect = ProtectionStatus::Protected; chat_id .set_protection_for_timestamp_sort(context, protect, timestamp, None) .await?; From c0b4d71db0323154a43bc1e3c8b7f5c1c4a9d1ac Mon Sep 17 00:00:00 2001 From: iequidoo Date: Sun, 22 Jun 2025 08:15:53 -0300 Subject: [PATCH 2/3] api(jsonrpc): Add CommandApi::create_group_chat_unencrypted() (#6927) --- deltachat-jsonrpc/src/api.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 0b6eda8f8b..8619e3c47b 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -953,7 +953,7 @@ impl CommandApi { Ok(contacts.iter().map(|id| id.to_u32()).collect::>()) } - /// Create a new group chat. + /// Create a new encrypted group chat (with key-contacts). /// /// After creation, /// the group has one member with the ID DC_CONTACT_ID_SELF @@ -971,14 +971,24 @@ impl CommandApi { /// /// @param protect If set to 1 the function creates group with protection initially enabled. /// Only verified members are allowed in these groups - /// and end-to-end-encryption is always enabled. async fn create_group_chat(&self, account_id: u32, name: String, protect: bool) -> Result { let ctx = self.get_context(account_id).await?; let protect = match protect { true => ProtectionStatus::Protected, false => ProtectionStatus::Unprotected, }; - chat::create_group_chat(&ctx, protect, &name) + chat::create_group_ex(&ctx, Some(protect), &name) + .await + .map(|id| id.to_u32()) + } + + /// Create a new unencrypted group chat. + /// + /// Same as [`Self::create_group_chat`], but the chat is unencrypted and can only have + /// address-contacts. + async fn create_group_chat_unencrypted(&self, account_id: u32, name: String) -> Result { + let ctx = self.get_context(account_id).await?; + chat::create_group_ex(&ctx, None, &name) .await .map(|id| id.to_u32()) } From 3286cb3922c8da0c6f737400d58ef847720c39e1 Mon Sep 17 00:00:00 2001 From: iequidoo Date: Sun, 22 Jun 2025 08:55:42 -0300 Subject: [PATCH 3/3] test: Unencrypted group creation (#6927) --- src/chat/chat_tests.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index b693934f76..8bf7419048 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -4698,6 +4698,32 @@ async fn test_no_key_contacts_in_adhoc_chats() -> Result<()> { Ok(()) } +/// Tests that key-contacts cannot be added to an unencrypted (ad hoc) group and the group and +/// messages report that they are unencrypted. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_create_unencrypted_group_chat() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + let charlie = &tcm.charlie().await; + + let chat_id = create_group_ex(alice, None, "Group chat").await?; + let bob_key_contact_id = alice.add_or_lookup_contact_id(bob).await; + let charlie_address_contact_id = alice.add_or_lookup_address_contact_id(charlie).await; + + let res = add_contact_to_chat(alice, chat_id, bob_key_contact_id).await; + assert!(res.is_err()); + + add_contact_to_chat(alice, chat_id, charlie_address_contact_id).await?; + + let chat = Chat::load_from_db(alice, chat_id).await?; + assert!(!chat.is_encrypted(alice).await?); + let sent_msg = alice.send_text(chat_id, "Hello").await; + let msg = Message::load_from_db(alice, sent_msg.sender_msg_id).await?; + assert!(!msg.get_showpadlock()); + Ok(()) +} + /// Tests that avatar cannot be set in ad hoc groups. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_no_avatar_in_adhoc_chats() -> Result<()> {