rskit_git/auth/
provider.rs1use std::sync::Arc;
10
11use rskit_errors::AppResult;
12use rskit_util::{SecretString, env};
13
14use super::{SigningConfig, TransportAuth};
15
16pub const DEFAULT_TOKEN_USERNAME: &str = "x-access-token";
21
22pub trait AuthProvider: Send + Sync {
24 fn transport_auth(&self, remote: Option<&str>) -> AppResult<Option<TransportAuth>>;
29
30 fn signing_config(&self) -> AppResult<Option<SigningConfig>> {
35 Ok(None)
36 }
37}
38
39impl AuthProvider for Arc<dyn AuthProvider> {
40 fn transport_auth(&self, remote: Option<&str>) -> AppResult<Option<TransportAuth>> {
41 (**self).transport_auth(remote)
42 }
43
44 fn signing_config(&self) -> AppResult<Option<SigningConfig>> {
45 (**self).signing_config()
46 }
47}
48
49#[derive(Debug, Clone, Copy, Default)]
54pub struct DefaultAuthProvider;
55
56impl AuthProvider for DefaultAuthProvider {
57 fn transport_auth(&self, _remote: Option<&str>) -> AppResult<Option<TransportAuth>> {
58 Ok(None)
59 }
60}
61
62#[derive(Debug, Clone)]
67pub struct StaticAuthProvider {
68 auth: TransportAuth,
69}
70
71impl StaticAuthProvider {
72 #[must_use]
74 pub fn new(auth: TransportAuth) -> Self {
75 Self { auth }
76 }
77}
78
79impl AuthProvider for StaticAuthProvider {
80 fn transport_auth(&self, _remote: Option<&str>) -> AppResult<Option<TransportAuth>> {
81 Ok(Some(self.auth.clone()))
82 }
83}
84
85#[derive(Debug, Clone)]
93pub struct EnvTokenAuthProvider {
94 vars: Vec<String>,
95 username: String,
96}
97
98impl EnvTokenAuthProvider {
99 #[must_use]
101 pub fn with_var(name: impl Into<String>) -> Self {
102 Self {
103 vars: vec![name.into()],
104 username: DEFAULT_TOKEN_USERNAME.to_string(),
105 }
106 }
107
108 #[must_use]
110 pub fn with_vars<I, S>(names: I) -> Self
111 where
112 I: IntoIterator<Item = S>,
113 S: Into<String>,
114 {
115 Self {
116 vars: names.into_iter().map(Into::into).collect(),
117 username: DEFAULT_TOKEN_USERNAME.to_string(),
118 }
119 }
120
121 #[must_use]
123 pub fn with_username(mut self, username: impl Into<String>) -> Self {
124 self.username = username.into();
125 self
126 }
127}
128
129impl AuthProvider for EnvTokenAuthProvider {
130 fn transport_auth(&self, _remote: Option<&str>) -> AppResult<Option<TransportAuth>> {
131 Ok(self
132 .vars
133 .iter()
134 .find_map(|var| env::get_non_empty(var))
135 .map(|token| TransportAuth::Token {
136 username: Some(self.username.clone()),
137 token: SecretString::new(token),
138 }))
139 }
140}
141
142#[derive(Clone, Default)]
145pub struct ChainAuthProvider {
146 providers: Vec<Arc<dyn AuthProvider>>,
147}
148
149impl ChainAuthProvider {
150 #[must_use]
152 pub fn new(providers: Vec<Arc<dyn AuthProvider>>) -> Self {
153 Self { providers }
154 }
155
156 #[must_use]
158 pub fn with(mut self, provider: Arc<dyn AuthProvider>) -> Self {
159 self.providers.push(provider);
160 self
161 }
162}
163
164impl std::fmt::Debug for ChainAuthProvider {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 f.debug_struct("ChainAuthProvider")
167 .field("providers", &self.providers.len())
168 .finish()
169 }
170}
171
172impl AuthProvider for ChainAuthProvider {
173 fn transport_auth(&self, remote: Option<&str>) -> AppResult<Option<TransportAuth>> {
174 for provider in &self.providers {
175 if let Some(auth) = provider.transport_auth(remote)? {
176 return Ok(Some(auth));
177 }
178 }
179 Ok(None)
180 }
181
182 fn signing_config(&self) -> AppResult<Option<SigningConfig>> {
183 for provider in &self.providers {
184 if let Some(config) = provider.signing_config()? {
185 return Ok(Some(config));
186 }
187 }
188 Ok(None)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 const PRESENT_VAR: &str = "CARGO_PKG_NAME";
199 const PRESENT_VALUE: &str = "rskit-git";
200 const ABSENT_VAR: &str = "RSKIT_GIT_AUTH_TEST_ABSENT_VAR_9F3A";
201
202 #[test]
203 fn default_provider_offers_nothing() {
204 let provider = DefaultAuthProvider;
205 assert_eq!(provider.transport_auth(None).expect("resolve"), None);
206 assert!(provider.signing_config().expect("resolve").is_none());
207 }
208
209 #[test]
210 fn static_provider_returns_fixed_transport() {
211 let provider = StaticAuthProvider::new(TransportAuth::SshAgent {
212 username: "git".to_string(),
213 });
214 assert_eq!(
215 provider.transport_auth(Some("origin")).expect("resolve"),
216 Some(TransportAuth::SshAgent {
217 username: "git".to_string(),
218 })
219 );
220 }
221
222 #[test]
223 fn env_token_provider_reads_present_variable() {
224 let provider = EnvTokenAuthProvider::with_vars([ABSENT_VAR, PRESENT_VAR]);
225 let auth = provider.transport_auth(None).expect("resolve");
226 assert_eq!(
227 auth,
228 Some(TransportAuth::Token {
229 username: Some(DEFAULT_TOKEN_USERNAME.to_string()),
230 token: SecretString::new(PRESENT_VALUE),
231 })
232 );
233 }
234
235 #[test]
236 fn env_token_provider_honors_username_override() {
237 let provider = EnvTokenAuthProvider::with_var(PRESENT_VAR).with_username("token-user");
238 let auth = provider.transport_auth(None).expect("resolve");
239 assert_eq!(
240 auth,
241 Some(TransportAuth::Token {
242 username: Some("token-user".to_string()),
243 token: SecretString::new(PRESENT_VALUE),
244 })
245 );
246 }
247
248 #[test]
249 fn env_token_provider_absent_variable_is_none() {
250 let provider = EnvTokenAuthProvider::with_var(ABSENT_VAR);
251 assert_eq!(provider.transport_auth(None).expect("resolve"), None);
252 }
253
254 #[test]
255 fn chain_returns_first_some() {
256 let chain = ChainAuthProvider::new(vec![
257 Arc::new(EnvTokenAuthProvider::with_var(ABSENT_VAR)),
258 Arc::new(EnvTokenAuthProvider::with_var(PRESENT_VAR)),
259 Arc::new(StaticAuthProvider::new(TransportAuth::SshAgent {
260 username: "unused".to_string(),
261 })),
262 ]);
263 let auth = chain.transport_auth(None).expect("resolve");
264 assert_eq!(
265 auth,
266 Some(TransportAuth::Token {
267 username: Some(DEFAULT_TOKEN_USERNAME.to_string()),
268 token: SecretString::new(PRESENT_VALUE),
269 })
270 );
271 }
272
273 #[test]
274 fn chain_falls_through_to_none() {
275 let chain = ChainAuthProvider::new(vec![
276 Arc::new(EnvTokenAuthProvider::with_var(ABSENT_VAR)),
277 Arc::new(DefaultAuthProvider),
278 ]);
279 assert_eq!(chain.transport_auth(None).expect("resolve"), None);
280 assert!(chain.signing_config().expect("resolve").is_none());
281 }
282}