distant_auth/methods/
static_key.rs

1use 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/// Authenticaton method for a static secret key
11#[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}