pull/146/head
Chip Senkbeil 2 years ago
parent 4c16b1f17d
commit 87b6140bb6
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

@ -1,28 +1,28 @@
use async_trait::async_trait;
use distant_net::auth::{AuthErrorKind, AuthHandler, AuthQuestion, AuthVerifyKind};
use distant_net::auth::{AuthHandler, ErrorKind, Question, VerificationKind};
use log::*;
use std::{collections::HashMap, io};
/// Configuration to use when creating a new [`DistantManagerClient`](super::DistantManagerClient)
pub struct DistantManagerClientConfig {
pub on_challenge:
Box<dyn FnMut(Vec<AuthQuestion>, HashMap<String, String>) -> io::Result<Vec<String>>>,
pub on_verify: Box<dyn FnMut(AuthVerifyKind, String) -> io::Result<bool>>,
Box<dyn FnMut(Vec<Question>, HashMap<String, String>) -> io::Result<Vec<String>>>,
pub on_verify: Box<dyn FnMut(VerificationKind, String) -> io::Result<bool>>,
pub on_info: Box<dyn FnMut(String) -> io::Result<()>>,
pub on_error: Box<dyn FnMut(AuthErrorKind, &str) -> io::Result<()>>,
pub on_error: Box<dyn FnMut(ErrorKind, &str) -> io::Result<()>>,
}
#[async_trait]
impl AuthHandler for DistantManagerClientConfig {
async fn on_challenge(
&mut self,
questions: Vec<AuthQuestion>,
questions: Vec<Question>,
options: HashMap<String, String>,
) -> io::Result<Vec<String>> {
(self.on_challenge)(questions, options)
}
async fn on_verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool> {
async fn on_verify(&mut self, kind: VerificationKind, text: String) -> io::Result<bool> {
(self.on_verify)(kind, text)
}
@ -30,7 +30,7 @@ impl AuthHandler for DistantManagerClientConfig {
(self.on_info)(text)
}
async fn on_error(&mut self, kind: AuthErrorKind, text: &str) -> io::Result<()> {
async fn on_error(&mut self, kind: ErrorKind, text: &str) -> io::Result<()> {
(self.on_error)(kind, text)
}
}
@ -77,7 +77,7 @@ impl DistantManagerClientConfig {
on_verify: Box::new(move |kind, text| {
trace!("[manager client] on_verify({kind}, {text})");
match kind {
AuthVerifyKind::Host => {
VerificationKind::Host => {
eprintln!("{}", text);
let answer = text_prompt("Enter [y/N]> ")?;

@ -1,7 +1,6 @@
mod authenticator;
mod data;
mod handler;
pub mod msg;
pub use authenticator::*;
pub use data::*;
pub use handler::*;

@ -1,4 +1,4 @@
use super::{data::*, AuthHandler};
use super::{msg::*, AuthHandler};
use crate::{utils, FramedTransport, Transport};
use async_trait::async_trait;
use log::*;
@ -17,19 +17,19 @@ pub trait Authenticator: Send {
/// Issues a challenge and returns the answers to the `questions` asked.
async fn challenge(
&mut self,
questions: Vec<AuthQuestion>,
questions: Vec<Question>,
options: HashMap<String, String>,
) -> io::Result<Vec<String>>;
/// Requests verification of some `kind` and `text`, returning true if passed verification.
async fn verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool>;
async fn verify(&mut self, kind: VerificationKind, text: String) -> io::Result<bool>;
/// Reports information with no response expected.
async fn info(&mut self, text: String) -> io::Result<()>;
/// Reports an error occurred during authentication, consuming the authenticator since no more
/// challenges should be issued.
async fn error(&mut self, kind: AuthErrorKind, text: String) -> io::Result<()>;
async fn error(&mut self, kind: ErrorKind, text: String) -> io::Result<()>;
/// Reports that the authentication has finished successfully, consuming the authenticator
/// since no more challenges should be issued.
@ -72,25 +72,28 @@ where
{
async fn authenticate(&mut self, mut handler: impl AuthHandler + Send) -> io::Result<()> {
loop {
match next_frame_as!(self, AuthRequest) {
AuthRequest::Challenge(x) => {
match next_frame_as!(self, Authentication) {
Authentication::Challenge(x) => {
trace!("Authenticate::Challenge({x:?})");
let answers = handler.on_challenge(x.questions, x.options).await?;
write_frame!(
self,
AuthResponse::Challenge(AuthChallengeResponse { answers })
AuthenticationResponse::Challenge(ChallengeResponse { answers })
);
}
AuthRequest::Verify(x) => {
Authentication::Verification(x) => {
trace!("Authenticate::Verify({x:?})");
let valid = handler.on_verify(x.kind, x.text).await?;
write_frame!(self, AuthResponse::Verify(AuthVerifyResponse { valid }));
write_frame!(
self,
AuthenticationResponse::Verification(VerificationResponse { valid })
);
}
AuthRequest::Info(x) => {
Authentication::Info(x) => {
trace!("Authenticate::Info({x:?})");
handler.on_info(x.text).await?;
}
AuthRequest::Error(x) => {
Authentication::Error(x) => {
trace!("Authenticate::Error({x:?})");
let kind = x.kind;
let text = x.text;
@ -98,20 +101,24 @@ where
handler.on_error(kind, &text).await?;
return Err(match kind {
AuthErrorKind::FailedChallenge => io::Error::new(
ErrorKind::FailedChallenge => io::Error::new(
io::ErrorKind::PermissionDenied,
format!("Failed challenge: {text}"),
),
AuthErrorKind::FailedVerification => io::Error::new(
ErrorKind::FailedVerification => io::Error::new(
io::ErrorKind::PermissionDenied,
format!("Failed verification: {text}"),
),
AuthErrorKind::Unknown => {
ErrorKind::Unknown => {
io::Error::new(io::ErrorKind::Other, format!("Unknown error: {text}"))
}
});
}
AuthRequest::Finished => {
Authentication::Start(x) => {
trace!("Authenticate::Start({x:?})");
return Ok(());
}
Authentication::Finished => {
trace!("Authenticate::Finished");
return Ok(());
}
@ -127,42 +134,39 @@ where
{
async fn challenge(
&mut self,
questions: Vec<AuthQuestion>,
questions: Vec<Question>,
options: HashMap<String, String>,
) -> io::Result<Vec<String>> {
trace!("Authenticator::challenge(questions = {questions:?}, options = {options:?})");
write_frame!(
self,
AuthRequest::from(AuthChallengeRequest { questions, options })
);
let response = next_frame_as!(self, AuthResponse, Challenge);
write_frame!(self, Authentication::from(Challenge { questions, options }));
let response = next_frame_as!(self, AuthenticationResponse, Challenge);
Ok(response.answers)
}
async fn verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool> {
async fn verify(&mut self, kind: VerificationKind, text: String) -> io::Result<bool> {
trace!("Authenticator::verify(kind = {kind:?}, text = {text:?})");
write_frame!(self, AuthRequest::from(AuthVerifyRequest { kind, text }));
let response = next_frame_as!(self, AuthResponse, Verify);
write_frame!(self, Authentication::from(Verification { kind, text }));
let response = next_frame_as!(self, AuthenticationResponse, Verification);
Ok(response.valid)
}
async fn info(&mut self, text: String) -> io::Result<()> {
trace!("Authenticator::info(text = {text:?})");
write_frame!(self, AuthRequest::from(AuthInfo { text }));
write_frame!(self, Authentication::from(Info { text }));
Ok(())
}
async fn error(&mut self, kind: AuthErrorKind, text: String) -> io::Result<()> {
async fn error(&mut self, kind: ErrorKind, text: String) -> io::Result<()> {
trace!("Authenticator::error(kind = {kind:?}, text = {text:?})");
write_frame!(self, AuthRequest::from(AuthError { kind, text }));
write_frame!(self, Authentication::from(Error { kind, text }));
Ok(())
}
async fn finished(&mut self) -> io::Result<()> {
trace!("Authenticator::finished()");
write_frame!(self, AuthRequest::Finished);
write_frame!(self, Authentication::Finished);
Ok(())
}
}

@ -1,126 +0,0 @@
use derive_more::{Display, From};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Represents authentication messages that act as initiators such as providing
/// a challenge, verifying information, presenting information, or highlighting an error
#[derive(Clone, Debug, From, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum AuthRequest {
/// Issues a challenge to be answered
Challenge(AuthChallengeRequest),
/// Requests verification of some text
Verify(AuthVerifyRequest),
/// Reports some information
Info(AuthInfo),
/// Reports an error occurrred
Error(AuthError),
/// Indicates that the authentication is finished
Finished,
}
/// Represents a challenge comprising a series of questions to be presented
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthChallengeRequest {
pub questions: Vec<AuthQuestion>,
pub options: HashMap<String, String>,
}
/// Represents an ask to verify some information
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthVerifyRequest {
pub kind: AuthVerifyKind,
pub text: String,
}
/// Represents some information to be presented
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthInfo {
pub text: String,
}
/// Represents some error that occurred
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthError {
pub kind: AuthErrorKind,
pub text: String,
}
/// Represents authentication messages that are responses to auth requests such
/// as answers to challenges or verifying information
#[derive(Clone, Debug, From, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum AuthResponse {
/// Contains answers to challenge request
Challenge(AuthChallengeResponse),
/// Contains response to a verification request
Verify(AuthVerifyResponse),
}
/// Represents the answers to a previously-asked challenge
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthChallengeResponse {
pub answers: Vec<String>,
}
/// Represents the answer to a previously-asked verify
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthVerifyResponse {
pub valid: bool,
}
/// Represents the type of verification being requested
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AuthVerifyKind {
/// An ask to verify the host such as with SSH
#[display(fmt = "host")]
Host,
/// When the verification is unknown (happens when other side is unaware of the kind)
#[display(fmt = "unknown")]
#[serde(other)]
Unknown,
}
/// Represents a single question in a challenge
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthQuestion {
/// The text of the question
pub text: String,
/// Any options information specific to a particular auth domain
/// such as including a username and instructions for SSH authentication
pub options: HashMap<String, String>,
}
impl AuthQuestion {
/// Creates a new question without any options data
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
options: HashMap::new(),
}
}
}
/// Represents the type of error encountered during authentication
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthErrorKind {
/// When the answer(s) to a challenge do not pass authentication
FailedChallenge,
/// When verification during authentication fails
/// (e.g. a host is not allowed or blocked)
FailedVerification,
/// When the error is unknown
Unknown,
}

@ -1,20 +1,22 @@
use super::data::*;
use super::msg::*;
use async_trait::async_trait;
use std::{collections::HashMap, io};
use std::io;
/// Interface for a handler of authentication requests
#[async_trait]
pub trait AuthHandler {
/// Callback when a challenge is received, returning answers to the given questions.
async fn on_challenge(
&mut self,
questions: Vec<AuthQuestion>,
options: HashMap<String, String>,
) -> io::Result<Vec<String>>;
async fn on_challenge(&mut self, challenge: Challenge) -> io::Result<Vec<String>>;
/// Callback when a verification request is received, returning true if approvided or false if
/// unapproved.
async fn on_verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool>;
async fn on_verification(&mut self, verification: Verification) -> io::Result<bool>;
/// Callback when authentication starts for a specific method
#[allow(unused_variables)]
async fn on_start(&mut self, start: AuthenticationStart) -> io::Result<()> {
Ok(())
}
/// Callback when authentication is finished and no more requests will be received
async fn on_finished(&mut self) -> io::Result<()> {
@ -23,7 +25,7 @@ pub trait AuthHandler {
/// Callback when information is received. To fail, return an error from this function.
#[allow(unused_variables)]
async fn on_info(&mut self, text: String) -> io::Result<()> {
async fn on_info(&mut self, info: Info) -> io::Result<()> {
Ok(())
}
@ -31,34 +33,7 @@ pub trait AuthHandler {
/// the authenticator. In the situation where a custom error would be preferred, have this
/// callback return an error.
#[allow(unused_variables)]
async fn on_error(&mut self, kind: AuthErrorKind, text: &str) -> io::Result<()> {
async fn on_error(&mut self, error: Error) -> io::Result<()> {
Ok(())
}
}
#[async_trait]
impl<H: AuthHandler + Send> AuthHandler for &mut H {
async fn on_challenge(
&mut self,
questions: Vec<AuthQuestion>,
options: HashMap<String, String>,
) -> io::Result<Vec<String>> {
AuthHandler::on_challenge(self, questions, options).await
}
async fn on_verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool> {
AuthHandler::on_verify(self, kind, text).await
}
async fn on_finished(&mut self) -> io::Result<()> {
AuthHandler::on_finished(self).await
}
async fn on_info(&mut self, text: String) -> io::Result<()> {
AuthHandler::on_info(self, text).await
}
async fn on_error(&mut self, kind: AuthErrorKind, text: &str) -> io::Result<()> {
AuthHandler::on_error(self, kind, text).await
}
}

@ -0,0 +1,158 @@
use derive_more::{Display, Error, From};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Represents messages from an authenticator that act as initiators such as providing
/// a challenge, verifying information, presenting information, or highlighting an error
#[derive(Clone, Debug, From, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum Authentication {
/// Indicates that authentication is starting for the specific `method`
Start(AuthenticationStart),
/// Lists methods available for authentication
Methods(AuthenticationMethods),
/// Issues a challenge to be answered
Challenge(Challenge),
/// Requests verification of some text
Verification(Verification),
/// Reports some information associated with authentication
Info(Info),
/// Reports an error occurrred during authentication
Error(Error),
/// Indicates that the authentication is finished
Finished,
}
/// Represents the start of authentication for some method
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AuthenticationStart {
pub method: AuthenticationMethod,
}
/// Represents a list of authentication methods available
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AuthenticationMethods {
pub methods: Vec<AuthenticationMethod>,
}
/// Represents the type of authentication method to use by the authenticator
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AuthenticationMethod {
/// Indicates that a static key is being used for authentication
StaticKey,
/// Indicates that re-authentication is being employed (using specialized key)
Reauthentication,
/// When the method is unknown (happens when other side is unaware of the method)
#[serde(other)]
Unknown,
}
/// Represents a challenge comprising a series of questions to be presented
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Challenge {
pub questions: Vec<Question>,
pub options: HashMap<String, String>,
}
/// Represents an ask to verify some information
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Verification {
pub kind: VerificationKind,
pub text: String,
}
/// Represents some information to be presented related to authentication
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Info {
pub text: String,
}
/// Represents authentication messages that are responses to authenticator requests such
/// as answers to challenges or verifying information
#[derive(Clone, Debug, From, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum AuthenticationResponse {
/// Contains answers to challenge request
Challenge(ChallengeResponse),
/// Contains response to a verification request
Verification(VerificationResponse),
}
/// Represents the answers to a previously-asked challenge associated with authentication
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ChallengeResponse {
pub answers: Vec<String>,
}
/// Represents the answer to a previously-asked verification associated with authentication
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VerificationResponse {
pub valid: bool,
}
/// Represents the type of verification being requested
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum VerificationKind {
/// An ask to verify the host such as with SSH
#[display(fmt = "host")]
Host,
/// When the verification is unknown (happens when other side is unaware of the kind)
#[display(fmt = "unknown")]
#[serde(other)]
Unknown,
}
/// Represents a single question in a challenge associated with authentication
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Question {
/// The text of the question
pub text: String,
/// Any options information specific to a particular auth domain
/// such as including a username and instructions for SSH authentication
pub options: HashMap<String, String>,
}
impl Question {
/// Creates a new question without any options data
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
options: HashMap::new(),
}
}
}
/// Represents some error that occurred during authentication
#[derive(Clone, Debug, Display, Error, Serialize, Deserialize)]
#[display(fmt = "{}: {}", kind, text)]
pub struct Error {
/// Represents the kind of error
pub kind: ErrorKind,
/// Description of the error
pub text: String,
}
/// Represents the type of error encountered during authentication
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorKind {
/// Error is unrecoverable
Fatal,
/// Error is recoverable
Error,
}

@ -42,7 +42,7 @@ where
mod tests {
use super::*;
use crate::{
auth::{AuthHandler, AuthQuestion, AuthVerifyKind, Authenticator},
auth::{AuthHandler, Authenticator, Question, VerificationKind},
Client, ConnectionCtx, Request, ServerCtx,
};
use std::{
@ -80,13 +80,13 @@ mod tests {
impl AuthHandler for TestAuthHandler {
async fn on_challenge(
&mut self,
_: Vec<AuthQuestion>,
_: Vec<Question>,
_: HashMap<String, String>,
) -> io::Result<Vec<String>> {
Ok(Vec::new())
}
async fn on_verify(&mut self, _: AuthVerifyKind, _: String) -> io::Result<bool> {
async fn on_verify(&mut self, _: VerificationKind, _: String) -> io::Result<bool> {
Ok(true)
}
}

@ -44,7 +44,7 @@ where
mod tests {
use super::*;
use crate::{
auth::{AuthHandler, AuthQuestion, AuthVerifyKind, Authenticator},
auth::{AuthHandler, Authenticator, Question, VerificationKind},
Client, ConnectionCtx, Request, ServerCtx,
};
use std::collections::HashMap;
@ -80,13 +80,13 @@ mod tests {
impl AuthHandler for TestAuthHandler {
async fn on_challenge(
&mut self,
_: Vec<AuthQuestion>,
_: Vec<Question>,
_: HashMap<String, String>,
) -> io::Result<Vec<String>> {
Ok(Vec::new())
}
async fn on_verify(&mut self, _: AuthVerifyKind, _: String) -> io::Result<bool> {
async fn on_verify(&mut self, _: VerificationKind, _: String) -> io::Result<bool> {
Ok(true)
}
}

Loading…
Cancel
Save