blob: 107d6db7a1664dd7779d193e907e680f66624318 [file] [log] [blame]
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<Program>,
/// 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<String>,
/// The password to use in the identity, if set.
pub password: Option<String>,
/// 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<gix_sec::identity::Account> {
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<Option<Outcome>, 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<BString>) -> 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<Self, Self::Error> {
Context::from_bytes(value.previous_output.as_ref())
}
}
impl From<Context> for NextAction {
fn from(ctx: Context) -> Self {
let mut buf = Vec::<u8>::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;