From 48416289ac0f0bb583b79786adbed4131d052569 Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 7 Nov 2023 02:47:59 +0000 Subject: [PATCH] api: add dc_contact_is_profile_verified() --- deltachat-ffi/deltachat.h | 27 ++++++++++++++------ deltachat-ffi/src/lib.rs | 15 +++++++++++ deltachat-jsonrpc/src/api/types/contact.rs | 9 ++++++- deltachat-rpc-client/tests/test_something.py | 12 +++++++++ src/chat.rs | 15 +++++++++-- src/contact.rs | 20 +++++++++++++-- 6 files changed, 85 insertions(+), 13 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 74eee7c6f..1b0ddc39f 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3743,13 +3743,9 @@ int dc_chat_can_send (const dc_chat_t* chat); * if `verified_one_on_one_chats` setting is enabled. * * UI should display a green checkmark - * in the chat title, - * in the chat profile title and + * in the chat title and * in the chatlist item * if chat protection is enabled. - * UI should also display a green checkmark - * in the contact profile - * if 1:1 chat with this contact exists and is protected. * * @memberof dc_chat_t * @param chat The chat object. @@ -5061,9 +5057,7 @@ int dc_contact_is_blocked (const dc_contact_t* contact); * * Do not use this function when displaying the contact profile view. * Display green checkmark in the title of the contact profile - * if 1:1 chat with the contact exists and is protected. - * Use dc_contact_get_verified_id to display the verifier contact - * in the info section of the contact profile. + * if dc_contact_profile_is_verified() returns true. * * @memberof dc_contact_t * @param contact The contact object. @@ -5073,6 +5067,23 @@ int dc_contact_is_blocked (const dc_contact_t* contact); int dc_contact_is_verified (dc_contact_t* contact); +/** + * Check if the contact profile title + * should contain a green checkmark. + * This indicates whether 1:1 chat with + * this contact has a green checkmark + * or will have if such chat is created. + * + * Use dc_contact_get_verified_id to display the verifier contact + * in the info section of the contact profile. + * + * @memberof dc_contact_t + * @param contact The contact object. + * @return 1=contact profile has a green checkmark in the title, + * 0=contact profile has no green checkmark in the title. + */ +int dc_contact_profile_is_verified (dc_contact_t* contact); + /** * Return the contact ID that verified a contact. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 081e7198a..ef7ccc166 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -4119,6 +4119,21 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l .unwrap_or_default() as libc::c_int } +#[no_mangle] +pub unsafe extern "C" fn dc_contact_profile_is_verified(contact: *mut dc_contact_t) -> libc::c_int { + if contact.is_null() { + eprintln!("ignoring careless call to dc_contact_profile_is_verified()"); + return 0; + } + let ffi_contact = &*contact; + let ctx = &*ffi_contact.context; + + block_on(ffi_contact.contact.is_profile_verified(ctx)) + .context("is_profile_verified failed") + .log_err(ctx) + .unwrap_or_default() as libc::c_int +} + #[no_mangle] pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t) -> u32 { if contact.is_null() { diff --git a/deltachat-jsonrpc/src/api/types/contact.rs b/deltachat-jsonrpc/src/api/types/contact.rs index 0a16ac2f0..c9e3ef79f 100644 --- a/deltachat-jsonrpc/src/api/types/contact.rs +++ b/deltachat-jsonrpc/src/api/types/contact.rs @@ -24,11 +24,16 @@ pub struct ContactObject { /// /// If this is true /// UI should display green checkmark after the contact name - /// in the title of the contact profile, /// in contact list items and /// in chat member list items. is_verified: bool, + /// True if the contact profile title should have a green checkmark. + /// + /// This indicates whether 1:1 chat has a green checkmark + /// or will have a green checkmark if created. + is_profile_verified: bool, + /// The ID of the contact that verified this contact. /// /// If this is present, @@ -52,6 +57,7 @@ impl ContactObject { None => None, }; let is_verified = contact.is_verified(context).await? == VerifiedStatus::BidirectVerified; + let is_profile_verified = contact.is_profile_verified(context).await?; let verifier_id = contact .get_verifier_id(context) @@ -70,6 +76,7 @@ impl ContactObject { name_and_addr: contact.get_name_n_addr(), is_blocked: contact.is_blocked(), is_verified, + is_profile_verified, verifier_id, last_seen: contact.last_seen(), was_seen_recently: contact.was_seen_recently(), diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 987288942..bcb4d21a8 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -391,6 +391,18 @@ def test_qr_setup_contact(acfactory) -> None: if event["kind"] == "SecurejoinInviterProgress" and event["progress"] == 1000: return + # Test that scanning Alice's QR code verifies Alice's profile. + bob_contact_alice = bob.get_contact_by_addr(alice.get_config("addr")) + bob_contact_alice_snapshot = bob_contact_alice.get_snapshot() + assert bob_contact_alice_snapshot.is_verified + assert bob_contact_alice_snapshot.is_profile_verified + + # Test that Alice symmetrically verified Bob's profile. + alice_contact_bob = alice.get_contact_by_addr(bob.get_config("addr")) + alice_contact_bob_snapshot = alice_contact_bob.get_snapshot() + assert alice_contact_bob_snapshot.is_verified + assert alice_contact_bob_snapshot.is_profile_verified + @pytest.mark.xfail() def test_verified_group_recovery(acfactory, rpc) -> None: diff --git a/src/chat.rs b/src/chat.rs index 40abe7681..c08b351af 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -208,9 +208,10 @@ impl ChatId { self == DC_CHAT_ID_ALLDONE_HINT } - /// Returns the [`ChatId`] for the 1:1 chat with `contact_id` if it exists. + /// Returns the [`ChatId`] for the 1:1 chat with `contact_id` + /// if it exists and is not blocked. /// - /// If it does not exist, `None` is returned. + /// If the chat does not exist or is blocked, `None` is returned. pub async fn lookup_by_contact( context: &Context, contact_id: ContactId, @@ -1251,6 +1252,16 @@ impl ChatId { Ok(()) } + + /// Returns true if the chat is protected. + pub async fn is_protected(self, context: &Context) -> Result { + let protection_status = context + .sql + .query_get_value("SELECT protected FROM chats WHERE id=?", (self,)) + .await? + .unwrap_or_default(); + Ok(protection_status) + } } impl std::fmt::Display for ChatId { diff --git a/src/contact.rs b/src/contact.rs index 62c4b5711..979acf677 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -19,7 +19,7 @@ use tokio::task; use tokio::time::{timeout, Duration}; use crate::aheader::EncryptPreference; -use crate::chat::ChatId; +use crate::chat::{ChatId, ProtectionStatus}; use crate::color::str_to_color; use crate::config::Config; use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY}; @@ -1263,7 +1263,7 @@ impl Contact { /// /// Do not use this function when displaying the contact profile view. /// Display green checkmark in the title of the contact profile - /// if 1:1 chat with the contact exists and is protected. + /// if [Self::is_profile_verified] returns true. /// Use [Self::get_verifier_id] to display the verifier contact /// in the info section of the contact profile. pub async fn is_verified(&self, context: &Context) -> Result { @@ -1317,6 +1317,22 @@ impl Contact { } } + /// Returns if the contact profile title should display a green checkmark. + /// + /// This generally should be consistent with the 1:1 chat with the contact + /// so 1:1 chat with the contact and the contact profile + /// either both display the green checkmark or both don't display a green checkmark. + pub async fn is_profile_verified(&self, context: &Context) -> Result { + let contact_id = self.id; + + if let Some(chat_id) = ChatId::lookup_by_contact(context, contact_id).await? { + Ok(chat_id.is_protected(context).await? == ProtectionStatus::Protected) + } else { + // 1:1 chat does not exist. + Ok(self.is_verified(context).await? == VerifiedStatus::BidirectVerified) + } + } + /// Returns the number of real (i.e. non-special) contacts in the database. pub async fn get_real_cnt(context: &Context) -> Result { if !context.sql.is_open().await {