mirror of https://github.com/chipsenkbeil/distant
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
4.0 KiB
Rust
134 lines
4.0 KiB
Rust
use std::io;
|
|
use std::str::FromStr;
|
|
|
|
use async_trait::async_trait;
|
|
|
|
use crate::authenticator::Authenticator;
|
|
use crate::methods::AuthenticationMethod;
|
|
use crate::msg::{Challenge, Error, Question};
|
|
|
|
/// Authenticaton method for a static secret key
|
|
#[derive(Clone, Debug)]
|
|
pub struct StaticKeyAuthenticationMethod<T> {
|
|
key: T,
|
|
}
|
|
|
|
impl<T> StaticKeyAuthenticationMethod<T> {
|
|
pub const ID: &str = "static_key";
|
|
|
|
#[inline]
|
|
pub fn new(key: T) -> Self {
|
|
Self { key }
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl<T> AuthenticationMethod for StaticKeyAuthenticationMethod<T>
|
|
where
|
|
T: FromStr + PartialEq + Send + Sync,
|
|
{
|
|
fn id(&self) -> &'static str {
|
|
Self::ID
|
|
}
|
|
|
|
async fn authenticate(&self, authenticator: &mut dyn Authenticator) -> io::Result<()> {
|
|
let response = authenticator
|
|
.challenge(Challenge {
|
|
questions: vec![Question {
|
|
label: "key".to_string(),
|
|
text: "Provide a key: ".to_string(),
|
|
options: Default::default(),
|
|
}],
|
|
options: Default::default(),
|
|
})
|
|
.await?;
|
|
|
|
if response.answers.is_empty() {
|
|
return Err(Error::non_fatal("missing answer").into_io_permission_denied());
|
|
}
|
|
|
|
match response.answers.into_iter().next().unwrap().parse::<T>() {
|
|
Ok(key) if key == self.key => Ok(()),
|
|
_ => Err(Error::non_fatal("answer does not match key").into_io_permission_denied()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use test_log::test;
|
|
|
|
use super::*;
|
|
use crate::authenticator::TestAuthenticator;
|
|
use crate::msg::*;
|
|
|
|
#[test(tokio::test)]
|
|
async fn authenticate_should_fail_if_key_challenge_fails() {
|
|
let method = StaticKeyAuthenticationMethod::new(String::new());
|
|
|
|
let mut authenticator = TestAuthenticator {
|
|
challenge: Box::new(|_| Err(io::Error::new(io::ErrorKind::InvalidData, "test error"))),
|
|
..Default::default()
|
|
};
|
|
|
|
let err = method.authenticate(&mut authenticator).await.unwrap_err();
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
|
|
assert_eq!(err.to_string(), "test error");
|
|
}
|
|
|
|
#[test(tokio::test)]
|
|
async fn authenticate_should_fail_if_no_answer_included_in_challenge_response() {
|
|
let method = StaticKeyAuthenticationMethod::new(String::new());
|
|
|
|
let mut authenticator = TestAuthenticator {
|
|
challenge: Box::new(|_| {
|
|
Ok(ChallengeResponse {
|
|
answers: Vec::new(),
|
|
})
|
|
}),
|
|
..Default::default()
|
|
};
|
|
|
|
let err = method.authenticate(&mut authenticator).await.unwrap_err();
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
|
|
assert_eq!(err.to_string(), "Error: missing answer");
|
|
}
|
|
|
|
#[test(tokio::test)]
|
|
async fn authenticate_should_fail_if_answer_does_not_match_key() {
|
|
let method = StaticKeyAuthenticationMethod::new(String::from("answer"));
|
|
|
|
let mut authenticator = TestAuthenticator {
|
|
challenge: Box::new(|_| {
|
|
Ok(ChallengeResponse {
|
|
answers: vec![String::from("other")],
|
|
})
|
|
}),
|
|
..Default::default()
|
|
};
|
|
|
|
let err = method.authenticate(&mut authenticator).await.unwrap_err();
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
|
|
assert_eq!(err.to_string(), "Error: answer does not match key");
|
|
}
|
|
|
|
#[test(tokio::test)]
|
|
async fn authenticate_should_succeed_if_answer_matches_key() {
|
|
let method = StaticKeyAuthenticationMethod::new(String::from("answer"));
|
|
|
|
let mut authenticator = TestAuthenticator {
|
|
challenge: Box::new(|_| {
|
|
Ok(ChallengeResponse {
|
|
answers: vec![String::from("answer")],
|
|
})
|
|
}),
|
|
..Default::default()
|
|
};
|
|
|
|
method.authenticate(&mut authenticator).await.unwrap();
|
|
}
|
|
}
|