mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
feat!: Withdraw broadcast invites. Add Qr::WithdrawJoinBroadcast and Qr::ReviveJoinBroadcast QR code types. (#7439)
Add the ability to withdraw broadcast invite codes After merging: - [x] Create issues in iOS, Desktop and UT repositories
This commit is contained in:
@@ -2578,8 +2578,10 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
||||
#define DC_QR_ERROR 400 // text1=error string
|
||||
#define DC_QR_WITHDRAW_VERIFYCONTACT 500
|
||||
#define DC_QR_WITHDRAW_VERIFYGROUP 502 // text1=groupname
|
||||
#define DC_QR_WITHDRAW_JOINBROADCAST 504 // text1=broadcast name
|
||||
#define DC_QR_REVIVE_VERIFYCONTACT 510
|
||||
#define DC_QR_REVIVE_VERIFYGROUP 512 // text1=groupname
|
||||
#define DC_QR_REVIVE_JOINBROADCAST 514 // text1=broadcast name
|
||||
#define DC_QR_LOGIN 520 // text1=email_address
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,8 +58,10 @@ impl Lot {
|
||||
Qr::Text { text } => Some(Cow::Borrowed(text)),
|
||||
Qr::WithdrawVerifyContact { .. } => None,
|
||||
Qr::WithdrawVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||
Qr::WithdrawJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
|
||||
Qr::ReviveVerifyContact { .. } => None,
|
||||
Qr::ReviveVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||
Qr::ReviveJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
|
||||
Qr::Login { address, .. } => Some(Cow::Borrowed(address)),
|
||||
},
|
||||
Self::Error(err) => Some(Cow::Borrowed(err)),
|
||||
@@ -112,8 +114,10 @@ impl Lot {
|
||||
Qr::Text { .. } => LotState::QrText,
|
||||
Qr::WithdrawVerifyContact { .. } => LotState::QrWithdrawVerifyContact,
|
||||
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
|
||||
Qr::WithdrawJoinBroadcast { .. } => LotState::QrWithdrawJoinBroadcast,
|
||||
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
|
||||
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
|
||||
Qr::ReviveJoinBroadcast { .. } => LotState::QrReviveJoinBroadcast,
|
||||
Qr::Login { .. } => LotState::QrLogin,
|
||||
},
|
||||
Self::Error(_err) => LotState::QrError,
|
||||
@@ -138,9 +142,11 @@ impl Lot {
|
||||
Qr::Url { .. } => Default::default(),
|
||||
Qr::Text { .. } => Default::default(),
|
||||
Qr::WithdrawVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::WithdrawVerifyGroup { .. } => Default::default(),
|
||||
Qr::WithdrawVerifyGroup { .. } | Qr::WithdrawJoinBroadcast { .. } => {
|
||||
Default::default()
|
||||
}
|
||||
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::ReviveVerifyGroup { .. } => Default::default(),
|
||||
Qr::ReviveVerifyGroup { .. } | Qr::ReviveJoinBroadcast { .. } => Default::default(),
|
||||
Qr::Login { .. } => Default::default(),
|
||||
},
|
||||
Self::Error(_) => Default::default(),
|
||||
@@ -207,11 +213,15 @@ pub enum LotState {
|
||||
|
||||
/// text1=groupname
|
||||
QrWithdrawVerifyGroup = 502,
|
||||
/// text1=broadcast channel name
|
||||
QrWithdrawJoinBroadcast = 504,
|
||||
|
||||
QrReviveVerifyContact = 510,
|
||||
|
||||
/// text1=groupname
|
||||
QrReviveVerifyGroup = 512,
|
||||
/// text1=groupname
|
||||
QrReviveJoinBroadcast = 514,
|
||||
|
||||
/// text1=email_address
|
||||
QrLogin = 520,
|
||||
|
||||
@@ -157,6 +157,21 @@ pub enum QrObject {
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
},
|
||||
/// Ask the user if they want to withdraw their own broadcast channel invite QR code.
|
||||
WithdrawJoinBroadcast {
|
||||
/// Broadcast name.
|
||||
name: String,
|
||||
/// ID, uniquely identifying this chat. Called grpid for historic reasons.
|
||||
grpid: String,
|
||||
/// Contact ID. Always `ContactId::SELF`.
|
||||
contact_id: u32,
|
||||
/// Fingerprint of the contact key as scanned from the QR code.
|
||||
fingerprint: String,
|
||||
/// Invite number.
|
||||
invitenumber: String,
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
},
|
||||
/// Ask the user if they want to revive their own QR code.
|
||||
ReviveVerifyContact {
|
||||
/// Contact ID.
|
||||
@@ -183,6 +198,21 @@ pub enum QrObject {
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
},
|
||||
/// Ask the user if they want to revive their own broadcast channel invite QR code.
|
||||
ReviveJoinBroadcast {
|
||||
/// Broadcast name.
|
||||
name: String,
|
||||
/// Globally unique chat ID. Called grpid for historic reasons.
|
||||
grpid: String,
|
||||
/// Contact ID. Always `ContactId::SELF`.
|
||||
contact_id: u32,
|
||||
/// Fingerprint of the contact key as scanned from the QR code.
|
||||
fingerprint: String,
|
||||
/// Invite number.
|
||||
invitenumber: String,
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
},
|
||||
/// `dclogin:` scheme parameters.
|
||||
///
|
||||
/// Ask the user if they want to login with the email address.
|
||||
@@ -306,6 +336,25 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::WithdrawJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::WithdrawJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
@@ -340,6 +389,25 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::ReviveJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::Login { address, .. } => QrObject::Login { address },
|
||||
}
|
||||
}
|
||||
|
||||
104
src/qr.rs
104
src/qr.rs
@@ -233,6 +233,31 @@ pub enum Qr {
|
||||
authcode: String,
|
||||
},
|
||||
|
||||
/// Ask the user if they want to withdraw their own broadcast channel invite QR code.
|
||||
WithdrawJoinBroadcast {
|
||||
/// The user-visible name of this broadcast channel
|
||||
name: String,
|
||||
|
||||
/// A string of random characters,
|
||||
/// uniquely identifying this broadcast channel across all databases/clients.
|
||||
/// Called `grpid` for historic reasons:
|
||||
/// The id of multi-user chats is always called `grpid` in the database
|
||||
/// because groups were once the only multi-user chats.
|
||||
grpid: String,
|
||||
|
||||
/// Contact ID. Always `ContactId::SELF`.
|
||||
contact_id: ContactId,
|
||||
|
||||
/// Fingerprint of the contact's key as scanned from the QR code.
|
||||
fingerprint: Fingerprint,
|
||||
|
||||
/// Invite number.
|
||||
invitenumber: String,
|
||||
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
},
|
||||
|
||||
/// Ask the user if they want to revive their own QR code.
|
||||
ReviveVerifyContact {
|
||||
/// Contact ID.
|
||||
@@ -269,6 +294,31 @@ pub enum Qr {
|
||||
authcode: String,
|
||||
},
|
||||
|
||||
/// Ask the user if they want to revive their own broadcast channel invite QR code.
|
||||
ReviveJoinBroadcast {
|
||||
/// The user-visible name of this broadcast channel
|
||||
name: String,
|
||||
|
||||
/// A string of random characters,
|
||||
/// uniquely identifying this broadcast channel across all databases/clients.
|
||||
/// Called `grpid` for historic reasons:
|
||||
/// The id of multi-user chats is always called `grpid` in the database
|
||||
/// because groups were once the only multi-user chats.
|
||||
grpid: String,
|
||||
|
||||
/// Contact ID. Always `ContactId::SELF`.
|
||||
contact_id: ContactId,
|
||||
|
||||
/// Fingerprint of the contact's key as scanned from the QR code.
|
||||
fingerprint: Fingerprint,
|
||||
|
||||
/// Invite number.
|
||||
invitenumber: String,
|
||||
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
},
|
||||
|
||||
/// `dclogin:` scheme parameters.
|
||||
///
|
||||
/// Ask the user if they want to login with the email address.
|
||||
@@ -500,14 +550,40 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
})
|
||||
}
|
||||
} else if let (Some(grpid), Some(name)) = (grpid, broadcast_name) {
|
||||
Ok(Qr::AskJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
})
|
||||
if context
|
||||
.is_self_addr(&addr)
|
||||
.await
|
||||
.with_context(|| format!("Can't check if {addr:?} is our address"))?
|
||||
{
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||
Ok(Qr::WithdrawJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
})
|
||||
} else {
|
||||
Ok(Qr::ReviveJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Ok(Qr::AskJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
})
|
||||
}
|
||||
} else if context.is_self_addr(&addr).await? {
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||
Ok(Qr::WithdrawVerifyContact {
|
||||
@@ -800,6 +876,12 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
invitenumber,
|
||||
authcode,
|
||||
..
|
||||
}
|
||||
| Qr::WithdrawJoinBroadcast {
|
||||
grpid,
|
||||
invitenumber,
|
||||
authcode,
|
||||
..
|
||||
} => {
|
||||
token::delete(context, &grpid).await?;
|
||||
context
|
||||
@@ -829,6 +911,12 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
authcode,
|
||||
grpid,
|
||||
..
|
||||
}
|
||||
| Qr::ReviveJoinBroadcast {
|
||||
invitenumber,
|
||||
authcode,
|
||||
grpid,
|
||||
..
|
||||
} => {
|
||||
let timestamp = time();
|
||||
token::save(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::chat::create_group;
|
||||
use crate::chat::{Chat, create_broadcast, create_group, get_chat_contacts};
|
||||
use crate::config::Config;
|
||||
use crate::login_param::EnteredCertificateChecks;
|
||||
use crate::provider::Socket;
|
||||
@@ -511,6 +511,56 @@ async fn test_withdraw_verifygroup() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_withdraw_joinbroadcast() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = create_broadcast(alice, "foo".to_string()).await?;
|
||||
let qr = get_securejoin_qr(alice, Some(chat_id)).await?;
|
||||
|
||||
// scanning own verify-group code offers withdrawing
|
||||
if let Qr::WithdrawJoinBroadcast { name, .. } = check_qr(alice, &qr).await? {
|
||||
assert_eq!(name, "foo");
|
||||
} else {
|
||||
bail!("Wrong QR type, expected WithdrawJoinBroadcast");
|
||||
}
|
||||
set_config_from_qr(alice, &qr).await?;
|
||||
|
||||
// scanning withdrawn verify-group code offers reviving
|
||||
if let Qr::ReviveJoinBroadcast { name, .. } = check_qr(alice, &qr).await? {
|
||||
assert_eq!(name, "foo");
|
||||
} else {
|
||||
bail!("Wrong QR type, expected ReviveJoinBroadcast");
|
||||
}
|
||||
|
||||
// someone else always scans as ask-verify-group
|
||||
if let Qr::AskJoinBroadcast { name, .. } = check_qr(bob, &qr).await? {
|
||||
assert_eq!(name, "foo");
|
||||
} else {
|
||||
bail!("Wrong QR type, expected AskJoinBroadcast");
|
||||
}
|
||||
assert!(set_config_from_qr(bob, &qr).await.is_err());
|
||||
|
||||
// Bob can't join using this QR code, since it's still withdrawn
|
||||
let bob_chat_id = tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
let bob_chat = Chat::load_from_db(bob, bob_chat_id).await?;
|
||||
assert_eq!(bob_chat.is_self_in_chat(bob).await?, false);
|
||||
assert_eq!(get_chat_contacts(alice, chat_id).await?.len(), 0);
|
||||
|
||||
// Revive
|
||||
set_config_from_qr(alice, &qr).await?;
|
||||
|
||||
// Now Bob can join
|
||||
let bob_chat_id2 = tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
assert_eq!(bob_chat_id, bob_chat_id2);
|
||||
let bob_chat = Chat::load_from_db(bob, bob_chat_id).await?;
|
||||
assert_eq!(bob_chat.is_self_in_chat(bob).await?, true);
|
||||
assert_eq!(get_chat_contacts(alice, chat_id).await?.len(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_withdraw_multidevice() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
|
||||
Reference in New Issue
Block a user