Skip to content

Commit 51b9e86

Browse files
authored
Opt-in weekly sending of statistics (#6851)
This way, the statistics / self-reporting bot will be made into an opt-in regular sending of statistics, where you enable the setting once and then they will be sent automatically. The statistics will be sent to a bot, so that the user can see exactly which data is being sent, and how often. The chat will be archived and muted by default, so that it doesn't disturb the user. The collected statistics will focus on the public-key-verification that is performed while scanning a QR code. Later on, we can add more statistics to collect. **Context:** _This is just to give a rough idea; I would need to write a lot more than a few paragraphs in order to fully explain all the context here_. End-to-end encrypted messengers are generally susceptible to MitM attacks. In order to mitigate against this, messengers offer some way of verifying the chat partner's public key. However, numerous studies found that most popular messengers implement this public-key-verification in a way that is not understood by users, and therefore ineffective - [a 2021 "State of Knowledge" paper concludes:](https://dl.acm.org/doi/pdf/10.1145/3558482.3581773) > Based on our evaluation, we have determined that all current E2EE apps, particularly when operating in opportunistic E2EE mode, are incapable of repelling active man-in-the-middle (MitM) attacks. In addition, we find that none of the current E2EE apps provide better and more usable [public key verification] ceremonies, resulting in insecure E2EE communications against active MitM attacks. This is why Delta Chat tries to go a different route: When the user scans a QR code (regardless of whether the QR code creates a 1:1 chat, invites to a group, or subscribes to a broadcast channel), a public-key-verification is performed in the background, without the user even having to know about this. The statistics collected here are supposed to tell us whether Delta Chat succeeds to nudge the users into using QR codes in a way that is secure against MitM attacks. **Plan for statistics-sending:** - [x] Get this PR reviewed and merged (but don't make it available in the UI yet; if Android wants to make a release in the meantime, I will create a PR that removes the option there) - [x] Set the interval to 1 week again (right now, it's 1 minute for testing) - [ ] Write something for people who are interested in what exactly we count, and link to it (see `TODO[blog post]` in the code) - [ ] Prepare a short survey for participants - [ ] Fine-tune the texts at deltachat/deltachat-android#3794, and get it reviewed and merged - [ ] After the next release, ask people to enable the statistics-sending
1 parent 347938a commit 51b9e86

File tree

17 files changed

+1747
-180
lines changed

17 files changed

+1747
-180
lines changed
File renamed without changes.

deltachat-jsonrpc/src/api.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ use self::types::{
6666
},
6767
};
6868
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
69-
use crate::api::types::qr::QrObject;
69+
use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath};
7070

7171
#[derive(Debug)]
7272
struct AccountState {
@@ -381,11 +381,6 @@ impl CommandApi {
381381
Ok(BlobObject::create_and_deduplicate(&ctx, file, file)?.to_abs_path())
382382
}
383383

384-
async fn draft_self_report(&self, account_id: u32) -> Result<u32> {
385-
let ctx = self.get_context(account_id).await?;
386-
Ok(ctx.draft_self_report().await?.to_u32())
387-
}
388-
389384
/// Sets the given configuration key.
390385
async fn set_config(&self, account_id: u32, key: String, value: Option<String>) -> Result<()> {
391386
let ctx = self.get_context(account_id).await?;
@@ -884,6 +879,38 @@ impl CommandApi {
884879
Ok(chat_id.to_u32())
885880
}
886881

882+
/// Like `secure_join()`, but allows to pass a source and a UI-path.
883+
/// You only need this if your UI has an option to send statistics
884+
/// to Delta Chat's developers.
885+
///
886+
/// **source**: The source where the QR code came from.
887+
/// E.g. a link that was clicked inside or outside Delta Chat,
888+
/// the "Paste from Clipboard" action,
889+
/// the "Load QR code as image" action,
890+
/// or a QR code scan.
891+
///
892+
/// **uipath**: Which UI path did the user use to arrive at the QR code screen.
893+
/// If the SecurejoinSource was ExternalLink or InternalLink,
894+
/// pass `None` here, because the QR code screen wasn't even opened.
895+
/// ```
896+
async fn secure_join_with_ux_info(
897+
&self,
898+
account_id: u32,
899+
qr: String,
900+
source: Option<SecurejoinSource>,
901+
uipath: Option<SecurejoinUiPath>,
902+
) -> Result<u32> {
903+
let ctx = self.get_context(account_id).await?;
904+
let chat_id = securejoin::join_securejoin_with_ux_info(
905+
&ctx,
906+
&qr,
907+
source.map(Into::into),
908+
uipath.map(Into::into),
909+
)
910+
.await?;
911+
Ok(chat_id.to_u32())
912+
}
913+
887914
async fn leave_group(&self, account_id: u32, chat_id: u32) -> Result<()> {
888915
let ctx = self.get_context(account_id).await?;
889916
remove_contact_from_chat(&ctx, ChatId::new(chat_id), ContactId::SELF).await

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use deltachat::qr::Qr;
2+
use serde::Deserialize;
23
use serde::Serialize;
34
use typescript_type_def::TypeDef;
45

@@ -304,3 +305,53 @@ impl From<Qr> for QrObject {
304305
}
305306
}
306307
}
308+
309+
#[derive(Deserialize, TypeDef, schemars::JsonSchema)]
310+
pub enum SecurejoinSource {
311+
/// Because of some problem, it is unknown where the QR code came from.
312+
Unknown,
313+
/// The user opened a link somewhere outside Delta Chat
314+
ExternalLink,
315+
/// The user clicked on a link in a message inside Delta Chat
316+
InternalLink,
317+
/// The user clicked "Paste from Clipboard" in the QR scan activity
318+
Clipboard,
319+
/// The user clicked "Load QR code as image" in the QR scan activity
320+
ImageLoaded,
321+
/// The user scanned a QR code
322+
Scan,
323+
}
324+
325+
#[derive(Deserialize, TypeDef, schemars::JsonSchema)]
326+
pub enum SecurejoinUiPath {
327+
/// The UI path is unknown, or the user didn't open the QR code screen at all.
328+
Unknown,
329+
/// The user directly clicked on the QR icon in the main screen
330+
QrIcon,
331+
/// The user first clicked on the `+` button in the main screen,
332+
/// and then on "New Contact"
333+
NewContact,
334+
}
335+
336+
impl From<SecurejoinSource> for deltachat::SecurejoinSource {
337+
fn from(value: SecurejoinSource) -> Self {
338+
match value {
339+
SecurejoinSource::Unknown => deltachat::SecurejoinSource::Unknown,
340+
SecurejoinSource::ExternalLink => deltachat::SecurejoinSource::ExternalLink,
341+
SecurejoinSource::InternalLink => deltachat::SecurejoinSource::InternalLink,
342+
SecurejoinSource::Clipboard => deltachat::SecurejoinSource::Clipboard,
343+
SecurejoinSource::ImageLoaded => deltachat::SecurejoinSource::ImageLoaded,
344+
SecurejoinSource::Scan => deltachat::SecurejoinSource::Scan,
345+
}
346+
}
347+
}
348+
349+
impl From<SecurejoinUiPath> for deltachat::SecurejoinUiPath {
350+
fn from(value: SecurejoinUiPath) -> Self {
351+
match value {
352+
SecurejoinUiPath::Unknown => deltachat::SecurejoinUiPath::Unknown,
353+
SecurejoinUiPath::QrIcon => deltachat::SecurejoinUiPath::QrIcon,
354+
SecurejoinUiPath::NewContact => deltachat::SecurejoinUiPath::NewContact,
355+
}
356+
}
357+
}

deltachat-time/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ impl SystemTimeTools {
2020
pub fn shift(duration: Duration) {
2121
*SYSTEM_TIME_SHIFT.write().unwrap() += duration;
2222
}
23+
24+
/// Simulates the system clock being rewound by `duration`.
25+
pub fn shift_back(duration: Duration) {
26+
*SYSTEM_TIME_SHIFT.write().unwrap() -= duration;
27+
}
2328
}
2429

2530
#[cfg(test)]

src/config.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use tokio::fs;
1414

1515
use crate::blob::BlobObject;
1616
use crate::configure::EnteredLoginParam;
17-
use crate::constants;
1817
use crate::context::Context;
1918
use crate::events::EventType;
2019
use crate::log::{LogExt, info};
@@ -23,6 +22,7 @@ use crate::mimefactory::RECOMMENDED_FILE_SIZE;
2322
use crate::provider::{Provider, get_provider_by_id};
2423
use crate::sync::{self, Sync::*, SyncData};
2524
use crate::tools::get_abs_path;
25+
use crate::{constants, stats};
2626

2727
/// The available configuration keys.
2828
#[derive(
@@ -408,9 +408,22 @@ pub enum Config {
408408
/// used for signatures, encryption to self and included in `Autocrypt` header.
409409
KeyId,
410410

411-
/// This key is sent to the self_reporting bot so that the bot can recognize the user
411+
/// Send statistics to Delta Chat's developers.
412+
/// Can be exposed to the user as a setting.
413+
StatsSending,
414+
415+
/// Last time statistics were sent to Delta Chat's developers
416+
StatsLastSent,
417+
418+
/// Last time `update_message_stats()` was called
419+
StatsLastUpdate,
420+
421+
/// This key is sent to the statistics bot so that the bot can recognize the user
412422
/// without storing the email address
413-
SelfReportingId,
423+
StatsId,
424+
425+
/// The last contact id that already existed when statistics-sending was enabled for the first time.
426+
StatsLastOldContactId,
414427

415428
/// MsgId of webxdc map integration.
416429
WebxdcIntegration,
@@ -576,8 +589,9 @@ impl Context {
576589
/// Returns boolean configuration value for the given key.
577590
pub async fn get_config_bool(&self, key: Config) -> Result<bool> {
578591
Ok(self
579-
.get_config_parsed::<i32>(key)
592+
.get_config(key)
580593
.await?
594+
.and_then(|s| s.parse::<i32>().ok())
581595
.map(|x| x != 0)
582596
.unwrap_or_default())
583597
}
@@ -716,10 +730,19 @@ impl Context {
716730
true => self.scheduler.pause(self).await?,
717731
_ => Default::default(),
718732
};
733+
if key == Config::StatsSending {
734+
let old_value = self.get_config(key).await?;
735+
let old_value = bool_from_config(old_value.as_deref());
736+
let new_value = bool_from_config(value);
737+
stats::pre_sending_config_change(self, old_value, new_value).await?;
738+
}
719739
self.set_config_internal(key, value).await?;
720740
if key == Config::SentboxWatch {
721741
self.last_full_folder_scan.lock().await.take();
722742
}
743+
if key == Config::StatsSending {
744+
stats::maybe_send_stats(self).await?;
745+
}
723746
Ok(())
724747
}
725748

@@ -871,6 +894,10 @@ pub(crate) fn from_bool(val: bool) -> Option<&'static str> {
871894
Some(if val { "1" } else { "0" })
872895
}
873896

897+
pub(crate) fn bool_from_config(config: Option<&str>) -> bool {
898+
config.is_some_and(|v| v.parse::<i32>().unwrap_or_default() != 0)
899+
}
900+
874901
// Separate impl block for self address handling
875902
impl Context {
876903
/// Determine whether the specified addr maps to the/a self addr.

src/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9);
101101
Copy,
102102
PartialEq,
103103
Eq,
104+
PartialOrd,
105+
Ord,
104106
FromPrimitive,
105107
ToPrimitive,
106108
FromSql,

0 commit comments

Comments
 (0)