summaryrefslogtreecommitdiffstats
path: root/vendor/gix-credentials/src/helper/cascade.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-credentials/src/helper/cascade.rs
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-credentials/src/helper/cascade.rs')
-rw-r--r--vendor/gix-credentials/src/helper/cascade.rs161
1 files changed, 161 insertions, 0 deletions
diff --git a/vendor/gix-credentials/src/helper/cascade.rs b/vendor/gix-credentials/src/helper/cascade.rs
new file mode 100644
index 000000000..9ec251161
--- /dev/null
+++ b/vendor/gix-credentials/src/helper/cascade.rs
@@ -0,0 +1,161 @@
+use crate::{helper, helper::Cascade, protocol, protocol::Context, Program};
+
+impl Default for Cascade {
+ fn default() -> Self {
+ Cascade {
+ programs: Vec::new(),
+ stderr: true,
+ use_http_path: false,
+ query_user_only: false,
+ }
+ }
+}
+
+/// Initialization
+impl Cascade {
+ /// Return the programs to run for the current platform.
+ ///
+ /// These are typically used as basis for all credential cascade invocations, with configured programs following afterwards.
+ ///
+ /// # Note
+ ///
+ /// These defaults emulate what typical git installations may use these days, as in fact it's a configurable which comes
+ /// from installation-specific configuration files which we cannot know (or guess at best).
+ /// This seems like an acceptable trade-off as helpers are ignored if they fail or are not existing.
+ pub fn platform_builtin() -> Vec<Program> {
+ if cfg!(target_os = "macos") {
+ Some("osxkeychain")
+ } else if cfg!(target_os = "linux") {
+ Some("libsecret")
+ } else if cfg!(target_os = "windows") {
+ Some("manager-core")
+ } else {
+ None
+ }
+ .map(|name| vec![Program::from_custom_definition(name)])
+ .unwrap_or_default()
+ }
+}
+
+/// Builder
+impl Cascade {
+ /// Extend the list of programs to run `programs`.
+ pub fn extend(mut self, programs: impl IntoIterator<Item = Program>) -> Self {
+ self.programs.extend(programs);
+ self
+ }
+ /// If `toggle` is true, http(s) urls will use the path portions of the url to obtain a credential for.
+ ///
+ /// Otherwise, they will only take the user name into account.
+ pub fn use_http_path(mut self, toggle: bool) -> Self {
+ self.use_http_path = toggle;
+ self
+ }
+
+ /// If `toggle` is true, a bogus password will be provided to prevent any helper program from prompting for it, nor will
+ /// we prompt for the password. The resulting identity will have a bogus password and it's expected to not be used by the
+ /// consuming transport.
+ pub fn query_user_only(mut self, toggle: bool) -> Self {
+ self.query_user_only = toggle;
+ self
+ }
+}
+
+/// Finalize
+impl Cascade {
+ /// Invoke the cascade by `invoking` each program with `action`, and configuring potential prompts with `prompt` options.
+ /// The latter can also be used to disable the prompt entirely when setting the `mode` to [`Disable`][gix_prompt::Mode::Disable];=.
+ ///
+ /// When _getting_ credentials, all programs are asked until the credentials are complete, stopping the cascade.
+ /// When _storing_ or _erasing_ all programs are instructed in order.
+ #[allow(clippy::result_large_err)]
+ pub fn invoke(&mut self, mut action: helper::Action, mut prompt: gix_prompt::Options<'_>) -> protocol::Result {
+ let mut url = action
+ .context_mut()
+ .map(|ctx| {
+ ctx.destructure_url_in_place(self.use_http_path).map(|ctx| {
+ if self.query_user_only && ctx.password.is_none() {
+ ctx.password = Some("".into());
+ }
+ ctx
+ })
+ })
+ .transpose()?
+ .and_then(|ctx| ctx.url.take());
+
+ for program in &mut self.programs {
+ program.stderr = self.stderr;
+ match helper::invoke::raw(program, &action) {
+ Ok(None) => {}
+ Ok(Some(stdout)) => {
+ let ctx = Context::from_bytes(&stdout)?;
+ if let Some(dst_ctx) = action.context_mut() {
+ if let Some(src) = ctx.path {
+ dst_ctx.path = Some(src);
+ }
+ for (src, dst) in [
+ (ctx.protocol, &mut dst_ctx.protocol),
+ (ctx.host, &mut dst_ctx.host),
+ (ctx.username, &mut dst_ctx.username),
+ (ctx.password, &mut dst_ctx.password),
+ ] {
+ if let Some(src) = src {
+ *dst = Some(src);
+ }
+ }
+ if let Some(src) = ctx.url {
+ dst_ctx.url = Some(src);
+ url = dst_ctx.destructure_url_in_place(self.use_http_path)?.url.take();
+ }
+ if dst_ctx.username.is_some() && dst_ctx.password.is_some() {
+ break;
+ }
+ if ctx.quit.unwrap_or_default() {
+ dst_ctx.quit = ctx.quit;
+ break;
+ }
+ }
+ }
+ Err(helper::Error::CredentialsHelperFailed { .. }) => continue, // ignore helpers that we can't call
+ Err(err) if action.context().is_some() => return Err(err.into()), // communication errors are fatal when getting credentials
+ Err(_) => {} // for other actions, ignore everything, try the operation
+ }
+ }
+
+ if prompt.mode != gix_prompt::Mode::Disable {
+ if let Some(ctx) = action.context_mut() {
+ ctx.url = url;
+ if ctx.username.is_none() {
+ let message = ctx.to_prompt("Username");
+ prompt.mode = gix_prompt::Mode::Visible;
+ ctx.username = gix_prompt::ask(&message, &prompt)
+ .map_err(|err| protocol::Error::Prompt {
+ prompt: message,
+ source: err,
+ })?
+ .into();
+ }
+ if ctx.password.is_none() {
+ let message = ctx.to_prompt("Password");
+ prompt.mode = gix_prompt::Mode::Hidden;
+ ctx.password = gix_prompt::ask(&message, &prompt)
+ .map_err(|err| protocol::Error::Prompt {
+ prompt: message,
+ source: err,
+ })?
+ .into();
+ }
+ }
+ }
+
+ protocol::helper_outcome_to_result(
+ action.context().map(|ctx| helper::Outcome {
+ username: ctx.username.clone(),
+ password: ctx.password.clone(),
+ quit: ctx.quit.unwrap_or(false),
+ next: ctx.to_owned().into(),
+ }),
+ action,
+ )
+ }
+}