distant_auth/methods/
static_key.rs1use std::io;
2use std::str::FromStr;
3
4use async_trait::async_trait;
5
6use crate::authenticator::Authenticator;
7use crate::methods::AuthenticationMethod;
8use crate::msg::{Challenge, Error, Question};
9
10#[derive(Clone, Debug)]
12pub struct StaticKeyAuthenticationMethod<T> {
13 key: T,
14}
15
16impl<T> StaticKeyAuthenticationMethod<T> {
17 pub const ID: &str = "static_key";
18
19 #[inline]
20 pub fn new(key: T) -> Self {
21 Self { key }
22 }
23}
24
25#[async_trait]
26impl<T> AuthenticationMethod for StaticKeyAuthenticationMethod<T>
27where
28 T: FromStr + PartialEq + Send + Sync,
29{
30 fn id(&self) -> &'static str {
31 Self::ID
32 }
33
34 async fn authenticate(&self, authenticator: &mut dyn Authenticator) -> io::Result<()> {
35 let response = authenticator
36 .challenge(Challenge {
37 questions: vec![Question {
38 label: "key".to_string(),
39 text: "Provide a key: ".to_string(),
40 options: Default::default(),
41 }],
42 options: Default::default(),
43 })
44 .await?;
45
46 if response.answers.is_empty() {
47 return Err(Error::non_fatal("missing answer").into_io_permission_denied());
48 }
49
50 match response.answers.into_iter().next().unwrap().parse::<T>() {
51 Ok(key) if key == self.key => Ok(()),
52 _ => Err(Error::non_fatal("answer does not match key").into_io_permission_denied()),
53 }
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use test_log::test;
60
61 use super::*;
62 use crate::authenticator::TestAuthenticator;
63 use crate::msg::*;
64
65 #[test(tokio::test)]
66 async fn authenticate_should_fail_if_key_challenge_fails() {
67 let method = StaticKeyAuthenticationMethod::new(String::new());
68
69 let mut authenticator = TestAuthenticator {
70 challenge: Box::new(|_| Err(io::Error::new(io::ErrorKind::InvalidData, "test error"))),
71 ..Default::default()
72 };
73
74 let err = method.authenticate(&mut authenticator).await.unwrap_err();
75
76 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
77 assert_eq!(err.to_string(), "test error");
78 }
79
80 #[test(tokio::test)]
81 async fn authenticate_should_fail_if_no_answer_included_in_challenge_response() {
82 let method = StaticKeyAuthenticationMethod::new(String::new());
83
84 let mut authenticator = TestAuthenticator {
85 challenge: Box::new(|_| {
86 Ok(ChallengeResponse {
87 answers: Vec::new(),
88 })
89 }),
90 ..Default::default()
91 };
92
93 let err = method.authenticate(&mut authenticator).await.unwrap_err();
94
95 assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
96 assert_eq!(err.to_string(), "Error: missing answer");
97 }
98
99 #[test(tokio::test)]
100 async fn authenticate_should_fail_if_answer_does_not_match_key() {
101 let method = StaticKeyAuthenticationMethod::new(String::from("answer"));
102
103 let mut authenticator = TestAuthenticator {
104 challenge: Box::new(|_| {
105 Ok(ChallengeResponse {
106 answers: vec![String::from("other")],
107 })
108 }),
109 ..Default::default()
110 };
111
112 let err = method.authenticate(&mut authenticator).await.unwrap_err();
113
114 assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
115 assert_eq!(err.to_string(), "Error: answer does not match key");
116 }
117
118 #[test(tokio::test)]
119 async fn authenticate_should_succeed_if_answer_matches_key() {
120 let method = StaticKeyAuthenticationMethod::new(String::from("answer"));
121
122 let mut authenticator = TestAuthenticator {
123 challenge: Box::new(|_| {
124 Ok(ChallengeResponse {
125 answers: vec![String::from("answer")],
126 })
127 }),
128 ..Default::default()
129 };
130
131 method.authenticate(&mut authenticator).await.unwrap();
132 }
133}