gix_credentials/protocol/
mod.rs

1use bstr::BString;
2
3use crate::helper;
4
5/// The outcome of the credentials top-level functions to obtain a complete identity.
6#[derive(Debug, Clone, Eq, PartialEq)]
7pub struct Outcome {
8    /// The identity provide by the helper.
9    pub identity: gix_sec::identity::Account,
10    /// A handle to the action to perform next in another call to [`helper::invoke()`][crate::helper::invoke()].
11    pub next: helper::NextAction,
12}
13
14/// The Result type used in credentials top-level functions to obtain a complete identity.
15pub type Result = std::result::Result<Option<Outcome>, Error>;
16
17/// The error returned top-level credential functions.
18#[derive(Debug, thiserror::Error)]
19#[allow(missing_docs)]
20pub enum Error {
21    #[error(transparent)]
22    UrlParse(#[from] gix_url::parse::Error),
23    #[error("The 'url' field must be set when performing a 'get/fill' action")]
24    UrlMissing,
25    #[error(transparent)]
26    ContextDecode(#[from] context::decode::Error),
27    #[error(transparent)]
28    InvokeHelper(#[from] helper::Error),
29    #[error("Could not obtain identity for context: {}", { let mut buf = Vec::<u8>::new(); context.write_to(&mut buf).ok(); String::from_utf8_lossy(&buf).into_owned() })]
30    IdentityMissing { context: Context },
31    #[error("The handler asked to stop trying to obtain credentials")]
32    Quit,
33    #[error("Couldn't obtain {prompt}")]
34    Prompt { prompt: String, source: gix_prompt::Error },
35}
36
37/// Additional context to be passed to the credentials helper.
38#[derive(Debug, Default, Clone, Eq, PartialEq)]
39pub struct Context {
40    /// The protocol over which the credential will be used (e.g., https).
41    pub protocol: Option<String>,
42    /// The remote hostname for a network credential. This includes the port number if one was specified (e.g., "example.com:8088").
43    pub host: Option<String>,
44    /// The path with which the credential will be used. E.g., for accessing a remote https repository, this will be the repository’s path on the server.
45    /// It can also be a path on the file system.
46    pub path: Option<BString>,
47    /// The credential’s username, if we already have one (e.g., from a URL, the configuration, the user, or from a previously run helper).
48    pub username: Option<String>,
49    /// The credential’s password, if we are asking it to be stored.
50    pub password: Option<String>,
51    /// An OAuth refresh token that may accompany a password. It is to be treated confidentially, just like the password.
52    pub oauth_refresh_token: Option<String>,
53    /// The expiry date of OAuth tokens as seconds from Unix epoch.
54    pub password_expiry_utc: Option<gix_date::SecondsSinceUnixEpoch>,
55    /// When this special attribute is read by git credential, the value is parsed as a URL and treated as if its constituent
56    /// parts were read (e.g., url=<https://example.com> would behave as if
57    /// protocol=https and host=example.com had been provided). This can help callers avoid parsing URLs themselves.
58    pub url: Option<BString>,
59    /// If true, the caller should stop asking for credentials immediately without calling more credential helpers in the chain.
60    pub quit: Option<bool>,
61}
62
63/// Convert the outcome of a helper invocation to a helper result, assuring that the identity is complete in the process.
64#[allow(clippy::result_large_err)]
65pub fn helper_outcome_to_result(outcome: Option<helper::Outcome>, action: helper::Action) -> Result {
66    match (action, outcome) {
67        (helper::Action::Get(ctx), None) => Err(Error::IdentityMissing {
68            context: ctx.redacted(),
69        }),
70        (helper::Action::Get(ctx), Some(mut outcome)) => match outcome.consume_identity() {
71            Some(identity) => Ok(Some(Outcome {
72                identity,
73                next: outcome.next,
74            })),
75            None => Err(if outcome.quit {
76                Error::Quit
77            } else {
78                Error::IdentityMissing {
79                    context: ctx.redacted(),
80                }
81            }),
82        },
83        (helper::Action::Store(_) | helper::Action::Erase(_), _ignore) => Ok(None),
84    }
85}
86
87///
88pub mod context;