Merge branch 'feature/impl-credentials-store' into develop
This commit is contained in:
		
						commit
						8049763b18
					
				
							
								
								
									
										12
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -123,6 +123,17 @@ version = "4.3.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" | checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "async-trait" | ||||||
|  | version = "0.1.57" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "atomic-waker" | name = "atomic-waker" | ||||||
| version = "1.0.0" | version = "1.0.0" | ||||||
| @ -504,6 +515,7 @@ name = "simple-auth" | |||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "async-std", |  "async-std", | ||||||
|  |  "async-trait", | ||||||
|  "json", |  "json", | ||||||
|  "lazy_static", |  "lazy_static", | ||||||
|  "regex", |  "regex", | ||||||
|  | |||||||
| @ -10,6 +10,10 @@ json = "0.12.4" | |||||||
| lazy_static = "1.4.0" | lazy_static = "1.4.0" | ||||||
| regex = "1" | regex = "1" | ||||||
| tokio = { version = "1.21.1", features = ["full"] } | tokio = { version = "1.21.1", features = ["full"] } | ||||||
|  | async-trait = "0.1.57" | ||||||
|  | 
 | ||||||
|  | # useful for tests (embedded files should be delete in release ?) | ||||||
|  | #rust-embed="6.4.1" | ||||||
| 
 | 
 | ||||||
| [dependencies.async-std] | [dependencies.async-std] | ||||||
| version = "1.6" | version = "1.6" | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | //! handlers module includes tools to parse an HTTP request and build and HTTP response
 | ||||||
|  | 
 | ||||||
| pub mod request; | pub mod request; | ||||||
| pub mod response; | pub mod response; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ impl HTTPStartLine { | |||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// check_method checks if the start_line method is in a predefined HTTP method list
 |     /// checks if the start_line method is in a predefined HTTP method list
 | ||||||
|     fn check_method(method: &String) -> bool { |     fn check_method(method: &String) -> bool { | ||||||
|         for m in HTTP_METHODS.iter() { |         for m in HTTP_METHODS.iter() { | ||||||
|             if m.to_string() == *method { |             if m.to_string() == *method { | ||||||
| @ -109,7 +109,7 @@ impl HTTPStartLine { | |||||||
|         false |         false | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// check_target checks if the start_line target is in a predefined HTTP target whitelist
 |     /// checks if the start_line target is in a predefined HTTP target whitelist
 | ||||||
|     fn check_target(target: &String) -> bool { |     fn check_target(target: &String) -> bool { | ||||||
|         for t in HTTP_TARGETS.iter() { |         for t in HTTP_TARGETS.iter() { | ||||||
|             if t.to_string() == *target { |             if t.to_string() == *target { | ||||||
| @ -145,7 +145,7 @@ impl Into<String> for HTTPStartLine { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// HTTPBody represents http request body
 | /// represents an HTTP request body
 | ||||||
| /// for simplicity, only json body is accepted
 | /// for simplicity, only json body is accepted
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct HTTPBody { | pub struct HTTPBody { | ||||||
| @ -156,6 +156,10 @@ impl HTTPBody { | |||||||
|     fn new(data: json::JsonValue) -> HTTPBody { |     fn new(data: json::JsonValue) -> HTTPBody { | ||||||
|         HTTPBody { data } |         HTTPBody { data } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn get_data(&self) -> &json::JsonValue { | ||||||
|  |         &self.data | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl TryFrom<String> for HTTPBody { | impl TryFrom<String> for HTTPBody { | ||||||
| @ -172,7 +176,7 @@ impl TryFrom<String> for HTTPBody { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Request defined the HTTP request
 | /// Represents an HTTP request (headers are not parsed)
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct HTTPRequest { | pub struct HTTPRequest { | ||||||
|     pub start_line: HTTPStartLine, |     pub start_line: HTTPStartLine, | ||||||
| @ -205,7 +209,7 @@ impl HTTPRequest { | |||||||
|         Ok((start_line, request_parts, body)) |         Ok((start_line, request_parts, body)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// parse parses the request by spliting the incoming request with the separator `\r\n`
 |     /// parse the request by spliting the incoming request with the separator `\r\n`
 | ||||||
|     fn parse(request: &str) -> Result<HTTPRequest, String> { |     fn parse(request: &str) -> Result<HTTPRequest, String> { | ||||||
|         let request = request.to_string(); |         let request = request.to_string(); | ||||||
| 
 | 
 | ||||||
| @ -260,7 +264,7 @@ impl From<&str> for HTTPRequest { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn handle_request(request: &str) -> HTTPRequest { | pub fn handle_request(request: &str) -> HTTPRequest { | ||||||
|     return HTTPRequest::from(request); |     HTTPRequest::from(request) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
|  | |||||||
| @ -3,13 +3,17 @@ | |||||||
| //! message specs. see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages
 | //! message specs. see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages
 | ||||||
| //! NOTE: only few parts of the specification has been implemented
 | //! NOTE: only few parts of the specification has been implemented
 | ||||||
| 
 | 
 | ||||||
| use json; |  | ||||||
| 
 |  | ||||||
| use crate::handlers::request::{HTTPRequest, HTTPVersion}; | use crate::handlers::request::{HTTPRequest, HTTPVersion}; | ||||||
|  | use async_trait::async_trait; | ||||||
|  | use json; | ||||||
|  | // add the Store trait to be used by `FileStore`
 | ||||||
|  | use crate::stores::FileStore; | ||||||
|  | use crate::stores::Store; | ||||||
| 
 | 
 | ||||||
| enum HTTPStatusCode { | enum HTTPStatusCode { | ||||||
|     Http200, |     Http200, | ||||||
|     Http400, |     Http400, | ||||||
|  |     Http403, | ||||||
|     Http404, |     Http404, | ||||||
|     Http500, |     Http500, | ||||||
| } | } | ||||||
| @ -20,6 +24,7 @@ impl Into<String> for HTTPStatusCode { | |||||||
|             Self::Http200 => "200".to_string(), |             Self::Http200 => "200".to_string(), | ||||||
|             Self::Http400 => "400".to_string(), |             Self::Http400 => "400".to_string(), | ||||||
|             Self::Http404 => "404".to_string(), |             Self::Http404 => "404".to_string(), | ||||||
|  |             Self::Http403 => "403".to_string(), | ||||||
|             Self::Http500 => "500".to_string(), |             Self::Http500 => "500".to_string(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -47,6 +52,14 @@ impl Into<String> for HTTPStatusLine { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl HTTPStatusLine { | ||||||
|  |     fn set_status_code(&mut self, code: HTTPStatusCode) { | ||||||
|  |         self.status_code = code; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// represents an HTTP response (headers are not parsed)
 | ||||||
|  | /// NOTE: for simplicity, only JSON body are accepted
 | ||||||
| pub struct HTTPResponse { | pub struct HTTPResponse { | ||||||
|     status_line: HTTPStatusLine, |     status_line: HTTPStatusLine, | ||||||
|     body: json::JsonValue, |     body: json::JsonValue, | ||||||
| @ -61,26 +74,6 @@ impl Default for HTTPResponse { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<HTTPRequest> for HTTPResponse { |  | ||||||
|     fn from(request: HTTPRequest) -> Self { |  | ||||||
|         let mut response = HTTPResponse::default(); |  | ||||||
|         if !request.is_valid() { |  | ||||||
|             return response; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let body = json::parse( |  | ||||||
|             r#"{"token": "header.payload.signature", "refresh": "header.payload.signature"}"#, |  | ||||||
|         ) |  | ||||||
|         .unwrap(); |  | ||||||
| 
 |  | ||||||
|         response.status_line.version = request.start_line.version; |  | ||||||
|         response.status_line.status_code = HTTPStatusCode::Http200; |  | ||||||
|         response.body = body; |  | ||||||
| 
 |  | ||||||
|         response |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Into<String> for HTTPResponse { | impl Into<String> for HTTPResponse { | ||||||
|     fn into(self) -> String { |     fn into(self) -> String { | ||||||
|         // move `self.body` into a new var
 |         // move `self.body` into a new var
 | ||||||
| @ -96,3 +89,54 @@ impl Into<String> for HTTPResponse { | |||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl HTTPResponse { | ||||||
|  |     /// creates a response from the incoming `Request`
 | ||||||
|  |     /// `From<T>` could be used instead of forcing it like this
 | ||||||
|  |     /// it fails using `async_trait` attributes (only custom traits work ?)
 | ||||||
|  |     pub async fn from(request: HTTPRequest) -> Self { | ||||||
|  |         let mut response = HTTPResponse::default(); | ||||||
|  |         if !request.is_valid() { | ||||||
|  |             return response; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // empty body -> invalid request (credentials needed)
 | ||||||
|  |         if let None = request.body { | ||||||
|  |             return Self::as_403(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // TODO: path to `store.txt` must not be hardcoded, should be in a config file and load at
 | ||||||
|  |         // runtime
 | ||||||
|  |         let mut store = FileStore::new("tests/data/store.txt".to_string()); | ||||||
|  |         let body = request.body.unwrap(); | ||||||
|  |         let is_auth = store.is_auth(&body.get_data()).await; | ||||||
|  | 
 | ||||||
|  |         if !is_auth { | ||||||
|  |             return Self::as_403(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // TODO: must be a valid JWT (to implement)
 | ||||||
|  |         let body = json::parse( | ||||||
|  |             r#"{"token": "header.payload.signature", "refresh": "header.payload.signature"}"#, | ||||||
|  |         ) | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|  |         response.status_line.version = request.start_line.version; | ||||||
|  |         response.status_line.status_code = HTTPStatusCode::Http200; | ||||||
|  |         response.body = body; | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// generates a 403 response with a correct error message
 | ||||||
|  |     pub fn as_403() -> Self { | ||||||
|  |         let mut response = HTTPResponse { | ||||||
|  |             status_line: HTTPStatusLine::default(), | ||||||
|  |             body: json::parse(r#"{"error": "invalid credentials"}"#).unwrap(), | ||||||
|  |         }; | ||||||
|  |         response | ||||||
|  |             .status_line | ||||||
|  |             .set_status_code(HTTPStatusCode::Http403); | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| mod handlers; | mod handlers; | ||||||
|  | mod stores; | ||||||
| 
 | 
 | ||||||
| use tokio::{ | use tokio::{ | ||||||
|     io::{AsyncReadExt, AsyncWriteExt}, |     io::{AsyncReadExt, AsyncWriteExt}, | ||||||
| @ -20,6 +21,7 @@ async fn main() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// parses the incoming request (partial spec implementation) and build an HTTP response
 | ||||||
| async fn handle_connection(mut stream: TcpStream) { | async fn handle_connection(mut stream: TcpStream) { | ||||||
|     let mut buffer: [u8; 1024] = [0; 1024]; |     let mut buffer: [u8; 1024] = [0; 1024]; | ||||||
|     let n = stream.read(&mut buffer).await.unwrap(); |     let n = stream.read(&mut buffer).await.unwrap(); | ||||||
| @ -27,7 +29,7 @@ async fn handle_connection(mut stream: TcpStream) { | |||||||
|     let request_string = std::str::from_utf8(&buffer[0..n]).unwrap(); |     let request_string = std::str::from_utf8(&buffer[0..n]).unwrap(); | ||||||
|     let request = handle_request(request_string); |     let request = handle_request(request_string); | ||||||
| 
 | 
 | ||||||
|     let response = HTTPResponse::from(request); |     let response = HTTPResponse::from(request).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(); | ||||||
|  | |||||||
							
								
								
									
										104
									
								
								src/stores/file.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/stores/file.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use json; | ||||||
|  | use json::object::Object; | ||||||
|  | use std::path::Path; | ||||||
|  | 
 | ||||||
|  | use tokio::fs::File; | ||||||
|  | use tokio::io::AsyncReadExt; // for read_to_end()
 | ||||||
|  | 
 | ||||||
|  | use super::store::{Credentials, Store}; | ||||||
|  | 
 | ||||||
|  | /// references a credentials store file
 | ||||||
|  | pub struct FileStore { | ||||||
|  |     path: String, | ||||||
|  |     credentials: Vec<Credentials>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FileStore { | ||||||
|  |     pub fn new(path: String) -> Self { | ||||||
|  |         FileStore { | ||||||
|  |             path, | ||||||
|  |             credentials: vec![], | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// loads and reads the file asynchonously
 | ||||||
|  |     /// parses the file line by line to retrieve the credentials
 | ||||||
|  |     async fn parse_contents(&mut self) { | ||||||
|  |         let contents = tokio::fs::read_to_string(&self.path).await; | ||||||
|  |         let mut credentials: Vec<Credentials> = vec![]; | ||||||
|  |         match contents { | ||||||
|  |             Ok(c) => { | ||||||
|  |                 let lines: Vec<&str> = c.split("\n").collect(); | ||||||
|  |                 for line in lines { | ||||||
|  |                     if line.starts_with("#") { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     let line_split: Vec<&str> = line.split(":").collect(); | ||||||
|  |                     if line_split.len() != 2 { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     credentials.push(Credentials::new( | ||||||
|  |                         line_split[0].to_string(), | ||||||
|  |                         line_split[1].to_string(), | ||||||
|  |                     )); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 eprintln!( | ||||||
|  |                     "error occurred while reading store file: {}, err={:?}", | ||||||
|  |                     self.path, e | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         self.credentials = credentials; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// checks if the credentials exist in the `FileStore`
 | ||||||
|  |     fn auth(&self, username: String, password: String) -> bool { | ||||||
|  |         let credentials: Vec<&Credentials> = self | ||||||
|  |             .credentials | ||||||
|  |             .iter() | ||||||
|  |             .filter(|x| x.username == username && x.password == password) | ||||||
|  |             .collect(); | ||||||
|  |         if credentials.len() == 1 { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         false | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl Store for FileStore { | ||||||
|  |     async fn is_auth(&mut self, data: &json::JsonValue) -> bool { | ||||||
|  |         // ensure that the store file already exists even after its instanciation
 | ||||||
|  |         if !Path::new(&self.path).is_file() { | ||||||
|  |             eprintln!("{} path referencing file store does not exist", self.path); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let credentials = Credentials::from(data); | ||||||
|  |         if credentials.is_empty() { | ||||||
|  |             eprintln!("unable to parse the credentials correctly from the incoming request"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let contents = self.parse_contents().await; | ||||||
|  | 
 | ||||||
|  |         self.auth(credentials.username, credentials.password) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_store() { | ||||||
|  |     use std::env; | ||||||
|  | 
 | ||||||
|  |     let root_path = env::var("CARGO_MANIFEST_DIR").unwrap(); | ||||||
|  |     // TODO: path::Path should be better
 | ||||||
|  |     let store_path = format!("{}/{}/{}/{}", root_path, "tests", "data", "store.txt"); | ||||||
|  | 
 | ||||||
|  |     let mut store = FileStore::new(store_path); | ||||||
|  | 
 | ||||||
|  |     let data = json::parse(r#"{"username": "toto", "password": "tata"}"#).unwrap(); | ||||||
|  |     assert_eq!(store.is_auth(&data).await, true); | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								src/stores/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/stores/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | //! store module lists interfaces available to check request credentials
 | ||||||
|  | //! each store must implement the trait `is_auth`
 | ||||||
|  | //! two stores are available :
 | ||||||
|  | //! * `FileStore`: credentials stored in a text file (like **/etc/passwd**)
 | ||||||
|  | //! * `DBStore`: credentials stored in a database (TODO)
 | ||||||
|  | 
 | ||||||
|  | mod file; | ||||||
|  | mod store; | ||||||
|  | 
 | ||||||
|  | pub use file::FileStore; | ||||||
|  | pub use store::Store; | ||||||
							
								
								
									
										72
									
								
								src/stores/store.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/stores/store.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use json; | ||||||
|  | use json::object::Object; | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | pub trait Store { | ||||||
|  |     async fn is_auth(&mut self, data: &json::JsonValue) -> bool; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// extracts `String` json value from a key
 | ||||||
|  | fn extract_json_value(data: &Object, key: &str) -> String { | ||||||
|  |     if let Some(u) = data.get(key) { | ||||||
|  |         match u.as_str() { | ||||||
|  |             Some(s) => return s.to_string(), | ||||||
|  |             None => return "".to_string(), | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |     "".to_string() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Default, Debug)] | ||||||
|  | pub struct Credentials { | ||||||
|  |     pub username: String, | ||||||
|  |     pub password: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Credentials { | ||||||
|  |     pub fn new(username: String, password: String) -> Self { | ||||||
|  |         Credentials { username, password } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn is_empty(&self) -> bool { | ||||||
|  |         self.username == "" || self.password == "" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<&json::JsonValue> for Credentials { | ||||||
|  |     fn from(data: &json::JsonValue) -> Self { | ||||||
|  |         let mut credentials = Credentials::default(); | ||||||
|  |         match data { | ||||||
|  |             json::JsonValue::Object(ref d) => { | ||||||
|  |                 credentials.username = extract_json_value(&d, "username"); | ||||||
|  |                 credentials.password = extract_json_value(&d, "password"); | ||||||
|  |             } | ||||||
|  |             _ => return credentials, | ||||||
|  |         } | ||||||
|  |         credentials | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_credentials() { | ||||||
|  |     struct Expect { | ||||||
|  |         data: json::JsonValue, | ||||||
|  |         is_empty: bool, | ||||||
|  |     } | ||||||
|  |     let test_cases: [Expect; 2] = [ | ||||||
|  |         Expect { | ||||||
|  |             data: json::parse(r#"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}"#).unwrap(), | ||||||
|  |             is_empty: true | ||||||
|  |         }, | ||||||
|  |         Expect { | ||||||
|  |             data: json::parse(r#"{"username":"toto","password": "tata"}"#).unwrap(), | ||||||
|  |             is_empty: false | ||||||
|  |         } | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     for t in test_cases { | ||||||
|  |         let credentials = Credentials::from(&t.data); | ||||||
|  |         assert_eq!(t.is_empty, credentials.is_empty()) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -11,15 +11,15 @@ URL="https://dev.thegux.fr" | |||||||
| for i in {0..10} | for i in {0..10} | ||||||
| do | do | ||||||
| 	http_response=$(curl -s -o response.txt -w "%{http_code}" ${URL}/get/ -d '{"username":"toto", "password":"tutu"}') | 	http_response=$(curl -s -o response.txt -w "%{http_code}" ${URL}/get/ -d '{"username":"toto", "password":"tutu"}') | ||||||
| 	if [ $http_response != "200" ] | 	if [ $http_response != "403" ] | ||||||
| 	then | 	then | ||||||
| 		echo "bad http status code : ${http_response}, expect 200" | 		echo "bad http status code : ${http_response}, expect 200" | ||||||
| 		exit 1 | 		exit 1 | ||||||
| 	fi | 	fi | ||||||
| 
 | 
 | ||||||
| 	if [ "$(cat response.txt | jq -r '.token')" != "header.payload.signature" ] | 	if [ "$(cat response.txt | jq -r '.error')" != "invalid credentials" ] | ||||||
| 	then | 	then | ||||||
| 		echo "bad data returned, expect : ok" | 		echo "bad data returned, expect : invalid credentials" | ||||||
| 		exit 1 | 		exit 1 | ||||||
| 	fi | 	fi | ||||||
| done | done | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								tests/data/store.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tests/data/store.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | # this a test password storage with password in clear | ||||||
|  | # need to be updated in the future to encrypt or hash the password | ||||||
|  | # <username>:<password> | ||||||
|  | toto:tata | ||||||
| @ -36,6 +36,28 @@ class TestResponse(TestCase): | |||||||
|             resp.json()["token"], "header.payload.signature", "bad status returned" |             resp.json()["token"], "header.payload.signature", "bad status returned" | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  |     def test_no_credentials(self): | ||||||
|  |         resp = requests.post(URL + "/get/") | ||||||
|  |         self.assertEqual(resp.status_code, 403, "bad status code returned") | ||||||
|  |         self.assertIsNotNone(resp.json(), "response data must not be empty") | ||||||
|  |         self.assertEqual( | ||||||
|  |             resp.json()["error"], | ||||||
|  |             "invalid credentials", | ||||||
|  |             "invalid error message returned", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def test_bad_credentials(self): | ||||||
|  |         resp = requests.post( | ||||||
|  |             URL + "/get/", json={"username": "tutu", "password": "titi"} | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(resp.status_code, 403, "bas status code returned") | ||||||
|  |         self.assertIsNotNone(resp.json(), "response data must not be empty") | ||||||
|  |         self.assertEqual( | ||||||
|  |             resp.json()["error"], | ||||||
|  |             "invalid credentials", | ||||||
|  |             "invalid error message returned", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|     def test_bad_target(self): |     def test_bad_target(self): | ||||||
|         resp = requests.post( |         resp = requests.post( | ||||||
|             URL + "/token/", json={"username": "toto", "password": "tata"} |             URL + "/token/", json={"username": "toto", "password": "tata"} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user