mirror of https://github.com/chipsenkbeil/distant
distant-ssh2 is compiling
parent
7baf2b5092
commit
920c3c5578
@ -1,38 +0,0 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Wrapper type around `T` that provides compile-time confirmation of being authenticated
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Authenticated<T>(T);
|
||||
|
||||
impl<T> Authenticated<T> {
|
||||
/// Consumes authenticated wrapper and returns the inner value
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Authenticated<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for Authenticated<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Authenticated<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Authenticated<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
use super::{
|
||||
Challenge, ChallengeResponse, Error, Info, Verification, VerificationKind, VerificationResponse,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use std::io;
|
||||
|
||||
/// Interface for a handler of authentication requests for a specific authentication method.
|
||||
#[async_trait]
|
||||
pub trait AuthMethodHandler {
|
||||
/// Callback when a challenge is received, returning answers to the given questions.
|
||||
async fn on_challenge(&mut self, challenge: Challenge) -> io::Result<ChallengeResponse>;
|
||||
|
||||
/// Callback when a verification request is received, returning true if approvided or false if
|
||||
/// unapproved.
|
||||
async fn on_verification(
|
||||
&mut self,
|
||||
verification: Verification,
|
||||
) -> io::Result<VerificationResponse>;
|
||||
|
||||
/// Callback when information is received. To fail, return an error from this function.
|
||||
async fn on_info(&mut self, info: Info) -> io::Result<()>;
|
||||
|
||||
/// Callback when an error is received. Regardless of the result returned, this will terminate
|
||||
/// the authenticator. In the situation where a custom error would be preferred, have this
|
||||
/// callback return an error.
|
||||
async fn on_error(&mut self, error: Error) -> io::Result<()>;
|
||||
}
|
||||
|
||||
mod prompt;
|
||||
pub use prompt::*;
|
||||
|
||||
mod static_key;
|
||||
pub use static_key::*;
|
@ -0,0 +1,88 @@
|
||||
use super::{
|
||||
AuthMethodHandler, Challenge, ChallengeResponse, Error, Info, Verification, VerificationKind,
|
||||
VerificationResponse,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::*;
|
||||
use std::io;
|
||||
|
||||
/// Blocking implementation of [`AuthMethodHandler`] that uses prompts to communicate challenge &
|
||||
/// verification requests, receiving responses to relay back.
|
||||
pub struct PromptAuthMethodHandler<T, U> {
|
||||
text_prompt: T,
|
||||
password_prompt: U,
|
||||
}
|
||||
|
||||
impl<T, U> PromptAuthMethodHandler<T, U> {
|
||||
pub fn new(text_prompt: T, password_prompt: U) -> Self {
|
||||
Self {
|
||||
text_prompt,
|
||||
password_prompt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T, U> AuthMethodHandler for PromptAuthMethodHandler<T, U>
|
||||
where
|
||||
T: Fn(&str) -> io::Result<String> + Send + Sync + 'static,
|
||||
U: Fn(&str) -> io::Result<String> + Send + Sync + 'static,
|
||||
{
|
||||
async fn on_challenge(&mut self, challenge: Challenge) -> io::Result<ChallengeResponse> {
|
||||
trace!("on_challenge({challenge:?})");
|
||||
let mut answers = Vec::new();
|
||||
for question in challenge.questions.iter() {
|
||||
// Contains all prompt lines including same line
|
||||
let mut lines = question.text.split('\n').collect::<Vec<_>>();
|
||||
|
||||
// Line that is prompt on same line as answer
|
||||
let line = lines.pop().unwrap();
|
||||
|
||||
// Go ahead and display all other lines
|
||||
for line in lines.into_iter() {
|
||||
eprintln!("{}", line);
|
||||
}
|
||||
|
||||
// Get an answer from user input, or use a blank string as an answer
|
||||
// if we fail to get input from the user
|
||||
let answer = (self.password_prompt)(line).unwrap_or_default();
|
||||
|
||||
answers.push(answer);
|
||||
}
|
||||
Ok(ChallengeResponse { answers })
|
||||
}
|
||||
|
||||
async fn on_verification(
|
||||
&mut self,
|
||||
verification: Verification,
|
||||
) -> io::Result<VerificationResponse> {
|
||||
trace!("on_verify({verification:?})");
|
||||
match verification.kind {
|
||||
VerificationKind::Host => {
|
||||
eprintln!("{}", verification.text);
|
||||
|
||||
let answer = (self.text_prompt)("Enter [y/N]> ")?;
|
||||
trace!("Verify? Answer = '{answer}'");
|
||||
Ok(VerificationResponse {
|
||||
valid: matches!(answer.trim(), "y" | "Y" | "yes" | "YES"),
|
||||
})
|
||||
}
|
||||
x => {
|
||||
error!("Unsupported verify kind: {x}");
|
||||
Ok(VerificationResponse { valid: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_info(&mut self, info: Info) -> io::Result<()> {
|
||||
trace!("on_info({info:?})");
|
||||
println!("{}", info.text);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn on_error(&mut self, error: Error) -> io::Result<()> {
|
||||
trace!("on_error({error:?})");
|
||||
eprintln!("{}: {}", error.kind, error.text);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
use super::{
|
||||
AuthMethodHandler, Challenge, ChallengeResponse, Error, Info, Verification,
|
||||
VerificationResponse,
|
||||
};
|
||||
use crate::common::HeapSecretKey;
|
||||
use async_trait::async_trait;
|
||||
use log::*;
|
||||
use std::io;
|
||||
|
||||
/// Implementation of [`AuthMethodHandler`] that answers challenge requests using a static
|
||||
/// [`HeapSecretKey`]. All other portions of method authentication are handled by another
|
||||
/// [`AuthMethodHandler`].
|
||||
pub struct StaticKeyAuthMethodHandler {
|
||||
key: HeapSecretKey,
|
||||
handler: Box<dyn AuthMethodHandler + Send>,
|
||||
}
|
||||
|
||||
impl StaticKeyAuthMethodHandler {
|
||||
/// Creates a new [`StaticKeyAuthMethodHandler`] that responds to challenges using a static
|
||||
/// `key`. All other requests are passed to the `handler`.
|
||||
pub fn new<T: AuthMethodHandler + Send + 'static>(
|
||||
key: impl Into<HeapSecretKey>,
|
||||
handler: T,
|
||||
) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
handler: Box::new(handler),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`StaticKeyAuthMethodHandler`] that responds to challenges using a static
|
||||
/// `key`. All other requests are passed automatically, meaning that verification is always
|
||||
/// approvide and info/errors are ignored.
|
||||
pub fn simple(key: impl Into<HeapSecretKey>) -> Self {
|
||||
Self::new(key, {
|
||||
struct __AuthMethodHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl AuthMethodHandler for __AuthMethodHandler {
|
||||
async fn on_challenge(&mut self, _: Challenge) -> io::Result<ChallengeResponse> {
|
||||
unreachable!("on_challenge should be handled by StaticKeyAuthMethodHandler");
|
||||
}
|
||||
|
||||
async fn on_verification(
|
||||
&mut self,
|
||||
_: Verification,
|
||||
) -> io::Result<VerificationResponse> {
|
||||
Ok(VerificationResponse { valid: true })
|
||||
}
|
||||
|
||||
async fn on_info(&mut self, _: Info) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn on_error(&mut self, _: Error) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
__AuthMethodHandler
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthMethodHandler for StaticKeyAuthMethodHandler {
|
||||
async fn on_challenge(&mut self, challenge: Challenge) -> io::Result<ChallengeResponse> {
|
||||
trace!("on_challenge({challenge:?})");
|
||||
let mut answers = Vec::new();
|
||||
for question in challenge.questions.iter() {
|
||||
// Only challenges with a "key" label are allowed, all else will fail
|
||||
if question.label != "key" {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Only 'key' challenges are supported",
|
||||
));
|
||||
}
|
||||
answers.push(self.key.to_string());
|
||||
}
|
||||
Ok(ChallengeResponse { answers })
|
||||
}
|
||||
|
||||
async fn on_verification(
|
||||
&mut self,
|
||||
verification: Verification,
|
||||
) -> io::Result<VerificationResponse> {
|
||||
trace!("on_verify({verification:?})");
|
||||
self.handler.on_verification(verification).await
|
||||
}
|
||||
|
||||
async fn on_info(&mut self, info: Info) -> io::Result<()> {
|
||||
trace!("on_info({info:?})");
|
||||
self.handler.on_info(info).await
|
||||
}
|
||||
|
||||
async fn on_error(&mut self, error: Error) -> io::Result<()> {
|
||||
trace!("on_error({error:?})");
|
||||
self.handler.on_error(error).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::common::authentication::msg::{ErrorKind, Question, VerificationKind};
|
||||
use test_log::test;
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn on_challenge_should_fail_if_non_key_question_received() {
|
||||
let mut handler = StaticKeyAuthMethodHandler::simple(HeapSecretKey::generate(32).unwrap());
|
||||
|
||||
handler
|
||||
.on_challenge(Challenge {
|
||||
questions: vec![Question::new("test")],
|
||||
options: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn on_challenge_should_answer_with_stringified_key_for_key_questions() {
|
||||
let mut handler = StaticKeyAuthMethodHandler::simple(HeapSecretKey::generate(32).unwrap());
|
||||
|
||||
let response = handler
|
||||
.on_challenge(Challenge {
|
||||
questions: vec![Question::new("key")],
|
||||
options: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.answers.len(), 1, "Wrong answer set received");
|
||||
assert!(!response.answers[0].is_empty(), "Empty answer being sent");
|
||||
}
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn on_verification_should_leverage_fallback_handler() {
|
||||
let mut handler = StaticKeyAuthMethodHandler::simple(HeapSecretKey::generate(32).unwrap());
|
||||
|
||||
let response = handler
|
||||
.on_verification(Verification {
|
||||
kind: VerificationKind::Host,
|
||||
text: "host".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(response.valid, "Unexpected result from fallback handler");
|
||||
}
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn on_info_should_leverage_fallback_handler() {
|
||||
let mut handler = StaticKeyAuthMethodHandler::simple(HeapSecretKey::generate(32).unwrap());
|
||||
|
||||
handler
|
||||
.on_info(Info {
|
||||
text: "info".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn on_error_should_leverage_fallback_handler() {
|
||||
let mut handler = StaticKeyAuthMethodHandler::simple(HeapSecretKey::generate(32).unwrap());
|
||||
|
||||
handler
|
||||
.on_error(Error {
|
||||
kind: ErrorKind::Error,
|
||||
text: "text".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue