P0: AWS Signature V4 implementation complete
- Full Canonical Request with signed headers - Proper URI encoding (encode_slash option) - X-Amz-Date timestamp support - SignedHeaders extraction from Authorization header - Payload hash from X-Amz-Content-Sha256 - 4 unit tests passing Tests: 297 passed (293 + 4 new)
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
use axum::{extract::Request, response::IntoResponse, Extension};
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::Request,
|
||||
http::{HeaderValue, StatusCode},
|
||||
middleware,
|
||||
response::IntoResponse,
|
||||
Extension,
|
||||
};
|
||||
use base64::Engine as _;
|
||||
use clap::Subcommand;
|
||||
use dav_server::{fakels::FakeLs, DavHandler};
|
||||
use dav_server::DavHandler;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -17,7 +25,14 @@ pub enum WebdavCommand {
|
||||
pub async fn handle_webdav_command(cmd: WebdavCommand) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
WebdavCommand::Start { port, user } => {
|
||||
let home_dir = PathBuf::from("/Users/accusys/momentry/var/sftpgo/data").join(&user);
|
||||
// Parse username and optional password (format: "name:password")
|
||||
let username = user.split(':').next().unwrap_or(&user).to_string();
|
||||
let password = user.split(':').nth(1).map(|s| s.to_string());
|
||||
|
||||
let default_root = format!("/Users/accusys/momentry/var/sftpgo/data/{}", username);
|
||||
let home_dir = PathBuf::from(
|
||||
std::env::var("MB_WEBDAV_ROOT").unwrap_or(default_root),
|
||||
);
|
||||
|
||||
if !home_dir.exists() {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -27,12 +42,15 @@ pub async fn handle_webdav_command(cmd: WebdavCommand) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
println!("=== MarkBase WebDAV Server (VFS) ===");
|
||||
println!("User: {}", user);
|
||||
println!("User: {}", username);
|
||||
if password.is_some() {
|
||||
println!("Auth: password protected");
|
||||
}
|
||||
println!("Port: {}", port);
|
||||
println!("Home: {}", home_dir.display());
|
||||
println!();
|
||||
|
||||
run_webdav_server(port, home_dir, user).await?;
|
||||
run_webdav_server(port, home_dir, username, password).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -42,6 +60,7 @@ async fn run_webdav_server(
|
||||
port: u16,
|
||||
home_dir: PathBuf,
|
||||
user: String,
|
||||
password: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
use axum::{routing::any, Router};
|
||||
use tokio::net::TcpListener;
|
||||
@@ -49,19 +68,56 @@ async fn run_webdav_server(
|
||||
let vfs = Box::new(crate::vfs::local_fs::LocalFs::new());
|
||||
let upload_hook = None;
|
||||
|
||||
let dav_fs = crate::webdav::VfsDavFs::new(vfs, home_dir, upload_hook, user);
|
||||
let dav_handler = crate::webdav::create_webdav_handler(vfs, home_dir, upload_hook, user.clone());
|
||||
|
||||
let dav_handler = DavHandler::builder()
|
||||
.filesystem(dav_fs)
|
||||
.locksystem(FakeLs::new())
|
||||
.strip_prefix("/webdav")
|
||||
.build_handler();
|
||||
async fn webdav_auth_middleware(
|
||||
Extension(expected): Extension<crate::webdav::WebdavCredentials>,
|
||||
req: Request,
|
||||
next: middleware::Next,
|
||||
) -> impl IntoResponse {
|
||||
let auth = req
|
||||
.headers()
|
||||
.get("Authorization")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.filter(|v| v.starts_with("Basic "))
|
||||
.and_then(|v| {
|
||||
let encoded = &v[6..];
|
||||
let decoded = base64::engine::general_purpose::STANDARD
|
||||
.decode(encoded)
|
||||
.ok()?;
|
||||
let creds = String::from_utf8(decoded).ok()?;
|
||||
let colon = creds.find(':')?;
|
||||
Some((creds[..colon].to_string(), creds[colon + 1..].to_string()))
|
||||
});
|
||||
|
||||
let valid = auth.is_some_and(|(u, p)| {
|
||||
u == expected.username && expected.password.as_ref().map_or(true, |exp| p == *exp)
|
||||
});
|
||||
|
||||
if !valid {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
[(
|
||||
"WWW-Authenticate",
|
||||
HeaderValue::from_static("Basic realm=\"MarkBase WebDAV\""),
|
||||
)],
|
||||
Body::from("Unauthorized"),
|
||||
).into_response();
|
||||
}
|
||||
|
||||
next.run(req).await
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/webdav", any(handle_dav))
|
||||
.route("/webdav/", any(handle_dav))
|
||||
.route("/webdav/*path", any(handle_dav))
|
||||
.layer(Extension(dav_handler));
|
||||
.layer(Extension(dav_handler))
|
||||
.layer(Extension(crate::webdav::WebdavCredentials {
|
||||
username: user,
|
||||
password,
|
||||
}))
|
||||
.layer(middleware::from_fn(webdav_auth_middleware));
|
||||
|
||||
let addr = format!("127.0.0.1:{}", port);
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
Reference in New Issue
Block a user