History

change!: Use `helper::Context` in `helper::Action::Fill()` (#450)

That way additional information, like from configuration, can be passed as well.

Sebastian Thiel committed 4 years ago

Commit 9c6f0241 parent 0e76efe5 files · +22 −147Browse files

Unified · Split

5 files
  1. git-credentials/src/helper/invoke.rs +11 −23
  2. git-credentials/src/helper/mod.rs +7 −49
  3. git-credentials/src/lib.rs +3 −7
  4. git-credentials/src/program.rs +1 −1
  5. git-credentials/tests/helper/mod.rs +0 −67
git-credentials/src/helper/invoke.rs+11−23
@@ -15,26 +15,25 @@ pub type Result = std::result::Result<Option<Outcome>, Error>; · 14 unmodified lines
1515 #[derive(Debug, thiserror::Error)]
1616 #[allow(missing_docs)]
1717 pub enum Error {
18− #[error("Cannot handle usernames or passwords in illformed UTF-8 encoding")]
19− IllformedUtf8InUsernameOrPassword,
18+ #[error(transparent)]
19+ Context(#[from] crate::helper::context::decode::Error),
2020 #[error("An IO error occurred while communicating to the credentials helper")]
2121 Io(#[from] std::io::Error),
2222 #[error("Could not find {name:?} in output of credentials helper")]
23− KeyNotFound { name: String },
23+ KeyNotFound { name: &'static str },
2424 #[error(transparent)]
2525 CredentialsHelperFailed { source: std::io::Error },
2626 }
2727
2828 pub(crate) mod function {
29− use crate::helper::{invoke::Error, invoke::Outcome, invoke::Result, message, Action, Context, NextAction};
30− use bstr::ByteVec;
29+ use crate::helper::{invoke::Error, invoke::Outcome, invoke::Result, Action, Context, NextAction};
3130 use std::io::Read;
3231
33− impl Action<'_> {
32+ impl Action {
3433 /// Send ourselves to the given `write` which is expected to be credentials-helper compatible
3534 pub fn send(&self, mut write: impl std::io::Write) -> std::io::Result<()> {
3635 match self {
37− Action::Fill(url) => message::encode(url, write),
36+ Action::Fill(ctx) => ctx.write_to(write),
3837 Action::Approve(last) | Action::Reject(last) => {
3938 write.write_all(last)?;
4039 write.write_all(&[b'\n'])
@@ -47,7 +46,7 @@ pub(crate) mod function { · 6 unmodified lines
4746 ///
4847 /// Usually the first call is performed with [`Action::Fill`] to obtain an identity, which subsequently can be used.
4948 /// On successful usage, use [`NextAction::approve()`], otherwise [`NextAction::reject()`].
50− pub fn invoke(mut helper: impl crate::Helper, action: Action<'_>, _context: Context) -> Result {
49+ pub fn invoke(mut helper: impl crate::Helper, action: Action) -> Result {
5150 let (stdin, stdout) = helper.start(&action)?;
5251 action.send(stdin)?;
5352 let stdout = stdout
@@ -67,22 +66,11 @@ pub(crate) mod function { · 13 unmodified lines
6766 match stdout {
6867 None => Ok(None),
6968 Some(stdout) => {
70− let kvs = message::decode(stdout.as_slice())?;
71− let find = |name: &str| {
72− kvs.iter()
73− .find(|(k, _)| k == name)
74− .ok_or_else(|| Error::KeyNotFound { name: name.into() })
75− .map(|(_, n)| n.to_vec())
76− };
69+ let ctx = Context::from_bytes(stdout.as_slice())?;
70+ let username = ctx.username.ok_or_else(|| Error::KeyNotFound { name: "username" })?;
71+ let password = ctx.password.ok_or_else(|| Error::KeyNotFound { name: "password" })?;
7772 Ok(Some(Outcome {
78− identity: git_sec::identity::Account {
79− username: find("username")?
80− .into_string()
81− .map_err(|_| Error::IllformedUtf8InUsernameOrPassword)?,
82− password: find("password")?
83− .into_string()
84− .map_err(|_| Error::IllformedUtf8InUsernameOrPassword)?,
85− },
73+ identity: git_sec::identity::Account { username, password },
8674 next: NextAction {
8775 previous_output: stdout.into(),
8876 },
git-credentials/src/helper/mod.rs+7−49
@@ -1,4 +1,4 @@
1−use bstr::{BStr, BString};
1+use bstr::BString;
22
33 /// The kind of helper program to use.
44 pub enum Kind {
@@ -31,16 +31,16 @@ pub mod context; · 26 unmodified lines
3131
3232 /// The action to perform by the credentials [helper][`crate::helper()`].
3333 #[derive(Clone, Debug)]
34−pub enum Action<'a> {
35− /// Provide credentials using the given repository URL (as &str) as context and pre-parsed url information as seen in [`Context`].
36− Fill(&'a BStr),
34+pub enum Action {
35+ /// Provide credentials using the given repository context, which must include the repository url.
36+ Fill(Context),
3737 /// Approve the credentials as identified by the previous input provided as `BString`, containing information from [`Context`].
3838 Approve(BString),
3939 /// Reject the credentials as identified by the previous input provided as `BString`. containing information from [`Context`].
4040 Reject(BString),
4141 }
4242
43−impl<'a> Action<'a> {
43+impl Action {
4444 /// Returns true if this action expects output from the helper.
4545 pub fn expects_output(&self) -> bool {
4646 matches!(self, Action::Fill(_))
@@ -67,11 +67,11 @@ pub struct NextAction { · 20 unmodified lines
6767
6868 impl NextAction {
6969 /// Approve the result of the previous [Action].
70− pub fn approve(self) -> Action<'static> {
70+ pub fn approve(self) -> Action {
7171 Action::Approve(self.previous_output)
7272 }
7373 /// Reject the result of the previous [Action].
74− pub fn reject(self) -> Action<'static> {
74+ pub fn reject(self) -> Action {
7575 Action::Reject(self.previous_output)
7676 }
7777 }
@@ -79,45 +79,3 @@ impl NextAction { · 1 unmodified line
7979 ///
8080 pub mod invoke;
8181 pub use invoke::function::invoke;
82−
83−///
84−pub mod message {
85− use bstr::{BStr, BString, ByteSlice};
86−
87− /// Encode `url` to `out` for consumption by a `git credentials` helper program.
88− pub fn encode(url: &BStr, mut out: impl std::io::Write) -> std::io::Result<()> {
89− validate(url)?;
90− writeln!(out, "url={}\n", url)
91− }
92−
93− fn validate(url: &BStr) -> std::io::Result<()> {
94− if url.contains(&0) || url.contains(&b'\n') {
95− return Err(std::io::Error::new(
96− std::io::ErrorKind::Other,
97− "token to encode must not contain newlines or null bytes",
98− ));
99− }
100− Ok(())
101− }
102−
103− /// Decode all lines in `input` as key-value pairs produced by a `git credentials` helper program.
104− pub fn decode(mut input: impl std::io::Read) -> std::io::Result<Vec<(String, BString)>> {
105− let mut buf = Vec::<u8>::with_capacity(512);
106− input.read_to_end(&mut buf)?;
107− buf.lines()
108− .take_while(|line| !line.is_empty())
109− .map(|line| {
110− let mut it = line.splitn(2, |b| *b == b'=');
111− match (it.next().and_then(|k| k.to_str().ok()), it.next().map(|v| v.as_bstr())) {
112− (Some(key), Some(value)) => validate(key.into())
113− .and_then(|_| validate(value.into()))
114− .map(|_| (key.into(), value.into())),
115− _ => Err(std::io::Error::new(
116− std::io::ErrorKind::Other,
117− format!("Invalid format, expecting key=value, got {:?}", line.as_bstr()),
118− )),
119− }
120− })
121− .collect::<std::io::Result<Vec<_>>>()
122− }
123−}
git-credentials/src/lib.rs+3−7
@@ -17,7 +17,7 @@ pub trait Helper { · 16 unmodified lines
1717 type Receive: std::io::Read;
1818
1919 /// Start the helper and provide handles to send and receive from it.
20− fn start(&mut self, action: &helper::Action<'_>) -> std::io::Result<(Self::Send, Option<Self::Receive>)>;
20+ fn start(&mut self, action: &helper::Action) -> std::io::Result<(Self::Send, Option<Self::Receive>)>;
2121 /// Stop the helper and provide a way to determine it's successful.
2222 fn finish(self) -> std::io::Result<()>;
2323 }
@@ -38,10 +38,6 @@ pub mod helper; · 14 unmodified lines
3838 /// Call the `git` credentials helper program performing the given `action`, without any context from git configuration.
3939 ///
4040 /// See [`invoke()`] for a more flexible implementation.
41−pub fn helper(action: helper::Action<'_>) -> helper::invoke::Result {
42− helper::invoke(
43− Program::from_kind(helper::Kind::GitCredential),
44− action,
45− helper::Context::default(),
46− )
41+pub fn helper(action: helper::Action) -> helper::invoke::Result {
42+ helper::invoke(Program::from_kind(helper::Kind::GitCredential), action)
4743 }
git-credentials/src/program.rs+1−1
@@ -12,7 +12,7 @@ impl Helper for Program { · 11 unmodified lines
1212 type Send = std::process::ChildStdin;
1313 type Receive = std::process::ChildStdout;
1414
15− fn start(&mut self, action: &helper::Action<'_>) -> std::io::Result<(Self::Send, Option<Self::Receive>)> {
15+ fn start(&mut self, action: &helper::Action) -> std::io::Result<(Self::Send, Option<Self::Receive>)> {
1616 match self {
1717 Program::Ready(kind) => {
1818 let (mut cmd, is_custom) = match kind {
git-credentials/tests/helper/mod.rs+0−67
@@ -86,70 +86,3 @@ username=bob"; · 85 unmodified lines
8686 }
8787 }
8888 }
89−
90−mod message {
91− mod encode {
92− use bstr::ByteSlice;
93− use git_credentials::helper::message;
94−
95− #[test]
96− fn from_url() -> crate::Result {
97− let mut out = Vec::new();
98− message::encode("https://github.com/byron/gitoxide".into(), &mut out)?;
99− assert_eq!(out.as_bstr(), b"url=https://github.com/byron/gitoxide\n\n".as_bstr());
100− Ok(())
101− }
102−
103− mod invalid {
104− use git_credentials::helper::message;
105− use std::io;
106−
107− #[test]
108− fn contains_null() {
109− assert_eq!(
110− message::encode("https://foo\u{0}".into(), Vec::new())
111− .err()
112− .map(|e| e.kind()),
113− Some(io::ErrorKind::Other)
114− );
115− }
116− #[test]
117− fn contains_newline() {
118− assert_eq!(
119− message::encode("https://foo\n".into(), Vec::new())
120− .err()
121− .map(|e| e.kind()),
122− Some(io::ErrorKind::Other)
123− );
124− }
125− }
126− }
127−
128− mod decode {
129− use git_credentials::helper::message;
130−
131− #[test]
132− fn typical_response() -> crate::Result {
133− assert_eq!(
134− message::decode(
135− "protocol=https
136−host=example.com
137−username=bob
138−password=secr3t\n\n
139−this=is-skipped-past-empty-line"
140− .as_bytes()
141− )?,
142− vec![
143− ("protocol", "https"),
144− ("host", "example.com"),
145− ("username", "bob"),
146− ("password", "secr3t")
147− ]
148− .iter()
149− .map(|(k, v)| (k.to_string(), (*v).into()))
150− .collect::<Vec<_>>()
151− );
152− Ok(())
153− }
154− }
155−}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t