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