Skip to content

Commit b96034a

Browse files
committed
feature: Add auth options to include
Adding support for authorization to connect external devrc files.
1 parent ed870c7 commit b96034a

File tree

19 files changed

+483
-132
lines changed

19 files changed

+483
-132
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Devrcfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,11 @@ format:
7171
desc: "Apply code formatter"
7272
exec: |
7373
cargo +nightly fmt --all --
74+
75+
76+
prepare-release level="patch":
77+
desc: "Prepare next release"
78+
run: |
79+
cargo release version {{ level }}
80+
cargo build
81+
devrc --help > xtests/outputs/test_help_test_1_1_stdout.ansitxt

cli/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ anyhow = "1.0.62"
5151

5252
devrc-plugins = { path = "../plugins", version = "0.5.4"}
5353
devrc-core = { path = "../core", version = "0.5.4"}
54+
netrc-rs = "0.1.2"
55+
base64 = "0.21.2"
5456

5557
[build-dependencies]
5658
datetime = { version = "0.5.2", default_features = false }

cli/src/auth.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use std::{convert::TryFrom, fs};
2+
3+
use netrc_rs::{Machine, Netrc};
4+
5+
use crate::{
6+
errors::DevrcError,
7+
netrc::get_user_defined_netrc_path,
8+
raw::auth::{NetrcAuth, NetrcAuthHeader},
9+
};
10+
11+
pub const HEADER_AUTHORIZATION: &str = "Authorization";
12+
13+
#[derive(Debug, Clone, Default)]
14+
pub enum AuthType {
15+
#[default]
16+
None,
17+
Bearer,
18+
BasicAuth,
19+
Header(String),
20+
}
21+
22+
#[derive(Debug, Clone, Default)]
23+
pub enum Auth {
24+
#[default]
25+
Empty,
26+
Loaded {
27+
machine: Machine,
28+
auth_type: AuthType,
29+
},
30+
}
31+
32+
impl TryFrom<crate::raw::auth::Auth> for Auth {
33+
type Error = DevrcError;
34+
35+
fn try_from(value: crate::raw::auth::Auth) -> Result<Self, Self::Error> {
36+
let file = get_user_defined_netrc_path().ok_or(DevrcError::NetrcNotFound)?;
37+
let content = fs::read_to_string(file).map_err(DevrcError::IoError)?;
38+
let netrc = Netrc::parse(content, false).map_err(DevrcError::NetrcParsingError)?;
39+
40+
match value {
41+
crate::raw::auth::Auth::Empty => Ok(Auth::Empty),
42+
crate::raw::auth::Auth::NetrcAuth(NetrcAuth {
43+
host,
44+
login,
45+
auth_type,
46+
}) => match get_machine(&netrc, &host, &login) {
47+
Some(machine) => Ok(Auth::Loaded {
48+
machine,
49+
auth_type: auth_type.into(),
50+
}),
51+
None => Ok(Auth::Empty),
52+
},
53+
crate::raw::auth::Auth::NetrcAuthHeader(NetrcAuthHeader {
54+
host,
55+
login,
56+
header,
57+
}) => match get_machine(&netrc, &host, &login) {
58+
Some(machine) => Ok(Auth::Loaded {
59+
machine,
60+
auth_type: AuthType::Header(header),
61+
}),
62+
None => Ok(Auth::Empty),
63+
},
64+
}
65+
}
66+
}
67+
68+
impl From<crate::raw::auth::AuthType> for AuthType {
69+
fn from(value: crate::raw::auth::AuthType) -> Self {
70+
match value {
71+
crate::raw::auth::AuthType::Empty => AuthType::None,
72+
crate::raw::auth::AuthType::Bearer => AuthType::Bearer,
73+
crate::raw::auth::AuthType::BasicAuth => AuthType::BasicAuth,
74+
}
75+
}
76+
}
77+
78+
fn get_machine(netrc: &Netrc, host: &str, login: &str) -> Option<Machine> {
79+
let mut default: Option<Machine> = None;
80+
81+
for machine in &netrc.machines {
82+
match (machine.name.as_ref(), machine.login.as_ref()) {
83+
(Some(record_host), Some(record_login))
84+
if record_host.as_str() == host && record_login.as_str() == login =>
85+
{
86+
return Some(machine.clone())
87+
}
88+
(None, Some(record_login)) if record_login.as_str() == login => {
89+
default = Some(machine.clone())
90+
}
91+
(_, _) => {}
92+
}
93+
}
94+
95+
default
96+
}
97+
98+
impl Auth {
99+
pub fn get_header(&self) -> Option<(String, String)> {
100+
if let Auth::Loaded { machine, auth_type } = self {
101+
let password = match &machine.password {
102+
Some(password) => password,
103+
None => return None,
104+
};
105+
let login = match &machine.login {
106+
Some(login) => login,
107+
None => return None,
108+
};
109+
110+
return match auth_type {
111+
AuthType::Bearer => Some((
112+
HEADER_AUTHORIZATION.to_string(),
113+
format!("Bearer {}", password),
114+
)),
115+
AuthType::BasicAuth => {
116+
let creds = format!("{:}:{:}", login, password);
117+
let b64 =
118+
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, creds);
119+
Some((HEADER_AUTHORIZATION.to_string(), format!("Basic {}", b64)))
120+
}
121+
AuthType::Header(header) => Some((header.to_owned(), password.to_owned())),
122+
AuthType::None => None,
123+
};
124+
}
125+
None
126+
}
127+
}

cli/src/devrcfile.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ use crate::{
44
config::Config,
55
environment::{Environment, RawEnvironment},
66
errors::{DevrcError, DevrcResult},
7-
raw::config::{DefaultOption, RawConfig},
8-
raw_devrcfile::{Kind, RawDevrcfile},
7+
raw::{
8+
config::{DefaultOption, RawConfig},
9+
devrcfile::{Kind, RawDevrcfile},
10+
},
911
scope::{child_scope, Scope},
1012
tasks::{
1113
arguments::{extract_task_args, TaskArguments},

cli/src/env_file.rs

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use std::{env, fs, path::PathBuf};
1+
use std::{convert::TryFrom, env, fs, path::PathBuf};
22

3+
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
34
use serde::Deserialize;
45
use sha256::digest;
56
use url::Url;
@@ -127,45 +128,67 @@ impl LocalFileImport {
127128

128129
match loading_location {
129130
Location::LocalFile(path) => fs::read_to_string(path).map_err(DevrcError::IoError),
130-
Location::Url(url) => match reqwest::blocking::get(url.as_str()) {
131-
Ok(response) if response.status() == 200 => {
132-
let content = response.text().map_err(|_| DevrcError::RuntimeError)?;
133-
134-
if let Some(control_checksum) = self.checksum.clone() {
135-
let content_checksum = digest(content.as_str());
136-
137-
if control_checksum != content_checksum {
138-
return Err(DevrcError::UrlImportChecksumError {
139-
url: url.as_str().to_string(),
140-
control_checksum,
141-
content_checksum,
142-
});
143-
}
144-
}
145-
146-
Ok(content)
147-
}
148-
Ok(response) => {
149-
config.log_level.debug(
150-
&format!(
151-
"Loadin ENV FILE error: invalid status code `{:}` ...",
152-
response.status()
153-
),
154-
&config.designer.banner(),
131+
Location::Remote { url, auth } => {
132+
let client = reqwest::blocking::Client::new();
133+
let mut headers_map: HeaderMap = HeaderMap::new();
134+
135+
if let Some((key, value)) = auth.get_header() {
136+
headers_map.insert(
137+
HeaderName::try_from(key.clone()).map_err(|_| {
138+
DevrcError::UrlImportHeadersError {
139+
name: key.clone(),
140+
value: value.clone(),
141+
}
142+
})?,
143+
HeaderValue::try_from(value.clone()).map_err(|_| {
144+
DevrcError::UrlImportHeadersError {
145+
name: key.clone(),
146+
value: value.clone(),
147+
}
148+
})?,
155149
);
156-
Err(DevrcError::EnvfileUrlImportStatusError {
157-
url: url.as_str().to_string(),
158-
status: response.status(),
159-
})
160150
}
161-
Err(error) => {
162-
config.log_level.debug(
163-
&format!("Error: `{:}` ...", &error),
164-
&config.designer.banner(),
165-
);
166-
Err(DevrcError::RuntimeError)
151+
152+
match client.get(url.as_str()).headers(headers_map).send() {
153+
Ok(response) if response.status() == 200 => {
154+
let content = response.text().map_err(|_| DevrcError::RuntimeError)?;
155+
156+
if let Some(control_checksum) = self.checksum.clone() {
157+
let content_checksum = digest(content.as_str());
158+
159+
if control_checksum != content_checksum {
160+
return Err(DevrcError::UrlImportChecksumError {
161+
url: url.as_str().to_string(),
162+
control_checksum,
163+
content_checksum,
164+
});
165+
}
166+
}
167+
168+
Ok(content)
169+
}
170+
Ok(response) => {
171+
config.log_level.debug(
172+
&format!(
173+
"Loadin ENV FILE error: invalid status code `{:}` ...",
174+
response.status()
175+
),
176+
&config.designer.banner(),
177+
);
178+
Err(DevrcError::EnvfileUrlImportStatusError {
179+
url: url.as_str().to_string(),
180+
status: response.status(),
181+
})
182+
}
183+
Err(error) => {
184+
config.log_level.debug(
185+
&format!("Error: `{:}` ...", &error),
186+
&config.designer.banner(),
187+
);
188+
Err(DevrcError::RuntimeError)
189+
}
167190
}
168-
},
191+
}
169192
_ => Ok("".to_string()),
170193
}
171194
}
@@ -195,7 +218,7 @@ impl LocalFileImport {
195218
Ok(Location::LocalFile(path))
196219
}
197220
},
198-
Location::Url(url) => match self.path_resolve {
221+
Location::Remote { url, auth } => match self.path_resolve {
199222
PathResolve::Relative => {
200223
let path = self
201224
.file
@@ -204,7 +227,10 @@ impl LocalFileImport {
204227
.into_string()
205228
.map_err(|_| DevrcError::RuntimeError)?;
206229
let include_url = url.join(&path).map_err(|_| DevrcError::RuntimeError)?;
207-
Ok(Location::Url(include_url))
230+
Ok(Location::Remote {
231+
url: include_url,
232+
auth,
233+
})
208234
}
209235
PathResolve::Pwd => {
210236
let path =

cli/src/errors.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ pub enum DevrcError {
5252
InvalidInterpreter,
5353
NestingLevelExceed,
5454
RuntimeError,
55-
5655
EnvfileImportError {
5756
location: Location,
5857
},
@@ -69,18 +68,24 @@ pub enum DevrcError {
6968
url: String,
7069
status: StatusCode,
7170
},
72-
UrlImportError {
71+
UrlImportRequestError {
7372
url: String,
7473
inner: reqwest::Error,
7574
},
76-
75+
UrlImportError,
76+
UrlImportHeadersError {
77+
name: String,
78+
value: String,
79+
},
7780
UrlImportChecksumError {
7881
url: String,
7982
control_checksum: String,
8083
content_checksum: String,
8184
},
8285
AnyhowError(anyhow::Error),
8386
HomeDirNotFound,
87+
NetrcNotFound,
88+
NetrcParsingError(netrc_rs::Error),
8489
}
8590

8691
impl Display for DevrcError {

cli/src/include.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
use crate::de::deserialize_some;
1+
use crate::{de::deserialize_some, raw::auth::Auth};
22
use serde::Deserialize;
33
use std::path::PathBuf;
44

55
use crate::resolver::PathResolve;
66

7+
pub(crate) fn get_default_skip_on_error() -> bool {
8+
false
9+
}
10+
711
#[derive(Debug, Deserialize, Clone)]
812
pub struct StringFileInclude(pub String);
913

@@ -23,6 +27,15 @@ pub struct UrlInclude {
2327
pub url: String,
2428

2529
pub checksum: String,
30+
31+
#[serde(default)]
32+
pub headers: indexmap::IndexMap<String, String>,
33+
34+
#[serde(default = "get_default_skip_on_error")]
35+
pub ignore_errors: bool,
36+
37+
#[serde(default)]
38+
pub auth: Auth,
2639
}
2740

2841
#[derive(Debug, Deserialize, Clone, Default)]

0 commit comments

Comments
 (0)