improve oauth2 config deserialization and configuration

This commit is contained in:
Clément DOUIN 2023-05-05 00:04:12 +02:00
parent 21f67bc7f5
commit f026e48733
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
9 changed files with 84 additions and 32 deletions

12
Cargo.lock generated
View file

@ -2098,6 +2098,7 @@ dependencies = [
[[package]] [[package]]
name = "pimalaya-email" name = "pimalaya-email"
version = "0.7.1" version = "0.7.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#22db3ec886536897c54b72d4ab7d20beff0ffecc"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"chrono", "chrono",
@ -2138,6 +2139,7 @@ dependencies = [
[[package]] [[package]]
name = "pimalaya-oauth2" name = "pimalaya-oauth2"
version = "0.0.1" version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#22db3ec886536897c54b72d4ab7d20beff0ffecc"
dependencies = [ dependencies = [
"log", "log",
"oauth2", "oauth2",
@ -2650,22 +2652,22 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.152" version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.152" version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.104", "syn 2.0.15",
] ]
[[package]] [[package]]

View file

@ -48,8 +48,7 @@ dialoguer = "0.10.2"
email_address = "0.2.4" email_address = "0.2.4"
env_logger = "0.8" env_logger = "0.8"
erased-serde = "0.3" erased-serde = "0.3"
# pimalaya-email = "0.7.1" pimalaya-email = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-email = { path = "/home/soywod/sourcehut/pimalaya/email" }
indicatif = "0.17" indicatif = "0.17"
log = "0.4" log = "0.4"
once_cell = "1.16.0" once_cell = "1.16.0"

View file

@ -1,6 +1,6 @@
pub mod args; pub mod args;
pub mod config; pub mod config;
pub mod prelude; pub mod prelude;
mod wizard; pub(crate) mod wizard;
pub use config::*; pub use config::*;

View file

@ -1,7 +1,7 @@
use pimalaya_email::{ use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, EmailHooks, EmailSender, EmailTextPlainFormat, folder::sync::Strategy as SyncFoldersStrategy, EmailHooks, EmailSender, EmailTextPlainFormat,
ImapAuthConfig, MaildirConfig, OAuth2Config, OAuth2Method, OAuth2Scopes, SendmailConfig, ImapAuthConfig, MaildirConfig, OAuth2ClientSecret, OAuth2Config, OAuth2Method, OAuth2Scopes,
SmtpConfig, SendmailConfig, SmtpConfig,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashSet, path::PathBuf}; use std::{collections::HashSet, path::PathBuf};
@ -47,7 +47,7 @@ pub struct ImapConfigDef {
pub insecure: Option<bool>, pub insecure: Option<bool>,
#[serde(rename = "imap-login")] #[serde(rename = "imap-login")]
pub login: String, pub login: String,
#[serde(flatten, with = "ImapAuthConfigDef")] #[serde(rename = "imap-auth", with = "ImapAuthConfigDef")]
pub auth: ImapAuthConfig, pub auth: ImapAuthConfig,
#[serde(rename = "imap-notify-cmd")] #[serde(rename = "imap-notify-cmd")]
pub notify_cmd: Option<String>, pub notify_cmd: Option<String>,
@ -57,16 +57,14 @@ pub struct ImapConfigDef {
pub watch_cmds: Option<Vec<String>>, pub watch_cmds: Option<Vec<String>>,
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "ImapAuthConfig", rename_all = "kebab-case")] #[serde(remote = "ImapAuthConfig", rename_all = "kebab-case")]
pub enum ImapAuthConfigDef { pub enum ImapAuthConfigDef {
#[default] #[serde(skip)]
None, None,
#[serde(rename = "imap-passwd")] RawPasswd(String),
Passwd(String),
#[serde(rename = "imap-passwd-cmd")]
PasswdCmd(String), PasswdCmd(String),
#[serde(rename = "imap-oauth2", with = "OAuth2ConfigDef")] #[serde(with = "OAuth2ConfigDef", rename = "oauth2")]
OAuth2(OAuth2Config), OAuth2(OAuth2Config),
} }
@ -76,7 +74,8 @@ pub struct OAuth2ConfigDef {
#[serde(with = "OAuth2MethodDef")] #[serde(with = "OAuth2MethodDef")]
pub method: OAuth2Method, pub method: OAuth2Method,
pub client_id: String, pub client_id: String,
pub client_secret: String, #[serde(with = "OAuth2ClientSecretDef")]
pub client_secret: OAuth2ClientSecret,
pub auth_url: String, pub auth_url: String,
pub token_url: String, pub token_url: String,
#[serde(flatten, with = "OAuth2ScopesDef")] #[serde(flatten, with = "OAuth2ScopesDef")]
@ -85,6 +84,14 @@ pub struct OAuth2ConfigDef {
pub pkce: bool, pub pkce: bool,
} }
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2ClientSecret", rename_all = "kebab-case")]
pub enum OAuth2ClientSecretDef {
Raw(String),
Cmd(String),
Keyring,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Method", rename_all = "lowercase")] #[serde(remote = "OAuth2Method", rename_all = "lowercase")]
pub enum OAuth2MethodDef { pub enum OAuth2MethodDef {

View file

@ -1,10 +1,13 @@
use super::{SECURITY_PROTOCOLS, THEME};
use crate::account::{
DeserializedAccountConfig, DeserializedBaseAccountConfig, DeserializedImapAccountConfig,
};
use anyhow::Result; use anyhow::Result;
use dialoguer::{Input, Select}; use dialoguer::{Input, Select};
use pimalaya_email::ImapConfig; use pimalaya_email::ImapConfig;
use std::io;
use crate::account::{
DeserializedAccountConfig, DeserializedBaseAccountConfig, DeserializedImapAccountConfig,
};
use super::{SECURITY_PROTOCOLS, THEME};
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<DeserializedAccountConfig> { pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<DeserializedAccountConfig> {
@ -56,3 +59,11 @@ pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<Deseriali
DeserializedImapAccountConfig { base, backend }, DeserializedImapAccountConfig { base, backend },
)) ))
} }
#[cfg(feature = "imap-backend")]
pub(crate) fn configure_oauth2_client_secret() -> io::Result<String> {
Input::with_theme(&*THEME)
.with_prompt("Enter your OAuth 2.0 client secret:")
.report(false)
.interact()
}

View file

@ -1,5 +1,5 @@
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
mod imap; pub(crate) mod imap;
mod maildir; mod maildir;
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
mod notmuch; mod notmuch;

View file

@ -10,12 +10,14 @@ use crate::{folder, ui::table};
const ARG_ACCOUNT: &str = "account"; const ARG_ACCOUNT: &str = "account";
const ARG_DRY_RUN: &str = "dry-run"; const ARG_DRY_RUN: &str = "dry-run";
const ARG_RESET: &str = "reset";
const CMD_ACCOUNTS: &str = "accounts"; const CMD_ACCOUNTS: &str = "accounts";
const CMD_CONFIGURE: &str = "configure"; const CMD_CONFIGURE: &str = "configure";
const CMD_LIST: &str = "list"; const CMD_LIST: &str = "list";
const CMD_SYNC: &str = "sync"; const CMD_SYNC: &str = "sync";
type DryRun = bool; type DryRun = bool;
type Reset = bool;
/// Represents the account commands. /// Represents the account commands.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@ -25,7 +27,7 @@ pub enum Cmd {
/// Represents the sync account command. /// Represents the sync account command.
Sync(Option<SyncFoldersStrategy>, DryRun), Sync(Option<SyncFoldersStrategy>, DryRun),
/// Configure the current selected account. /// Configure the current selected account.
Configure, Configure(Reset),
} }
/// Represents the account command matcher. /// Represents the account command matcher.
@ -54,9 +56,10 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
info!("list accounts subcommand matched"); info!("list accounts subcommand matched");
let max_table_width = table::args::parse_max_width(m); let max_table_width = table::args::parse_max_width(m);
Some(Cmd::List(max_table_width)) Some(Cmd::List(max_table_width))
} else if let Some(_) = m.subcommand_matches(CMD_CONFIGURE) { } else if let Some(m) = m.subcommand_matches(CMD_CONFIGURE) {
info!("configure account subcommand matched"); info!("configure account subcommand matched");
Some(Cmd::Configure) let reset = parse_reset_flag(m);
Some(Cmd::Configure(reset))
} else { } else {
info!("no account subcommand matched, falling back to subcommand list"); info!("no account subcommand matched, falling back to subcommand list");
Some(Cmd::List(None)) Some(Cmd::List(None))
@ -88,7 +91,8 @@ pub fn subcmd() -> Command {
.arg(dry_run()), .arg(dry_run()),
Command::new(CMD_CONFIGURE) Command::new(CMD_CONFIGURE)
.about("Configure the current selected account") .about("Configure the current selected account")
.aliases(["config", "conf", "cfg"]), .aliases(["config", "conf", "cfg"])
.arg(reset_flag()),
]) ])
} }
@ -126,3 +130,15 @@ Changes can be visualized with the RUST_LOG=trace environment variable.",
pub fn parse_dry_run_arg(m: &ArgMatches) -> bool { pub fn parse_dry_run_arg(m: &ArgMatches) -> bool {
m.get_flag(ARG_DRY_RUN) m.get_flag(ARG_DRY_RUN)
} }
pub fn reset_flag() -> Arg {
Arg::new(ARG_RESET)
.help("Reset the configuration")
.short('r')
.long("reset")
.action(ArgAction::SetTrue)
}
pub fn parse_reset_flag(m: &ArgMatches) -> bool {
m.get_flag(ARG_RESET)
}

View file

@ -11,15 +11,32 @@ use pimalaya_email::{
}; };
use crate::{ use crate::{
config::DeserializedConfig, config::{wizard::imap::configure_oauth2_client_secret, DeserializedConfig},
printer::{PrintTableOpts, Printer}, printer::{PrintTableOpts, Printer},
Accounts, Accounts,
}; };
/// Configure the current selected account /// Configure the current selected account
pub fn configure(account_config: &AccountConfig, backend_config: &BackendConfig) -> Result<()> { pub fn configure(
account_config: &AccountConfig,
backend_config: &BackendConfig,
reset: bool,
) -> Result<()> {
info!("entering the configure account handler"); info!("entering the configure account handler");
backend_config.configure(&account_config.name)?; match backend_config {
BackendConfig::None => (),
BackendConfig::Maildir(_) => (),
#[cfg(feature = "imap-backend")]
BackendConfig::Imap(imap_config) => {
imap_config.auth.configure(
&account_config.name,
reset,
configure_oauth2_client_secret,
)?;
}
#[cfg(feature = "notmuch-backend")]
BackendConfig::Notmuch(config) => (),
};
println!("Account {} configured!", account_config.name); println!("Account {} configured!", account_config.name);
Ok(()) Ok(())
} }

View file

@ -137,8 +137,8 @@ fn main() -> Result<()> {
backend.close()?; backend.close()?;
return Ok(()); return Ok(());
} }
Some(account::args::Cmd::Configure) => { Some(account::args::Cmd::Configure(reset)) => {
return account::handlers::configure(&account_config, &backend_config); return account::handlers::configure(&account_config, &backend_config, reset);
} }
_ => (), _ => (),
} }