gix_credentials/protocol/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
use bstr::BString;
use crate::helper;
/// The outcome of the credentials top-level functions to obtain a complete identity.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Outcome {
/// The identity provide by the helper.
pub identity: gix_sec::identity::Account,
/// A handle to the action to perform next in another call to [`helper::invoke()`][crate::helper::invoke()].
pub next: helper::NextAction,
}
/// The Result type used in credentials top-level functions to obtain a complete identity.
pub type Result = std::result::Result<Option<Outcome>, Error>;
/// The error returned top-level credential functions.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
UrlParse(#[from] gix_url::parse::Error),
#[error("The 'url' field must be set when performing a 'get/fill' action")]
UrlMissing,
#[error(transparent)]
ContextDecode(#[from] context::decode::Error),
#[error(transparent)]
InvokeHelper(#[from] helper::Error),
#[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() })]
IdentityMissing { context: Context },
#[error("The handler asked to stop trying to obtain credentials")]
Quit,
#[error("Couldn't obtain {prompt}")]
Prompt { prompt: String, source: gix_prompt::Error },
}
/// Additional context to be passed to the credentials helper.
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Context {
/// The protocol over which the credential will be used (e.g., https).
pub protocol: Option<String>,
/// The remote hostname for a network credential. This includes the port number if one was specified (e.g., "example.com:8088").
pub host: Option<String>,
/// 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.
/// It can also be a path on the file system.
pub path: Option<BString>,
/// The credential’s username, if we already have one (e.g., from a URL, the configuration, the user, or from a previously run helper).
pub username: Option<String>,
/// The credential’s password, if we are asking it to be stored.
pub password: Option<String>,
/// When this special attribute is read by git credential, the value is parsed as a URL and treated as if its constituent
/// parts were read (e.g., url=<https://example.com> would behave as if
/// protocol=https and host=example.com had been provided). This can help callers avoid parsing URLs themselves.
pub url: Option<BString>,
/// If true, the caller should stop asking for credentials immediately without calling more credential helpers in the chain.
pub quit: Option<bool>,
}
/// Convert the outcome of a helper invocation to a helper result, assuring that the identity is complete in the process.
#[allow(clippy::result_large_err)]
pub fn helper_outcome_to_result(outcome: Option<helper::Outcome>, action: helper::Action) -> Result {
fn redact(mut ctx: Context) -> Context {
if let Some(pw) = ctx.password.as_mut() {
*pw = "<redacted>".into();
}
ctx
}
match (action, outcome) {
(helper::Action::Get(ctx), None) => Err(Error::IdentityMissing { context: redact(ctx) }),
(helper::Action::Get(ctx), Some(mut outcome)) => match outcome.consume_identity() {
Some(identity) => Ok(Some(Outcome {
identity,
next: outcome.next,
})),
None => Err(if outcome.quit {
Error::Quit
} else {
Error::IdentityMissing { context: redact(ctx) }
}),
},
(helper::Action::Store(_) | helper::Action::Erase(_), _ignore) => Ok(None),
}
}
///
pub mod context;