Skip to content

api: Unencrypted group creation (#6927) #6932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ impl CommandApi {
Ok(contacts.iter().map(|id| id.to_u32()).collect::<Vec<u32>>())
}

/// Create a new group chat.
/// Create a new encrypted group chat (with PGP contacts).
///
/// After creation,
/// the group has one member with the ID DC_CONTACT_ID_SELF
Expand All @@ -963,14 +963,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<u32> {
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<u32> {
let ctx = self.get_context(account_id).await?;
chat::create_group_ex(&ctx, None, &name)
.await
.map(|id| id.to_u32())
}
Expand Down
25 changes: 21 additions & 4 deletions src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3570,15 +3570,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<ChatId> {
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 PGP contacts) and can be protected.
/// * `name` - Chat name.
pub async fn create_group_ex(
context: &Context,
encryption: Option<ProtectionStatus>,
name: &str,
) -> Result<ChatId> {
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
Expand All @@ -3598,7 +3614,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?;
Expand Down
26 changes: 26 additions & 0 deletions src/chat/chat_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4312,6 +4312,32 @@ async fn test_no_key_contacts_in_adhoc_chats() -> Result<()> {
Ok(())
}

/// Tests that PGP-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 pgp_bob_contact_id = alice.add_or_lookup_contact_id(bob).await;
let email_charlie_contact_id = alice.add_or_lookup_email_contact_id(charlie).await;

let res = add_contact_to_chat(alice, chat_id, pgp_bob_contact_id).await;
assert!(res.is_err());

add_contact_to_chat(alice, chat_id, email_charlie_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<()> {
Expand Down
Loading