#8 return a 403 if credentials are invalid + adjust corresponding test
This commit is contained in:
		
							parent
							
								
									73059c724f
								
							
						
					
					
						commit
						384e868501
					
				| @ -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 { | ||||||
|  | |||||||
| @ -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,12 @@ impl Into<String> for HTTPStatusLine { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl HTTPStatusLine { | ||||||
|  |     fn set_status_code(&mut self, code: HTTPStatusCode) { | ||||||
|  |         self.status_code = code; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| pub struct HTTPResponse { | pub struct HTTPResponse { | ||||||
|     status_line: HTTPStatusLine, |     status_line: HTTPStatusLine, | ||||||
|     body: json::JsonValue, |     body: json::JsonValue, | ||||||
| @ -61,28 +72,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; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // TODO: impl a valid credentials in `Store`
 |  | ||||||
| 
 |  | ||||||
|         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
 | ||||||
| @ -98,3 +87,53 @@ impl Into<String> for HTTPResponse { | |||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl HTTPResponse { | ||||||
|  |     // `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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// as_403 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 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -28,7 +28,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(); | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ pub struct FileStore { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl FileStore { | impl FileStore { | ||||||
|     fn new(path: String) -> Self { |     pub fn new(path: String) -> Self { | ||||||
|         FileStore { |         FileStore { | ||||||
|             path, |             path, | ||||||
|             credentials: vec![], |             credentials: vec![], | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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