From 6a5c58ee79fc9b4714784ef136a377bc71e6d01d Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 18:56:23 +0700 Subject: Add support for hashed password (sha256) --- src/args.rs | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 825a4ac..404225a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -39,7 +39,7 @@ struct CLIArgs { /// Set authentication (username:password) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] - auth: Option<(String, String)>, + auth: Option, /// Generate a random 6-hexdigit route #[structopt(long = "random-route")] @@ -76,36 +76,42 @@ fn parse_interface(src: &str) -> Result { } /// Checks wether the auth string is valid, i.e. it follows the syntax username:password -fn parse_auth(src: &str) -> Result<(String, String), String> { +fn parse_auth(src: &str) -> Result { let mut split = src.splitn(2, ':'); + let errmsg = "Invalid credentials string, expected format is username:password".to_owned(); let username = match split.next() { Some(username) => username, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - let password = match split.next() { + let second_part = match split.next() { // This allows empty passwords, as the spec does not forbid it Some(password) => password, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - // To make it Windows-compatible, the password needs to be shorter than 255 characters. - // After 255 characters, Windows will truncate the value. - // As for the username, the spec does not mention a limit in length - if password.len() > 255 { - return Err("Password length cannot exceed 255 characters".to_owned()); - } + let password = match split.next() { + Some(hash) => match second_part { + "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), + _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) + }, + None => { + // To make it Windows-compatible, the password needs to be shorter than 255 characters. + // After 255 characters, Windows will truncate the value. + // As for the username, the spec does not mention a limit in length + if second_part.len() > 255 { + return Err("Password length cannot exceed 255 characters".to_owned()); + } + + auth::RequiredAuthPassword::Plain(second_part.to_owned()) + }, + }; - Ok((username.to_owned(), password.to_owned())) + Ok(auth::RequiredAuth { + username: username.to_owned(), + password, + }) } /// Parses the command line arguments @@ -121,11 +127,6 @@ pub fn parse_args() -> crate::MiniserveConfig { ] }; - let auth = match args.auth { - Some((username, password)) => Some(auth::BasicAuthParams { username, password }), - None => None, - }; - let random_route = if args.random_route { Some(nanoid::custom(6, &ROUTE_ALPHABET)) } else { @@ -141,7 +142,7 @@ pub fn parse_args() -> crate::MiniserveConfig { path: args.path.unwrap_or_else(|| PathBuf::from(".")), port: args.port, interfaces, - auth, + auth: args.auth, path_explicitly_chosen, no_symlinks: args.no_symlinks, random_route, -- cgit v1.2.3 From 090958451244c54fa0abe5791a12bedc26674c41 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 19:36:59 +0700 Subject: Add support for sha-512 --- src/args.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 404225a..9c96fd7 100644 --- a/src/args.rs +++ b/src/args.rs @@ -94,6 +94,7 @@ fn parse_auth(src: &str) -> Result { let password = match split.next() { Some(hash) => match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), + "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) }, None => { -- cgit v1.2.3 From 57d923329adaae200c5595e3dcb04e207460d59c Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 21:13:38 +0700 Subject: Fix parse_auth and add some tests --- src/args.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 9c96fd7..c7a4917 100644 --- a/src/args.rs +++ b/src/args.rs @@ -77,7 +77,7 @@ fn parse_interface(src: &str) -> Result { /// Checks wether the auth string is valid, i.e. it follows the syntax username:password fn parse_auth(src: &str) -> Result { - let mut split = src.splitn(2, ':'); + let mut split = src.splitn(3, ':'); let errmsg = "Invalid credentials string, expected format is username:password".to_owned(); let username = match split.next() { @@ -152,3 +152,47 @@ pub fn parse_args() -> crate::MiniserveConfig { file_upload: args.file_upload, } } + +#[cfg(test)] +mod tests { + use super::*; + + fn create_required_auth (username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { + use auth::*; + use RequiredAuthPassword::*; + + RequiredAuth { + username: username.to_owned(), + password: match encrypt { + "plain" => Plain(password.to_owned()), + "sha256" => Sha256(password.to_owned()), + "sha512" => Sha512(password.to_owned()), + _ => panic!("Unknown encryption type") + }, + } + } + + #[test] + fn parse_auth_plain() { + assert_eq!( + parse_auth("username:password").unwrap(), + create_required_auth("username", "password", "plain") + ); + } + + #[test] + fn parse_auth_sha256() { + assert_eq!( + parse_auth("username:sha256:hash").unwrap(), + create_required_auth("username", "hash", "sha256") + ); + } + + #[test] + fn parse_auth_sha512() { + assert_eq!( + parse_auth("username:sha512:hash").unwrap(), + create_required_auth("username", "hash", "sha512") + ); + } +} \ No newline at end of file -- cgit v1.2.3 From 8a548fd1eba41ecca91928b855eb4241352ba1d8 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 22:44:31 +0700 Subject: Resolve https://github.com/svenstaro/miniserve/pull/76#discussion_r277011948 --- src/args.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index c7a4917..d26fb41 100644 --- a/src/args.rs +++ b/src/args.rs @@ -91,22 +91,21 @@ fn parse_auth(src: &str) -> Result { None => return Err(errmsg), }; - let password = match split.next() { - Some(hash) => match second_part { + let password = if let Some(hash) = split.next() { + match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) - }, - None => { - // To make it Windows-compatible, the password needs to be shorter than 255 characters. - // After 255 characters, Windows will truncate the value. - // As for the username, the spec does not mention a limit in length - if second_part.len() > 255 { - return Err("Password length cannot exceed 255 characters".to_owned()); - } - - auth::RequiredAuthPassword::Plain(second_part.to_owned()) - }, + } + } else { + // To make it Windows-compatible, the password needs to be shorter than 255 characters. + // After 255 characters, Windows will truncate the value. + // As for the username, the spec does not mention a limit in length + if second_part.len() > 255 { + return Err("Password length cannot exceed 255 characters".to_owned()); + } + + auth::RequiredAuthPassword::Plain(second_part.to_owned()) }; Ok(auth::RequiredAuth { -- cgit v1.2.3 From 03c196b76f28384e4a340312be67aab4a9c7885d Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 23:05:44 +0700 Subject: Use '?' --- src/args.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index c7a4917..00d3c9f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -173,26 +173,32 @@ mod tests { } #[test] - fn parse_auth_plain() { + fn parse_auth_plain() -> Result<(), String> { assert_eq!( - parse_auth("username:password").unwrap(), + parse_auth("username:password")?, create_required_auth("username", "password", "plain") ); + + Ok(()) } #[test] - fn parse_auth_sha256() { + fn parse_auth_sha256() -> Result<(), String> { assert_eq!( - parse_auth("username:sha256:hash").unwrap(), + parse_auth("username:sha256:hash")?, create_required_auth("username", "hash", "sha256") ); + + Ok(()) } #[test] - fn parse_auth_sha512() { + fn parse_auth_sha512() -> Result<(), String> { assert_eq!( - parse_auth("username:sha512:hash").unwrap(), + parse_auth("username:sha512:hash")?, create_required_auth("username", "hash", "sha512") ); + + Ok(()) } } \ No newline at end of file -- cgit v1.2.3 From 4928988b04dd1c63aa3163dcd98e6483cae0e323 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 02:31:32 +0700 Subject: Correct error message --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 25d70ad..c749ac4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -95,7 +95,7 @@ fn parse_auth(src: &str) -> Result { match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), - _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) + _ => return Err("Invalid hash method, only accept either sha256 or sha512".to_owned()) } } else { // To make it Windows-compatible, the password needs to be shorter than 255 characters. -- cgit v1.2.3 From b57f0db13f5d2d55fb6ae2942279896fd3922586 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 02:59:29 +0700 Subject: Add tests for where parse_auth fails --- src/args.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index c749ac4..d42d317 100644 --- a/src/args.rs +++ b/src/args.rs @@ -200,4 +200,31 @@ mod tests { Ok(()) } + + #[test] + fn parse_auth_invalid_syntax() { + assert_eq!( + parse_auth("foo").unwrap_err(), + "Invalid credentials string, expected format is username:password".to_owned() + ); + } + + #[test] + fn parse_auth_invalid_hash_method() { + assert_eq!( + parse_auth("username:blahblah:hash").unwrap_err(), + "Invalid hash method, only accept either sha256 or sha512".to_owned() + ); + } + + #[test] + fn parse_auth_excessive_length() { + let password = &"x".repeat(256); + let param = "username:".to_owned() + password; + + assert_eq!( + parse_auth(&*param).unwrap_err(), + "Password length cannot exceed 255 characters".to_owned() + ); + } } \ No newline at end of file -- cgit v1.2.3 From a8983921f1b20112591bb41dc6f9ab6ba8340c2f Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 03:03:40 +0700 Subject: Update help description for --auth --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index d42d317..433840e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -37,7 +37,7 @@ struct CLIArgs { )] interfaces: Vec, - /// Set authentication (username:password) + /// Set authentication (username:password, username:sha256:hash, or username:sha512:hash) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] auth: Option, -- cgit v1.2.3 From f5d7a051a13ff6b16d69b80f91a06d264c0cd978 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Tue, 23 Apr 2019 23:12:03 +0700 Subject: Convert hex strings to raw byte vectors and compare them --- src/args.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 433840e..5617704 100644 --- a/src/args.rs +++ b/src/args.rs @@ -91,11 +91,16 @@ fn parse_auth(src: &str) -> Result { None => return Err(errmsg), }; - let password = if let Some(hash) = split.next() { + let password = if let Some(hash_hex) = split.next() { + let hash_bin = match hex::decode(hash_hex) { + Ok(hash_bin) => hash_bin, + _ => return Err("Hash string is not a valid hex code".to_owned()), + }; + match second_part { - "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), - "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), - _ => return Err("Invalid hash method, only accept either sha256 or sha512".to_owned()) + "sha256" => auth::RequiredAuthPassword::Sha256(hash_bin.to_owned()), + "sha512" => auth::RequiredAuthPassword::Sha512(hash_bin.to_owned()), + _ => return Err("Invalid hash method, only accept either sha256 or sha512".to_owned()), } } else { // To make it Windows-compatible, the password needs to be shorter than 255 characters. @@ -164,8 +169,8 @@ mod tests { username: username.to_owned(), password: match encrypt { "plain" => Plain(password.to_owned()), - "sha256" => Sha256(password.to_owned()), - "sha512" => Sha512(password.to_owned()), + "sha256" => Sha256(hex::decode(password.to_owned()).unwrap()), + "sha512" => Sha512(hex::decode(password.to_owned()).unwrap()), _ => panic!("Unknown encryption type") }, } @@ -184,8 +189,8 @@ mod tests { #[test] fn parse_auth_sha256() -> Result<(), String> { assert_eq!( - parse_auth("username:sha256:hash")?, - create_required_auth("username", "hash", "sha256") + parse_auth("username:sha256:abcd")?, + create_required_auth("username", "abcd", "sha256") ); Ok(()) @@ -194,8 +199,8 @@ mod tests { #[test] fn parse_auth_sha512() -> Result<(), String> { assert_eq!( - parse_auth("username:sha512:hash")?, - create_required_auth("username", "hash", "sha512") + parse_auth("username:sha512:abcd")?, + create_required_auth("username", "abcd", "sha512") ); Ok(()) @@ -212,11 +217,19 @@ mod tests { #[test] fn parse_auth_invalid_hash_method() { assert_eq!( - parse_auth("username:blahblah:hash").unwrap_err(), + parse_auth("username:blahblah:abcd").unwrap_err(), "Invalid hash method, only accept either sha256 or sha512".to_owned() ); } + #[test] + fn parse_auth_invalid_hash_string() { + assert_eq!( + parse_auth("username:sha256:invalid").unwrap_err(), + "Hash string is not a valid hex code".to_owned() + ); + } + #[test] fn parse_auth_excessive_length() { let password = &"x".repeat(256); @@ -227,4 +240,4 @@ mod tests { "Password length cannot exceed 255 characters".to_owned() ); } -} \ No newline at end of file +} -- cgit v1.2.3 From dbe6acefcb76b30a6f85ee4a622e3558d242598f Mon Sep 17 00:00:00 2001 From: khai96_ Date: Wed, 24 Apr 2019 19:01:27 +0700 Subject: Use format! instead of manual string concatenation --- src/args.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 5617704..b7e31b0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -232,11 +232,10 @@ mod tests { #[test] fn parse_auth_excessive_length() { - let password = &"x".repeat(256); - let param = "username:".to_owned() + password; + let auth_string = format!("username:{}", "x".repeat(256)); assert_eq!( - parse_auth(&*param).unwrap_err(), + parse_auth(&*auth_string).unwrap_err(), "Password length cannot exceed 255 characters".to_owned() ); } -- cgit v1.2.3 From 706649e38b850f6210dbface1a6c0ca5ccdb468f Mon Sep 17 00:00:00 2001 From: khai96_ Date: Wed, 24 Apr 2019 19:03:48 +0700 Subject: Run cargo fmt --- src/args.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index b7e31b0..f13d14f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -161,7 +161,7 @@ pub fn parse_args() -> crate::MiniserveConfig { mod tests { use super::*; - fn create_required_auth (username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { + fn create_required_auth(username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { use auth::*; use RequiredAuthPassword::*; @@ -171,7 +171,7 @@ mod tests { "plain" => Plain(password.to_owned()), "sha256" => Sha256(hex::decode(password.to_owned()).unwrap()), "sha512" => Sha512(hex::decode(password.to_owned()).unwrap()), - _ => panic!("Unknown encryption type") + _ => panic!("Unknown encryption type"), }, } } -- cgit v1.2.3 From 0b731cf0a0337f4e406baf4cbbcc407e7d9af9eb Mon Sep 17 00:00:00 2001 From: khai96_ Date: Thu, 25 Apr 2019 19:13:18 +0700 Subject: Use rstest_parametrize for unit tests --- src/args.rs | 85 ++++++++++++++++++++----------------------------------------- 1 file changed, 27 insertions(+), 58 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index f13d14f..496d697 100644 --- a/src/args.rs +++ b/src/args.rs @@ -160,6 +160,7 @@ pub fn parse_args() -> crate::MiniserveConfig { #[cfg(test)] mod tests { use super::*; + use rstest::rstest_parametrize; fn create_required_auth(username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { use auth::*; @@ -176,67 +177,35 @@ mod tests { } } - #[test] - fn parse_auth_plain() -> Result<(), String> { - assert_eq!( - parse_auth("username:password")?, - create_required_auth("username", "password", "plain") - ); - - Ok(()) - } - - #[test] - fn parse_auth_sha256() -> Result<(), String> { - assert_eq!( - parse_auth("username:sha256:abcd")?, - create_required_auth("username", "abcd", "sha256") - ); - - Ok(()) - } - - #[test] - fn parse_auth_sha512() -> Result<(), String> { - assert_eq!( - parse_auth("username:sha512:abcd")?, - create_required_auth("username", "abcd", "sha512") - ); - - Ok(()) - } - - #[test] - fn parse_auth_invalid_syntax() { - assert_eq!( - parse_auth("foo").unwrap_err(), - "Invalid credentials string, expected format is username:password".to_owned() - ); - } - - #[test] - fn parse_auth_invalid_hash_method() { - assert_eq!( - parse_auth("username:blahblah:abcd").unwrap_err(), - "Invalid hash method, only accept either sha256 or sha512".to_owned() - ); - } - - #[test] - fn parse_auth_invalid_hash_string() { + #[rstest_parametrize( + auth_string, username, password, encrypt, + case("username:password", "username", "password", "plain"), + case("username:sha256:abcd", "username", "abcd", "sha256"), + case("username:sha512:abcd", "username", "abcd", "sha512") + )] + fn parse_auth_valid(auth_string: &str, username: &str, password: &str, encrypt: &str) { assert_eq!( - parse_auth("username:sha256:invalid").unwrap_err(), - "Hash string is not a valid hex code".to_owned() + parse_auth(auth_string).unwrap(), + create_required_auth(username, password, encrypt), ); } - #[test] - fn parse_auth_excessive_length() { - let auth_string = format!("username:{}", "x".repeat(256)); - - assert_eq!( - parse_auth(&*auth_string).unwrap_err(), - "Password length cannot exceed 255 characters".to_owned() - ); + #[rstest_parametrize( + auth_string, err_msg, + case( + "foo", + "Invalid credentials string, expected format is username:password" + ), + case( + "username:blahblah:abcd", + "Invalid hash method, only accept either sha256 or sha512" + ), + case( + "username:sha256:invalid", + "Hash string is not a valid hex code" + ), + )] + fn parse_auth_invalid(auth_string: &str, err_msg: &str) { + assert_eq!(parse_auth(auth_string).unwrap_err(), err_msg.to_owned(),); } } -- cgit v1.2.3 From aac70e6607d3e8172f15df91bc16dc3244473ea9 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 26 Apr 2019 17:08:23 +0700 Subject: Comply to change requests - Added doc comments - Added an additional test case --- src/args.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 8f15ea4..97b391f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -38,7 +38,8 @@ struct CLIArgs { )] interfaces: Vec, - /// Set authentication (username:password, username:sha256:hash, or username:sha512:hash) + /// Set authentication. Currently supported formats: + /// username:password, username:sha256:hash, username:sha512:hash #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] auth: Option, @@ -88,6 +89,7 @@ fn parse_auth(src: &str) -> Result { None => return invalid_auth_format, }; + // second_part is either password in username:password or method in username:method:hash let second_part = match split.next() { // This allows empty passwords, as the spec does not forbid it Some(password) => password, @@ -169,6 +171,7 @@ mod tests { use super::*; use rstest::rstest_parametrize; + /// Helper function that creates a `RequiredAuth` structure fn create_required_auth(username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { use auth::*; use RequiredAuthPassword::*; @@ -211,6 +214,10 @@ mod tests { "username:sha256:invalid", "Invalid format for password hash. Expected hex code" ), + case( + "username:sha512:invalid", + "Invalid format for password hash. Expected hex code" + ), )] fn parse_auth_invalid(auth_string: &str, err_msg: &str) { let err = parse_auth(auth_string).unwrap_err(); -- cgit v1.2.3 From 7accdf8f480a34353c4d190ef893e51242275553 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 26 Apr 2019 17:15:46 +0700 Subject: Use 'if let' --- src/args.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index 97b391f..4077f35 100644 --- a/src/args.rs +++ b/src/args.rs @@ -97,9 +97,10 @@ fn parse_auth(src: &str) -> Result { }; let password = if let Some(hash_hex) = split.next() { - let hash_bin = match hex::decode(hash_hex) { - Ok(hash_bin) => hash_bin, - _ => return Err(ContextualError::new(ContextualErrorKind::InvalidPasswordHash)), + let hash_bin = if let Ok(hash_bin) = hex::decode(hash_hex) { + hash_bin + } else { + return Err(ContextualError::new(ContextualErrorKind::InvalidPasswordHash)) }; match second_part { -- cgit v1.2.3