mirror of
https://github.com/chatmail/core.git
synced 2026-05-18 14:26:31 +03:00
Compare commits
25 Commits
v2.22.0
...
wofwca/per
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
781de46e26 | ||
|
|
fc81cef113 | ||
|
|
04c2585c27 | ||
|
|
59fac54f7b | ||
|
|
65b61efb31 | ||
|
|
afc74b0829 | ||
|
|
2481a0f48e | ||
|
|
6c24edb40d | ||
|
|
e4178789da | ||
|
|
b417ba86bc | ||
|
|
498a831873 | ||
|
|
c6722d36de | ||
|
|
90f0d5c060 | ||
|
|
90ec2f2518 | ||
|
|
5b66535134 | ||
|
|
eea848f72b | ||
|
|
214a1d3e2d | ||
|
|
e270a502d1 | ||
|
|
b863345600 | ||
|
|
61b49a9339 | ||
|
|
6fd3645360 | ||
|
|
5256013615 | ||
|
|
9826c28581 | ||
|
|
9ceceebdc3 | ||
|
|
187d913f84 |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -7,6 +7,8 @@ updates:
|
||||
commit-message:
|
||||
prefix: "chore(cargo)"
|
||||
open-pull-requests-limit: 50
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
# Keep GitHub Actions up to date.
|
||||
# <https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot>
|
||||
@@ -14,3 +16,5 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
2
.github/workflows/zizmor-scan.yml
vendored
2
.github/workflows/zizmor-scan.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d
|
||||
|
||||
- name: Run zizmor
|
||||
run: uvx zizmor --format sarif . > results.sarif
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -36,6 +36,7 @@ deltachat-ffi/xml
|
||||
coverage/
|
||||
.DS_Store
|
||||
.vscode
|
||||
.zed
|
||||
python/accounts.txt
|
||||
python/all-testaccounts.txt
|
||||
tmp/
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1316,7 +1316,6 @@ dependencies = [
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"hickory-resolver",
|
||||
"http-body-util",
|
||||
"humansize",
|
||||
"hyper",
|
||||
|
||||
@@ -61,7 +61,6 @@ fd-lock = "4"
|
||||
futures-lite = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hex = "0.4.0"
|
||||
hickory-resolver = "0.25.2"
|
||||
http-body-util = "0.1.3"
|
||||
humansize = "2"
|
||||
hyper = "1"
|
||||
|
||||
@@ -1763,9 +1763,7 @@ dc_chat_t* dc_get_chat (dc_context_t* context, uint32_t ch
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param protect If set to 1 the function creates group with protection initially enabled.
|
||||
* Only verified members are allowed in these groups
|
||||
* and end-to-end-encryption is always enabled.
|
||||
* @param protect Deprecated 2025-08-31, ignored.
|
||||
* @param name The name of the group chat to create.
|
||||
* The name may be changed later using dc_set_chat_name().
|
||||
* To find out the name of a group later, see dc_chat_get_name()
|
||||
@@ -3890,18 +3888,12 @@ int dc_chat_can_send (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a chat is protected.
|
||||
*
|
||||
* Only verified contacts
|
||||
* as determined by dc_contact_is_verified()
|
||||
* can be added to protected chats.
|
||||
*
|
||||
* Protected chats are created using dc_create_group_chat()
|
||||
* by setting the 'protect' parameter to 1.
|
||||
* Deprecated, always returns 0.
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
* @param chat The chat object.
|
||||
* @return 1=chat protected, 0=chat is not protected.
|
||||
* @return Always 0.
|
||||
* @deprecated 2025-09-09
|
||||
*/
|
||||
int dc_chat_is_protected (const dc_chat_t* chat);
|
||||
|
||||
@@ -5350,11 +5342,9 @@ dc_provider_t* dc_provider_new_from_email (const dc_context_t* conte
|
||||
|
||||
|
||||
/**
|
||||
* Create a provider struct for the given e-mail address by local and DNS lookup.
|
||||
* Create a provider struct for the given e-mail address by local lookup.
|
||||
*
|
||||
* First lookup is done from the local database as of dc_provider_new_from_email().
|
||||
* If the first lookup fails, an additional DNS lookup is done,
|
||||
* trying to figure out the provider belonging to custom domains.
|
||||
* DNS lookup is not used anymore and this function is deprecated.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param context The context object.
|
||||
@@ -5362,6 +5352,7 @@ dc_provider_t* dc_provider_new_from_email (const dc_context_t* conte
|
||||
* @return A dc_provider_t struct which can be used with the dc_provider_get_*
|
||||
* accessor functions. If no provider info is found, NULL will be
|
||||
* returned.
|
||||
* @deprecated 2025-10-17 use dc_provider_new_from_email() instead.
|
||||
*/
|
||||
dc_provider_t* dc_provider_new_from_email_with_dns (const dc_context_t* context, const char* email);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ use std::sync::{Arc, LazyLock};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use deltachat::chat::{ChatId, ChatVisibility, MessageListOptions, MuteDuration, ProtectionStatus};
|
||||
use deltachat::chat::{ChatId, ChatVisibility, MessageListOptions, MuteDuration};
|
||||
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
|
||||
use deltachat::contact::{Contact, ContactId, Origin};
|
||||
use deltachat::context::{Context, ContextBuilder};
|
||||
@@ -1532,6 +1532,7 @@ pub unsafe extern "C" fn dc_get_chat_media(
|
||||
msg_type: libc::c_int,
|
||||
or_msg_type2: libc::c_int,
|
||||
or_msg_type3: libc::c_int,
|
||||
// limit: u32,
|
||||
) -> *mut dc_array::dc_array_t {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to dc_get_chat_media()");
|
||||
@@ -1551,7 +1552,7 @@ pub unsafe extern "C" fn dc_get_chat_media(
|
||||
|
||||
block_on(async move {
|
||||
Box::into_raw(Box::new(
|
||||
chat::get_chat_media(ctx, chat_id, msg_type, or_msg_type2, or_msg_type3)
|
||||
chat::get_chat_media(ctx, chat_id, msg_type, or_msg_type2, or_msg_type3, None)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "Failed get_chat_media")
|
||||
.into(),
|
||||
@@ -1721,7 +1722,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_create_group_chat(
|
||||
context: *mut dc_context_t,
|
||||
protect: libc::c_int,
|
||||
_protect: libc::c_int,
|
||||
name: *const libc::c_char,
|
||||
) -> u32 {
|
||||
if context.is_null() || name.is_null() {
|
||||
@@ -1729,22 +1730,12 @@ pub unsafe extern "C" fn dc_create_group_chat(
|
||||
return 0;
|
||||
}
|
||||
let ctx = &*context;
|
||||
let Some(protect) = ProtectionStatus::from_i32(protect)
|
||||
.context("Bad protect-value for dc_create_group_chat()")
|
||||
.log_err(ctx)
|
||||
.ok()
|
||||
else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
chat::create_group_chat(ctx, protect, &to_string_lossy(name))
|
||||
.await
|
||||
.context("Failed to create group chat")
|
||||
.log_err(ctx)
|
||||
.map(|id| id.to_u32())
|
||||
.unwrap_or(0)
|
||||
})
|
||||
block_on(chat::create_group(ctx, &to_string_lossy(name)))
|
||||
.context("Failed to create group chat")
|
||||
.log_err(ctx)
|
||||
.map(|id| id.to_u32())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3206,13 +3197,8 @@ pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_chat_is_protected(chat: *mut dc_chat_t) -> libc::c_int {
|
||||
if chat.is_null() {
|
||||
eprintln!("ignoring careless call to dc_chat_is_protected()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_chat = &*chat;
|
||||
ffi_chat.chat.is_protected() as libc::c_int
|
||||
pub extern "C" fn dc_chat_is_protected(_chat: *mut dc_chat_t) -> libc::c_int {
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -4661,13 +4647,9 @@ pub unsafe extern "C" fn dc_provider_new_from_email(
|
||||
|
||||
let ctx = &*context;
|
||||
|
||||
match block_on(provider::get_provider_info_by_addr(
|
||||
ctx,
|
||||
addr.as_str(),
|
||||
true,
|
||||
))
|
||||
.log_err(ctx)
|
||||
.unwrap_or_default()
|
||||
match provider::get_provider_info_by_addr(addr.as_str())
|
||||
.log_err(ctx)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Some(provider) => provider,
|
||||
None => ptr::null_mut(),
|
||||
@@ -4686,25 +4668,13 @@ pub unsafe extern "C" fn dc_provider_new_from_email_with_dns(
|
||||
let addr = to_string_lossy(addr);
|
||||
|
||||
let ctx = &*context;
|
||||
let proxy_enabled = block_on(ctx.get_config_bool(config::Config::ProxyEnabled))
|
||||
.context("Can't get config")
|
||||
.log_err(ctx);
|
||||
|
||||
match proxy_enabled {
|
||||
Ok(proxy_enabled) => {
|
||||
match block_on(provider::get_provider_info_by_addr(
|
||||
ctx,
|
||||
addr.as_str(),
|
||||
proxy_enabled,
|
||||
))
|
||||
.log_err(ctx)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Some(provider) => provider,
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
Err(_) => ptr::null_mut(),
|
||||
match provider::get_provider_info_by_addr(addr.as_str())
|
||||
.log_err(ctx)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Some(provider) => provider,
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ use deltachat::calls::ice_servers;
|
||||
use deltachat::chat::{
|
||||
self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
|
||||
marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
|
||||
ProtectionStatus,
|
||||
};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::config::Config;
|
||||
@@ -336,21 +335,10 @@ impl CommandApi {
|
||||
/// instead of the domain.
|
||||
async fn get_provider_info(
|
||||
&self,
|
||||
account_id: u32,
|
||||
_account_id: u32,
|
||||
email: String,
|
||||
) -> Result<Option<ProviderInfo>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
let proxy_enabled = ctx
|
||||
.get_config_bool(deltachat::config::Config::ProxyEnabled)
|
||||
.await?;
|
||||
|
||||
let provider_info = get_provider_info(
|
||||
&ctx,
|
||||
email.split('@').next_back().unwrap_or(""),
|
||||
proxy_enabled,
|
||||
)
|
||||
.await;
|
||||
let provider_info = get_provider_info(email.split('@').next_back().unwrap_or(""));
|
||||
Ok(ProviderInfo::from_dc_type(provider_info))
|
||||
}
|
||||
|
||||
@@ -978,18 +966,9 @@ impl CommandApi {
|
||||
///
|
||||
/// To check, if a chat is still unpromoted, you can look at the `is_unpromoted` property of `BasicChat` or `FullChat`.
|
||||
/// This may be useful if you want to show some help for just created groups.
|
||||
///
|
||||
/// @param protect If set to 1 the function creates group with protection initially enabled.
|
||||
/// Only verified members are allowed in these groups
|
||||
async fn create_group_chat(&self, account_id: u32, name: String, protect: bool) -> Result<u32> {
|
||||
async fn create_group_chat(&self, account_id: u32, name: String) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let protect = match protect {
|
||||
true => ProtectionStatus::Protected,
|
||||
false => ProtectionStatus::Unprotected,
|
||||
};
|
||||
chat::create_group_ex(&ctx, Some(protect), &name)
|
||||
.await
|
||||
.map(|id| id.to_u32())
|
||||
chat::create_group(&ctx, &name).await.map(|id| id.to_u32())
|
||||
}
|
||||
|
||||
/// Create a new unencrypted group chat.
|
||||
@@ -998,7 +977,7 @@ impl CommandApi {
|
||||
/// address-contacts.
|
||||
async fn create_group_chat_unencrypted(&self, account_id: u32, name: String) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
chat::create_group_ex(&ctx, None, &name)
|
||||
chat::create_group_unencrypted(&ctx, &name)
|
||||
.await
|
||||
.map(|id| id.to_u32())
|
||||
}
|
||||
@@ -1755,6 +1734,7 @@ impl CommandApi {
|
||||
message_type: MessageViewtype,
|
||||
or_message_type2: Option<MessageViewtype>,
|
||||
or_message_type3: Option<MessageViewtype>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
@@ -1766,7 +1746,8 @@ impl CommandApi {
|
||||
let or_msg_type2 = or_message_type2.map_or(Viewtype::Unknown, |v| v.into());
|
||||
let or_msg_type3 = or_message_type3.map_or(Viewtype::Unknown, |v| v.into());
|
||||
|
||||
let media = get_chat_media(&ctx, chat_id, msg_type, or_msg_type2, or_msg_type3).await?;
|
||||
let media =
|
||||
get_chat_media(&ctx, chat_id, msg_type, or_msg_type2, or_msg_type3, limit).await?;
|
||||
Ok(media.iter().map(|msg_id| msg_id.to_u32()).collect())
|
||||
}
|
||||
|
||||
|
||||
@@ -19,18 +19,6 @@ pub struct FullChat {
|
||||
id: u32,
|
||||
name: String,
|
||||
|
||||
/// True if the chat is protected.
|
||||
///
|
||||
/// Only verified contacts
|
||||
/// as determined by [`ContactObject::is_verified`] / `Contact.isVerified`
|
||||
/// can be added to protected chats.
|
||||
///
|
||||
/// Protected chats are created using [`create_group_chat`] / `createGroupChat()`
|
||||
/// by setting the 'protect' parameter to true.
|
||||
///
|
||||
/// [`create_group_chat`]: crate::api::CommandApi::create_group_chat
|
||||
is_protected: bool,
|
||||
|
||||
/// True if the chat is encrypted.
|
||||
/// This means that all messages in the chat are encrypted,
|
||||
/// and all contacts in the chat are "key-contacts",
|
||||
@@ -131,7 +119,6 @@ impl FullChat {
|
||||
Ok(FullChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
is_protected: chat.is_protected(),
|
||||
is_encrypted: chat.is_encrypted(context).await?,
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
@@ -172,18 +159,6 @@ pub struct BasicChat {
|
||||
id: u32,
|
||||
name: String,
|
||||
|
||||
/// True if the chat is protected.
|
||||
///
|
||||
/// UI should display a green checkmark
|
||||
/// in the chat title,
|
||||
/// in the chat profile 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.
|
||||
is_protected: bool,
|
||||
|
||||
/// True if the chat is encrypted.
|
||||
/// This means that all messages in the chat are encrypted,
|
||||
/// and all contacts in the chat are "key-contacts",
|
||||
@@ -234,7 +209,6 @@ impl BasicChat {
|
||||
Ok(BasicChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
is_protected: chat.is_protected(),
|
||||
is_encrypted: chat.is_encrypted(context).await?,
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
|
||||
@@ -30,7 +30,6 @@ pub enum ChatListItemFetchResult {
|
||||
summary_status: u32,
|
||||
/// showing preview if last chat message is image
|
||||
summary_preview_image: Option<String>,
|
||||
is_protected: bool,
|
||||
|
||||
/// True if the chat is encrypted.
|
||||
/// This means that all messages in the chat are encrypted,
|
||||
@@ -161,7 +160,6 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
summary_text2,
|
||||
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
|
||||
summary_preview_image,
|
||||
is_protected: chat.is_protected(),
|
||||
is_encrypted: chat.is_encrypted(ctx).await?,
|
||||
is_group: chat.get_type() == Chattype::Group,
|
||||
fresh_message_counter,
|
||||
|
||||
@@ -532,7 +532,6 @@ pub struct MessageSearchResult {
|
||||
chat_color: String,
|
||||
chat_name: String,
|
||||
chat_type: u32,
|
||||
is_chat_protected: bool,
|
||||
is_chat_contact_request: bool,
|
||||
is_chat_archived: bool,
|
||||
message: String,
|
||||
@@ -572,7 +571,6 @@ impl MessageSearchResult {
|
||||
chat_color,
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
chat_profile_image,
|
||||
is_chat_protected: chat.is_protected(),
|
||||
is_chat_contact_request: chat.is_contact_request(),
|
||||
is_chat_archived: chat.get_visibility() == ChatVisibility::Archived,
|
||||
message: message.get_text(),
|
||||
|
||||
@@ -6,9 +6,7 @@ use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use deltachat::chat::{
|
||||
self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration, ProtectionStatus,
|
||||
};
|
||||
use deltachat::chat::{self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration};
|
||||
use deltachat::chatlist::*;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
@@ -347,7 +345,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
createchat <contact-id>\n\
|
||||
creategroup <name>\n\
|
||||
createbroadcast <name>\n\
|
||||
createprotected <name>\n\
|
||||
addmember <contact-id>\n\
|
||||
removemember <contact-id>\n\
|
||||
groupname <name>\n\
|
||||
@@ -563,7 +560,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
for i in (0..cnt).rev() {
|
||||
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)?).await?;
|
||||
println!(
|
||||
"{}#{}: {} [{} fresh] {}{}{}{}",
|
||||
"{}#{}: {} [{} fresh] {}{}{}",
|
||||
chat_prefix(&chat),
|
||||
chat.get_id(),
|
||||
chat.get_name(),
|
||||
@@ -574,7 +571,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ChatVisibility::Archived => "📦",
|
||||
ChatVisibility::Pinned => "📌",
|
||||
},
|
||||
if chat.is_protected() { "🛡️" } else { "" },
|
||||
if chat.is_contact_request() {
|
||||
"🆕"
|
||||
} else {
|
||||
@@ -689,7 +685,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
format!("{} member(s)", members.len())
|
||||
};
|
||||
println!(
|
||||
"{}#{}: {} [{}]{}{}{} {}",
|
||||
"{}#{}: {} [{}]{}{}{}",
|
||||
chat_prefix(sel_chat),
|
||||
sel_chat.get_id(),
|
||||
sel_chat.get_name(),
|
||||
@@ -707,11 +703,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
},
|
||||
_ => "".to_string(),
|
||||
},
|
||||
if sel_chat.is_protected() {
|
||||
"🛡️"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
);
|
||||
log_msglist(&context, &msglist).await?;
|
||||
if let Some(draft) = sel_chat.get_id().get_draft(&context).await? {
|
||||
@@ -740,8 +731,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"creategroup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
let chat_id =
|
||||
chat::create_group_chat(&context, ProtectionStatus::Unprotected, arg1).await?;
|
||||
let chat_id = chat::create_group(&context, arg1).await?;
|
||||
|
||||
println!("Group#{chat_id} created successfully.");
|
||||
}
|
||||
@@ -751,13 +741,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
|
||||
println!("Broadcast#{chat_id} created successfully.");
|
||||
}
|
||||
"createprotected" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
let chat_id =
|
||||
chat::create_group_chat(&context, ProtectionStatus::Protected, arg1).await?;
|
||||
|
||||
println!("Group#{chat_id} created and protected successfully.");
|
||||
}
|
||||
"addmember" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected");
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
@@ -1033,6 +1016,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
Viewtype::Image,
|
||||
Viewtype::Gif,
|
||||
Viewtype::Video,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
println!("{} images or videos: ", images.len());
|
||||
@@ -1266,10 +1250,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"providerinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <addr> missing.");
|
||||
let proxy_enabled = context
|
||||
.get_config_bool(config::Config::ProxyEnabled)
|
||||
.await?;
|
||||
match provider::get_provider_info(&context, arg1, proxy_enabled).await {
|
||||
match provider::get_provider_info(arg1) {
|
||||
Some(info) => {
|
||||
println!("Information for provider belonging to {arg1}:");
|
||||
println!("status: {}", info.status as u32);
|
||||
|
||||
@@ -300,7 +300,7 @@ class Account:
|
||||
chats.append(AttrDict(item))
|
||||
return chats
|
||||
|
||||
def create_group(self, name: str, protect: bool = False) -> Chat:
|
||||
def create_group(self, name: str) -> Chat:
|
||||
"""Create a new group chat.
|
||||
|
||||
After creation,
|
||||
@@ -317,12 +317,8 @@ class Account:
|
||||
To check, if a chat is still unpromoted, you can look at the `is_unpromoted` property of a chat
|
||||
(see `get_full_snapshot()` / `get_basic_snapshot()`).
|
||||
This may be useful if you want to show some help for just created groups.
|
||||
|
||||
:param protect: If set to 1 the function creates group with protection initially enabled.
|
||||
Only verified members are allowed in these groups
|
||||
and end-to-end-encryption is always enabled.
|
||||
"""
|
||||
return Chat(self, self._rpc.create_group_chat(self.id, name, protect))
|
||||
return Chat(self, self._rpc.create_group_chat(self.id, name))
|
||||
|
||||
def create_broadcast(self, name: str) -> Chat:
|
||||
"""Create a new **broadcast channel**
|
||||
|
||||
@@ -58,8 +58,7 @@ def test_qr_setup_contact_svg(acfactory) -> None:
|
||||
assert "Alice" in svg
|
||||
|
||||
|
||||
@pytest.mark.parametrize("protect", [True, False])
|
||||
def test_qr_securejoin(acfactory, protect):
|
||||
def test_qr_securejoin(acfactory):
|
||||
alice, bob, fiona = acfactory.get_online_accounts(3)
|
||||
|
||||
# Setup second device for Alice
|
||||
@@ -67,8 +66,7 @@ def test_qr_securejoin(acfactory, protect):
|
||||
alice2 = alice.clone()
|
||||
|
||||
logging.info("Alice creates a group")
|
||||
alice_chat = alice.create_group("Group", protect=protect)
|
||||
assert alice_chat.get_basic_snapshot().is_protected == protect
|
||||
alice_chat = alice.create_group("Group")
|
||||
|
||||
logging.info("Bob joins the group")
|
||||
qr_code = alice_chat.get_qr_code()
|
||||
@@ -89,7 +87,6 @@ def test_qr_securejoin(acfactory, protect):
|
||||
|
||||
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
|
||||
assert snapshot.chat.get_basic_snapshot().is_protected == protect
|
||||
|
||||
# Test that Bob verified Alice's profile.
|
||||
bob_contact_alice = bob.create_contact(alice)
|
||||
@@ -125,8 +122,8 @@ def test_qr_securejoin_contact_request(acfactory) -> None:
|
||||
bob_chat_alice = snapshot.chat
|
||||
assert bob_chat_alice.get_basic_snapshot().is_contact_request
|
||||
|
||||
alice_chat = alice.create_group("Verified group", protect=True)
|
||||
logging.info("Bob joins verified group")
|
||||
alice_chat = alice.create_group("Group")
|
||||
logging.info("Bob joins the group")
|
||||
qr_code = alice_chat.get_qr_code()
|
||||
bob.secure_join(qr_code)
|
||||
while True:
|
||||
@@ -150,8 +147,8 @@ def test_qr_readreceipt(acfactory) -> None:
|
||||
for joiner in [bob, charlie]:
|
||||
joiner.wait_for_securejoin_joiner_success()
|
||||
|
||||
logging.info("Alice creates a verified group")
|
||||
group = alice.create_group("Group", protect=True)
|
||||
logging.info("Alice creates a group")
|
||||
group = alice.create_group("Group")
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_contact_charlie = alice.create_contact(charlie, "Charlie")
|
||||
@@ -216,11 +213,10 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
|
||||
"""Tests verified group recovery by reverifying then removing and adding a member back."""
|
||||
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||
|
||||
logging.info("ac1 creates verified group")
|
||||
chat = ac1.create_group("Verified group", protect=True)
|
||||
assert chat.get_basic_snapshot().is_protected
|
||||
logging.info("ac1 creates a group")
|
||||
chat = ac1.create_group("Group")
|
||||
|
||||
logging.info("ac2 joins verified group")
|
||||
logging.info("ac2 joins the group")
|
||||
qr_code = chat.get_qr_code()
|
||||
ac2.secure_join(qr_code)
|
||||
ac2.wait_for_securejoin_joiner_success()
|
||||
@@ -302,8 +298,8 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
||||
# we first create a fully joined verified group, and then start
|
||||
# joining a second time but interrupt it, to create pending bob state
|
||||
|
||||
logging.info("ac1: create verified group that ac2 fully joins")
|
||||
ch1 = ac1.create_group("Group", protect=True)
|
||||
logging.info("ac1: create a group that ac2 fully joins")
|
||||
ch1 = ac1.create_group("Group")
|
||||
qr_code = ch1.get_qr_code()
|
||||
ac2.secure_join(qr_code)
|
||||
ac1.wait_for_securejoin_inviter_success()
|
||||
@@ -313,7 +309,6 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
||||
while 1:
|
||||
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
if snapshot.text == "ac1 says hello":
|
||||
assert snapshot.chat.get_basic_snapshot().is_protected
|
||||
break
|
||||
|
||||
logging.info("ac1: let ac2 join again but shutoff ac1 in the middle of securejoin")
|
||||
@@ -327,7 +322,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
||||
assert ac2.create_contact(ac3).get_snapshot().is_verified
|
||||
|
||||
logging.info("ac3: create a verified group VG with ac2")
|
||||
vg = ac3.create_group("ac3-created", protect=True)
|
||||
vg = ac3.create_group("ac3-created")
|
||||
vg.add_contact(ac3.create_contact(ac2))
|
||||
|
||||
# ensure ac2 receives message in VG
|
||||
@@ -335,7 +330,6 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
||||
while 1:
|
||||
msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
if msg.text == "hello":
|
||||
assert msg.chat.get_basic_snapshot().is_protected
|
||||
break
|
||||
|
||||
logging.info("ac3: create a join-code for group VG and let ac4 join, check that ac2 got it")
|
||||
@@ -359,7 +353,7 @@ def test_qr_new_group_unblocked(acfactory):
|
||||
"""
|
||||
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1_chat = ac1.create_group("Group for joining", protect=True)
|
||||
ac1_chat = ac1.create_group("Group for joining")
|
||||
qr_code = ac1_chat.get_qr_code()
|
||||
ac2.secure_join(qr_code)
|
||||
|
||||
@@ -384,8 +378,7 @@ def test_aeap_flow_verified(acfactory):
|
||||
addr, password = acfactory.get_credentials()
|
||||
|
||||
logging.info("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat = ac1.create_group("hello", protect=True)
|
||||
assert chat.get_basic_snapshot().is_protected
|
||||
chat = ac1.create_group("hello")
|
||||
qr_code = chat.get_qr_code()
|
||||
logging.info("ac2: start QR-code based join-group protocol")
|
||||
ac2.secure_join(qr_code)
|
||||
@@ -439,7 +432,6 @@ def test_gossip_verification(acfactory) -> None:
|
||||
|
||||
logging.info("Bob creates an Autocrypt group")
|
||||
bob_group_chat = bob.create_group("Autocrypt Group")
|
||||
assert not bob_group_chat.get_basic_snapshot().is_protected
|
||||
bob_group_chat.add_contact(bob_contact_alice)
|
||||
bob_group_chat.add_contact(bob_contact_carol)
|
||||
bob_group_chat.send_message(text="Hello Autocrypt group")
|
||||
@@ -448,13 +440,12 @@ def test_gossip_verification(acfactory) -> None:
|
||||
assert snapshot.text == "Hello Autocrypt group"
|
||||
assert snapshot.show_padlock
|
||||
|
||||
# Autocrypt group does not propagate verification.
|
||||
# Group propagates verification using Autocrypt-Gossip header.
|
||||
carol_contact_alice_snapshot = carol_contact_alice.get_snapshot()
|
||||
assert not carol_contact_alice_snapshot.is_verified
|
||||
assert carol_contact_alice_snapshot.is_verified
|
||||
|
||||
logging.info("Bob creates a Securejoin group")
|
||||
bob_group_chat = bob.create_group("Securejoin Group", protect=True)
|
||||
assert bob_group_chat.get_basic_snapshot().is_protected
|
||||
bob_group_chat = bob.create_group("Securejoin Group")
|
||||
bob_group_chat.add_contact(bob_contact_alice)
|
||||
bob_group_chat.add_contact(bob_contact_carol)
|
||||
bob_group_chat.send_message(text="Hello Securejoin group")
|
||||
@@ -477,7 +468,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||
|
||||
# ac3 creates protected group with ac1.
|
||||
ac3_chat = ac3.create_group("Verified group", protect=True)
|
||||
ac3_chat = ac3.create_group("Group")
|
||||
|
||||
# ac1 joins ac3 group.
|
||||
ac3_qr_code = ac3_chat.get_qr_code()
|
||||
@@ -525,7 +516,6 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.is_info
|
||||
ac2_chat = snapshot.chat
|
||||
assert ac2_chat.get_basic_snapshot().is_protected
|
||||
assert len(ac2_chat.get_contacts()) == 3
|
||||
|
||||
# ac1 is still "not verified" for ac2 due to inconsistent state.
|
||||
@@ -535,9 +525,8 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
def test_withdraw_securejoin_qr(acfactory):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
logging.info("Alice creates a verified group")
|
||||
alice_chat = alice.create_group("Verified group", protect=True)
|
||||
assert alice_chat.get_basic_snapshot().is_protected
|
||||
logging.info("Alice creates a group")
|
||||
alice_chat = alice.create_group("Group")
|
||||
logging.info("Bob joins verified group")
|
||||
|
||||
qr_code = alice_chat.get_qr_code()
|
||||
@@ -548,7 +537,6 @@ def test_withdraw_securejoin_qr(acfactory):
|
||||
|
||||
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
|
||||
assert snapshot.chat.get_basic_snapshot().is_protected
|
||||
bob_chat.leave()
|
||||
|
||||
snapshot = alice.get_message_by_id(alice.wait_for_msgs_changed_event().msg_id).get_snapshot()
|
||||
|
||||
@@ -570,8 +570,11 @@ def test_provider_info(rpc) -> None:
|
||||
assert provider_info is None
|
||||
|
||||
# Test MX record resolution.
|
||||
# This previously resulted in Gmail provider
|
||||
# because MX record pointed to google.com domain,
|
||||
# but MX record resolution has been removed.
|
||||
provider_info = rpc.get_provider_info(account_id, "github.com")
|
||||
assert provider_info["id"] == "gmail"
|
||||
assert provider_info is None
|
||||
|
||||
# Disable MX record resolution.
|
||||
rpc.set_config(account_id, "proxy_enabled", "1")
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
def test_vcard(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob, fiona = acfactory.get_online_accounts(3)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_contact_charlie = alice.create_contact("charlie@example.org", "Charlie")
|
||||
alice_contact_charlie_snapshot = alice_contact_charlie.get_snapshot()
|
||||
alice_contact_fiona = alice.create_contact(fiona, "Fiona")
|
||||
alice_contact_fiona_snapshot = alice_contact_fiona.get_snapshot()
|
||||
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
alice_chat_bob.send_contact(alice_contact_charlie)
|
||||
@@ -12,3 +15,12 @@ def test_vcard(acfactory) -> None:
|
||||
snapshot = message.get_snapshot()
|
||||
assert snapshot.vcard_contact
|
||||
assert snapshot.vcard_contact.addr == "charlie@example.org"
|
||||
assert snapshot.vcard_contact.color == alice_contact_charlie_snapshot.color
|
||||
|
||||
alice_chat_bob.send_contact(alice_contact_fiona)
|
||||
event = bob.wait_for_incoming_msg_event()
|
||||
message = bob.get_message_by_id(event.msg_id)
|
||||
snapshot = message.get_snapshot()
|
||||
assert snapshot.vcard_contact
|
||||
assert snapshot.vcard_contact.key
|
||||
assert snapshot.vcard_contact.color == alice_contact_fiona_snapshot.color
|
||||
|
||||
@@ -404,18 +404,16 @@ class Account:
|
||||
self,
|
||||
name: str,
|
||||
contacts: Optional[List[Contact]] = None,
|
||||
verified: bool = False,
|
||||
) -> Chat:
|
||||
"""create a new group chat object.
|
||||
|
||||
Chats are unpromoted until the first message is sent.
|
||||
|
||||
:param contacts: list of contacts to add
|
||||
:param verified: if true only verified contacts can be added.
|
||||
:returns: a :class:`deltachat.chat.Chat` object.
|
||||
"""
|
||||
bytes_name = name.encode("utf8")
|
||||
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
|
||||
chat_id = lib.dc_create_group_chat(self._dc_context, 0, bytes_name)
|
||||
chat = Chat(self, chat_id)
|
||||
if contacts is not None:
|
||||
for contact in contacts:
|
||||
|
||||
@@ -142,13 +142,6 @@ class Chat:
|
||||
"""
|
||||
return bool(lib.dc_chat_can_send(self._dc_chat))
|
||||
|
||||
def is_protected(self) -> bool:
|
||||
"""return True if this chat is a protected chat.
|
||||
|
||||
:returns: True if chat is protected, False otherwise.
|
||||
"""
|
||||
return bool(lib.dc_chat_is_protected(self._dc_chat))
|
||||
|
||||
def get_name(self) -> Optional[str]:
|
||||
"""return name of this chat.
|
||||
|
||||
|
||||
@@ -604,20 +604,6 @@ class ACFactory:
|
||||
ac2.create_chat(ac1)
|
||||
return ac1.create_chat(ac2)
|
||||
|
||||
def get_protected_chat(self, ac1: Account, ac2: Account):
|
||||
chat = ac1.create_group_chat("Protected Group", verified=True)
|
||||
qr = chat.get_join_qr()
|
||||
ac2.qr_join_chat(qr)
|
||||
ac2._evtracker.wait_securejoin_joiner_progress(1000)
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
msg = ac2.get_message_by_id(ev.data2)
|
||||
assert msg is not None
|
||||
assert msg.text == "Messages are end-to-end encrypted."
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg is not None
|
||||
assert "Member Me " in msg.text and " added by " in msg.text
|
||||
return chat
|
||||
|
||||
def introduce_each_other(self, accounts, sending=True):
|
||||
to_wait = []
|
||||
for i, acc in enumerate(accounts):
|
||||
|
||||
@@ -118,8 +118,7 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
|
||||
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||
ac1_addr = ac1.get_self_contact().addr
|
||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat1 = ac1.create_group_chat("hello", verified=True)
|
||||
assert chat1.is_protected()
|
||||
chat1 = ac1.create_group_chat("hello")
|
||||
qr = chat1.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
chat2 = ac2.qr_join_chat(qr)
|
||||
@@ -142,7 +141,6 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
|
||||
lp.sec("ac2: read message and check that it's a verified chat")
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg.text == "hello"
|
||||
assert msg.chat.is_protected()
|
||||
assert msg.is_encrypted()
|
||||
|
||||
lp.sec("ac2: Check that ac2 verified ac1")
|
||||
@@ -173,8 +171,10 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
|
||||
lp.sec("ac2: Check that ac1 verified ac3 for ac2")
|
||||
ac2_ac1_contact = ac2.get_contacts()[0]
|
||||
assert ac2.get_self_contact().get_verifier(ac2_ac1_contact).id == dc.const.DC_CONTACT_ID_SELF
|
||||
ac2_ac3_contact = ac2.get_contacts()[1]
|
||||
assert ac2.get_self_contact().get_verifier(ac2_ac3_contact).addr == ac1_addr
|
||||
for ac2_contact in chat2.get_contacts():
|
||||
if ac2_contact == ac2_ac1_contact or ac2_contact.id == dc.const.DC_CONTACT_ID_SELF:
|
||||
continue
|
||||
assert ac2.get_self_contact().get_verifier(ac2_contact).addr == ac1_addr
|
||||
|
||||
lp.sec("ac2: send message and let ac3 read it")
|
||||
chat2.send_text("hi")
|
||||
@@ -266,8 +266,7 @@ def test_see_new_verified_member_after_going_online(acfactory, tmp_path, lp):
|
||||
ac1_offl.stop_io()
|
||||
|
||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat = ac1.create_group_chat("hello", verified=True)
|
||||
assert chat.is_protected()
|
||||
chat = ac1.create_group_chat("hello")
|
||||
qr = chat.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
chat2 = ac2.qr_join_chat(qr)
|
||||
@@ -321,8 +320,7 @@ def test_use_new_verified_group_after_going_online(acfactory, data, tmp_path, lp
|
||||
ac1.set_avatar(avatar_path)
|
||||
|
||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat = ac1.create_group_chat("hello", verified=True)
|
||||
assert chat.is_protected()
|
||||
chat = ac1.create_group_chat("hello")
|
||||
qr = chat.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
ac2.qr_join_chat(qr)
|
||||
@@ -336,7 +334,6 @@ def test_use_new_verified_group_after_going_online(acfactory, data, tmp_path, lp
|
||||
assert msg_in.is_system_message()
|
||||
assert contact.addr == ac1.get_config("addr")
|
||||
chat2 = msg_in.chat
|
||||
assert chat2.is_protected()
|
||||
assert chat2.get_messages()[0].text == "Messages are end-to-end encrypted."
|
||||
assert open(contact.get_profile_image(), "rb").read() == open(avatar_path, "rb").read()
|
||||
|
||||
@@ -376,8 +373,7 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
|
||||
ac2_offl.stop_io()
|
||||
|
||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat1 = ac1.create_group_chat("hello", verified=True)
|
||||
assert chat1.is_protected()
|
||||
chat1 = ac1.create_group_chat("hello")
|
||||
qr = chat1.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
chat2 = ac2.qr_join_chat(qr)
|
||||
@@ -402,28 +398,17 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
|
||||
assert ac2_offl_ac1_contact.addr == ac1.get_config("addr")
|
||||
assert not ac2_offl_ac1_contact.is_verified()
|
||||
chat2_offl = msg_in.chat
|
||||
assert not chat2_offl.is_protected()
|
||||
|
||||
lp.sec("ac2: sending message re-gossiping Autocrypt keys")
|
||||
chat2.send_text("hi2")
|
||||
|
||||
lp.sec("ac2_offl: receiving message")
|
||||
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg_in = ac2_offl.get_message_by_id(ev.data2)
|
||||
assert msg_in.is_system_message()
|
||||
assert msg_in.text == "Messages are end-to-end encrypted."
|
||||
|
||||
# We need to consume one event that has data2=0
|
||||
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev.data2 == 0
|
||||
|
||||
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg_in = ac2_offl.get_message_by_id(ev.data2)
|
||||
assert not msg_in.is_system_message()
|
||||
assert msg_in.text == "hi2"
|
||||
assert msg_in.chat == chat2_offl
|
||||
assert msg_in.get_sender_contact().addr == ac2.get_config("addr")
|
||||
assert msg_in.chat.is_protected()
|
||||
assert ac2_offl_ac1_contact.is_verified()
|
||||
|
||||
|
||||
|
||||
@@ -1204,7 +1204,7 @@ def test_import_export_online_all(acfactory, tmp_path, data, lp):
|
||||
|
||||
def test_qr_email_capitalization(acfactory, lp):
|
||||
"""Regression test for a bug
|
||||
that resulted in failure to propagate verification via gossip in a verified group
|
||||
that resulted in failure to propagate verification
|
||||
when the database already contained the contact with a different email address capitalization.
|
||||
"""
|
||||
|
||||
@@ -1215,17 +1215,17 @@ def test_qr_email_capitalization(acfactory, lp):
|
||||
lp.sec(f"ac1 creates a contact for ac2 ({ac2_addr_uppercase})")
|
||||
ac1.create_contact(ac2_addr_uppercase)
|
||||
|
||||
lp.sec("ac3 creates a verified group with a QR code")
|
||||
chat = ac3.create_group_chat("hello", verified=True)
|
||||
lp.sec("ac3 creates a group with a QR code")
|
||||
chat = ac3.create_group_chat("hello")
|
||||
qr = chat.get_join_qr()
|
||||
|
||||
lp.sec("ac1 joins a verified group via a QR code")
|
||||
lp.sec("ac1 joins a group via a QR code")
|
||||
ac1_chat = ac1.qr_join_chat(qr)
|
||||
msg = ac1._evtracker.wait_next_incoming_message()
|
||||
assert msg.text == "Member Me added by {}.".format(ac3.get_config("addr"))
|
||||
assert len(ac1_chat.get_contacts()) == 2
|
||||
|
||||
lp.sec("ac2 joins a verified group via a QR code")
|
||||
lp.sec("ac2 joins a group via a QR code")
|
||||
ac2.qr_join_chat(qr)
|
||||
ac1._evtracker.wait_next_incoming_message()
|
||||
|
||||
|
||||
@@ -271,10 +271,9 @@ class TestOfflineChat:
|
||||
chat.set_name("Homework")
|
||||
assert chat.get_messages()[-1].text == "abc homework xyz Homework"
|
||||
|
||||
@pytest.mark.parametrize("verified", [True, False])
|
||||
def test_group_chat_qr(self, acfactory, ac1, verified):
|
||||
def test_group_chat_qr(self, acfactory, ac1):
|
||||
ac2 = acfactory.get_pseudo_configured_account()
|
||||
chat = ac1.create_group_chat(name="title1", verified=verified)
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
assert chat.is_group()
|
||||
qr = chat.get_join_qr()
|
||||
assert ac2.check_qr(qr).is_ask_verifygroup
|
||||
|
||||
20
spec.md
20
spec.md
@@ -1,6 +1,6 @@
|
||||
# Chatmail Specification
|
||||
|
||||
Version: 0.36.0
|
||||
Version: 0.37.0
|
||||
Status: In-progress
|
||||
Format: [Semantic Line Breaks](https://sembr.org/)
|
||||
|
||||
@@ -582,6 +582,24 @@ and e.g. simply search for the line starting with `EMAIL`
|
||||
in order to get the email address.
|
||||
|
||||
|
||||
# Verifications
|
||||
|
||||
Keys obtained using [SecureJoin](https://securejoin.readthedocs.io) protocol
|
||||
and corresponding contacts
|
||||
are considered "verified".
|
||||
|
||||
As an extension to `Autocrypt-Gossip` header,
|
||||
chatmail clients can add `_verified=1` attribute
|
||||
(underscore marks the attribute as non-critical)
|
||||
to indicate that they have the gossiped key
|
||||
and the corresponding contact marked as verified.
|
||||
|
||||
When receiving such `Autocrypt-Gossip` header
|
||||
in a message signed by a verified key,
|
||||
chatmail clients mark the gossiped key
|
||||
as indirectly verified.
|
||||
|
||||
|
||||
# Transitioning to a new e-mail address (AEAP)
|
||||
|
||||
When receiving a message:
|
||||
|
||||
452
src/chat.rs
452
src/chat.rs
@@ -12,7 +12,6 @@ use std::time::Duration;
|
||||
use anyhow::{Context as _, Result, anyhow, bail, ensure};
|
||||
use chrono::TimeZone;
|
||||
use deltachat_contact_tools::{ContactAddress, sanitize_bidi_characters, sanitize_single_line};
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use mail_builder::mime::MimePart;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::EnumIter;
|
||||
@@ -31,6 +30,7 @@ use crate::debug_logging::maybe_set_logging_xdc;
|
||||
use crate::download::DownloadState;
|
||||
use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
|
||||
use crate::events::EventType;
|
||||
use crate::key::self_fingerprint;
|
||||
use crate::location;
|
||||
use crate::log::{LogExt, error, info, warn};
|
||||
use crate::logged_debug_assert;
|
||||
@@ -67,41 +67,6 @@ pub enum ChatItem {
|
||||
},
|
||||
}
|
||||
|
||||
/// Chat protection status.
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Display,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
FromPrimitive,
|
||||
ToPrimitive,
|
||||
FromSql,
|
||||
ToSql,
|
||||
IntoStaticStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u32)]
|
||||
pub enum ProtectionStatus {
|
||||
/// Chat is not protected.
|
||||
#[default]
|
||||
Unprotected = 0,
|
||||
|
||||
/// Chat is protected.
|
||||
///
|
||||
/// All members of the chat must be verified.
|
||||
Protected = 1,
|
||||
// `2` was never used as a value.
|
||||
|
||||
// Chats don't break in Core v2 anymore. Chats with broken protection existing before the
|
||||
// key-contacts migration are treated as `Unprotected`.
|
||||
//
|
||||
// ProtectionBroken = 3,
|
||||
}
|
||||
|
||||
/// The reason why messages cannot be sent to the chat.
|
||||
///
|
||||
/// The reason is mainly for logging and displaying in debug REPL, thus not translated.
|
||||
@@ -306,14 +271,12 @@ impl ChatId {
|
||||
|
||||
/// Create a group or mailinglist raw database record with the given parameters.
|
||||
/// The function does not add SELF nor checks if the record already exists.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) async fn create_multiuser_record(
|
||||
context: &Context,
|
||||
chattype: Chattype,
|
||||
grpid: &str,
|
||||
grpname: &str,
|
||||
create_blocked: Blocked,
|
||||
create_protected: ProtectionStatus,
|
||||
param: Option<String>,
|
||||
timestamp: i64,
|
||||
) -> Result<Self> {
|
||||
@@ -321,31 +284,27 @@ impl ChatId {
|
||||
let timestamp = cmp::min(timestamp, smeared_time(context));
|
||||
let row_id =
|
||||
context.sql.insert(
|
||||
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, ?, ?);",
|
||||
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, 0, ?);",
|
||||
(
|
||||
chattype,
|
||||
&grpname,
|
||||
grpid,
|
||||
create_blocked,
|
||||
timestamp,
|
||||
create_protected,
|
||||
param.unwrap_or_default(),
|
||||
),
|
||||
).await?;
|
||||
|
||||
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
|
||||
if create_protected == ProtectionStatus::Protected {
|
||||
chat_id
|
||||
.add_protection_msg(context, ProtectionStatus::Protected, None, timestamp)
|
||||
.await?;
|
||||
} else {
|
||||
chat_id.maybe_add_encrypted_msg(context, timestamp).await?;
|
||||
if chat.is_encrypted(context).await? {
|
||||
chat_id.add_encrypted_msg(context, timestamp).await?;
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"Created group/mailinglist '{}' grpid={} as {}, blocked={}, protected={create_protected}.",
|
||||
"Created group/mailinglist '{}' grpid={} as {}, blocked={}.",
|
||||
&grpname,
|
||||
grpid,
|
||||
chat_id,
|
||||
@@ -500,111 +459,8 @@ impl ChatId {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets protection without sending a message.
|
||||
///
|
||||
/// Returns whether the protection status was actually modified.
|
||||
pub(crate) async fn inner_set_protection(
|
||||
self,
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
) -> Result<bool> {
|
||||
ensure!(!self.is_special(), "Invalid chat-id {self}.");
|
||||
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
|
||||
if protect == chat.protected {
|
||||
info!(context, "Protection status unchanged for {}.", self);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
match protect {
|
||||
ProtectionStatus::Protected => match chat.typ {
|
||||
Chattype::Single
|
||||
| Chattype::Group
|
||||
| Chattype::OutBroadcast
|
||||
| Chattype::InBroadcast => {}
|
||||
Chattype::Mailinglist => bail!("Cannot protect mailing lists"),
|
||||
},
|
||||
ProtectionStatus::Unprotected => {}
|
||||
};
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute("UPDATE chats SET protected=? WHERE id=?;", (protect, self))
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::ChatModified(self));
|
||||
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||
|
||||
// make sure, the receivers will get all keys
|
||||
self.reset_gossiped_timestamp(context).await?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Adds an info message to the chat, telling the user that the protection status changed.
|
||||
///
|
||||
/// Params:
|
||||
///
|
||||
/// * `contact_id`: In a 1:1 chat, pass the chat partner's contact id.
|
||||
/// * `timestamp_sort` is used as the timestamp of the added message
|
||||
/// and should be the timestamp of the change happening.
|
||||
pub(crate) async fn add_protection_msg(
|
||||
self,
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
contact_id: Option<ContactId>,
|
||||
timestamp_sort: i64,
|
||||
) -> Result<()> {
|
||||
if contact_id == Some(ContactId::SELF) {
|
||||
// Do not add protection messages to Saved Messages chat.
|
||||
// This chat never gets protected and unprotected,
|
||||
// we do not want the first message
|
||||
// to be a protection message with an arbitrary timestamp.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let text = context.stock_protection_msg(protect, contact_id).await;
|
||||
let cmd = match protect {
|
||||
ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
|
||||
ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
|
||||
};
|
||||
add_info_msg_with_cmd(
|
||||
context,
|
||||
self,
|
||||
&text,
|
||||
cmd,
|
||||
timestamp_sort,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds message "Messages are end-to-end encrypted" if appropriate.
|
||||
///
|
||||
/// This function is rather slow because it does a lot of database queries,
|
||||
/// but this is fine because it is only called on chat creation.
|
||||
async fn maybe_add_encrypted_msg(self, context: &Context, timestamp_sort: i64) -> Result<()> {
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
|
||||
// as secure-join adds its own message on success (after some other messasges),
|
||||
// we do not want to add "Messages are end-to-end encrypted" on chat creation.
|
||||
// we detect secure join by `can_send` (for Bob, scanner side) and by `blocked` (for Alice, inviter side) below.
|
||||
if !chat.is_encrypted(context).await?
|
||||
|| self <= DC_CHAT_ID_LAST_SPECIAL
|
||||
|| chat.is_device_talk()
|
||||
|| chat.is_self_talk()
|
||||
|| (!chat.can_send(context).await? && !chat.is_contact_request())
|
||||
|| chat.blocked == Blocked::Yes
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Adds message "Messages are end-to-end encrypted".
|
||||
async fn add_encrypted_msg(self, context: &Context, timestamp_sort: i64) -> Result<()> {
|
||||
let text = stock_str::messages_e2e_encrypted(context).await;
|
||||
add_info_msg_with_cmd(
|
||||
context,
|
||||
@@ -621,74 +477,6 @@ impl ChatId {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets protection and adds a message.
|
||||
///
|
||||
/// `timestamp_sort` is used as the timestamp of the added message
|
||||
/// and should be the timestamp of the change happening.
|
||||
async fn set_protection_for_timestamp_sort(
|
||||
self,
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
timestamp_sort: i64,
|
||||
contact_id: Option<ContactId>,
|
||||
) -> Result<()> {
|
||||
let protection_status_modified = self
|
||||
.inner_set_protection(context, protect)
|
||||
.await
|
||||
.with_context(|| format!("Cannot set protection for {self}"))?;
|
||||
if protection_status_modified {
|
||||
self.add_protection_msg(context, protect, contact_id, timestamp_sort)
|
||||
.await?;
|
||||
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets protection and sends or adds a message.
|
||||
///
|
||||
/// `timestamp_sent` is the "sent" timestamp of a message caused the protection state change.
|
||||
pub(crate) async fn set_protection(
|
||||
self,
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
timestamp_sent: i64,
|
||||
contact_id: Option<ContactId>,
|
||||
) -> Result<()> {
|
||||
let sort_to_bottom = true;
|
||||
let (received, incoming) = (false, false);
|
||||
let ts = self
|
||||
.calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, received, incoming)
|
||||
.await?
|
||||
// Always sort protection messages below `SystemMessage::SecurejoinWait{,Timeout}` ones
|
||||
// in case of race conditions.
|
||||
.saturating_add(1);
|
||||
self.set_protection_for_timestamp_sort(context, protect, ts, contact_id)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the 1:1 chat with the given address to ProtectionStatus::Protected,
|
||||
/// and posts a `SystemMessage::ChatProtectionEnabled` into it.
|
||||
///
|
||||
/// If necessary, creates a hidden chat for this.
|
||||
pub(crate) async fn set_protection_for_contact(
|
||||
context: &Context,
|
||||
contact_id: ContactId,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
let chat_id = ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Yes)
|
||||
.await
|
||||
.with_context(|| format!("can't create chat for {contact_id}"))?;
|
||||
chat_id
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
timestamp,
|
||||
Some(contact_id),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Archives or unarchives a chat.
|
||||
pub async fn set_visibility(self, context: &Context, visibility: ChatVisibility) -> Result<()> {
|
||||
self.set_visibility_ex(context, Sync, visibility).await
|
||||
@@ -1396,16 +1184,6 @@ impl ChatId {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if the chat is protected.
|
||||
pub async fn is_protected(self, context: &Context) -> Result<ProtectionStatus> {
|
||||
let protection_status = context
|
||||
.sql
|
||||
.query_get_value("SELECT protected FROM chats WHERE id=?", (self,))
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
Ok(protection_status)
|
||||
}
|
||||
|
||||
/// Returns the sort timestamp for a new message in the chat.
|
||||
///
|
||||
/// `message_timestamp` should be either the message "sent" timestamp or a timestamp of the
|
||||
@@ -1560,9 +1338,6 @@ pub struct Chat {
|
||||
|
||||
/// Duration of the chat being muted.
|
||||
pub mute_duration: MuteDuration,
|
||||
|
||||
/// If the chat is protected (verified).
|
||||
pub(crate) protected: ProtectionStatus,
|
||||
}
|
||||
|
||||
impl Chat {
|
||||
@@ -1572,7 +1347,7 @@ impl Chat {
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT c.type, c.name, c.grpid, c.param, c.archived,
|
||||
c.blocked, c.locations_send_until, c.muted_until, c.protected
|
||||
c.blocked, c.locations_send_until, c.muted_until
|
||||
FROM chats c
|
||||
WHERE c.id=?;",
|
||||
(chat_id,),
|
||||
@@ -1587,7 +1362,6 @@ impl Chat {
|
||||
blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
|
||||
is_sending_locations: row.get(6)?,
|
||||
mute_duration: row.get(7)?,
|
||||
protected: row.get(8)?,
|
||||
};
|
||||
Ok(c)
|
||||
},
|
||||
@@ -1868,53 +1642,38 @@ impl Chat {
|
||||
!self.is_unpromoted()
|
||||
}
|
||||
|
||||
/// Returns true if chat protection is enabled.
|
||||
///
|
||||
/// UI should display a green checkmark
|
||||
/// in the chat title,
|
||||
/// in the chat profile 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.
|
||||
pub fn is_protected(&self) -> bool {
|
||||
self.protected == ProtectionStatus::Protected
|
||||
}
|
||||
|
||||
/// Returns true if the chat is encrypted.
|
||||
pub async fn is_encrypted(&self, context: &Context) -> Result<bool> {
|
||||
let is_encrypted = self.is_protected()
|
||||
|| match self.typ {
|
||||
Chattype::Single => {
|
||||
match context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT cc.contact_id, c.fingerprint<>''
|
||||
let is_encrypted = match self.typ {
|
||||
Chattype::Single => {
|
||||
match context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT cc.contact_id, c.fingerprint<>''
|
||||
FROM chats_contacts cc LEFT JOIN contacts c
|
||||
ON c.id=cc.contact_id
|
||||
WHERE cc.chat_id=?
|
||||
",
|
||||
(self.id,),
|
||||
|row| {
|
||||
let id: ContactId = row.get(0)?;
|
||||
let is_key: bool = row.get(1)?;
|
||||
Ok((id, is_key))
|
||||
},
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some((id, is_key)) => is_key || id == ContactId::DEVICE,
|
||||
None => true,
|
||||
}
|
||||
(self.id,),
|
||||
|row| {
|
||||
let id: ContactId = row.get(0)?;
|
||||
let is_key: bool = row.get(1)?;
|
||||
Ok((id, is_key))
|
||||
},
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some((id, is_key)) => is_key || id == ContactId::DEVICE,
|
||||
None => true,
|
||||
}
|
||||
Chattype::Group => {
|
||||
// Do not encrypt ad-hoc groups.
|
||||
!self.grpid.is_empty()
|
||||
}
|
||||
Chattype::Mailinglist => false,
|
||||
Chattype::OutBroadcast | Chattype::InBroadcast => true,
|
||||
};
|
||||
}
|
||||
Chattype::Group => {
|
||||
// Do not encrypt ad-hoc groups.
|
||||
!self.grpid.is_empty()
|
||||
}
|
||||
Chattype::Mailinglist => false,
|
||||
Chattype::OutBroadcast | Chattype::InBroadcast => true,
|
||||
};
|
||||
Ok(is_encrypted)
|
||||
}
|
||||
|
||||
@@ -2248,17 +2007,21 @@ impl Chat {
|
||||
/// Sends a `SyncAction` synchronising chat contacts to other devices.
|
||||
pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
|
||||
if self.is_encrypted(context).await? {
|
||||
let self_fp = self_fingerprint(context).await?;
|
||||
let fingerprint_addrs = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT c.fingerprint, c.addr
|
||||
"SELECT c.id, c.fingerprint, c.addr
|
||||
FROM contacts c INNER JOIN chats_contacts cc
|
||||
ON c.id=cc.contact_id
|
||||
WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
|
||||
(self.id,),
|
||||
|row| {
|
||||
let fingerprint = row.get(0)?;
|
||||
let addr = row.get(1)?;
|
||||
if row.get::<_, ContactId>(0)? == ContactId::SELF {
|
||||
return Ok((self_fp.to_string(), String::new()));
|
||||
}
|
||||
let fingerprint = row.get(1)?;
|
||||
let addr = row.get(2)?;
|
||||
Ok((fingerprint, addr))
|
||||
},
|
||||
|addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
@@ -2625,7 +2388,6 @@ impl ChatIdBlocked {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let protected = contact_id == ContactId::SELF || contact.is_verified(context).await?;
|
||||
let smeared_time = create_smeared_timestamp(context);
|
||||
|
||||
let chat_id = context
|
||||
@@ -2633,19 +2395,14 @@ impl ChatIdBlocked {
|
||||
.transaction(move |transaction| {
|
||||
transaction.execute(
|
||||
"INSERT INTO chats
|
||||
(type, name, param, blocked, created_timestamp, protected)
|
||||
VALUES(?, ?, ?, ?, ?, ?)",
|
||||
(type, name, param, blocked, created_timestamp)
|
||||
VALUES(?, ?, ?, ?, ?)",
|
||||
(
|
||||
Chattype::Single,
|
||||
chat_name,
|
||||
params.to_string(),
|
||||
create_blocked as u8,
|
||||
smeared_time,
|
||||
if protected {
|
||||
ProtectionStatus::Protected
|
||||
} else {
|
||||
ProtectionStatus::Unprotected
|
||||
},
|
||||
),
|
||||
)?;
|
||||
let chat_id = ChatId::new(
|
||||
@@ -2666,19 +2423,12 @@ impl ChatIdBlocked {
|
||||
})
|
||||
.await?;
|
||||
|
||||
if protected {
|
||||
chat_id
|
||||
.add_protection_msg(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
Some(contact_id),
|
||||
smeared_time,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
chat_id
|
||||
.maybe_add_encrypted_msg(context, smeared_time)
|
||||
.await?;
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
if chat.is_encrypted(context).await?
|
||||
&& !chat.param.exists(Param::Devicetalk)
|
||||
&& !chat.param.exists(Param::Selftalk)
|
||||
{
|
||||
chat_id.add_encrypted_msg(context, smeared_time).await?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
@@ -3542,6 +3292,7 @@ pub async fn get_chat_media(
|
||||
msg_type: Viewtype,
|
||||
msg_type2: Viewtype,
|
||||
msg_type3: Viewtype,
|
||||
limit: Option<u32>,
|
||||
) -> Result<Vec<MsgId>> {
|
||||
let list = if msg_type == Viewtype::Webxdc
|
||||
&& msg_type2 == Viewtype::Unknown
|
||||
@@ -3556,12 +3307,14 @@ pub async fn get_chat_media(
|
||||
AND chat_id != ?
|
||||
AND type = ?
|
||||
AND hidden=0
|
||||
ORDER BY max(timestamp, timestamp_rcvd), id;",
|
||||
ORDER BY max(timestamp, timestamp_rcvd), id
|
||||
LIMIT ?;",
|
||||
(
|
||||
chat_id.is_none(),
|
||||
chat_id.unwrap_or_else(|| ChatId::new(0)),
|
||||
DC_CHAT_ID_TRASH,
|
||||
Viewtype::Webxdc,
|
||||
limit.map(|l| l.to_string()).unwrap_or("ALL".to_string()),
|
||||
),
|
||||
|row| row.get::<_, MsgId>(0),
|
||||
|ids| Ok(ids.flatten().collect()),
|
||||
@@ -3577,7 +3330,8 @@ pub async fn get_chat_media(
|
||||
AND chat_id != ?
|
||||
AND type IN (?, ?, ?)
|
||||
AND hidden=0
|
||||
ORDER BY timestamp, id;",
|
||||
ORDER BY timestamp, id
|
||||
LIMIT ?;",
|
||||
(
|
||||
chat_id.is_none(),
|
||||
chat_id.unwrap_or_else(|| ChatId::new(0)),
|
||||
@@ -3593,6 +3347,7 @@ pub async fn get_chat_media(
|
||||
} else {
|
||||
msg_type
|
||||
},
|
||||
limit.map(|l| l.to_string()).unwrap_or("ALL".to_string()),
|
||||
),
|
||||
|row| row.get::<_, MsgId>(0),
|
||||
|ids| Ok(ids.flatten().collect()),
|
||||
@@ -3650,23 +3405,26 @@ pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Resul
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
/// Creates a group chat with a given `name`.
|
||||
/// Deprecated on 2025-06-21, use `create_group_ex()`.
|
||||
pub async fn create_group_chat(
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
name: &str,
|
||||
) -> Result<ChatId> {
|
||||
create_group_ex(context, Some(protect), name).await
|
||||
/// Creates an encrypted group chat.
|
||||
pub async fn create_group(context: &Context, name: &str) -> Result<ChatId> {
|
||||
create_group_ex(context, Sync, create_id(), name).await
|
||||
}
|
||||
|
||||
/// Creates an unencrypted group chat.
|
||||
pub async fn create_group_unencrypted(context: &Context, name: &str) -> Result<ChatId> {
|
||||
create_group_ex(context, Sync, String::new(), name).await
|
||||
}
|
||||
|
||||
/// Creates a group chat.
|
||||
///
|
||||
/// * `encryption` - If `Some`, the chat is encrypted (with key-contacts) and can be protected.
|
||||
/// * `sync` - Whether a multi-device synchronization message should be sent. Ignored for
|
||||
/// unencrypted chats currently.
|
||||
/// * `grpid` - Group ID. Iff nonempty, the chat is encrypted (with key-contacts).
|
||||
/// * `name` - Chat name.
|
||||
pub async fn create_group_ex(
|
||||
pub(crate) async fn create_group_ex(
|
||||
context: &Context,
|
||||
encryption: Option<ProtectionStatus>,
|
||||
sync: sync::Sync,
|
||||
grpid: String,
|
||||
name: &str,
|
||||
) -> Result<ChatId> {
|
||||
let mut chat_name = sanitize_single_line(name);
|
||||
@@ -3677,11 +3435,6 @@ pub async fn create_group_ex(
|
||||
chat_name = "…".to_string();
|
||||
}
|
||||
|
||||
let grpid = match encryption {
|
||||
Some(_) => create_id(),
|
||||
None => String::new(),
|
||||
};
|
||||
|
||||
let timestamp = create_smeared_timestamp(context);
|
||||
let row_id = context
|
||||
.sql
|
||||
@@ -3689,7 +3442,7 @@ pub async fn create_group_ex(
|
||||
"INSERT INTO chats
|
||||
(type, name, grpid, param, created_timestamp)
|
||||
VALUES(?, ?, ?, \'U=1\', ?);",
|
||||
(Chattype::Group, chat_name, grpid, timestamp),
|
||||
(Chattype::Group, &chat_name, &grpid, timestamp),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -3700,19 +3453,9 @@ pub async fn create_group_ex(
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
|
||||
match encryption {
|
||||
Some(ProtectionStatus::Protected) => {
|
||||
let protect = ProtectionStatus::Protected;
|
||||
chat_id
|
||||
.set_protection_for_timestamp_sort(context, protect, timestamp, None)
|
||||
.await?;
|
||||
}
|
||||
Some(ProtectionStatus::Unprotected) => {
|
||||
// Add "Messages are end-to-end encrypted." message
|
||||
// even to unprotected chats.
|
||||
chat_id.maybe_add_encrypted_msg(context, timestamp).await?;
|
||||
}
|
||||
None => {}
|
||||
if !grpid.is_empty() {
|
||||
// Add "Messages are end-to-end encrypted." message.
|
||||
chat_id.add_encrypted_msg(context, timestamp).await?;
|
||||
}
|
||||
|
||||
if !context.get_config_bool(Config::Bot).await?
|
||||
@@ -3721,7 +3464,11 @@ pub async fn create_group_ex(
|
||||
let text = stock_str::new_group_send_first_message(context).await;
|
||||
add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
|
||||
}
|
||||
|
||||
if let (true, true) = (sync.into(), !grpid.is_empty()) {
|
||||
let id = SyncId::Grpid(grpid);
|
||||
let action = SyncAction::CreateGroupEncrypted(chat_name);
|
||||
self::sync(context, id, action).await.log_err(context).ok();
|
||||
}
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
@@ -3737,7 +3484,7 @@ pub async fn create_group_ex(
|
||||
/// which would make it hard to grep for it.
|
||||
///
|
||||
/// After creation, the chat contains no recipients and is in _unpromoted_ state;
|
||||
/// see [`create_group_chat`] for more information on the unpromoted state.
|
||||
/// see [`create_group`] for more information on the unpromoted state.
|
||||
///
|
||||
/// Returns the created chat's id.
|
||||
pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
|
||||
@@ -3961,13 +3708,6 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
}
|
||||
} else {
|
||||
// else continue and send status mail
|
||||
if chat.is_protected() && !contact.is_verified(context).await? {
|
||||
error!(
|
||||
context,
|
||||
"Cannot add non-bidirectionally verified contact {contact_id} to protected chat {chat_id}."
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
if is_contact_in_chat(context, chat_id, contact_id).await? {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -4627,24 +4367,21 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of `(chatid, is_protected, blocked)`.
|
||||
/// Returns a tuple of `(chatid, blocked)`.
|
||||
pub(crate) async fn get_chat_id_by_grpid(
|
||||
context: &Context,
|
||||
grpid: &str,
|
||||
) -> Result<Option<(ChatId, bool, Blocked)>> {
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT id, blocked, protected FROM chats WHERE grpid=?;",
|
||||
"SELECT id, blocked FROM chats WHERE grpid=?;",
|
||||
(grpid,),
|
||||
|row| {
|
||||
let chat_id = row.get::<_, ChatId>(0)?;
|
||||
|
||||
let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
|
||||
let p = row
|
||||
.get::<_, Option<ProtectionStatus>>(2)?
|
||||
.unwrap_or_default();
|
||||
Ok((chat_id, p == ProtectionStatus::Protected, b))
|
||||
Ok((chat_id, b))
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -4947,16 +4684,14 @@ async fn set_contacts_by_fingerprints(
|
||||
"Cannot add key-contacts to unencrypted chat {id}"
|
||||
);
|
||||
ensure!(
|
||||
chat.typ == Chattype::OutBroadcast,
|
||||
"{id} is not a broadcast list",
|
||||
matches!(chat.typ, Chattype::Group | Chattype::OutBroadcast),
|
||||
"{id} is not a group or broadcast",
|
||||
);
|
||||
let mut contacts = HashSet::new();
|
||||
for (fingerprint, addr) in fingerprint_addrs {
|
||||
let contact_addr = ContactAddress::new(addr)?;
|
||||
let contact =
|
||||
Contact::add_or_lookup_ex(context, "", &contact_addr, fingerprint, Origin::Hidden)
|
||||
.await?
|
||||
.0;
|
||||
let contact = Contact::add_or_lookup_ex(context, "", addr, fingerprint, Origin::Hidden)
|
||||
.await?
|
||||
.0;
|
||||
contacts.insert(contact);
|
||||
}
|
||||
let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
|
||||
@@ -4995,7 +4730,7 @@ pub(crate) enum SyncId {
|
||||
/// "Message-ID"-s, from oldest to latest. Used for ad-hoc groups.
|
||||
Msgids(Vec<String>),
|
||||
|
||||
// Special id for device chat.
|
||||
/// Special id for device chat.
|
||||
Device,
|
||||
}
|
||||
|
||||
@@ -5009,6 +4744,8 @@ pub(crate) enum SyncAction {
|
||||
SetMuted(MuteDuration),
|
||||
/// Create broadcast channel with the given name.
|
||||
CreateBroadcast(String),
|
||||
/// Create encrypted group chat with the given name.
|
||||
CreateGroupEncrypted(String),
|
||||
Rename(String),
|
||||
/// Set chat contacts by their addresses.
|
||||
SetContacts(Vec<String>),
|
||||
@@ -5074,6 +4811,9 @@ impl Context {
|
||||
if let SyncAction::CreateBroadcast(name) = action {
|
||||
create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
|
||||
return Ok(());
|
||||
} else if let SyncAction::CreateGroupEncrypted(name) = action {
|
||||
create_group_ex(self, Nosync, grpid.clone(), name).await?;
|
||||
return Ok(());
|
||||
}
|
||||
get_chat_id_by_grpid(self, grpid)
|
||||
.await?
|
||||
@@ -5095,7 +4835,7 @@ impl Context {
|
||||
SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
|
||||
SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
|
||||
SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
|
||||
SyncAction::CreateBroadcast(_) => {
|
||||
SyncAction::CreateBroadcast(_) | SyncAction::CreateGroupEncrypted(..) => {
|
||||
Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
|
||||
}
|
||||
SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
|
||||
|
||||
@@ -96,7 +96,7 @@ async fn test_get_draft() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_draft() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "abc").await?;
|
||||
let chat_id = create_group(&t, "abc").await?;
|
||||
|
||||
let mut msg = Message::new_text("hi!".to_string());
|
||||
chat_id.set_draft(&t, Some(&mut msg)).await?;
|
||||
@@ -120,7 +120,7 @@ async fn test_forwarding_draft_failing() -> Result<()> {
|
||||
chat_id.set_draft(&t, Some(&mut msg)).await?;
|
||||
assert_eq!(msg.id, chat_id.get_draft(&t).await?.unwrap().id);
|
||||
|
||||
let chat_id2 = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id2 = create_group(&t, "foo").await?;
|
||||
assert!(forward_msgs(&t, &[msg.id], chat_id2).await.is_err());
|
||||
Ok(())
|
||||
}
|
||||
@@ -169,7 +169,7 @@ async fn test_draft_stable_ids() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_only_one_draft_per_chat() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "abc").await?;
|
||||
let chat_id = create_group(&t, "abc").await?;
|
||||
|
||||
let msgs: Vec<message::Message> = (1..=1000)
|
||||
.map(|i| Message::new_text(i.to_string()))
|
||||
@@ -196,7 +196,7 @@ async fn test_only_one_draft_per_chat() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_change_quotes_on_reused_message_object() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
let chat_id = create_group(&t, "chat").await?;
|
||||
let quote1 =
|
||||
Message::load_from_db(&t, send_text_msg(&t, chat_id, "quote1".to_string()).await?).await?;
|
||||
let quote2 =
|
||||
@@ -247,7 +247,7 @@ async fn test_quote_replies() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
let grp_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
let grp_chat_id = create_group(&alice, "grp").await?;
|
||||
let grp_msg_id = send_text_msg(&alice, grp_chat_id, "bar".to_string()).await?;
|
||||
let grp_msg = Message::load_from_db(&alice, grp_msg_id).await?;
|
||||
|
||||
@@ -295,9 +295,7 @@ async fn test_quote_replies() -> Result<()> {
|
||||
async fn test_add_contact_to_chat_ex_add_self() {
|
||||
// Adding self to a contact should succeed, even though it's pointless.
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id = create_group(&t, "foo").await.unwrap();
|
||||
let added = add_contact_to_chat_ex(&t, Nosync, chat_id, ContactId::SELF, false)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -336,8 +334,7 @@ async fn test_member_add_remove() -> Result<()> {
|
||||
}
|
||||
|
||||
tcm.section("Create and promote a group.");
|
||||
let alice_chat_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(&alice, "Group chat").await?;
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
let sent = alice
|
||||
@@ -399,8 +396,7 @@ async fn test_parallel_member_remove() -> Result<()> {
|
||||
let alice_charlie_contact_id = alice.add_or_lookup_contact_id(&charlie).await;
|
||||
|
||||
tcm.section("Alice creates and promotes a group");
|
||||
let alice_chat_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(&alice, "Group chat").await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
let alice_sent_msg = alice
|
||||
@@ -457,8 +453,7 @@ async fn test_msg_with_implicit_member_removed() -> Result<()> {
|
||||
let alice_bob_contact_id = alice.add_or_lookup_contact_id(&bob).await;
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
let bob_fiona_contact_id = bob.add_or_lookup_contact_id(&fiona).await;
|
||||
let alice_chat_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(&alice, "Group chat").await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
let sent_msg = alice.send_text(alice_chat_id, "I created a group").await;
|
||||
let bob_received_msg = bob.recv_msg(&sent_msg).await;
|
||||
@@ -488,7 +483,7 @@ async fn test_msg_with_implicit_member_removed() -> Result<()> {
|
||||
// If Bob sends a message to Alice now, Fiona is removed.
|
||||
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 3);
|
||||
let sent_msg = bob
|
||||
.send_text(alice_chat_id, "I have removed Fiona some time ago.")
|
||||
.send_text(bob_chat_id, "I have removed Fiona some time ago.")
|
||||
.await;
|
||||
alice.recv_msg(&sent_msg).await;
|
||||
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 2);
|
||||
@@ -504,7 +499,7 @@ async fn test_modify_chat_multi_device() -> Result<()> {
|
||||
a1.set_config_bool(Config::BccSelf, true).await?;
|
||||
|
||||
// create group and sync it to the second device
|
||||
let a1_chat_id = create_group_chat(&a1, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let a1_chat_id = create_group(&a1, "foo").await?;
|
||||
let sent = a1.send_text(a1_chat_id, "ho!").await;
|
||||
let a1_msg = a1.get_last_msg().await;
|
||||
let a1_chat = Chat::load_from_db(&a1, a1_chat_id).await?;
|
||||
@@ -602,7 +597,7 @@ async fn test_modify_chat_disordered() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
let fiona_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
|
||||
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let alice_chat_id = create_group(&alice, "foo").await?;
|
||||
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
|
||||
|
||||
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
|
||||
@@ -649,9 +644,7 @@ async fn test_lost_member_added() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "Group", &[bob])
|
||||
.await;
|
||||
let alice_chat_id = alice.create_group_with_members("Group", &[bob]).await;
|
||||
let alice_sent = alice.send_text(alice_chat_id, "Hi!").await;
|
||||
let bob_chat_id = bob.recv_msg(&alice_sent).await.chat_id;
|
||||
assert_eq!(get_chat_contacts(bob, bob_chat_id).await?.len(), 2);
|
||||
@@ -681,7 +674,7 @@ async fn test_modify_chat_lost() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
let fiona_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
|
||||
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let alice_chat_id = create_group(&alice, "foo").await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, charlie_id).await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, fiona_id).await?;
|
||||
@@ -722,7 +715,7 @@ async fn test_leave_group() -> Result<()> {
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
tcm.section("Alice creates group chat with Bob.");
|
||||
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let alice_chat_id = create_group(&alice, "foo").await?;
|
||||
let bob_contact = alice.add_or_lookup_contact(&bob).await.id;
|
||||
add_contact_to_chat(&alice, alice_chat_id, bob_contact).await?;
|
||||
|
||||
@@ -1381,9 +1374,7 @@ async fn test_pinned() {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
let chat_id2 = t.get_self_chat().await.id;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
let chat_id3 = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id3 = create_group(&t, "foo").await.unwrap();
|
||||
|
||||
let chatlist = get_chats_from_chat_list(&t, DC_GCL_NO_SPECIALS).await;
|
||||
assert_eq!(chatlist, vec![chat_id3, chat_id2, chat_id1]);
|
||||
@@ -1473,9 +1464,7 @@ async fn test_set_chat_name() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foo")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id = create_group(alice, "foo").await.unwrap();
|
||||
assert_eq!(
|
||||
Chat::load_from_db(alice, chat_id).await.unwrap().get_name(),
|
||||
"foo"
|
||||
@@ -1547,7 +1536,7 @@ async fn test_shall_attach_selfavatar() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(alice, "foo").await?;
|
||||
assert!(!shall_attach_selfavatar(alice, chat_id).await?);
|
||||
|
||||
let contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
@@ -1569,7 +1558,7 @@ async fn test_profile_data_on_group_leave() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = create_group_chat(t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(t, "foo").await?;
|
||||
|
||||
let contact_id = t.add_or_lookup_contact_id(bob).await;
|
||||
add_contact_to_chat(t, chat_id, contact_id).await?;
|
||||
@@ -1594,9 +1583,7 @@ async fn test_profile_data_on_group_leave() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_set_mute_duration() {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id = create_group(&t, "foo").await.unwrap();
|
||||
// Initial
|
||||
assert_eq!(
|
||||
Chat::load_from_db(&t, chat_id).await.unwrap().is_muted(),
|
||||
@@ -1645,7 +1632,7 @@ async fn test_set_mute_duration() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_add_info_msg() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
add_info_msg(&t, chat_id, "foo info", time()).await?;
|
||||
|
||||
let msg = t.get_last_msg_in(chat_id).await;
|
||||
@@ -1662,7 +1649,7 @@ async fn test_add_info_msg() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_add_info_msg_with_cmd() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let msg_id = add_info_msg_with_cmd(
|
||||
&t,
|
||||
chat_id,
|
||||
@@ -1931,14 +1918,14 @@ async fn test_classic_email_chat() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_get_color() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_ex(&t, None, "a chat").await?;
|
||||
let chat_id = create_group_unencrypted(&t, "a chat").await?;
|
||||
let color1 = Chat::load_from_db(&t, chat_id).await?.get_color(&t).await?;
|
||||
assert_eq!(color1, 0x6239dc);
|
||||
|
||||
// upper-/lowercase makes a difference for the colors, these are different groups
|
||||
// (in contrast to email addresses, where upper-/lowercase is ignored in practise)
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_ex(&t, None, "A CHAT").await?;
|
||||
let chat_id = create_group_unencrypted(&t, "A CHAT").await?;
|
||||
let color2 = Chat::load_from_db(&t, chat_id).await?.get_color(&t).await?;
|
||||
assert_ne!(color2, color1);
|
||||
Ok(())
|
||||
@@ -1948,7 +1935,7 @@ async fn test_chat_get_color() -> Result<()> {
|
||||
async fn test_chat_get_color_encrypted() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let chat_id = create_group_ex(t, Some(ProtectionStatus::Unprotected), "a chat").await?;
|
||||
let chat_id = create_group(t, "a chat").await?;
|
||||
let color1 = Chat::load_from_db(t, chat_id).await?.get_color(t).await?;
|
||||
set_chat_name(t, chat_id, "A CHAT").await?;
|
||||
let color2 = Chat::load_from_db(t, chat_id).await?.get_color(t).await?;
|
||||
@@ -2137,7 +2124,7 @@ async fn test_forward_info_msg() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let chat_id1 = create_group_chat(alice, ProtectionStatus::Unprotected, "a").await?;
|
||||
let chat_id1 = create_group(alice, "a").await?;
|
||||
send_text_msg(alice, chat_id1, "msg one".to_string()).await?;
|
||||
let bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
add_contact_to_chat(alice, chat_id1, bob_id).await?;
|
||||
@@ -2204,8 +2191,7 @@ async fn test_forward_group() -> Result<()> {
|
||||
let bob_chat = bob.create_chat(&alice).await;
|
||||
|
||||
// Alice creates a group with Bob.
|
||||
let alice_group_chat_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_group_chat_id = create_group(&alice, "Group").await?;
|
||||
let bob_id = alice.add_or_lookup_contact_id(&bob).await;
|
||||
let charlie_id = alice.add_or_lookup_contact_id(&charlie).await;
|
||||
add_contact_to_chat(&alice, alice_group_chat_id, bob_id).await?;
|
||||
@@ -2257,8 +2243,7 @@ async fn test_only_minimal_data_are_forwarded() -> Result<()> {
|
||||
.set_config(Config::Displayname, Some("secretname"))
|
||||
.await?;
|
||||
let bob_id = alice.add_or_lookup_contact_id(&bob).await;
|
||||
let group_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "secretgrpname").await?;
|
||||
let group_id = create_group(&alice, "secretgrpname").await?;
|
||||
add_contact_to_chat(&alice, group_id, bob_id).await?;
|
||||
let mut msg = Message::new_text("bla foo".to_owned());
|
||||
let sent_msg = alice.send_msg(group_id, &mut msg).await;
|
||||
@@ -2273,7 +2258,7 @@ async fn test_only_minimal_data_are_forwarded() -> Result<()> {
|
||||
let orig_msg = bob.recv_msg(&sent_msg).await;
|
||||
let charlie_id = bob.add_or_lookup_contact_id(&charlie).await;
|
||||
let single_id = ChatId::create_for_contact(&bob, charlie_id).await?;
|
||||
let group_id = create_group_chat(&bob, ProtectionStatus::Unprotected, "group2").await?;
|
||||
let group_id = create_group(&bob, "group2").await?;
|
||||
add_contact_to_chat(&bob, group_id, charlie_id).await?;
|
||||
let broadcast_id = create_broadcast(&bob, "Channel".to_string()).await?;
|
||||
add_contact_to_chat(&bob, broadcast_id, charlie_id).await?;
|
||||
@@ -2371,7 +2356,7 @@ async fn test_save_msgs_order() -> Result<()> {
|
||||
for a in [alice, alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Protected, "grp").await?;
|
||||
let chat_id = create_group(alice, "grp").await?;
|
||||
let sent = [
|
||||
alice.send_text(chat_id, "0").await,
|
||||
alice.send_text(chat_id, "1").await,
|
||||
@@ -2427,14 +2412,14 @@ async fn test_forward_from_saved_to_saved() -> Result<()> {
|
||||
let bob = TestContext::new_bob().await;
|
||||
let sent = alice.send_text(alice.create_chat(&bob).await.id, "k").await;
|
||||
|
||||
bob.recv_msg(&sent).await;
|
||||
let received_message = bob.recv_msg(&sent).await;
|
||||
let orig = bob.get_last_msg().await;
|
||||
let self_chat = bob.get_self_chat().await;
|
||||
save_msgs(&bob, &[orig.id]).await?;
|
||||
let saved1 = bob.get_last_msg().await;
|
||||
assert_eq!(
|
||||
saved1.get_original_msg_id(&bob).await?.unwrap(),
|
||||
sent.sender_msg_id
|
||||
received_message.id
|
||||
);
|
||||
assert_ne!(saved1.from_id, ContactId::SELF);
|
||||
|
||||
@@ -2492,7 +2477,7 @@ async fn test_resend_own_message() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let fiona = TestContext::new_fiona().await;
|
||||
let alice_grp = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
let alice_grp = create_group(&alice, "grp").await?;
|
||||
add_contact_to_chat(
|
||||
&alice,
|
||||
alice_grp,
|
||||
@@ -2579,7 +2564,7 @@ async fn test_resend_foreign_message_fails() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice_grp = create_group_chat(alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
let alice_grp = create_group(alice, "grp").await?;
|
||||
add_contact_to_chat(alice, alice_grp, alice.add_or_lookup_contact_id(bob).await).await?;
|
||||
let sent1 = alice.send_text(alice_grp, "alice->bob").await;
|
||||
|
||||
@@ -2596,7 +2581,7 @@ async fn test_resend_info_message_fails() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
let alice_grp = create_group_chat(alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
let alice_grp = create_group(alice, "grp").await?;
|
||||
add_contact_to_chat(alice, alice_grp, alice.add_or_lookup_contact_id(bob).await).await?;
|
||||
alice.send_text(alice_grp, "alice->bob").await;
|
||||
|
||||
@@ -2619,7 +2604,7 @@ async fn test_can_send_group() -> Result<()> {
|
||||
let chat_id = ChatId::create_for_contact(&alice, bob).await?;
|
||||
let chat = Chat::load_from_db(&alice, chat_id).await?;
|
||||
assert!(chat.can_send(&alice).await?);
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&alice, "foo").await?;
|
||||
assert_eq!(
|
||||
Chat::load_from_db(&alice, chat_id)
|
||||
.await?
|
||||
@@ -2659,7 +2644,7 @@ async fn test_broadcast() -> Result<()> {
|
||||
add_contact_to_chat(
|
||||
&alice,
|
||||
broadcast_id,
|
||||
get_chat_contacts(&alice, chat_bob.id).await?.pop().unwrap(),
|
||||
get_chat_contacts(&alice, msg.chat_id).await?.pop().unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let fiona_contact_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
@@ -3115,7 +3100,7 @@ async fn test_chat_get_encryption_info() -> Result<()> {
|
||||
let contact_bob = alice.add_or_lookup_contact_id(bob).await;
|
||||
let contact_fiona = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let chat_id = create_group(alice, "Group").await?;
|
||||
assert_eq!(
|
||||
chat_id.get_encryption_info(alice).await?,
|
||||
"End-to-end encryption available"
|
||||
@@ -3177,9 +3162,7 @@ async fn test_out_failed_on_all_keys_missing() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
|
||||
let bob_chat_id = bob
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "", &[alice, fiona])
|
||||
.await;
|
||||
let bob_chat_id = bob.create_group_with_members("", &[alice, fiona]).await;
|
||||
bob.send_text(bob_chat_id, "Gossiping Fiona's key").await;
|
||||
alice
|
||||
.recv_msg(&bob.send_text(bob_chat_id, "No key gossip").await)
|
||||
@@ -3197,8 +3180,8 @@ async fn test_out_failed_on_all_keys_missing() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_chat_media() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id1 = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id2 = create_group_chat(&t, ProtectionStatus::Unprotected, "bar").await?;
|
||||
let chat_id1 = create_group(&t, "foo").await?;
|
||||
let chat_id2 = create_group(&t, "bar").await?;
|
||||
|
||||
assert_eq!(
|
||||
get_chat_media(
|
||||
@@ -3206,7 +3189,8 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Some(chat_id1),
|
||||
Viewtype::Image,
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Unknown
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3267,6 +3251,7 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Image,
|
||||
Viewtype::Unknown,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3279,6 +3264,7 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Unknown,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3291,11 +3277,25 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Image,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
get_chat_media(
|
||||
&t,
|
||||
Some(chat_id1),
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Image,
|
||||
Viewtype::Unknown,
|
||||
Some(1),
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
get_chat_media(
|
||||
&t,
|
||||
@@ -3303,6 +3303,7 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Webxdc,
|
||||
Viewtype::Unknown,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3315,6 +3316,7 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Image,
|
||||
Viewtype::Unknown,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3327,6 +3329,7 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Image,
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3339,6 +3342,33 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Image,
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Webxdc,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
4
|
||||
);
|
||||
assert_eq!(
|
||||
get_chat_media(
|
||||
&t,
|
||||
None,
|
||||
Viewtype::Image,
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Webxdc,
|
||||
Some(3),
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
3
|
||||
);
|
||||
assert_eq!(
|
||||
get_chat_media(
|
||||
&t,
|
||||
None,
|
||||
Viewtype::Image,
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Webxdc,
|
||||
Some(99),
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3354,6 +3384,7 @@ async fn test_get_chat_media() -> Result<()> {
|
||||
Viewtype::Image,
|
||||
Viewtype::Sticker,
|
||||
Viewtype::Webxdc,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.len(),
|
||||
@@ -3395,6 +3426,7 @@ async fn test_get_chat_media_webxdc_order() -> Result<()> {
|
||||
Viewtype::Webxdc,
|
||||
Viewtype::Unknown,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(media.first().unwrap(), &instance1_id);
|
||||
@@ -3410,6 +3442,7 @@ async fn test_get_chat_media_webxdc_order() -> Result<()> {
|
||||
Viewtype::Webxdc,
|
||||
Viewtype::Unknown,
|
||||
Viewtype::Unknown,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(media.first().unwrap(), &instance2_id);
|
||||
@@ -3422,7 +3455,7 @@ async fn test_get_chat_media_webxdc_order() -> Result<()> {
|
||||
async fn test_blob_renaming() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let chat_id = create_group(&alice, "Group").await?;
|
||||
add_contact_to_chat(&alice, chat_id, alice.add_or_lookup_contact_id(&bob).await).await?;
|
||||
let file = alice.get_blobdir().join("harmless_file.\u{202e}txt.exe");
|
||||
fs::write(&file, "aaa").await?;
|
||||
@@ -3484,9 +3517,7 @@ async fn test_sync_blocked() -> Result<()> {
|
||||
// - Group chats synchronisation.
|
||||
// - That blocking a group deletes it on other devices.
|
||||
let fiona = TestContext::new_fiona().await;
|
||||
let fiona_grp_chat_id = fiona
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[alice0])
|
||||
.await;
|
||||
let fiona_grp_chat_id = fiona.create_group_with_members("grp", &[alice0]).await;
|
||||
let sent_msg = fiona.send_text(fiona_grp_chat_id, "hi").await;
|
||||
let a0_grp_chat_id = alice0.recv_msg(&sent_msg).await.chat_id;
|
||||
let a1_grp_chat_id = alice1.recv_msg(&sent_msg).await.chat_id;
|
||||
@@ -3619,9 +3650,7 @@ async fn test_sync_delete_chat() -> Result<()> {
|
||||
.get_matching(|evt| matches!(evt, EventType::ChatDeleted { .. }))
|
||||
.await;
|
||||
|
||||
let bob_grp_chat_id = bob
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[alice0])
|
||||
.await;
|
||||
let bob_grp_chat_id = bob.create_group_with_members("grp", &[alice0]).await;
|
||||
let sent_msg = bob.send_text(bob_grp_chat_id, "hi").await;
|
||||
let a0_grp_chat_id = alice0.recv_msg(&sent_msg).await.chat_id;
|
||||
let a1_grp_chat_id = alice1.recv_msg(&sent_msg).await.chat_id;
|
||||
@@ -3854,6 +3883,61 @@ async fn test_sync_name() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_sync_create_group() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice0 = &tcm.alice().await;
|
||||
let alice1 = &tcm.alice().await;
|
||||
for a in [alice0, alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
let bob = &tcm.bob().await;
|
||||
let a0_bob_contact_id = alice0.add_or_lookup_contact_id(bob).await;
|
||||
let a1_bob_contact_id = alice1.add_or_lookup_contact_id(bob).await;
|
||||
let a0_chat_id = create_group(alice0, "grp").await?;
|
||||
sync(alice0, alice1).await;
|
||||
let a0_chat = Chat::load_from_db(alice0, a0_chat_id).await?;
|
||||
let a1_chat_id = get_chat_id_by_grpid(alice1, &a0_chat.grpid)
|
||||
.await?
|
||||
.unwrap()
|
||||
.0;
|
||||
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
|
||||
assert_eq!(a1_chat.get_type(), Chattype::Group);
|
||||
assert_eq!(a1_chat.is_promoted(), false);
|
||||
assert_eq!(a1_chat.get_name(), "grp");
|
||||
|
||||
set_chat_name(alice0, a0_chat_id, "renamed").await?;
|
||||
sync(alice0, alice1).await;
|
||||
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
|
||||
assert_eq!(a1_chat.is_promoted(), false);
|
||||
assert_eq!(a1_chat.get_name(), "renamed");
|
||||
|
||||
add_contact_to_chat(alice0, a0_chat_id, a0_bob_contact_id).await?;
|
||||
sync(alice0, alice1).await;
|
||||
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
|
||||
assert_eq!(a1_chat.is_promoted(), false);
|
||||
assert_eq!(
|
||||
get_chat_contacts(alice1, a1_chat_id).await?,
|
||||
[a1_bob_contact_id, ContactId::SELF]
|
||||
);
|
||||
|
||||
// Let's test a contact removal from another device.
|
||||
remove_contact_from_chat(alice1, a1_chat_id, a1_bob_contact_id).await?;
|
||||
sync(alice1, alice0).await;
|
||||
let a0_chat = Chat::load_from_db(alice0, a0_chat_id).await?;
|
||||
assert_eq!(a0_chat.is_promoted(), false);
|
||||
assert_eq!(
|
||||
get_chat_contacts(alice0, a0_chat_id).await?,
|
||||
[ContactId::SELF]
|
||||
);
|
||||
|
||||
let sent_msg = alice0.send_text(a0_chat_id, "hi").await;
|
||||
let msg = alice1.recv_msg(&sent_msg).await;
|
||||
assert_eq!(msg.chat_id, a1_chat_id);
|
||||
assert_eq!(a1_chat_id.is_promoted(alice1).await?, true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests sending JPEG image with .png extension.
|
||||
///
|
||||
/// This is a regression test, previously sending failed
|
||||
@@ -3988,9 +4072,7 @@ async fn test_info_contact_id() -> Result<()> {
|
||||
}
|
||||
|
||||
// Alice creates group, Bob receives group
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "play", &[bob])
|
||||
.await;
|
||||
let alice_chat_id = alice.create_group_with_members("play", &[bob]).await;
|
||||
let sent_msg1 = alice.send_text(alice_chat_id, "moin").await;
|
||||
|
||||
let msg = bob.recv_msg(&sent_msg1).await;
|
||||
@@ -4036,26 +4118,27 @@ async fn test_info_contact_id() -> Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let fiona_id = alice.add_or_lookup_contact_id(&tcm.fiona().await).await; // contexts are in sync, fiona_id is same everywhere
|
||||
add_contact_to_chat(alice, alice_chat_id, fiona_id).await?;
|
||||
let alice_fiona_id = alice.add_or_lookup_contact_id(&tcm.fiona().await).await;
|
||||
let bob_fiona_id = bob.add_or_lookup_contact_id(&tcm.fiona().await).await;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_fiona_id).await?;
|
||||
pop_recv_and_check(
|
||||
alice,
|
||||
alice2,
|
||||
bob,
|
||||
SystemMessage::MemberAddedToGroup,
|
||||
fiona_id,
|
||||
fiona_id,
|
||||
alice_fiona_id,
|
||||
bob_fiona_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
remove_contact_from_chat(alice, alice_chat_id, fiona_id).await?;
|
||||
remove_contact_from_chat(alice, alice_chat_id, alice_fiona_id).await?;
|
||||
pop_recv_and_check(
|
||||
alice,
|
||||
alice2,
|
||||
bob,
|
||||
SystemMessage::MemberRemovedFromGroup,
|
||||
fiona_id,
|
||||
fiona_id,
|
||||
alice_fiona_id,
|
||||
bob_fiona_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -4063,11 +4146,12 @@ async fn test_info_contact_id() -> Result<()> {
|
||||
// We raw delete in db as Contact::delete() leaves a tombstone (which is great as the tap works longer then)
|
||||
alice
|
||||
.sql
|
||||
.execute("DELETE FROM contacts WHERE id=?", (fiona_id,))
|
||||
.execute("DELETE FROM contacts WHERE id=?", (alice_fiona_id,))
|
||||
.await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.get_info_type(), SystemMessage::MemberRemovedFromGroup);
|
||||
assert!(msg.get_info_contact_id(alice).await?.is_none());
|
||||
assert!(msg.get_info_contact_id(bob).await?.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4085,8 +4169,7 @@ async fn test_add_member_bug() -> Result<()> {
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
// Create a group.
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(alice, "Group chat").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
|
||||
@@ -4130,8 +4213,7 @@ async fn test_past_members() -> Result<()> {
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
tcm.section("Alice creates a chat.");
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(alice, "Group chat").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
alice
|
||||
.send_text(alice_chat_id, "Hi! I created a group.")
|
||||
@@ -4165,8 +4247,7 @@ async fn test_non_member_cannot_modify_member_list() -> Result<()> {
|
||||
|
||||
let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(alice, "Group chat").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
let alice_sent_msg = alice
|
||||
.send_text(alice_chat_id, "Hi! I created a group.")
|
||||
@@ -4209,8 +4290,7 @@ async fn unpromoted_group_no_tombstones() -> Result<()> {
|
||||
let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(alice, "Group chat").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
assert_eq!(get_chat_contacts(alice, alice_chat_id).await?.len(), 3);
|
||||
@@ -4241,8 +4321,7 @@ async fn test_expire_past_members_after_60_days() -> Result<()> {
|
||||
let fiona = &tcm.fiona().await;
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(alice, "Group chat").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_fiona_contact_id).await?;
|
||||
alice
|
||||
.send_text(alice_chat_id, "Hi! I created a group.")
|
||||
@@ -4279,7 +4358,7 @@ async fn test_past_members_order() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
let fiona_contact_id = t.add_or_lookup_contact_id(&fiona).await;
|
||||
|
||||
let chat_id = create_group_chat(t, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let chat_id = create_group(t, "Group chat").await?;
|
||||
add_contact_to_chat(t, chat_id, bob_contact_id).await?;
|
||||
add_contact_to_chat(t, chat_id, charlie_contact_id).await?;
|
||||
add_contact_to_chat(t, chat_id, fiona_contact_id).await?;
|
||||
@@ -4341,8 +4420,7 @@ async fn test_restore_backup_after_60_days() -> Result<()> {
|
||||
let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let alice_charlie_contact_id = alice.add_or_lookup_contact_id(charlie).await;
|
||||
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let alice_chat_id = create_group(alice, "Group chat").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_charlie_contact_id).await?;
|
||||
|
||||
@@ -4530,9 +4608,7 @@ async fn test_cannot_send_edit_request() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[bob])
|
||||
.await;
|
||||
let chat_id = alice.create_group_with_members("My Group", &[bob]).await;
|
||||
|
||||
// Alice can edit her message
|
||||
let sent1 = alice.send_text(chat_id, "foo").await;
|
||||
@@ -4703,7 +4779,7 @@ async fn test_no_address_contacts_in_group_chats() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group chat").await?;
|
||||
let chat_id = create_group(alice, "Group chat").await?;
|
||||
let bob_key_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let charlie_address_contact_id = alice.add_or_lookup_address_contact_id(charlie).await;
|
||||
|
||||
@@ -4762,7 +4838,7 @@ async fn test_create_unencrypted_group_chat() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
let chat_id = create_group_ex(alice, None, "Group chat").await?;
|
||||
let chat_id = create_group_unencrypted(alice, "Group chat").await?;
|
||||
let bob_key_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let charlie_address_contact_id = alice.add_or_lookup_address_contact_id(charlie).await;
|
||||
|
||||
@@ -4783,7 +4859,7 @@ async fn test_create_unencrypted_group_chat() -> Result<()> {
|
||||
async fn test_create_group_invalid_name() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let chat_id = create_group_ex(alice, None, " ").await?;
|
||||
let chat_id = create_group(alice, " ").await?;
|
||||
let chat = Chat::load_from_db(alice, chat_id).await?;
|
||||
assert_eq!(chat.get_name(), "…");
|
||||
Ok(())
|
||||
@@ -4829,7 +4905,7 @@ async fn test_long_group_name() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let group_name = "δδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδδ";
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, group_name).await?;
|
||||
let alice_chat_id = create_group(alice, group_name).await?;
|
||||
let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
let sent = alice
|
||||
|
||||
@@ -481,8 +481,8 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::chat::save_msgs;
|
||||
use crate::chat::{
|
||||
ProtectionStatus, add_contact_to_chat, create_group_chat, get_chat_contacts,
|
||||
remove_contact_from_chat, send_text_msg,
|
||||
add_contact_to_chat, create_group, get_chat_contacts, remove_contact_from_chat,
|
||||
send_text_msg,
|
||||
};
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::stock_str::StockMessage;
|
||||
@@ -495,15 +495,9 @@ mod tests {
|
||||
async fn test_try_load() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id1 = create_group_chat(bob, ProtectionStatus::Unprotected, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id2 = create_group_chat(bob, ProtectionStatus::Unprotected, "b chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id3 = create_group_chat(bob, ProtectionStatus::Unprotected, "c chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id1 = create_group(bob, "a chat").await.unwrap();
|
||||
let chat_id2 = create_group(bob, "b chat").await.unwrap();
|
||||
let chat_id3 = create_group(bob, "c chat").await.unwrap();
|
||||
|
||||
// check that the chatlist starts with the most recent message
|
||||
let chats = Chatlist::try_load(bob, 0, None, None).await.unwrap();
|
||||
@@ -536,9 +530,7 @@ mod tests {
|
||||
|
||||
// receive a message from alice
|
||||
let alice = &tcm.alice().await;
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "alice chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_chat_id = create_group(alice, "alice chat").await.unwrap();
|
||||
add_contact_to_chat(
|
||||
alice,
|
||||
alice_chat_id,
|
||||
@@ -576,9 +568,7 @@ mod tests {
|
||||
async fn test_sort_self_talk_up_on_forward() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.update_device_chats().await.unwrap();
|
||||
create_group_chat(&t, ProtectionStatus::Unprotected, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
create_group(&t, "a chat").await.unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 3);
|
||||
@@ -765,9 +755,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_summary_unwrap() {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id1 = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id1 = create_group(&t, "a chat").await.unwrap();
|
||||
|
||||
let mut msg = Message::new_text("foo:\nbar \r\n test".to_string());
|
||||
chat_id1.set_draft(&t, Some(&mut msg)).await.unwrap();
|
||||
@@ -783,9 +771,7 @@ mod tests {
|
||||
async fn test_get_summary_deleted_draft() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id = create_group(&t, "a chat").await.unwrap();
|
||||
let mut msg = Message::new_text("Foobar".to_string());
|
||||
chat_id.set_draft(&t, Some(&mut msg)).await.unwrap();
|
||||
|
||||
@@ -824,15 +810,9 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_load_broken() {
|
||||
let t = TestContext::new_bob().await;
|
||||
let chat_id1 = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
create_group_chat(&t, ProtectionStatus::Unprotected, "b chat")
|
||||
.await
|
||||
.unwrap();
|
||||
create_group_chat(&t, ProtectionStatus::Unprotected, "c chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id1 = create_group(&t, "a chat").await.unwrap();
|
||||
create_group(&t, "b chat").await.unwrap();
|
||||
create_group(&t, "c chat").await.unwrap();
|
||||
|
||||
// check that the chatlist starts with the most recent message
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
|
||||
@@ -389,12 +389,6 @@ pub enum Config {
|
||||
/// Make all outgoing messages with Autocrypt header "multipart/signed".
|
||||
SignUnencrypted,
|
||||
|
||||
/// Enable header protection for `Autocrypt` header.
|
||||
///
|
||||
/// This is an experimental setting not compatible to other MUAs
|
||||
/// and older Delta Chat versions (core version <= v1.149.0).
|
||||
ProtectAutocrypt,
|
||||
|
||||
/// Let the core save all events to the database.
|
||||
/// This value is used internally to remember the MsgId of the logging xdc
|
||||
#[strum(props(default = "0"))]
|
||||
|
||||
@@ -300,8 +300,6 @@ async fn get_configured_param(
|
||||
param.smtp.password.clone()
|
||||
};
|
||||
|
||||
let proxy_enabled = ctx.get_config_bool(Config::ProxyEnabled).await?;
|
||||
|
||||
let mut addr = param.addr.clone();
|
||||
if param.oauth2 {
|
||||
// the used oauth2 addr may differ, check this.
|
||||
@@ -343,7 +341,7 @@ async fn get_configured_param(
|
||||
"checking internal provider-info for offline autoconfig"
|
||||
);
|
||||
|
||||
provider = provider::get_provider_info(ctx, ¶m_domain, proxy_enabled).await;
|
||||
provider = provider::get_provider_info(¶m_domain);
|
||||
if let Some(provider) = provider {
|
||||
if provider.server.is_empty() {
|
||||
info!(ctx, "Offline autoconfig found, but no servers defined.");
|
||||
|
||||
@@ -118,7 +118,7 @@ pub enum Chattype {
|
||||
|
||||
/// Group chat.
|
||||
///
|
||||
/// Created by [`crate::chat::create_group_chat`].
|
||||
/// Created by [`crate::chat::create_group`].
|
||||
Group = 120,
|
||||
|
||||
/// An (unencrypted) mailing list,
|
||||
|
||||
@@ -1790,9 +1790,7 @@ WHERE type=? AND id IN (
|
||||
// also unblock mailinglist
|
||||
// if the contact is a mailinglist address explicitly created to allow unblocking
|
||||
if !new_blocking && contact.origin == Origin::MailinglistAddress {
|
||||
if let Some((chat_id, _, _)) =
|
||||
chat::get_chat_id_by_grpid(context, &contact.addr).await?
|
||||
{
|
||||
if let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await? {
|
||||
chat_id.unblock_ex(context, Nosync).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use deltachat_contact_tools::{addr_cmp, may_be_valid_addr};
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{Chat, ProtectionStatus, get_chat_contacts, send_text_msg};
|
||||
use crate::chat::{Chat, get_chat_contacts, send_text_msg};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::securejoin::get_securejoin_qr;
|
||||
@@ -1320,9 +1320,6 @@ async fn test_self_is_verified() -> Result<()> {
|
||||
assert!(contact.get_verifier_id(&alice).await?.is_none());
|
||||
assert!(contact.is_key_contact());
|
||||
|
||||
let chat_id = ChatId::get_for_contact(&alice, ContactId::SELF).await?;
|
||||
assert!(chat_id.is_protected(&alice).await.unwrap() == ProtectionStatus::Protected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use pgp::types::PublicKeyTrait;
|
||||
use ratelimit::Ratelimit;
|
||||
use tokio::sync::{Mutex, Notify, RwLock};
|
||||
|
||||
use crate::chat::{ChatId, ProtectionStatus, get_chat_cnt};
|
||||
use crate::chat::{ChatId, get_chat_cnt};
|
||||
use crate::chatlist_events;
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
@@ -1035,12 +1035,6 @@ impl Context {
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"protect_autocrypt",
|
||||
self.get_config_int(Config::ProtectAutocrypt)
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"debug_logging",
|
||||
self.get_config_int(Config::DebugLogging).await?.to_string(),
|
||||
@@ -1089,7 +1083,6 @@ impl Context {
|
||||
async fn get_self_report(&self) -> Result<String> {
|
||||
#[derive(Default)]
|
||||
struct ChatNumbers {
|
||||
protected: u32,
|
||||
opportunistic_dc: u32,
|
||||
opportunistic_mua: u32,
|
||||
unencrypted_dc: u32,
|
||||
@@ -1124,7 +1117,6 @@ impl Context {
|
||||
res += &format!("key_created {key_created}\n");
|
||||
|
||||
// how many of the chats active in the last months are:
|
||||
// - protected
|
||||
// - opportunistic-encrypted and the contact uses Delta Chat
|
||||
// - opportunistic-encrypted and the contact uses a classical MUA
|
||||
// - unencrypted and the contact uses Delta Chat
|
||||
@@ -1133,7 +1125,7 @@ impl Context {
|
||||
let chats = self
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT c.protected, m.param, m.msgrmsg
|
||||
"SELECT m.param, m.msgrmsg
|
||||
FROM chats c
|
||||
JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
@@ -1151,23 +1143,20 @@ impl Context {
|
||||
GROUP BY c.id",
|
||||
(DownloadState::Done, ContactId::INFO, three_months_ago),
|
||||
|row| {
|
||||
let protected: ProtectionStatus = row.get(0)?;
|
||||
let message_param: Params =
|
||||
row.get::<_, String>(1)?.parse().unwrap_or_default();
|
||||
let is_dc_message: bool = row.get(2)?;
|
||||
Ok((protected, message_param, is_dc_message))
|
||||
Ok((message_param, is_dc_message))
|
||||
},
|
||||
|rows| {
|
||||
let mut chats = ChatNumbers::default();
|
||||
for row in rows {
|
||||
let (protected, message_param, is_dc_message) = row?;
|
||||
let (message_param, is_dc_message) = row?;
|
||||
let encrypted = message_param
|
||||
.get_bool(Param::GuaranteeE2ee)
|
||||
.unwrap_or(false);
|
||||
|
||||
if protected == ProtectionStatus::Protected {
|
||||
chats.protected += 1;
|
||||
} else if encrypted {
|
||||
if encrypted {
|
||||
if is_dc_message {
|
||||
chats.opportunistic_dc += 1;
|
||||
} else {
|
||||
@@ -1183,7 +1172,6 @@ impl Context {
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
res += &format!("chats_protected {}\n", chats.protected);
|
||||
res += &format!("chats_opportunistic_dc {}\n", chats.opportunistic_dc);
|
||||
res += &format!("chats_opportunistic_mua {}\n", chats.opportunistic_mua);
|
||||
res += &format!("chats_unencrypted_dc {}\n", chats.unencrypted_dc);
|
||||
@@ -1216,9 +1204,6 @@ impl Context {
|
||||
mark_contact_id_as_verified(self, contact_id, Some(ContactId::SELF)).await?;
|
||||
|
||||
let chat_id = ChatId::create_for_contact(self, contact_id).await?;
|
||||
chat_id
|
||||
.set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
|
||||
.await?;
|
||||
|
||||
let mut msg = Message::new_text(self.get_self_report().await?);
|
||||
|
||||
|
||||
@@ -604,15 +604,12 @@ async fn test_draft_self_report() -> Result<()> {
|
||||
|
||||
let chat_id = alice.draft_self_report().await?;
|
||||
let msg = get_chat_msg(&alice, chat_id, 0, 1).await;
|
||||
assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionEnabled);
|
||||
|
||||
let chat = Chat::load_from_db(&alice, chat_id).await?;
|
||||
assert!(chat.is_protected());
|
||||
assert_eq!(msg.get_info_type(), SystemMessage::ChatE2ee);
|
||||
|
||||
let mut draft = chat_id.get_draft(&alice).await?.unwrap();
|
||||
assert!(draft.text.starts_with("core_version"));
|
||||
|
||||
// Test that sending into the protected chat works:
|
||||
// Test that sending into the chat works:
|
||||
let _sent = alice.send_msg(chat_id, &mut draft).await;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
use crate::timesmearing::MAX_SECONDS_TO_LEND_FROM_FUTURE;
|
||||
use crate::{
|
||||
chat::{self, Chat, ChatItem, ProtectionStatus, create_group_chat, send_text_msg},
|
||||
chat::{self, Chat, ChatItem, create_group, send_text_msg},
|
||||
tools::IsNoneOrEmpty,
|
||||
};
|
||||
|
||||
@@ -164,7 +164,7 @@ async fn test_ephemeral_enable_disable() -> Result<()> {
|
||||
async fn test_ephemeral_unpromoted() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "Group name").await?;
|
||||
let chat_id = create_group(&alice, "Group name").await?;
|
||||
|
||||
// Group is unpromoted, the timer can be changed without sending a message.
|
||||
assert!(chat_id.is_unpromoted(&alice).await?);
|
||||
@@ -799,8 +799,7 @@ async fn test_ephemeral_timer_non_member() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let alice_chat_id =
|
||||
create_group_chat(alice, ProtectionStatus::Unprotected, "Group name").await?;
|
||||
let alice_chat_id = create_group(alice, "Group name").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?;
|
||||
send_text_msg(alice, alice_chat_id, "Hi!".to_string()).await?;
|
||||
|
||||
|
||||
@@ -66,8 +66,7 @@ mod test_chatlist_events {
|
||||
use crate::{
|
||||
EventType,
|
||||
chat::{
|
||||
self, ChatId, ChatVisibility, MuteDuration, ProtectionStatus, create_broadcast,
|
||||
create_group_chat, set_muted,
|
||||
self, ChatId, ChatVisibility, MuteDuration, create_broadcast, create_group, set_muted,
|
||||
},
|
||||
config::Config,
|
||||
constants::*,
|
||||
@@ -138,12 +137,7 @@ mod test_chatlist_events {
|
||||
async fn test_change_chat_visibility() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat_id = create_group_chat(
|
||||
&alice,
|
||||
crate::chat::ProtectionStatus::Unprotected,
|
||||
"my_group",
|
||||
)
|
||||
.await?;
|
||||
let chat_id = create_group(&alice, "my_group").await?;
|
||||
|
||||
chat_id
|
||||
.set_visibility(&alice, ChatVisibility::Pinned)
|
||||
@@ -289,7 +283,7 @@ mod test_chatlist_events {
|
||||
async fn test_delete_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat.delete(&alice).await?;
|
||||
@@ -299,11 +293,11 @@ mod test_chatlist_events {
|
||||
|
||||
/// Create group chat
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_group_chat() -> Result<()> {
|
||||
async fn test_create_group() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
alice.evtracker.clear_events();
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
wait_for_chatlist_and_specific_item(&alice, chat).await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -324,7 +318,7 @@ mod test_chatlist_events {
|
||||
async fn test_mute_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat::set_muted(&alice, chat, MuteDuration::Forever).await?;
|
||||
@@ -343,7 +337,7 @@ mod test_chatlist_events {
|
||||
async fn test_mute_chat_expired() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
|
||||
let mute_duration = MuteDuration::Until(
|
||||
std::time::SystemTime::now()
|
||||
@@ -363,7 +357,7 @@ mod test_chatlist_events {
|
||||
async fn test_change_chat_name() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat::set_chat_name(&alice, chat, "New Name").await?;
|
||||
@@ -377,7 +371,7 @@ mod test_chatlist_events {
|
||||
async fn test_change_chat_profile_image() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
let file = alice.dir.path().join("avatar.png");
|
||||
@@ -395,9 +389,7 @@ mod test_chatlist_events {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
let chat = alice.create_group_with_members("My Group", &[&bob]).await;
|
||||
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
@@ -419,9 +411,7 @@ mod test_chatlist_events {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
let chat = alice.create_group_with_members("My Group", &[&bob]).await;
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
|
||||
@@ -438,9 +428,7 @@ mod test_chatlist_events {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
let chat = alice.create_group_with_members("My Group", &[&bob]).await;
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
|
||||
@@ -456,7 +444,7 @@ mod test_chatlist_events {
|
||||
async fn test_delete_message() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
let message = chat::send_text_msg(&alice, chat, "Hello World".to_owned()).await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
@@ -473,9 +461,7 @@ mod test_chatlist_events {
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
let chat = alice.create_group_with_members("My Group", &[&bob]).await;
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
chat_id_for_bob.accept(&bob).await?;
|
||||
@@ -516,7 +502,7 @@ mod test_chatlist_events {
|
||||
async fn test_update_after_ephemeral_messages() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
chat.set_ephemeral_timer(&alice, crate::ephemeral::Timer::Enabled { duration: 60 })
|
||||
.await?;
|
||||
alice
|
||||
@@ -560,8 +546,7 @@ First thread."#;
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let alice_chatid =
|
||||
chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat").await?;
|
||||
let alice_chatid = chat::create_group(&alice.ctx, "the chat").await?;
|
||||
|
||||
// Step 1: Generate QR-code, secure-join implied by chatid
|
||||
let qr = get_securejoin_qr(&alice.ctx, Some(alice_chatid)).await?;
|
||||
@@ -608,7 +593,7 @@ First thread."#;
|
||||
async fn test_resend_message() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
|
||||
let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
|
||||
let _ = alice.pop_sent_msg().await;
|
||||
@@ -628,7 +613,7 @@ First thread."#;
|
||||
async fn test_reaction() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let chat = create_group(&alice, "My Group").await?;
|
||||
let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
|
||||
let _ = alice.pop_sent_msg().await;
|
||||
|
||||
|
||||
@@ -964,10 +964,6 @@ impl MimeFactory {
|
||||
hidden_headers.push(header.clone());
|
||||
} else if is_hidden(&header_name) {
|
||||
hidden_headers.push(header.clone());
|
||||
} else if header_name == "autocrypt"
|
||||
&& !context.get_config_bool(Config::ProtectAutocrypt).await?
|
||||
{
|
||||
unprotected_headers.push(header.clone());
|
||||
} else if header_name == "from" {
|
||||
// Unencrypted securejoin messages should _not_ include the display name:
|
||||
if is_encrypted || !is_securejoin_message {
|
||||
@@ -1085,6 +1081,17 @@ impl MimeFactory {
|
||||
.is_none_or(|ts| now >= ts + gossip_period || now < ts)
|
||||
};
|
||||
|
||||
let verifier_id: Option<u32> = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT verifier FROM contacts WHERE fingerprint=?",
|
||||
(&fingerprint,),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let is_verified =
|
||||
verifier_id.is_some_and(|verifier_id| verifier_id != 0);
|
||||
|
||||
if !should_do_gossip {
|
||||
continue;
|
||||
}
|
||||
@@ -1095,7 +1102,7 @@ impl MimeFactory {
|
||||
// Autocrypt 1.1.0 specification says that
|
||||
// `prefer-encrypt` attribute SHOULD NOT be included.
|
||||
prefer_encrypt: EncryptPreference::NoPreference,
|
||||
verified: false,
|
||||
verified: is_verified,
|
||||
}
|
||||
.to_string();
|
||||
|
||||
@@ -1320,20 +1327,6 @@ impl MimeFactory {
|
||||
let command = msg.param.get_cmd();
|
||||
let mut placeholdertext = None;
|
||||
|
||||
let send_verified_headers = match chat.typ {
|
||||
Chattype::Single => true,
|
||||
Chattype::Group => true,
|
||||
// Mailinglists and broadcast channels can actually never be verified:
|
||||
Chattype::Mailinglist => false,
|
||||
Chattype::OutBroadcast | Chattype::InBroadcast => false,
|
||||
};
|
||||
if chat.is_protected() && send_verified_headers {
|
||||
headers.push((
|
||||
"Chat-Verified",
|
||||
mail_builder::headers::raw::Raw::new("1").into(),
|
||||
));
|
||||
}
|
||||
|
||||
if chat.typ == Chattype::Group {
|
||||
// Send group ID unless it is an ad hoc group that has no ID.
|
||||
if !chat.grpid.is_empty() {
|
||||
|
||||
@@ -6,8 +6,7 @@ use std::time::Duration;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{
|
||||
self, ChatId, ProtectionStatus, add_contact_to_chat, create_group_chat,
|
||||
remove_contact_from_chat, send_text_msg,
|
||||
self, ChatId, add_contact_to_chat, create_group, remove_contact_from_chat, send_text_msg,
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::constants;
|
||||
@@ -352,9 +351,7 @@ async fn test_subject_in_group() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let group_id = chat::create_group_chat(&t, chat::ProtectionStatus::Unprotected, "groupname")
|
||||
.await
|
||||
.unwrap();
|
||||
let group_id = chat::create_group(&t, "groupname").await.unwrap();
|
||||
let bob_contact_id = t.add_or_lookup_contact_id(&bob).await;
|
||||
chat::add_contact_to_chat(&t, group_id, bob_contact_id).await?;
|
||||
|
||||
@@ -666,7 +663,7 @@ async fn test_selfavatar_unencrypted_signed() {
|
||||
assert_eq!(part.match_indices("From:").count(), 1);
|
||||
assert_eq!(part.match_indices("Message-ID:").count(), 0);
|
||||
assert_eq!(part.match_indices("Subject:").count(), 1);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 0);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 1);
|
||||
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
|
||||
|
||||
let part = payload.next().unwrap();
|
||||
@@ -717,7 +714,7 @@ async fn test_selfavatar_unencrypted_signed() {
|
||||
assert_eq!(part.match_indices("From:").count(), 1);
|
||||
assert_eq!(part.match_indices("Message-ID:").count(), 0);
|
||||
assert_eq!(part.match_indices("Subject:").count(), 1);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 0);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 1);
|
||||
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
|
||||
|
||||
let part = payload.next().unwrap();
|
||||
@@ -756,7 +753,7 @@ async fn test_remove_member_bcc() -> Result<()> {
|
||||
let charlie_contact = Contact::get_by_id(alice, charlie_id).await?;
|
||||
let charlie_addr = charlie_contact.get_addr();
|
||||
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let alice_chat_id = create_group(alice, "foo").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, bob_id).await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, charlie_id).await?;
|
||||
send_text_msg(alice, alice_chat_id, "Creating a group".to_string()).await?;
|
||||
@@ -846,16 +843,12 @@ async fn test_dont_remove_self() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let first_group = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "First group", &[bob])
|
||||
.await;
|
||||
let first_group = alice.create_group_with_members("First group", &[bob]).await;
|
||||
alice.send_text(first_group, "Hi! I created a group.").await;
|
||||
remove_contact_from_chat(alice, first_group, ContactId::SELF).await?;
|
||||
alice.pop_sent_msg().await;
|
||||
|
||||
let second_group = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "First group", &[bob])
|
||||
.await;
|
||||
let second_group = alice.create_group_with_members("First group", &[bob]).await;
|
||||
let sent = alice
|
||||
.send_text(second_group, "Hi! I created another group.")
|
||||
.await;
|
||||
@@ -883,9 +876,7 @@ async fn test_new_member_is_first_recipient() -> Result<()> {
|
||||
let bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let charlie_id = alice.add_or_lookup_contact_id(charlie).await;
|
||||
|
||||
let group = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "Group", &[bob])
|
||||
.await;
|
||||
let group = alice.create_group_with_members("Group", &[bob]).await;
|
||||
alice.send_text(group, "Hi! I created a group.").await;
|
||||
|
||||
SystemTime::shift(Duration::from_secs(60));
|
||||
|
||||
@@ -1817,39 +1817,6 @@ async fn test_take_last_header() {
|
||||
);
|
||||
}
|
||||
|
||||
async fn test_protect_autocrypt(enabled: bool) -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let chat = alice.create_chat(bob).await;
|
||||
alice
|
||||
.set_config_bool(Config::ProtectAutocrypt, enabled)
|
||||
.await?;
|
||||
let sent = alice.send_text(chat.id, "Hello!").await;
|
||||
assert_eq!(sent.payload().contains("Autocrypt: "), !enabled);
|
||||
let msg = bob.recv_msg(&sent).await;
|
||||
assert_eq!(msg.get_showpadlock(), true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that if `protect_autocrypt` is enabled,
|
||||
/// `Autocrypt` header does not appear in the outer headers
|
||||
/// of encrypted messages.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_protect_autocrypt_enabled() -> Result<()> {
|
||||
test_protect_autocrypt(true).await
|
||||
}
|
||||
|
||||
/// Tests that if `protect_autocrypt` is disabled,
|
||||
/// `Autocrypt` header appears in the outer headers
|
||||
/// of encrypted messages.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_protect_autocrypt_false() -> Result<()> {
|
||||
test_protect_autocrypt(false).await
|
||||
}
|
||||
|
||||
/// Tests that CRLF before MIME boundary
|
||||
/// is not treated as the part body.
|
||||
///
|
||||
|
||||
@@ -14,17 +14,6 @@ use crate::provider;
|
||||
use crate::provider::Oauth2Authorizer;
|
||||
use crate::tools::time;
|
||||
|
||||
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||
// see <https://developers.google.com/identity/protocols/OAuth2InstalledApp>
|
||||
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
|
||||
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
|
||||
init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code",
|
||||
refresh_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&refresh_token=$REFRESH_TOKEN&grant_type=refresh_token",
|
||||
get_userinfo: Some(
|
||||
"https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=$ACCESS_TOKEN",
|
||||
),
|
||||
};
|
||||
|
||||
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
||||
// see <https://tech.yandex.com/oauth/doc/dg/reference/auto-code-client-docpage/>
|
||||
client_id: "c4d0b6735fc8420a816d7e1303469341",
|
||||
@@ -64,7 +53,7 @@ pub async fn get_oauth2_url(
|
||||
addr: &str,
|
||||
redirect_uri: &str,
|
||||
) -> Result<Option<String>> {
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr).await {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
|
||||
@@ -84,7 +73,7 @@ pub(crate) async fn get_oauth2_access_token(
|
||||
code: &str,
|
||||
regenerate: bool,
|
||||
) -> Result<Option<String>> {
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr).await {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
let lock = context.oauth2_mutex.lock().await;
|
||||
|
||||
// read generated token
|
||||
@@ -232,7 +221,7 @@ pub(crate) async fn get_oauth2_addr(
|
||||
addr: &str,
|
||||
code: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let oauth2 = match Oauth2::from_address(context, addr).await {
|
||||
let oauth2 = match Oauth2::from_address(addr) {
|
||||
Some(o) => o,
|
||||
None => return Ok(None),
|
||||
};
|
||||
@@ -267,19 +256,16 @@ pub(crate) async fn get_oauth2_addr(
|
||||
}
|
||||
|
||||
impl Oauth2 {
|
||||
async fn from_address(context: &Context, addr: &str) -> Option<Self> {
|
||||
fn from_address(addr: &str) -> Option<Self> {
|
||||
let addr_normalized = normalize_addr(addr);
|
||||
let skip_mx = true;
|
||||
if let Some(domain) = addr_normalized
|
||||
.find('@')
|
||||
.map(|index| addr_normalized.split_at(index + 1).1)
|
||||
{
|
||||
if let Some(oauth2_authorizer) = provider::get_provider_info(context, domain, skip_mx)
|
||||
.await
|
||||
if let Some(oauth2_authorizer) = provider::get_provider_info(domain)
|
||||
.and_then(|provider| provider.oauth2_authorizer.as_ref())
|
||||
{
|
||||
return Some(match oauth2_authorizer {
|
||||
Oauth2Authorizer::Gmail => OAUTH2_GMAIL,
|
||||
Oauth2Authorizer::Yandex => OAUTH2_YANDEX,
|
||||
});
|
||||
}
|
||||
@@ -366,21 +352,16 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_oauth_from_address() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// Delta Chat does not have working Gmail client ID anymore.
|
||||
assert_eq!(Oauth2::from_address(&t, "hello@gmail.com").await, None);
|
||||
assert_eq!(Oauth2::from_address(&t, "hello@googlemail.com").await, None);
|
||||
assert_eq!(Oauth2::from_address("hello@gmail.com"), None);
|
||||
assert_eq!(Oauth2::from_address("hello@googlemail.com"), None);
|
||||
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@yandex.com").await,
|
||||
Oauth2::from_address("hello@yandex.com"),
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@yandex.ru").await,
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
assert_eq!(Oauth2::from_address(&t, "hello@web.de").await, None);
|
||||
assert_eq!(Oauth2::from_address("hello@yandex.ru"), Some(OAUTH2_YANDEX));
|
||||
assert_eq!(Oauth2::from_address("hello@web.de"), None);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
@@ -574,7 +574,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
EventType,
|
||||
chat::{self, ChatId, ProtectionStatus, add_contact_to_chat, resend_msgs, send_msg},
|
||||
chat::{self, ChatId, add_contact_to_chat, resend_msgs, send_msg},
|
||||
message::{Message, Viewtype},
|
||||
test_utils::{TestContext, TestContextManager},
|
||||
};
|
||||
@@ -616,7 +616,7 @@ mod tests {
|
||||
loop {
|
||||
let event = bob.evtracker.recv().await.unwrap();
|
||||
if let EventType::WebxdcRealtimeAdvertisementReceived { msg_id } = event.typ {
|
||||
assert!(msg_id == alice_webxdc.id);
|
||||
assert!(msg_id == bob_webxdc.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -962,9 +962,7 @@ mod tests {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &mut tcm.alice().await;
|
||||
let bob = &mut tcm.bob().await;
|
||||
let group = chat::create_group_chat(alice, ProtectionStatus::Unprotected, "group chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let group = chat::create_group(alice, "group chat").await.unwrap();
|
||||
|
||||
// Alice sends webxdc to bob
|
||||
let mut instance = Message::new(Viewtype::File);
|
||||
|
||||
34
src/pgp.rs
34
src/pgp.rs
@@ -178,7 +178,7 @@ pub async fn pk_encrypt(
|
||||
let msg = MessageBuilder::from_bytes("", plain);
|
||||
let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
|
||||
for pkey in pkeys {
|
||||
msg.encrypt_to_key(&mut rng, &pkey)?;
|
||||
msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
|
||||
}
|
||||
|
||||
if let Some(ref skey) = private_key_for_signing {
|
||||
@@ -347,6 +347,8 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::{alice_keypair, bob_keypair};
|
||||
use pgp::composed::Esk;
|
||||
use pgp::packet::PublicKeyEncryptedSessionKey;
|
||||
|
||||
fn pk_decrypt_and_validate<'a>(
|
||||
ctext: &'a [u8],
|
||||
@@ -543,4 +545,34 @@ mod tests {
|
||||
assert_eq!(content, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 0);
|
||||
}
|
||||
|
||||
/// Tests that recipient key IDs and fingerprints
|
||||
/// are omitted or replaced with wildcards.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_anonymous_recipients() -> Result<()> {
|
||||
let ctext = ctext_signed().await.as_bytes();
|
||||
let cursor = Cursor::new(ctext);
|
||||
let (msg, _headers) = Message::from_armor(cursor)?;
|
||||
|
||||
let Message::Encrypted { esk, .. } = msg else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
for encrypted_session_key in esk {
|
||||
let Esk::PublicKeyEncryptedSessionKey(pkesk) = encrypted_session_key else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
match pkesk {
|
||||
PublicKeyEncryptedSessionKey::V3 { id, .. } => {
|
||||
assert!(id.is_wildcard());
|
||||
}
|
||||
PublicKeyEncryptedSessionKey::V6 { fingerprint, .. } => {
|
||||
assert!(fingerprint.is_none());
|
||||
}
|
||||
PublicKeyEncryptedSessionKey::Other { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
155
src/provider.rs
155
src/provider.rs
@@ -4,13 +4,9 @@ pub(crate) mod data;
|
||||
|
||||
use anyhow::Result;
|
||||
use deltachat_contact_tools::EmailAddress;
|
||||
use hickory_resolver::name_server::TokioConnectionProvider;
|
||||
use hickory_resolver::{Resolver, TokioResolver, config};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::log::warn;
|
||||
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS};
|
||||
|
||||
/// Provider status according to manual testing.
|
||||
@@ -82,13 +78,9 @@ pub enum UsernamePattern {
|
||||
|
||||
/// Type of OAuth 2 authorization.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum Oauth2Authorizer {
|
||||
/// Yandex.
|
||||
Yandex = 1,
|
||||
|
||||
/// Gmail.
|
||||
Gmail = 2,
|
||||
Yandex,
|
||||
}
|
||||
|
||||
/// Email server endpoint.
|
||||
@@ -175,61 +167,17 @@ impl ProviderOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get resolver to query MX records.
|
||||
///
|
||||
/// We first try to read the system's resolver from `/etc/resolv.conf`.
|
||||
/// This does not work at least on some Androids, therefore we fallback
|
||||
/// to the default `ResolverConfig` which uses eg. to google's `8.8.8.8` or `8.8.4.4`.
|
||||
fn get_resolver() -> Result<TokioResolver> {
|
||||
if let Ok(resolver) = TokioResolver::builder_tokio() {
|
||||
return Ok(resolver.build());
|
||||
}
|
||||
let resolver = Resolver::builder_with_config(
|
||||
config::ResolverConfig::default(),
|
||||
TokioConnectionProvider::default(),
|
||||
);
|
||||
Ok(resolver.build())
|
||||
}
|
||||
|
||||
/// Returns provider for the given an e-mail address.
|
||||
///
|
||||
/// Returns an error if provided address is not valid.
|
||||
pub async fn get_provider_info_by_addr(
|
||||
context: &Context,
|
||||
addr: &str,
|
||||
skip_mx: bool,
|
||||
) -> Result<Option<&'static Provider>> {
|
||||
pub fn get_provider_info_by_addr(addr: &str) -> Result<Option<&'static Provider>> {
|
||||
let addr = EmailAddress::new(addr)?;
|
||||
|
||||
let provider = get_provider_info(context, &addr.domain, skip_mx).await;
|
||||
Ok(provider)
|
||||
}
|
||||
|
||||
/// Returns provider for the given domain.
|
||||
///
|
||||
/// This function looks up domain in offline database first. If not
|
||||
/// found, it queries MX record for the domain and looks up offline
|
||||
/// database for MX domains.
|
||||
pub async fn get_provider_info(
|
||||
context: &Context,
|
||||
domain: &str,
|
||||
skip_mx: bool,
|
||||
) -> Option<&'static Provider> {
|
||||
if let Some(provider) = get_provider_by_domain(domain) {
|
||||
return Some(provider);
|
||||
}
|
||||
|
||||
if !skip_mx {
|
||||
if let Some(provider) = get_provider_by_mx(context, domain).await {
|
||||
return Some(provider);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Ok(get_provider_info(&addr.domain))
|
||||
}
|
||||
|
||||
/// Finds a provider in offline database based on domain.
|
||||
pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
|
||||
pub fn get_provider_info(domain: &str) -> Option<&'static Provider> {
|
||||
let domain = domain.to_lowercase();
|
||||
for (pattern, provider) in PROVIDER_DATA {
|
||||
if let Some(suffix) = pattern.strip_prefix('*') {
|
||||
@@ -247,51 +195,6 @@ pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Finds a provider based on MX record for the given domain.
|
||||
///
|
||||
/// For security reasons, only Gmail can be configured this way.
|
||||
pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'static Provider> {
|
||||
let Ok(resolver) = get_resolver() else {
|
||||
warn!(context, "Cannot get a resolver to check MX records.");
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut fqdn: String = domain.to_string();
|
||||
if !fqdn.ends_with('.') {
|
||||
fqdn.push('.');
|
||||
}
|
||||
|
||||
let Ok(mx_domains) = resolver.mx_lookup(fqdn).await else {
|
||||
warn!(context, "Cannot resolve MX records for {domain:?}.");
|
||||
return None;
|
||||
};
|
||||
|
||||
for (provider_domain_pattern, provider) in PROVIDER_DATA {
|
||||
if provider.id != "gmail" {
|
||||
// MX lookup is limited to Gmail for security reasons
|
||||
continue;
|
||||
}
|
||||
|
||||
if provider_domain_pattern.starts_with('*') {
|
||||
// Skip wildcard patterns.
|
||||
continue;
|
||||
}
|
||||
|
||||
let provider_fqdn = provider_domain_pattern.to_string() + ".";
|
||||
let provider_fqdn_dot = ".".to_string() + &provider_fqdn;
|
||||
|
||||
for mx_domain in mx_domains.iter() {
|
||||
let mx_domain = mx_domain.exchange().to_lowercase().to_utf8();
|
||||
|
||||
if mx_domain == provider_fqdn || mx_domain.ends_with(&provider_fqdn_dot) {
|
||||
return Some(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns a provider with the given ID from the database.
|
||||
pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
|
||||
if let Some(provider) = PROVIDER_IDS.get(id) {
|
||||
@@ -304,24 +207,23 @@ pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[test]
|
||||
fn test_get_provider_by_domain_unexistant() {
|
||||
let provider = get_provider_by_domain("unexistant.org");
|
||||
let provider = get_provider_info("unexistant.org");
|
||||
assert!(provider.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_provider_by_domain_mixed_case() {
|
||||
let provider = get_provider_by_domain("nAUta.Cu").unwrap();
|
||||
let provider = get_provider_info("nAUta.Cu").unwrap();
|
||||
assert!(provider.status == Status::Ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_provider_by_domain() {
|
||||
fn test_get_provider_info() {
|
||||
let addr = "nauta.cu";
|
||||
let provider = get_provider_by_domain(addr).unwrap();
|
||||
let provider = get_provider_info(addr).unwrap();
|
||||
assert!(provider.status == Status::Ok);
|
||||
let server = &provider.server[0];
|
||||
assert_eq!(server.protocol, Protocol::Imap);
|
||||
@@ -336,13 +238,17 @@ mod tests {
|
||||
assert_eq!(server.port, 25);
|
||||
assert_eq!(server.username_pattern, UsernamePattern::Email);
|
||||
|
||||
let provider = get_provider_by_domain("gmail.com").unwrap();
|
||||
let provider = get_provider_info("gmail.com").unwrap();
|
||||
assert!(provider.status == Status::Preparation);
|
||||
assert!(!provider.before_login_hint.is_empty());
|
||||
assert!(!provider.overview_page.is_empty());
|
||||
|
||||
let provider = get_provider_by_domain("googlemail.com").unwrap();
|
||||
let provider = get_provider_info("googlemail.com").unwrap();
|
||||
assert!(provider.status == Status::Preparation);
|
||||
|
||||
assert!(get_provider_info("").is_none());
|
||||
assert!(get_provider_info("google.com").unwrap().id == "gmail");
|
||||
assert!(get_provider_info("example@google.com").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -351,39 +257,10 @@ mod tests {
|
||||
assert!(provider.id == "gmail");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_provider_info() {
|
||||
let t = TestContext::new().await;
|
||||
assert!(get_provider_info(&t, "", false).await.is_none());
|
||||
assert!(get_provider_info(&t, "google.com", false).await.unwrap().id == "gmail");
|
||||
assert!(
|
||||
get_provider_info(&t, "example@google.com", false)
|
||||
.await
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_provider_info_by_addr() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
assert!(
|
||||
get_provider_info_by_addr(&t, "google.com", false)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
get_provider_info_by_addr(&t, "example@google.com", false)
|
||||
.await?
|
||||
.unwrap()
|
||||
.id
|
||||
== "gmail"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_resolver() -> Result<()> {
|
||||
assert!(get_resolver().is_ok());
|
||||
assert!(get_provider_info_by_addr("google.com").is_err());
|
||||
assert!(get_provider_info_by_addr("example@google.com")?.unwrap().id == "gmail");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
25
src/qr.rs
25
src/qr.rs
@@ -19,7 +19,7 @@ use crate::key::Fingerprint;
|
||||
use crate::net::http::post_empty;
|
||||
use crate::net::proxy::{DEFAULT_SOCKS_PORT, ProxyConfig};
|
||||
use crate::token;
|
||||
use crate::tools::validate_id;
|
||||
use crate::tools::{time, validate_id};
|
||||
|
||||
const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase
|
||||
const IDELTACHAT_SCHEME: &str = "https://i.delta.chat/#";
|
||||
@@ -741,8 +741,16 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
authcode,
|
||||
..
|
||||
} => {
|
||||
token::save(context, token::Namespace::InviteNumber, None, &invitenumber).await?;
|
||||
token::save(context, token::Namespace::Auth, None, &authcode).await?;
|
||||
let timestamp = time();
|
||||
token::save(
|
||||
context,
|
||||
token::Namespace::InviteNumber,
|
||||
None,
|
||||
&invitenumber,
|
||||
timestamp,
|
||||
)
|
||||
.await?;
|
||||
token::save(context, token::Namespace::Auth, None, &authcode, timestamp).await?;
|
||||
context.sync_qr_code_tokens(None).await?;
|
||||
context.scheduler.interrupt_inbox().await;
|
||||
}
|
||||
@@ -752,14 +760,23 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
grpid,
|
||||
..
|
||||
} => {
|
||||
let timestamp = time();
|
||||
token::save(
|
||||
context,
|
||||
token::Namespace::InviteNumber,
|
||||
Some(&grpid),
|
||||
&invitenumber,
|
||||
timestamp,
|
||||
)
|
||||
.await?;
|
||||
token::save(
|
||||
context,
|
||||
token::Namespace::Auth,
|
||||
Some(&grpid),
|
||||
&authcode,
|
||||
timestamp,
|
||||
)
|
||||
.await?;
|
||||
token::save(context, token::Namespace::Auth, Some(&grpid), &authcode).await?;
|
||||
context.sync_qr_code_tokens(Some(&grpid)).await?;
|
||||
context.scheduler.interrupt_inbox().await;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::chat::{ProtectionStatus, create_group_chat};
|
||||
use crate::chat::create_group;
|
||||
use crate::config::Config;
|
||||
use crate::securejoin::get_securejoin_qr;
|
||||
use crate::test_utils::{TestContext, TestContextManager, sync};
|
||||
@@ -479,7 +479,7 @@ async fn test_withdraw_verifycontact() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_withdraw_verifygroup() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&alice, "foo").await?;
|
||||
let qr = get_securejoin_qr(&alice, Some(chat_id)).await?;
|
||||
|
||||
// scanning own verify-group code offers withdrawing
|
||||
@@ -520,8 +520,8 @@ async fn test_withdraw_multidevice() -> Result<()> {
|
||||
|
||||
// Alice creates two QR codes on the first device:
|
||||
// group QR code and contact QR code.
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let chat2_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group 2").await?;
|
||||
let chat_id = create_group(alice, "Group").await?;
|
||||
let chat2_id = create_group(alice, "Group 2").await?;
|
||||
let contact_qr = get_securejoin_qr(alice, None).await?;
|
||||
let group_qr = get_securejoin_qr(alice, Some(chat_id)).await?;
|
||||
let group2_qr = get_securejoin_qr(alice, Some(chat2_id)).await?;
|
||||
|
||||
@@ -14,9 +14,7 @@ use mailparse::SingleInfo;
|
||||
use num_traits::FromPrimitive;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::chat::{
|
||||
self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, remove_from_chat_contacts_table,
|
||||
};
|
||||
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, remove_from_chat_contacts_table};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{self, Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
|
||||
use crate::contact::{Contact, ContactId, Origin, mark_contact_id_as_verified};
|
||||
@@ -248,9 +246,7 @@ async fn get_to_and_past_contact_ids(
|
||||
let chat_id = match chat_assignment {
|
||||
ChatAssignment::Trash => None,
|
||||
ChatAssignment::GroupChat { grpid } => {
|
||||
if let Some((chat_id, _protected, _blocked)) =
|
||||
chat::get_chat_id_by_grpid(context, grpid).await?
|
||||
{
|
||||
if let Some((chat_id, _blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
|
||||
Some(chat_id)
|
||||
} else {
|
||||
None
|
||||
@@ -742,7 +738,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
let verified_encryption = has_verified_encryption(context, &mime_parser, from_id).await?;
|
||||
|
||||
if verified_encryption == VerifiedEncryption::Verified {
|
||||
mark_recipients_as_verified(context, from_id, &to_ids, &mime_parser).await?;
|
||||
mark_recipients_as_verified(context, from_id, &mime_parser).await?;
|
||||
}
|
||||
|
||||
let received_msg = if let Some(received_msg) = received_msg {
|
||||
@@ -794,7 +790,6 @@ pub(crate) async fn receive_imf_inner(
|
||||
allow_creation,
|
||||
&mut mime_parser,
|
||||
is_partial_download,
|
||||
&verified_encryption,
|
||||
parent_message,
|
||||
)
|
||||
.await?;
|
||||
@@ -812,7 +807,6 @@ pub(crate) async fn receive_imf_inner(
|
||||
is_partial_download,
|
||||
replace_msg_id,
|
||||
prevent_rename,
|
||||
verified_encryption,
|
||||
chat_id,
|
||||
chat_id_blocked,
|
||||
is_dc_message,
|
||||
@@ -860,7 +854,9 @@ pub(crate) async fn receive_imf_inner(
|
||||
if let Some(ref sync_items) = mime_parser.sync_items {
|
||||
if from_id == ContactId::SELF {
|
||||
if mime_parser.was_encrypted() {
|
||||
context.execute_sync_items(sync_items).await;
|
||||
context
|
||||
.execute_sync_items(sync_items, mime_parser.timestamp_sent)
|
||||
.await;
|
||||
} else {
|
||||
warn!(context, "Sync items are not encrypted.");
|
||||
}
|
||||
@@ -1319,7 +1315,6 @@ async fn do_chat_assignment(
|
||||
allow_creation: bool,
|
||||
mime_parser: &mut MimeMessage,
|
||||
is_partial_download: Option<u32>,
|
||||
verified_encryption: &VerifiedEncryption,
|
||||
parent_message: Option<Message>,
|
||||
) -> Result<(ChatId, Blocked)> {
|
||||
let is_bot = context.get_config_bool(Config::Bot).await?;
|
||||
@@ -1362,9 +1357,7 @@ async fn do_chat_assignment(
|
||||
}
|
||||
ChatAssignment::GroupChat { grpid } => {
|
||||
// Try to assign to a chat based on Chat-Group-ID.
|
||||
if let Some((id, _protected, blocked)) =
|
||||
chat::get_chat_id_by_grpid(context, grpid).await?
|
||||
{
|
||||
if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
|
||||
chat_id = Some(id);
|
||||
chat_id_blocked = blocked;
|
||||
} else if allow_creation || test_normal_chat.is_some() {
|
||||
@@ -1376,7 +1369,6 @@ async fn do_chat_assignment(
|
||||
from_id,
|
||||
to_ids,
|
||||
past_ids,
|
||||
verified_encryption,
|
||||
grpid,
|
||||
)
|
||||
.await?
|
||||
@@ -1478,45 +1470,6 @@ async fn do_chat_assignment(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the message was sent with verified encryption and set the protection of
|
||||
// the 1:1 chat accordingly.
|
||||
let chat = match is_partial_download.is_none()
|
||||
&& mime_parser.get_header(HeaderDef::SecureJoin).is_none()
|
||||
{
|
||||
true => Some(Chat::load_from_db(context, chat_id).await?)
|
||||
.filter(|chat| chat.typ == Chattype::Single),
|
||||
false => None,
|
||||
};
|
||||
if let Some(chat) = chat {
|
||||
ensure_and_debug_assert!(
|
||||
chat.typ == Chattype::Single,
|
||||
"Chat {chat_id} is not Single",
|
||||
);
|
||||
let new_protection = match verified_encryption {
|
||||
VerifiedEncryption::Verified => ProtectionStatus::Protected,
|
||||
VerifiedEncryption::NotVerified(_) => ProtectionStatus::Unprotected,
|
||||
};
|
||||
|
||||
ensure_and_debug_assert!(
|
||||
chat.protected == ProtectionStatus::Unprotected
|
||||
|| new_protection == ProtectionStatus::Protected,
|
||||
"Chat {chat_id} can't downgrade to Unprotected",
|
||||
);
|
||||
if chat.protected != new_protection {
|
||||
// The message itself will be sorted under the device message since the device
|
||||
// message is `MessageState::InNoticed`, which means that all following
|
||||
// messages are sorted under it.
|
||||
chat_id
|
||||
.set_protection(
|
||||
context,
|
||||
new_protection,
|
||||
mime_parser.timestamp_sent,
|
||||
Some(from_id),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1533,9 +1486,7 @@ async fn do_chat_assignment(
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
ChatAssignment::GroupChat { grpid } => {
|
||||
if let Some((id, _protected, blocked)) =
|
||||
chat::get_chat_id_by_grpid(context, grpid).await?
|
||||
{
|
||||
if let Some((id, blocked)) = chat::get_chat_id_by_grpid(context, grpid).await? {
|
||||
chat_id = Some(id);
|
||||
chat_id_blocked = blocked;
|
||||
} else if allow_creation {
|
||||
@@ -1547,7 +1498,6 @@ async fn do_chat_assignment(
|
||||
from_id,
|
||||
to_ids,
|
||||
past_ids,
|
||||
verified_encryption,
|
||||
grpid,
|
||||
)
|
||||
.await?
|
||||
@@ -1603,7 +1553,7 @@ async fn do_chat_assignment(
|
||||
if chat_id.is_none() && allow_creation {
|
||||
let to_contact = Contact::get_by_id(context, to_id).await?;
|
||||
if let Some(list_id) = to_contact.param.get(Param::ListId) {
|
||||
if let Some((id, _, blocked)) =
|
||||
if let Some((id, blocked)) =
|
||||
chat::get_chat_id_by_grpid(context, list_id).await?
|
||||
{
|
||||
chat_id = Some(id);
|
||||
@@ -1669,7 +1619,6 @@ async fn add_parts(
|
||||
is_partial_download: Option<u32>,
|
||||
mut replace_msg_id: Option<MsgId>,
|
||||
prevent_rename: bool,
|
||||
verified_encryption: VerifiedEncryption,
|
||||
chat_id: ChatId,
|
||||
chat_id_blocked: Blocked,
|
||||
is_dc_message: MessengerMessage,
|
||||
@@ -1718,16 +1667,7 @@ async fn add_parts(
|
||||
apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
|
||||
}
|
||||
Chattype::Group => {
|
||||
apply_group_changes(
|
||||
context,
|
||||
mime_parser,
|
||||
&mut chat,
|
||||
from_id,
|
||||
to_ids,
|
||||
past_ids,
|
||||
&verified_encryption,
|
||||
)
|
||||
.await?
|
||||
apply_group_changes(context, mime_parser, &mut chat, from_id, to_ids, past_ids).await?
|
||||
}
|
||||
Chattype::InBroadcast => {
|
||||
apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
|
||||
@@ -2224,6 +2164,7 @@ RETURNING id
|
||||
|
||||
if !chat_id.is_trash() && !hidden {
|
||||
let mut chat = Chat::load_from_db(context, chat_id).await?;
|
||||
let mut update_param = false;
|
||||
|
||||
// In contrast to most other update-timestamps,
|
||||
// use `sort_timestamp` instead of `sent_timestamp` for the subject-timestamp comparison.
|
||||
@@ -2237,6 +2178,14 @@ RETURNING id
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
|
||||
chat.param.set(Param::LastSubject, subject);
|
||||
update_param = true;
|
||||
}
|
||||
|
||||
if chat.is_unpromoted() {
|
||||
chat.param.remove(Param::Unpromoted);
|
||||
update_param = true;
|
||||
}
|
||||
if update_param {
|
||||
chat.update_param(context).await?;
|
||||
}
|
||||
}
|
||||
@@ -2632,27 +2581,12 @@ async fn create_group(
|
||||
from_id: ContactId,
|
||||
to_ids: &[Option<ContactId>],
|
||||
past_ids: &[Option<ContactId>],
|
||||
verified_encryption: &VerifiedEncryption,
|
||||
grpid: &str,
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
|
||||
let mut chat_id = None;
|
||||
let mut chat_id_blocked = Default::default();
|
||||
|
||||
let create_protected = if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
|
||||
if let VerifiedEncryption::NotVerified(err) = verified_encryption {
|
||||
warn!(
|
||||
context,
|
||||
"Creating unprotected group because of the verification problem: {err:#}."
|
||||
);
|
||||
ProtectionStatus::Unprotected
|
||||
} else {
|
||||
ProtectionStatus::Protected
|
||||
}
|
||||
} else {
|
||||
ProtectionStatus::Unprotected
|
||||
};
|
||||
|
||||
async fn self_explicitly_added(
|
||||
context: &Context,
|
||||
mime_parser: &&mut MimeMessage,
|
||||
@@ -2688,7 +2622,6 @@ async fn create_group(
|
||||
grpid,
|
||||
grpname,
|
||||
create_blocked,
|
||||
create_protected,
|
||||
None,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
@@ -2860,7 +2793,6 @@ async fn apply_group_changes(
|
||||
from_id: ContactId,
|
||||
to_ids: &[Option<ContactId>],
|
||||
past_ids: &[Option<ContactId>],
|
||||
verified_encryption: &VerifiedEncryption,
|
||||
) -> Result<GroupChangesInfo> {
|
||||
let to_ids_flat: Vec<ContactId> = to_ids.iter().filter_map(|x| *x).collect();
|
||||
ensure!(chat.typ == Chattype::Group);
|
||||
@@ -2875,24 +2807,6 @@ async fn apply_group_changes(
|
||||
let is_from_in_chat =
|
||||
!chat_contacts.contains(&ContactId::SELF) || chat_contacts.contains(&from_id);
|
||||
|
||||
if mime_parser.get_header(HeaderDef::ChatVerified).is_some() && !chat.is_protected() {
|
||||
if let VerifiedEncryption::NotVerified(err) = verified_encryption {
|
||||
warn!(
|
||||
context,
|
||||
"Not marking chat {} as protected due to verification problem: {err:#}.", chat.id,
|
||||
);
|
||||
} else {
|
||||
chat.id
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
mime_parser.timestamp_sent,
|
||||
Some(from_id),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
|
||||
// TODO: if address "alice@example.org" is a member of the group twice,
|
||||
// with old and new key,
|
||||
@@ -3280,7 +3194,7 @@ async fn create_or_lookup_mailinglist_or_broadcast(
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
let listid = mailinglist_header_listid(list_id_header)?;
|
||||
|
||||
if let Some((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
|
||||
if let Some((chat_id, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
|
||||
return Ok(Some((chat_id, blocked)));
|
||||
}
|
||||
|
||||
@@ -3318,7 +3232,6 @@ async fn create_or_lookup_mailinglist_or_broadcast(
|
||||
&listid,
|
||||
name,
|
||||
blocked,
|
||||
ProtectionStatus::Unprotected,
|
||||
param,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
@@ -3599,7 +3512,6 @@ async fn create_adhoc_group(
|
||||
"", // Ad hoc groups have no ID.
|
||||
grpname,
|
||||
create_blocked,
|
||||
ProtectionStatus::Unprotected,
|
||||
None,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
@@ -3678,7 +3590,6 @@ async fn has_verified_encryption(
|
||||
async fn mark_recipients_as_verified(
|
||||
context: &Context,
|
||||
from_id: ContactId,
|
||||
to_ids: &[Option<ContactId>],
|
||||
mimeparser: &MimeMessage,
|
||||
) -> Result<()> {
|
||||
let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
|
||||
@@ -3697,19 +3608,6 @@ async fn mark_recipients_as_verified(
|
||||
}
|
||||
|
||||
mark_contact_id_as_verified(context, to_id, verifier_id).await?;
|
||||
ChatId::set_protection_for_contact(context, to_id, mimeparser.timestamp_sent).await?;
|
||||
}
|
||||
|
||||
if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
for to_id in to_ids.iter().filter_map(|&x| x) {
|
||||
if to_id == ContactId::SELF || to_id == from_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
mark_contact_id_as_verified(context, to_id, verifier_id).await?;
|
||||
ChatId::set_protection_for_contact(context, to_id, mimeparser.timestamp_sent).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -5,7 +5,7 @@ use tokio::fs;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{
|
||||
ChatItem, ChatVisibility, add_contact_to_chat, add_to_chat_contacts_table, create_group_chat,
|
||||
ChatItem, ChatVisibility, add_contact_to_chat, add_to_chat_contacts_table, create_group,
|
||||
get_chat_contacts, get_chat_msgs, is_contact_in_chat, remove_contact_from_chat, send_text_msg,
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
@@ -1000,7 +1000,7 @@ async fn test_other_device_writes_to_mailinglist() -> Result<()> {
|
||||
chat::get_chat_id_by_grpid(&t, "delta.codespeak.net")
|
||||
.await?
|
||||
.unwrap(),
|
||||
(first_chat.id, false, Blocked::Request)
|
||||
(first_chat.id, Blocked::Request)
|
||||
);
|
||||
|
||||
receive_imf(
|
||||
@@ -2901,9 +2901,9 @@ async fn test_accept_outgoing() -> Result<()> {
|
||||
let bob1_chat = bob1.create_chat(&alice1).await;
|
||||
let sent = bob1.send_text(bob1_chat.id, "Hello!").await;
|
||||
|
||||
alice1.recv_msg(&sent).await;
|
||||
let alice1_msg = alice1.recv_msg(&sent).await;
|
||||
alice2.recv_msg(&sent).await;
|
||||
let alice1_msg = bob2.recv_msg(&sent).await;
|
||||
bob2.recv_msg(&sent).await;
|
||||
assert_eq!(alice1_msg.text, "Hello!");
|
||||
let alice1_chat = chat::Chat::load_from_db(&alice1, alice1_msg.chat_id).await?;
|
||||
assert!(alice1_chat.is_contact_request());
|
||||
@@ -2944,7 +2944,7 @@ async fn test_outgoing_private_reply_multidevice() -> Result<()> {
|
||||
let charlie = tcm.charlie().await;
|
||||
|
||||
// =============== Bob creates a group ===============
|
||||
let group_id = chat::create_group_chat(&bob, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let group_id = chat::create_group(&bob, "Group").await?;
|
||||
chat::add_to_chat_contacts_table(
|
||||
&bob,
|
||||
time(),
|
||||
@@ -3042,9 +3042,7 @@ async fn test_auto_accept_protected_group_for_bots() -> Result<()> {
|
||||
bob.set_config(Config::Bot, Some("1")).await.unwrap();
|
||||
mark_as_verified(alice, bob).await;
|
||||
mark_as_verified(bob, alice).await;
|
||||
let group_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob])
|
||||
.await;
|
||||
let group_id = alice.create_group_with_members("Group", &[bob]).await;
|
||||
let sent = alice.send_text(group_id, "Hello!").await;
|
||||
let msg = bob.recv_msg(&sent).await;
|
||||
let chat = chat::Chat::load_from_db(bob, msg.chat_id).await?;
|
||||
@@ -3092,13 +3090,11 @@ async fn test_bot_accepts_another_group_after_qr_scan() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
bob.set_config(Config::Bot, Some("1")).await?;
|
||||
|
||||
let group_id = chat::create_group_chat(alice, ProtectionStatus::Protected, "Group").await?;
|
||||
let group_id = chat::create_group(alice, "Group").await?;
|
||||
let qr = get_securejoin_qr(alice, Some(group_id)).await?;
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
|
||||
let group_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob])
|
||||
.await;
|
||||
let group_id = alice.create_group_with_members("Group", &[bob]).await;
|
||||
let sent = alice.send_text(group_id, "Hello!").await;
|
||||
let msg = bob.recv_msg(&sent).await;
|
||||
let chat = chat::Chat::load_from_db(bob, msg.chat_id).await?;
|
||||
@@ -3152,7 +3148,7 @@ async fn test_no_private_reply_to_blocked_account() -> Result<()> {
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
tcm.section("Bob creates a group");
|
||||
let group_id = chat::create_group_chat(&bob, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let group_id = chat::create_group(&bob, "Group").await?;
|
||||
chat::add_to_chat_contacts_table(
|
||||
&bob,
|
||||
time(),
|
||||
@@ -3228,11 +3224,7 @@ async fn test_blocked_contact_creates_group() -> Result<()> {
|
||||
chat.id.block(&alice).await?;
|
||||
|
||||
let group_id = bob
|
||||
.create_group_with_members(
|
||||
ProtectionStatus::Unprotected,
|
||||
"group name",
|
||||
&[&alice, &fiona],
|
||||
)
|
||||
.create_group_with_members("group name", &[&alice, &fiona])
|
||||
.await;
|
||||
|
||||
let sent = bob.send_text(group_id, "Heyho, I'm a spammer!").await;
|
||||
@@ -3253,7 +3245,7 @@ async fn test_blocked_contact_creates_group() -> Result<()> {
|
||||
assert_eq!(rcvd.chat_blocked, Blocked::Request);
|
||||
// In order not to lose context, Bob's message should also be shown in the group
|
||||
let msgs = chat::get_chat_msgs(&alice, rcvd.chat_id).await?;
|
||||
assert_eq!(msgs.len(), 2);
|
||||
assert_eq!(msgs.len(), 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3728,7 +3720,7 @@ async fn test_unsigned_chat_group_hdr() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let bob_addr = bob.get_config(Config::Addr).await?.unwrap();
|
||||
let bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foos").await?;
|
||||
let alice_chat_id = create_group(alice, "foos").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, bob_id).await?;
|
||||
send_text_msg(alice, alice_chat_id, "populate".to_string()).await?;
|
||||
let sent_msg = alice.pop_sent_msg().await;
|
||||
@@ -3774,7 +3766,7 @@ async fn test_sync_member_list_on_rejoin() -> Result<()> {
|
||||
let bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let fiona_id = alice.add_or_lookup_contact_id(fiona).await;
|
||||
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foos").await?;
|
||||
let alice_chat_id = create_group(alice, "foos").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, bob_id).await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, fiona_id).await?;
|
||||
|
||||
@@ -3812,7 +3804,7 @@ async fn test_ignore_outdated_membership_changes() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice_bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
let alice_chat_id = create_group(alice, "grp").await?;
|
||||
|
||||
// Alice creates a group chat. Bob accepts it.
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_id).await?;
|
||||
@@ -3860,7 +3852,7 @@ async fn test_dont_recreate_contacts_on_add_remove() -> Result<()> {
|
||||
let fiona = &tcm.fiona().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_chat_id = create_group(alice, "Group").await?;
|
||||
|
||||
add_contact_to_chat(
|
||||
alice,
|
||||
@@ -3911,7 +3903,7 @@ async fn test_delayed_removal_is_ignored() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let chat_id = create_group(alice, "Group").await?;
|
||||
let alice_bob = alice.add_or_lookup_contact_id(bob).await;
|
||||
let alice_fiona = alice.add_or_lookup_contact_id(fiona).await;
|
||||
// create chat with three members
|
||||
@@ -3964,7 +3956,7 @@ async fn test_dont_readd_with_normal_msg() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_chat_id = create_group(alice, "Group").await?;
|
||||
|
||||
add_contact_to_chat(
|
||||
alice,
|
||||
@@ -4202,7 +4194,7 @@ async fn test_member_left_does_not_create_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_chat_id = create_group(alice, "Group").await?;
|
||||
add_contact_to_chat(
|
||||
alice,
|
||||
alice_chat_id,
|
||||
@@ -4230,7 +4222,7 @@ async fn test_recreate_member_list_on_missing_add_of_self() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_chat_id = create_group(alice, "Group").await?;
|
||||
add_contact_to_chat(
|
||||
alice,
|
||||
alice_chat_id,
|
||||
@@ -4274,7 +4266,7 @@ async fn test_recreate_member_list_on_missing_add_of_self() -> Result<()> {
|
||||
async fn test_keep_member_list_if_possibly_nomember() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_chat_id = create_group(&alice, "Group").await?;
|
||||
add_contact_to_chat(
|
||||
&alice,
|
||||
alice_chat_id,
|
||||
@@ -4414,7 +4406,7 @@ async fn test_create_group_with_big_msg() -> Result<()> {
|
||||
|
||||
let file_bytes = include_bytes!("../../test-data/image/screenshot.png");
|
||||
|
||||
let bob_grp_id = create_group_chat(&bob, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let bob_grp_id = create_group(&bob, "Group").await?;
|
||||
add_contact_to_chat(&bob, bob_grp_id, ba_contact).await?;
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
msg.set_file_from_bytes(&bob, "a.jpg", file_bytes, None)?;
|
||||
@@ -4445,7 +4437,7 @@ async fn test_create_group_with_big_msg() -> Result<()> {
|
||||
|
||||
// Now Bob can send encrypted messages to Alice.
|
||||
|
||||
let bob_grp_id = create_group_chat(&bob, ProtectionStatus::Unprotected, "Group1").await?;
|
||||
let bob_grp_id = create_group(&bob, "Group1").await?;
|
||||
add_contact_to_chat(&bob, bob_grp_id, ba_contact).await?;
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
msg.set_file_from_bytes(&bob, "a.jpg", file_bytes, None)?;
|
||||
@@ -4486,7 +4478,7 @@ async fn test_partial_group_consistency() -> Result<()> {
|
||||
let bob = tcm.bob().await;
|
||||
let fiona = tcm.fiona().await;
|
||||
let bob_id = alice.add_or_lookup_contact_id(&bob).await;
|
||||
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foos").await?;
|
||||
let alice_chat_id = create_group(&alice, "foos").await?;
|
||||
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
|
||||
|
||||
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
|
||||
@@ -4566,7 +4558,7 @@ async fn test_protected_group_add_remove_member_missing_key() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
mark_as_verified(alice, bob).await;
|
||||
let group_id = create_group_chat(alice, ProtectionStatus::Protected, "Group").await?;
|
||||
let group_id = create_group(alice, "Group").await?;
|
||||
let alice_bob_id = alice.add_or_lookup_contact(bob).await.id;
|
||||
add_contact_to_chat(alice, group_id, alice_bob_id).await?;
|
||||
alice.send_text(group_id, "Hello!").await;
|
||||
@@ -4663,7 +4655,7 @@ async fn test_unarchive_on_member_removal() -> Result<()> {
|
||||
let fiona = &tcm.fiona().await;
|
||||
let bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let fiona_id = alice.add_or_lookup_contact_id(fiona).await;
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foos").await?;
|
||||
let alice_chat_id = create_group(alice, "foos").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, bob_id).await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, fiona_id).await?;
|
||||
|
||||
@@ -4696,9 +4688,7 @@ async fn test_no_op_member_added_is_trash() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "foos", &[bob])
|
||||
.await;
|
||||
let alice_chat_id = alice.create_group_with_members("foos", &[bob]).await;
|
||||
send_text_msg(alice, alice_chat_id, "populate".to_string()).await?;
|
||||
let msg = alice.pop_sent_msg().await;
|
||||
bob.recv_msg(&msg).await;
|
||||
@@ -4765,7 +4755,7 @@ async fn test_references() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
alice.set_config_bool(Config::BccSelf, true).await?;
|
||||
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_chat_id = create_group(alice, "Group").await?;
|
||||
alice
|
||||
.send_text(alice_chat_id, "Hi! I created a group.")
|
||||
.await;
|
||||
@@ -4809,7 +4799,7 @@ async fn test_prefer_references_to_downloaded_msgs() -> Result<()> {
|
||||
let fiona = &tcm.fiona().await;
|
||||
let alice_bob_id = tcm.send_recv(bob, alice, "hi").await.from_id;
|
||||
let alice_fiona_id = tcm.send_recv(fiona, alice, "hi").await.from_id;
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let alice_chat_id = create_group(alice, "Group").await?;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_id).await?;
|
||||
// W/o fiona the test doesn't work -- the last message is assigned to the 1:1 chat due to
|
||||
// `is_probably_private_reply()`.
|
||||
@@ -5009,12 +4999,7 @@ async fn test_group_name_with_newline() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let chat_id = create_group_chat(
|
||||
alice,
|
||||
ProtectionStatus::Unprotected,
|
||||
"Group\r\nwith\nnewlines",
|
||||
)
|
||||
.await?;
|
||||
let chat_id = create_group(alice, "Group\r\nwith\nnewlines").await?;
|
||||
add_contact_to_chat(alice, chat_id, alice.add_or_lookup_contact_id(bob).await).await?;
|
||||
send_text_msg(alice, chat_id, "populate".to_string()).await?;
|
||||
let bob_chat_id = bob.recv_msg(&alice.pop_sent_msg().await).await.chat_id;
|
||||
@@ -5032,7 +5017,7 @@ async fn test_rename_chat_on_missing_message() -> Result<()> {
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let charlie = tcm.charlie().await;
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let chat_id = create_group(&alice, "Group").await?;
|
||||
add_to_chat_contacts_table(
|
||||
&alice,
|
||||
time(),
|
||||
@@ -5068,7 +5053,7 @@ async fn test_rename_chat_after_creating_invite() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
for populate_before_securejoin in [false, true] {
|
||||
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Protected, "Group").await?;
|
||||
let alice_chat_id = create_group(alice, "Group").await?;
|
||||
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await?;
|
||||
|
||||
SystemTime::shift(Duration::from_secs(60));
|
||||
@@ -5098,7 +5083,7 @@ async fn test_two_group_securejoins() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
|
||||
let group_id = chat::create_group_chat(alice, ProtectionStatus::Protected, "Group").await?;
|
||||
let group_id = chat::create_group(alice, "Group").await?;
|
||||
|
||||
let qr = get_securejoin_qr(alice, Some(group_id)).await?;
|
||||
|
||||
@@ -5123,8 +5108,7 @@ async fn test_unverified_member_msg() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
|
||||
let alice_chat_id =
|
||||
chat::create_group_chat(alice, ProtectionStatus::Protected, "Group").await?;
|
||||
let alice_chat_id = chat::create_group(alice, "Group").await?;
|
||||
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await?;
|
||||
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
@@ -5150,12 +5134,15 @@ async fn test_dont_reverify_by_self_on_outgoing_msg() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
|
||||
let bob_chat_id = chat::create_group_chat(bob, ProtectionStatus::Protected, "Group").await?;
|
||||
let bob_chat_id = chat::create_group(bob, "Group").await?;
|
||||
let qr = get_securejoin_qr(bob, Some(bob_chat_id)).await?;
|
||||
tcm.exec_securejoin_qr(fiona, bob, &qr).await;
|
||||
tcm.exec_securejoin_qr(a0, bob, &qr).await;
|
||||
tcm.exec_securejoin_qr(a1, bob, &qr).await;
|
||||
|
||||
// Shift time by one week to trigger gossip.
|
||||
SystemTime::shift(Duration::from_secs(7 * 24 * 3600));
|
||||
|
||||
let a0_chat_id = a0.get_last_msg().await.chat_id;
|
||||
let a0_sent_msg = a0.send_text(a0_chat_id, "Hi").await;
|
||||
a1.recv_msg(&a0_sent_msg).await;
|
||||
@@ -5211,9 +5198,7 @@ async fn test_no_address_contact_added_into_group() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "Group", &[bob])
|
||||
.await;
|
||||
let alice_chat_id = alice.create_group_with_members("Group", &[bob]).await;
|
||||
let bob_received_msg = bob
|
||||
.recv_msg(&alice.send_text(alice_chat_id, "Message").await)
|
||||
.await;
|
||||
@@ -5493,7 +5478,7 @@ async fn test_small_unencrypted_group() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_chat_id = chat::create_group_ex(alice, None, "Unencrypted group").await?;
|
||||
let alice_chat_id = chat::create_group_unencrypted(alice, "Unencrypted group").await?;
|
||||
let alice_bob_id = alice.add_or_lookup_address_contact_id(bob).await;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_id).await?;
|
||||
send_text_msg(alice, alice_chat_id, "Hello!".to_string()).await?;
|
||||
|
||||
@@ -4,8 +4,7 @@ use anyhow::{Context as _, Error, Result, bail, ensure};
|
||||
use deltachat_contact_tools::ContactAddress;
|
||||
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
|
||||
|
||||
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, get_chat_id_by_grpid};
|
||||
use crate::chatlist_events;
|
||||
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, get_chat_id_by_grpid};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{Blocked, Chattype, NON_ALPHANUMERIC_WITHOUT_DOT};
|
||||
use crate::contact::mark_contact_id_as_verified;
|
||||
@@ -23,6 +22,7 @@ use crate::qr::check_qr;
|
||||
use crate::securejoin::bob::JoinerProgress;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::token;
|
||||
use crate::tools::{create_id, time};
|
||||
|
||||
mod bob;
|
||||
mod qrinvite;
|
||||
@@ -87,10 +87,21 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
|
||||
let sync_token = token::lookup(context, Namespace::InviteNumber, grpid)
|
||||
.await?
|
||||
.is_none();
|
||||
// invitenumber will be used to allow starting the handshake,
|
||||
// auth will be used to verify the fingerprint
|
||||
// Invite number is used to request the inviter key.
|
||||
let invitenumber = token::lookup_or_new(context, Namespace::InviteNumber, grpid).await?;
|
||||
let auth = token::lookup_or_new(context, Namespace::Auth, grpid).await?;
|
||||
|
||||
// Auth token is used to verify the key-contact
|
||||
// if the token is not old
|
||||
// and add the contact to the group
|
||||
// if there is an associated group ID.
|
||||
//
|
||||
// We always generate a new auth token
|
||||
// because auth tokens "expire"
|
||||
// and can only be used to join groups
|
||||
// without verification afterwards.
|
||||
let auth = create_id();
|
||||
token::save(context, Namespace::Auth, grpid, &auth, time()).await?;
|
||||
|
||||
let self_addr = context.get_primary_self_addr().await?;
|
||||
let self_name = context
|
||||
.get_config(Config::Displayname)
|
||||
@@ -378,7 +389,19 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
);
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
};
|
||||
let Some(grpid) = token::auth_foreign_key(context, auth).await? else {
|
||||
let Some((grpid, timestamp)) = context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT foreign_key, timestamp FROM tokens WHERE namespc=? AND token=?",
|
||||
(Namespace::Auth, auth),
|
||||
|row| {
|
||||
let foreign_key: String = row.get(0)?;
|
||||
let timestamp: i64 = row.get(1)?;
|
||||
Ok((foreign_key, timestamp))
|
||||
},
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
warn!(
|
||||
context,
|
||||
"Ignoring {step} message because of invalid auth code."
|
||||
@@ -396,7 +419,11 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
}
|
||||
};
|
||||
|
||||
if !verify_sender_by_fingerprint(context, &fingerprint, contact_id).await? {
|
||||
let sender_contact = Contact::get_by_id(context, contact_id).await?;
|
||||
if sender_contact
|
||||
.fingerprint()
|
||||
.is_none_or(|fp| fp != fingerprint)
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"Ignoring {step} message because of fingerprint mismatch."
|
||||
@@ -404,6 +431,11 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
info!(context, "Fingerprint verified via Auth code.",);
|
||||
|
||||
// Mark the contact as verified if auth code is 600 seconds old.
|
||||
if time() < timestamp + 600 {
|
||||
mark_contact_id_as_verified(context, contact_id, Some(ContactId::SELF)).await?;
|
||||
}
|
||||
contact_id.regossip_keys(context).await?;
|
||||
ContactId::scaleup_origin(context, &[contact_id], Origin::SecurejoinInvited).await?;
|
||||
// for setup-contact, make Alice's one-to-one chat with Bob visible
|
||||
@@ -414,13 +446,6 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
||||
if let Some(group_chat_id) = group_chat_id {
|
||||
// Join group.
|
||||
secure_connection_established(
|
||||
context,
|
||||
contact_id,
|
||||
group_chat_id,
|
||||
mime_message.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
chat::add_contact_to_chat_ex(context, Nosync, group_chat_id, contact_id, true)
|
||||
.await?;
|
||||
let is_group = true;
|
||||
@@ -431,13 +456,6 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
} else {
|
||||
let chat_id = info_chat_id(context, contact_id).await?;
|
||||
// Setup verified contact.
|
||||
secure_connection_established(
|
||||
context,
|
||||
contact_id,
|
||||
chat_id,
|
||||
mime_message.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
send_alice_handshake_msg(context, contact_id, "vc-contact-confirm")
|
||||
.await
|
||||
.context("failed sending vc-contact-confirm message")?;
|
||||
@@ -560,8 +578,6 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
||||
|
||||
mark_contact_id_as_verified(context, contact_id, Some(ContactId::SELF)).await?;
|
||||
|
||||
ChatId::set_protection_for_contact(context, contact_id, mime_message.timestamp_sent).await?;
|
||||
|
||||
if step == "vg-member-added" || step == "vc-contact-confirm" {
|
||||
let is_group = mime_message
|
||||
.get_header(HeaderDef::ChatGroupMemberAdded)
|
||||
@@ -592,28 +608,6 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
||||
}
|
||||
}
|
||||
|
||||
async fn secure_connection_established(
|
||||
context: &Context,
|
||||
contact_id: ContactId,
|
||||
chat_id: ChatId,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
let private_chat_id = ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Yes)
|
||||
.await?
|
||||
.id;
|
||||
private_chat_id
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
timestamp,
|
||||
Some(contact_id),
|
||||
)
|
||||
.await?;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Tools: Misc.
|
||||
******************************************************************************/
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::{Context as _, Result};
|
||||
|
||||
use super::HandshakeMessage;
|
||||
use super::qrinvite::QrInvite;
|
||||
use crate::chat::{self, ChatId, ProtectionStatus, is_contact_in_chat};
|
||||
use crate::chat::{self, ChatId, is_contact_in_chat};
|
||||
use crate::constants::{Blocked, Chattype};
|
||||
use crate::contact::Origin;
|
||||
use crate::context::Context;
|
||||
@@ -74,16 +74,6 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
||||
send_handshake_message(context, &invite, chat_id, BobHandshakeMsg::RequestWithAuth)
|
||||
.await?;
|
||||
|
||||
// Mark 1:1 chat as verified already.
|
||||
chat_id
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
time(),
|
||||
Some(invite.contact_id()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::SecurejoinJoinerProgress {
|
||||
contact_id: invite.contact_id(),
|
||||
progress: JoinerProgress::RequestWithAuthSent.to_usize(),
|
||||
@@ -123,21 +113,19 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
||||
let ts_sort = chat_id
|
||||
.calc_sort_timestamp(context, 0, sort_to_bottom, received, incoming)
|
||||
.await?;
|
||||
if chat_id.is_protected(context).await? == ProtectionStatus::Unprotected {
|
||||
let ts_start = time();
|
||||
chat::add_info_msg_with_cmd(
|
||||
context,
|
||||
chat_id,
|
||||
&stock_str::securejoin_wait(context).await,
|
||||
SystemMessage::SecurejoinWait,
|
||||
ts_sort,
|
||||
Some(ts_start),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let ts_start = time();
|
||||
chat::add_info_msg_with_cmd(
|
||||
context,
|
||||
chat_id,
|
||||
&stock_str::securejoin_wait(context).await,
|
||||
SystemMessage::SecurejoinWait,
|
||||
ts_sort,
|
||||
Some(ts_start),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(chat_id)
|
||||
}
|
||||
}
|
||||
@@ -215,15 +203,6 @@ pub(super) async fn handle_auth_required(
|
||||
}
|
||||
}
|
||||
|
||||
chat_id
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
message.timestamp_sent,
|
||||
Some(invite.contact_id()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::SecurejoinJoinerProgress {
|
||||
contact_id: invite.contact_id(),
|
||||
progress: JoinerProgress::RequestWithAuthSent.to_usize(),
|
||||
@@ -348,7 +327,7 @@ async fn joining_chat_id(
|
||||
QrInvite::Contact { .. } => Ok(alice_chat_id),
|
||||
QrInvite::Group { grpid, name, .. } => {
|
||||
let group_chat_id = match chat::get_chat_id_by_grpid(context, grpid).await? {
|
||||
Some((chat_id, _protected, _blocked)) => {
|
||||
Some((chat_id, _blocked)) => {
|
||||
chat_id.unblock_ex(context, Nosync).await?;
|
||||
chat_id
|
||||
}
|
||||
@@ -359,7 +338,6 @@ async fn joining_chat_id(
|
||||
grpid,
|
||||
name,
|
||||
Blocked::Not,
|
||||
ProtectionStatus::Unprotected, // protection is added later as needed
|
||||
None,
|
||||
create_smeared_timestamp(context),
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_contact_tools::EmailAddress;
|
||||
|
||||
use super::*;
|
||||
@@ -5,19 +7,17 @@ use crate::chat::{CantSendReason, remove_contact_from_chat};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::constants::Chattype;
|
||||
use crate::key::self_fingerprint;
|
||||
use crate::mimeparser::GossipedKey;
|
||||
use crate::mimeparser::{GossipedKey, SystemMessage};
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::stock_str::{self, messages_e2e_encrypted};
|
||||
use crate::test_utils::{
|
||||
TestContext, TestContextManager, TimeShiftFalsePositiveNote, get_chat_msg,
|
||||
};
|
||||
use crate::tools::SystemTime;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum SetupContactCase {
|
||||
Normal,
|
||||
CheckProtectionTimestamp,
|
||||
WrongAliceGossip,
|
||||
AliceIsBot,
|
||||
AliceHasName,
|
||||
@@ -28,11 +28,6 @@ async fn test_setup_contact() {
|
||||
test_setup_contact_ex(SetupContactCase::Normal).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_setup_contact_protection_timestamp() {
|
||||
test_setup_contact_ex(SetupContactCase::CheckProtectionTimestamp).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_setup_contact_wrong_alice_gossip() {
|
||||
test_setup_contact_ex(SetupContactCase::WrongAliceGossip).await
|
||||
@@ -164,10 +159,6 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
||||
assert!(sent.payload.contains("Auto-Submitted: auto-replied"));
|
||||
assert!(!sent.payload.contains("Bob Examplenet"));
|
||||
let mut msg = alice.parse_msg(&sent).await;
|
||||
let vc_request_with_auth_ts_sent = msg
|
||||
.get_header(HeaderDef::Date)
|
||||
.and_then(|value| mailparse::dateparse(value).ok())
|
||||
.unwrap();
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
@@ -214,10 +205,6 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
||||
assert_eq!(contact_bob.is_verified(&alice).await.unwrap(), false);
|
||||
assert_eq!(contact_bob.get_authname(), "");
|
||||
|
||||
if case == SetupContactCase::CheckProtectionTimestamp {
|
||||
SystemTime::shift(Duration::from_secs(3600));
|
||||
}
|
||||
|
||||
tcm.section("Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm");
|
||||
alice.recv_msg_trash(&sent).await;
|
||||
assert_eq!(contact_bob.is_verified(&alice).await.unwrap(), true);
|
||||
@@ -247,9 +234,6 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
||||
assert!(msg.is_info());
|
||||
let expected_text = messages_e2e_encrypted(&alice).await;
|
||||
assert_eq!(msg.get_text(), expected_text);
|
||||
if case == SetupContactCase::CheckProtectionTimestamp {
|
||||
assert_eq!(msg.timestamp_sort, vc_request_with_auth_ts_sent + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure Alice hasn't yet sent their name to Bob.
|
||||
@@ -292,10 +276,10 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
||||
let mut i = 0..msg_cnt;
|
||||
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
|
||||
assert!(msg.is_info());
|
||||
assert_eq!(msg.get_text(), stock_str::securejoin_wait(&bob).await);
|
||||
assert_eq!(msg.get_text(), messages_e2e_encrypted(&bob).await);
|
||||
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
|
||||
assert!(msg.is_info());
|
||||
assert_eq!(msg.get_text(), messages_e2e_encrypted(&bob).await);
|
||||
assert_eq!(msg.get_text(), stock_str::securejoin_wait(&bob).await);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -448,8 +432,7 @@ async fn test_secure_join() -> Result<()> {
|
||||
assert_eq!(Chatlist::try_load(&alice, 0, None, None).await?.len(), 0);
|
||||
assert_eq!(Chatlist::try_load(&bob, 0, None, None).await?.len(), 0);
|
||||
|
||||
let alice_chatid =
|
||||
chat::create_group_chat(&alice, ProtectionStatus::Protected, "the chat").await?;
|
||||
let alice_chatid = chat::create_group(&alice, "the chat").await?;
|
||||
|
||||
tcm.section("Step 1: Generate QR-code, secure-join implied by chatid");
|
||||
let qr = get_securejoin_qr(&alice, Some(alice_chatid)).await.unwrap();
|
||||
@@ -583,7 +566,7 @@ async fn test_secure_join() -> Result<()> {
|
||||
Blocked::Yes,
|
||||
"Alice's 1:1 chat with Bob is not hidden"
|
||||
);
|
||||
// There should be 3 messages in the chat:
|
||||
// There should be 2 messages in the chat:
|
||||
// - The ChatProtectionEnabled message
|
||||
// - You added member bob@example.net
|
||||
let msg = get_chat_msg(&alice, alice_chatid, 0, 2).await;
|
||||
@@ -619,7 +602,6 @@ async fn test_secure_join() -> Result<()> {
|
||||
}
|
||||
|
||||
let bob_chat = Chat::load_from_db(&bob.ctx, bob_chatid).await?;
|
||||
assert!(bob_chat.is_protected());
|
||||
assert!(bob_chat.typ == Chattype::Group);
|
||||
|
||||
// On this "happy path", Alice and Bob get only a group-chat where all information are added to.
|
||||
@@ -667,7 +649,7 @@ async fn test_unknown_sender() -> Result<()> {
|
||||
tcm.execute_securejoin(&alice, &bob).await;
|
||||
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group with Bob", &[&bob])
|
||||
.create_group_with_members("Group with Bob", &[&bob])
|
||||
.await;
|
||||
|
||||
let sent = alice.send_text(alice_chat_id, "Hi!").await;
|
||||
@@ -733,10 +715,8 @@ async fn test_parallel_securejoin() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_chat1_id =
|
||||
chat::create_group_chat(alice, ProtectionStatus::Protected, "First chat").await?;
|
||||
let alice_chat2_id =
|
||||
chat::create_group_chat(alice, ProtectionStatus::Protected, "Second chat").await?;
|
||||
let alice_chat1_id = chat::create_group(alice, "First chat").await?;
|
||||
let alice_chat2_id = chat::create_group(alice, "Second chat").await?;
|
||||
|
||||
let qr1 = get_securejoin_qr(alice, Some(alice_chat1_id)).await?;
|
||||
let qr2 = get_securejoin_qr(alice, Some(alice_chat2_id)).await?;
|
||||
@@ -862,3 +842,120 @@ async fn test_wrong_auth_token() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that scanning a QR code week later
|
||||
/// allows Bob to establish a contact with Alice,
|
||||
/// but does not mark Bob as verified for Alice.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_expired_contact_auth_token() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
// Alice creates a QR code.
|
||||
let qr = get_securejoin_qr(alice, None).await?;
|
||||
|
||||
// One week passes, QR code expires.
|
||||
SystemTime::shift(Duration::from_secs(7 * 24 * 3600));
|
||||
|
||||
// Bob scans the QR code.
|
||||
join_securejoin(bob, &qr).await?;
|
||||
|
||||
// vc-request
|
||||
alice.recv_msg_trash(&bob.pop_sent_msg().await).await;
|
||||
|
||||
// vc-auth-requried
|
||||
bob.recv_msg_trash(&alice.pop_sent_msg().await).await;
|
||||
|
||||
// vc-request-with-auth
|
||||
alice.recv_msg_trash(&bob.pop_sent_msg().await).await;
|
||||
|
||||
// Bob should not be verified for Alice.
|
||||
let contact_bob = alice.add_or_lookup_contact_no_key(bob).await;
|
||||
assert_eq!(contact_bob.is_verified(alice).await.unwrap(), false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_expired_group_auth_token() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_chat_id = chat::create_group(alice, "Group").await?;
|
||||
|
||||
// Alice creates a group QR code.
|
||||
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap();
|
||||
|
||||
// One week passes, QR code expires.
|
||||
SystemTime::shift(Duration::from_secs(7 * 24 * 3600));
|
||||
|
||||
// Bob scans the QR code.
|
||||
join_securejoin(bob, &qr).await?;
|
||||
|
||||
// vg-request
|
||||
alice.recv_msg_trash(&bob.pop_sent_msg().await).await;
|
||||
|
||||
// vg-auth-requried
|
||||
bob.recv_msg_trash(&alice.pop_sent_msg().await).await;
|
||||
|
||||
// vg-request-with-auth
|
||||
alice.recv_msg_trash(&bob.pop_sent_msg().await).await;
|
||||
|
||||
// vg-member-added
|
||||
let bob_member_added_msg = bob.recv_msg(&alice.pop_sent_msg().await).await;
|
||||
assert!(bob_member_added_msg.is_info());
|
||||
assert_eq!(
|
||||
bob_member_added_msg.get_info_type(),
|
||||
SystemMessage::MemberAddedToGroup
|
||||
);
|
||||
|
||||
// Bob should not be verified for Alice.
|
||||
let contact_bob = alice.add_or_lookup_contact_no_key(bob).await;
|
||||
assert_eq!(contact_bob.is_verified(alice).await.unwrap(), false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that old token is considered expired
|
||||
/// even if sync message just arrived.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_expired_synced_auth_token() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let alice2 = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
alice.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
alice2.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
|
||||
// Alice creates a QR code on the second device.
|
||||
let qr = get_securejoin_qr(alice2, None).await?;
|
||||
|
||||
alice2.send_sync_msg().await.unwrap();
|
||||
let sync_msg = alice2.pop_sent_sync_msg().await;
|
||||
|
||||
// One week passes, QR code expires.
|
||||
SystemTime::shift(Duration::from_secs(7 * 24 * 3600));
|
||||
|
||||
alice.recv_msg_trash(&sync_msg).await;
|
||||
|
||||
// Bob scans the QR code.
|
||||
join_securejoin(bob, &qr).await?;
|
||||
|
||||
// vc-request
|
||||
alice.recv_msg_trash(&bob.pop_sent_msg().await).await;
|
||||
|
||||
// vc-auth-requried
|
||||
bob.recv_msg_trash(&alice.pop_sent_msg().await).await;
|
||||
|
||||
// vc-request-with-auth
|
||||
alice.recv_msg_trash(&bob.pop_sent_msg().await).await;
|
||||
|
||||
// Bob should not be verified for Alice.
|
||||
let contact_bob = alice.add_or_lookup_contact_no_key(bob).await;
|
||||
assert_eq!(contact_bob.is_verified(alice).await.unwrap(), false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::key::DcKey;
|
||||
use crate::log::{info, warn};
|
||||
use crate::login_param::ConfiguredLoginParam;
|
||||
use crate::message::MsgId;
|
||||
use crate::provider::get_provider_by_domain;
|
||||
use crate::provider::get_provider_info;
|
||||
use crate::sql::Sql;
|
||||
use crate::tools::{Time, inc_and_check, time_elapsed};
|
||||
|
||||
@@ -382,7 +382,7 @@ UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
|
||||
context
|
||||
.set_config_internal(
|
||||
Config::ConfiguredProvider,
|
||||
get_provider_by_domain(&domain).map(|provider| provider.id),
|
||||
get_provider_info(&domain).map(|provider| provider.id),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
@@ -1261,6 +1261,16 @@ CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
|
||||
.await?;
|
||||
}
|
||||
|
||||
inc_and_check(&mut migration_version, 134)?;
|
||||
if dbversion < migration_version {
|
||||
// Reset all indirect verifications.
|
||||
sql.execute_migration(
|
||||
"UPDATE contacts SET verifier=0 WHERE verifier!=1",
|
||||
migration_version,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
|
||||
@@ -122,7 +122,7 @@ async fn test_key_contacts_migration_email2() -> Result<()> {
|
||||
.await?
|
||||
.is_empty()
|
||||
);
|
||||
let pgp_bob = Contact::get_by_id(&t, ContactId::new(11)).await?;
|
||||
let pgp_bob = Contact::get_by_id(&t, ContactId::new(11001)).await?;
|
||||
assert_eq!(pgp_bob.is_key_contact(), true);
|
||||
assert_eq!(pgp_bob.origin, Origin::Hidden);
|
||||
|
||||
@@ -159,7 +159,10 @@ async fn test_key_contacts_migration_verified() -> Result<()> {
|
||||
INSERT INTO chats_contacts VALUES(10,10,1745609547,0);
|
||||
"#,
|
||||
)?)).await?;
|
||||
t.sql.run_migrations(&t).await?;
|
||||
|
||||
STOP_MIGRATIONS_AT
|
||||
.scope(133, t.sql.run_migrations(&t))
|
||||
.await?;
|
||||
|
||||
// Hidden address-contact can't be looked up.
|
||||
assert!(
|
||||
|
||||
@@ -11,7 +11,7 @@ use tokio::sync::RwLock;
|
||||
|
||||
use crate::accounts::Accounts;
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::{self, Chat, ChatId, ProtectionStatus};
|
||||
use crate::chat::{self, Chat, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::contact::{Contact, ContactId, Origin};
|
||||
use crate::context::Context;
|
||||
@@ -1083,13 +1083,6 @@ pub(crate) async fn messages_e2e_encrypted(context: &Context) -> String {
|
||||
translated(context, StockMessage::ChatProtectionEnabled).await
|
||||
}
|
||||
|
||||
/// Stock string: `%1$s sent a message from another device.`
|
||||
pub(crate) async fn chat_protection_disabled(context: &Context, contact_id: ContactId) -> String {
|
||||
translated(context, StockMessage::ChatProtectionDisabled)
|
||||
.await
|
||||
.replace1(&contact_id.get_stock_name(context).await)
|
||||
}
|
||||
|
||||
/// Stock string: `Reply`.
|
||||
pub(crate) async fn reply_noun(context: &Context) -> String {
|
||||
translated(context, StockMessage::ReplyNoun).await
|
||||
@@ -1334,26 +1327,6 @@ impl Context {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a stock message saying that protection status has changed.
|
||||
pub(crate) async fn stock_protection_msg(
|
||||
&self,
|
||||
protect: ProtectionStatus,
|
||||
contact_id: Option<ContactId>,
|
||||
) -> String {
|
||||
match protect {
|
||||
ProtectionStatus::Unprotected => {
|
||||
if let Some(contact_id) = contact_id {
|
||||
chat_protection_disabled(self, contact_id).await
|
||||
} else {
|
||||
// In a group chat, it's not possible to downgrade verification.
|
||||
// In a 1:1 chat, the `contact_id` always has to be provided.
|
||||
"[Error] No contact_id given".to_string()
|
||||
}
|
||||
}
|
||||
ProtectionStatus::Protected => messages_e2e_encrypted(self).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn update_device_chats(&self) -> Result<()> {
|
||||
if self.get_config_bool(Config::Bot).await? {
|
||||
return Ok(());
|
||||
|
||||
39
src/sync.rs
39
src/sync.rs
@@ -253,13 +253,19 @@ impl Context {
|
||||
/// If an error is returned, the caller shall not try over because some sync items could be
|
||||
/// already executed. Sync items are considered independent and executed in the given order but
|
||||
/// regardless of whether executing of the previous items succeeded.
|
||||
pub(crate) async fn execute_sync_items(&self, items: &SyncItems) {
|
||||
pub(crate) async fn execute_sync_items(&self, items: &SyncItems, timestamp_sent: i64) {
|
||||
info!(self, "executing {} sync item(s)", items.items.len());
|
||||
for item in &items.items {
|
||||
// Limit the timestamp to ensure it is not in the future.
|
||||
//
|
||||
// `sent_timestamp` should be already corrected
|
||||
// if the `Date` header is in the future.
|
||||
let timestamp = std::cmp::min(item.timestamp, timestamp_sent);
|
||||
|
||||
match &item.data {
|
||||
SyncDataOrUnknown::SyncData(data) => match data {
|
||||
AddQrToken(token) => self.add_qr_token(token).await,
|
||||
DeleteQrToken(token) => self.delete_qr_token(token).await,
|
||||
AddQrToken(token) => self.add_qr_token(token, timestamp).await,
|
||||
DeleteQrToken(token) => self.delete_qr_token(token, timestamp).await,
|
||||
AlterChat { id, action } => self.sync_alter_chat(id, action).await,
|
||||
SyncData::Config { key, val } => self.sync_config(key, val).await,
|
||||
SyncData::SaveMessage { src, dest } => self.save_message(src, dest).await,
|
||||
@@ -284,21 +290,28 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_qr_token(&self, token: &QrTokenData) -> Result<()> {
|
||||
async fn add_qr_token(&self, token: &QrTokenData, timestamp: i64) -> Result<()> {
|
||||
let grpid = token.grpid.as_deref();
|
||||
token::save(self, Namespace::InviteNumber, grpid, &token.invitenumber).await?;
|
||||
token::save(self, Namespace::Auth, grpid, &token.auth).await?;
|
||||
token::save(
|
||||
self,
|
||||
Namespace::InviteNumber,
|
||||
grpid,
|
||||
&token.invitenumber,
|
||||
timestamp,
|
||||
)
|
||||
.await?;
|
||||
token::save(self, Namespace::Auth, grpid, &token.auth, timestamp).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_qr_token(&self, token: &QrTokenData) -> Result<()> {
|
||||
async fn delete_qr_token(&self, token: &QrTokenData, timestamp: i64) -> Result<()> {
|
||||
self.sql
|
||||
.execute(
|
||||
"DELETE FROM tokens
|
||||
WHERE foreign_key IN
|
||||
(SELECT foreign_key FROM tokens
|
||||
WHERE token=? OR token=?)",
|
||||
(&token.invitenumber, &token.auth),
|
||||
WHERE token=? OR token=? AND timestamp <= ?)",
|
||||
(&token.invitenumber, &token.auth, timestamp),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -339,7 +352,7 @@ mod tests {
|
||||
use anyhow::bail;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{Chat, ProtectionStatus, remove_contact_from_chat};
|
||||
use crate::chat::{Chat, remove_contact_from_chat};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::securejoin::get_securejoin_qr;
|
||||
@@ -550,6 +563,7 @@ mod tests {
|
||||
|
||||
assert!(!token::exists(&t, Namespace::Auth, "yip-auth").await?);
|
||||
|
||||
let timestamp_sent = time();
|
||||
let sync_items = t
|
||||
.parse_sync_items(
|
||||
r#"{"items":[
|
||||
@@ -564,7 +578,7 @@ mod tests {
|
||||
.to_string(),
|
||||
)
|
||||
?;
|
||||
t.execute_sync_items(&sync_items).await;
|
||||
t.execute_sync_items(&sync_items, timestamp_sent).await;
|
||||
|
||||
assert!(
|
||||
Contact::lookup_id_by_addr(&t, "bob@example.net", Origin::Unknown)
|
||||
@@ -714,8 +728,7 @@ mod tests {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
let alice_chatid =
|
||||
chat::create_group_chat(alice, ProtectionStatus::Protected, "the chat").await?;
|
||||
let alice_chatid = chat::create_group(alice, "the chat").await?;
|
||||
let qr = get_securejoin_qr(alice, Some(alice_chatid)).await?;
|
||||
|
||||
// alice2 syncs the QR code token.
|
||||
|
||||
@@ -21,8 +21,7 @@ use tokio::runtime::Handle;
|
||||
use tokio::{fs, task};
|
||||
|
||||
use crate::chat::{
|
||||
self, Chat, ChatId, ChatIdBlocked, MessageListOptions, ProtectionStatus,
|
||||
add_to_chat_contacts_table, create_group_chat,
|
||||
self, Chat, ChatId, ChatIdBlocked, MessageListOptions, add_to_chat_contacts_table, create_group,
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
@@ -84,6 +83,7 @@ impl TestContextManager {
|
||||
pub async fn alice(&mut self) -> TestContext {
|
||||
TestContext::builder()
|
||||
.configure_alice()
|
||||
.with_id_offset(1000)
|
||||
.with_log_sink(self.log_sink.clone())
|
||||
.build(Some(&mut self.used_names))
|
||||
.await
|
||||
@@ -92,6 +92,7 @@ impl TestContextManager {
|
||||
pub async fn bob(&mut self) -> TestContext {
|
||||
TestContext::builder()
|
||||
.configure_bob()
|
||||
.with_id_offset(2000)
|
||||
.with_log_sink(self.log_sink.clone())
|
||||
.build(Some(&mut self.used_names))
|
||||
.await
|
||||
@@ -100,6 +101,7 @@ impl TestContextManager {
|
||||
pub async fn charlie(&mut self) -> TestContext {
|
||||
TestContext::builder()
|
||||
.configure_charlie()
|
||||
.with_id_offset(3000)
|
||||
.with_log_sink(self.log_sink.clone())
|
||||
.build(Some(&mut self.used_names))
|
||||
.await
|
||||
@@ -108,6 +110,7 @@ impl TestContextManager {
|
||||
pub async fn dom(&mut self) -> TestContext {
|
||||
TestContext::builder()
|
||||
.configure_dom()
|
||||
.with_id_offset(4000)
|
||||
.with_log_sink(self.log_sink.clone())
|
||||
.build(Some(&mut self.used_names))
|
||||
.await
|
||||
@@ -116,6 +119,7 @@ impl TestContextManager {
|
||||
pub async fn elena(&mut self) -> TestContext {
|
||||
TestContext::builder()
|
||||
.configure_elena()
|
||||
.with_id_offset(5000)
|
||||
.with_log_sink(self.log_sink.clone())
|
||||
.build(Some(&mut self.used_names))
|
||||
.await
|
||||
@@ -124,6 +128,7 @@ impl TestContextManager {
|
||||
pub async fn fiona(&mut self) -> TestContext {
|
||||
TestContext::builder()
|
||||
.configure_fiona()
|
||||
.with_id_offset(6000)
|
||||
.with_log_sink(self.log_sink.clone())
|
||||
.build(Some(&mut self.used_names))
|
||||
.await
|
||||
@@ -263,6 +268,11 @@ pub struct TestContextBuilder {
|
||||
/// so the caller should store the LogSink elsewhere to
|
||||
/// prevent it from being dropped immediately.
|
||||
log_sink: Option<LogSink>,
|
||||
|
||||
/// Offset for chat-,message-,contact ids.
|
||||
///
|
||||
/// This makes tests fail where ids from different accounts were mixed up.
|
||||
id_offset: Option<u32>,
|
||||
}
|
||||
|
||||
impl TestContextBuilder {
|
||||
@@ -328,6 +338,14 @@ impl TestContextBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an offset for chat-, message-, contact IDs.
|
||||
///
|
||||
/// This makes it harder to accidentally mix up IDs from different accounts.
|
||||
pub fn with_id_offset(mut self, offset: u32) -> Self {
|
||||
self.id_offset = Some(offset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the [`TestContext`].
|
||||
pub async fn build(self, used_names: Option<&mut BTreeSet<String>>) -> TestContext {
|
||||
if let Some(key_pair) = self.key_pair {
|
||||
@@ -360,6 +378,22 @@ impl TestContextBuilder {
|
||||
key::store_self_keypair(&test_context, &key_pair)
|
||||
.await
|
||||
.expect("Failed to save key");
|
||||
|
||||
if let Some(offset) = self.id_offset {
|
||||
test_context
|
||||
.ctx
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE sqlite_sequence SET seq = ?
|
||||
WHERE name = 'contacts'
|
||||
OR name = 'chats'
|
||||
OR name = 'msgs'",
|
||||
(offset,),
|
||||
)
|
||||
.await
|
||||
.expect("Failed set id offset");
|
||||
}
|
||||
|
||||
test_context
|
||||
} else {
|
||||
TestContext::new_internal(None, self.log_sink).await
|
||||
@@ -409,21 +443,33 @@ impl TestContext {
|
||||
///
|
||||
/// This is a shortcut which configures alice@example.org with a fixed key.
|
||||
pub async fn new_alice() -> Self {
|
||||
Self::builder().configure_alice().build(None).await
|
||||
Self::builder()
|
||||
.configure_alice()
|
||||
.with_id_offset(11000)
|
||||
.build(None)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a new configured [`TestContext`].
|
||||
///
|
||||
/// This is a shortcut which configures bob@example.net with a fixed key.
|
||||
pub async fn new_bob() -> Self {
|
||||
Self::builder().configure_bob().build(None).await
|
||||
Self::builder()
|
||||
.configure_bob()
|
||||
.with_id_offset(12000)
|
||||
.build(None)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a new configured [`TestContext`].
|
||||
///
|
||||
/// This is a shortcut which configures fiona@example.net with a fixed key.
|
||||
pub async fn new_fiona() -> Self {
|
||||
Self::builder().configure_fiona().build(None).await
|
||||
Self::builder()
|
||||
.configure_fiona()
|
||||
.with_id_offset(13000)
|
||||
.build(None)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Print current chat state.
|
||||
@@ -1013,7 +1059,7 @@ impl TestContext {
|
||||
};
|
||||
writeln!(
|
||||
res,
|
||||
"{}#{}: {} [{}]{}{}{} {}",
|
||||
"{}#{}: {} [{}]{}{}{}",
|
||||
sel_chat.typ,
|
||||
sel_chat.get_id(),
|
||||
sel_chat.get_name(),
|
||||
@@ -1031,11 +1077,6 @@ impl TestContext {
|
||||
},
|
||||
_ => "".to_string(),
|
||||
},
|
||||
if sel_chat.is_protected() {
|
||||
"🛡️"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1066,11 +1107,10 @@ impl TestContext {
|
||||
|
||||
pub async fn create_group_with_members(
|
||||
&self,
|
||||
protect: ProtectionStatus,
|
||||
chat_name: &str,
|
||||
members: &[&TestContext],
|
||||
) -> ChatId {
|
||||
let chat_id = create_group_chat(self, protect, chat_name).await.unwrap();
|
||||
let chat_id = create_group(self, chat_name).await.unwrap();
|
||||
let mut to_add = vec![];
|
||||
for member in members {
|
||||
let contact_id = self.add_or_lookup_contact_id(member).await;
|
||||
@@ -1630,4 +1670,32 @@ mod tests {
|
||||
let runtime = tokio::runtime::Runtime::new().expect("unable to create tokio runtime");
|
||||
runtime.block_on(TestContext::new());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_id_offset() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let fiona = tcm.fiona().await;
|
||||
|
||||
// chat ids
|
||||
let alice_bob_chat = alice.create_chat(&bob).await;
|
||||
let bob_alice_chat = bob.create_chat(&alice).await;
|
||||
assert_ne!(alice_bob_chat.id, bob_alice_chat.id);
|
||||
|
||||
// contact ids
|
||||
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(&fiona).await;
|
||||
let fiona_fiona_contact_id = bob.add_or_lookup_contact_id(&fiona).await;
|
||||
assert_ne!(alice_fiona_contact_id, fiona_fiona_contact_id);
|
||||
|
||||
// message ids
|
||||
let alice_group_id = alice
|
||||
.create_group_with_members("test group", &[&bob, &fiona])
|
||||
.await;
|
||||
let alice_sent_msg = alice.send_text(alice_group_id, "testing").await;
|
||||
let bob_received_id = bob.recv_msg(&alice_sent_msg).await;
|
||||
assert_ne!(alice_sent_msg.sender_msg_id, bob_received_id.id);
|
||||
let fiona_received_id = fiona.recv_msg(&alice_sent_msg).await;
|
||||
assert_ne!(bob_received_id.id, fiona_received_id.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::chat::{self, Chat, ChatId, ProtectionStatus};
|
||||
use crate::chat::{self, Chat, ChatId};
|
||||
use crate::contact::{Contact, ContactId};
|
||||
use crate::message::Message;
|
||||
use crate::receive_imf::receive_imf;
|
||||
@@ -90,24 +90,12 @@ async fn check_aeap_transition(chat_for_transition: ChatForTransition, verified:
|
||||
}
|
||||
|
||||
let mut groups = vec![
|
||||
chat::create_group_chat(bob, chat::ProtectionStatus::Unprotected, "Group 0")
|
||||
.await
|
||||
.unwrap(),
|
||||
chat::create_group_chat(bob, chat::ProtectionStatus::Unprotected, "Group 1")
|
||||
.await
|
||||
.unwrap(),
|
||||
chat::create_group(bob, "Group 0").await.unwrap(),
|
||||
chat::create_group(bob, "Group 1").await.unwrap(),
|
||||
];
|
||||
if verified {
|
||||
groups.push(
|
||||
chat::create_group_chat(bob, chat::ProtectionStatus::Protected, "Group 2")
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
groups.push(
|
||||
chat::create_group_chat(bob, chat::ProtectionStatus::Protected, "Group 3")
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
groups.push(chat::create_group(bob, "Group 2").await.unwrap());
|
||||
groups.push(chat::create_group(bob, "Group 3").await.unwrap());
|
||||
}
|
||||
|
||||
let alice_contact = bob.add_or_lookup_contact_id(alice).await;
|
||||
@@ -201,8 +189,7 @@ async fn test_aeap_replay_attack() -> Result<()> {
|
||||
tcm.send_recv_accept(&alice, &bob, "Hi").await;
|
||||
tcm.send_recv(&bob, &alice, "Hi back").await;
|
||||
|
||||
let group =
|
||||
chat::create_group_chat(&bob, chat::ProtectionStatus::Unprotected, "Group 0").await?;
|
||||
let group = chat::create_group(&bob, "Group 0").await?;
|
||||
|
||||
let bob_alice_contact = bob.add_or_lookup_contact_id(&alice).await;
|
||||
let bob_fiona_contact = bob.add_or_lookup_contact_id(&fiona).await;
|
||||
@@ -217,10 +204,12 @@ async fn test_aeap_replay_attack() -> Result<()> {
|
||||
// Fiona gets the message, replaces the From addr...
|
||||
let sent = sent
|
||||
.payload()
|
||||
.replace("From: <alice@example.org>", "From: <fiona@example.net>")
|
||||
.replace("addr=alice@example.org;", "addr=fiona@example.net;");
|
||||
.replace("From: <alice@example.org>", "From: <fiona@example.net>");
|
||||
sent.find("From: <fiona@example.net>").unwrap(); // Assert that it worked
|
||||
sent.find("addr=fiona@example.net;").unwrap(); // Assert that it worked
|
||||
|
||||
// Autocrypt header is protected, nothing to replace outside.
|
||||
// In the signed part we cannot replace it without breaking the signature.
|
||||
assert!(!sent.contains("addr=alice@example.org;"));
|
||||
|
||||
tcm.section("Fiona replaced the From addr and forwards the message to Bob");
|
||||
receive_imf(&bob, sent.as_bytes(), false).await?.unwrap();
|
||||
@@ -243,16 +232,13 @@ async fn test_write_to_alice_after_aeap() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_grp_id = chat::create_group_chat(alice, ProtectionStatus::Protected, "Group").await?;
|
||||
let alice_grp_id = chat::create_group(alice, "Group").await?;
|
||||
let qr = get_securejoin_qr(alice, Some(alice_grp_id)).await?;
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
let bob_alice_contact = bob.add_or_lookup_contact(alice).await;
|
||||
assert!(bob_alice_contact.is_verified(bob).await?);
|
||||
let bob_alice_chat = bob.create_chat(alice).await;
|
||||
assert!(bob_alice_chat.is_protected());
|
||||
let bob_unprotected_grp_id = bob
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "Group", &[alice])
|
||||
.await;
|
||||
let bob_unprotected_grp_id = bob.create_group_with_members("Group", &[alice]).await;
|
||||
|
||||
tcm.change_addr(alice, "alice@someotherdomain.xyz").await;
|
||||
let sent = alice.send_text(alice_grp_id, "Hello!").await;
|
||||
@@ -260,7 +246,6 @@ async fn test_write_to_alice_after_aeap() -> Result<()> {
|
||||
|
||||
assert!(bob_alice_contact.is_verified(bob).await?);
|
||||
let bob_alice_chat = Chat::load_from_db(bob, bob_alice_chat.id).await?;
|
||||
assert!(bob_alice_chat.is_protected());
|
||||
let mut msg = Message::new_text("hi".to_string());
|
||||
chat::send_msg(bob, bob_alice_chat.id, &mut msg).await?;
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@ use anyhow::Result;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::chat::resend_msgs;
|
||||
use crate::chat::{
|
||||
self, Chat, ProtectionStatus, add_contact_to_chat, remove_contact_from_chat, send_msg,
|
||||
};
|
||||
use crate::chat::{self, Chat, add_contact_to_chat, remove_contact_from_chat, send_msg};
|
||||
use crate::config::Config;
|
||||
use crate::constants::Chattype;
|
||||
use crate::contact::{Contact, ContactId};
|
||||
@@ -38,8 +36,8 @@ async fn check_verified_oneonone_chat_protection_not_broken(by_classical_email:
|
||||
|
||||
tcm.execute_securejoin(&alice, &bob).await;
|
||||
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&bob, &alice, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
assert_verified(&bob, &alice).await;
|
||||
|
||||
if by_classical_email {
|
||||
tcm.section("Bob uses a classical MUA to send a message to Alice");
|
||||
@@ -59,7 +57,7 @@ async fn check_verified_oneonone_chat_protection_not_broken(by_classical_email:
|
||||
.unwrap();
|
||||
let contact = alice.add_or_lookup_contact(&bob).await;
|
||||
assert_eq!(contact.is_verified(&alice).await.unwrap(), true);
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
} else {
|
||||
tcm.section("Bob sets up another Delta Chat device");
|
||||
let bob2 = tcm.unconfigured().await;
|
||||
@@ -71,7 +69,7 @@ async fn check_verified_oneonone_chat_protection_not_broken(by_classical_email:
|
||||
.await;
|
||||
let contact = alice.add_or_lookup_contact(&bob2).await;
|
||||
assert_eq!(contact.is_verified(&alice).await.unwrap(), false);
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
}
|
||||
|
||||
tcm.section("Bob sends another message from DC");
|
||||
@@ -79,7 +77,7 @@ async fn check_verified_oneonone_chat_protection_not_broken(by_classical_email:
|
||||
tcm.send_recv(&bob, &alice, "Using DC again").await;
|
||||
|
||||
// Bob's chat is marked as verified again
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -91,21 +89,17 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
|
||||
tcm.execute_securejoin(&alice, &bob).await;
|
||||
tcm.execute_securejoin(&bob, &fiona).await;
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&bob, &alice, ProtectionStatus::Protected).await;
|
||||
assert_verified(&bob, &fiona, ProtectionStatus::Protected).await;
|
||||
assert_verified(&fiona, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
assert_verified(&bob, &alice).await;
|
||||
assert_verified(&bob, &fiona).await;
|
||||
assert_verified(&fiona, &bob).await;
|
||||
|
||||
let group_id = bob
|
||||
.create_group_with_members(
|
||||
ProtectionStatus::Protected,
|
||||
"Group with everyone",
|
||||
&[&alice, &fiona],
|
||||
)
|
||||
.create_group_with_members("Group with everyone", &[&alice, &fiona])
|
||||
.await;
|
||||
assert_eq!(
|
||||
get_chat_msg(&bob, group_id, 0, 1).await.get_info_type(),
|
||||
SystemMessage::ChatProtectionEnabled
|
||||
SystemMessage::ChatE2ee
|
||||
);
|
||||
|
||||
{
|
||||
@@ -117,7 +111,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
get_chat_msg(&fiona, msg.chat_id, 0, 2)
|
||||
.await
|
||||
.get_info_type(),
|
||||
SystemMessage::ChatProtectionEnabled
|
||||
SystemMessage::ChatE2ee
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,26 +119,6 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
let alice_fiona_contact = alice.add_or_lookup_contact(&fiona).await;
|
||||
assert!(alice_fiona_contact.is_verified(&alice).await.unwrap(),);
|
||||
|
||||
// Alice should have a hidden protected chat with Fiona
|
||||
{
|
||||
let chat = alice.get_chat(&fiona).await;
|
||||
assert!(chat.is_protected());
|
||||
|
||||
let msg = get_chat_msg(&alice, chat.id, 0, 1).await;
|
||||
let expected_text = stock_str::messages_e2e_encrypted(&alice).await;
|
||||
assert_eq!(msg.text, expected_text);
|
||||
}
|
||||
|
||||
// Fiona should have a hidden protected chat with Alice
|
||||
{
|
||||
let chat = fiona.get_chat(&alice).await;
|
||||
assert!(chat.is_protected());
|
||||
|
||||
let msg0 = get_chat_msg(&fiona, chat.id, 0, 1).await;
|
||||
let expected_text = stock_str::messages_e2e_encrypted(&fiona).await;
|
||||
assert_eq!(msg0.text, expected_text);
|
||||
}
|
||||
|
||||
tcm.section("Fiona reinstalls DC");
|
||||
drop(fiona);
|
||||
|
||||
@@ -155,19 +129,12 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
tcm.send_recv(&fiona_new, &alice, "I have a new device")
|
||||
.await;
|
||||
|
||||
// Alice gets a new unprotected chat with new Fiona contact.
|
||||
// Alice gets a new chat with new Fiona contact.
|
||||
{
|
||||
let chat = alice.get_chat(&fiona_new).await;
|
||||
assert!(!chat.is_protected());
|
||||
|
||||
let msg = get_chat_msg(&alice, chat.id, 1, E2EE_INFO_MSGS + 1).await;
|
||||
assert_eq!(msg.text, "I have a new device");
|
||||
|
||||
// After recreating the chat, it should still be unprotected
|
||||
chat.id.delete(&alice).await?;
|
||||
|
||||
let chat = alice.create_chat(&fiona_new).await;
|
||||
assert!(!chat.is_protected());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -180,7 +147,7 @@ async fn test_missing_key_reexecute_securejoin() -> Result<()> {
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = tcm.execute_securejoin(bob, alice).await;
|
||||
let chat = Chat::load_from_db(bob, chat_id).await?;
|
||||
assert!(chat.is_protected());
|
||||
assert!(chat.can_send(bob).await?);
|
||||
bob.sql
|
||||
.execute(
|
||||
"DELETE FROM public_keys WHERE fingerprint=?",
|
||||
@@ -191,43 +158,12 @@ async fn test_missing_key_reexecute_securejoin() -> Result<()> {
|
||||
.hex(),),
|
||||
)
|
||||
.await?;
|
||||
let chat = Chat::load_from_db(bob, chat_id).await?;
|
||||
assert!(!chat.can_send(bob).await?);
|
||||
|
||||
let chat_id = tcm.execute_securejoin(bob, alice).await;
|
||||
let chat = Chat::load_from_db(bob, chat_id).await?;
|
||||
assert!(chat.is_protected());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_unverified_oneonone_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
// A chat with an unknown contact should be created unprotected
|
||||
let chat = alice.create_chat(&bob).await;
|
||||
assert!(!chat.is_protected());
|
||||
|
||||
receive_imf(
|
||||
&alice,
|
||||
b"From: Bob <bob@example.net>\n\
|
||||
To: alice@example.org\n\
|
||||
Message-ID: <1234-2@example.org>\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
chat.id.delete(&alice).await.unwrap();
|
||||
// Now Bob is a known contact, new chats should still be created unprotected
|
||||
let chat = alice.create_chat(&bob).await;
|
||||
assert!(!chat.is_protected());
|
||||
|
||||
tcm.send_recv(&bob, &alice, "hi").await;
|
||||
chat.id.delete(&alice).await.unwrap();
|
||||
// Now we have a public key, new chats should still be created unprotected
|
||||
let chat = alice.create_chat(&bob).await;
|
||||
assert!(!chat.is_protected());
|
||||
assert!(chat.can_send(bob).await?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -245,7 +181,6 @@ async fn test_degrade_verified_oneonone_chat() -> Result<()> {
|
||||
mark_as_verified(&alice, &bob).await;
|
||||
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
assert!(alice_chat.is_protected());
|
||||
|
||||
receive_imf(
|
||||
&alice,
|
||||
@@ -261,7 +196,7 @@ async fn test_degrade_verified_oneonone_chat() -> Result<()> {
|
||||
let msg0 = get_chat_msg(&alice, alice_chat.id, 0, 1).await;
|
||||
let enabled = stock_str::messages_e2e_encrypted(&alice).await;
|
||||
assert_eq!(msg0.text, enabled);
|
||||
assert_eq!(msg0.param.get_cmd(), SystemMessage::ChatProtectionEnabled);
|
||||
assert_eq!(msg0.param.get_cmd(), SystemMessage::ChatE2ee);
|
||||
|
||||
let email_chat = alice.get_email_chat(&bob).await;
|
||||
assert!(!email_chat.is_encrypted(&alice).await?);
|
||||
@@ -369,7 +304,7 @@ async fn test_mdn_doesnt_disable_verification() -> Result<()> {
|
||||
let body = rendered_msg.message;
|
||||
receive_imf(&alice, body.as_bytes(), false).await.unwrap();
|
||||
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -384,7 +319,7 @@ async fn test_outgoing_mua_msg() -> Result<()> {
|
||||
mark_as_verified(&bob, &alice).await;
|
||||
|
||||
tcm.send_recv_accept(&bob, &alice, "Heyho from DC").await;
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
|
||||
let sent = receive_imf(
|
||||
&alice,
|
||||
@@ -497,7 +432,7 @@ async fn test_message_from_old_dc_setup() -> Result<()> {
|
||||
mark_as_verified(bob, alice).await;
|
||||
|
||||
tcm.send_recv(bob, alice, "Now i have it!").await;
|
||||
assert_verified(alice, bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(alice, bob).await;
|
||||
|
||||
let msg = alice.recv_msg(&sent_old).await;
|
||||
assert!(msg.get_showpadlock());
|
||||
@@ -506,8 +441,6 @@ async fn test_message_from_old_dc_setup() -> Result<()> {
|
||||
// The outdated Bob's Autocrypt header isn't applied
|
||||
// and the message goes to another chat, so the verification preserves.
|
||||
assert!(contact.is_verified(alice).await.unwrap());
|
||||
let chat = alice.get_chat(bob).await;
|
||||
assert!(chat.is_protected());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -528,7 +461,7 @@ async fn test_verify_then_verify_again() -> Result<()> {
|
||||
mark_as_verified(&bob, &alice).await;
|
||||
|
||||
alice.create_chat(&bob).await;
|
||||
assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob).await;
|
||||
|
||||
tcm.section("Bob reinstalls DC");
|
||||
drop(bob);
|
||||
@@ -537,42 +470,39 @@ async fn test_verify_then_verify_again() -> Result<()> {
|
||||
e2ee::ensure_secret_key_exists(&bob_new).await?;
|
||||
|
||||
tcm.execute_securejoin(&bob_new, &alice).await;
|
||||
assert_verified(&alice, &bob_new, ProtectionStatus::Protected).await;
|
||||
assert_verified(&alice, &bob_new).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that on the second device of a protected group creator the first message is
|
||||
/// `SystemMessage::ChatProtectionEnabled` and the second one is the message populating the group.
|
||||
/// Tests that on the second device of a group creator the first message is
|
||||
/// `SystemMessage::ChatE2ee` and the second one is the message populating the group.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_protected_grp_multidev() -> Result<()> {
|
||||
async fn test_create_grp_multidev() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let alice1 = &tcm.alice().await;
|
||||
|
||||
let group_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group", &[])
|
||||
.await;
|
||||
let group_id = alice.create_group_with_members("Group", &[]).await;
|
||||
assert_eq!(
|
||||
get_chat_msg(alice, group_id, 0, 1).await.get_info_type(),
|
||||
SystemMessage::ChatProtectionEnabled
|
||||
SystemMessage::ChatE2ee
|
||||
);
|
||||
|
||||
let sent = alice.send_text(group_id, "Hey").await;
|
||||
// This time shift is necessary to reproduce the bug when the original message is sorted over
|
||||
// the "protection enabled" message so that these messages have different timestamps.
|
||||
// the "Messages are end-to-end encrypted" message so that these messages have different timestamps.
|
||||
SystemTime::shift(std::time::Duration::from_secs(3600));
|
||||
let msg = alice1.recv_msg(&sent).await;
|
||||
let group1 = Chat::load_from_db(alice1, msg.chat_id).await?;
|
||||
assert_eq!(group1.get_type(), Chattype::Group);
|
||||
assert!(group1.is_protected());
|
||||
assert_eq!(
|
||||
chat::get_chat_contacts(alice1, group1.id).await?,
|
||||
vec![ContactId::SELF]
|
||||
);
|
||||
assert_eq!(
|
||||
get_chat_msg(alice1, group1.id, 0, 2).await.get_info_type(),
|
||||
SystemMessage::ChatProtectionEnabled
|
||||
SystemMessage::ChatE2ee
|
||||
);
|
||||
assert_eq!(get_chat_msg(alice1, group1.id, 1, 2).await.id, msg.id);
|
||||
|
||||
@@ -592,10 +522,8 @@ async fn test_verified_member_added_reordering() -> Result<()> {
|
||||
tcm.execute_securejoin(bob, alice).await;
|
||||
tcm.execute_securejoin(fiona, alice).await;
|
||||
|
||||
// Alice creates protected group with Bob.
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob])
|
||||
.await;
|
||||
// Alice creates a group with Bob.
|
||||
let alice_chat_id = alice.create_group_with_members("Group", &[bob]).await;
|
||||
let alice_sent_group_promotion = alice.send_text(alice_chat_id, "I created a group").await;
|
||||
let msg = bob.recv_msg(&alice_sent_group_promotion).await;
|
||||
let bob_chat_id = msg.chat_id;
|
||||
@@ -614,15 +542,13 @@ async fn test_verified_member_added_reordering() -> Result<()> {
|
||||
// "Member added" message, so unverified group is created.
|
||||
let fiona_received_message = fiona.recv_msg(&bob_sent_message).await;
|
||||
let fiona_chat = Chat::load_from_db(fiona, fiona_received_message.chat_id).await?;
|
||||
assert!(!fiona_chat.can_send(fiona).await?);
|
||||
|
||||
assert_eq!(fiona_received_message.get_text(), "Hi");
|
||||
assert_eq!(fiona_chat.is_protected(), false);
|
||||
|
||||
// Fiona receives late "Member added" message
|
||||
// and the chat becomes protected.
|
||||
fiona.recv_msg(&alice_sent_member_added).await;
|
||||
let fiona_chat = Chat::load_from_db(fiona, fiona_received_message.chat_id).await?;
|
||||
assert_eq!(fiona_chat.is_protected(), true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -665,9 +591,7 @@ async fn test_verified_lost_member_added() -> Result<()> {
|
||||
tcm.execute_securejoin(bob, alice).await;
|
||||
tcm.execute_securejoin(fiona, alice).await;
|
||||
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob])
|
||||
.await;
|
||||
let alice_chat_id = alice.create_group_with_members("Group", &[bob]).await;
|
||||
let alice_sent = alice.send_text(alice_chat_id, "Hi!").await;
|
||||
let bob_chat_id = bob.recv_msg(&alice_sent).await.chat_id;
|
||||
assert_eq!(chat::get_chat_contacts(bob, bob_chat_id).await?.len(), 2);
|
||||
@@ -728,9 +652,7 @@ async fn test_verified_chat_editor_reordering() -> Result<()> {
|
||||
tcm.execute_securejoin(alice, bob).await;
|
||||
|
||||
tcm.section("Alice creates a protected group with Bob");
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob])
|
||||
.await;
|
||||
let alice_chat_id = alice.create_group_with_members("Group", &[bob]).await;
|
||||
let alice_sent = alice.send_text(alice_chat_id, "Hi!").await;
|
||||
let bob_chat_id = bob.recv_msg(&alice_sent).await.chat_id;
|
||||
|
||||
@@ -811,7 +733,7 @@ async fn test_no_reverification() -> Result<()> {
|
||||
|
||||
tcm.section("Alice creates a protected group with Bob, Charlie and Fiona");
|
||||
let alice_chat_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob, charlie, fiona])
|
||||
.create_group_with_members("Group", &[bob, charlie, fiona])
|
||||
.await;
|
||||
let alice_sent = alice.send_text(alice_chat_id, "Hi!").await;
|
||||
let bob_rcvd_msg = bob.recv_msg(&alice_sent).await;
|
||||
@@ -859,15 +781,47 @@ async fn test_no_reverification() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that if our second device observes
|
||||
/// us gossiping a verification,
|
||||
/// it is not treated as direct verification.
|
||||
///
|
||||
/// Direct verifications should only happen
|
||||
/// as a result of SecureJoin.
|
||||
/// If we see our second device gossiping
|
||||
/// a verification of some contact,
|
||||
/// it may be indirect verification,
|
||||
/// so we should mark the contact as verified,
|
||||
/// but with unknown verifier.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_direct_verification_via_bcc() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let alice2 = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
mark_as_verified(alice, bob).await;
|
||||
|
||||
let alice_chat_id = alice.create_chat_id(bob).await;
|
||||
let alice_sent_msg = alice.send_text(alice_chat_id, "Hello!").await;
|
||||
alice2.recv_msg(&alice_sent_msg).await;
|
||||
|
||||
// Alice 2 observes Alice 1 gossiping verification for Bob.
|
||||
// Alice 2 does not know if Alice 1 has verified Bob directly though.
|
||||
let alice2_bob_contact = alice2.add_or_lookup_contact(bob).await;
|
||||
assert_eq!(alice2_bob_contact.is_verified(alice2).await?, true);
|
||||
|
||||
// There is some verifier, but it is unknown to Alice's second device.
|
||||
assert_eq!(
|
||||
alice2_bob_contact.get_verifier_id(alice2).await?,
|
||||
Some(None)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ============== Helper Functions ==============
|
||||
|
||||
async fn assert_verified(this: &TestContext, other: &TestContext, protected: ProtectionStatus) {
|
||||
async fn assert_verified(this: &TestContext, other: &TestContext) {
|
||||
let contact = this.add_or_lookup_contact(other).await;
|
||||
assert_eq!(contact.is_verified(this).await.unwrap(), true);
|
||||
|
||||
let chat = this.get_chat(other).await;
|
||||
assert_eq!(
|
||||
chat.is_protected(),
|
||||
protected == ProtectionStatus::Protected
|
||||
);
|
||||
}
|
||||
|
||||
24
src/token.rs
24
src/token.rs
@@ -28,12 +28,13 @@ pub async fn save(
|
||||
namespace: Namespace,
|
||||
foreign_key: Option<&str>,
|
||||
token: &str,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO tokens (namespc, foreign_key, token, timestamp) VALUES (?, ?, ?, ?)",
|
||||
(namespace, foreign_key.unwrap_or(""), token, time()),
|
||||
(namespace, foreign_key.unwrap_or(""), token, timestamp),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -71,7 +72,8 @@ pub async fn lookup_or_new(
|
||||
}
|
||||
|
||||
let token = create_id();
|
||||
save(context, namespace, foreign_key, &token).await?;
|
||||
let timestamp = time();
|
||||
save(context, namespace, foreign_key, &token, timestamp).await?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
@@ -86,24 +88,6 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> Res
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Looks up foreign key by auth token.
|
||||
///
|
||||
/// Returns None if auth token is not valid.
|
||||
/// Returns an empty string if the token corresponds to "setup contact" rather than group join.
|
||||
pub async fn auth_foreign_key(context: &Context, token: &str) -> Result<Option<String>> {
|
||||
context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT foreign_key FROM tokens WHERE namespc=? AND token=?",
|
||||
(Namespace::Auth, token),
|
||||
|row| {
|
||||
let foreign_key: String = row.get(0)?;
|
||||
Ok(foreign_key)
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Resets all tokens corresponding to the `foreign_key`.
|
||||
///
|
||||
/// `foreign_key` is a group ID to reset all group tokens
|
||||
|
||||
@@ -169,7 +169,7 @@ pub(crate) async fn intercept_get_updates(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::chat::{ChatId, ProtectionStatus, create_group_chat};
|
||||
use crate::chat::{ChatId, create_group};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::contact::Contact;
|
||||
use crate::message::Message;
|
||||
@@ -231,7 +231,7 @@ mod tests {
|
||||
assert_eq!(msg.chat_id, bob_chat_id);
|
||||
|
||||
// Integrate Webxdc into another group
|
||||
let group_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let group_id = create_group(&t, "foo").await?;
|
||||
let integration_id = t.init_webxdc_integration(Some(group_id)).await?.unwrap();
|
||||
|
||||
let locations = location::get_range(&t, Some(group_id), None, 0, 0).await?;
|
||||
|
||||
@@ -5,8 +5,8 @@ use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{
|
||||
ChatId, ProtectionStatus, add_contact_to_chat, create_broadcast, create_group_chat,
|
||||
forward_msgs, remove_contact_from_chat, resend_msgs, send_msg, send_text_msg,
|
||||
ChatId, add_contact_to_chat, create_broadcast, create_group, forward_msgs,
|
||||
remove_contact_from_chat, resend_msgs, send_msg, send_text_msg,
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
@@ -78,7 +78,7 @@ async fn send_webxdc_instance(t: &TestContext, chat_id: ChatId) -> Result<Messag
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_send_webxdc_instance() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
|
||||
// send as .xdc file
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
@@ -97,7 +97,7 @@ async fn test_send_webxdc_instance() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_send_invalid_webxdc() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
|
||||
// sending invalid .xdc as file is possible, but must not result in Viewtype::Webxdc
|
||||
let mut instance = create_webxdc_instance(
|
||||
@@ -126,7 +126,7 @@ async fn test_send_invalid_webxdc() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_set_draft_invalid_webxdc() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
@@ -143,7 +143,7 @@ async fn test_set_draft_invalid_webxdc() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_send_special_webxdc_format() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
|
||||
// chess.xdc is failing for some zip-versions, see #3476, if we know more details about why, we can have a nicer name for the test :)
|
||||
let mut instance = create_webxdc_instance(
|
||||
@@ -164,7 +164,7 @@ async fn test_send_special_webxdc_format() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_forward_webxdc_instance() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
t.send_webxdc_status_update(
|
||||
instance.id,
|
||||
@@ -213,7 +213,7 @@ async fn test_resend_webxdc_instance_and_info() -> Result<()> {
|
||||
|
||||
// Alice uses webxdc in a group
|
||||
alice.set_config_bool(Config::BccSelf, false).await?;
|
||||
let alice_grp = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
let alice_grp = create_group(&alice, "grp").await?;
|
||||
let alice_instance = send_webxdc_instance(&alice, alice_grp).await?;
|
||||
assert_eq!(alice_grp.get_msg_cnt(&alice).await?, 2);
|
||||
alice
|
||||
@@ -395,7 +395,7 @@ async fn test_webxdc_update_for_not_downloaded_instance() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_webxdc_instance() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
let now = tools::time();
|
||||
t.receive_status_update(
|
||||
@@ -428,7 +428,7 @@ async fn test_delete_webxdc_instance() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_chat_with_webxdc() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
let now = tools::time();
|
||||
t.receive_status_update(
|
||||
@@ -461,7 +461,7 @@ async fn test_delete_chat_with_webxdc() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_webxdc_draft() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
@@ -498,7 +498,7 @@ async fn test_delete_webxdc_draft() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_status_update_record() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
|
||||
assert_eq!(
|
||||
@@ -633,7 +633,7 @@ async fn test_create_status_update_record() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_receive_status_update() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
let now = tools::time();
|
||||
|
||||
@@ -904,7 +904,7 @@ async fn test_send_big_webxdc_status_update() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_render_webxdc_status_update_object() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat").await?;
|
||||
let chat_id = create_group(&t, "a chat").await?;
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"minimal.xdc",
|
||||
@@ -932,7 +932,7 @@ async fn test_render_webxdc_status_update_object() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_render_webxdc_status_update_object_range() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat").await?;
|
||||
let chat_id = create_group(&t, "a chat").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
t.send_webxdc_status_update(instance.id, r#"{"payload": 1}"#)
|
||||
.await?;
|
||||
@@ -979,7 +979,7 @@ async fn test_render_webxdc_status_update_object_range() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_pop_status_update() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat").await?;
|
||||
let chat_id = create_group(&t, "a chat").await?;
|
||||
let instance1 = send_webxdc_instance(&t, chat_id).await?;
|
||||
let instance2 = send_webxdc_instance(&t, chat_id).await?;
|
||||
let instance3 = send_webxdc_instance(&t, chat_id).await?;
|
||||
@@ -1109,7 +1109,7 @@ async fn test_draft_and_send_webxdc_status_update() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_send_webxdc_status_update_to_non_webxdc() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let msg_id = send_text_msg(&t, chat_id, "ho!".to_string()).await?;
|
||||
assert!(
|
||||
t.send_webxdc_status_update(msg_id, r#"{"foo":"bar"}"#)
|
||||
@@ -1122,7 +1122,7 @@ async fn test_send_webxdc_status_update_to_non_webxdc() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_webxdc_blob() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
|
||||
let buf = instance.get_webxdc_blob(&t, "index.html").await?;
|
||||
@@ -1141,7 +1141,7 @@ async fn test_get_webxdc_blob() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_webxdc_blob_default_icon() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
|
||||
let buf = instance.get_webxdc_blob(&t, WEBXDC_DEFAULT_ICON).await?;
|
||||
@@ -1153,7 +1153,7 @@ async fn test_get_webxdc_blob_default_icon() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_webxdc_blob_with_absolute_paths() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
|
||||
let buf = instance.get_webxdc_blob(&t, "/index.html").await?;
|
||||
@@ -1166,7 +1166,7 @@ async fn test_get_webxdc_blob_with_absolute_paths() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_webxdc_blob_with_subdirs() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"some-files.xdc",
|
||||
@@ -1265,7 +1265,7 @@ async fn test_parse_webxdc_manifest_source_code_url() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_min_api_too_large() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
let chat_id = create_group(&t, "chat").await?;
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"with-min-api-1001.xdc",
|
||||
@@ -1283,7 +1283,7 @@ async fn test_webxdc_min_api_too_large() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_webxdc_info() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
let info = instance.get_webxdc_info(&t).await?;
|
||||
@@ -1363,7 +1363,7 @@ async fn test_get_webxdc_info() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_webxdc_self_addr() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&t, "foo").await?;
|
||||
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
let info1 = instance.get_webxdc_info(&t).await?;
|
||||
@@ -1601,7 +1601,7 @@ async fn test_webxdc_info_msg_cleanup_series() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_info_msg_no_cleanup_on_interrupted_series() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "c").await?;
|
||||
let chat_id = create_group(&t, "c").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
|
||||
t.send_webxdc_status_update(instance.id, r#"{"info":"i1", "payload":1}"#)
|
||||
@@ -1623,7 +1623,7 @@ async fn test_webxdc_no_internet_access() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let self_id = t.get_self_chat().await.id;
|
||||
let single_id = t.create_chat_with_contact("bob", "bob@e.com").await.id;
|
||||
let group_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
let group_id = create_group(&t, "chat").await?;
|
||||
let broadcast_id = create_broadcast(&t, "Channel".to_string()).await?;
|
||||
|
||||
for chat_id in [self_id, single_id, group_id, broadcast_id] {
|
||||
@@ -1655,7 +1655,7 @@ async fn test_webxdc_no_internet_access() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_chatlist_summary() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
let chat_id = create_group(&t, "chat").await?;
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"with-minimal-manifest.xdc",
|
||||
@@ -1727,7 +1727,7 @@ async fn test_webxdc_reject_updates_from_non_groupmembers() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let contact_bob = alice.add_or_lookup_contact_id(bob).await;
|
||||
let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let chat_id = create_group(alice, "Group").await?;
|
||||
add_contact_to_chat(alice, chat_id, contact_bob).await?;
|
||||
let instance = send_webxdc_instance(alice, chat_id).await?;
|
||||
bob.recv_msg(&alice.pop_sent_msg().await).await;
|
||||
@@ -1758,7 +1758,7 @@ async fn test_webxdc_reject_updates_from_non_groupmembers() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_delete_event() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let chat_id = create_group(&alice, "foo").await?;
|
||||
let instance = send_webxdc_instance(&alice, chat_id).await?;
|
||||
message::delete_msgs(&alice, &[instance.id]).await?;
|
||||
alice
|
||||
@@ -1912,7 +1912,7 @@ async fn test_webxdc_notify_one() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
|
||||
let grp_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona])
|
||||
.create_group_with_members("grp", &[&bob, &fiona])
|
||||
.await;
|
||||
let alice_instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
@@ -1958,7 +1958,7 @@ async fn test_webxdc_notify_multiple() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
|
||||
let grp_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona])
|
||||
.create_group_with_members("grp", &[&bob, &fiona])
|
||||
.await;
|
||||
let alice_instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
@@ -2001,9 +2001,7 @@ async fn test_webxdc_no_notify_self() -> Result<()> {
|
||||
let alice = tcm.alice().await;
|
||||
let alice2 = tcm.alice().await;
|
||||
|
||||
let grp_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[])
|
||||
.await;
|
||||
let grp_id = alice.create_group_with_members("grp", &[]).await;
|
||||
let alice_instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
let alice2_instance = alice2.recv_msg(&sent1).await;
|
||||
@@ -2043,7 +2041,7 @@ async fn test_webxdc_notify_all() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
|
||||
let grp_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona])
|
||||
.create_group_with_members("grp", &[&bob, &fiona])
|
||||
.await;
|
||||
let alice_instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
@@ -2083,7 +2081,7 @@ async fn test_webxdc_notify_bob_and_all() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
|
||||
let grp_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona])
|
||||
.create_group_with_members("grp", &[&bob, &fiona])
|
||||
.await;
|
||||
let alice_instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
@@ -2117,7 +2115,7 @@ async fn test_webxdc_notify_all_and_bob() -> Result<()> {
|
||||
let fiona = tcm.fiona().await;
|
||||
|
||||
let grp_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona])
|
||||
.create_group_with_members("grp", &[&bob, &fiona])
|
||||
.await;
|
||||
let alice_instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
@@ -2149,9 +2147,7 @@ async fn test_webxdc_href() -> Result<()> {
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let grp_id = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob])
|
||||
.await;
|
||||
let grp_id = alice.create_group_with_members("grp", &[&bob]).await;
|
||||
let instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
|
||||
@@ -2181,9 +2177,7 @@ async fn test_webxdc_href() -> Result<()> {
|
||||
async fn test_self_addr_consistency() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let alice_chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "No friends :(", &[])
|
||||
.await;
|
||||
let alice_chat = alice.create_group_with_members("No friends :(", &[]).await;
|
||||
let mut instance = create_webxdc_instance(
|
||||
alice,
|
||||
"minimal.xdc",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Group#Chat#10: Group chat [3 member(s)]
|
||||
Group#Chat#2001: Group chat [3 member(s)]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#11🔒: (Contact#Contact#10): Hi! I created a group. [FRESH]
|
||||
Msg#12🔒: Me (Contact#Contact#Self): You left the group. [INFO] √
|
||||
Msg#13🔒: (Contact#Contact#10): Member charlie@example.net added by alice@example.org. [FRESH][INFO]
|
||||
Msg#14🔒: (Contact#Contact#10): What a silence! [FRESH]
|
||||
Msg#2001: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#2002🔒: (Contact#Contact#2001): Hi! I created a group. [FRESH]
|
||||
Msg#2003🔒: Me (Contact#Contact#Self): You left the group. [INFO] √
|
||||
Msg#2004🔒: (Contact#Contact#2001): Member charlie@example.net added by alice@example.org. [FRESH][INFO]
|
||||
Msg#2005🔒: (Contact#Contact#2001): What a silence! [FRESH]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
Group#Chat#10: Group [5 member(s)]
|
||||
Group#Chat#1001: Group [5 member(s)]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#11🔒: Me (Contact#Contact#Self): populate √
|
||||
Msg#12: info (Contact#Contact#Info): Member dom@example.net added. [NOTICED][INFO]
|
||||
Msg#13: info (Contact#Contact#Info): Member fiona@example.net removed. [NOTICED][INFO]
|
||||
Msg#14🔒: (Contact#Contact#10): Member elena@example.net added by bob@example.net. [FRESH][INFO]
|
||||
Msg#15🔒: Me (Contact#Contact#Self): You added member fiona@example.net. [INFO] o
|
||||
Msg#16🔒: (Contact#Contact#10): Member fiona@example.net removed by bob@example.net. [FRESH][INFO]
|
||||
Msg#1001: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#1002🔒: Me (Contact#Contact#Self): populate √
|
||||
Msg#1003: info (Contact#Contact#Info): Member dom@example.net added. [NOTICED][INFO]
|
||||
Msg#1004: info (Contact#Contact#Info): Member fiona@example.net removed. [NOTICED][INFO]
|
||||
Msg#1005🔒: (Contact#Contact#1001): Member elena@example.net added by bob@example.net. [FRESH][INFO]
|
||||
Msg#1006🔒: Me (Contact#Contact#Self): You added member fiona@example.net. [INFO] o
|
||||
Msg#1007🔒: (Contact#Contact#1001): Member fiona@example.net removed by bob@example.net. [FRESH][INFO]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Single#Chat#10: bob@example.net [bob@example.net] Icon: 4138c52e5bc1c576cda7dd44d088c07.png
|
||||
Single#Chat#1001: bob@example.net [bob@example.net] Icon: 4138c52e5bc1c576cda7dd44d088c07.png
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: Me (Contact#Contact#Self): We share this account √
|
||||
Msg#11: Me (Contact#Contact#Self): I'm Alice too √
|
||||
Msg#1001: Me (Contact#Contact#Self): We share this account √
|
||||
Msg#1002: Me (Contact#Contact#Self): I'm Alice too √
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Single#Chat#10: Bob [bob@example.net] Icon: 4138c52e5bc1c576cda7dd44d088c07.png
|
||||
Single#Chat#11001: Bob [bob@example.net] Icon: 4138c52e5bc1c576cda7dd44d088c07.png
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: Me (Contact#Contact#Self): Happy birthday, Bob! √
|
||||
Msg#11: (Contact#Contact#10): Happy birthday to me, Alice! [FRESH]
|
||||
Msg#11001: Me (Contact#Contact#Self): Happy birthday, Bob! √
|
||||
Msg#11002: (Contact#Contact#11001): Happy birthday to me, Alice! [FRESH]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Single#Chat#10: bob@example.net [KEY bob@example.net] 🛡️
|
||||
Single#Chat#1001: bob@example.net [KEY bob@example.net]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO 🛡️]
|
||||
Msg#11🔒: Me (Contact#Contact#Self): Test – This is encrypted, signed, and has an Autocrypt Header without prefer-encrypt=mutual. √
|
||||
Msg#1001: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#1002🔒: Me (Contact#Contact#Self): Test – This is encrypted, signed, and has an Autocrypt Header without prefer-encrypt=mutual. √
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Single#Chat#11: bob@example.net [bob@example.net] Icon: 4138c52e5bc1c576cda7dd44d088c07.png
|
||||
Single#Chat#1002: bob@example.net [bob@example.net] Icon: 4138c52e5bc1c576cda7dd44d088c07.png
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#12: Me (Contact#Contact#Self): One classical MUA message √
|
||||
Msg#1003: Me (Contact#Contact#Self): One classical MUA message √
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Single#Chat#10: bob@example.net [KEY bob@example.net] 🛡️
|
||||
Single#Chat#1001: bob@example.net [KEY bob@example.net]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO 🛡️]
|
||||
Msg#11🔒: (Contact#Contact#10): Heyho from DC [FRESH]
|
||||
Msg#13🔒: Me (Contact#Contact#Self): Sending with DC again √
|
||||
Msg#1001: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#1002🔒: (Contact#Contact#1001): Heyho from DC [FRESH]
|
||||
Msg#1004🔒: Me (Contact#Contact#Self): Sending with DC again √
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Group#Chat#11: Group [3 member(s)] 🛡️
|
||||
Group#Chat#6002: Group [3 member(s)]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#11: info (Contact#Contact#Info): alice@example.org invited you to join this group.
|
||||
Msg#6004: info (Contact#Contact#Info): alice@example.org invited you to join this group.
|
||||
|
||||
Waiting for the device of alice@example.org to reply… [NOTICED][INFO]
|
||||
Msg#13: info (Contact#Contact#Info): alice@example.org replied, waiting for being added to the group… [NOTICED][INFO]
|
||||
Msg#17: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO 🛡️]
|
||||
Msg#18🔒: (Contact#Contact#10): Member Me added by alice@example.org. [FRESH][INFO]
|
||||
Msg#6006: info (Contact#Contact#Info): alice@example.org replied, waiting for being added to the group… [NOTICED][INFO]
|
||||
Msg#6003: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#6008🔒: (Contact#Contact#6001): Member Me added by alice@example.org. [FRESH][INFO]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Group#Chat#11: Group [3 member(s)] 🛡️
|
||||
Group#Chat#3002: Group [3 member(s)]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#11: info (Contact#Contact#Info): alice@example.org invited you to join this group.
|
||||
Msg#3004: info (Contact#Contact#Info): alice@example.org invited you to join this group.
|
||||
|
||||
Waiting for the device of alice@example.org to reply… [NOTICED][INFO]
|
||||
Msg#13: info (Contact#Contact#Info): alice@example.org replied, waiting for being added to the group… [NOTICED][INFO]
|
||||
Msg#16🔒: (Contact#Contact#11): [FRESH]
|
||||
Msg#18: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO 🛡️]
|
||||
Msg#19: info (Contact#Contact#Info): Member bob@example.net added. [NOTICED][INFO]
|
||||
Msg#20🔒: (Contact#Contact#10): Member Me added by alice@example.org. [FRESH][INFO]
|
||||
Msg#3006: info (Contact#Contact#Info): alice@example.org replied, waiting for being added to the group… [NOTICED][INFO]
|
||||
Msg#3003: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#3008🔒: (Contact#Contact#3002): [FRESH]
|
||||
Msg#3009: info (Contact#Contact#Info): Member bob@example.net added. [NOTICED][INFO]
|
||||
Msg#3010🔒: (Contact#Contact#3001): Member Me added by alice@example.org. [FRESH][INFO]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -34,88 +34,112 @@ Content-Transfer-Encoding: 7bit
|
||||
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
|
||||
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
|
||||
=a+ak
|
||||
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
|
||||
9itRo7h+e2Dv7NI9SbkWo+E=
|
||||
=OL4B
|
||||
-----END PGP MESSAGE-----
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user