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 { key: T, } impl StaticKeyAuthenticationMethod { pub const ID: &str = "static_key"; #[inline] pub fn new(key: T) -> Self { Self { key } } } #[async_trait] impl AuthenticationMethod for StaticKeyAuthenticationMethod 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::() { 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(); } }