Authenticate configured WebDAV users with omitted empty passwords
This commit is contained in:
+66
-27
@@ -26,6 +26,28 @@ struct Session {
|
|||||||
authorization: Option<String>,
|
authorization: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
fn from_config(config: &[(String, Setting)]) -> Result<Self, ErrorCode> {
|
||||||
|
let lookup = |wanted: &str| {
|
||||||
|
config
|
||||||
|
.iter()
|
||||||
|
.find(|(key, _)| key == wanted)
|
||||||
|
.and_then(|(_, value)| text(Some(value.clone())))
|
||||||
|
};
|
||||||
|
let root = lookup("url").ok_or(ErrorCode::InvalidInput)?;
|
||||||
|
if !root.starts_with("https://") && !root.starts_with("http://") {
|
||||||
|
return Err(ErrorCode::InvalidInput);
|
||||||
|
}
|
||||||
|
let authorization = lookup("username").map(|username| {
|
||||||
|
dav::basic(&username, &lookup("password").unwrap_or_default())
|
||||||
|
});
|
||||||
|
Ok(Self {
|
||||||
|
root,
|
||||||
|
authorization,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static SESSION: RefCell<Option<Session>> = const { RefCell::new(None) };
|
static SESSION: RefCell<Option<Session>> = const { RefCell::new(None) };
|
||||||
}
|
}
|
||||||
@@ -227,26 +249,7 @@ impl Guest for Webdav {
|
|||||||
config: Vec<(String, Setting)>,
|
config: Vec<(String, Setting)>,
|
||||||
) -> Result<Capabilities, ErrorCode> {
|
) -> Result<Capabilities, ErrorCode> {
|
||||||
SESSION.set(None);
|
SESSION.set(None);
|
||||||
let lookup = |wanted: &str| {
|
SESSION.set(Some(Session::from_config(&config)?));
|
||||||
config
|
|
||||||
.iter()
|
|
||||||
.find(|(key, _)| key == wanted)
|
|
||||||
.and_then(|(_, value)| text(Some(value.clone())))
|
|
||||||
};
|
|
||||||
let root = lookup("url").ok_or(ErrorCode::InvalidInput)?;
|
|
||||||
if !root.starts_with("https://") && !root.starts_with("http://") {
|
|
||||||
return Err(ErrorCode::InvalidInput);
|
|
||||||
}
|
|
||||||
let authorization = match (lookup("username"), lookup("password")) {
|
|
||||||
(Some(username), Some(password)) => {
|
|
||||||
Some(dav::basic(&username, &password))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
SESSION.set(Some(Session {
|
|
||||||
root,
|
|
||||||
authorization,
|
|
||||||
}));
|
|
||||||
let probe = Self::stat(Locator::Path("/".to_owned())).and_then(|info| {
|
let probe = Self::stat(Locator::Path("/".to_owned())).and_then(|info| {
|
||||||
if matches!(info.kind, EntryKind::Directory) {
|
if matches!(info.kind, EntryKind::Directory) {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -429,23 +432,59 @@ sdk::export!(Webdav with_types_in sdk);
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{SESSION, Session, Webdav, text};
|
use super::{SESSION, Session, Webdav};
|
||||||
use crate::sdk::{ErrorCode, Guest, Setting};
|
use crate::sdk::{ErrorCode, Guest, Setting};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn passwords_keep_leading_and_trailing_whitespace() {
|
async fn passwords_keep_leading_and_trailing_whitespace()
|
||||||
|
-> Result<(), ErrorCode> {
|
||||||
|
let config = vec![
|
||||||
|
(
|
||||||
|
"url".to_owned(),
|
||||||
|
Setting::Text("https://dav.example/".to_owned()),
|
||||||
|
),
|
||||||
|
("username".to_owned(), Setting::Text("user".to_owned())),
|
||||||
|
(
|
||||||
|
"password".to_owned(),
|
||||||
|
Setting::Secret(" pass word \t".to_owned()),
|
||||||
|
),
|
||||||
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text(Some(Setting::Secret(" pass word \t".to_owned()))),
|
Session::from_config(&config)?.authorization.as_deref(),
|
||||||
Some(" pass word \t".to_owned())
|
Some("Basic dXNlcjogcGFzcyB3b3JkIAk=")
|
||||||
);
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn an_empty_password_still_supplies_basic_auth_credentials() {
|
async fn a_username_without_a_password_setting_sends_basic_auth()
|
||||||
|
-> Result<(), ErrorCode> {
|
||||||
|
let config = vec![
|
||||||
|
(
|
||||||
|
"url".to_owned(),
|
||||||
|
Setting::Text("https://dav.example/".to_owned()),
|
||||||
|
),
|
||||||
|
("username".to_owned(), Setting::Text("user".to_owned())),
|
||||||
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text(Some(Setting::Secret(String::new()))),
|
Session::from_config(&config)?.authorization.as_deref(),
|
||||||
Some(String::new())
|
Some("Basic dXNlcjo=")
|
||||||
);
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn no_username_keeps_the_connection_anonymous() -> Result<(), ErrorCode>
|
||||||
|
{
|
||||||
|
let config = vec![
|
||||||
|
(
|
||||||
|
"url".to_owned(),
|
||||||
|
Setting::Text("https://dav.example/".to_owned()),
|
||||||
|
),
|
||||||
|
("password".to_owned(), Setting::Secret("unused".to_owned())),
|
||||||
|
];
|
||||||
|
assert!(Session::from_config(&config)?.authorization.is_none());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
Reference in New Issue
Block a user