mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 17:36:29 +03:00
fix: Let Alice send vb-member-added so that the chat is immediately shown on Bob's device
This commit is contained in:
@@ -4004,7 +4004,7 @@ pub(crate) async fn add_contact_to_chat_ex(
|
|||||||
// this also makes sure, no contacts are added to special or normal chats
|
// this also makes sure, no contacts are added to special or normal chats
|
||||||
let mut chat = Chat::load_from_db(context, chat_id).await?;
|
let mut chat = Chat::load_from_db(context, chat_id).await?;
|
||||||
ensure!(
|
ensure!(
|
||||||
chat.typ == Chattype::Group,
|
chat.typ == Chattype::Group || (from_handshake && chat.typ == Chattype::OutBroadcast),
|
||||||
"{} is not a group where one can add members",
|
"{} is not a group where one can add members",
|
||||||
chat_id
|
chat_id
|
||||||
);
|
);
|
||||||
@@ -4013,7 +4013,6 @@ pub(crate) async fn add_contact_to_chat_ex(
|
|||||||
"invalid contact_id {} for adding to group",
|
"invalid contact_id {} for adding to group",
|
||||||
contact_id
|
contact_id
|
||||||
);
|
);
|
||||||
ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
|
|
||||||
ensure!(
|
ensure!(
|
||||||
chat.is_encrypted(context).await? == contact.is_key_contact(),
|
chat.is_encrypted(context).await? == contact.is_key_contact(),
|
||||||
"Only key-contacts can be added to encrypted chats"
|
"Only key-contacts can be added to encrypted chats"
|
||||||
@@ -4060,9 +4059,6 @@ pub(crate) async fn add_contact_to_chat_ex(
|
|||||||
);
|
);
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
if is_contact_in_chat(context, chat_id, contact_id).await? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
|
add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
|
||||||
}
|
}
|
||||||
if chat.is_promoted() {
|
if chat.is_promoted() {
|
||||||
|
|||||||
@@ -2873,6 +2873,37 @@ async fn test_broadcasts_name_and_avatar() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests that directly after broadcast-securejoin,
|
||||||
|
/// the brodacast is shown correctly on both devices.
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_broadcast_joining_golden() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let bob = &tcm.bob().await;
|
||||||
|
|
||||||
|
alice.set_config(Config::Displayname, Some("Alice")).await?;
|
||||||
|
|
||||||
|
tcm.section("Create a broadcast channel with an avatar");
|
||||||
|
let alice_chat_id = create_broadcast(alice, "My Channel".to_string()).await?;
|
||||||
|
let file = alice.get_blobdir().join("avatar.png");
|
||||||
|
tokio::fs::write(&file, AVATAR_64x64_BYTES).await?;
|
||||||
|
set_chat_profile_image(alice, alice_chat_id, file.to_str().unwrap()).await?;
|
||||||
|
alice.pop_sent_msg().await; // TODO check if Alice wrongly sends out a message here
|
||||||
|
|
||||||
|
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap();
|
||||||
|
let bob_chat_id = tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||||
|
|
||||||
|
// TODO it's not nice that it says 'you added member bob'
|
||||||
|
// and 'Secure-Join: vb-request-with-auth'
|
||||||
|
alice
|
||||||
|
.golden_test_chat(alice_chat_id, "test_broadcast_joining_golden_alice")
|
||||||
|
.await;
|
||||||
|
bob.golden_test_chat(bob_chat_id, "test_broadcast_joining_golden_bob")
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// - Create a broadcast channel
|
/// - Create a broadcast channel
|
||||||
/// - Block it
|
/// - Block it
|
||||||
/// - Check that the broadcast channel appears in the list of blocked contacts
|
/// - Check that the broadcast channel appears in the list of blocked contacts
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use std::collections::{BTreeSet, HashSet};
|
use std::collections::{BTreeSet, HashSet};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, anyhow, bail, ensure};
|
use anyhow::{Context as _, Result, anyhow, bail};
|
||||||
use base64::Engine as _;
|
use base64::Engine as _;
|
||||||
use data_encoding::BASE32_NOPAD;
|
use data_encoding::BASE32_NOPAD;
|
||||||
use deltachat_contact_tools::sanitize_bidi_characters;
|
use deltachat_contact_tools::sanitize_bidi_characters;
|
||||||
@@ -415,8 +415,18 @@ impl MimeFactory {
|
|||||||
req_mdn = true;
|
req_mdn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO if hidden_recipients but email_to_remove is some,
|
// If undisclosed_recipients, and this is a member-added/removed message,
|
||||||
// only send to email_to_remove
|
// only send to the added/removed member
|
||||||
|
if undisclosed_recipients
|
||||||
|
&& matches!(
|
||||||
|
msg.param.get_cmd(),
|
||||||
|
SystemMessage::MemberRemovedFromGroup | SystemMessage::MemberAddedToGroup
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if let Some(member) = msg.param.get(Param::Arg) {
|
||||||
|
recipients.retain(|addr| addr == member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
encryption_keys = if !is_encrypted {
|
encryption_keys = if !is_encrypted {
|
||||||
None
|
None
|
||||||
@@ -571,6 +581,7 @@ impl MimeFactory {
|
|||||||
|| step == "vc-request-with-auth"
|
|| step == "vc-request-with-auth"
|
||||||
|| step == "vb-request-with-auth"
|
|| step == "vb-request-with-auth"
|
||||||
|| step == "vg-member-added"
|
|| step == "vg-member-added"
|
||||||
|
|| step == "vb-member-added"
|
||||||
|| step == "vc-contact-confirm"
|
|| step == "vc-contact-confirm"
|
||||||
// TODO possibly add vb-member-added here
|
// TODO possibly add vb-member-added here
|
||||||
}
|
}
|
||||||
@@ -1396,7 +1407,6 @@ impl MimeFactory {
|
|||||||
|
|
||||||
match command {
|
match command {
|
||||||
SystemMessage::MemberRemovedFromGroup => {
|
SystemMessage::MemberRemovedFromGroup => {
|
||||||
ensure!(chat.typ != Chattype::OutBroadcast);
|
|
||||||
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
|
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
|
|
||||||
if email_to_remove
|
if email_to_remove
|
||||||
@@ -1420,7 +1430,6 @@ impl MimeFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SystemMessage::MemberAddedToGroup => {
|
SystemMessage::MemberAddedToGroup => {
|
||||||
ensure!(chat.typ != Chattype::OutBroadcast);
|
|
||||||
// TODO: lookup the contact by ID rather than email address.
|
// TODO: lookup the contact by ID rather than email address.
|
||||||
// We are adding key-contacts, the cannot be looked up by address.
|
// We are adding key-contacts, the cannot be looked up by address.
|
||||||
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default();
|
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
@@ -1434,14 +1443,15 @@ impl MimeFactory {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & DC_FROM_HANDSHAKE {
|
if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & DC_FROM_HANDSHAKE {
|
||||||
info!(
|
let step = match chat.typ {
|
||||||
context,
|
Chattype::Group => "vg-member-added",
|
||||||
"Sending secure-join message {:?}.", "vg-member-added",
|
Chattype::OutBroadcast => "vb-member-added",
|
||||||
);
|
_ => bail!("Wrong chattype {}", chat.typ),
|
||||||
|
};
|
||||||
|
info!(context, "Sending secure-join message {:?}.", step,);
|
||||||
headers.push((
|
headers.push((
|
||||||
"Secure-Join",
|
"Secure-Join",
|
||||||
mail_builder::headers::raw::Raw::new("vg-member-added".to_string())
|
mail_builder::headers::raw::Raw::new(step.to_string()).into(),
|
||||||
.into(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use deltachat_contact_tools::ContactAddress;
|
|||||||
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
|
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
|
||||||
|
|
||||||
use crate::chat::{
|
use crate::chat::{
|
||||||
self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, add_to_chat_contacts_table,
|
self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, get_chat_id_by_grpid,
|
||||||
get_chat_id_by_grpid, load_broadcast_shared_secret,
|
load_broadcast_shared_secret,
|
||||||
};
|
};
|
||||||
use crate::chatlist_events;
|
use crate::chatlist_events;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -27,7 +27,6 @@ use crate::qr::check_qr;
|
|||||||
use crate::securejoin::bob::JoinerProgress;
|
use crate::securejoin::bob::JoinerProgress;
|
||||||
use crate::sync::Sync::*;
|
use crate::sync::Sync::*;
|
||||||
use crate::token;
|
use crate::token;
|
||||||
use crate::tools::time;
|
|
||||||
|
|
||||||
mod bob;
|
mod bob;
|
||||||
mod qrinvite;
|
mod qrinvite;
|
||||||
@@ -293,7 +292,10 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
|
|
||||||
// TODO talk with link2xt about whether we need to protect against this identity-misbinding attack,
|
// TODO talk with link2xt about whether we need to protect against this identity-misbinding attack,
|
||||||
// and if so, how
|
// and if so, how
|
||||||
if !matches!(step, "vg-request" | "vc-request" | "vb-request-with-auth") {
|
if !matches!(
|
||||||
|
step,
|
||||||
|
"vg-request" | "vc-request" | "vb-request-with-auth" | "vb-member-added"
|
||||||
|
) {
|
||||||
let mut self_found = false;
|
let mut self_found = false;
|
||||||
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
|
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
|
||||||
for (addr, key) in &mime_message.gossiped_keys {
|
for (addr, key) in &mime_message.gossiped_keys {
|
||||||
@@ -438,14 +440,9 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
mime_message.timestamp_sent,
|
mime_message.timestamp_sent,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if step.starts_with("vb-") {
|
|
||||||
// TODO extract into variable
|
|
||||||
add_to_chat_contacts_table(context, time(), group_chat_id, &[contact_id])
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
chat::add_contact_to_chat_ex(context, Nosync, group_chat_id, contact_id, true)
|
chat::add_contact_to_chat_ex(context, Nosync, group_chat_id, contact_id, true)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
|
||||||
inviter_progress(context, contact_id, 800);
|
inviter_progress(context, contact_id, 800);
|
||||||
inviter_progress(context, contact_id, 1000);
|
inviter_progress(context, contact_id, 1000);
|
||||||
if step.starts_with("vb-") {
|
if step.starts_with("vb-") {
|
||||||
@@ -486,7 +483,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
});
|
});
|
||||||
Ok(HandshakeMessage::Ignore)
|
Ok(HandshakeMessage::Ignore)
|
||||||
}
|
}
|
||||||
"vg-member-added" => {
|
"vg-member-added" | "vb-member-added" => {
|
||||||
let Some(member_added) = mime_message.get_header(HeaderDef::ChatGroupMemberAdded)
|
let Some(member_added) = mime_message.get_header(HeaderDef::ChatGroupMemberAdded)
|
||||||
else {
|
else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -554,7 +551,11 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
|||||||
|
|
||||||
if !matches!(
|
if !matches!(
|
||||||
step,
|
step,
|
||||||
"vg-request-with-auth" | "vc-request-with-auth" | "vg-member-added" | "vc-contact-confirm"
|
"vg-request-with-auth"
|
||||||
|
| "vc-request-with-auth"
|
||||||
|
| "vg-member-added"
|
||||||
|
| "vb-member-added"
|
||||||
|
| "vc-contact-confirm"
|
||||||
) {
|
) {
|
||||||
return Ok(HandshakeMessage::Ignore);
|
return Ok(HandshakeMessage::Ignore);
|
||||||
};
|
};
|
||||||
@@ -594,10 +595,12 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
|||||||
if step == "vg-member-added" {
|
if step == "vg-member-added" {
|
||||||
inviter_progress(context, contact_id, 800);
|
inviter_progress(context, contact_id, 800);
|
||||||
}
|
}
|
||||||
if step == "vg-member-added" || step == "vc-contact-confirm" {
|
if step == "vg-member-added" || step == "vb-member-added" || step == "vc-contact-confirm" {
|
||||||
inviter_progress(context, contact_id, 1000);
|
inviter_progress(context, contact_id, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO not sure if I should ad vb-request-with-auth here
|
||||||
|
// Actually, I'm not even sure why vg-request-with-auth is here - why do we create a 1:1 chat??
|
||||||
if step == "vg-request-with-auth" || step == "vc-request-with-auth" {
|
if step == "vg-request-with-auth" || step == "vc-request-with-auth" {
|
||||||
// This actually reflects what happens on the first device (which does the secure
|
// This actually reflects what happens on the first device (which does the secure
|
||||||
// join) and causes a subsequent "vg-member-added" message to create an unblocked
|
// join) and causes a subsequent "vg-member-added" message to create an unblocked
|
||||||
@@ -605,7 +608,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
|||||||
ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await?;
|
ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if step == "vg-member-added" {
|
if step == "vg-member-added" || step == "vb-member-added" {
|
||||||
Ok(HandshakeMessage::Propagate)
|
Ok(HandshakeMessage::Propagate)
|
||||||
} else {
|
} else {
|
||||||
Ok(HandshakeMessage::Ignore)
|
Ok(HandshakeMessage::Ignore)
|
||||||
|
|||||||
@@ -222,10 +222,10 @@ impl TestContextManager {
|
|||||||
self.exec_securejoin_qr(scanner, scanned, &qr).await
|
self.exec_securejoin_qr(scanner, scanned, &qr).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes SecureJoin initiated by `scanner` scanning `qr` generated by `scanned`.
|
/// Executes SecureJoin initiated by `joiner` scanning `qr` generated by `inviter`.
|
||||||
///
|
///
|
||||||
/// The [`ChatId`] of the created chat is returned, for a SetupContact QR this is the 1:1
|
/// The [`ChatId`] of the created chat is returned, for a SetupContact QR this is the 1:1
|
||||||
/// chat with `scanned`, for a SecureJoin QR this is the group chat.
|
/// chat with `inviter`, for a SecureJoin QR this is the group chat.
|
||||||
pub async fn exec_securejoin_qr(
|
pub async fn exec_securejoin_qr(
|
||||||
&self,
|
&self,
|
||||||
joiner: &TestContext,
|
joiner: &TestContext,
|
||||||
@@ -236,16 +236,23 @@ impl TestContextManager {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes SecureJoin initiated by `scanner` scanning `qr` generated by `scanned`.
|
/// Executes SecureJoin initiated by `joiner`
|
||||||
|
/// scanning `qr` generated by one of the `inviters` devices.
|
||||||
|
/// All of the `inviters` devices will get the messages and send replies.
|
||||||
///
|
///
|
||||||
/// The [`ChatId`] of the created chat is returned, for a SetupContact QR this is the 1:1
|
/// The [`ChatId`] of the created chat is returned, for a SetupContact QR this is the 1:1
|
||||||
/// chat with `scanned`, for a SecureJoin QR this is the group chat.
|
/// chat with `inviter`, for a SecureJoin QR this is the group chat.
|
||||||
pub async fn exec_securejoin_qr_multi_device(
|
pub async fn exec_securejoin_qr_multi_device(
|
||||||
&self,
|
&self,
|
||||||
joiner: &TestContext,
|
joiner: &TestContext,
|
||||||
inviters: &[&TestContext],
|
inviters: &[&TestContext],
|
||||||
qr: &str,
|
qr: &str,
|
||||||
) -> ChatId {
|
) -> ChatId {
|
||||||
|
assert!(joiner.pop_sent_msg_opt(Duration::ZERO).await.is_none());
|
||||||
|
for inviter in inviters {
|
||||||
|
assert!(inviter.pop_sent_msg_opt(Duration::ZERO).await.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
let chat_id = join_securejoin(&joiner.ctx, qr).await.unwrap();
|
let chat_id = join_securejoin(&joiner.ctx, qr).await.unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
6
test-data/golden/test_broadcast_joining_golden_alice
Normal file
6
test-data/golden/test_broadcast_joining_golden_alice
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
OutBroadcast#Chat#10: My Channel [1 member(s)] Icon: e9b6c7a78aa2e4f415644f55a553e73.png
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Msg#10🔒: Me (Contact#Contact#Self): You changed the group image. [INFO] √
|
||||||
|
Msg#12🔒: Me (Contact#Contact#Self): You added member bob@example.net. [INFO] √
|
||||||
|
Msg#13🔒: (Contact#Contact#10): Secure-Join: vb-request-with-auth [FRESH]
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
5
test-data/golden/test_broadcast_joining_golden_bob
Normal file
5
test-data/golden/test_broadcast_joining_golden_bob
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
InBroadcast#Chat#11: My Channel [1 member(s)] Icon: e9b6c7a78aa2e4f415644f55a553e73.png
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Msg#12: info (Contact#Contact#Info): You were invited to join this channel. Waiting for the channel owner's device to reply… [NOTICED][INFO]
|
||||||
|
Msg#13🔒: (Contact#Contact#10): I added member bob@example.net. [FRESH][INFO]
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
Reference in New Issue
Block a user