mirror of
https://github.com/chatmail/core.git
synced 2026-07-04 14:35:08 +03:00
Compare commits
1 Commits
1.70.0
...
e2ee-force
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd81ecdb5d |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,19 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.70.0
|
||||
|
||||
### Fixes
|
||||
- fix: do not abort Param parsing on unknown keys #2856
|
||||
- fix: execute `Chat-Group-Member-Removed:` even when arriving disordered #2857
|
||||
|
||||
|
||||
## 1.69.0
|
||||
|
||||
### Fixes
|
||||
- fix group-related system messages in multi-device setups #2848
|
||||
- fix "Google Workspace" (former "G Suite") issues related to bad resolvers #2852
|
||||
|
||||
|
||||
## 1.68.0
|
||||
|
||||
### Fixes
|
||||
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1056,7 +1056,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.70.0"
|
||||
version = "1.68.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1136,7 +1136,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.70.0"
|
||||
version = "1.68.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.70.0"
|
||||
version = "1.68.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.70.0"
|
||||
version = "1.68.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -287,6 +287,8 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* To save traffic, however, the avatar is attached only as needed
|
||||
* and also recoded to a reasonable size.
|
||||
* - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
|
||||
* - `e2ee_force` = 1=ignore encryption preferences of others,
|
||||
* 0=use majority vote when deciding whether to encrypt (default).
|
||||
* - `mdns_enabled` = 0=do not send or request read receipts,
|
||||
* 1=send and request read receipts (default)
|
||||
* - `bcc_self` = 0=do not send a copy of outgoing messages to self (default),
|
||||
|
||||
@@ -3753,11 +3753,7 @@ pub unsafe extern "C" fn dc_provider_new_from_email(
|
||||
|
||||
match socks5_enabled {
|
||||
Ok(socks5_enabled) => {
|
||||
match block_on(provider::get_provider_info(
|
||||
ctx,
|
||||
addr.as_str(),
|
||||
socks5_enabled,
|
||||
)) {
|
||||
match block_on(provider::get_provider_info(addr.as_str(), socks5_enabled)) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
|
||||
@@ -1185,7 +1185,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let socks5_enabled = context
|
||||
.get_config_bool(config::Config::Socks5Enabled)
|
||||
.await?;
|
||||
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
|
||||
match provider::get_provider_info(arg1, socks5_enabled).await {
|
||||
Some(info) => {
|
||||
println!("Information for provider belonging to {}:", arg1);
|
||||
println!("status: {}", info.status as u32);
|
||||
|
||||
141
src/chat.rs
141
src/chat.rs
@@ -3225,147 +3225,6 @@ mod tests {
|
||||
assert_eq!(added, false);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_modify_chat_multi_device() -> Result<()> {
|
||||
let a1 = TestContext::new_alice().await;
|
||||
let a2 = TestContext::new_alice().await;
|
||||
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?;
|
||||
send_text_msg(&a1, a1_chat_id, "ho!".to_string()).await?;
|
||||
let a1_msg = a1.get_last_msg().await;
|
||||
let a1_chat = Chat::load_from_db(&a1, a1_chat_id).await?;
|
||||
|
||||
a2.recv_msg(&a1.pop_sent_msg().await).await;
|
||||
let a2_msg = a2.get_last_msg().await;
|
||||
let a2_chat_id = a2_msg.chat_id;
|
||||
let a2_chat = Chat::load_from_db(&a2, a2_chat_id).await?;
|
||||
|
||||
assert!(!a1_msg.is_system_message());
|
||||
assert!(!a2_msg.is_system_message());
|
||||
assert_eq!(a1_chat.grpid, a2_chat.grpid);
|
||||
assert_eq!(a1_chat.name, "foo");
|
||||
assert_eq!(a2_chat.name, "foo");
|
||||
assert_eq!(a1_chat.get_profile_image(&a1).await?, None);
|
||||
assert_eq!(a2_chat.get_profile_image(&a2).await?, None);
|
||||
assert_eq!(get_chat_contacts(&a1, a1_chat_id).await?.len(), 1);
|
||||
assert_eq!(get_chat_contacts(&a2, a2_chat_id).await?.len(), 1);
|
||||
|
||||
// add a member to the group
|
||||
let bob = Contact::create(&a1, "", "bob@example.org").await?;
|
||||
add_contact_to_chat(&a1, a1_chat_id, bob).await?;
|
||||
let a1_msg = a1.get_last_msg().await;
|
||||
|
||||
a2.recv_msg(&a1.pop_sent_msg().await).await;
|
||||
let a2_msg = a2.get_last_msg().await;
|
||||
|
||||
assert!(a1_msg.is_system_message());
|
||||
assert!(a2_msg.is_system_message());
|
||||
assert_eq!(a1_msg.get_info_type(), SystemMessage::MemberAddedToGroup);
|
||||
assert_eq!(a2_msg.get_info_type(), SystemMessage::MemberAddedToGroup);
|
||||
assert_eq!(get_chat_contacts(&a1, a1_chat_id).await?.len(), 2);
|
||||
assert_eq!(get_chat_contacts(&a2, a2_chat_id).await?.len(), 2);
|
||||
|
||||
// rename the group
|
||||
set_chat_name(&a1, a1_chat_id, "bar").await?;
|
||||
let a1_msg = a1.get_last_msg().await;
|
||||
|
||||
a2.recv_msg(&a1.pop_sent_msg().await).await;
|
||||
let a2_msg = a2.get_last_msg().await;
|
||||
|
||||
assert!(a1_msg.is_system_message());
|
||||
assert!(a2_msg.is_system_message());
|
||||
assert_eq!(a1_msg.get_info_type(), SystemMessage::GroupNameChanged);
|
||||
assert_eq!(a2_msg.get_info_type(), SystemMessage::GroupNameChanged);
|
||||
assert_eq!(Chat::load_from_db(&a1, a1_chat_id).await?.name, "bar");
|
||||
assert_eq!(Chat::load_from_db(&a2, a2_chat_id).await?.name, "bar");
|
||||
|
||||
// remove member from group
|
||||
remove_contact_from_chat(&a1, a1_chat_id, bob).await?;
|
||||
let a1_msg = a1.get_last_msg().await;
|
||||
|
||||
a2.recv_msg(&a1.pop_sent_msg().await).await;
|
||||
let a2_msg = a2.get_last_msg().await;
|
||||
|
||||
assert!(a1_msg.is_system_message());
|
||||
assert!(a2_msg.is_system_message());
|
||||
assert_eq!(
|
||||
a1_msg.get_info_type(),
|
||||
SystemMessage::MemberRemovedFromGroup
|
||||
);
|
||||
assert_eq!(
|
||||
a2_msg.get_info_type(),
|
||||
SystemMessage::MemberRemovedFromGroup
|
||||
);
|
||||
assert_eq!(get_chat_contacts(&a1, a1_chat_id).await?.len(), 1);
|
||||
assert_eq!(get_chat_contacts(&a2, a2_chat_id).await?.len(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_modify_chat_disordered() -> Result<()> {
|
||||
// Alice creates a group with Bob, Claire and Daisy and then removes Claire and Daisy
|
||||
// (sleep() is needed as otherwise smeared time from Alice looks to Bob like messages from the future which are all set to "now" then)
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let bob_id = Contact::create(&alice, "", "bob@example.net").await?;
|
||||
let claire_id = Contact::create(&alice, "", "claire@foo.de").await?;
|
||||
let daisy_id = Contact::create(&alice, "", "daisy@bar.de").await?;
|
||||
|
||||
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
|
||||
|
||||
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
let add1 = alice.pop_sent_msg().await;
|
||||
|
||||
add_contact_to_chat(&alice, alice_chat_id, claire_id).await?;
|
||||
let add2 = alice.pop_sent_msg().await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
add_contact_to_chat(&alice, alice_chat_id, daisy_id).await?;
|
||||
let add3 = alice.pop_sent_msg().await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 4);
|
||||
|
||||
remove_contact_from_chat(&alice, alice_chat_id, claire_id).await?;
|
||||
let remove1 = alice.pop_sent_msg().await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
remove_contact_from_chat(&alice, alice_chat_id, daisy_id).await?;
|
||||
let remove2 = alice.pop_sent_msg().await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 2);
|
||||
|
||||
// Bob receives the add and deletion messages out of order
|
||||
let bob = TestContext::new_bob().await;
|
||||
bob.recv_msg(&add1).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
bob.recv_msg(&add3).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
bob.recv_msg(&add2).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
let bob_chat_id = bob.get_last_msg().await.chat_id;
|
||||
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 4);
|
||||
|
||||
bob.recv_msg(&remove2).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
bob.recv_msg(&remove1).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_add_remove_contact_for_single() {
|
||||
let ctx = TestContext::new_alice().await;
|
||||
|
||||
@@ -64,6 +64,13 @@ pub enum Config {
|
||||
#[strum(props(default = "1"))]
|
||||
E2eeEnabled,
|
||||
|
||||
/// Ignore Autocrypt recommendation for message encryption if possible.
|
||||
///
|
||||
/// The only expection is when recommendation is "disable", i.e. encryption is not possible
|
||||
/// because some recipient has no OpenPGP key.
|
||||
#[strum(props(default = "0"))]
|
||||
E2eeForce,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
|
||||
|
||||
@@ -221,9 +221,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
"checking internal provider-info for offline autoconfig"
|
||||
);
|
||||
|
||||
if let Some(provider) =
|
||||
provider::get_provider_info(ctx, ¶m_domain, socks5_enabled).await
|
||||
{
|
||||
if let Some(provider) = provider::get_provider_info(¶m_domain, socks5_enabled).await {
|
||||
param.provider = Some(provider);
|
||||
match provider.status {
|
||||
provider::Status::Ok | provider::Status::Preparation => {
|
||||
|
||||
@@ -307,6 +307,7 @@ impl Context {
|
||||
.await?
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
|
||||
let e2ee_force = self.get_config_int(Config::E2eeForce).await?;
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
|
||||
let bcc_self = self.get_config_int(Config::BccSelf).await?;
|
||||
let send_sync_msgs = self.get_config_int(Config::SendSyncMsgs).await?;
|
||||
@@ -394,6 +395,7 @@ impl Context {
|
||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
||||
res.insert("mdns_enabled", mdns_enabled.to_string());
|
||||
res.insert("e2ee_enabled", e2ee_enabled.to_string());
|
||||
res.insert("e2ee_force", e2ee_force.to_string());
|
||||
res.insert(
|
||||
"key_gen_type",
|
||||
self.get_config_int(Config::KeyGenType).await?.to_string(),
|
||||
|
||||
@@ -825,18 +825,6 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
apply_group_changes(
|
||||
context,
|
||||
mime_parser,
|
||||
sent_timestamp,
|
||||
chat_id,
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if chat_id.is_none() && self_sent {
|
||||
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
|
||||
// maybe an Autocrypt Setup Message
|
||||
@@ -1706,14 +1694,13 @@ async fn apply_group_changes(
|
||||
send_event_chat_modified = true;
|
||||
}
|
||||
} else if let Some(contact_id) = removed_id {
|
||||
// in contrast to `Chat-Group-Member-Added` we do not reconstruct the whole state from To: on removing -
|
||||
// otherwise out-of-order removals won't work as members are re-added quickly.
|
||||
//
|
||||
// therefore, we need to ignore `MemberListTimestamp`
|
||||
// and execute `Chat-Group-Member-Removed` statements even when they arrive out of order.
|
||||
// we do not set `MemberListTimestamp` as we do not reconstuct the memberlist.
|
||||
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
|
||||
send_event_chat_modified = true;
|
||||
if chat_id
|
||||
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
|
||||
.await?
|
||||
{
|
||||
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
|
||||
send_event_chat_modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if send_event_chat_modified {
|
||||
|
||||
82
src/e2ee.rs
82
src/e2ee.rs
@@ -19,6 +19,7 @@ use crate::pgp;
|
||||
#[derive(Debug)]
|
||||
pub struct EncryptHelper {
|
||||
pub prefer_encrypt: EncryptPreference,
|
||||
force_preference: bool,
|
||||
pub addr: String,
|
||||
pub public_key: SignedPublicKey,
|
||||
}
|
||||
@@ -28,6 +29,7 @@ impl EncryptHelper {
|
||||
let prefer_encrypt =
|
||||
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await?)
|
||||
.unwrap_or_default();
|
||||
let force_preference = context.get_config_bool(Config::E2eeForce).await?;
|
||||
let addr = match context.get_config(Config::ConfiguredAddr).await? {
|
||||
None => {
|
||||
bail!("addr not configured!");
|
||||
@@ -39,6 +41,7 @@ impl EncryptHelper {
|
||||
|
||||
Ok(EncryptHelper {
|
||||
prefer_encrypt,
|
||||
force_preference,
|
||||
addr,
|
||||
public_key,
|
||||
})
|
||||
@@ -100,11 +103,17 @@ impl EncryptHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// Count number of recipients, including self.
|
||||
// This does not depend on whether we send a copy to self or not.
|
||||
let recipients_count = peerstates.len() + 1;
|
||||
let want_encrypt = if self.force_preference {
|
||||
// Ignore preferences of others.
|
||||
self.prefer_encrypt == EncryptPreference::Mutual
|
||||
} else {
|
||||
// Count number of recipients, including self.
|
||||
// This does not depend on whether we send a copy to self or not.
|
||||
let recipients_count = peerstates.len() + 1;
|
||||
2 * prefer_encrypt_count > recipients_count
|
||||
};
|
||||
|
||||
Ok(e2ee_guaranteed || 2 * prefer_encrypt_count > recipients_count)
|
||||
Ok(e2ee_guaranteed || want_encrypt)
|
||||
}
|
||||
|
||||
/// Tries to encrypt the passed in `mail`.
|
||||
@@ -381,6 +390,7 @@ mod tests {
|
||||
|
||||
use crate::chat;
|
||||
use crate::constants::Viewtype;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::message::Message;
|
||||
use crate::param::Param;
|
||||
use crate::peerstate::ToSave;
|
||||
@@ -602,4 +612,68 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_e2ee_force() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
let bob_chat = bob.create_chat(&alice).await;
|
||||
|
||||
alice.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
bob.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
// Alice does not prefer encryption.
|
||||
alice.set_config(Config::E2eeEnabled, Some("0")).await?;
|
||||
bob.set_config(Config::E2eeEnabled, Some("1")).await?;
|
||||
|
||||
// Alice sends her key to Bob.
|
||||
let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let received_msg = bob.get_last_msg().await;
|
||||
assert!(!received_msg.get_showpadlock());
|
||||
|
||||
// Bob should not encrypt, because Alice does not prefer encryption.
|
||||
let sent_msg = bob
|
||||
.send_text(bob_chat.id, "This should not be encrypted")
|
||||
.await;
|
||||
alice.recv_msg(&sent_msg).await;
|
||||
let received_msg = alice.get_last_msg().await;
|
||||
assert!(!received_msg.get_showpadlock());
|
||||
|
||||
// Bob ignores Alice's preference for no encryption.
|
||||
bob.set_config(Config::E2eeForce, Some("1")).await?;
|
||||
let sent_msg = bob.send_text(bob_chat.id, "This should be encrypted").await;
|
||||
alice.recv_msg(&sent_msg).await;
|
||||
let received_msg = alice.get_last_msg().await;
|
||||
assert!(received_msg.get_showpadlock());
|
||||
|
||||
// Alice switches to MUA without Autocrypt support.
|
||||
dc_receive_imf(
|
||||
&bob,
|
||||
br#"Subject: Hello from MUA
|
||||
Message-ID: foobar@example.com
|
||||
To: Bob <bob@example.net>
|
||||
From: Alice <alice@example.com>
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Date: Sun, 14 Mar 2500 00:00:00 +0000
|
||||
|
||||
Hello from MUA."#,
|
||||
"INBOX",
|
||||
100,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Bob can't encrypt now because Alice has no key.
|
||||
let sent_msg = bob
|
||||
.send_text(bob_chat.id, "This should not be encrypted again")
|
||||
.await;
|
||||
alice.recv_msg(&sent_msg).await;
|
||||
let received_msg = alice.get_last_msg().await;
|
||||
assert!(!received_msg.get_showpadlock());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ pub async fn dc_get_oauth2_url(
|
||||
redirect_uri: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
|
||||
@@ -79,7 +79,7 @@ pub async fn dc_get_oauth2_access_token(
|
||||
regenerate: bool,
|
||||
) -> Result<Option<String>> {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
|
||||
let lock = context.oauth2_mutex.lock().await;
|
||||
|
||||
// read generated token
|
||||
@@ -225,7 +225,7 @@ pub async fn dc_get_oauth2_addr(
|
||||
code: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
let oauth2 = match Oauth2::from_address(context, addr, socks5_enabled).await {
|
||||
let oauth2 = match Oauth2::from_address(addr, socks5_enabled).await {
|
||||
Some(o) => o,
|
||||
None => return Ok(None),
|
||||
};
|
||||
@@ -253,13 +253,13 @@ pub async fn dc_get_oauth2_addr(
|
||||
}
|
||||
|
||||
impl Oauth2 {
|
||||
async fn from_address(context: &Context, addr: &str, skip_mx: bool) -> Option<Self> {
|
||||
async fn from_address(addr: &str, skip_mx: bool) -> Option<Self> {
|
||||
let addr_normalized = normalize_addr(addr);
|
||||
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)
|
||||
if let Some(oauth2_authorizer) = provider::get_provider_info(domain, skip_mx)
|
||||
.await
|
||||
.and_then(|provider| provider.oauth2_authorizer.as_ref())
|
||||
{
|
||||
@@ -356,39 +356,32 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_oauth_from_address() {
|
||||
let t = TestContext::new().await;
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@gmail.com", false).await,
|
||||
Oauth2::from_address("hello@gmail.com", false).await,
|
||||
Some(OAUTH2_GMAIL)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@googlemail.com", false).await,
|
||||
Oauth2::from_address("hello@googlemail.com", false).await,
|
||||
Some(OAUTH2_GMAIL)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@yandex.com", false).await,
|
||||
Oauth2::from_address("hello@yandex.com", false).await,
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@yandex.ru", false).await,
|
||||
Oauth2::from_address("hello@yandex.ru", false).await,
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
assert_eq!(Oauth2::from_address(&t, "hello@web.de", false).await, None);
|
||||
|
||||
assert_eq!(Oauth2::from_address("hello@web.de", false).await, None);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_oauth_from_mx() {
|
||||
// youtube staff seems to use "google workspace with oauth2", figures this out by MX lookup
|
||||
let t = TestContext::new().await;
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@youtube.com", false).await,
|
||||
Oauth2::from_address("hello@google.com", false).await,
|
||||
Some(OAUTH2_GMAIL)
|
||||
);
|
||||
// without MX lookup, we would not know as youtube.com is not in our provider-db
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@youtube.com", true).await,
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
|
||||
19
src/param.rs
19
src/param.rs
@@ -191,11 +191,6 @@ impl fmt::Display for Params {
|
||||
impl str::FromStr for Params {
|
||||
type Err = Error;
|
||||
|
||||
/// Parse a raw string to Param.
|
||||
///
|
||||
/// Silently ignore unknown keys:
|
||||
/// they may come from a downgrade (when a shortly new version adds a key)
|
||||
/// or from an upgrade (when a key is dropped but was used in the past)
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut inner = BTreeMap::new();
|
||||
let mut lines = s.lines().peekable();
|
||||
@@ -215,6 +210,8 @@ impl str::FromStr for Params {
|
||||
|
||||
if let Some(key) = key.as_bytes().first().and_then(|key| Param::from_u8(*key)) {
|
||||
inner.insert(key, value);
|
||||
} else {
|
||||
bail!("Unknown key: {}", key);
|
||||
}
|
||||
} else {
|
||||
bail!("Not a key-value pair: {:?}", line);
|
||||
@@ -417,12 +414,10 @@ impl<'a> ParamsFile<'a> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use anyhow::Result;
|
||||
use async_std::fs;
|
||||
use async_std::path::Path;
|
||||
|
||||
use crate::test_utils::TestContext;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_dc_param() {
|
||||
@@ -525,14 +520,4 @@ mod tests {
|
||||
assert!(p.get_path(Param::File, &t).unwrap().is_none());
|
||||
assert!(p.get_blob(Param::File, &t, false).await.unwrap().is_none());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_params_unknown_key() -> Result<()> {
|
||||
// 'Z' is used as a key that is known to be unused; these keys should be ignored silently by definition.
|
||||
let p = Params::from_str("w=12\nZ=13\nh=14")?;
|
||||
assert_eq!(p.len(), 2);
|
||||
assert_eq!(p.get(Param::Width), Some("12"));
|
||||
assert_eq!(p.get(Param::Height), Some("14"));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
mod data;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS, PROVIDER_UPDATED};
|
||||
use anyhow::Result;
|
||||
use async_std_resolver::{config, resolver, resolver_from_system_conf, AsyncStdResolver};
|
||||
use async_std_resolver::resolver_from_system_conf;
|
||||
use chrono::{NaiveDateTime, NaiveTime};
|
||||
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
@@ -83,23 +81,6 @@ pub struct Provider {
|
||||
pub oauth2_authorizer: Option<Oauth2Authorizer>,
|
||||
}
|
||||
|
||||
/// Get resolver to query MX records.
|
||||
///
|
||||
/// We first try resolver_from_system_conf() which reads the system's resolver from `/etc/resolv.conf`.
|
||||
/// This does not work at least on some Androids, therefore we use use ResolverConfig::default()
|
||||
/// which default eg. to google's 8.8.8.8 or 8.8.4.4 as a fallback.
|
||||
async fn get_resolver() -> Result<AsyncStdResolver> {
|
||||
if let Ok(resolver) = resolver_from_system_conf().await {
|
||||
return Ok(resolver);
|
||||
}
|
||||
let resolver = resolver(
|
||||
config::ResolverConfig::default(),
|
||||
config::ResolverOpts::default(),
|
||||
)
|
||||
.await?;
|
||||
Ok(resolver)
|
||||
}
|
||||
|
||||
/// Returns provider for the given domain.
|
||||
///
|
||||
/// This function looks up domain in offline database first. If not
|
||||
@@ -108,11 +89,7 @@ async fn get_resolver() -> Result<AsyncStdResolver> {
|
||||
///
|
||||
/// For compatibility, email address can be passed to this function
|
||||
/// instead of the domain.
|
||||
pub async fn get_provider_info(
|
||||
context: &Context,
|
||||
domain: &str,
|
||||
skip_mx: bool,
|
||||
) -> Option<&'static Provider> {
|
||||
pub async fn get_provider_info(domain: &str, skip_mx: bool) -> Option<&'static Provider> {
|
||||
let domain = domain.rsplitn(2, '@').next()?;
|
||||
|
||||
if let Some(provider) = get_provider_by_domain(domain) {
|
||||
@@ -120,7 +97,7 @@ pub async fn get_provider_info(
|
||||
}
|
||||
|
||||
if !skip_mx {
|
||||
if let Some(provider) = get_provider_by_mx(context, domain).await {
|
||||
if let Some(provider) = get_provider_by_mx(domain).await {
|
||||
return Some(provider);
|
||||
}
|
||||
}
|
||||
@@ -140,8 +117,8 @@ pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
|
||||
/// 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> {
|
||||
if let Ok(resolver) = get_resolver().await {
|
||||
pub async fn get_provider_by_mx(domain: &str) -> Option<&'static Provider> {
|
||||
if let Ok(resolver) = resolver_from_system_conf().await {
|
||||
let mut fqdn: String = domain.to_string();
|
||||
if !fqdn.ends_with('.') {
|
||||
fqdn.push('.');
|
||||
@@ -166,8 +143,6 @@ pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'sta
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(context, "cannot get a resolver to check MX records.");
|
||||
}
|
||||
|
||||
None
|
||||
@@ -194,7 +169,6 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::dc_tools::time;
|
||||
use crate::test_utils::TestContext;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
#[test]
|
||||
@@ -244,13 +218,12 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
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("", false).await.is_none());
|
||||
assert!(get_provider_info("google.com", false).await.unwrap().id == "gmail");
|
||||
|
||||
// get_provider_info() accepts email addresses for backwards compatibility
|
||||
assert!(
|
||||
get_provider_info(&t, "example@google.com", false)
|
||||
get_provider_info("example@google.com", false)
|
||||
.await
|
||||
.unwrap()
|
||||
.id
|
||||
@@ -269,10 +242,4 @@ mod tests {
|
||||
assert!(get_provider_update_timestamp() <= time());
|
||||
assert!(get_provider_update_timestamp() > timestamp_past);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_get_resolver() -> Result<()> {
|
||||
assert!(get_resolver().await.is_ok());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user