Skip to content

Commit 26f6b85

Browse files
authored
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
1 parent 10b6dd1 commit 26f6b85

File tree

5 files changed

+229
-11
lines changed

5 files changed

+229
-11
lines changed

deltachat-ffi/deltachat.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2578,8 +2578,10 @@ void dc_stop_ongoing_process (dc_context_t* context);
25782578
#define DC_QR_ERROR 400 // text1=error string
25792579
#define DC_QR_WITHDRAW_VERIFYCONTACT 500
25802580
#define DC_QR_WITHDRAW_VERIFYGROUP 502 // text1=groupname
2581+
#define DC_QR_WITHDRAW_JOINBROADCAST 504 // text1=broadcast name
25812582
#define DC_QR_REVIVE_VERIFYCONTACT 510
25822583
#define DC_QR_REVIVE_VERIFYGROUP 512 // text1=groupname
2584+
#define DC_QR_REVIVE_JOINBROADCAST 514 // text1=broadcast name
25832585
#define DC_QR_LOGIN 520 // text1=email_address
25842586

25852587
/**

deltachat-ffi/src/lot.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ impl Lot {
5858
Qr::Text { text } => Some(Cow::Borrowed(text)),
5959
Qr::WithdrawVerifyContact { .. } => None,
6060
Qr::WithdrawVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
61+
Qr::WithdrawJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
6162
Qr::ReviveVerifyContact { .. } => None,
6263
Qr::ReviveVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
64+
Qr::ReviveJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
6365
Qr::Login { address, .. } => Some(Cow::Borrowed(address)),
6466
},
6567
Self::Error(err) => Some(Cow::Borrowed(err)),
@@ -112,8 +114,10 @@ impl Lot {
112114
Qr::Text { .. } => LotState::QrText,
113115
Qr::WithdrawVerifyContact { .. } => LotState::QrWithdrawVerifyContact,
114116
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
117+
Qr::WithdrawJoinBroadcast { .. } => LotState::QrWithdrawJoinBroadcast,
115118
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
116119
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
120+
Qr::ReviveJoinBroadcast { .. } => LotState::QrReviveJoinBroadcast,
117121
Qr::Login { .. } => LotState::QrLogin,
118122
},
119123
Self::Error(_err) => LotState::QrError,
@@ -138,9 +142,11 @@ impl Lot {
138142
Qr::Url { .. } => Default::default(),
139143
Qr::Text { .. } => Default::default(),
140144
Qr::WithdrawVerifyContact { contact_id, .. } => contact_id.to_u32(),
141-
Qr::WithdrawVerifyGroup { .. } => Default::default(),
145+
Qr::WithdrawVerifyGroup { .. } | Qr::WithdrawJoinBroadcast { .. } => {
146+
Default::default()
147+
}
142148
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
143-
Qr::ReviveVerifyGroup { .. } => Default::default(),
149+
Qr::ReviveVerifyGroup { .. } | Qr::ReviveJoinBroadcast { .. } => Default::default(),
144150
Qr::Login { .. } => Default::default(),
145151
},
146152
Self::Error(_) => Default::default(),
@@ -207,11 +213,15 @@ pub enum LotState {
207213

208214
/// text1=groupname
209215
QrWithdrawVerifyGroup = 502,
216+
/// text1=broadcast channel name
217+
QrWithdrawJoinBroadcast = 504,
210218

211219
QrReviveVerifyContact = 510,
212220

213221
/// text1=groupname
214222
QrReviveVerifyGroup = 512,
223+
/// text1=groupname
224+
QrReviveJoinBroadcast = 514,
215225

216226
/// text1=email_address
217227
QrLogin = 520,

deltachat-jsonrpc/src/api/types/qr.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,21 @@ pub enum QrObject {
157157
/// Authentication code.
158158
authcode: String,
159159
},
160+
/// Ask the user if they want to withdraw their own broadcast channel invite QR code.
161+
WithdrawJoinBroadcast {
162+
/// Broadcast name.
163+
name: String,
164+
/// ID, uniquely identifying this chat. Called grpid for historic reasons.
165+
grpid: String,
166+
/// Contact ID. Always `ContactId::SELF`.
167+
contact_id: u32,
168+
/// Fingerprint of the contact key as scanned from the QR code.
169+
fingerprint: String,
170+
/// Invite number.
171+
invitenumber: String,
172+
/// Authentication code.
173+
authcode: String,
174+
},
160175
/// Ask the user if they want to revive their own QR code.
161176
ReviveVerifyContact {
162177
/// Contact ID.
@@ -183,6 +198,21 @@ pub enum QrObject {
183198
/// Authentication code.
184199
authcode: String,
185200
},
201+
/// Ask the user if they want to revive their own broadcast channel invite QR code.
202+
ReviveJoinBroadcast {
203+
/// Broadcast name.
204+
name: String,
205+
/// Globally unique chat ID. Called grpid for historic reasons.
206+
grpid: String,
207+
/// Contact ID. Always `ContactId::SELF`.
208+
contact_id: u32,
209+
/// Fingerprint of the contact key as scanned from the QR code.
210+
fingerprint: String,
211+
/// Invite number.
212+
invitenumber: String,
213+
/// Authentication code.
214+
authcode: String,
215+
},
186216
/// `dclogin:` scheme parameters.
187217
///
188218
/// Ask the user if they want to login with the email address.
@@ -306,6 +336,25 @@ impl From<Qr> for QrObject {
306336
authcode,
307337
}
308338
}
339+
Qr::WithdrawJoinBroadcast {
340+
name,
341+
grpid,
342+
contact_id,
343+
fingerprint,
344+
invitenumber,
345+
authcode,
346+
} => {
347+
let contact_id = contact_id.to_u32();
348+
let fingerprint = fingerprint.to_string();
349+
QrObject::WithdrawJoinBroadcast {
350+
name,
351+
grpid,
352+
contact_id,
353+
fingerprint,
354+
invitenumber,
355+
authcode,
356+
}
357+
}
309358
Qr::ReviveVerifyContact {
310359
contact_id,
311360
fingerprint,
@@ -340,6 +389,25 @@ impl From<Qr> for QrObject {
340389
authcode,
341390
}
342391
}
392+
Qr::ReviveJoinBroadcast {
393+
name,
394+
grpid,
395+
contact_id,
396+
fingerprint,
397+
invitenumber,
398+
authcode,
399+
} => {
400+
let contact_id = contact_id.to_u32();
401+
let fingerprint = fingerprint.to_string();
402+
QrObject::ReviveJoinBroadcast {
403+
name,
404+
grpid,
405+
contact_id,
406+
fingerprint,
407+
invitenumber,
408+
authcode,
409+
}
410+
}
343411
Qr::Login { address, .. } => QrObject::Login { address },
344412
}
345413
}

src/qr.rs

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,31 @@ pub enum Qr {
233233
authcode: String,
234234
},
235235

236+
/// Ask the user if they want to withdraw their own broadcast channel invite QR code.
237+
WithdrawJoinBroadcast {
238+
/// The user-visible name of this broadcast channel
239+
name: String,
240+
241+
/// A string of random characters,
242+
/// uniquely identifying this broadcast channel across all databases/clients.
243+
/// Called `grpid` for historic reasons:
244+
/// The id of multi-user chats is always called `grpid` in the database
245+
/// because groups were once the only multi-user chats.
246+
grpid: String,
247+
248+
/// Contact ID. Always `ContactId::SELF`.
249+
contact_id: ContactId,
250+
251+
/// Fingerprint of the contact's key as scanned from the QR code.
252+
fingerprint: Fingerprint,
253+
254+
/// Invite number.
255+
invitenumber: String,
256+
257+
/// Authentication code.
258+
authcode: String,
259+
},
260+
236261
/// Ask the user if they want to revive their own QR code.
237262
ReviveVerifyContact {
238263
/// Contact ID.
@@ -269,6 +294,31 @@ pub enum Qr {
269294
authcode: String,
270295
},
271296

297+
/// Ask the user if they want to revive their own broadcast channel invite QR code.
298+
ReviveJoinBroadcast {
299+
/// The user-visible name of this broadcast channel
300+
name: String,
301+
302+
/// A string of random characters,
303+
/// uniquely identifying this broadcast channel across all databases/clients.
304+
/// Called `grpid` for historic reasons:
305+
/// The id of multi-user chats is always called `grpid` in the database
306+
/// because groups were once the only multi-user chats.
307+
grpid: String,
308+
309+
/// Contact ID. Always `ContactId::SELF`.
310+
contact_id: ContactId,
311+
312+
/// Fingerprint of the contact's key as scanned from the QR code.
313+
fingerprint: Fingerprint,
314+
315+
/// Invite number.
316+
invitenumber: String,
317+
318+
/// Authentication code.
319+
authcode: String,
320+
},
321+
272322
/// `dclogin:` scheme parameters.
273323
///
274324
/// 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> {
500550
})
501551
}
502552
} else if let (Some(grpid), Some(name)) = (grpid, broadcast_name) {
503-
Ok(Qr::AskJoinBroadcast {
504-
name,
505-
grpid,
506-
contact_id,
507-
fingerprint,
508-
invitenumber,
509-
authcode,
510-
})
553+
if context
554+
.is_self_addr(&addr)
555+
.await
556+
.with_context(|| format!("Can't check if {addr:?} is our address"))?
557+
{
558+
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
559+
Ok(Qr::WithdrawJoinBroadcast {
560+
name,
561+
grpid,
562+
contact_id,
563+
fingerprint,
564+
invitenumber,
565+
authcode,
566+
})
567+
} else {
568+
Ok(Qr::ReviveJoinBroadcast {
569+
name,
570+
grpid,
571+
contact_id,
572+
fingerprint,
573+
invitenumber,
574+
authcode,
575+
})
576+
}
577+
} else {
578+
Ok(Qr::AskJoinBroadcast {
579+
name,
580+
grpid,
581+
contact_id,
582+
fingerprint,
583+
invitenumber,
584+
authcode,
585+
})
586+
}
511587
} else if context.is_self_addr(&addr).await? {
512588
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
513589
Ok(Qr::WithdrawVerifyContact {
@@ -800,6 +876,12 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
800876
invitenumber,
801877
authcode,
802878
..
879+
}
880+
| Qr::WithdrawJoinBroadcast {
881+
grpid,
882+
invitenumber,
883+
authcode,
884+
..
803885
} => {
804886
token::delete(context, &grpid).await?;
805887
context
@@ -829,6 +911,12 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
829911
authcode,
830912
grpid,
831913
..
914+
}
915+
| Qr::ReviveJoinBroadcast {
916+
invitenumber,
917+
authcode,
918+
grpid,
919+
..
832920
} => {
833921
let timestamp = time();
834922
token::save(

src/qr/qr_tests.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::*;
2-
use crate::chat::create_group;
2+
use crate::chat::{Chat, create_broadcast, create_group, get_chat_contacts};
33
use crate::config::Config;
44
use crate::login_param::EnteredCertificateChecks;
55
use crate::provider::Socket;
@@ -511,6 +511,56 @@ async fn test_withdraw_verifygroup() -> Result<()> {
511511
Ok(())
512512
}
513513

514+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
515+
async fn test_withdraw_joinbroadcast() -> Result<()> {
516+
let mut tcm = TestContextManager::new();
517+
let alice = &tcm.alice().await;
518+
let bob = &tcm.bob().await;
519+
let chat_id = create_broadcast(alice, "foo".to_string()).await?;
520+
let qr = get_securejoin_qr(alice, Some(chat_id)).await?;
521+
522+
// scanning own verify-group code offers withdrawing
523+
if let Qr::WithdrawJoinBroadcast { name, .. } = check_qr(alice, &qr).await? {
524+
assert_eq!(name, "foo");
525+
} else {
526+
bail!("Wrong QR type, expected WithdrawJoinBroadcast");
527+
}
528+
set_config_from_qr(alice, &qr).await?;
529+
530+
// scanning withdrawn verify-group code offers reviving
531+
if let Qr::ReviveJoinBroadcast { name, .. } = check_qr(alice, &qr).await? {
532+
assert_eq!(name, "foo");
533+
} else {
534+
bail!("Wrong QR type, expected ReviveJoinBroadcast");
535+
}
536+
537+
// someone else always scans as ask-verify-group
538+
if let Qr::AskJoinBroadcast { name, .. } = check_qr(bob, &qr).await? {
539+
assert_eq!(name, "foo");
540+
} else {
541+
bail!("Wrong QR type, expected AskJoinBroadcast");
542+
}
543+
assert!(set_config_from_qr(bob, &qr).await.is_err());
544+
545+
// Bob can't join using this QR code, since it's still withdrawn
546+
let bob_chat_id = tcm.exec_securejoin_qr(bob, alice, &qr).await;
547+
let bob_chat = Chat::load_from_db(bob, bob_chat_id).await?;
548+
assert_eq!(bob_chat.is_self_in_chat(bob).await?, false);
549+
assert_eq!(get_chat_contacts(alice, chat_id).await?.len(), 0);
550+
551+
// Revive
552+
set_config_from_qr(alice, &qr).await?;
553+
554+
// Now Bob can join
555+
let bob_chat_id2 = tcm.exec_securejoin_qr(bob, alice, &qr).await;
556+
assert_eq!(bob_chat_id, bob_chat_id2);
557+
let bob_chat = Chat::load_from_db(bob, bob_chat_id).await?;
558+
assert_eq!(bob_chat.is_self_in_chat(bob).await?, true);
559+
assert_eq!(get_chat_contacts(alice, chat_id).await?.len(), 1);
560+
561+
Ok(())
562+
}
563+
514564
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
515565
async fn test_withdraw_multidevice() -> Result<()> {
516566
let mut tcm = TestContextManager::new();

0 commit comments

Comments
 (0)