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/auth.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 10e7a4a..6aed8cf 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@ use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; +use sha2::{Sha256, Digest}; pub struct Auth; @@ -16,6 +17,19 @@ pub struct BasicAuthParams { pub password: String, } +#[derive(Clone, Debug)] +pub enum RequiredAuthPassword { + Plain(String), + Sha256(String), +} + +#[derive(Clone, Debug)] +/// Authentication structure to match BasicAuthParams against +pub struct RequiredAuth { + pub username: String, + pub password: RequiredAuthPassword, +} + /// Decode a HTTP basic auth string into a tuple of username and password. pub fn parse_basic_auth( authorization_header: &header::HeaderValue, @@ -34,6 +48,22 @@ pub fn parse_basic_auth( }) } +pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> bool { + if basic_auth.username != required_auth.username { + return false; + } + + match &required_auth.password { + RequiredAuthPassword::Plain(ref required_password) => basic_auth.password == *required_password, + RequiredAuthPassword::Sha256(password_hash) => { + let mut hasher = Sha256::new(); + hasher.input(basic_auth.password); + let received_hash = hex::encode(hasher.result()); + received_hash == *password_hash + } + } +} + impl Middleware for Auth { fn response( &self, @@ -51,9 +81,7 @@ impl Middleware for Auth { )))); } }; - if auth_req.username != required_auth.username - || auth_req.password != required_auth.password - { + if match_auth(auth_req, required_auth) { let new_resp = HttpResponse::Unauthorized().finish(); return Ok(Response::Done(new_resp)); } -- 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/auth.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 6aed8cf..94a2fda 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; -use sha2::{Sha256, Digest}; +use sha2::{Sha256, Sha512, Digest}; pub struct Auth; @@ -21,6 +21,7 @@ pub struct BasicAuthParams { pub enum RequiredAuthPassword { Plain(String), Sha256(String), + Sha512(String), } #[derive(Clone, Debug)] @@ -55,15 +56,18 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> match &required_auth.password { RequiredAuthPassword::Plain(ref required_password) => basic_auth.password == *required_password, - RequiredAuthPassword::Sha256(password_hash) => { - let mut hasher = Sha256::new(); - hasher.input(basic_auth.password); - let received_hash = hex::encode(hasher.result()); - received_hash == *password_hash - } + RequiredAuthPassword::Sha256(password_hash) => compare_hash::(basic_auth.password, password_hash), + RequiredAuthPassword::Sha512(password_hash) => compare_hash::(basic_auth.password, password_hash), } } +pub fn compare_hash(password: String, hash: &String) -> bool { + let mut hasher = T::new(); + hasher.input(password); + let received_hash = hex::encode(hasher.result()); + received_hash == *hash +} + impl Middleware for Auth { fn response( &self, -- cgit v1.2.3 From 4c0ac05831ddc6933647a12cdbb56d9d8523b681 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 20:16:48 +0700 Subject: Create get_hash_hex --- src/auth.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 94a2fda..48e36ce 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -62,10 +62,13 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } pub fn compare_hash(password: String, hash: &String) -> bool { + get_hash_hex::(password) == *hash +} + +fn get_hash_hex(text: String) -> String { let mut hasher = T::new(); - hasher.input(password); - let received_hash = hex::encode(hasher.result()); - received_hash == *hash + hasher.input(text); + hex::encode(hasher.result()) } impl Middleware for Auth { -- cgit v1.2.3 From ad2ad76e6b4e285e0d33d14c556d8d60137b9d23 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 20:30:17 +0700 Subject: Fix false positive --- src/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 48e36ce..c53d26a 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -88,7 +88,7 @@ impl Middleware for Auth { )))); } }; - if match_auth(auth_req, required_auth) { + if !match_auth(auth_req, required_auth) { let new_resp = HttpResponse::Unauthorized().finish(); return Ok(Response::Done(new_resp)); } -- 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/auth.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 3 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index c53d26a..e96a0ce 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -17,14 +17,14 @@ pub struct BasicAuthParams { pub password: String, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum RequiredAuthPassword { Plain(String), Sha256(String), Sha512(String), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] /// Authentication structure to match BasicAuthParams against pub struct RequiredAuth { pub username: String, @@ -65,7 +65,7 @@ pub fn compare_hash(password: String, hash: &String) -> bool { get_hash_hex::(password) == *hash } -fn get_hash_hex(text: String) -> String { +pub fn get_hash_hex(text: String) -> String { let mut hasher = T::new(); hasher.input(text); hex::encode(hasher.result()) @@ -105,3 +105,91 @@ impl Middleware for Auth { Ok(Response::Done(resp)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_hash_hex_sha256() { + let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".to_owned(); + let received = get_hash_hex::("abc".to_owned()); + assert_eq!(expectation, received); + } + + #[test] + fn get_hash_hex_sha512() { + let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f".to_owned(); + let received = get_hash_hex::("abc".to_owned()); + assert_eq!(expectation, received); + } + + fn create_auth_params (username: &str, password: &str) -> BasicAuthParams { + BasicAuthParams { + username: username.to_owned(), + password: password.to_owned(), + } + } + + fn create_required_auth (username: &str, password: &str, encrypt: &str) -> RequiredAuth { + use RequiredAuthPassword::*; + + RequiredAuth { + username: username.to_owned(), + password: match encrypt { + "plain" => Plain(password.to_owned()), + "sha256" => Sha256(get_hash_hex::(password.to_owned())), + "sha512" => Sha512(get_hash_hex::(password.to_owned())), + _ => panic!("Unknown encryption type") + }, + } + } + + #[test] + fn match_auth_plain_password_should_pass() { + assert!(match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hello there", "plain"), + )); + } + + #[test] + fn match_auth_plain_password_should_fail() { + assert!(!match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hi!", "plain"), + )); + } + + #[test] + fn match_auth_sha256_password_should_pass() { + assert!(match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hello there", "sha256"), + )); + } + + #[test] + fn match_auth_sha256_password_should_fail() { + assert!(!match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hi!", "sha256"), + )); + } + + #[test] + fn match_auth_sha512_password_should_pass() { + assert!(match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hello there", "sha512"), + )); + } + + #[test] + fn match_auth_sha512_password_should_fail() { + assert!(!match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hi!", "sha512"), + )); + } +} -- cgit v1.2.3 From 646190e5c6a5c93b32a5f6efcc4fc7b7af11037d Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 14:14:24 +0700 Subject: Remove spaces before function names --- src/auth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index e96a0ce..fed3732 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -109,7 +109,7 @@ impl Middleware for Auth { #[cfg(test)] mod tests { use super::*; - + #[test] fn get_hash_hex_sha256() { let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".to_owned(); @@ -124,14 +124,14 @@ mod tests { assert_eq!(expectation, received); } - fn create_auth_params (username: &str, password: &str) -> BasicAuthParams { + fn create_auth_params(username: &str, password: &str) -> BasicAuthParams { BasicAuthParams { username: username.to_owned(), password: password.to_owned(), } } - fn create_required_auth (username: &str, password: &str, encrypt: &str) -> RequiredAuth { + fn create_required_auth(username: &str, password: &str, encrypt: &str) -> RequiredAuth { use RequiredAuthPassword::*; RequiredAuth { -- 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/auth.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index fed3732..806bdb7 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -20,8 +20,8 @@ pub struct BasicAuthParams { #[derive(Clone, Debug, PartialEq)] pub enum RequiredAuthPassword { Plain(String), - Sha256(String), - Sha512(String), + Sha256(Vec), + Sha512(Vec), } #[derive(Clone, Debug, PartialEq)] @@ -61,14 +61,14 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } } -pub fn compare_hash(password: String, hash: &String) -> bool { - get_hash_hex::(password) == *hash +pub fn compare_hash(password: String, hash: &Vec) -> bool { + get_hash::(password) == *hash } -pub fn get_hash_hex(text: String) -> String { +pub fn get_hash(text: String) -> Vec { let mut hasher = T::new(); hasher.input(text); - hex::encode(hasher.result()) + hasher.result().to_vec() } impl Middleware for Auth { @@ -110,18 +110,23 @@ impl Middleware for Auth { mod tests { use super::*; + fn assert_hex_eq(expectation: &str, received: Vec) { + let bin = hex::decode(expectation).expect("Provided string is not a valid hex code"); + assert_eq!(bin, received); + } + #[test] fn get_hash_hex_sha256() { - let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".to_owned(); - let received = get_hash_hex::("abc".to_owned()); - assert_eq!(expectation, received); + let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; + let received = get_hash::("abc".to_owned()); + assert_hex_eq(expectation, received); } #[test] fn get_hash_hex_sha512() { - let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f".to_owned(); - let received = get_hash_hex::("abc".to_owned()); - assert_eq!(expectation, received); + let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; + let received = get_hash::("abc".to_owned()); + assert_hex_eq(expectation, received); } fn create_auth_params(username: &str, password: &str) -> BasicAuthParams { @@ -138,8 +143,8 @@ mod tests { username: username.to_owned(), password: match encrypt { "plain" => Plain(password.to_owned()), - "sha256" => Sha256(get_hash_hex::(password.to_owned())), - "sha512" => Sha512(get_hash_hex::(password.to_owned())), + "sha256" => Sha256(get_hash::(password.to_owned())), + "sha512" => Sha512(get_hash::(password.to_owned())), _ => panic!("Unknown encryption type") }, } -- 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/auth.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 806bdb7..a4b3555 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; -use sha2::{Sha256, Sha512, Digest}; +use sha2::{Digest, Sha256, Sha512}; pub struct Auth; @@ -55,9 +55,15 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } match &required_auth.password { - RequiredAuthPassword::Plain(ref required_password) => basic_auth.password == *required_password, - RequiredAuthPassword::Sha256(password_hash) => compare_hash::(basic_auth.password, password_hash), - RequiredAuthPassword::Sha512(password_hash) => compare_hash::(basic_auth.password, password_hash), + RequiredAuthPassword::Plain(ref required_password) => { + basic_auth.password == *required_password + } + RequiredAuthPassword::Sha256(password_hash) => { + compare_hash::(basic_auth.password, password_hash) + } + RequiredAuthPassword::Sha512(password_hash) => { + compare_hash::(basic_auth.password, password_hash) + } } } @@ -145,7 +151,7 @@ mod tests { "plain" => Plain(password.to_owned()), "sha256" => Sha256(get_hash::(password.to_owned())), "sha512" => Sha512(get_hash::(password.to_owned())), - _ => 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/auth.rs | 112 ++++++++++++++++++++++++------------------------------------ 1 file changed, 44 insertions(+), 68 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index a4b3555..2db422d 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -115,31 +115,26 @@ impl Middleware for Auth { #[cfg(test)] mod tests { use super::*; + use rstest::rstest_parametrize; - fn assert_hex_eq(expectation: &str, received: Vec) { - let bin = hex::decode(expectation).expect("Provided string is not a valid hex code"); - assert_eq!(bin, received); - } - - #[test] - fn get_hash_hex_sha256() { - let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; - let received = get_hash::("abc".to_owned()); - assert_hex_eq(expectation, received); + fn get_hash_func(name: &str) -> impl FnOnce(String) -> Vec { + match name { + "sha256" => get_hash::, + "sha512" => get_hash::, + _ => panic!("Invalid hash method"), + } } - #[test] - fn get_hash_hex_sha512() { - let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; - let received = get_hash::("abc".to_owned()); - assert_hex_eq(expectation, received); - } - - fn create_auth_params(username: &str, password: &str) -> BasicAuthParams { - BasicAuthParams { - username: username.to_owned(), - password: password.to_owned(), - } + #[rstest_parametrize( + password, hash_method, hash, + case("abc", "sha256", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"), + case("abc", "sha512", "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"), + )] + fn test_get_hash(password: &str, hash_method: &str, hash: &str) { + let hash_func = get_hash_func(hash_method); + let expected = hex::decode(hash).expect("Provided hash is not a valid hex code"); + let received = hash_func(password.to_owned()); + assert_eq!(received, expected); } fn create_required_auth(username: &str, password: &str, encrypt: &str) -> RequiredAuth { @@ -156,51 +151,32 @@ mod tests { } } - #[test] - fn match_auth_plain_password_should_pass() { - assert!(match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hello there", "plain"), - )); - } - - #[test] - fn match_auth_plain_password_should_fail() { - assert!(!match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hi!", "plain"), - )); - } - - #[test] - fn match_auth_sha256_password_should_pass() { - assert!(match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hello there", "sha256"), - )); - } - - #[test] - fn match_auth_sha256_password_should_fail() { - assert!(!match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hi!", "sha256"), - )); - } - - #[test] - fn match_auth_sha512_password_should_pass() { - assert!(match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hello there", "sha512"), - )); - } - - #[test] - fn match_auth_sha512_password_should_fail() { - assert!(!match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hi!", "sha512"), - )); + #[rstest_parametrize( + should_pass, param_username, param_password, required_username, required_password, encrypt, + case(true, "obi", "hello there", "obi", "hello there", "plain"), + case(false, "obi", "hello there", "obi", "hi!", "plain"), + case(true, "obi", "hello there", "obi", "hello there", "sha256"), + case(false, "obi", "hello there", "obi", "hi!", "sha256"), + case(true, "obi", "hello there", "obi", "hello there", "sha512"), + case(false, "obi", "hello there", "obi", "hi!", "sha512"), + )] + fn test_auth( + should_pass: bool, + param_username: &str, + param_password: &str, + required_username: &str, + required_password: &str, + encrypt: &str, + ) { + assert_eq!( + match_auth( + BasicAuthParams { + username: param_username.to_owned(), + password: param_password.to_owned(), + }, + &create_required_auth(required_username, required_password, encrypt), + ), + should_pass, + ) } } -- 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/auth.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 432f6ce..e75f498 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -15,6 +15,7 @@ pub struct BasicAuthParams { } #[derive(Clone, Debug, PartialEq)] +/// `password` field of `RequiredAuth` pub enum RequiredAuthPassword { Plain(String), Sha256(Vec), @@ -22,7 +23,7 @@ pub enum RequiredAuthPassword { } #[derive(Clone, Debug, PartialEq)] -/// Authentication structure to match BasicAuthParams against +/// Authentication structure to match `BasicAuthParams` against pub struct RequiredAuth { pub username: String, pub password: RequiredAuthPassword, @@ -54,6 +55,7 @@ pub fn parse_basic_auth( }) } +/// Verify authentication pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> bool { if basic_auth.username != required_auth.username { return false; @@ -72,10 +74,12 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } } +/// Return `true` if hashing of `password` by `T` algorithm equals to `hash` pub fn compare_hash(password: String, hash: &Vec) -> bool { get_hash::(password) == *hash } +/// Get hash of a `text` pub fn get_hash(text: String) -> Vec { let mut hasher = T::new(); hasher.input(text); @@ -124,6 +128,7 @@ mod tests { use super::*; use rstest::rstest_parametrize; + /// Return a hashing function corresponds to given name fn get_hash_func(name: &str) -> impl FnOnce(String) -> Vec { match name { "sha256" => get_hash::, @@ -144,6 +149,7 @@ mod tests { assert_eq!(received, expected); } + /// Helper function that creates a `RequiredAuth` structure and encrypt `password` if necessary fn create_required_auth(username: &str, password: &str, encrypt: &str) -> RequiredAuth { use RequiredAuthPassword::*; -- cgit v1.2.3