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()) } 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?; 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<()> {