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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use bstr::{BStr, BString};

use crate::{protocol, protocol::Context, Program};

/// A list of helper programs to run in order to obtain credentials.
#[allow(dead_code)]
#[derive(Debug)]
pub struct Cascade {
    /// The programs to run in order to obtain credentials
    pub programs: Vec<Program>,
    /// If true, stderr is enabled when `programs` are run, which is the default.
    pub stderr: bool,
    /// If true, http(s) urls will take their path portion into account when obtaining credentials. Default is false.
    /// Other protocols like ssh will always use the path portion.
    pub use_http_path: bool,
    /// If true, default false, when getting credentials, we will set a bogus password to only obtain the user name.
    /// Storage and cancellation work the same, but without a password set.
    pub query_user_only: bool,
}

/// The outcome of the credentials helper [invocation][crate::helper::invoke()].
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Outcome {
    /// The username to use in the identity, if set.
    pub username: Option<String>,
    /// The password to use in the identity, if set.
    pub password: Option<String>,
    /// If set, the helper asked to stop the entire process, whether the identity is complete or not.
    pub quit: bool,
    /// A handle to the action to perform next in another call to [`helper::invoke()`][crate::helper::invoke()].
    pub next: NextAction,
}

impl Outcome {
    /// Try to fetch username _and_ password to form an identity. This will fail if one of them is not set.
    ///
    /// This does nothing if only one of the fields is set, or consume both.
    pub fn consume_identity(&mut self) -> Option<gix_sec::identity::Account> {
        if self.username.is_none() || self.password.is_none() {
            return None;
        }
        self.username
            .take()
            .zip(self.password.take())
            .map(|(username, password)| gix_sec::identity::Account { username, password })
    }
}

/// The Result type used in [`invoke()`][crate::helper::invoke()].
pub type Result = std::result::Result<Option<Outcome>, Error>;

/// The error used in the [credentials helper invocation][crate::helper::invoke()].
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
    #[error(transparent)]
    ContextDecode(#[from] protocol::context::decode::Error),
    #[error("An IO error occurred while communicating to the credentials helper")]
    Io(#[from] std::io::Error),
    #[error(transparent)]
    CredentialsHelperFailed { source: std::io::Error },
}

/// The action to perform by the credentials [helper][`crate::helper::invoke()`].
#[derive(Clone, Debug)]
pub enum Action {
    /// Provide credentials using the given repository context, which must include the repository url.
    Get(Context),
    /// Approve the credentials as identified by the previous input provided as `BString`, containing information from [`Context`].
    Store(BString),
    /// Reject the credentials as identified by the previous input provided as `BString`. containing information from [`Context`].
    Erase(BString),
}

/// Initialization
impl Action {
    /// Create a `Get` action with context containing the given URL.
    /// Note that this creates an `Action` suitable for the credential helper cascade only.
    pub fn get_for_url(url: impl Into<BString>) -> Action {
        Action::Get(Context {
            url: Some(url.into()),
            ..Default::default()
        })
    }
}

/// Access
impl Action {
    /// Return the payload of store or erase actions.
    pub fn payload(&self) -> Option<&BStr> {
        use bstr::ByteSlice;
        match self {
            Action::Get(_) => None,
            Action::Store(p) | Action::Erase(p) => Some(p.as_bstr()),
        }
    }
    /// Return the context of a get operation, or `None`.
    ///
    /// The opposite of [`payload`][Action::payload()].
    pub fn context(&self) -> Option<&Context> {
        match self {
            Action::Get(ctx) => Some(ctx),
            Action::Erase(_) | Action::Store(_) => None,
        }
    }

    /// Return the mutable context of a get operation, or `None`.
    pub fn context_mut(&mut self) -> Option<&mut Context> {
        match self {
            Action::Get(ctx) => Some(ctx),
            Action::Erase(_) | Action::Store(_) => None,
        }
    }

    /// Returns true if this action expects output from the helper.
    pub fn expects_output(&self) -> bool {
        matches!(self, Action::Get(_))
    }

    /// The name of the argument to describe this action. If `is_external` is true, the target program is
    /// a custom credentials helper, not a built-in one.
    pub fn as_arg(&self, is_external: bool) -> &str {
        match self {
            Action::Get(_) if is_external => "get",
            Action::Get(_) => "fill",
            Action::Store(_) if is_external => "store",
            Action::Store(_) => "approve",
            Action::Erase(_) if is_external => "erase",
            Action::Erase(_) => "reject",
        }
    }
}

/// A handle to [store][NextAction::store()] or [erase][NextAction::erase()] the outcome of the initial action.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NextAction {
    previous_output: BString,
}

impl TryFrom<&NextAction> for Context {
    type Error = protocol::context::decode::Error;

    fn try_from(value: &NextAction) -> std::result::Result<Self, Self::Error> {
        Context::from_bytes(value.previous_output.as_ref())
    }
}

impl From<Context> for NextAction {
    fn from(ctx: Context) -> Self {
        let mut buf = Vec::<u8>::new();
        ctx.write_to(&mut buf).expect("cannot fail");
        NextAction {
            previous_output: buf.into(),
        }
    }
}

impl NextAction {
    /// Approve the result of the previous [Action] and store for lookup.
    pub fn store(self) -> Action {
        Action::Store(self.previous_output)
    }
    /// Reject the result of the previous [Action] and erase it as to not be returned when being looked up.
    pub fn erase(self) -> Action {
        Action::Erase(self.previous_output)
    }
}

mod cascade;
pub(crate) mod invoke;

pub use invoke::invoke;