155 lines · 5.8 KB · Rust
RawDownload
use crate::helper::Context;use bstr::{BStr, BString}; /// The outcome of the credentials [`helper`][crate::helper()].#[derive(Debug, Clone, Eq, PartialEq)]pub struct Outcome {    /// The obtained identity.    pub identity: git_sec::identity::Account,    /// A handle to the action to perform next in another call to [`helper::invoke()`][crate::helper::invoke()].    pub next: NextAction,} /// The Result type used in [`helper()`][crate::helper()].pub type Result = std::result::Result<Option<Outcome>, Error>; /// The error used in the [credentials helper][crate::helper::invoke()].#[derive(Debug, thiserror::Error)]#[allow(missing_docs)]pub enum Error {    #[error(transparent)]    Context(#[from] crate::helper::context::decode::Error),    #[error("An IO error occurred while communicating to the credentials helper")]    Io(#[from] std::io::Error),    #[error("Could not find {name:?} in output of credentials helper")]    KeyNotFound { name: &'static str },    #[error(transparent)]    CredentialsHelperFailed { source: std::io::Error },} /// The action to perform by the credentials [helper][`crate::helper()`].#[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),} /// Initializationimpl Action {    /// Create a `Get` action with context containing the given URL    pub fn get_for_url(url: impl Into<BString>) -> Action {        Action::Get(Context {            url: Some(url.into()),            ..Default::default()        })    }} /// Accessimpl 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()),        }    }    /// 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_custom` is true, the target program is    /// a custom credentials helper, not a built-in one.    pub fn as_helper_arg(&self, is_custom: bool) -> &str {        match self {            Action::Get(_) if is_custom => "get",            Action::Get(_) => "fill",            Action::Store(_) if is_custom => "store",            Action::Store(_) => "approve",            Action::Erase(_) if is_custom => "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 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)    }} pub(crate) mod function {    use crate::helper::invoke::{Action, NextAction};    use crate::helper::{invoke::Error, invoke::Outcome, invoke::Result, Context};    use std::io::Read;     impl Action {        /// Send ourselves to the given `write` which is expected to be credentials-helper compatible        pub fn send(&self, mut write: impl std::io::Write) -> std::io::Result<()> {            match self {                Action::Get(ctx) => ctx.write_to(write),                Action::Store(last) | Action::Erase(last) => {                    write.write_all(last)?;                    write.write_all(&[b'\n'])                }            }        }    }     /// Invoke the given `helper` with `action` in `context`.    ///    /// Usually the first call is performed with [`Action::Get`] to obtain `Some` identity, which subsequently can be used.    /// On successful usage, use [`NextAction::store()`], otherwise [`NextAction::erase()`], which returns `Ok(None)` as no outcome    /// is expected.    pub fn invoke(mut helper: impl crate::Helper, action: Action) -> Result {        let (stdin, stdout) = helper.start(&action)?;        if let (Action::Get(_), None) = (&action, &stdout) {            panic!("BUG: `Helper` impls must return an output handle to read output from if Action::Get is provided")        }        action.send(stdin)?;        let stdout = stdout            .map(|mut stdout| {                let mut buf = Vec::new();                stdout.read_to_end(&mut buf).map(|_| buf)            })            .transpose()?;        helper.finish().map_err(|err| {            if err.kind() == std::io::ErrorKind::Other {                Error::CredentialsHelperFailed { source: err }            } else {                err.into()            }        })?;         match matches!(action, Action::Get(_)).then(|| stdout).flatten() {            None => Ok(None),            Some(stdout) => {                let ctx = Context::from_bytes(stdout.as_slice())?;                let username = ctx.username.ok_or(Error::KeyNotFound { name: "username" })?;                let password = ctx.password.ok_or(Error::KeyNotFound { name: "password" })?;                Ok(Some(Outcome {                    identity: git_sec::identity::Account { username, password },                    next: NextAction {                        previous_output: stdout.into(),                    },                }))            }        }    }}