mirror of
https://github.com/chatmail/core.git
synced 2026-04-19 14:36:29 +03:00
feat: key-contacts
This change introduces a new type of contacts
identified by their public key fingerprint
rather than an e-mail address.
Encrypted chats now stay encrypted
and unencrypted chats stay unencrypted.
For example, 1:1 chats with key-contacts
are encrypted and 1:1 chats with address-contacts
are unencrypted.
Groups that have a group ID are encrypted
and can only contain key-contacts
while groups that don't have a group ID ("adhoc groups")
are unencrypted and can only contain address-contacts.
JSON-RPC API `reset_contact_encryption` is removed.
Python API `Contact.reset_encryption` is removed.
"Group tracking plugin" in legacy Python API was removed because it
relied on parsing email addresses from system messages with regexps.
Co-authored-by: Hocuri <hocuri@gmx.de>
Co-authored-by: iequidoo <dgreshilov@gmail.com>
Co-authored-by: B. Petersen <r10s@b44t.com>
This commit is contained in:
@@ -7,6 +7,7 @@ use crate::imex::{has_backup, imex, ImexMode};
|
||||
use crate::message::{delete_msgs, MessengerMessage};
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{sync, TestContext, TestContextManager, TimeShiftFalsePositiveNote};
|
||||
use pretty_assertions::assert_eq;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::fs;
|
||||
|
||||
@@ -19,31 +20,39 @@ async fn test_chat_info() {
|
||||
// Ensure we can serialize this.
|
||||
println!("{}", serde_json::to_string_pretty(&info).unwrap());
|
||||
|
||||
let expected = r#"
|
||||
{
|
||||
"id": 10,
|
||||
"type": 100,
|
||||
"name": "bob",
|
||||
"archived": false,
|
||||
"param": "",
|
||||
"gossiped_timestamp": 0,
|
||||
"is_sending_locations": false,
|
||||
"color": 35391,
|
||||
"profile_image": "",
|
||||
"draft": "",
|
||||
"is_muted": false,
|
||||
"ephemeral_timer": "Disabled"
|
||||
}
|
||||
"#;
|
||||
let expected = format!(
|
||||
r#"{{
|
||||
"id": 10,
|
||||
"type": 100,
|
||||
"name": "bob",
|
||||
"archived": false,
|
||||
"param": "",
|
||||
"is_sending_locations": false,
|
||||
"color": 35391,
|
||||
"profile_image": {},
|
||||
"draft": "",
|
||||
"is_muted": false,
|
||||
"ephemeral_timer": "Disabled"
|
||||
}}"#,
|
||||
// We need to do it like this so that the test passes on Windows:
|
||||
serde_json::to_string(
|
||||
t.get_blobdir()
|
||||
.join("4138c52e5bc1c576cda7dd44d088c07.png")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// Ensure we can deserialize this.
|
||||
let loaded: ChatInfo = serde_json::from_str(expected).unwrap();
|
||||
assert_eq!(info, loaded);
|
||||
serde_json::from_str::<ChatInfo>(&expected).unwrap();
|
||||
|
||||
assert_eq!(serde_json::to_string_pretty(&info).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_draft_no_draft() {
|
||||
let t = TestContext::new().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat = t.get_self_chat().await;
|
||||
let draft = chat.id.get_draft(&t).await.unwrap();
|
||||
assert!(draft.is_none());
|
||||
@@ -60,14 +69,14 @@ async fn test_get_draft_special_chat_id() {
|
||||
async fn test_get_draft_no_chat() {
|
||||
// This is a weird case, maybe this should be an error but we
|
||||
// do not get this info from the database currently.
|
||||
let t = TestContext::new().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let draft = ChatId::new(42).get_draft(&t).await.unwrap();
|
||||
assert!(draft.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_draft() {
|
||||
let t = TestContext::new().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = &t.get_self_chat().await.id;
|
||||
let mut msg = Message::new_text("hello".to_string());
|
||||
|
||||
@@ -320,18 +329,20 @@ async fn test_member_add_remove() -> Result<()> {
|
||||
assert_eq!(alice_bob_contact.get_display_name(), "robert");
|
||||
}
|
||||
|
||||
// Create and promote a group.
|
||||
tcm.section("Create and promote a group.");
|
||||
let alice_chat_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
alice
|
||||
let sent = alice
|
||||
.send_text(alice_chat_id, "Hi! I created a group.")
|
||||
.await;
|
||||
let fiona_chat_id = fiona.recv_msg(&sent).await.chat_id;
|
||||
|
||||
// Alice adds Bob to the chat.
|
||||
tcm.section("Alice adds Bob to the chat.");
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
fiona.recv_msg(&sent).await;
|
||||
|
||||
// Locally set name "robert" should not leak.
|
||||
assert!(!sent.payload.contains("robert"));
|
||||
@@ -339,8 +350,15 @@ async fn test_member_add_remove() -> Result<()> {
|
||||
sent.load_from_db().await.get_text(),
|
||||
"You added member robert."
|
||||
);
|
||||
let fiona_contact_ids = get_chat_contacts(&fiona, fiona_chat_id).await?;
|
||||
assert_eq!(fiona_contact_ids.len(), 3);
|
||||
for contact_id in fiona_contact_ids {
|
||||
let contact = Contact::get_by_id(&fiona, contact_id).await?;
|
||||
assert_ne!(contact.get_name(), "robert");
|
||||
assert!(contact.is_key_contact());
|
||||
}
|
||||
|
||||
// Alice removes Bob from the chat.
|
||||
tcm.section("Alice removes Bob from the chat.");
|
||||
remove_contact_from_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
assert!(!sent.payload.contains("robert"));
|
||||
@@ -371,7 +389,7 @@ async fn test_parallel_member_remove() -> Result<()> {
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
let alice_charlie_contact_id = alice.add_or_lookup_contact_id(&charlie).await;
|
||||
|
||||
// Create and promote a group.
|
||||
tcm.section("Alice creates and promotes a group");
|
||||
let alice_chat_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
@@ -384,31 +402,31 @@ async fn test_parallel_member_remove() -> Result<()> {
|
||||
let bob_chat_id = bob_received_msg.get_chat_id();
|
||||
bob_chat_id.accept(&bob).await?;
|
||||
|
||||
// Alice adds Charlie to the chat.
|
||||
tcm.section("Alice adds Charlie to the chat");
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_charlie_contact_id).await?;
|
||||
let alice_sent_add_msg = alice.pop_sent_msg().await;
|
||||
|
||||
// Bob leaves the chat.
|
||||
tcm.section("Bob leaves the chat");
|
||||
remove_contact_from_chat(&bob, bob_chat_id, ContactId::SELF).await?;
|
||||
bob.pop_sent_msg().await;
|
||||
|
||||
// Bob receives a msg about Alice adding Claire to the group.
|
||||
tcm.section("Bob receives a message about Alice adding Charlie to the group");
|
||||
bob.recv_msg(&alice_sent_add_msg).await;
|
||||
|
||||
SystemTime::shift(Duration::from_secs(3600));
|
||||
|
||||
// Alice sends a message to Bob because the message about leaving is lost.
|
||||
tcm.section("Alice sends a message to Bob because the message about leaving is lost");
|
||||
let alice_sent_msg = alice.send_text(alice_chat_id, "What a silence!").await;
|
||||
bob.recv_msg(&alice_sent_msg).await;
|
||||
|
||||
bob.golden_test_chat(bob_chat_id, "chat_test_parallel_member_remove")
|
||||
.await;
|
||||
|
||||
// Alice removes Bob from the chat.
|
||||
tcm.section("Alice removes Bob from the chat");
|
||||
remove_contact_from_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
let alice_sent_remove_msg = alice.pop_sent_msg().await;
|
||||
|
||||
// Bob receives a msg about Alice removing him from the group.
|
||||
tcm.section("Bob receives a msg about Alice removing him from the group");
|
||||
let bob_received_remove_msg = bob.recv_msg(&alice_sent_remove_msg).await;
|
||||
|
||||
// Test that remove message is rewritten.
|
||||
@@ -621,6 +639,7 @@ async fn test_lost_member_added() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "Group", &[bob])
|
||||
.await;
|
||||
@@ -629,8 +648,8 @@ async fn test_lost_member_added() -> Result<()> {
|
||||
assert_eq!(get_chat_contacts(bob, bob_chat_id).await?.len(), 2);
|
||||
|
||||
// Attempt to add member, but message is lost.
|
||||
let claire_id = Contact::create(alice, "", "claire@foo.de").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, claire_id).await?;
|
||||
let charlie_id = alice.add_or_lookup_contact_id(charlie).await;
|
||||
add_contact_to_chat(alice, alice_chat_id, charlie_id).await?;
|
||||
alice.pop_sent_msg().await;
|
||||
|
||||
let alice_sent = alice.send_text(alice_chat_id, "Hi again!").await;
|
||||
@@ -693,12 +712,12 @@ async fn test_leave_group() -> Result<()> {
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
// Create group chat with Bob.
|
||||
tcm.section("Alice creates group chat with Bob.");
|
||||
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let bob_contact = alice.add_or_lookup_contact(&bob).await.id;
|
||||
add_contact_to_chat(&alice, alice_chat_id, bob_contact).await?;
|
||||
|
||||
// Alice sends first message to group.
|
||||
tcm.section("Alice sends first message to group.");
|
||||
let sent_msg = alice.send_text(alice_chat_id, "Hello!").await;
|
||||
let bob_msg = bob.recv_msg(&sent_msg).await;
|
||||
|
||||
@@ -711,7 +730,7 @@ async fn test_leave_group() -> Result<()> {
|
||||
// Shift the time so that we can later check the 'Group left' message's timestamp:
|
||||
SystemTime::shift(Duration::from_secs(60));
|
||||
|
||||
// Bob leaves the group.
|
||||
tcm.section("Bob leaves the group.");
|
||||
let bob_chat_id = bob_msg.chat_id;
|
||||
bob_chat_id.accept(&bob).await?;
|
||||
remove_contact_from_chat(&bob, bob_chat_id, ContactId::SELF).await?;
|
||||
@@ -897,7 +916,11 @@ async fn test_add_device_msg_labelled() -> Result<()> {
|
||||
assert!(chat.why_cant_send(&t).await? == Some(CantSendReason::DeviceChat));
|
||||
|
||||
assert_eq!(chat.name, stock_str::device_messages(&t).await);
|
||||
assert!(chat.get_profile_image(&t).await?.is_some());
|
||||
let device_msg_icon = chat.get_profile_image(&t).await?.unwrap();
|
||||
assert_eq!(
|
||||
device_msg_icon.metadata()?.len(),
|
||||
include_bytes!("../../assets/icon-device.png").len() as u64
|
||||
);
|
||||
|
||||
// delete device message, make sure it is not added again
|
||||
message::delete_msgs(&t, &[*msg1_id.as_ref().unwrap()]).await?;
|
||||
@@ -968,7 +991,7 @@ async fn test_delete_device_chat() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_device_chat_cannot_sent() {
|
||||
let t = TestContext::new().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
t.update_device_chats().await.unwrap();
|
||||
let device_chat_id = ChatId::get_for_contact(&t, ContactId::DEVICE)
|
||||
.await
|
||||
@@ -981,6 +1004,18 @@ async fn test_device_chat_cannot_sent() {
|
||||
assert!(forward_msgs(&t, &[msg_id], device_chat_id).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_device_chat_is_encrypted() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.update_device_chats().await.unwrap();
|
||||
let device_chat_id = ChatId::get_for_contact(&t, ContactId::DEVICE)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let device_chat = Chat::load_from_db(&t, device_chat_id).await.unwrap();
|
||||
assert!(device_chat.is_encrypted(&t).await.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_and_reset_all_device_msgs() {
|
||||
let t = TestContext::new().await;
|
||||
@@ -1015,7 +1050,7 @@ async fn chatlist_len(ctx: &Context, listflags: usize) -> usize {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_archive() {
|
||||
// create two chats
|
||||
let t = TestContext::new().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let mut msg = Message::new_text("foo".to_string());
|
||||
let msg_id = add_device_msg(&t, None, Some(&mut msg)).await.unwrap();
|
||||
let chat_id1 = message::Message::load_from_db(&t, msg_id)
|
||||
@@ -1313,7 +1348,7 @@ async fn get_chats_from_chat_list(ctx: &Context, listflags: usize) -> Vec<ChatId
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_pinned() {
|
||||
let t = TestContext::new().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
// create 3 chats, wait 1 second in between to get a reliable order (we order by time)
|
||||
let mut msg = Message::new_text("foo".to_string());
|
||||
@@ -1481,25 +1516,22 @@ async fn test_create_same_chat_twice() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_shall_attach_selfavatar() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
assert!(!shall_attach_selfavatar(&t, chat_id).await?);
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let (contact_id, _) = Contact::add_or_lookup(
|
||||
&t,
|
||||
"",
|
||||
&ContactAddress::new("foo@bar.org")?,
|
||||
Origin::IncomingUnknownTo,
|
||||
)
|
||||
.await?;
|
||||
add_contact_to_chat(&t, chat_id, contact_id).await?;
|
||||
assert!(shall_attach_selfavatar(&t, chat_id).await?);
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
assert!(!shall_attach_selfavatar(alice, chat_id).await?);
|
||||
|
||||
chat_id.set_selfavatar_timestamp(&t, time()).await?;
|
||||
assert!(!shall_attach_selfavatar(&t, chat_id).await?);
|
||||
let contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
add_contact_to_chat(alice, chat_id, contact_id).await?;
|
||||
assert!(shall_attach_selfavatar(alice, chat_id).await?);
|
||||
|
||||
t.set_config(Config::Selfavatar, None).await?; // setting to None also forces re-sending
|
||||
assert!(shall_attach_selfavatar(&t, chat_id).await?);
|
||||
chat_id.set_selfavatar_timestamp(alice, time()).await?;
|
||||
assert!(!shall_attach_selfavatar(alice, chat_id).await?);
|
||||
|
||||
alice.set_config(Config::Selfavatar, None).await?; // setting to None also forces re-sending
|
||||
assert!(shall_attach_selfavatar(alice, chat_id).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1509,15 +1541,10 @@ async fn test_shall_attach_selfavatar() -> Result<()> {
|
||||
async fn test_profile_data_on_group_leave() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = create_group_chat(t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
|
||||
let (contact_id, _) = Contact::add_or_lookup(
|
||||
t,
|
||||
"",
|
||||
&ContactAddress::new("foo@bar.org")?,
|
||||
Origin::IncomingUnknownTo,
|
||||
)
|
||||
.await?;
|
||||
let contact_id = t.add_or_lookup_contact_id(bob).await;
|
||||
add_contact_to_chat(t, chat_id, contact_id).await?;
|
||||
|
||||
send_text_msg(t, chat_id, "populate".to_string()).await?;
|
||||
@@ -1532,7 +1559,8 @@ async fn test_profile_data_on_group_leave() -> Result<()> {
|
||||
|
||||
remove_contact_from_chat(t, chat_id, ContactId::SELF).await?;
|
||||
let sent_msg = t.pop_sent_msg().await;
|
||||
assert!(sent_msg.payload().contains("Chat-User-Avatar"));
|
||||
let msg = bob.parse_msg(&sent_msg).await;
|
||||
assert!(msg.header_exists(HeaderDef::ChatUserAvatar));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2183,40 +2211,46 @@ async fn test_forward_group() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_only_minimal_data_are_forwarded() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let charlie = tcm.charlie().await;
|
||||
|
||||
// send a message from Alice to a group with Bob
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice
|
||||
.set_config(Config::Displayname, Some("secretname"))
|
||||
.await?;
|
||||
let bob_id = Contact::create(&alice, "bob", "bob@example.net").await?;
|
||||
let bob_id = alice.add_or_lookup_contact_id(&bob).await;
|
||||
let group_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "secretgrpname").await?;
|
||||
add_contact_to_chat(&alice, group_id, bob_id).await?;
|
||||
let mut msg = Message::new_text("bla foo".to_owned());
|
||||
let sent_msg = alice.send_msg(group_id, &mut msg).await;
|
||||
assert!(sent_msg.payload().contains("secretgrpname"));
|
||||
assert!(sent_msg.payload().contains("secretname"));
|
||||
assert!(sent_msg.payload().contains("alice"));
|
||||
let parsed_msg = alice.parse_msg(&sent_msg).await;
|
||||
let encrypted_payload = String::from_utf8(parsed_msg.decoded_data.clone()).unwrap();
|
||||
assert!(encrypted_payload.contains("secretgrpname"));
|
||||
assert!(encrypted_payload.contains("secretname"));
|
||||
assert!(encrypted_payload.contains("alice"));
|
||||
|
||||
// Bob forwards that message to Claire -
|
||||
// Claire should not get information about Alice for the original Group
|
||||
let bob = TestContext::new_bob().await;
|
||||
let orig_msg = bob.recv_msg(&sent_msg).await;
|
||||
let claire_id = Contact::create(&bob, "claire", "claire@foo").await?;
|
||||
let single_id = ChatId::create_for_contact(&bob, claire_id).await?;
|
||||
let charlie_id = bob.add_or_lookup_contact_id(&charlie).await;
|
||||
let single_id = ChatId::create_for_contact(&bob, charlie_id).await?;
|
||||
let group_id = create_group_chat(&bob, ProtectionStatus::Unprotected, "group2").await?;
|
||||
add_contact_to_chat(&bob, group_id, claire_id).await?;
|
||||
add_contact_to_chat(&bob, group_id, charlie_id).await?;
|
||||
let broadcast_id = create_broadcast_list(&bob).await?;
|
||||
add_contact_to_chat(&bob, broadcast_id, claire_id).await?;
|
||||
add_contact_to_chat(&bob, broadcast_id, charlie_id).await?;
|
||||
for chat_id in &[single_id, group_id, broadcast_id] {
|
||||
forward_msgs(&bob, &[orig_msg.id], *chat_id).await?;
|
||||
let sent_msg = bob.pop_sent_msg().await;
|
||||
assert!(sent_msg
|
||||
.payload()
|
||||
.contains("---------- Forwarded message ----------"));
|
||||
assert!(!sent_msg.payload().contains("secretgrpname"));
|
||||
assert!(!sent_msg.payload().contains("secretname"));
|
||||
assert!(!sent_msg.payload().contains("alice"));
|
||||
let parsed_msg = bob.parse_msg(&sent_msg).await;
|
||||
let encrypted_payload = String::from_utf8(parsed_msg.decoded_data.clone()).unwrap();
|
||||
|
||||
assert!(encrypted_payload.contains("---------- Forwarded message ----------"));
|
||||
assert!(!encrypted_payload.contains("secretgrpname"));
|
||||
assert!(!encrypted_payload.contains("secretname"));
|
||||
assert!(!encrypted_payload.contains("alice"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2500,43 +2534,6 @@ async fn test_resend_foreign_message_fails() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_resend_opportunistically_encryption() -> Result<()> {
|
||||
// Alice creates group with Bob and sends an initial message
|
||||
let alice = TestContext::new_alice().await;
|
||||
let alice_grp = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
add_contact_to_chat(
|
||||
&alice,
|
||||
alice_grp,
|
||||
Contact::create(&alice, "", "bob@example.net").await?,
|
||||
)
|
||||
.await?;
|
||||
let sent1 = alice.send_text(alice_grp, "alice->bob").await;
|
||||
|
||||
// Bob now can send an encrypted message
|
||||
let bob = TestContext::new_bob().await;
|
||||
let msg = bob.recv_msg(&sent1).await;
|
||||
assert!(!msg.get_showpadlock());
|
||||
|
||||
msg.chat_id.accept(&bob).await?;
|
||||
let sent2 = bob.send_text(msg.chat_id, "bob->alice").await;
|
||||
let msg = bob.get_last_msg().await;
|
||||
assert!(msg.get_showpadlock());
|
||||
|
||||
// Bob adds Claire and resends his last message: this will drop encryption in opportunistic chats
|
||||
add_contact_to_chat(
|
||||
&bob,
|
||||
msg.chat_id,
|
||||
Contact::create(&bob, "", "claire@example.org").await?,
|
||||
)
|
||||
.await?;
|
||||
let _sent3 = bob.pop_sent_msg().await;
|
||||
resend_msgs(&bob, &[sent2.sender_msg_id]).await?;
|
||||
let _sent4 = bob.pop_sent_msg().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_resend_info_message_fails() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
@@ -2737,53 +2734,64 @@ async fn test_create_for_contact_with_blocked() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_get_encryption_info() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
|
||||
let contact_bob = Contact::create(&alice, "Bob", "bob@example.net").await?;
|
||||
let contact_fiona = Contact::create(&alice, "", "fiona@example.net").await?;
|
||||
let contact_bob = alice.add_or_lookup_contact_id(bob).await;
|
||||
let contact_fiona = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
assert_eq!(chat_id.get_encryption_info(&alice).await?, "");
|
||||
|
||||
add_contact_to_chat(&alice, chat_id, contact_bob).await?;
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
assert_eq!(
|
||||
chat_id.get_encryption_info(&alice).await?,
|
||||
"No encryption:\n\
|
||||
bob@example.net"
|
||||
chat_id.get_encryption_info(alice).await?,
|
||||
"End-to-end encryption available"
|
||||
);
|
||||
|
||||
add_contact_to_chat(&alice, chat_id, contact_fiona).await?;
|
||||
add_contact_to_chat(alice, chat_id, contact_bob).await?;
|
||||
assert_eq!(
|
||||
chat_id.get_encryption_info(&alice).await?,
|
||||
"No encryption:\n\
|
||||
fiona@example.net\n\
|
||||
bob@example.net"
|
||||
chat_id.get_encryption_info(alice).await?,
|
||||
"End-to-end encryption available\n\
|
||||
\n\
|
||||
bob@example.net\n\
|
||||
CCCB 5AA9 F6E1 141C 9431\n\
|
||||
65F1 DB18 B18C BCF7 0487"
|
||||
);
|
||||
|
||||
let direct_chat = bob.create_chat(&alice).await;
|
||||
send_text_msg(&bob, direct_chat.id, "Hello!".to_string()).await?;
|
||||
alice.recv_msg(&bob.pop_sent_msg().await).await;
|
||||
|
||||
add_contact_to_chat(alice, chat_id, contact_fiona).await?;
|
||||
assert_eq!(
|
||||
chat_id.get_encryption_info(&alice).await?,
|
||||
"No encryption:\n\
|
||||
fiona@example.net\n\
|
||||
\n\
|
||||
End-to-end encryption available:\n\
|
||||
bob@example.net"
|
||||
chat_id.get_encryption_info(alice).await?,
|
||||
"End-to-end encryption available\n\
|
||||
\n\
|
||||
fiona@example.net\n\
|
||||
C8BA 50BF 4AC1 2FAF 38D7\n\
|
||||
F657 DDFC 8E9F 3C79 9195\n\
|
||||
\n\
|
||||
bob@example.net\n\
|
||||
CCCB 5AA9 F6E1 141C 9431\n\
|
||||
65F1 DB18 B18C BCF7 0487"
|
||||
);
|
||||
|
||||
send_text_msg(&bob, direct_chat.id, "Hello!".to_string()).await?;
|
||||
alice.recv_msg(&bob.pop_sent_msg().await).await;
|
||||
|
||||
let email_chat = alice.create_email_chat(bob).await;
|
||||
assert_eq!(
|
||||
chat_id.get_encryption_info(&alice).await?,
|
||||
"No encryption:\n\
|
||||
fiona@example.net\n\
|
||||
\n\
|
||||
End-to-end encryption available:\n\
|
||||
bob@example.net"
|
||||
email_chat.id.get_encryption_info(alice).await?,
|
||||
"No encryption"
|
||||
);
|
||||
|
||||
alice.sql.execute("DELETE FROM public_keys", ()).await?;
|
||||
assert_eq!(
|
||||
chat_id.get_encryption_info(alice).await?,
|
||||
"End-to-end encryption available\n\
|
||||
\n\
|
||||
fiona@example.net\n\
|
||||
(key missing)\n\
|
||||
C8BA 50BF 4AC1 2FAF 38D7\n\
|
||||
F657 DDFC 8E9F 3C79 9195\n\
|
||||
\n\
|
||||
bob@example.net\n\
|
||||
(key missing)\n\
|
||||
CCCB 5AA9 F6E1 141C 9431\n\
|
||||
65F1 DB18 B18C BCF7 0487"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -3039,40 +3047,41 @@ async fn test_blob_renaming() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_sync_blocked() -> Result<()> {
|
||||
let alice0 = &TestContext::new_alice().await;
|
||||
let alice1 = &TestContext::new_alice().await;
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice0 = &tcm.alice().await;
|
||||
let alice1 = &tcm.alice().await;
|
||||
for a in [alice0, alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
let bob = TestContext::new_bob().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let ba_chat = bob.create_chat(alice0).await;
|
||||
let sent_msg = bob.send_text(ba_chat.id, "hi").await;
|
||||
let a0b_chat_id = alice0.recv_msg(&sent_msg).await.chat_id;
|
||||
alice1.recv_msg(&sent_msg).await;
|
||||
let a0b_contact_id = alice0.add_or_lookup_contact_id(&bob).await;
|
||||
let a0b_contact_id = alice0.add_or_lookup_contact_id(bob).await;
|
||||
|
||||
assert_eq!(alice1.get_chat(&bob).await.blocked, Blocked::Request);
|
||||
assert_eq!(alice1.get_chat(bob).await.blocked, Blocked::Request);
|
||||
a0b_chat_id.accept(alice0).await?;
|
||||
sync(alice0, alice1).await;
|
||||
assert_eq!(alice1.get_chat(&bob).await.blocked, Blocked::Not);
|
||||
assert_eq!(alice1.get_chat(bob).await.blocked, Blocked::Not);
|
||||
a0b_chat_id.block(alice0).await?;
|
||||
sync(alice0, alice1).await;
|
||||
assert_eq!(alice1.get_chat(&bob).await.blocked, Blocked::Yes);
|
||||
assert_eq!(alice1.get_chat(bob).await.blocked, Blocked::Yes);
|
||||
a0b_chat_id.unblock(alice0).await?;
|
||||
sync(alice0, alice1).await;
|
||||
assert_eq!(alice1.get_chat(&bob).await.blocked, Blocked::Not);
|
||||
assert_eq!(alice1.get_chat(bob).await.blocked, Blocked::Not);
|
||||
|
||||
// Unblocking a 1:1 chat doesn't unblock the contact currently.
|
||||
Contact::unblock(alice0, a0b_contact_id).await?;
|
||||
|
||||
assert!(!alice1.add_or_lookup_contact(&bob).await.is_blocked());
|
||||
assert!(!alice1.add_or_lookup_contact(bob).await.is_blocked());
|
||||
Contact::block(alice0, a0b_contact_id).await?;
|
||||
sync(alice0, alice1).await;
|
||||
assert!(alice1.add_or_lookup_contact(&bob).await.is_blocked());
|
||||
assert!(alice1.add_or_lookup_contact(bob).await.is_blocked());
|
||||
Contact::unblock(alice0, a0b_contact_id).await?;
|
||||
sync(alice0, alice1).await;
|
||||
assert!(!alice1.add_or_lookup_contact(&bob).await.is_blocked());
|
||||
assert!(!alice1.add_or_lookup_contact(bob).await.is_blocked());
|
||||
|
||||
// Test accepting and blocking groups. This way we test:
|
||||
// - Group chats synchronisation.
|
||||
@@ -3132,7 +3141,7 @@ async fn test_sync_accept_before_first_msg() -> Result<()> {
|
||||
assert_eq!(alice1_contacts.len(), 1);
|
||||
let a1b_contact_id = alice1_contacts[0];
|
||||
let a1b_contact = Contact::get_by_id(alice1, a1b_contact_id).await?;
|
||||
assert_eq!(a1b_contact.get_addr(), "bob@example.net");
|
||||
assert_eq!(a1b_contact.get_addr(), "");
|
||||
assert_eq!(a1b_contact.origin, Origin::CreateChat);
|
||||
let a1b_chat = alice1.get_chat(bob).await;
|
||||
assert_eq!(a1b_chat.blocked, Blocked::Not);
|
||||
@@ -3146,23 +3155,30 @@ async fn test_sync_accept_before_first_msg() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_sync_block_before_first_msg() -> Result<()> {
|
||||
let alice0 = &TestContext::new_alice().await;
|
||||
let alice1 = &TestContext::new_alice().await;
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice0 = &tcm.alice().await;
|
||||
let alice1 = &tcm.alice().await;
|
||||
for a in [alice0, alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
let bob = TestContext::new_bob().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let ba_chat = bob.create_chat(alice0).await;
|
||||
let sent_msg = bob.send_text(ba_chat.id, "hi").await;
|
||||
let rcvd_msg = alice0.recv_msg(&sent_msg).await;
|
||||
let a0b_chat_id = rcvd_msg.chat_id;
|
||||
let a0b_contact_id = rcvd_msg.from_id;
|
||||
assert_eq!(alice0.get_chat(&bob).await.blocked, Blocked::Request);
|
||||
assert_eq!(
|
||||
Chat::load_from_db(alice0, a0b_chat_id).await?.blocked,
|
||||
Blocked::Request
|
||||
);
|
||||
a0b_chat_id.block(alice0).await?;
|
||||
let a0b_contact = Contact::get_by_id(alice0, a0b_contact_id).await?;
|
||||
assert_eq!(a0b_contact.origin, Origin::IncomingUnknownFrom);
|
||||
assert_eq!(alice0.get_chat(&bob).await.blocked, Blocked::Yes);
|
||||
assert_eq!(
|
||||
Chat::load_from_db(alice0, a0b_chat_id).await?.blocked,
|
||||
Blocked::Yes
|
||||
);
|
||||
|
||||
sync(alice0, alice1).await;
|
||||
let alice1_contacts = Contact::get_all(alice1, 0, None).await?;
|
||||
@@ -3172,9 +3188,14 @@ async fn test_sync_block_before_first_msg() -> Result<()> {
|
||||
let a1b_contact_id = rcvd_msg.from_id;
|
||||
let a1b_contact = Contact::get_by_id(alice1, a1b_contact_id).await?;
|
||||
assert_eq!(a1b_contact.origin, Origin::IncomingUnknownFrom);
|
||||
let a1b_chat = alice1.get_chat(&bob).await;
|
||||
assert_eq!(a1b_chat.blocked, Blocked::Yes);
|
||||
assert_eq!(rcvd_msg.chat_id, a1b_chat.id);
|
||||
let ChatIdBlocked {
|
||||
id: a1b_chat_id,
|
||||
blocked: a1b_chat_blocked,
|
||||
} = ChatIdBlocked::lookup_by_contact(alice1, a1b_contact_id)
|
||||
.await?
|
||||
.unwrap();
|
||||
assert_eq!(a1b_chat_blocked, Blocked::Yes);
|
||||
assert_eq!(rcvd_msg.chat_id, a1b_chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3366,8 +3387,8 @@ async fn test_sync_broadcast() -> Result<()> {
|
||||
for a in [alice0, alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
let bob = TestContext::new_bob().await;
|
||||
let a0b_contact_id = alice0.add_or_lookup_contact(&bob).await.id;
|
||||
let bob = &tcm.bob().await;
|
||||
let a0b_contact_id = alice0.add_or_lookup_contact(bob).await.id;
|
||||
|
||||
let a0_broadcast_id = create_broadcast_list(alice0).await?;
|
||||
sync(alice0, alice1).await;
|
||||
@@ -3382,20 +3403,19 @@ async fn test_sync_broadcast() -> Result<()> {
|
||||
assert!(get_chat_contacts(alice1, a1_broadcast_id).await?.is_empty());
|
||||
add_contact_to_chat(alice0, a0_broadcast_id, a0b_contact_id).await?;
|
||||
sync(alice0, alice1).await;
|
||||
let a1b_contact_id = Contact::lookup_id_by_addr(
|
||||
alice1,
|
||||
&bob.get_config(Config::Addr).await?.unwrap(),
|
||||
Origin::Hidden,
|
||||
)
|
||||
.await?
|
||||
.unwrap();
|
||||
|
||||
// This also imports Bob's key from the vCard.
|
||||
// Otherwise it is possible that second device
|
||||
// does not have Bob's key as only the fingerprint
|
||||
// is transferred in the sync message.
|
||||
let a1b_contact_id = alice1.add_or_lookup_contact(bob).await.id;
|
||||
assert_eq!(
|
||||
get_chat_contacts(alice1, a1_broadcast_id).await?,
|
||||
vec![a1b_contact_id]
|
||||
);
|
||||
let sent_msg = alice1.send_text(a1_broadcast_id, "hi").await;
|
||||
let msg = bob.recv_msg(&sent_msg).await;
|
||||
let chat = Chat::load_from_db(&bob, msg.chat_id).await?;
|
||||
let chat = Chat::load_from_db(bob, msg.chat_id).await?;
|
||||
assert_eq!(chat.get_type(), Chattype::Mailinglist);
|
||||
let msg = alice0.recv_msg(&sent_msg).await;
|
||||
assert_eq!(msg.chat_id, a0_broadcast_id);
|
||||
@@ -3514,7 +3534,7 @@ async fn test_info_contact_id() -> Result<()> {
|
||||
expected_bob_id: ContactId,
|
||||
) -> Result<()> {
|
||||
let sent_msg = alice.pop_sent_msg().await;
|
||||
let msg = Message::load_from_db(alice, sent_msg.sender_msg_id).await?;
|
||||
let msg = sent_msg.load_from_db().await;
|
||||
assert_eq!(msg.get_info_type(), expected_type);
|
||||
assert_eq!(
|
||||
msg.get_info_contact_id(alice).await?,
|
||||
@@ -3677,19 +3697,24 @@ async fn test_past_members() -> Result<()> {
|
||||
let fiona = &tcm.fiona().await;
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
tcm.section("Alice creates a chat.");
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
alice
|
||||
.send_text(alice_chat_id, "Hi! I created a group.")
|
||||
.await;
|
||||
|
||||
tcm.section("Alice removes Fiona from the chat.");
|
||||
remove_contact_from_chat(alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
assert_eq!(get_past_chat_contacts(alice, alice_chat_id).await?.len(), 1);
|
||||
|
||||
tcm.section("Alice adds Bob to the chat.");
|
||||
let bob = &tcm.bob().await;
|
||||
let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
|
||||
tcm.section("Bob receives a message.");
|
||||
let add_message = alice.pop_sent_msg().await;
|
||||
let bob_add_message = bob.recv_msg(&add_message).await;
|
||||
let bob_chat_id = bob_add_message.chat_id;
|
||||
@@ -4212,5 +4237,108 @@ async fn test_oneone_gossip() -> Result<()> {
|
||||
assert_eq!(rcvd_msg2.get_showpadlock(), true);
|
||||
assert_eq!(rcvd_msg2.text, "Hello from second device!");
|
||||
|
||||
tcm.section("Alice sends another message from the first devicer");
|
||||
let sent_msg3 = alice.send_text(alice_chat.id, "Hello again, Bob!").await;
|
||||
|
||||
// This message has no Autocrypt-Gossip header,
|
||||
// but should still be assigned to key-contact.
|
||||
tcm.section("Alice receives a copy of another message on second device");
|
||||
let rcvd_msg3 = alice2.recv_msg(&sent_msg3).await;
|
||||
assert_eq!(rcvd_msg3.get_showpadlock(), true);
|
||||
assert_eq!(rcvd_msg3.chat_id, rcvd_msg.chat_id);
|
||||
|
||||
// Check that there was no gossip.
|
||||
let parsed_msg3 = alice2.parse_msg(&sent_msg3).await;
|
||||
assert!(!parsed_msg3.header_exists(HeaderDef::AutocryptGossip));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that address-contacts cannot be added to encrypted group chats.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_address_contacts_in_group_chats() -> 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_chat(alice, ProtectionStatus::Unprotected, "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;
|
||||
|
||||
// key-contact should be added successfully.
|
||||
add_contact_to_chat(alice, chat_id, bob_key_contact_id).await?;
|
||||
|
||||
// Adding address-contact should fail.
|
||||
let res = add_contact_to_chat(alice, chat_id, charlie_address_contact_id).await;
|
||||
assert!(res.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that key-contacts cannot be added to ad hoc groups.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_key_contacts_in_adhoc_chats() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
let chat_id = receive_imf(
|
||||
alice,
|
||||
b"Subject: Email thread\r\n\
|
||||
From: alice@example.org\r\n\
|
||||
To: Bob <bob@example.net>, Fiona <fiona@example.net>\r\n\
|
||||
Date: Mon, 2 Dec 2023 16:59:39 +0000\r\n\
|
||||
Message-ID: <alice-mail@example.org>\r\n\
|
||||
\r\n\
|
||||
Starting a new thread\r\n",
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.unwrap()
|
||||
.chat_id;
|
||||
|
||||
let bob_address_contact_id = alice.add_or_lookup_address_contact_id(bob).await;
|
||||
let charlie_key_contact_id = alice.add_or_lookup_contact_id(charlie).await;
|
||||
|
||||
// Address-contact should be added successfully.
|
||||
add_contact_to_chat(alice, chat_id, bob_address_contact_id).await?;
|
||||
|
||||
// Adding key-contact should fail.
|
||||
let res = add_contact_to_chat(alice, chat_id, charlie_key_contact_id).await;
|
||||
assert!(res.is_err());
|
||||
|
||||
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<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
|
||||
let chat_id = receive_imf(
|
||||
alice,
|
||||
b"Subject: Email thread\r\n\
|
||||
From: alice@example.org\r\n\
|
||||
To: Bob <bob@example.net>, Fiona <fiona@example.net>\r\n\
|
||||
Date: Mon, 2 Dec 2023 16:59:39 +0000\r\n\
|
||||
Message-ID: <alice-mail@example.org>\r\n\
|
||||
\r\n\
|
||||
Starting a new thread\r\n",
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.unwrap()
|
||||
.chat_id;
|
||||
|
||||
// Test that setting avatar in ad hoc group is not possible.
|
||||
let file = alice.dir.path().join("avatar.png");
|
||||
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
|
||||
tokio::fs::write(&file, bytes).await?;
|
||||
let res = set_chat_profile_image(alice, chat_id, file.to_str().unwrap()).await;
|
||||
assert!(res.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user