diff --git a/CHANGELOG.md b/CHANGELOG.md index 20262d261..4b81228ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - send normal messages with higher priority than MDNs #3243 - make Scheduler stateless #3302 - support `source_code_url` from Webxdc manifests #3314 +- improve chat encryption info, make it easier to find contacts without keys #3318 ### API-Changes - deprecate unused `marker1before` argument of `dc_get_chat_msgs` diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 7d19ec57d..b00c1bea4 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -689,7 +689,7 @@ def test_gossip_encryption_preference(acfactory, lp): msg = ac1._evtracker.wait_next_incoming_message() assert msg.text == "first message" assert not msg.is_encrypted() - res = "{} End-to-end encryption preferred.".format(ac2.get_config('addr')) + res = "End-to-end encryption preferred:\n{}\n".format(ac2.get_config('addr')) assert msg.chat.get_encryption_info() == res lp.sec("ac2 learns that ac3 prefers encryption") ac2.create_chat(ac3) @@ -701,7 +701,7 @@ def test_gossip_encryption_preference(acfactory, lp): lp.sec("ac3 does not know that ac1 prefers encryption") ac1.create_chat(ac3) chat = ac3.create_chat(ac1) - res = "{} No encryption.".format(ac1.get_config('addr')) + res = "No encryption:\n{}\n".format(ac1.get_config('addr')) assert chat.get_encryption_info() == res msg = chat.send_text("not encrypted") msg = ac1._evtracker.wait_next_incoming_message() @@ -712,7 +712,7 @@ def test_gossip_encryption_preference(acfactory, lp): group_chat = ac1.create_group_chat("hello") group_chat.add_contact(ac2) encryption_info = group_chat.get_encryption_info() - res = "{} End-to-end encryption preferred.".format(ac2.get_config("addr")) + res = "End-to-end encryption preferred:\n{}\n".format(ac2.get_config("addr")) assert encryption_info == res msg = group_chat.send_text("hi") @@ -727,10 +727,9 @@ def test_gossip_encryption_preference(acfactory, lp): lp.sec("ac3 learns that ac1 prefers encryption") msg = ac3._evtracker.wait_next_incoming_message() encryption_info = msg.chat.get_encryption_info().splitlines() - res = "{} End-to-end encryption preferred.".format(ac1.get_config("addr")) - assert res in encryption_info - res = "{} End-to-end encryption preferred.".format(ac2.get_config("addr")) - assert res in encryption_info + assert encryption_info[0] == "End-to-end encryption preferred:" + assert ac1.get_config("addr") in encryption_info[1:] + assert ac2.get_config("addr") in encryption_info[1:] msg = chat.send_text("encrypted") assert msg.is_encrypted() diff --git a/src/chat.rs b/src/chat.rs index bbb6f9cdf..55c815d77 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -842,7 +842,9 @@ impl ChatId { /// /// To get more verbose summary for a contact, including its key fingerprint, use [`Contact::get_encrinfo`]. pub async fn get_encryption_info(self, context: &Context) -> Result { - let mut ret = String::new(); + let mut ret_mutual = String::new(); + let mut ret_nopreference = String::new(); + let mut ret_reset = String::new(); for contact_id in get_chat_contacts(context, self) .await? @@ -853,7 +855,7 @@ impl ChatId { let addr = contact.get_addr(); let peerstate = Peerstate::from_addr(context, addr).await?; - let stock_message = match peerstate + match peerstate .filter(|peerstate| { peerstate .peek_key(PeerstateVerifiedStatus::Unverified) @@ -861,15 +863,36 @@ impl ChatId { }) .map(|peerstate| peerstate.prefer_encrypt) { - Some(EncryptPreference::Mutual) => stock_str::e2e_preferred(context).await, - Some(EncryptPreference::NoPreference) => stock_str::e2e_available(context).await, - Some(EncryptPreference::Reset) => stock_str::encr_none(context).await, - None => stock_str::encr_none(context).await, + Some(EncryptPreference::Mutual) => ret_mutual += &format!("{}\n", addr), + Some(EncryptPreference::NoPreference) => ret_nopreference += &format!("{}\n", addr), + Some(EncryptPreference::Reset) | None => ret_reset += &format!("{}\n", addr), }; + } + + let mut ret = String::new(); + if !ret_reset.is_empty() { + ret += &stock_str::encr_none(context).await; + ret.push(':'); + ret.push('\n'); + ret += &ret_reset; + } + if !ret_nopreference.is_empty() { if !ret.is_empty() { - ret.push('\n') + ret.push('\n'); } - ret += &format!("{} {}", addr, stock_message); + ret += &stock_str::e2e_available(context).await; + ret.push(':'); + ret.push('\n'); + ret += &ret_nopreference; + } + if !ret_mutual.is_empty() { + if !ret.is_empty() { + ret.push('\n'); + } + ret += &stock_str::e2e_preferred(context).await; + ret.push(':'); + ret.push('\n'); + ret += &ret_mutual; } Ok(ret) @@ -5374,4 +5397,59 @@ mod tests { Ok(()) } + + #[async_std::test] + async fn test_chat_get_encryption_info() -> Result<()> { + let alice = TestContext::new_alice().await; + let bob = TestContext::new_bob().await; + + let contact_bob = Contact::create(&alice, "Bob", "bob@example.net").await?; + let contact_fiona = Contact::create(&alice, "", "fiona@example.net").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?; + assert_eq!( + chat_id.get_encryption_info(&alice).await?, + "No encryption:\n\ + bob@example.net\n" + ); + + add_contact_to_chat(&alice, chat_id, contact_fiona).await?; + assert_eq!( + chat_id.get_encryption_info(&alice).await?, + "No encryption:\n\ + bob@example.net\n\ + fiona@example.net\n" + ); + + 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; + + assert_eq!( + chat_id.get_encryption_info(&alice).await?, + "No encryption:\n\ + fiona@example.net\n\ + \n\ + End-to-end encryption preferred:\n\ + bob@example.net\n" + ); + + bob.set_config(Config::E2eeEnabled, Some("0")).await?; + send_text_msg(&bob, direct_chat.id, "Hello!".to_string()).await?; + alice.recv_msg(&bob.pop_sent_msg().await).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\n" + ); + + Ok(()) + } } diff --git a/src/contact.rs b/src/contact.rs index 853caccab..54f8ab468 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -889,7 +889,7 @@ impl Contact { }; ret += &format!( - "{}\n{}:", + "{}.\n{}:", stock_message, stock_str::finger_prints(context).await ); @@ -1961,7 +1961,7 @@ mod tests { .await?; let encrinfo = Contact::get_encrinfo(&alice, contact_bob_id).await?; - assert_eq!(encrinfo, "No encryption."); + assert_eq!(encrinfo, "No encryption"); let bob = TestContext::new_bob().await; let chat_alice = bob diff --git a/src/stock_str.rs b/src/stock_str.rs index d1d41ba2a..b340d21c9 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -73,10 +73,10 @@ pub enum StockMessage { #[strum(props(fallback = "Encrypted message"))] EncryptedMsg = 24, - #[strum(props(fallback = "End-to-end encryption available."))] + #[strum(props(fallback = "End-to-end encryption available"))] E2eAvailable = 25, - #[strum(props(fallback = "No encryption."))] + #[strum(props(fallback = "No encryption"))] EncrNone = 28, #[strum(props(fallback = "This message was encrypted for another setup."))] @@ -94,7 +94,7 @@ pub enum StockMessage { #[strum(props(fallback = "Group image deleted."))] MsgGrpImgDeleted = 33, - #[strum(props(fallback = "End-to-end encryption preferred."))] + #[strum(props(fallback = "End-to-end encryption preferred"))] E2ePreferred = 34, #[strum(props(fallback = "%1$s verified."))]