mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
implement dclogin scheme (#3541)
* start implementing dclogin scheme * fix formatting * add test for usename+extension@host cases * add test with all advanced options * add changelog * jsonrpc api and regenerate node constants * Update src/qr/dclogin_scheme.rs Co-authored-by: Hocuri <hocuri@gmx.de> * apply Hocuris comments from code review * fix clippy * Use .eq_ignore_ascii_case() * rename internal function apply_from_login_qr to configure_from_login_qr * fix error message * cargo fmt * remove test todo comment Co-authored-by: Hocuri <hocuri@gmx.de>
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
- jsonrpc: add `MessageNotificationInfo` & `messageGetNotificationInfo()` #3614
|
||||
- jsonrpc: add `chat_get_neighboring_media` function #3610
|
||||
|
||||
### Added
|
||||
- `dclogin:` scheme to allow configuration from a qr code (data inside qrcode, contrary to `dcaccount:` which points to an api to create an account) #3541
|
||||
|
||||
### Changes
|
||||
- truncate incoming messages by lines instead of just length #3480
|
||||
- emit separate `DC_EVENT_MSGS_CHANGED` for each expired message,
|
||||
|
||||
@@ -468,10 +468,10 @@ int dc_set_stock_translation(dc_context_t* context, uint32_t stock_i
|
||||
/**
|
||||
* Set configuration values from a QR code.
|
||||
* Before this function is called, dc_check_qr() should confirm the type of the
|
||||
* QR code is DC_QR_ACCOUNT or DC_QR_WEBRTC_INSTANCE.
|
||||
* QR code is DC_QR_ACCOUNT, DC_QR_LOGIN or DC_QR_WEBRTC_INSTANCE.
|
||||
*
|
||||
* Internally, the function will call dc_set_config() with the appropriate keys,
|
||||
* e.g. `addr` and `mail_pw` for DC_QR_ACCOUNT
|
||||
* e.g. `addr` and `mail_pw` for DC_QR_ACCOUNT and DC_QR_LOGIN
|
||||
* or `webrtc_instance` for DC_QR_WEBRTC_INSTANCE.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
@@ -2270,6 +2270,7 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
||||
#define DC_QR_WITHDRAW_VERIFYGROUP 502 // text1=groupname
|
||||
#define DC_QR_REVIVE_VERIFYCONTACT 510
|
||||
#define DC_QR_REVIVE_VERIFYGROUP 512 // text1=groupname
|
||||
#define DC_QR_LOGIN 520 // text1=email_address
|
||||
|
||||
/**
|
||||
* Check a scanned QR code.
|
||||
@@ -2342,6 +2343,10 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
||||
* ask the user if they want to revive the withdrawn group-invite code;
|
||||
* if so, call dc_set_config_from_qr().
|
||||
*
|
||||
* - DC_QR_LOGIN with dc_lot_t::text1=email_address:
|
||||
* ask the user if they want to login with the email_address,
|
||||
* if so, call dc_set_config_from_qr() and then dc_configure().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param qr The text of the scanned QR code.
|
||||
|
||||
@@ -58,6 +58,7 @@ impl Lot {
|
||||
Qr::WithdrawVerifyGroup { grpname, .. } => Some(grpname),
|
||||
Qr::ReviveVerifyContact { .. } => None,
|
||||
Qr::ReviveVerifyGroup { grpname, .. } => Some(grpname),
|
||||
Qr::Login { address, .. } => Some(address),
|
||||
},
|
||||
Self::Error(err) => Some(err),
|
||||
}
|
||||
@@ -108,6 +109,7 @@ impl Lot {
|
||||
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
|
||||
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
|
||||
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
|
||||
Qr::Login { .. } => LotState::QrLogin,
|
||||
},
|
||||
Self::Error(_err) => LotState::QrError,
|
||||
}
|
||||
@@ -131,6 +133,7 @@ impl Lot {
|
||||
Qr::WithdrawVerifyGroup { .. } => Default::default(),
|
||||
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::ReviveVerifyGroup { .. } => Default::default(),
|
||||
Qr::Login { .. } => Default::default(),
|
||||
},
|
||||
Self::Error(_) => Default::default(),
|
||||
}
|
||||
@@ -195,6 +198,9 @@ pub enum LotState {
|
||||
/// text1=groupname
|
||||
QrReviveVerifyGroup = 512,
|
||||
|
||||
/// text1=email_address
|
||||
QrLogin = 520,
|
||||
|
||||
// Message States
|
||||
MsgInFresh = 10,
|
||||
MsgInNoticed = 13,
|
||||
|
||||
@@ -94,6 +94,9 @@ pub enum QrObject {
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
Login {
|
||||
address: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Qr> for QrObject {
|
||||
@@ -224,6 +227,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::Login { address, .. } => QrObject::Login { address },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
export type U32=number;
|
||||
export type Account=(({"type":"Configured";}&{"id":U32;"displayName":(string|null);"addr":(string|null);"profileImage":(string|null);"color":string;})|({"type":"Unconfigured";}&{"id":U32;}));
|
||||
export type ProviderInfo={"beforeLoginHint":string;"overviewPage":string;"status":U32;};
|
||||
export type Qr=(({"type":"askVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"askVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"fprOk";}&{"contact_id":U32;})|({"type":"fprMismatch";}&{"contact_id":(U32|null);})|({"type":"fprWithoutAddr";}&{"fingerprint":string;})|({"type":"account";}&{"domain":string;})|({"type":"webrtcInstance";}&{"domain":string;"instance_pattern":string;})|({"type":"addr";}&{"contact_id":U32;"draft":(string|null);})|({"type":"url";}&{"url":string;})|({"type":"text";}&{"text":string;})|({"type":"withdrawVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"withdrawVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;}));
|
||||
export type Qr=(({"type":"askVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"askVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"fprOk";}&{"contact_id":U32;})|({"type":"fprMismatch";}&{"contact_id":(U32|null);})|({"type":"fprWithoutAddr";}&{"fingerprint":string;})|({"type":"account";}&{"domain":string;})|({"type":"webrtcInstance";}&{"domain":string;"instance_pattern":string;})|({"type":"addr";}&{"contact_id":U32;"draft":(string|null);})|({"type":"url";}&{"url":string;})|({"type":"text";}&{"text":string;})|({"type":"withdrawVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"withdrawVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"login";}&{"address":string;}));
|
||||
export type Usize=number;
|
||||
export type ChatListEntry=[U32,U32];
|
||||
export type I64=number;
|
||||
|
||||
@@ -103,6 +103,7 @@ module.exports = {
|
||||
DC_QR_FPR_MISMATCH: 220,
|
||||
DC_QR_FPR_OK: 210,
|
||||
DC_QR_FPR_WITHOUT_ADDR: 230,
|
||||
DC_QR_LOGIN: 520,
|
||||
DC_QR_REVIVE_VERIFYCONTACT: 510,
|
||||
DC_QR_REVIVE_VERIFYGROUP: 512,
|
||||
DC_QR_TEXT: 330,
|
||||
|
||||
@@ -103,6 +103,7 @@ export enum C {
|
||||
DC_QR_FPR_MISMATCH = 220,
|
||||
DC_QR_FPR_OK = 210,
|
||||
DC_QR_FPR_WITHOUT_ADDR = 230,
|
||||
DC_QR_LOGIN = 520,
|
||||
DC_QR_REVIVE_VERIFYCONTACT = 510,
|
||||
DC_QR_REVIVE_VERIFYGROUP = 512,
|
||||
DC_QR_TEXT = 330,
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2};
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
use crate::{context::Context, provider::Socket};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum CertificateChecks {
|
||||
|
||||
112
src/qr.rs
112
src/qr.rs
@@ -1,5 +1,8 @@
|
||||
//! # QR code module.
|
||||
|
||||
mod dclogin_scheme;
|
||||
pub use dclogin_scheme::LoginOptions;
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context as _, Error, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use percent_encoding::percent_decode_str;
|
||||
@@ -17,8 +20,11 @@ use crate::peerstate::Peerstate;
|
||||
use crate::tools::time;
|
||||
use crate::{token, EventType};
|
||||
|
||||
use self::dclogin_scheme::configure_from_login_qr;
|
||||
|
||||
const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase
|
||||
const DCACCOUNT_SCHEME: &str = "DCACCOUNT:";
|
||||
pub(super) const DCLOGIN_SCHEME: &str = "DCLOGIN:";
|
||||
const DCWEBRTC_SCHEME: &str = "DCWEBRTC:";
|
||||
const MAILTO_SCHEME: &str = "mailto:";
|
||||
const MATMSG_SCHEME: &str = "MATMSG:";
|
||||
@@ -97,6 +103,10 @@ pub enum Qr {
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
Login {
|
||||
address: String,
|
||||
options: LoginOptions,
|
||||
},
|
||||
}
|
||||
|
||||
fn starts_with_ignore_case(string: &str, pattern: &str) -> bool {
|
||||
@@ -115,6 +125,8 @@ pub async fn check_qr(context: &Context, qr: &str) -> Result<Qr> {
|
||||
.context("failed to decode OPENPGP4FPR QR code")?
|
||||
} else if starts_with_ignore_case(qr, DCACCOUNT_SCHEME) {
|
||||
decode_account(qr)?
|
||||
} else if starts_with_ignore_case(qr, DCLOGIN_SCHEME) {
|
||||
dclogin_scheme::decode_login(qr)?
|
||||
} else if starts_with_ignore_case(qr, DCWEBRTC_SCHEME) {
|
||||
decode_webrtc_instance(context, qr)?
|
||||
} else if qr.starts_with(MAILTO_SCHEME) {
|
||||
@@ -462,6 +474,9 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
context.sync_qr_code_tokens(chat_id).await?;
|
||||
context.send_sync_msg().await?;
|
||||
}
|
||||
Qr::Login { address, options } => {
|
||||
configure_from_login_qr(context, &address, options).await?
|
||||
}
|
||||
_ => bail!("qr code {:?} does not contain config", qr),
|
||||
}
|
||||
|
||||
@@ -1024,6 +1039,103 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_decode_and_apply_dclogin() -> Result<()> {
|
||||
let ctx = TestContext::new().await;
|
||||
|
||||
let result = check_qr(&ctx.ctx, "dclogin:usename+extension@host?p=1234&v=1").await?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "usename+extension@host".to_owned());
|
||||
|
||||
if let LoginOptions::V1 { mail_pw, .. } = options {
|
||||
assert_eq!(mail_pw, "1234".to_owned());
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
|
||||
assert!(ctx.ctx.get_config(Config::Addr).await?.is_none());
|
||||
assert!(ctx.ctx.get_config(Config::MailPw).await?.is_none());
|
||||
|
||||
set_config_from_qr(&ctx.ctx, "dclogin:username+extension@host?p=1234&v=1").await?;
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::Addr).await?,
|
||||
Some("username+extension@host".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::MailPw).await?,
|
||||
Some("1234".to_owned())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_decode_and_apply_dclogin_advanced_options() -> Result<()> {
|
||||
let ctx = TestContext::new().await;
|
||||
set_config_from_qr(&ctx.ctx, "dclogin:username+extension@host?p=1234&spw=4321&sh=send.host&sp=7273&su=SendUser&ih=host.tld&ip=4343&iu=user&ipw=password&is=ssl&ic=1&sc=3&ss=plain&v=1").await?;
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::Addr).await?,
|
||||
Some("username+extension@host".to_owned())
|
||||
);
|
||||
|
||||
// `p=1234` is ignored, because `ipw=password` is set
|
||||
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::MailServer).await?,
|
||||
Some("host.tld".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::MailPort).await?,
|
||||
Some("4343".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::MailUser).await?,
|
||||
Some("user".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::MailPw).await?,
|
||||
Some("password".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::MailSecurity).await?,
|
||||
Some("1".to_owned()) // ssl
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::ImapCertificateChecks).await?,
|
||||
Some("1".to_owned())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::SendPw).await?,
|
||||
Some("4321".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::SendServer).await?,
|
||||
Some("send.host".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::SendPort).await?,
|
||||
Some("7273".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::SendUser).await?,
|
||||
Some("SendUser".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::SmtpCertificateChecks).await?,
|
||||
Some("3".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.ctx.get_config(Config::SendSecurity).await?,
|
||||
Some("3".to_owned()) // plain
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_decode_account() -> Result<()> {
|
||||
let ctx = TestContext::new().await;
|
||||
|
||||
388
src/qr/dclogin_scheme.rs
Normal file
388
src/qr/dclogin_scheme.rs
Normal file
@@ -0,0 +1,388 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::provider::Socket;
|
||||
use crate::{contact, login_param::CertificateChecks};
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
use super::{Qr, DCLOGIN_SCHEME};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum LoginOptions {
|
||||
UnsuportedVersion(u32),
|
||||
V1 {
|
||||
mail_pw: String,
|
||||
imap_host: Option<String>,
|
||||
imap_port: Option<u16>,
|
||||
imap_username: Option<String>,
|
||||
imap_password: Option<String>,
|
||||
imap_security: Option<Socket>,
|
||||
imap_certificate_checks: Option<CertificateChecks>,
|
||||
smtp_host: Option<String>,
|
||||
smtp_port: Option<u16>,
|
||||
smtp_username: Option<String>,
|
||||
smtp_password: Option<String>,
|
||||
smtp_security: Option<Socket>,
|
||||
smtp_certificate_checks: Option<CertificateChecks>,
|
||||
},
|
||||
}
|
||||
|
||||
/// scheme: `dclogin://user@host/?p=password&v=1[&options]`
|
||||
/// read more about the scheme at <https://github.com/deltachat/interface/blob/master/uri-schemes.md#DCLOGIN>
|
||||
pub(super) fn decode_login(qr: &str) -> Result<Qr> {
|
||||
let url = url::Url::parse(qr).with_context(|| format!("Malformed url: {:?}", qr))?;
|
||||
|
||||
let url_without_scheme = qr
|
||||
.get(DCLOGIN_SCHEME.len()..)
|
||||
.context("invalid DCLOGIN payload E1")?;
|
||||
let payload = url_without_scheme
|
||||
.strip_prefix("//")
|
||||
.unwrap_or(url_without_scheme);
|
||||
|
||||
let addr = payload
|
||||
.split(|c| c == '?' || c == '/')
|
||||
.next()
|
||||
.context("invalid DCLOGIN payload E3")?;
|
||||
|
||||
if url.scheme().eq_ignore_ascii_case("dclogin") {
|
||||
let options = url.query_pairs();
|
||||
if options.count() == 0 {
|
||||
bail!("invalid DCLOGIN payload E4")
|
||||
}
|
||||
// load options into hashmap
|
||||
let parameter_map: HashMap<String, String> = options
|
||||
.map(|(key, value)| (key.into_owned(), value.into_owned()))
|
||||
.collect();
|
||||
|
||||
// check if username is there
|
||||
if !contact::may_be_valid_addr(addr) {
|
||||
bail!("invalid DCLOGIN payload: invalid username E5");
|
||||
}
|
||||
|
||||
// apply to result struct
|
||||
let options: LoginOptions = match parameter_map.get("v").map(|i| i.parse::<u32>()) {
|
||||
Some(Ok(1)) => LoginOptions::V1 {
|
||||
mail_pw: parameter_map
|
||||
.get("p")
|
||||
.map(|s| s.to_owned())
|
||||
.context("password missing")?,
|
||||
imap_host: parameter_map.get("ih").map(|s| s.to_owned()),
|
||||
imap_port: parse_port(parameter_map.get("ip"))
|
||||
.context("could not parse imap port")?,
|
||||
imap_username: parameter_map.get("iu").map(|s| s.to_owned()),
|
||||
imap_password: parameter_map.get("ipw").map(|s| s.to_owned()),
|
||||
imap_security: parse_socket_security(parameter_map.get("is"))?,
|
||||
imap_certificate_checks: parse_certificate_checks(parameter_map.get("ic"))?,
|
||||
smtp_host: parameter_map.get("sh").map(|s| s.to_owned()),
|
||||
smtp_port: parse_port(parameter_map.get("sp"))
|
||||
.context("could not parse smtp port")?,
|
||||
smtp_username: parameter_map.get("su").map(|s| s.to_owned()),
|
||||
smtp_password: parameter_map.get("spw").map(|s| s.to_owned()),
|
||||
smtp_security: parse_socket_security(parameter_map.get("ss"))?,
|
||||
smtp_certificate_checks: parse_certificate_checks(parameter_map.get("sc"))?,
|
||||
},
|
||||
Some(Ok(v)) => LoginOptions::UnsuportedVersion(v),
|
||||
Some(Err(_)) => bail!("version could not be parsed as number E6"),
|
||||
None => bail!("invalid DCLOGIN payload: version missing E7"),
|
||||
};
|
||||
|
||||
Ok(Qr::Login {
|
||||
address: addr.to_owned(),
|
||||
options,
|
||||
})
|
||||
} else {
|
||||
bail!("Bad scheme for account URL: {:?}.", payload);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_port(port: Option<&String>) -> core::result::Result<Option<u16>, std::num::ParseIntError> {
|
||||
match port {
|
||||
Some(p) => Ok(Some(p.parse::<u16>()?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_socket_security(security: Option<&String>) -> Result<Option<Socket>> {
|
||||
Ok(match security.map(|s| s.as_str()) {
|
||||
Some("ssl") => Some(Socket::Ssl),
|
||||
Some("starttls") => Some(Socket::Starttls),
|
||||
Some("default") => Some(Socket::Automatic),
|
||||
Some("plain") => Some(Socket::Plain),
|
||||
Some(other) => bail!("Unknown security level: {}", other),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_certificate_checks(
|
||||
certificate_checks: Option<&String>,
|
||||
) -> Result<Option<CertificateChecks>> {
|
||||
Ok(match certificate_checks.map(|s| s.as_str()) {
|
||||
Some("0") => Some(CertificateChecks::Automatic),
|
||||
Some("1") => Some(CertificateChecks::Strict),
|
||||
Some("3") => Some(CertificateChecks::AcceptInvalidCertificates),
|
||||
Some(other) => bail!("Unknown certificatecheck level: {}", other),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn configure_from_login_qr(
|
||||
context: &Context,
|
||||
address: &str,
|
||||
options: LoginOptions,
|
||||
) -> Result<()> {
|
||||
context.set_config(Config::Addr, Some(address)).await?;
|
||||
|
||||
match options {
|
||||
LoginOptions::V1 {
|
||||
mail_pw,
|
||||
imap_host,
|
||||
imap_port,
|
||||
imap_username,
|
||||
imap_password,
|
||||
imap_security,
|
||||
imap_certificate_checks,
|
||||
smtp_host,
|
||||
smtp_port,
|
||||
smtp_username,
|
||||
smtp_password,
|
||||
smtp_security,
|
||||
smtp_certificate_checks,
|
||||
} => {
|
||||
context.set_config(Config::MailPw, Some(&mail_pw)).await?;
|
||||
if let Some(value) = imap_host {
|
||||
context.set_config(Config::MailServer, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = imap_port {
|
||||
context
|
||||
.set_config(Config::MailPort, Some(&value.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = imap_username {
|
||||
context.set_config(Config::MailUser, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = imap_password {
|
||||
context.set_config(Config::MailPw, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = imap_security {
|
||||
let code = value
|
||||
.to_u8()
|
||||
.context("could not convert imap security value to number")?;
|
||||
context
|
||||
.set_config(Config::MailSecurity, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = imap_certificate_checks {
|
||||
let code = value
|
||||
.to_u32()
|
||||
.context("could not convert imap certificate checks value to number")?;
|
||||
context
|
||||
.set_config(Config::ImapCertificateChecks, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = smtp_host {
|
||||
context.set_config(Config::SendServer, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = smtp_port {
|
||||
context
|
||||
.set_config(Config::SendPort, Some(&value.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = smtp_username {
|
||||
context.set_config(Config::SendUser, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = smtp_password {
|
||||
context.set_config(Config::SendPw, Some(&value)).await?;
|
||||
}
|
||||
if let Some(value) = smtp_security {
|
||||
let code = value
|
||||
.to_u8()
|
||||
.context("could not convert smtp security value to number")?;
|
||||
context
|
||||
.set_config(Config::SendSecurity, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
if let Some(value) = smtp_certificate_checks {
|
||||
let code = value
|
||||
.to_u32()
|
||||
.context("could not convert smtp certificate checks value to number")?;
|
||||
context
|
||||
.set_config(Config::SmtpCertificateChecks, Some(&code.to_string()))
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => bail!(
|
||||
"DeltaChat does not understand this QR Code yet, please update the app and try again."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{decode_login, LoginOptions};
|
||||
use crate::{login_param::CertificateChecks, provider::Socket, qr::Qr};
|
||||
use anyhow::{self, bail};
|
||||
|
||||
macro_rules! login_options_just_pw {
|
||||
($pw: expr) => {
|
||||
LoginOptions::V1 {
|
||||
mail_pw: $pw,
|
||||
imap_host: None,
|
||||
imap_port: None,
|
||||
imap_username: None,
|
||||
imap_password: None,
|
||||
imap_security: None,
|
||||
imap_certificate_checks: None,
|
||||
smtp_host: None,
|
||||
smtp_port: None,
|
||||
smtp_username: None,
|
||||
smtp_password: None,
|
||||
smtp_security: None,
|
||||
smtp_certificate_checks: None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minimal_no_options() -> anyhow::Result<()> {
|
||||
let result = decode_login("dclogin://email@host.tld?p=123&v=1")?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(options, login_options_just_pw!("123".to_owned()));
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
let result = decode_login("dclogin://email@host.tld/?p=123456&v=1")?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(options, login_options_just_pw!("123456".to_owned()));
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
let result = decode_login("dclogin://email@host.tld/ignored/path?p=123456&v=1")?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(options, login_options_just_pw!("123456".to_owned()));
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn minimal_no_options_no_double_slash() -> anyhow::Result<()> {
|
||||
let result = decode_login("dclogin:email@host.tld?p=123&v=1")?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(options, login_options_just_pw!("123".to_owned()));
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
let result = decode_login("dclogin:email@host.tld/?p=123456&v=1")?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(options, login_options_just_pw!("123456".to_owned()));
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
let result = decode_login("dclogin:email@host.tld/ignored/path?p=123456&v=1")?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(options, login_options_just_pw!("123456".to_owned()));
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_version_set() {
|
||||
assert!(decode_login("dclogin:email@host.tld?p=123").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_version_set() {
|
||||
assert!(decode_login("dclogin:email@host.tld?p=123&v=").is_err());
|
||||
assert!(decode_login("dclogin:email@host.tld?p=123&v=%40").is_err());
|
||||
assert!(decode_login("dclogin:email@host.tld?p=123&v=-20").is_err());
|
||||
assert!(decode_login("dclogin:email@host.tld?p=123&v=hi").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_too_new() -> anyhow::Result<()> {
|
||||
let result = decode_login("dclogin:email@host.tld/?p=123456&v=2")?;
|
||||
if let Qr::Login { options, .. } = result {
|
||||
assert_eq!(options, LoginOptions::UnsuportedVersion(2));
|
||||
} else {
|
||||
bail!("wrong type");
|
||||
}
|
||||
let result = decode_login("dclogin:email@host.tld/?p=123456&v=5")?;
|
||||
if let Qr::Login { options, .. } = result {
|
||||
assert_eq!(options, LoginOptions::UnsuportedVersion(5));
|
||||
} else {
|
||||
bail!("wrong type");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_advanced_options() -> anyhow::Result<()> {
|
||||
let result = decode_login(
|
||||
"dclogin:email@host.tld?p=secret&v=1&ih=imap.host.tld&ip=4000&iu=max&ipw=87654&is=ssl&ic=1&sh=mail.host.tld&sp=3000&su=max@host.tld&spw=3242HS&ss=plain&sc=3",
|
||||
)?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(
|
||||
options,
|
||||
LoginOptions::V1 {
|
||||
mail_pw: "secret".to_owned(),
|
||||
imap_host: Some("imap.host.tld".to_owned()),
|
||||
imap_port: Some(4000),
|
||||
imap_username: Some("max".to_owned()),
|
||||
imap_password: Some("87654".to_owned()),
|
||||
imap_security: Some(Socket::Ssl),
|
||||
imap_certificate_checks: Some(CertificateChecks::Strict),
|
||||
smtp_host: Some("mail.host.tld".to_owned()),
|
||||
smtp_port: Some(3000),
|
||||
smtp_username: Some("max@host.tld".to_owned()),
|
||||
smtp_password: Some("3242HS".to_owned()),
|
||||
smtp_security: Some(Socket::Plain),
|
||||
smtp_certificate_checks: Some(CertificateChecks::AcceptInvalidCertificates),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_encoded_password() -> anyhow::Result<()> {
|
||||
let result = decode_login(
|
||||
"dclogin:email@host.tld?p=%7BDaehFl%3B%22as%40%21fhdodn5%24234%22%7B%7Dfg&v=1",
|
||||
)?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "email@host.tld".to_owned());
|
||||
assert_eq!(
|
||||
options,
|
||||
login_options_just_pw!("{DaehFl;\"as@!fhdodn5$234\"{}fg".to_owned())
|
||||
);
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_with_plus_extension() -> anyhow::Result<()> {
|
||||
let result = decode_login("dclogin:usename+extension@host?p=1234&v=1")?;
|
||||
if let Qr::Login { address, options } = result {
|
||||
assert_eq!(address, "usename+extension@host".to_owned());
|
||||
assert_eq!(options, login_options_just_pw!("1234".to_owned()));
|
||||
} else {
|
||||
bail!("wrong type")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user