From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id C4C8662E18 for ; Wed, 28 Oct 2020 12:37:23 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id BB9591EB1A for ; Wed, 28 Oct 2020 12:36:53 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 4F2F41E9E6 for ; Wed, 28 Oct 2020 12:36:49 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 1A26445E7F for ; Wed, 28 Oct 2020 12:36:49 +0100 (CET) From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= To: pbs-devel@lists.proxmox.com Date: Wed, 28 Oct 2020 12:36:31 +0100 Message-Id: <20201028113632.814586-11-f.gruenbichler@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201028113632.814586-1-f.gruenbichler@proxmox.com> References: <20201028113632.814586-1-f.gruenbichler@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.023 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_FILL_THIS_FORM_SHORT 0.01 Fill in a short form with personal information URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [remote.rs, pull.rs, download-speed.rs, catalog.rs, proxmox-backup-manager.rs, mount.rs, upload-speed.rs, task.rs, mod.rs, benchmark.rs, proxmox-backup-client.rs] Subject: [pbs-devel] [PATCH proxmox-backup 08/16] client/remote: allow using ApiToken + secret X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 28 Oct 2020 11:37:23 -0000 in place of user + password. Signed-off-by: Fabian Grünbichler --- Notes: this will require more follow-up to disentagle the CLI args properly, but it allows using tokens with the client/pull as is. examples/download-speed.rs | 6 +- examples/upload-speed.rs | 6 +- src/api2/config/remote.rs | 2 +- src/api2/pull.rs | 2 +- src/api2/types/mod.rs | 2 +- src/bin/proxmox-backup-client.rs | 32 +++++----- src/bin/proxmox-backup-manager.rs | 4 +- src/bin/proxmox_backup_client/benchmark.rs | 2 +- src/bin/proxmox_backup_client/catalog.rs | 4 +- src/bin/proxmox_backup_client/mount.rs | 2 +- src/bin/proxmox_backup_client/task.rs | 8 +-- src/client/backup_repo.rs | 25 +++++--- src/client/http_client.rs | 68 +++++++++++++++------- src/client/pull.rs | 2 +- src/config/remote.rs | 2 +- 15 files changed, 101 insertions(+), 66 deletions(-) diff --git a/examples/download-speed.rs b/examples/download-speed.rs index fa278436..3ccf4ce7 100644 --- a/examples/download-speed.rs +++ b/examples/download-speed.rs @@ -2,7 +2,7 @@ use std::io::Write; use anyhow::{Error}; -use proxmox_backup::api2::types::Userid; +use proxmox_backup::api2::types::Authid; use proxmox_backup::client::{HttpClient, HttpClientOptions, BackupReader}; pub struct DummyWriter { @@ -26,13 +26,13 @@ async fn run() -> Result<(), Error> { let host = "localhost"; - let username = Userid::root_userid(); + let auth_id = Authid::root_auth_id(); let options = HttpClientOptions::new() .interactive(true) .ticket_cache(true); - let client = HttpClient::new(host, 8007, username, options)?; + let client = HttpClient::new(host, 8007, auth_id, options)?; let backup_time = proxmox::tools::time::parse_rfc3339("2019-06-28T10:49:48Z")?; diff --git a/examples/upload-speed.rs b/examples/upload-speed.rs index d26e6db3..641ed952 100644 --- a/examples/upload-speed.rs +++ b/examples/upload-speed.rs @@ -1,6 +1,6 @@ use anyhow::{Error}; -use proxmox_backup::api2::types::Userid; +use proxmox_backup::api2::types::Authid; use proxmox_backup::client::*; async fn upload_speed() -> Result { @@ -8,13 +8,13 @@ async fn upload_speed() -> Result { let host = "localhost"; let datastore = "store2"; - let username = Userid::root_userid(); + let auth_id = Authid::root_auth_id(); let options = HttpClientOptions::new() .interactive(true) .ticket_cache(true); - let client = HttpClient::new(host, 8007, username, options)?; + let client = HttpClient::new(host, 8007, auth_id, options)?; let backup_time = proxmox::tools::time::epoch_i64(); diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs index dd2777c9..96869695 100644 --- a/src/api2/config/remote.rs +++ b/src/api2/config/remote.rs @@ -201,7 +201,7 @@ pub fn update_remote( comment: Option, host: Option, port: Option, - userid: Option, + userid: Option, password: Option, fingerprint: Option, delete: Option>, diff --git a/src/api2/pull.rs b/src/api2/pull.rs index aef7de4e..d70fe014 100644 --- a/src/api2/pull.rs +++ b/src/api2/pull.rs @@ -56,7 +56,7 @@ pub async fn get_pull_parameters( let src_repo = BackupRepository::new(Some(remote.userid.clone()), Some(remote.host.clone()), remote.port, remote_store.to_string()); - let client = HttpClient::new(&src_repo.host(), src_repo.port(), &src_repo.user(), options)?; + let client = HttpClient::new(&src_repo.host(), src_repo.port(), &src_repo.auth_id(), options)?; let _auth_info = client.login() // make sure we can auth .await .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?; diff --git a/src/api2/types/mod.rs b/src/api2/types/mod.rs index b1287583..4084112d 100644 --- a/src/api2/types/mod.rs +++ b/src/api2/types/mod.rs @@ -67,7 +67,7 @@ const_regex!{ pub DNS_NAME_OR_IP_REGEX = concat!(r"^(?:", DNS_NAME!(), "|", IPRE!(), r")$"); - pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE_BRACKET!() ,"):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$"); + pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), "|", APITOKEN_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE_BRACKET!() ,"):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$"); pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$"; diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 8c68ffd2..ce263a77 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -193,7 +193,7 @@ pub fn complete_repository(_arg: &str, _param: &HashMap) -> Vec< result } -fn connect(server: &str, port: u16, userid: &Userid) -> Result { +fn connect(server: &str, port: u16, auth_id: &Authid) -> Result { let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok(); @@ -212,7 +212,7 @@ fn connect(server: &str, port: u16, userid: &Userid) -> Result Result { let repo = extract_repository_from_value(¶m)?; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = format!("api2/json/admin/datastore/{}/groups", repo.store()); @@ -435,7 +435,7 @@ async fn change_backup_owner(group: String, mut param: Value) -> Result<(), Erro let repo = extract_repository_from_value(¶m)?; - let mut client = connect(repo.host(), repo.port(), repo.user())?; + let mut client = connect(repo.host(), repo.port(), repo.auth_id())?; param.as_object_mut().unwrap().remove("repository"); @@ -478,7 +478,7 @@ async fn list_snapshots(param: Value) -> Result { let output_format = get_output_format(¶m); - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let group: Option = if let Some(path) = param["group"].as_str() { Some(path.parse()?) @@ -543,7 +543,7 @@ async fn forget_snapshots(param: Value) -> Result { let path = tools::required_string_param(¶m, "snapshot")?; let snapshot: BackupDir = path.parse()?; - let mut client = connect(repo.host(), repo.port(), repo.user())?; + let mut client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store()); @@ -573,7 +573,7 @@ async fn api_login(param: Value) -> Result { let repo = extract_repository_from_value(¶m)?; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; client.login().await?; record_repository(&repo); @@ -630,7 +630,7 @@ async fn api_version(param: Value) -> Result<(), Error> { let repo = extract_repository_from_value(¶m); if let Ok(repo) = repo { - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; match client.get("api2/json/version", None).await { Ok(mut result) => version_info["server"] = result["data"].take(), @@ -680,7 +680,7 @@ async fn list_snapshot_files(param: Value) -> Result { let output_format = get_output_format(¶m); - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = format!("api2/json/admin/datastore/{}/files", repo.store()); @@ -724,7 +724,7 @@ async fn start_garbage_collection(param: Value) -> Result { let output_format = get_output_format(¶m); - let mut client = connect(repo.host(), repo.port(), repo.user())?; + let mut client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = format!("api2/json/admin/datastore/{}/gc", repo.store()); @@ -1036,7 +1036,7 @@ async fn create_backup( let backup_time = backup_time_opt.unwrap_or_else(|| epoch_i64()); - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; record_repository(&repo); println!("Starting backup: {}/{}/{}", backup_type, backup_id, BackupDir::backup_time_to_string(backup_time)?); @@ -1339,7 +1339,7 @@ async fn restore(param: Value) -> Result { let archive_name = tools::required_string_param(¶m, "archive-name")?; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; record_repository(&repo); @@ -1512,7 +1512,7 @@ async fn upload_log(param: Value) -> Result { let snapshot = tools::required_string_param(¶m, "snapshot")?; let snapshot: BackupDir = snapshot.parse()?; - let mut client = connect(repo.host(), repo.port(), repo.user())?; + let mut client = connect(repo.host(), repo.port(), repo.auth_id())?; let (keydata, crypt_mode) = keyfile_parameters(¶m)?; @@ -1583,7 +1583,7 @@ fn prune<'a>( async fn prune_async(mut param: Value) -> Result { let repo = extract_repository_from_value(¶m)?; - let mut client = connect(repo.host(), repo.port(), repo.user())?; + let mut client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = format!("api2/json/admin/datastore/{}/prune", repo.store()); @@ -1666,7 +1666,7 @@ async fn status(param: Value) -> Result { let output_format = get_output_format(¶m); - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = format!("api2/json/admin/datastore/{}/status", repo.store()); @@ -1711,7 +1711,7 @@ async fn try_get(repo: &BackupRepository, url: &str) -> Value { .fingerprint_cache(true) .ticket_cache(true); - let client = match HttpClient::new(repo.host(), repo.port(), repo.user(), options) { + let client = match HttpClient::new(repo.host(), repo.port(), repo.auth_id(), options) { Ok(v) => v, _ => return Value::Null, }; diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index f13e55ee..549be2ef 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -62,10 +62,10 @@ fn connect() -> Result { let ticket = Ticket::new("PBS", Userid::root_userid())? .sign(private_auth_key(), None)?; options = options.password(Some(ticket)); - HttpClient::new("localhost", 8007, Userid::root_userid(), options)? + HttpClient::new("localhost", 8007, Authid::root_auth_id(), options)? } else { options = options.ticket_cache(true).interactive(true); - HttpClient::new("localhost", 8007, Userid::root_userid(), options)? + HttpClient::new("localhost", 8007, Authid::root_auth_id(), options)? }; Ok(client) diff --git a/src/bin/proxmox_backup_client/benchmark.rs b/src/bin/proxmox_backup_client/benchmark.rs index 37bb87fb..b434956d 100644 --- a/src/bin/proxmox_backup_client/benchmark.rs +++ b/src/bin/proxmox_backup_client/benchmark.rs @@ -225,7 +225,7 @@ async fn test_upload_speed( let backup_time = proxmox::tools::time::epoch_i64(); - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; record_repository(&repo); if verbose { eprintln!("Connecting to backup server"); } diff --git a/src/bin/proxmox_backup_client/catalog.rs b/src/bin/proxmox_backup_client/catalog.rs index e35692f2..87d80f3b 100644 --- a/src/bin/proxmox_backup_client/catalog.rs +++ b/src/bin/proxmox_backup_client/catalog.rs @@ -79,7 +79,7 @@ async fn dump_catalog(param: Value) -> Result { } }; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let client = BackupReader::start( client, @@ -153,7 +153,7 @@ async fn dump_catalog(param: Value) -> Result { /// Shell to interactively inspect and restore snapshots. async fn catalog_shell(param: Value) -> Result<(), Error> { let repo = extract_repository_from_value(¶m)?; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = tools::required_string_param(¶m, "snapshot")?; let archive_name = tools::required_string_param(¶m, "archive-name")?; diff --git a/src/bin/proxmox_backup_client/mount.rs b/src/bin/proxmox_backup_client/mount.rs index 69ab6d3f..6c91c132 100644 --- a/src/bin/proxmox_backup_client/mount.rs +++ b/src/bin/proxmox_backup_client/mount.rs @@ -163,7 +163,7 @@ fn mount( async fn mount_do(param: Value, pipe: Option) -> Result { let repo = extract_repository_from_value(¶m)?; let archive_name = tools::required_string_param(¶m, "archive-name")?; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let target = param["target"].as_str(); diff --git a/src/bin/proxmox_backup_client/task.rs b/src/bin/proxmox_backup_client/task.rs index 72d8095c..3bf817d8 100644 --- a/src/bin/proxmox_backup_client/task.rs +++ b/src/bin/proxmox_backup_client/task.rs @@ -48,7 +48,7 @@ async fn task_list(param: Value) -> Result { let output_format = get_output_format(¶m); let repo = extract_repository_from_value(¶m)?; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; let limit = param["limit"].as_u64().unwrap_or(50) as usize; let running = !param["all"].as_bool().unwrap_or(false); @@ -57,7 +57,7 @@ async fn task_list(param: Value) -> Result { "running": running, "start": 0, "limit": limit, - "userfilter": repo.user(), + "userfilter": repo.auth_id(), "store": repo.store(), }); @@ -96,7 +96,7 @@ async fn task_log(param: Value) -> Result { let repo = extract_repository_from_value(¶m)?; let upid = tools::required_string_param(¶m, "upid")?; - let client = connect(repo.host(), repo.port(), repo.user())?; + let client = connect(repo.host(), repo.port(), repo.auth_id())?; display_task_log(client, upid, true).await?; @@ -122,7 +122,7 @@ async fn task_stop(param: Value) -> Result { let repo = extract_repository_from_value(¶m)?; let upid_str = tools::required_string_param(¶m, "upid")?; - let mut client = connect(repo.host(), repo.port(), repo.user())?; + let mut client = connect(repo.host(), repo.port(), repo.auth_id())?; let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str); let _ = client.delete(&path, None).await?; diff --git a/src/client/backup_repo.rs b/src/client/backup_repo.rs index c33314bf..091d2707 100644 --- a/src/client/backup_repo.rs +++ b/src/client/backup_repo.rs @@ -16,7 +16,7 @@ pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_RE #[derive(Debug)] pub struct BackupRepository { /// The user name used for Authentication - user: Option, + auth_id: Option, /// The host name or IP address host: Option, /// The port @@ -27,20 +27,29 @@ pub struct BackupRepository { impl BackupRepository { - pub fn new(user: Option, host: Option, port: Option, store: String) -> Self { + pub fn new(auth_id: Option, host: Option, port: Option, store: String) -> Self { let host = match host { Some(host) if (IP_V6_REGEX.regex_obj)().is_match(&host) => { Some(format!("[{}]", host)) }, other => other, }; - Self { user, host, port, store } + Self { auth_id, host, port, store } + } + + pub fn auth_id(&self) -> &Authid { + if let Some(ref auth_id) = self.auth_id { + return auth_id; + } + + &Authid::root_auth_id() } pub fn user(&self) -> &Userid { - if let Some(ref user) = self.user { - return &user; + if let Some(auth_id) = &self.auth_id { + return auth_id.user(); } + Userid::root_userid() } @@ -65,8 +74,8 @@ impl BackupRepository { impl fmt::Display for BackupRepository { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match (&self.user, &self.host, self.port) { - (Some(user), _, _) => write!(f, "{}@{}:{}:{}", user, self.host(), self.port(), self.store), + match (&self.auth_id, &self.host, self.port) { + (Some(auth_id), _, _) => write!(f, "{}@{}:{}:{}", auth_id, self.host(), self.port(), self.store), (None, Some(host), None) => write!(f, "{}:{}", host, self.store), (None, _, Some(port)) => write!(f, "{}:{}:{}", self.host(), port, self.store), (None, None, None) => write!(f, "{}", self.store), @@ -88,7 +97,7 @@ impl std::str::FromStr for BackupRepository { .ok_or_else(|| format_err!("unable to parse repository url '{}'", url))?; Ok(Self { - user: cap.get(1).map(|m| Userid::try_from(m.as_str().to_owned())).transpose()?, + auth_id: cap.get(1).map(|m| Authid::try_from(m.as_str().to_owned())).transpose()?, host: cap.get(2).map(|m| m.as_str().to_owned()), port: cap.get(3).map(|m| m.as_str().parse::()).transpose()?, store: cap[4].to_owned(), diff --git a/src/client/http_client.rs b/src/client/http_client.rs index b57630f8..3b7597fe 100644 --- a/src/client/http_client.rs +++ b/src/client/http_client.rs @@ -21,7 +21,7 @@ use proxmox::{ }; use super::pipe_to_stream::PipeToSendStream; -use crate::api2::types::Userid; +use crate::api2::types::{Authid, Userid}; use crate::tools::{ self, BroadcastFuture, @@ -31,7 +31,7 @@ use crate::tools::{ #[derive(Clone)] pub struct AuthInfo { - pub userid: Userid, + pub auth_id: Authid, pub ticket: String, pub token: String, } @@ -102,7 +102,7 @@ pub struct HttpClient { server: String, port: u16, fingerprint: Arc>>, - first_auth: BroadcastFuture<()>, + first_auth: Option>, auth: Arc>, ticket_abort: futures::future::AbortHandle, _options: HttpClientOptions, @@ -251,7 +251,7 @@ impl HttpClient { pub fn new( server: &str, port: u16, - userid: &Userid, + auth_id: &Authid, mut options: HttpClientOptions, ) -> Result { @@ -311,6 +311,11 @@ impl HttpClient { let password = if let Some(password) = password { password } else { + let userid = if auth_id.is_token() { + bail!("API token secret must be provided!"); + } else { + auth_id.user() + }; let mut ticket_info = None; if use_ticket_cache { ticket_info = load_ticket_info(options.prefix.as_ref().unwrap(), server, userid); @@ -323,7 +328,7 @@ impl HttpClient { }; let auth = Arc::new(RwLock::new(AuthInfo { - userid: userid.clone(), + auth_id: auth_id.clone(), ticket: password.clone(), token: "".to_string(), })); @@ -336,14 +341,14 @@ impl HttpClient { let renewal_future = async move { loop { tokio::time::delay_for(Duration::new(60*15, 0)).await; // 15 minutes - let (userid, ticket) = { + let (auth_id, ticket) = { let authinfo = auth2.read().unwrap().clone(); - (authinfo.userid, authinfo.ticket) + (authinfo.auth_id, authinfo.ticket) }; - match Self::credentials(client2.clone(), server2.clone(), port, userid, ticket).await { + match Self::credentials(client2.clone(), server2.clone(), port, auth_id.user().clone(), ticket).await { Ok(auth) => { if use_ticket_cache & &prefix2.is_some() { - let _ = store_ticket_info(prefix2.as_ref().unwrap(), &server2, &auth.userid.to_string(), &auth.ticket, &auth.token); + let _ = store_ticket_info(prefix2.as_ref().unwrap(), &server2, &auth.auth_id.to_string(), &auth.ticket, &auth.token); } *auth2.write().unwrap() = auth; }, @@ -361,7 +366,7 @@ impl HttpClient { client.clone(), server.to_owned(), port, - userid.to_owned(), + auth_id.user().clone(), password.to_owned(), ).map_ok({ let server = server.to_string(); @@ -370,13 +375,20 @@ impl HttpClient { move |auth| { if use_ticket_cache & &prefix.is_some() { - let _ = store_ticket_info(prefix.as_ref().unwrap(), &server, &auth.userid.to_string(), &auth.ticket, &auth.token); + let _ = store_ticket_info(prefix.as_ref().unwrap(), &server, &auth.auth_id.to_string(), &auth.ticket, &auth.token); } *authinfo.write().unwrap() = auth; tokio::spawn(renewal_future); } }); + let first_auth = if auth_id.is_token() { + // TODO check access here? + None + } else { + Some(BroadcastFuture::new(Box::new(login_future))) + }; + Ok(Self { client, server: String::from(server), @@ -384,7 +396,7 @@ impl HttpClient { fingerprint: verified_fingerprint, auth, ticket_abort, - first_auth: BroadcastFuture::new(Box::new(login_future)), + first_auth, _options: options, }) } @@ -394,7 +406,10 @@ impl HttpClient { /// Login is done on demand, so this is only required if you need /// access to authentication data in 'AuthInfo'. pub async fn login(&self) -> Result { - self.first_auth.listen().await?; + if let Some(future) = &self.first_auth { + future.listen().await?; + } + let authinfo = self.auth.read().unwrap(); Ok(authinfo.clone()) } @@ -477,10 +492,14 @@ impl HttpClient { let client = self.client.clone(); let auth = self.login().await?; - - let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)); - req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap()); - req.headers_mut().insert("CSRFPreventionToken", HeaderValue::from_str(&auth.token).unwrap()); + if auth.auth_id.is_token() { + let enc_api_token = format!("{}:{}", auth.auth_id, percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)); + req.headers_mut().insert("Authorization", HeaderValue::from_str(&enc_api_token).unwrap()); + } else { + let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)); + req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap()); + req.headers_mut().insert("CSRFPreventionToken", HeaderValue::from_str(&auth.token).unwrap()); + } Self::api_request(client, req).await } @@ -579,11 +598,18 @@ impl HttpClient { protocol_name: String, ) -> Result<(H2Client, futures::future::AbortHandle), Error> { - let auth = self.login().await?; let client = self.client.clone(); + let auth = self.login().await?; + + if auth.auth_id.is_token() { + let enc_api_token = format!("{}:{}", auth.auth_id, percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)); + req.headers_mut().insert("Authorization", HeaderValue::from_str(&enc_api_token).unwrap()); + } else { + let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)); + req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap()); + req.headers_mut().insert("CSRFPreventionToken", HeaderValue::from_str(&auth.token).unwrap()); + } - let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)); - req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap()); req.headers_mut().insert("UPGRADE", HeaderValue::from_str(&protocol_name).unwrap()); let resp = client.request(req).await?; @@ -636,7 +662,7 @@ impl HttpClient { let req = Self::request_builder(&server, port, "POST", "/api2/json/access/ticket", Some(data))?; let cred = Self::api_request(client, req).await?; let auth = AuthInfo { - userid: cred["data"]["username"].as_str().unwrap().parse()?, + auth_id: cred["data"]["username"].as_str().unwrap().parse()?, ticket: cred["data"]["ticket"].as_str().unwrap().to_owned(), token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(), }; diff --git a/src/client/pull.rs b/src/client/pull.rs index 7cb8d8e1..a80b10dc 100644 --- a/src/client/pull.rs +++ b/src/client/pull.rs @@ -451,7 +451,7 @@ pub async fn pull_group( .password(Some(auth_info.ticket.clone())) .fingerprint(fingerprint.clone()); - let new_client = HttpClient::new(src_repo.host(), src_repo.port(), src_repo.user(), options)?; + let new_client = HttpClient::new(src_repo.host(), src_repo.port(), src_repo.auth_id(), options)?; let reader = BackupReader::start( new_client, diff --git a/src/config/remote.rs b/src/config/remote.rs index 7ad653ac..14c57c0e 100644 --- a/src/config/remote.rs +++ b/src/config/remote.rs @@ -65,7 +65,7 @@ pub struct Remote { pub host: String, #[serde(skip_serializing_if="Option::is_none")] pub port: Option, - pub userid: Userid, + pub userid: Authid, #[serde(skip_serializing_if="String::is_empty")] #[serde(with = "proxmox::tools::serde::string_as_base64")] pub password: String, -- 2.20.1