diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-credentials/src/helper/cascade.rs | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-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.rs | 161 |
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, + ) + } +} |