client/remote: allow using ApiToken + secret
in place of user + password. Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
parent
babab85b56
commit
34aa8e13b6
@ -2,7 +2,7 @@ use std::io::Write;
|
|||||||
|
|
||||||
use anyhow::{Error};
|
use anyhow::{Error};
|
||||||
|
|
||||||
use proxmox_backup::api2::types::Userid;
|
use proxmox_backup::api2::types::Authid;
|
||||||
use proxmox_backup::client::{HttpClient, HttpClientOptions, BackupReader};
|
use proxmox_backup::client::{HttpClient, HttpClientOptions, BackupReader};
|
||||||
|
|
||||||
pub struct DummyWriter {
|
pub struct DummyWriter {
|
||||||
@ -26,13 +26,13 @@ async fn run() -> Result<(), Error> {
|
|||||||
|
|
||||||
let host = "localhost";
|
let host = "localhost";
|
||||||
|
|
||||||
let username = Userid::root_userid();
|
let auth_id = Authid::root_auth_id();
|
||||||
|
|
||||||
let options = HttpClientOptions::new()
|
let options = HttpClientOptions::new()
|
||||||
.interactive(true)
|
.interactive(true)
|
||||||
.ticket_cache(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")?;
|
let backup_time = proxmox::tools::time::parse_rfc3339("2019-06-28T10:49:48Z")?;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use anyhow::{Error};
|
use anyhow::{Error};
|
||||||
|
|
||||||
use proxmox_backup::api2::types::Userid;
|
use proxmox_backup::api2::types::Authid;
|
||||||
use proxmox_backup::client::*;
|
use proxmox_backup::client::*;
|
||||||
|
|
||||||
async fn upload_speed() -> Result<f64, Error> {
|
async fn upload_speed() -> Result<f64, Error> {
|
||||||
@ -8,13 +8,13 @@ async fn upload_speed() -> Result<f64, Error> {
|
|||||||
let host = "localhost";
|
let host = "localhost";
|
||||||
let datastore = "store2";
|
let datastore = "store2";
|
||||||
|
|
||||||
let username = Userid::root_userid();
|
let auth_id = Authid::root_auth_id();
|
||||||
|
|
||||||
let options = HttpClientOptions::new()
|
let options = HttpClientOptions::new()
|
||||||
.interactive(true)
|
.interactive(true)
|
||||||
.ticket_cache(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();
|
let backup_time = proxmox::tools::time::epoch_i64();
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ pub fn update_remote(
|
|||||||
comment: Option<String>,
|
comment: Option<String>,
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
userid: Option<Userid>,
|
userid: Option<Authid>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
fingerprint: Option<String>,
|
fingerprint: Option<String>,
|
||||||
delete: Option<Vec<DeletableProperty>>,
|
delete: Option<Vec<DeletableProperty>>,
|
||||||
|
@ -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 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
|
let _auth_info = client.login() // make sure we can auth
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?;
|
.map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?;
|
||||||
|
@ -67,7 +67,7 @@ const_regex!{
|
|||||||
|
|
||||||
pub DNS_NAME_OR_IP_REGEX = concat!(r"^(?:", DNS_NAME!(), "|", IPRE!(), r")$");
|
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}$";
|
pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ pub fn complete_repository(_arg: &str, _param: &HashMap<String, String>) -> Vec<
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect(server: &str, port: u16, userid: &Userid) -> Result<HttpClient, Error> {
|
fn connect(server: &str, port: u16, auth_id: &Authid) -> Result<HttpClient, Error> {
|
||||||
|
|
||||||
let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
|
let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ fn connect(server: &str, port: u16, userid: &Userid) -> Result<HttpClient, Error
|
|||||||
.fingerprint_cache(true)
|
.fingerprint_cache(true)
|
||||||
.ticket_cache(true);
|
.ticket_cache(true);
|
||||||
|
|
||||||
HttpClient::new(server, port, userid, options)
|
HttpClient::new(server, port, auth_id, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn view_task_result(
|
async fn view_task_result(
|
||||||
@ -366,7 +366,7 @@ async fn list_backup_groups(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let repo = extract_repository_from_value(¶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 path = format!("api2/json/admin/datastore/{}/groups", repo.store());
|
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 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");
|
param.as_object_mut().unwrap().remove("repository");
|
||||||
|
|
||||||
@ -478,7 +478,7 @@ async fn list_snapshots(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let output_format = get_output_format(¶m);
|
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<BackupGroup> = if let Some(path) = param["group"].as_str() {
|
let group: Option<BackupGroup> = if let Some(path) = param["group"].as_str() {
|
||||||
Some(path.parse()?)
|
Some(path.parse()?)
|
||||||
@ -543,7 +543,7 @@ async fn forget_snapshots(param: Value) -> Result<Value, Error> {
|
|||||||
let path = tools::required_string_param(¶m, "snapshot")?;
|
let path = tools::required_string_param(¶m, "snapshot")?;
|
||||||
let snapshot: BackupDir = path.parse()?;
|
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());
|
let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store());
|
||||||
|
|
||||||
@ -573,7 +573,7 @@ async fn api_login(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let repo = extract_repository_from_value(¶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())?;
|
||||||
client.login().await?;
|
client.login().await?;
|
||||||
|
|
||||||
record_repository(&repo);
|
record_repository(&repo);
|
||||||
@ -630,7 +630,7 @@ async fn api_version(param: Value) -> Result<(), Error> {
|
|||||||
|
|
||||||
let repo = extract_repository_from_value(¶m);
|
let repo = extract_repository_from_value(¶m);
|
||||||
if let Ok(repo) = repo {
|
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 {
|
match client.get("api2/json/version", None).await {
|
||||||
Ok(mut result) => version_info["server"] = result["data"].take(),
|
Ok(mut result) => version_info["server"] = result["data"].take(),
|
||||||
@ -680,7 +680,7 @@ async fn list_snapshot_files(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let output_format = get_output_format(¶m);
|
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());
|
let path = format!("api2/json/admin/datastore/{}/files", repo.store());
|
||||||
|
|
||||||
@ -724,7 +724,7 @@ async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let output_format = get_output_format(¶m);
|
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());
|
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 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);
|
record_repository(&repo);
|
||||||
|
|
||||||
println!("Starting backup: {}/{}/{}", backup_type, backup_id, BackupDir::backup_time_to_string(backup_time)?);
|
println!("Starting backup: {}/{}/{}", backup_type, backup_id, BackupDir::backup_time_to_string(backup_time)?);
|
||||||
@ -1339,7 +1339,7 @@ async fn restore(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let archive_name = tools::required_string_param(¶m, "archive-name")?;
|
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);
|
record_repository(&repo);
|
||||||
|
|
||||||
@ -1512,7 +1512,7 @@ async fn upload_log(param: Value) -> Result<Value, Error> {
|
|||||||
let snapshot = tools::required_string_param(¶m, "snapshot")?;
|
let snapshot = tools::required_string_param(¶m, "snapshot")?;
|
||||||
let snapshot: BackupDir = snapshot.parse()?;
|
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)?;
|
let (keydata, crypt_mode) = keyfile_parameters(¶m)?;
|
||||||
|
|
||||||
@ -1583,7 +1583,7 @@ fn prune<'a>(
|
|||||||
async fn prune_async(mut param: Value) -> Result<Value, Error> {
|
async fn prune_async(mut param: Value) -> Result<Value, Error> {
|
||||||
let repo = extract_repository_from_value(¶m)?;
|
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());
|
let path = format!("api2/json/admin/datastore/{}/prune", repo.store());
|
||||||
|
|
||||||
@ -1669,7 +1669,7 @@ async fn status(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let output_format = get_output_format(¶m);
|
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());
|
let path = format!("api2/json/admin/datastore/{}/status", repo.store());
|
||||||
|
|
||||||
@ -1714,7 +1714,7 @@ async fn try_get(repo: &BackupRepository, url: &str) -> Value {
|
|||||||
.fingerprint_cache(true)
|
.fingerprint_cache(true)
|
||||||
.ticket_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,
|
Ok(v) => v,
|
||||||
_ => return Value::Null,
|
_ => return Value::Null,
|
||||||
};
|
};
|
||||||
|
@ -62,10 +62,10 @@ fn connect() -> Result<HttpClient, Error> {
|
|||||||
let ticket = Ticket::new("PBS", Userid::root_userid())?
|
let ticket = Ticket::new("PBS", Userid::root_userid())?
|
||||||
.sign(private_auth_key(), None)?;
|
.sign(private_auth_key(), None)?;
|
||||||
options = options.password(Some(ticket));
|
options = options.password(Some(ticket));
|
||||||
HttpClient::new("localhost", 8007, Userid::root_userid(), options)?
|
HttpClient::new("localhost", 8007, Authid::root_auth_id(), options)?
|
||||||
} else {
|
} else {
|
||||||
options = options.ticket_cache(true).interactive(true);
|
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)
|
Ok(client)
|
||||||
|
@ -225,7 +225,7 @@ async fn test_upload_speed(
|
|||||||
|
|
||||||
let backup_time = proxmox::tools::time::epoch_i64();
|
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);
|
record_repository(&repo);
|
||||||
|
|
||||||
if verbose { eprintln!("Connecting to backup server"); }
|
if verbose { eprintln!("Connecting to backup server"); }
|
||||||
|
@ -79,7 +79,7 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = connect(repo.host(), repo.port(), repo.user())?;
|
let client = connect(repo.host(), repo.port(), repo.auth_id())?;
|
||||||
|
|
||||||
let client = BackupReader::start(
|
let client = BackupReader::start(
|
||||||
client,
|
client,
|
||||||
@ -153,7 +153,7 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> {
|
|||||||
/// Shell to interactively inspect and restore snapshots.
|
/// Shell to interactively inspect and restore snapshots.
|
||||||
async fn catalog_shell(param: Value) -> Result<(), Error> {
|
async fn catalog_shell(param: Value) -> Result<(), Error> {
|
||||||
let repo = extract_repository_from_value(¶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 path = tools::required_string_param(¶m, "snapshot")?;
|
let path = tools::required_string_param(¶m, "snapshot")?;
|
||||||
let archive_name = tools::required_string_param(¶m, "archive-name")?;
|
let archive_name = tools::required_string_param(¶m, "archive-name")?;
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ fn mount(
|
|||||||
async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> {
|
async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> {
|
||||||
let repo = extract_repository_from_value(¶m)?;
|
let repo = extract_repository_from_value(¶m)?;
|
||||||
let archive_name = tools::required_string_param(¶m, "archive-name")?;
|
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();
|
let target = param["target"].as_str();
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ async fn task_list(param: Value) -> Result<Value, Error> {
|
|||||||
let output_format = get_output_format(¶m);
|
let output_format = get_output_format(¶m);
|
||||||
|
|
||||||
let repo = extract_repository_from_value(¶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 limit = param["limit"].as_u64().unwrap_or(50) as usize;
|
||||||
let running = !param["all"].as_bool().unwrap_or(false);
|
let running = !param["all"].as_bool().unwrap_or(false);
|
||||||
@ -57,7 +57,7 @@ async fn task_list(param: Value) -> Result<Value, Error> {
|
|||||||
"running": running,
|
"running": running,
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"userfilter": repo.user(),
|
"userfilter": repo.auth_id(),
|
||||||
"store": repo.store(),
|
"store": repo.store(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ async fn task_log(param: Value) -> Result<Value, Error> {
|
|||||||
let repo = extract_repository_from_value(¶m)?;
|
let repo = extract_repository_from_value(¶m)?;
|
||||||
let upid = tools::required_string_param(¶m, "upid")?;
|
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?;
|
display_task_log(client, upid, true).await?;
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ async fn task_stop(param: Value) -> Result<Value, Error> {
|
|||||||
let repo = extract_repository_from_value(¶m)?;
|
let repo = extract_repository_from_value(¶m)?;
|
||||||
let upid_str = tools::required_string_param(¶m, "upid")?;
|
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 path = format!("api2/json/nodes/localhost/tasks/{}", upid_str);
|
||||||
let _ = client.delete(&path, None).await?;
|
let _ = client.delete(&path, None).await?;
|
||||||
|
@ -16,7 +16,7 @@ pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_RE
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BackupRepository {
|
pub struct BackupRepository {
|
||||||
/// The user name used for Authentication
|
/// The user name used for Authentication
|
||||||
user: Option<Userid>,
|
auth_id: Option<Authid>,
|
||||||
/// The host name or IP address
|
/// The host name or IP address
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
/// The port
|
/// The port
|
||||||
@ -27,20 +27,29 @@ pub struct BackupRepository {
|
|||||||
|
|
||||||
impl BackupRepository {
|
impl BackupRepository {
|
||||||
|
|
||||||
pub fn new(user: Option<Userid>, host: Option<String>, port: Option<u16>, store: String) -> Self {
|
pub fn new(auth_id: Option<Authid>, host: Option<String>, port: Option<u16>, store: String) -> Self {
|
||||||
let host = match host {
|
let host = match host {
|
||||||
Some(host) if (IP_V6_REGEX.regex_obj)().is_match(&host) => {
|
Some(host) if (IP_V6_REGEX.regex_obj)().is_match(&host) => {
|
||||||
Some(format!("[{}]", host))
|
Some(format!("[{}]", host))
|
||||||
},
|
},
|
||||||
other => other,
|
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 {
|
pub fn user(&self) -> &Userid {
|
||||||
if let Some(ref user) = self.user {
|
if let Some(auth_id) = &self.auth_id {
|
||||||
return &user;
|
return auth_id.user();
|
||||||
}
|
}
|
||||||
|
|
||||||
Userid::root_userid()
|
Userid::root_userid()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,8 +74,8 @@ impl BackupRepository {
|
|||||||
|
|
||||||
impl fmt::Display for BackupRepository {
|
impl fmt::Display for BackupRepository {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match (&self.user, &self.host, self.port) {
|
match (&self.auth_id, &self.host, self.port) {
|
||||||
(Some(user), _, _) => write!(f, "{}@{}:{}:{}", user, self.host(), self.port(), self.store),
|
(Some(auth_id), _, _) => write!(f, "{}@{}:{}:{}", auth_id, self.host(), self.port(), self.store),
|
||||||
(None, Some(host), None) => write!(f, "{}:{}", host, self.store),
|
(None, Some(host), None) => write!(f, "{}:{}", host, self.store),
|
||||||
(None, _, Some(port)) => write!(f, "{}:{}:{}", self.host(), port, self.store),
|
(None, _, Some(port)) => write!(f, "{}:{}:{}", self.host(), port, self.store),
|
||||||
(None, None, None) => write!(f, "{}", 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_or_else(|| format_err!("unable to parse repository url '{}'", url))?;
|
||||||
|
|
||||||
Ok(Self {
|
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()),
|
host: cap.get(2).map(|m| m.as_str().to_owned()),
|
||||||
port: cap.get(3).map(|m| m.as_str().parse::<u16>()).transpose()?,
|
port: cap.get(3).map(|m| m.as_str().parse::<u16>()).transpose()?,
|
||||||
store: cap[4].to_owned(),
|
store: cap[4].to_owned(),
|
||||||
|
@ -21,7 +21,7 @@ use proxmox::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::pipe_to_stream::PipeToSendStream;
|
use super::pipe_to_stream::PipeToSendStream;
|
||||||
use crate::api2::types::Userid;
|
use crate::api2::types::{Authid, Userid};
|
||||||
use crate::tools::{
|
use crate::tools::{
|
||||||
self,
|
self,
|
||||||
BroadcastFuture,
|
BroadcastFuture,
|
||||||
@ -31,7 +31,7 @@ use crate::tools::{
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AuthInfo {
|
pub struct AuthInfo {
|
||||||
pub userid: Userid,
|
pub auth_id: Authid,
|
||||||
pub ticket: String,
|
pub ticket: String,
|
||||||
pub token: String,
|
pub token: String,
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ pub struct HttpClient {
|
|||||||
server: String,
|
server: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
fingerprint: Arc<Mutex<Option<String>>>,
|
fingerprint: Arc<Mutex<Option<String>>>,
|
||||||
first_auth: BroadcastFuture<()>,
|
first_auth: Option<BroadcastFuture<()>>,
|
||||||
auth: Arc<RwLock<AuthInfo>>,
|
auth: Arc<RwLock<AuthInfo>>,
|
||||||
ticket_abort: futures::future::AbortHandle,
|
ticket_abort: futures::future::AbortHandle,
|
||||||
_options: HttpClientOptions,
|
_options: HttpClientOptions,
|
||||||
@ -251,7 +251,7 @@ impl HttpClient {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
server: &str,
|
server: &str,
|
||||||
port: u16,
|
port: u16,
|
||||||
userid: &Userid,
|
auth_id: &Authid,
|
||||||
mut options: HttpClientOptions,
|
mut options: HttpClientOptions,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
|
||||||
@ -311,6 +311,11 @@ impl HttpClient {
|
|||||||
let password = if let Some(password) = password {
|
let password = if let Some(password) = password {
|
||||||
password
|
password
|
||||||
} else {
|
} else {
|
||||||
|
let userid = if auth_id.is_token() {
|
||||||
|
bail!("API token secret must be provided!");
|
||||||
|
} else {
|
||||||
|
auth_id.user()
|
||||||
|
};
|
||||||
let mut ticket_info = None;
|
let mut ticket_info = None;
|
||||||
if use_ticket_cache {
|
if use_ticket_cache {
|
||||||
ticket_info = load_ticket_info(options.prefix.as_ref().unwrap(), server, userid);
|
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 {
|
let auth = Arc::new(RwLock::new(AuthInfo {
|
||||||
userid: userid.clone(),
|
auth_id: auth_id.clone(),
|
||||||
ticket: password.clone(),
|
ticket: password.clone(),
|
||||||
token: "".to_string(),
|
token: "".to_string(),
|
||||||
}));
|
}));
|
||||||
@ -336,14 +341,14 @@ impl HttpClient {
|
|||||||
let renewal_future = async move {
|
let renewal_future = async move {
|
||||||
loop {
|
loop {
|
||||||
tokio::time::delay_for(Duration::new(60*15, 0)).await; // 15 minutes
|
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();
|
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) => {
|
Ok(auth) => {
|
||||||
if use_ticket_cache & &prefix2.is_some() {
|
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;
|
*auth2.write().unwrap() = auth;
|
||||||
},
|
},
|
||||||
@ -361,7 +366,7 @@ impl HttpClient {
|
|||||||
client.clone(),
|
client.clone(),
|
||||||
server.to_owned(),
|
server.to_owned(),
|
||||||
port,
|
port,
|
||||||
userid.to_owned(),
|
auth_id.user().clone(),
|
||||||
password.to_owned(),
|
password.to_owned(),
|
||||||
).map_ok({
|
).map_ok({
|
||||||
let server = server.to_string();
|
let server = server.to_string();
|
||||||
@ -370,13 +375,20 @@ impl HttpClient {
|
|||||||
|
|
||||||
move |auth| {
|
move |auth| {
|
||||||
if use_ticket_cache & &prefix.is_some() {
|
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;
|
*authinfo.write().unwrap() = auth;
|
||||||
tokio::spawn(renewal_future);
|
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 {
|
Ok(Self {
|
||||||
client,
|
client,
|
||||||
server: String::from(server),
|
server: String::from(server),
|
||||||
@ -384,7 +396,7 @@ impl HttpClient {
|
|||||||
fingerprint: verified_fingerprint,
|
fingerprint: verified_fingerprint,
|
||||||
auth,
|
auth,
|
||||||
ticket_abort,
|
ticket_abort,
|
||||||
first_auth: BroadcastFuture::new(Box::new(login_future)),
|
first_auth,
|
||||||
_options: options,
|
_options: options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -394,7 +406,10 @@ impl HttpClient {
|
|||||||
/// Login is done on demand, so this is only required if you need
|
/// Login is done on demand, so this is only required if you need
|
||||||
/// access to authentication data in 'AuthInfo'.
|
/// access to authentication data in 'AuthInfo'.
|
||||||
pub async fn login(&self) -> Result<AuthInfo, Error> {
|
pub async fn login(&self) -> Result<AuthInfo, Error> {
|
||||||
self.first_auth.listen().await?;
|
if let Some(future) = &self.first_auth {
|
||||||
|
future.listen().await?;
|
||||||
|
}
|
||||||
|
|
||||||
let authinfo = self.auth.read().unwrap();
|
let authinfo = self.auth.read().unwrap();
|
||||||
Ok(authinfo.clone())
|
Ok(authinfo.clone())
|
||||||
}
|
}
|
||||||
@ -477,10 +492,14 @@ impl HttpClient {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
|
|
||||||
let auth = self.login().await?;
|
let auth = self.login().await?;
|
||||||
|
if auth.auth_id.is_token() {
|
||||||
let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET));
|
let enc_api_token = format!("{}:{}", auth.auth_id, percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET));
|
||||||
req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap());
|
req.headers_mut().insert("Authorization", HeaderValue::from_str(&enc_api_token).unwrap());
|
||||||
req.headers_mut().insert("CSRFPreventionToken", HeaderValue::from_str(&auth.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
|
Self::api_request(client, req).await
|
||||||
}
|
}
|
||||||
@ -579,11 +598,18 @@ impl HttpClient {
|
|||||||
protocol_name: String,
|
protocol_name: String,
|
||||||
) -> Result<(H2Client, futures::future::AbortHandle), Error> {
|
) -> Result<(H2Client, futures::future::AbortHandle), Error> {
|
||||||
|
|
||||||
let auth = self.login().await?;
|
|
||||||
let client = self.client.clone();
|
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());
|
req.headers_mut().insert("UPGRADE", HeaderValue::from_str(&protocol_name).unwrap());
|
||||||
|
|
||||||
let resp = client.request(req).await?;
|
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 req = Self::request_builder(&server, port, "POST", "/api2/json/access/ticket", Some(data))?;
|
||||||
let cred = Self::api_request(client, req).await?;
|
let cred = Self::api_request(client, req).await?;
|
||||||
let auth = AuthInfo {
|
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(),
|
ticket: cred["data"]["ticket"].as_str().unwrap().to_owned(),
|
||||||
token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(),
|
token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(),
|
||||||
};
|
};
|
||||||
|
@ -451,7 +451,7 @@ pub async fn pull_group(
|
|||||||
.password(Some(auth_info.ticket.clone()))
|
.password(Some(auth_info.ticket.clone()))
|
||||||
.fingerprint(fingerprint.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(
|
let reader = BackupReader::start(
|
||||||
new_client,
|
new_client,
|
||||||
|
@ -65,7 +65,7 @@ pub struct Remote {
|
|||||||
pub host: String,
|
pub host: String,
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
pub userid: Userid,
|
pub userid: Authid,
|
||||||
#[serde(skip_serializing_if="String::is_empty")]
|
#[serde(skip_serializing_if="String::is_empty")]
|
||||||
#[serde(with = "proxmox::tools::serde::string_as_base64")]
|
#[serde(with = "proxmox::tools::serde::string_as_base64")]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
Loading…
Reference in New Issue
Block a user