gix_credentials/helper/
mod.rs

1use bstr::{BStr, BString};
2
3use crate::{protocol, protocol::Context, Program};
4
5/// A list of helper programs to run in order to obtain credentials.
6#[allow(dead_code)]
7#[derive(Debug)]
8pub struct Cascade {
9    /// The programs to run in order to obtain credentials
10    pub programs: Vec<Program>,
11    /// If true, stderr is enabled when `programs` are run, which is the default.
12    pub stderr: bool,
13    /// If true, http(s) urls will take their path portion into account when obtaining credentials. Default is false.
14    /// Other protocols like ssh will always use the path portion.
15    pub use_http_path: bool,
16    /// If true, default false, when getting credentials, we will set a bogus password to only obtain the user name.
17    /// Storage and cancellation work the same, but without a password set.
18    pub query_user_only: bool,
19}
20
21/// The outcome of the credentials helper [invocation][crate::helper::invoke()].
22#[derive(Debug, Clone, Eq, PartialEq)]
23pub struct Outcome {
24    /// The username to use in the identity, if set.
25    pub username: Option<String>,
26    /// The password to use in the identity, if set.
27    pub password: Option<String>,
28    /// If set, the helper asked to stop the entire process, whether the identity is complete or not.
29    pub quit: bool,
30    /// A handle to the action to perform next in another call to [`helper::invoke()`][crate::helper::invoke()].
31    pub next: NextAction,
32}
33
34impl Outcome {
35    /// Try to fetch username _and_ password to form an identity. This will fail if one of them is not set.
36    ///
37    /// This does nothing if only one of the fields is set, or consume both.
38    pub fn consume_identity(&mut self) -> Option<gix_sec::identity::Account> {
39        if self.username.is_none() || self.password.is_none() {
40            return None;
41        }
42        self.username
43            .take()
44            .zip(self.password.take())
45            .map(|(username, password)| gix_sec::identity::Account { username, password })
46    }
47}
48
49/// The Result type used in [`invoke()`][crate::helper::invoke()].
50pub type Result = std::result::Result<Option<Outcome>, Error>;
51
52/// The error used in the [credentials helper invocation][crate::helper::invoke()].
53#[derive(Debug, thiserror::Error)]
54#[allow(missing_docs)]
55pub enum Error {
56    #[error(transparent)]
57    ContextDecode(#[from] protocol::context::decode::Error),
58    #[error("An IO error occurred while communicating to the credentials helper")]
59    Io(#[from] std::io::Error),
60    #[error(transparent)]
61    CredentialsHelperFailed { source: std::io::Error },
62}
63
64/// The action to perform by the credentials [helper][`crate::helper::invoke()`].
65#[derive(Clone, Debug)]
66pub enum Action {
67    /// Provide credentials using the given repository context, which must include the repository url.
68    Get(Context),
69    /// Approve the credentials as identified by the previous input provided as `BString`, containing information from [`Context`].
70    Store(BString),
71    /// Reject the credentials as identified by the previous input provided as `BString`. containing information from [`Context`].
72    Erase(BString),
73}
74
75/// Initialization
76impl Action {
77    /// Create a `Get` action with context containing the given URL.
78    /// Note that this creates an `Action` suitable for the credential helper cascade only.
79    pub fn get_for_url(url: impl Into<BString>) -> Action {
80        Action::Get(Context {
81            url: Some(url.into()),
82            ..Default::default()
83        })
84    }
85}
86
87/// Access
88impl Action {
89    /// Return the payload of store or erase actions.
90    pub fn payload(&self) -> Option<&BStr> {
91        use bstr::ByteSlice;
92        match self {
93            Action::Get(_) => None,
94            Action::Store(p) | Action::Erase(p) => Some(p.as_bstr()),
95        }
96    }
97    /// Return the context of a get operation, or `None`.
98    ///
99    /// The opposite of [`payload`][Action::payload()].
100    pub fn context(&self) -> Option<&Context> {
101        match self {
102            Action::Get(ctx) => Some(ctx),
103            Action::Erase(_) | Action::Store(_) => None,
104        }
105    }
106
107    /// Return the mutable context of a get operation, or `None`.
108    pub fn context_mut(&mut self) -> Option<&mut Context> {
109        match self {
110            Action::Get(ctx) => Some(ctx),
111            Action::Erase(_) | Action::Store(_) => None,
112        }
113    }
114
115    /// Returns true if this action expects output from the helper.
116    pub fn expects_output(&self) -> bool {
117        matches!(self, Action::Get(_))
118    }
119
120    /// The name of the argument to describe this action. If `is_external` is true, the target program is
121    /// a custom credentials helper, not a built-in one.
122    pub fn as_arg(&self, is_external: bool) -> &str {
123        match self {
124            Action::Get(_) if is_external => "get",
125            Action::Get(_) => "fill",
126            Action::Store(_) if is_external => "store",
127            Action::Store(_) => "approve",
128            Action::Erase(_) if is_external => "erase",
129            Action::Erase(_) => "reject",
130        }
131    }
132}
133
134/// A handle to [store][NextAction::store()] or [erase][NextAction::erase()] the outcome of the initial action.
135#[derive(Clone, Debug, Eq, PartialEq)]
136pub struct NextAction {
137    previous_output: BString,
138}
139
140impl TryFrom<&NextAction> for Context {
141    type Error = protocol::context::decode::Error;
142
143    fn try_from(value: &NextAction) -> std::result::Result<Self, Self::Error> {
144        Context::from_bytes(value.previous_output.as_ref())
145    }
146}
147
148impl From<Context> for NextAction {
149    fn from(ctx: Context) -> Self {
150        let mut buf = Vec::<u8>::new();
151        ctx.write_to(&mut buf).expect("cannot fail");
152        NextAction {
153            previous_output: buf.into(),
154        }
155    }
156}
157
158impl NextAction {
159    /// Approve the result of the previous [Action] and store for lookup.
160    pub fn store(self) -> Action {
161        Action::Store(self.previous_output)
162    }
163    /// Reject the result of the previous [Action] and erase it as to not be returned when being looked up.
164    pub fn erase(self) -> Action {
165        Action::Erase(self.previous_output)
166    }
167}
168
169mod cascade;
170pub(crate) mod invoke;
171
172pub use invoke::invoke;