Browse Source

implemented sigoden's suggestions

pull/60/head
Joe Koop 3 years ago
parent
commit
e799117959
No known key found for this signature in database
GPG Key ID: B2D0C6242D5AC1FF
  1. 21
      src/args.rs
  2. 68
      src/auth.rs
  3. 10
      src/server.rs
  4. 92
      tests/auth.rs

21
src/args.rs

@ -5,6 +5,7 @@ use std::net::IpAddr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::auth::AccessControl; use crate::auth::AccessControl;
use crate::auth::AuthMethod;
use crate::tls::{load_certs, load_private_key}; use crate::tls::{load_certs, load_private_key};
use crate::BoxResult; use crate::BoxResult;
@ -48,10 +49,12 @@ fn app() -> Command<'static> {
.help("Specify an url path prefix"), .help("Specify an url path prefix"),
) )
.arg( .arg(
Arg::new("basic-auth") Arg::new("auth-method")
.short('B') .long("auth-method")
.long("basic-auth") .help("Choose auth method")
.help("Use HTTP basic auth instead of digest auth"), .possible_values(["basic", "digest"])
.default_value("digest")
.value_name("value"),
) )
.arg( .arg(
Arg::new("auth") Arg::new("auth")
@ -129,7 +132,7 @@ pub struct Args {
pub path_is_file: bool, pub path_is_file: bool,
pub path_prefix: String, pub path_prefix: String,
pub uri_prefix: String, pub uri_prefix: String,
pub basic_auth: bool, pub auth_method: AuthMethod,
pub auth: AccessControl, pub auth: AccessControl,
pub allow_upload: bool, pub allow_upload: bool,
pub allow_delete: bool, pub allow_delete: bool,
@ -169,7 +172,11 @@ impl Args {
.values_of("auth") .values_of("auth")
.map(|v| v.collect()) .map(|v| v.collect())
.unwrap_or_default(); .unwrap_or_default();
let basic_auth = matches.is_present("basic-auth"); let auth_method = match matches.value_of("auth-method").unwrap() {
"basic" => AuthMethod::Basic,
"digest" => AuthMethod::Digest,
_ => todo!(),
};
let auth = AccessControl::new(&auth, &uri_prefix)?; let auth = AccessControl::new(&auth, &uri_prefix)?;
let allow_upload = matches.is_present("allow-all") || matches.is_present("allow-upload"); let allow_upload = matches.is_present("allow-all") || matches.is_present("allow-upload");
let allow_delete = matches.is_present("allow-all") || matches.is_present("allow-delete"); let allow_delete = matches.is_present("allow-all") || matches.is_present("allow-delete");
@ -193,7 +200,7 @@ impl Args {
path_is_file, path_is_file,
path_prefix, path_prefix,
uri_prefix, uri_prefix,
basic_auth, auth_method,
auth, auth,
enable_cors, enable_cors,
allow_delete, allow_delete,

68
src/auth.rs

@ -76,7 +76,7 @@ impl AccessControl {
path: &str, path: &str,
method: &Method, method: &Method,
authorization: Option<&HeaderValue>, authorization: Option<&HeaderValue>,
basic_auth: bool, auth_method: AuthMethod,
) -> GuardType { ) -> GuardType {
if self.rules.is_empty() { if self.rules.is_empty() {
return GuardType::ReadWrite; return GuardType::ReadWrite;
@ -87,11 +87,10 @@ impl AccessControl {
controls.push(control); controls.push(control);
if let Some(authorization) = authorization { if let Some(authorization) = authorization {
let Account { user, pass } = &control.readwrite; let Account { user, pass } = &control.readwrite;
if basic_auth { if auth_method
if valid_basic_auth(authorization, user, pass).is_some() { .validate(authorization, method.as_str(), user, pass)
return GuardType::ReadWrite; .is_some()
} {
} else if valid_digest(authorization, method.as_str(), user, pass).is_some() {
return GuardType::ReadWrite; return GuardType::ReadWrite;
} }
} }
@ -104,11 +103,10 @@ impl AccessControl {
} }
if let Some(authorization) = authorization { if let Some(authorization) = authorization {
if let Some(Account { user, pass }) = &control.readonly { if let Some(Account { user, pass }) = &control.readonly {
if basic_auth { if auth_method
if valid_basic_auth(authorization, user, pass).is_some() { .validate(authorization, method.as_str(), user, pass)
return GuardType::ReadOnly; .is_some()
} {
} else if valid_digest(authorization, method.as_str(), user, pass).is_some() {
return GuardType::ReadOnly; return GuardType::ReadOnly;
} }
} }
@ -176,10 +174,19 @@ impl Account {
} }
} }
pub fn generate_www_auth(stale: bool, basic_auth: bool) -> String { #[derive(Debug, Clone)]
if basic_auth { pub enum AuthMethod {
Basic,
Digest,
}
impl AuthMethod {
pub fn www_auth(&self, stale: bool) -> String {
match self {
AuthMethod::Basic => {
format!("Basic realm=\"{}\"", REALM) format!("Basic realm=\"{}\"", REALM)
} else { }
AuthMethod::Digest => {
let str_stale = if stale { "stale=true," } else { "" }; let str_stale = if stale { "stale=true," } else { "" };
format!( format!(
"Digest realm=\"{}\",nonce=\"{}\",{}qop=\"auth\"", "Digest realm=\"{}\",nonce=\"{}\",{}qop=\"auth\"",
@ -188,15 +195,21 @@ pub fn generate_www_auth(stale: bool, basic_auth: bool) -> String {
str_stale str_stale
) )
} }
} }
}
pub fn valid_basic_auth( pub fn validate(
&self,
authorization: &HeaderValue, authorization: &HeaderValue,
method: &str,
auth_user: &str, auth_user: &str,
auth_pass: &str, auth_pass: &str,
) -> Option<()> { ) -> Option<()> {
let value: Vec<u8> = base64::decode(strip_prefix(authorization.as_bytes(), b"Basic ").unwrap()).unwrap(); match self {
let parts: Vec<&str> = std::str::from_utf8(&value).unwrap().split(":").collect(); AuthMethod::Basic => {
let value: Vec<u8> =
base64::decode(strip_prefix(authorization.as_bytes(), b"Basic ").unwrap())
.unwrap();
let parts: Vec<&str> = std::str::from_utf8(&value).unwrap().split(':').collect();
if parts[0] != auth_user { if parts[0] != auth_user {
return None; return None;
@ -211,15 +224,9 @@ pub fn valid_basic_auth(
return Some(()); return Some(());
} }
return None; None
} }
AuthMethod::Digest => {
pub fn valid_digest(
authorization: &HeaderValue,
method: &str,
auth_user: &str,
auth_pass: &str,
) -> Option<()> {
let digest_value = strip_prefix(authorization.as_bytes(), b"Digest ")?; let digest_value = strip_prefix(authorization.as_bytes(), b"Digest ")?;
let user_vals = to_headermap(digest_value).ok()?; let user_vals = to_headermap(digest_value).ok()?;
if let (Some(username), Some(nonce), Some(user_response)) = ( if let (Some(username), Some(nonce), Some(user_response)) = (
@ -285,6 +292,9 @@ pub fn valid_digest(
} }
} }
None None
}
}
}
} }
/// Check if a nonce is still valid. /// Check if a nonce is still valid.

10
src/server.rs

@ -1,4 +1,3 @@
use crate::auth::generate_www_auth;
use crate::streamer::Streamer; use crate::streamer::Streamer;
use crate::utils::{decode_uri, encode_uri}; use crate::utils::{decode_uri, encode_uri};
use crate::{Args, BoxResult}; use crate::{Args, BoxResult};
@ -96,7 +95,12 @@ impl Server {
} }
let authorization = headers.get(AUTHORIZATION); let authorization = headers.get(AUTHORIZATION);
let guard_type = self.args.auth.guard(req_path, &method, authorization, self.args.basic_auth); let guard_type = self.args.auth.guard(
req_path,
&method,
authorization,
self.args.auth_method.clone(),
);
if guard_type.is_reject() { if guard_type.is_reject() {
self.auth_reject(&mut res); self.auth_reject(&mut res);
return Ok(res); return Ok(res);
@ -720,7 +724,7 @@ const DATA =
} }
fn auth_reject(&self, res: &mut Response) { fn auth_reject(&self, res: &mut Response) {
let value = generate_www_auth(false, self.args.basic_auth); let value = self.args.auth_method.www_auth(false);
set_webdav_headers(res); set_webdav_headers(res);
res.headers_mut().typed_insert(Connection::close()); res.headers_mut().typed_insert(Connection::close());
res.headers_mut() res.headers_mut()

92
tests/auth.rs

@ -6,7 +6,7 @@ use fixtures::{server, Error, TestServer};
use rstest::rstest; use rstest::rstest;
#[rstest] #[rstest]
fn no_auth_digest(#[with(&["--auth", "/@user:pass", "-A"])] server: TestServer) -> Result<(), Error> { fn no_auth(#[with(&["--auth", "/@user:pass", "-A"])] server: TestServer) -> Result<(), Error> {
let resp = reqwest::blocking::get(server.url())?; let resp = reqwest::blocking::get(server.url())?;
assert_eq!(resp.status(), 401); assert_eq!(resp.status(), 401);
assert!(resp.headers().contains_key("www-authenticate")); assert!(resp.headers().contains_key("www-authenticate"));
@ -17,7 +17,7 @@ fn no_auth_digest(#[with(&["--auth", "/@user:pass", "-A"])] server: TestServer)
} }
#[rstest] #[rstest]
fn auth_digest(#[with(&["--auth", "/@user:pass", "-A"])] server: TestServer) -> Result<(), Error> { fn auth(#[with(&["--auth", "/@user:pass", "-A"])] server: TestServer) -> Result<(), Error> {
let url = format!("{}file1", server.url()); let url = format!("{}file1", server.url());
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?; let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
assert_eq!(resp.status(), 401); assert_eq!(resp.status(), 401);
@ -29,14 +29,14 @@ fn auth_digest(#[with(&["--auth", "/@user:pass", "-A"])] server: TestServer) ->
} }
#[rstest] #[rstest]
fn auth_skip_digest(#[with(&["--auth", "/@user:pass@*"])] server: TestServer) -> Result<(), Error> { fn auth_skip(#[with(&["--auth", "/@user:pass@*"])] server: TestServer) -> Result<(), Error> {
let resp = reqwest::blocking::get(server.url())?; let resp = reqwest::blocking::get(server.url())?;
assert_eq!(resp.status(), 200); assert_eq!(resp.status(), 200);
Ok(()) Ok(())
} }
#[rstest] #[rstest]
fn auth_readonly_digest( fn auth_readonly(
#[with(&["--auth", "/@user:pass@user2:pass2", "-A"])] server: TestServer, #[with(&["--auth", "/@user:pass@user2:pass2", "-A"])] server: TestServer,
) -> Result<(), Error> { ) -> Result<(), Error> {
let url = format!("{}index.html", server.url()); let url = format!("{}index.html", server.url());
@ -53,7 +53,7 @@ fn auth_readonly_digest(
} }
#[rstest] #[rstest]
fn auth_nest_digest( fn auth_nest(
#[with(&["--auth", "/@user:pass@user2:pass2", "--auth", "/dira@user3:pass3", "-A"])] #[with(&["--auth", "/@user:pass@user2:pass2", "--auth", "/dira@user3:pass3", "-A"])]
server: TestServer, server: TestServer,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -72,7 +72,7 @@ fn auth_nest_digest(
} }
#[rstest] #[rstest]
fn auth_nest_share_digest( fn auth_nest_share(
#[with(&["--auth", "/@user:pass@*", "--auth", "/dira@user3:pass3", "-A"])] server: TestServer, #[with(&["--auth", "/@user:pass@*", "--auth", "/dira@user3:pass3", "-A"])] server: TestServer,
) -> Result<(), Error> { ) -> Result<(), Error> {
let url = format!("{}index.html", server.url()); let url = format!("{}index.html", server.url());
@ -80,83 +80,3 @@ fn auth_nest_share_digest(
assert_eq!(resp.status(), 200); assert_eq!(resp.status(), 200);
Ok(()) Ok(())
} }
#[rstest]
fn no_auth_basic(#[with(&["--basic-auth", "--auth", "/@user:pass", "-A"])] server: TestServer) -> Result<(), Error> {
let resp = reqwest::blocking::get(server.url())?;
assert_eq!(resp.status(), 401);
assert!(resp.headers().contains_key("www-authenticate"));
let url = format!("{}file1", server.url());
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
assert_eq!(resp.status(), 401);
Ok(())
}
#[rstest]
fn auth_basic(#[with(&["--basic-auth", "--auth", "/@user:pass", "-A"])] server: TestServer) -> Result<(), Error> {
let url = format!("{}file1", server.url());
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
assert_eq!(resp.status(), 401);
let resp = fetch!(b"PUT", &url)
.body(b"abc".to_vec())
.basic_auth("user", Some("pass"))
.send()?;
assert_eq!(resp.status(), 201);
Ok(())
}
#[rstest]
fn auth_skip_basic(#[with(&["--basic-auth", "--auth", "/@user:pass@*"])] server: TestServer) -> Result<(), Error> {
let resp = reqwest::blocking::get(server.url())?;
assert_eq!(resp.status(), 200);
Ok(())
}
#[rstest]
fn auth_readonly_basic(
#[with(&["--basic-auth", "--auth", "/@user:pass@user2:pass2", "-A"])] server: TestServer,
) -> Result<(), Error> {
let url = format!("{}index.html", server.url());
let resp = fetch!(b"GET", &url).send()?;
assert_eq!(resp.status(), 401);
let resp = fetch!(b"GET", &url).basic_auth("user2", Some("pass2")).send()?;
assert_eq!(resp.status(), 200);
let url = format!("{}file1", server.url());
let resp = fetch!(b"PUT", &url)
.body(b"abc".to_vec())
.basic_auth("user2", Some("pass2"))
.send()?;
assert_eq!(resp.status(), 401);
Ok(())
}
#[rstest]
fn auth_nest_basic(
#[with(&["--basic-auth", "--auth", "/@user:pass@user2:pass2", "--auth", "/dira@user3:pass3", "-A"])]
server: TestServer,
) -> Result<(), Error> {
let url = format!("{}dira/file1", server.url());
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
assert_eq!(resp.status(), 401);
let resp = fetch!(b"PUT", &url)
.body(b"abc".to_vec())
.basic_auth("user3", Some("pass3"))
.send()?;
assert_eq!(resp.status(), 201);
let resp = fetch!(b"PUT", &url)
.body(b"abc".to_vec())
.basic_auth("user", Some("pass"))
.send()?;
assert_eq!(resp.status(), 201);
Ok(())
}
#[rstest]
fn auth_nest_share_basic(
#[with(&["--basic-auth", "--auth", "/@user:pass@*", "--auth", "/dira@user3:pass3", "-A"])] server: TestServer,
) -> Result<(), Error> {
let url = format!("{}index.html", server.url());
let resp = fetch!(b"GET", &url).send()?;
assert_eq!(resp.status(), 200);
Ok(())
}

Loading…
Cancel
Save