use std::convert::TryFrom; use bstr::{BStr, BString}; use crate::{protocol, protocol::Context, Program}; /// A list of helper programs to run in order to obtain credentials. #[allow(dead_code)] #[derive(Debug)] pub struct Cascade { /// The programs to run in order to obtain credentials pub programs: Vec, /// If true, stderr is enabled when `programs` are run, which is the default. pub stderr: bool, /// If true, http(s) urls will take their path portion into account when obtaining credentials. Default is false. /// Other protocols like ssh will always use the path portion. pub use_http_path: bool, /// If true, default false, when getting credentials, we will set a bogus password to only obtain the user name. /// Storage and cancellation work the same, but without a password set. pub query_user_only: bool, } /// The outcome of the credentials helper [invocation][crate::helper::invoke()]. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Outcome { /// The username to use in the identity, if set. pub username: Option, /// The password to use in the identity, if set. pub password: Option, /// If set, the helper asked to stop the entire process, whether the identity is complete or not. pub quit: bool, /// A handle to the action to perform next in another call to [`helper::invoke()`][crate::helper::invoke()]. pub next: NextAction, } impl Outcome { /// Try to fetch username _and_ password to form an identity. This will fail if one of them is not set. /// /// This does nothing if only one of the fields is set, or consume both. pub fn consume_identity(&mut self) -> Option { if self.username.is_none() || self.password.is_none() { return None; } self.username .take() .zip(self.password.take()) .map(|(username, password)| gix_sec::identity::Account { username, password }) } } /// The Result type used in [`invoke()`][crate::helper::invoke()]. pub type Result = std::result::Result, Error>; /// The error used in the [credentials helper invocation][crate::helper::invoke()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] ContextDecode(#[from] protocol::context::decode::Error), #[error("An IO error occurred while communicating to the credentials helper")] Io(#[from] std::io::Error), #[error(transparent)] CredentialsHelperFailed { source: std::io::Error }, } /// The action to perform by the credentials [helper][`crate::helper::invoke()`]. #[derive(Clone, Debug)] pub enum Action { /// Provide credentials using the given repository context, which must include the repository url. Get(Context), /// Approve the credentials as identified by the previous input provided as `BString`, containing information from [`Context`]. Store(BString), /// Reject the credentials as identified by the previous input provided as `BString`. containing information from [`Context`]. Erase(BString), } /// Initialization impl Action { /// Create a `Get` action with context containing the given URL. /// Note that this creates an `Action` suitable for the credential helper cascade only. pub fn get_for_url(url: impl Into) -> Action { Action::Get(Context { url: Some(url.into()), ..Default::default() }) } } /// Access impl Action { /// Return the payload of store or erase actions. pub fn payload(&self) -> Option<&BStr> { use bstr::ByteSlice; match self { Action::Get(_) => None, Action::Store(p) | Action::Erase(p) => Some(p.as_bstr()), } } /// Return the context of a get operation, or `None`. /// /// The opposite of [`payload`][Action::payload()]. pub fn context(&self) -> Option<&Context> { match self { Action::Get(ctx) => Some(ctx), Action::Erase(_) | Action::Store(_) => None, } } /// Return the mutable context of a get operation, or `None`. pub fn context_mut(&mut self) -> Option<&mut Context> { match self { Action::Get(ctx) => Some(ctx), Action::Erase(_) | Action::Store(_) => None, } } /// Returns true if this action expects output from the helper. pub fn expects_output(&self) -> bool { matches!(self, Action::Get(_)) } /// The name of the argument to describe this action. If `is_external` is true, the target program is /// a custom credentials helper, not a built-in one. pub fn as_arg(&self, is_external: bool) -> &str { match self { Action::Get(_) if is_external => "get", Action::Get(_) => "fill", Action::Store(_) if is_external => "store", Action::Store(_) => "approve", Action::Erase(_) if is_external => "erase", Action::Erase(_) => "reject", } } } /// A handle to [store][NextAction::store()] or [erase][NextAction::erase()] the outcome of the initial action. #[derive(Clone, Debug, Eq, PartialEq)] pub struct NextAction { previous_output: BString, } impl TryFrom<&NextAction> for Context { type Error = protocol::context::decode::Error; fn try_from(value: &NextAction) -> std::result::Result { Context::from_bytes(value.previous_output.as_ref()) } } impl From for NextAction { fn from(ctx: Context) -> Self { let mut buf = Vec::::new(); ctx.write_to(&mut buf).expect("cannot fail"); NextAction { previous_output: buf.into(), } } } impl NextAction { /// Approve the result of the previous [Action] and store for lookup. pub fn store(self) -> Action { Action::Store(self.previous_output) } /// Reject the result of the previous [Action] and erase it as to not be returned when being looked up. pub fn erase(self) -> Action { Action::Erase(self.previous_output) } } mod cascade; pub(crate) mod invoke; pub use invoke::invoke;