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
15#[derive(Debug, thiserror::Error)]15#[derive(Debug, thiserror::Error)]
16#[allow(missing_docs)]16#[allow(missing_docs)]
17pub enum Error {17pub enum Error {
18 #[error("Cannot handle usernames or passwords in illformed UTF-8 encoding")]18 #[error(transparent)]
19 IllformedUtf8InUsernameOrPassword,19 Context(#[from] crate::helper::context::decode::Error),
20 #[error("An IO error occurred while communicating to the credentials helper")]20 #[error("An IO error occurred while communicating to the credentials helper")]
21 Io(#[from] std::io::Error),21 Io(#[from] std::io::Error),
22 #[error("Could not find {name:?} in output of credentials helper")]22 #[error("Could not find {name:?} in output of credentials helper")]
23 KeyNotFound { name: String },23 KeyNotFound { name: &'static str },
24 #[error(transparent)]24 #[error(transparent)]
25 CredentialsHelperFailed { source: std::io::Error },25 CredentialsHelperFailed { source: std::io::Error },
26}26}
27 27
28pub(crate) mod function {28pub(crate) mod function {
29 use crate::helper::{invoke::Error, invoke::Outcome, invoke::Result, message, Action, Context, NextAction};29 use crate::helper::{invoke::Error, invoke::Outcome, invoke::Result, Action, Context, NextAction};
30 use bstr::ByteVec;
31 use std::io::Read;30 use std::io::Read;
32 31
33 impl Action<'_> {32 impl Action {
34 /// Send ourselves to the given `write` which is expected to be credentials-helper compatible33 /// Send ourselves to the given `write` which is expected to be credentials-helper compatible
35 pub fn send(&self, mut write: impl std::io::Write) -> std::io::Result<()> {34 pub fn send(&self, mut write: impl std::io::Write) -> std::io::Result<()> {
36 match self {35 match self {
37 Action::Fill(url) => message::encode(url, write),36 Action::Fill(ctx) => ctx.write_to(write),
38 Action::Approve(last) | Action::Reject(last) => {37 Action::Approve(last) | Action::Reject(last) => {
39 write.write_all(last)?;38 write.write_all(last)?;
40 write.write_all(&[b'\n'])39 write.write_all(&[b'\n'])
@@ -47,7 +46,7 @@ pub(crate) mod function { · 6 unmodified lines
47 ///46 ///
48 /// Usually the first call is performed with [`Action::Fill`] to obtain an identity, which subsequently can be used.47 /// Usually the first call is performed with [`Action::Fill`] to obtain an identity, which subsequently can be used.
49 /// On successful usage, use [`NextAction::approve()`], otherwise [`NextAction::reject()`].48 /// 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 {
51 let (stdin, stdout) = helper.start(&action)?;50 let (stdin, stdout) = helper.start(&action)?;
52 action.send(stdin)?;51 action.send(stdin)?;
53 let stdout = stdout52 let stdout = stdout
@@ -67,22 +66,11 @@ pub(crate) mod function { · 13 unmodified lines
67 match stdout {66 match stdout {
68 None => Ok(None),67 None => Ok(None),
69 Some(stdout) => {68 Some(stdout) => {
70 let kvs = message::decode(stdout.as_slice())?;69 let ctx = Context::from_bytes(stdout.as_slice())?;
71 let find = |name: &str| {70 let username = ctx.username.ok_or_else(|| Error::KeyNotFound { name: "username" })?;
72 kvs.iter()71 let password = ctx.password.ok_or_else(|| Error::KeyNotFound { name: "password" })?;
73 .find(|(k, _)| k == name)
74 .ok_or_else(|| Error::KeyNotFound { name: name.into() })
75 .map(|(_, n)| n.to_vec())
76 };
77 Ok(Some(Outcome {72 Ok(Some(Outcome {
78 identity: git_sec::identity::Account {73 identity: git_sec::identity::Account { username, password },
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 },
86 next: NextAction {74 next: NextAction {
87 previous_output: stdout.into(),75 previous_output: stdout.into(),
88 },76 },
git-credentials/src/helper/mod.rs+7−49
@@ -1,4 +1,4 @@
1use bstr::{BStr, BString};1use bstr::BString;
2 2
3/// The kind of helper program to use.3/// The kind of helper program to use.
4pub enum Kind {4pub enum Kind {
@@ -31,16 +31,16 @@ pub mod context; · 26 unmodified lines
31 31
32/// The action to perform by the credentials [helper][`crate::helper()`].32/// The action to perform by the credentials [helper][`crate::helper()`].
33#[derive(Clone, Debug)]33#[derive(Clone, Debug)]
34pub enum Action<'a> {34pub enum Action {
35 /// Provide credentials using the given repository URL (as &str) as context and pre-parsed url information as seen in [`Context`].35 /// Provide credentials using the given repository context, which must include the repository url.
36 Fill(&'a BStr),36 Fill(Context),
37 /// Approve the credentials as identified by the previous input provided as `BString`, containing information from [`Context`].37 /// Approve the credentials as identified by the previous input provided as `BString`, containing information from [`Context`].
38 Approve(BString),38 Approve(BString),
39 /// Reject the credentials as identified by the previous input provided as `BString`. containing information from [`Context`].39 /// Reject the credentials as identified by the previous input provided as `BString`. containing information from [`Context`].
40 Reject(BString),40 Reject(BString),
41}41}
42 42
43impl<'a> Action<'a> {43impl Action {
44 /// Returns true if this action expects output from the helper.44 /// Returns true if this action expects output from the helper.
45 pub fn expects_output(&self) -> bool {45 pub fn expects_output(&self) -> bool {
46 matches!(self, Action::Fill(_))46 matches!(self, Action::Fill(_))
@@ -67,11 +67,11 @@ pub struct NextAction { · 20 unmodified lines
67 67
68impl NextAction {68impl NextAction {
69 /// Approve the result of the previous [Action].69 /// Approve the result of the previous [Action].
70 pub fn approve(self) -> Action<'static> {70 pub fn approve(self) -> Action {
71 Action::Approve(self.previous_output)71 Action::Approve(self.previous_output)
72 }72 }
73 /// Reject the result of the previous [Action].73 /// Reject the result of the previous [Action].
74 pub fn reject(self) -> Action<'static> {74 pub fn reject(self) -> Action {
75 Action::Reject(self.previous_output)75 Action::Reject(self.previous_output)
76 }76 }
77}77}
@@ -79,45 +79,3 @@ impl NextAction { · 1 unmodified line
79///79///
80pub mod invoke;80pub mod invoke;
81pub use invoke::function::invoke;81pub use invoke::function::invoke;
82
83///
84pub 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
17 type Receive: std::io::Read;17 type Receive: std::io::Read;
18 18
19 /// Start the helper and provide handles to send and receive from it.19 /// 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>)>;
21 /// Stop the helper and provide a way to determine it's successful.21 /// Stop the helper and provide a way to determine it's successful.
22 fn finish(self) -> std::io::Result<()>;22 fn finish(self) -> std::io::Result<()>;
23}23}
@@ -38,10 +38,6 @@ pub mod helper; · 14 unmodified lines
38/// Call the `git` credentials helper program performing the given `action`, without any context from git configuration.38/// Call the `git` credentials helper program performing the given `action`, without any context from git configuration.
39///39///
40/// See [`invoke()`] for a more flexible implementation.40/// See [`invoke()`] for a more flexible implementation.
41pub fn helper(action: helper::Action<'_>) -> helper::invoke::Result {41pub fn helper(action: helper::Action) -> helper::invoke::Result {
42 helper::invoke(42 helper::invoke(Program::from_kind(helper::Kind::GitCredential), action)
43 Program::from_kind(helper::Kind::GitCredential),
44 action,
45 helper::Context::default(),
46 )
47}43}
git-credentials/src/program.rs+1−1
@@ -12,7 +12,7 @@ impl Helper for Program { · 11 unmodified lines
12 type Send = std::process::ChildStdin;12 type Send = std::process::ChildStdin;
13 type Receive = std::process::ChildStdout;13 type Receive = std::process::ChildStdout;
14 14
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>)> {
16 match self {16 match self {
17 Program::Ready(kind) => {17 Program::Ready(kind) => {
18 let (mut cmd, is_custom) = match kind {18 let (mut cmd, is_custom) = match kind {
git-credentials/tests/helper/mod.rs+0−67
@@ -86,70 +86,3 @@ username=bob"; · 85 unmodified lines
86 }86 }
87 }87 }
88}88}
89
90mod 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
136host=example.com
137username=bob
138password=secr3t\n\n
139this=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