Merge branch 'release/v0.3.0'
This commit is contained in:
		
						commit
						bf9393354e
					
				
							
								
								
									
										142
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										142
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -169,6 +169,12 @@ version = "0.1.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" | checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "base64" | ||||||
|  | version = "0.13.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "base64ct" | name = "base64ct" | ||||||
| version = "1.5.2" | version = "1.5.2" | ||||||
| @ -297,6 +303,17 @@ dependencies = [ | |||||||
|  "wasm-bindgen", |  "wasm-bindgen", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "colored" | ||||||
|  | version = "2.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" | ||||||
|  | dependencies = [ | ||||||
|  |  "atty", | ||||||
|  |  "lazy_static", | ||||||
|  |  "winapi", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "concurrent-queue" | name = "concurrent-queue" | ||||||
| version = "1.2.4" | version = "1.2.4" | ||||||
| @ -781,7 +798,7 @@ dependencies = [ | |||||||
|  "libc", |  "libc", | ||||||
|  "log", |  "log", | ||||||
|  "wasi", |  "wasi", | ||||||
|  "windows-sys", |  "windows-sys 0.36.1", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -842,6 +859,15 @@ dependencies = [ | |||||||
|  "libc", |  "libc", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "num_threads" | ||||||
|  | version = "0.1.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "once_cell" | name = "once_cell" | ||||||
| version = "1.15.0" | version = "1.15.0" | ||||||
| @ -902,7 +928,7 @@ dependencies = [ | |||||||
|  "libc", |  "libc", | ||||||
|  "redox_syscall", |  "redox_syscall", | ||||||
|  "smallvec", |  "smallvec", | ||||||
|  "windows-sys", |  "windows-sys 0.36.1", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -1209,15 +1235,31 @@ version = "0.2.0" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "async-std", |  "async-std", | ||||||
|  "async-trait", |  "async-trait", | ||||||
|  |  "base64", | ||||||
|  "clap", |  "clap", | ||||||
|  "configparser", |  "configparser", | ||||||
|  "json", |  "json", | ||||||
|  "jwt-simple", |  "jwt-simple", | ||||||
|  "lazy_static", |  "lazy_static", | ||||||
|  |  "log", | ||||||
|  "regex", |  "regex", | ||||||
|  |  "simple_logger", | ||||||
|  "tokio", |  "tokio", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "simple_logger" | ||||||
|  | version = "4.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e190a521c2044948158666916d9e872cbb9984f755e9bb3b5b75a836205affcd" | ||||||
|  | dependencies = [ | ||||||
|  |  "atty", | ||||||
|  |  "colored", | ||||||
|  |  "log", | ||||||
|  |  "time", | ||||||
|  |  "windows-sys 0.42.0", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "slab" | name = "slab" | ||||||
| version = "0.4.7" | version = "0.4.7" | ||||||
| @ -1327,6 +1369,35 @@ dependencies = [ | |||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "time" | ||||||
|  | version = "0.3.17" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" | ||||||
|  | dependencies = [ | ||||||
|  |  "itoa", | ||||||
|  |  "libc", | ||||||
|  |  "num_threads", | ||||||
|  |  "serde", | ||||||
|  |  "time-core", | ||||||
|  |  "time-macros", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "time-core" | ||||||
|  | version = "0.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "time-macros" | ||||||
|  | version = "0.2.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" | ||||||
|  | dependencies = [ | ||||||
|  |  "time-core", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio" | name = "tokio" | ||||||
| version = "1.21.2" | version = "1.21.2" | ||||||
| @ -1520,43 +1591,100 @@ version = "0.36.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "windows_aarch64_msvc", |  "windows_aarch64_msvc 0.36.1", | ||||||
|  "windows_i686_gnu", |  "windows_i686_gnu 0.36.1", | ||||||
|  "windows_i686_msvc", |  "windows_i686_msvc 0.36.1", | ||||||
|  "windows_x86_64_gnu", |  "windows_x86_64_gnu 0.36.1", | ||||||
|  "windows_x86_64_msvc", |  "windows_x86_64_msvc 0.36.1", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows-sys" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" | ||||||
|  | dependencies = [ | ||||||
|  |  "windows_aarch64_gnullvm", | ||||||
|  |  "windows_aarch64_msvc 0.42.0", | ||||||
|  |  "windows_i686_gnu 0.42.0", | ||||||
|  |  "windows_i686_msvc 0.42.0", | ||||||
|  |  "windows_x86_64_gnu 0.42.0", | ||||||
|  |  "windows_x86_64_gnullvm", | ||||||
|  |  "windows_x86_64_msvc 0.42.0", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows_aarch64_gnullvm" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_aarch64_msvc" | name = "windows_aarch64_msvc" | ||||||
| version = "0.36.1" | version = "0.36.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows_aarch64_msvc" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_i686_gnu" | name = "windows_i686_gnu" | ||||||
| version = "0.36.1" | version = "0.36.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows_i686_gnu" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_i686_msvc" | name = "windows_i686_msvc" | ||||||
| version = "0.36.1" | version = "0.36.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows_i686_msvc" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_x86_64_gnu" | name = "windows_x86_64_gnu" | ||||||
| version = "0.36.1" | version = "0.36.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows_x86_64_gnu" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows_x86_64_gnullvm" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows_x86_64_msvc" | name = "windows_x86_64_msvc" | ||||||
| version = "0.36.1" | version = "0.36.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows_x86_64_msvc" | ||||||
|  | version = "0.42.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "zeroize" | name = "zeroize" | ||||||
| version = "1.5.7" | version = "1.5.7" | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| [package] | [package] | ||||||
| name = "simple-auth" | name = "simple-auth" | ||||||
| version = "0.2.0" | version = "0.3.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| 
 | 
 | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
| @ -12,6 +12,9 @@ regex = "1" | |||||||
| tokio = { version = "1.21.1", features = ["full"] } | tokio = { version = "1.21.1", features = ["full"] } | ||||||
| async-trait = "0.1.57" | async-trait = "0.1.57" | ||||||
| jwt-simple = "0.11.1" | jwt-simple = "0.11.1" | ||||||
|  | simple_logger = "4.0.0" | ||||||
|  | log = "0.4.17" | ||||||
|  | base64 = "0.13.1" | ||||||
| 
 | 
 | ||||||
| # useful for tests (embedded files should be delete in release ?) | # useful for tests (embedded files should be delete in release ?) | ||||||
| #rust-embed="6.4.1" | #rust-embed="6.4.1" | ||||||
|  | |||||||
| @ -45,13 +45,19 @@ expiration_time = 2 # in hours | |||||||
| ```bash | ```bash | ||||||
| ./simple-auth <ini_path> | ./simple-auth <ini_path> | ||||||
| 
 | 
 | ||||||
|  | # get a JWT | ||||||
| curl http://<ip>:<port>/get/ -d '{"username":"<user>", "password":"<password>"}' | curl http://<ip>:<port>/get/ -d '{"username":"<user>", "password":"<password>"}' | ||||||
| # should returned | # should returned | ||||||
| {"token":"<header>.<payload>.<signature>"} | {"token":"<header>.<payload>.<signature>"} | ||||||
| 
 | 
 | ||||||
|  | # validate a JWT | ||||||
| curl http://<ip>:<port>/validate/ -d '{"token":"<header>.<payload>.<signature>"}' | curl http://<ip>:<port>/validate/ -d '{"token":"<header>.<payload>.<signature>"}' | ||||||
| # should returned (if valid) | # should returned (if valid) | ||||||
| {"valid":"true"} | {"valid":"true"} | ||||||
|  | 
 | ||||||
|  | # get the public key for local validation | ||||||
|  | curl http://<ip>:<port>/pubkey/ | ||||||
|  | {"pubkey":"<b64_encoded_public_key>"} | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Test | ## Test | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| //! config module implements all the utilities to properly create and validate a router config
 |  | ||||||
| 
 |  | ||||||
| use configparser::ini::Ini; | use configparser::ini::Ini; | ||||||
| use std::str::FromStr; | use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
| @ -34,7 +32,10 @@ impl TryFrom<Ini> for Config { | |||||||
|             match u64::from_str(&exp_time) { |             match u64::from_str(&exp_time) { | ||||||
|                 Ok(v) => v, |                 Ok(v) => v, | ||||||
|                 Err(e) => { |                 Err(e) => { | ||||||
|                     eprintln!("unable to convert JWT expiration time into u64 err={}", e); |                     log::error!( | ||||||
|  |                         "unable to convert JWT expiration time into u64 details={}", | ||||||
|  |                         e | ||||||
|  |                     ); | ||||||
|                     0 |                     0 | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -59,27 +60,27 @@ impl Config { | |||||||
|     /// validates config ini file
 |     /// validates config ini file
 | ||||||
|     fn validate(&self) -> bool { |     fn validate(&self) -> bool { | ||||||
|         if self.jwt_exp_time <= 0 { |         if self.jwt_exp_time <= 0 { | ||||||
|             eprintln!("invalid config parameter: JWT expiration time is negative or equals to 0"); |             log::error!("invalid config parameter: JWT expiration time is negative or equals to 0"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if self.jwt_issuer == "" { |         if self.jwt_issuer == "" { | ||||||
|             eprintln!("invalid config parameter: JWT issuer is empty"); |             log::error!("invalid config parameter: JWT issuer is empty"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if self.jwt_pub_key == "" { |         if self.jwt_pub_key == "" { | ||||||
|             eprintln!("invalid config parameter: JWT public key file path is empty"); |             log::error!("invalid config parameter: JWT public key file path is empty"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if self.jwt_priv_key == "" { |         if self.jwt_priv_key == "" { | ||||||
|             eprintln!("invalid config parameter: JWT private key file path is empty"); |             log::error!("invalid config parameter: JWT private key file path is empty"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if self.filestore_path == "" { |         if self.filestore_path == "" { | ||||||
|             eprintln!("invalid config parameter: filestore path is empty"); |             log::error!("invalid config parameter: filestore path is empty"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | //! provides `Config` struct to load and validate `.ini` file
 | ||||||
| mod config; | mod config; | ||||||
| 
 | 
 | ||||||
| pub use config::Config; | pub use config::Config; | ||||||
|  | |||||||
| @ -143,10 +143,7 @@ impl TryFrom<String> for HTTPBody { | |||||||
|         let body = body.replace(NULL_CHAR, ""); |         let body = body.replace(NULL_CHAR, ""); | ||||||
|         match json::parse(&body) { |         match json::parse(&body) { | ||||||
|             Ok(v) => Ok(HTTPBody::new(v)), |             Ok(v) => Ok(HTTPBody::new(v)), | ||||||
|             Err(e) => Err(format!( |             Err(e) => Err(format!("during request body parsing details={}", e)), | ||||||
|                 "error occurred during request body parsing err={}", |  | ||||||
|                 e |  | ||||||
|             )), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -156,6 +153,8 @@ impl TryFrom<String> for HTTPBody { | |||||||
| pub struct HTTPRequest { | pub struct HTTPRequest { | ||||||
|     pub start_line: HTTPStartLine, |     pub start_line: HTTPStartLine, | ||||||
|     pub body: Option<HTTPBody>, |     pub body: Option<HTTPBody>, | ||||||
|  |     // includes the client IP + port (should be in the headers)
 | ||||||
|  |     pub addr: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl HTTPRequest { | impl HTTPRequest { | ||||||
| @ -190,19 +189,19 @@ impl HTTPRequest { | |||||||
|                 let start_line = HTTPStartLine::parse(&rp.0); |                 let start_line = HTTPStartLine::parse(&rp.0); | ||||||
|                 match start_line { |                 match start_line { | ||||||
|                     Ok(v) => request.start_line = v, |                     Ok(v) => request.start_line = v, | ||||||
|                     Err(e) => eprintln!("error occurred while parsing start_line err={}", e), |                     Err(e) => log::error!("while parsing start_line details={}", e), | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 let body = HTTPBody::try_from(rp.2); |                 let body = HTTPBody::try_from(rp.2); | ||||||
|                 match body { |                 match body { | ||||||
|                     Ok(v) => request.body = Some(v), |                     Ok(v) => request.body = Some(v), | ||||||
|                     Err(e) => eprintln!("error occurred during body parsing err={}", e), |                     Err(e) => log::warn!("{}", e), | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return Ok(request); |                 return Ok(request); | ||||||
|             } |             } | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 return Err(format!("error occurred getting request parts err={}", e)); |                 return Err(e); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -218,10 +217,18 @@ impl HTTPRequest { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn get_method(&self) -> String { | ||||||
|  |         self.start_line.method.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     #[allow(dead_code)] |     #[allow(dead_code)] | ||||||
|     pub fn is_valid(&self) -> bool { |     pub fn is_valid(&self) -> bool { | ||||||
|         return self.start_line.is_valid(); |         return self.start_line.is_valid(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn set_addr(&mut self, addr: String) { | ||||||
|  |         self.addr = addr; | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for HTTPRequest { | impl Default for HTTPRequest { | ||||||
| @ -229,6 +236,7 @@ impl Default for HTTPRequest { | |||||||
|         HTTPRequest { |         HTTPRequest { | ||||||
|             start_line: HTTPStartLine::default(), |             start_line: HTTPStartLine::default(), | ||||||
|             body: None, |             body: None, | ||||||
|  |             addr: "".to_string(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -237,8 +245,8 @@ impl From<&str> for HTTPRequest { | |||||||
|     fn from(request: &str) -> Self { |     fn from(request: &str) -> Self { | ||||||
|         match Self::parse(request) { |         match Self::parse(request) { | ||||||
|             Ok(v) => v, |             Ok(v) => v, | ||||||
|             Err(v) => { |             Err(e) => { | ||||||
|                 eprintln!("{}", format!("[ERR]: {v}")); |                 log::error!("{}", e); | ||||||
|                 return HTTPRequest::default(); |                 return HTTPRequest::default(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| //! router aims to handle correctly the request corresponding to the target
 | //! router aims to handle correctly the request corresponding to the target
 | ||||||
| //! it implements all the logic to build an `HTTPResponse`
 | //! it implements all the logic to build an `HTTPResponse`
 | ||||||
| 
 | 
 | ||||||
|  | use base64; | ||||||
| use json; | use json; | ||||||
| 
 | 
 | ||||||
| use super::{HTTPMessage, HTTPRequest, HTTPResponse}; | use super::{HTTPMessage, HTTPRequest, HTTPResponse}; | ||||||
| @ -11,8 +12,13 @@ use crate::stores::{FileStore, Store}; | |||||||
| // TODO: must be mapped with corresponding handler
 | // TODO: must be mapped with corresponding handler
 | ||||||
| const GET_ROUTE: &'static str = "/get/"; | const GET_ROUTE: &'static str = "/get/"; | ||||||
| const VALIDATE_ROUTE: &'static str = "/validate/"; | const VALIDATE_ROUTE: &'static str = "/validate/"; | ||||||
|  | const PUBKEY_ROUTE: &'static str = "/pubkey/"; | ||||||
|  | 
 | ||||||
|  | async fn handle_get(request: HTTPRequest, config: Config, method: &str) -> HTTPResponse { | ||||||
|  |     if method.trim().to_lowercase() != "post" { | ||||||
|  |         return HTTPResponse::as_400(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| async fn handle_get(request: HTTPRequest, config: Config) -> HTTPResponse { |  | ||||||
|     let mut store = FileStore::new(config.filestore_path.clone()); |     let mut store = FileStore::new(config.filestore_path.clone()); | ||||||
|     match &request.body { |     match &request.body { | ||||||
|         Some(ref b) => { |         Some(ref b) => { | ||||||
| @ -46,7 +52,11 @@ async fn handle_get(request: HTTPRequest, config: Config) -> HTTPResponse { | |||||||
| /// validates the token by checking:
 | /// validates the token by checking:
 | ||||||
| /// * expiration time
 | /// * expiration time
 | ||||||
| /// * signature
 | /// * signature
 | ||||||
| async fn handle_validate(request: HTTPRequest, config: Config) -> HTTPResponse { | async fn handle_validate(request: HTTPRequest, config: Config, method: &str) -> HTTPResponse { | ||||||
|  |     if request.get_method().trim().to_lowercase() != method { | ||||||
|  |         return HTTPResponse::as_400(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     let token = { |     let token = { | ||||||
|         match request.get_body_value("token") { |         match request.get_body_value("token") { | ||||||
|             Some(t) => t, |             Some(t) => t, | ||||||
| @ -87,16 +97,46 @@ async fn handle_validate(request: HTTPRequest, config: Config) -> HTTPResponse { | |||||||
|     HTTPResponse::as_200(Some(json)) |     HTTPResponse::as_200(Some(json)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// returns the JWT public key in base64 encoded
 | ||||||
|  | async fn handle_public_key(request: HTTPRequest, config: Config, method: &str) -> HTTPResponse { | ||||||
|  |     if request.get_method().trim().to_lowercase() != method { | ||||||
|  |         return HTTPResponse::as_400(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let jwt_signer = { | ||||||
|  |         match JWTSigner::new(config).await { | ||||||
|  |             Ok(s) => s, | ||||||
|  |             Err(e) => { | ||||||
|  |                 let message = HTTPMessage::error(&e); | ||||||
|  |                 let json = message.try_into().unwrap(); | ||||||
|  |                 return HTTPResponse::as_500(Some(json)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let public_key = jwt_signer.get_public_key(); | ||||||
|  | 
 | ||||||
|  |     let mut message = HTTPMessage::default(); | ||||||
|  |     message.put("pubkey", &base64::encode(public_key)); | ||||||
|  | 
 | ||||||
|  |     let json = message.try_into().unwrap(); | ||||||
|  |     HTTPResponse::as_200(Some(json)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| pub struct Router; | pub struct Router; | ||||||
| 
 | 
 | ||||||
| impl Router { | impl Router { | ||||||
|     pub async fn route(&self, request_str: &str, config: Config) -> HTTPResponse { |     /// routes the request to the corresponding handling method
 | ||||||
|         let request = HTTPRequest::from(request_str); |     pub async fn route(&self, request_str: &str, addr: String, config: Config) -> HTTPResponse { | ||||||
|  |         let mut request = HTTPRequest::from(request_str); | ||||||
|  |         request.set_addr(addr); | ||||||
|  | 
 | ||||||
|         let target = request.start_line.get_target(); |         let target = request.start_line.get_target(); | ||||||
| 
 | 
 | ||||||
|         match target.as_str() { |         match target.as_str() { | ||||||
|             GET_ROUTE => handle_get(request, config).await, |             GET_ROUTE => handle_get(request, config, "post").await, | ||||||
|             VALIDATE_ROUTE => handle_validate(request, config).await, |             VALIDATE_ROUTE => handle_validate(request, config, "post").await, | ||||||
|  |             PUBKEY_ROUTE => handle_public_key(request, config, "get").await, | ||||||
|             _ => HTTPResponse::as_404(), |             _ => HTTPResponse::as_404(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -113,7 +153,7 @@ async fn test_route() { | |||||||
|     let config: Config = Config::default(); |     let config: Config = Config::default(); | ||||||
|     let request_str = "POST /get/ HTTP/1.1\r\n\r\n"; |     let request_str = "POST /get/ HTTP/1.1\r\n\r\n"; | ||||||
| 
 | 
 | ||||||
|     let response: HTTPResponse = router.route(request_str, config).await; |     let response: HTTPResponse = router.route(request_str, "".to_string(), config).await; | ||||||
|     assert_eq!( |     assert_eq!( | ||||||
|         HTTPStatusCode::Http400, |         HTTPStatusCode::Http400, | ||||||
|         response.status_line.get_status_code() |         response.status_line.get_status_code() | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| //! simple module to read `.pem` files and sign the token
 |  | ||||||
| 
 |  | ||||||
| use crate::config::Config; | use crate::config::Config; | ||||||
| use jwt_simple::common::VerificationOptions; | use jwt_simple::common::VerificationOptions; | ||||||
| use jwt_simple::prelude::*; | use jwt_simple::prelude::*; | ||||||
| @ -28,7 +26,7 @@ impl JWTSigner { | |||||||
|                 jwt_signer.private_key = c; |                 jwt_signer.private_key = c; | ||||||
|             } |             } | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 return Err(format!("unable to read the private key err={}", e)); |                 return Err(format!("unable to read the private key details={}", e)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -37,7 +35,7 @@ impl JWTSigner { | |||||||
|                 jwt_signer.public_key = c; |                 jwt_signer.public_key = c; | ||||||
|             } |             } | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 return Err(format!("unable to read the public key err={}", e)); |                 return Err(format!("unable to read the public key details={}", e)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -60,7 +58,7 @@ impl JWTSigner { | |||||||
|             match RS384KeyPair::from_pem(&self.private_key) { |             match RS384KeyPair::from_pem(&self.private_key) { | ||||||
|                 Ok(k) => k, |                 Ok(k) => k, | ||||||
|                 Err(e) => { |                 Err(e) => { | ||||||
|                     return Err(format!("unable to load the private key err={}", e)); |                     return Err(format!("unable to load the private key details={}", e)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| @ -70,7 +68,7 @@ impl JWTSigner { | |||||||
|         match jwt_key.sign(claims) { |         match jwt_key.sign(claims) { | ||||||
|             Ok(token) => Ok(token), |             Ok(token) => Ok(token), | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 return Err(format!("unable to sign the token err={}", e)); |                 return Err(format!("unable to sign the token details={}", e)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -82,16 +80,20 @@ impl JWTSigner { | |||||||
|                 if let Err(e) = |                 if let Err(e) = | ||||||
|                     key.verify_token::<NoCustomClaims>(token, Some(verification_options)) |                     key.verify_token::<NoCustomClaims>(token, Some(verification_options)) | ||||||
|                 { |                 { | ||||||
|                     return Err(format!("token validation failed err={}", e)); |                     return Err(format!("token validation failed details={}", e)); | ||||||
|                 } |                 } | ||||||
|                 Ok(()) |                 Ok(()) | ||||||
|             } |             } | ||||||
|             Err(e) => Err(format!( |             Err(e) => Err(format!( | ||||||
|                 "token validation failed can't read the public key err={}", |                 "token validation failed, can't read the public key details={}", | ||||||
|                 e |                 e | ||||||
|             )), |             )), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn get_public_key(&self) -> String { | ||||||
|  |         self.public_key.clone() | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | //! simple module to read `.pem` files and sign the token
 | ||||||
| mod jwt; | mod jwt; | ||||||
| 
 | 
 | ||||||
| pub use jwt::JWTSigner; | pub use jwt::JWTSigner; | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -24,13 +24,14 @@ struct Cli { | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|  |     simple_logger::init_with_level(log::Level::Info).unwrap(); | ||||||
|     let args = Cli::parse(); |     let args = Cli::parse(); | ||||||
| 
 | 
 | ||||||
|     let mut config = Ini::new(); |     let mut config = Ini::new(); | ||||||
|     match config.load(args.config) { |     match config.load(args.config) { | ||||||
|         Ok(c) => c, |         Ok(c) => c, | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             eprintln!("error while loading the config file, err={}", e); |             log::error!("error while loading the config file details={}", e); | ||||||
|             std::process::exit(1); |             std::process::exit(1); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| @ -39,11 +40,11 @@ async fn main() { | |||||||
|     let listener = { |     let listener = { | ||||||
|         match TcpListener::bind(&server_url).await { |         match TcpListener::bind(&server_url).await { | ||||||
|             Ok(t) => { |             Ok(t) => { | ||||||
|                 println!("server is listening on '{}'", server_url); |                 log::info!("server is listening on '{}'", server_url); | ||||||
|                 t |                 t | ||||||
|             } |             } | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 eprintln!("error occurred while initializing tcp listener err={}", e); |                 log::error!("while initializing tcp listener details={}", e); | ||||||
|                 std::process::exit(1); |                 std::process::exit(1); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -52,28 +53,31 @@ async fn main() { | |||||||
|     let router_config: Config = { |     let router_config: Config = { | ||||||
|         match Config::try_from(config) { |         match Config::try_from(config) { | ||||||
|             Ok(c) => c, |             Ok(c) => c, | ||||||
|             Err(_e) => { |             Err(e) => { | ||||||
|  |                 log::error!("unable to load the configuration details={}", e); | ||||||
|                 std::process::exit(1); |                 std::process::exit(1); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     loop { |     loop { | ||||||
|         let (stream, _) = listener.accept().await.unwrap(); |         let (stream, addr) = listener.accept().await.unwrap(); | ||||||
|         let conf = router_config.clone(); |         let conf = router_config.clone(); | ||||||
|         tokio::spawn(handle_connection(stream, conf.clone())); |         tokio::spawn(handle_connection(stream, addr.to_string(), conf.clone())); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// parses the incoming request (partial spec implementation) and build an HTTP response
 | /// parses the incoming request (partial spec implementation) and build an HTTP response
 | ||||||
| async fn handle_connection(mut stream: TcpStream, config: Config) { | async fn handle_connection(mut stream: TcpStream, addr: String, config: Config) { | ||||||
|  |     log::info!("client connected: {}", addr); | ||||||
|  | 
 | ||||||
|     let mut message = vec![]; |     let mut message = vec![]; | ||||||
|     let mut buffer: [u8; 1024] = [0; 1024]; |     let mut buffer: [u8; 1024] = [0; 1024]; | ||||||
| 
 | 
 | ||||||
|     let duration = Duration::from_micros(500); |     let duration = Duration::from_millis(5); | ||||||
| 
 | 
 | ||||||
|     // loop until the message is read
 |     // loop until the message is read
 | ||||||
|     // the stream can be fragmented so, using a timeout (500um should be enough) for the future for completion
 |     // the stream can be fragmented so, using a timeout (5ms should be far enough) for the future for completion
 | ||||||
|     // after the timeout, the message is "considered" as entirely read
 |     // after the timeout, the message is "considered" as entirely read
 | ||||||
|     loop { |     loop { | ||||||
|         match timeout(duration, stream.read(&mut buffer)).await { |         match timeout(duration, stream.read(&mut buffer)).await { | ||||||
| @ -86,9 +90,11 @@ async fn handle_connection(mut stream: TcpStream, config: Config) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let request_string = std::str::from_utf8(&message).unwrap(); |     let request_string = std::str::from_utf8(&message).unwrap(); | ||||||
|     let response = ROUTER.route(request_string, config).await; |     let response = ROUTER.route(request_string, addr.clone(), config).await; | ||||||
|     let response_str: String = response.into(); |     let response_str: String = response.into(); | ||||||
| 
 | 
 | ||||||
|     stream.write(response_str.as_bytes()).await.unwrap(); |     stream.write(response_str.as_bytes()).await.unwrap(); | ||||||
|     stream.flush().await.unwrap(); |     stream.flush().await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     log::info!("connection closed: {}", addr); | ||||||
| } | } | ||||||
|  | |||||||
| @ -41,10 +41,7 @@ impl FileStore { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 eprintln!( |                 log::error!("while reading store file: {}, details={:?}", self.path, e); | ||||||
|                     "error occurred while reading store file: {}, err={:?}", |  | ||||||
|                     self.path, e |  | ||||||
|                 ); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         self.credentials = credentials; |         self.credentials = credentials; | ||||||
| @ -69,13 +66,13 @@ impl Store for FileStore { | |||||||
|     async fn is_auth(&mut self, data: &json::JsonValue) -> bool { |     async fn is_auth(&mut self, data: &json::JsonValue) -> bool { | ||||||
|         // ensure that the store file already exists even after its instanciation
 |         // ensure that the store file already exists even after its instanciation
 | ||||||
|         if !Path::new(&self.path).is_file() { |         if !Path::new(&self.path).is_file() { | ||||||
|             eprintln!("{} path referencing file store does not exist", self.path); |             log::error!("{} path referencing file store does not exist", self.path); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let credentials = Credentials::from(data); |         let credentials = Credentials::from(data); | ||||||
|         if credentials.is_empty() { |         if credentials.is_empty() { | ||||||
|             eprintln!("unable to parse the credentials correctly from the incoming request"); |             log::error!("unable to parse the credentials correctly from the incoming request"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | //! includes utility function, that's all !
 | ||||||
| mod utils; | mod utils; | ||||||
| 
 | 
 | ||||||
| pub use utils::extract_json_value; | pub use utils::extract_json_value; | ||||||
|  | |||||||
| @ -6,10 +6,11 @@ | |||||||
| # | # | ||||||
| ####################################### | ####################################### | ||||||
| 
 | 
 | ||||||
| if [ -z ${SIMPLE_AUTH_URL} ] | URL=${SIMPLE_AUTH_URL} | ||||||
|  | if [ -z ${URL} ] | ||||||
| then | then | ||||||
| 	echo "[WARN]: SIMPLE_AUTH_URL is empty, set to http://localhost:9001" | 	echo "[WARN]: SIMPLE_AUTH_URL is empty, set to http://localhost:5555" | ||||||
| 	URL="http://localhost:9001" | 	URL="http://localhost:5555" | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
| for i in {0..10} | for i in {0..10} | ||||||
| @ -38,3 +39,14 @@ do | |||||||
| 		exit 1 | 		exit 1 | ||||||
| 	fi | 	fi | ||||||
| done | done | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | for i in {0..10} | ||||||
|  | do | ||||||
|  | 	http_response=$(curl -s -o response.txt -w "%{http_code}" ${URL}/pubkey/) | ||||||
|  | 	if [ $http_response != "200" ] | ||||||
|  | 	then | ||||||
|  | 		echo "bad http status code : ${http_response}, expect 400" | ||||||
|  | 		exit 1 | ||||||
|  | 	fi | ||||||
|  | done | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import base64 | ||||||
| import jwt | import jwt | ||||||
| import os | import os | ||||||
| import requests | import requests | ||||||
| @ -5,7 +6,7 @@ import requests | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from unittest import TestCase | from unittest import TestCase | ||||||
| 
 | 
 | ||||||
| URL = os.getenv("SIMPLE_AUTH_URL", "http://127.0.0.1:9001") | URL = os.getenv("SIMPLE_AUTH_URL", "http://127.0.0.1:5555") | ||||||
| PUB_KEY_PATH = os.getenv("SIMPLE_AUTH_PUB_KEY", "") | PUB_KEY_PATH = os.getenv("SIMPLE_AUTH_PUB_KEY", "") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -56,7 +57,7 @@ class TestResponse(TestCase): | |||||||
|         self.assertEqual(resp.json()["valid"], "false", "bad status returned") |         self.assertEqual(resp.json()["valid"], "false", "bad status returned") | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             resp.json()["reason"], |             resp.json()["reason"], | ||||||
|             "token validation failed err=JWT compact encoding error", |             "token validation failed details=JWT compact encoding error", | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def test_validate_target(self): |     def test_validate_target(self): | ||||||
| @ -113,3 +114,23 @@ class TestResponse(TestCase): | |||||||
|             "the url requested does not exist", |             "the url requested does not exist", | ||||||
|             "invalid error message returned", |             "invalid error message returned", | ||||||
|         ) |         ) | ||||||
|  | 
 | ||||||
|  |     def test_get_pubkey(self): | ||||||
|  |         resp = requests.get(URL + "/pubkey/") | ||||||
|  |         self.assertEqual(resp.status_code, 200, "bad status code returned") | ||||||
|  |         self.assertIsNotNone(resp.json(), "response data must not be empty") | ||||||
|  |         self.assertIsNotNone(resp.json()["pubkey"], "invalid error message returned") | ||||||
|  | 
 | ||||||
|  |         b64_pubkey = base64.b64decode(resp.json()["pubkey"]) | ||||||
|  |         self.assertIsNotNone(b64_pubkey, "public key b64 decoded can't be empty") | ||||||
|  |         self.assertIn("-BEGIN PUBLIC KEY-", b64_pubkey.decode()) | ||||||
|  | 
 | ||||||
|  |     def test_get_pubkey_bad_method(self): | ||||||
|  |         resp = requests.post(URL + "/pubkey/", json={"tutu": "toto"}) | ||||||
|  |         self.assertEqual(resp.status_code, 400, "bad status code returned") | ||||||
|  |         self.assertIsNotNone(resp.json(), "response data must not be empty") | ||||||
|  |         self.assertEqual( | ||||||
|  |             resp.json()["error"], | ||||||
|  |             "the incoming request is not valid", | ||||||
|  |             "invalid error message returned", | ||||||
|  |         ) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user