in place of user + password.
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
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 {
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")?;
use anyhow::{Error};
-use proxmox_backup::api2::types::Userid;
+use proxmox_backup::api2::types::Authid;
use proxmox_backup::client::*;
async fn upload_speed() -> Result<f64, Error> {
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();
comment: Option<String>,
host: Option<String>,
port: Option<u16>,
- userid: Option<Userid>,
+ userid: Option<Authid>,
password: Option<String>,
fingerprint: Option<String>,
delete: Option<Vec<DeletableProperty>>,
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))?;
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}$";
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();
.fingerprint_cache(true)
.ticket_cache(true);
- HttpClient::new(server, port, userid, options)
+ HttpClient::new(server, port, auth_id, options)
}
async fn view_task_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());
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");
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() {
Some(path.parse()?)
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());
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);
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(),
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 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 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)?);
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);
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)?;
async fn prune_async(mut param: Value) -> Result<Value, Error> {
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 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());
.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,
};
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)
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"); }
}
};
- let client = connect(repo.host(), repo.port(), repo.user())?;
+ let client = connect(repo.host(), repo.port(), repo.auth_id())?;
let client = BackupReader::start(
client,
/// 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")?;
async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> {
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();
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);
"running": running,
"start": 0,
"limit": limit,
- "userfilter": repo.user(),
+ "userfilter": repo.auth_id(),
"store": repo.store(),
});
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?;
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?;
#[derive(Debug)]
pub struct BackupRepository {
/// The user name used for Authentication
- user: Option<Userid>,
+ auth_id: Option<Authid>,
/// The host name or IP address
host: Option<String>,
/// The port
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 {
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()
}
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),
.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::<u16>()).transpose()?,
store: cap[4].to_owned(),
};
use super::pipe_to_stream::PipeToSendStream;
-use crate::api2::types::Userid;
+use crate::api2::types::{Authid, Userid};
use crate::tools::{
self,
BroadcastFuture,
#[derive(Clone)]
pub struct AuthInfo {
- pub userid: Userid,
+ pub auth_id: Authid,
pub ticket: String,
pub token: String,
}
server: String,
port: u16,
fingerprint: Arc<Mutex<Option<String>>>,
- first_auth: BroadcastFuture<()>,
+ first_auth: Option<BroadcastFuture<()>>,
auth: Arc<RwLock<AuthInfo>>,
ticket_abort: futures::future::AbortHandle,
_options: HttpClientOptions,
pub fn new(
server: &str,
port: u16,
- userid: &Userid,
+ auth_id: &Authid,
mut options: HttpClientOptions,
) -> Result<Self, Error> {
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);
};
let auth = Arc::new(RwLock::new(AuthInfo {
- userid: userid.clone(),
+ auth_id: auth_id.clone(),
ticket: password.clone(),
token: "".to_string(),
}));
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;
},
client.clone(),
server.to_owned(),
port,
- userid.to_owned(),
+ auth_id.user().clone(),
password.to_owned(),
).map_ok({
let server = server.to_string();
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),
fingerprint: verified_fingerprint,
auth,
ticket_abort,
- first_auth: BroadcastFuture::new(Box::new(login_future)),
+ first_auth,
_options: options,
})
}
/// 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<AuthInfo, Error> {
- self.first_auth.listen().await?;
+ if let Some(future) = &self.first_auth {
+ future.listen().await?;
+ }
+
let authinfo = self.auth.read().unwrap();
Ok(authinfo.clone())
}
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
}
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?;
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(),
};
.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,
pub host: String,
#[serde(skip_serializing_if="Option::is_none")]
pub port: Option<u16>,
- pub userid: Userid,
+ pub userid: Authid,
#[serde(skip_serializing_if="String::is_empty")]
#[serde(with = "proxmox::tools::serde::string_as_base64")]
pub password: String,