api!: get rid of Qr::FprWithoutAddr variant

We don't look at the address in OPENPGP4FPR QR codes anymore.
If we cannot find a contact by fingerprint,
then we return Qr::FprMismatch without trying to look up
the contact by address.

BREAKING CHANGE: removed DC_QR_FPR_WITHOUT_ADDR constant from CFFI
BREAKING CHANGE: removed FprWithoutAddr variant from JSON-RPC QrObject returned by check_qr
This commit is contained in:
link2xt
2026-09-16 00:23:32 +00:00
parent fae6a63023
commit 527570be8e
5 changed files with 38 additions and 60 deletions

View File

@@ -2456,7 +2456,6 @@ void dc_stop_ongoing_process (dc_context_t* context);
#define DC_QR_ASK_VERIFYBROADCAST 204 // text1=broadcast name #define DC_QR_ASK_VERIFYBROADCAST 204 // text1=broadcast name
#define DC_QR_FPR_OK 210 // id=contact #define DC_QR_FPR_OK 210 // id=contact
#define DC_QR_FPR_MISMATCH 220 // id=contact #define DC_QR_FPR_MISMATCH 220 // id=contact
#define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint
#define DC_QR_ACCOUNT 250 // text1=domain #define DC_QR_ACCOUNT 250 // text1=domain
#define DC_QR_BACKUP2 252 #define DC_QR_BACKUP2 252
#define DC_QR_BACKUP_TOO_NEW 255 #define DC_QR_BACKUP_TOO_NEW 255
@@ -2498,11 +2497,8 @@ void dc_stop_ongoing_process (dc_context_t* context);
* if so, call dc_create_chat_by_contact_id(). * if so, call dc_create_chat_by_contact_id().
* *
* - DC_QR_FPR_MISMATCH with dc_lot_t::id=Contact ID: * - DC_QR_FPR_MISMATCH with dc_lot_t::id=Contact ID:
* scanned fingerprint does not match last seen fingerprint. * scanned fingerprint does not match any contact
* * or the key for this contact is not available.
* - DC_QR_FPR_WITHOUT_ADDR with dc_lot_t::text1=Formatted fingerprint
* the scanned QR code contains a fingerprint but no e-mail address;
* suggest the user to establish an encrypted connection first.
* *
* - DC_QR_ACCOUNT dc_lot_t::text1=domain: * - DC_QR_ACCOUNT dc_lot_t::text1=domain:
* ask the user if they want to create an account on the given domain, * ask the user if they want to create an account on the given domain,

View File

@@ -48,7 +48,6 @@ impl Lot {
Qr::AskJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)), Qr::AskJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
Qr::FprOk { .. } => None, Qr::FprOk { .. } => None,
Qr::FprMismatch { .. } => None, Qr::FprMismatch { .. } => None,
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
Qr::Account { domain } => Some(Cow::Borrowed(domain)), Qr::Account { domain } => Some(Cow::Borrowed(domain)),
Qr::Backup2 { .. } => None, Qr::Backup2 { .. } => None,
Qr::BackupTooNew { .. } => None, Qr::BackupTooNew { .. } => None,
@@ -104,7 +103,6 @@ impl Lot {
Qr::AskJoinBroadcast { .. } => LotState::QrAskJoinBroadcast, Qr::AskJoinBroadcast { .. } => LotState::QrAskJoinBroadcast,
Qr::FprOk { .. } => LotState::QrFprOk, Qr::FprOk { .. } => LotState::QrFprOk,
Qr::FprMismatch { .. } => LotState::QrFprMismatch, Qr::FprMismatch { .. } => LotState::QrFprMismatch,
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
Qr::Account { .. } => LotState::QrAccount, Qr::Account { .. } => LotState::QrAccount,
Qr::Backup2 { .. } => LotState::QrBackup2, Qr::Backup2 { .. } => LotState::QrBackup2,
Qr::BackupTooNew { .. } => LotState::QrBackupTooNew, Qr::BackupTooNew { .. } => LotState::QrBackupTooNew,
@@ -133,7 +131,6 @@ impl Lot {
Qr::AskJoinBroadcast { .. } => Default::default(), Qr::AskJoinBroadcast { .. } => Default::default(),
Qr::FprOk { contact_id } => contact_id.to_u32(), Qr::FprOk { contact_id } => contact_id.to_u32(),
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(), Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
Qr::FprWithoutAddr { .. } => Default::default(),
Qr::Account { .. } => Default::default(), Qr::Account { .. } => Default::default(),
Qr::Backup2 { .. } => Default::default(), Qr::Backup2 { .. } => Default::default(),
Qr::BackupTooNew { .. } => Default::default(), Qr::BackupTooNew { .. } => Default::default(),
@@ -184,9 +181,6 @@ pub enum LotState {
/// id=contact /// id=contact
QrFprMismatch = 220, QrFprMismatch = 220,
/// text1=formatted fingerprint
QrFprWithoutAddr = 230,
/// text1=domain /// text1=domain
QrAccount = 250, QrAccount = 250,

View File

@@ -73,11 +73,6 @@ pub enum QrObject {
/// Contact ID. /// Contact ID.
contact_id: Option<u32>, contact_id: Option<u32>,
}, },
/// The scanned QR code contains a fingerprint but no e-mail address.
FprWithoutAddr {
/// Key fingerprint.
fingerprint: String,
},
/// Ask the user if they want to create an account on the given domain. /// Ask the user if they want to create an account on the given domain.
Account { Account {
/// Server domain name. /// Server domain name.
@@ -300,7 +295,6 @@ impl From<Qr> for QrObject {
let contact_id = contact_id.map(|contact_id| contact_id.to_u32()); let contact_id = contact_id.map(|contact_id| contact_id.to_u32());
QrObject::FprMismatch { contact_id } QrObject::FprMismatch { contact_id }
} }
Qr::FprWithoutAddr { fingerprint } => QrObject::FprWithoutAddr { fingerprint },
Qr::Account { domain } => QrObject::Account { domain }, Qr::Account { domain } => QrObject::Account { domain },
Qr::Backup2 { Qr::Backup2 {
ref node_addr, ref node_addr,

View File

@@ -133,18 +133,15 @@ pub enum Qr {
contact_id: ContactId, contact_id: ContactId,
}, },
/// Scanned fingerprint does not match the last seen fingerprint. /// Scanned fingerprint does not match any contact
/// or the key for this contact is not available.
FprMismatch { FprMismatch {
/// Contact ID. /// Contact ID if key-contact exists, but we have no key.
///
/// `None` if there is no contact corresponding to the scanned fingerprint.
contact_id: Option<ContactId>, contact_id: Option<ContactId>,
}, },
/// The scanned QR code contains a fingerprint but no e-mail address.
FprWithoutAddr {
/// Key fingerprint.
fingerprint: String,
},
/// Ask the user if they want to create an account on the given domain. /// Ask the user if they want to create an account on the given domain.
Account { Account {
/// Server domain name. /// Server domain name.
@@ -651,24 +648,28 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
is_v3, is_v3,
}) })
} }
} else if let Some(addr) = addrs.first() {
let fingerprint = fingerprint.hex();
let (contact_id, _) =
Contact::add_or_lookup_ext(context, "", addr, &fingerprint, Origin::UnhandledQrScan)
.await?;
let contact = Contact::get_by_id(context, contact_id).await?;
if contact.public_key(context).await?.is_some() {
Ok(Qr::FprOk { contact_id })
} else {
Ok(Qr::FprMismatch {
contact_id: Some(contact_id),
})
}
} else { } else {
Ok(Qr::FprWithoutAddr { let fingerprint = fingerprint.hex();
fingerprint: fingerprint.human_readable(), let contact_id: Option<ContactId> = context
}) .sql
.query_get_value(
"SELECT id FROM contacts WHERE fingerprint=?",
(fingerprint,),
)
.await?;
if let Some(contact_id) = contact_id {
let contact = Contact::get_by_id(context, contact_id).await?;
if contact.public_key(context).await?.is_some() {
Ok(Qr::FprOk { contact_id })
} else {
Ok(Qr::FprMismatch {
contact_id: Some(contact_id),
})
}
} else {
Ok(Qr::FprMismatch { contact_id: None })
}
} }
} }

View File

@@ -373,16 +373,15 @@ async fn test_decode_openpgp_fingerprint() -> Result<()> {
let alice_contact = bob.add_or_lookup_contact(alice).await; let alice_contact = bob.add_or_lookup_contact(alice).await;
let alice_contact_id = alice_contact.id; let alice_contact_id = alice_contact.id;
// OPENPGP4FPR may have an address,
// but it is not used anymore since key contacts are introduced.
// We lookup the contact only by fingerprint and ignore the address.
let qr = check_qr( let qr = check_qr(
bob, bob,
"OPENPGP4FPR:1234567890123456789012345678901234567890#a=alice@example.org", "OPENPGP4FPR:1234567890123456789012345678901234567890#a=alice@example.org",
) )
.await?; .await?;
if let Qr::FprMismatch { contact_id, .. } = qr { assert_eq!(qr, Qr::FprMismatch { contact_id: None });
assert_ne!(contact_id.unwrap(), alice_contact_id);
} else {
bail!("Wrong QR code type");
}
let qr = check_qr( let qr = check_qr(
bob, bob,
@@ -410,6 +409,10 @@ async fn test_decode_openpgp_fingerprint() -> Result<()> {
Ok(()) Ok(())
} }
/// Tests OPENPGP4FPR QR codes without an email address.
///
/// Email address in OPENPGP4FPR QR codes was an extension
/// used before switch to identifying contacts by fingerprint.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decode_openpgp_without_addr() -> Result<()> { async fn test_decode_openpgp_without_addr() -> Result<()> {
let ctx = TestContext::new().await; let ctx = TestContext::new().await;
@@ -419,12 +422,7 @@ async fn test_decode_openpgp_without_addr() -> Result<()> {
"OPENPGP4FPR:1234567890123456789012345678901234567890", "OPENPGP4FPR:1234567890123456789012345678901234567890",
) )
.await?; .await?;
assert_eq!( assert_eq!(qr, Qr::FprMismatch { contact_id: None });
qr,
Qr::FprWithoutAddr {
fingerprint: "1234 5678 9012 3456 7890\n1234 5678 9012 3456 7890".to_string()
}
);
// Test it again with lowercased "openpgp4fpr:" uri scheme // Test it again with lowercased "openpgp4fpr:" uri scheme
@@ -433,12 +431,7 @@ async fn test_decode_openpgp_without_addr() -> Result<()> {
"openpgp4fpr:1234567890123456789012345678901234567890", "openpgp4fpr:1234567890123456789012345678901234567890",
) )
.await?; .await?;
assert_eq!( assert_eq!(qr, Qr::FprMismatch { contact_id: None });
qr,
Qr::FprWithoutAddr {
fingerprint: "1234 5678 9012 3456 7890\n1234 5678 9012 3456 7890".to_string()
}
);
let res = check_qr(&ctx.ctx, "OPENPGP4FPR:12345678901234567890").await; let res = check_qr(&ctx.ctx, "OPENPGP4FPR:12345678901234567890").await;
assert!(res.is_err()); assert!(res.is_err());