gix_credentials/helper/mod.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
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;