Skip to content

Commit 67c751b

Browse files
committed
CRED-2150: Add PAT auth support - templates and tests
1 parent f2e7ab9 commit 67c751b

File tree

3 files changed

+203
-13
lines changed

3 files changed

+203
-13
lines changed

.generator/src/generator/templates/api.j2

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -384,19 +384,14 @@ impl {{ structName }} {
384384
};
385385

386386
// build auth
387-
{%- set authMethods = operation.security if "security" in operation else openapi.security %}
388-
{%- if authMethods %}
389-
{%- for authMethod in authMethods %}
390-
{%- for name in authMethod %}
391-
{%- set schema = openapi.components.securitySchemes[name] %}
392-
{%- if schema.type == "apiKey" and schema.in != "cookie" %}
393-
if let Some(local_key) = local_configuration.auth_keys.get("{{ name }}") {
394-
headers.insert("{{schema.name}}", HeaderValue::from_str(local_key.key.as_str()).expect("failed to parse {{schema.name}} header"));
395-
};
396-
{%- endif %}
397-
{%- endfor %}
398-
{%- endfor %}
399-
{%- endif %}
387+
for (key, value) in local_configuration.auth_headers() {
388+
headers.insert(
389+
reqwest::header::HeaderName::from_bytes(key.as_bytes())
390+
.expect("failed to parse auth header name"),
391+
HeaderValue::from_str(value.as_str())
392+
.expect("failed to parse auth header value"),
393+
);
394+
}
400395

401396
{% if formParameter %}
402397
// build form parameters

.generator/src/generator/templates/configuration.j2

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub struct Configuration {
4444
pub(crate) user_agent: String,
4545
pub(crate) unstable_operations: HashMap<String, bool>,
4646
pub(crate) auth_keys: HashMap<String, APIKey>,
47+
{%- if "bearerAuth" in openapi.components.securitySchemes %}
48+
pub(crate) pat: Option<String>,
49+
{%- endif %}
4750
pub server_index: usize,
4851
pub server_variables: HashMap<String, String>,
4952
pub server_operation_index: HashMap<String, usize>,
@@ -106,6 +109,40 @@ impl Configuration {
106109
self.auth_keys.insert(operation_str.to_string(), api_key);
107110
}
108111

112+
{%- if "bearerAuth" in openapi.components.securitySchemes %}
113+
114+
/// Set a bearer token for authentication.
115+
pub fn set_pat(&mut self, pat: String) {
116+
self.pat = Some(pat);
117+
}
118+
119+
{%- endif %}
120+
121+
/// Build authentication headers for an API request.
122+
/// All configured auth credentials are sent; the server decides which to use.
123+
pub fn auth_headers(&self) -> Vec<(String, String)> {
124+
let mut headers = Vec::new();
125+
{%- if "bearerAuth" in openapi.components.securitySchemes %}
126+
if let Some(ref pat) = self.pat {
127+
headers.push(("Authorization".to_string(), format!("Bearer {}", pat)));
128+
}
129+
{%- endif %}
130+
{%- set authMethods = openapi.security %}
131+
{%- if authMethods %}
132+
{%- for authMethod in authMethods %}
133+
{%- for name in authMethod %}
134+
{%- set schema = openapi.components.securitySchemes[name] %}
135+
{%- if schema.type == "apiKey" and schema.in != "cookie" %}
136+
if let Some(key) = self.auth_keys.get("{{ name }}") {
137+
headers.push(("{{ schema.name }}".to_string(), key.key.clone()));
138+
}
139+
{%- endif %}
140+
{%- endfor %}
141+
{%- endfor %}
142+
{%- endif %}
143+
headers
144+
}
145+
109146
pub fn set_proxy_url(&mut self, proxy_url: Option<String>) {
110147
self.proxy_url = proxy_url;
111148
}
@@ -149,10 +186,20 @@ impl Default for Configuration {
149186
{%- endfor %}
150187
{%- endif %}
151188

189+
{%- if "bearerAuth" in openapi.components.securitySchemes %}
190+
{%- set bearerEnvName = openapi.components.securitySchemes.bearerAuth["x-env-name"] %}
191+
192+
// {{ bearerEnvName }} env var enables Bearer token auth (mutually exclusive with API key auth)
193+
let pat = env::var("{{ bearerEnvName }}").ok().filter(|p| !p.is_empty());
194+
{%- endif %}
195+
152196
Self {
153197
user_agent: DEFAULT_USER_AGENT.clone(),
154198
unstable_operations,
155199
auth_keys,
200+
{%- if "bearerAuth" in openapi.components.securitySchemes %}
201+
pat,
202+
{%- endif %}
156203
server_index: 0,
157204
server_variables: HashMap::from([(
158205
"site".into(),

tests/configuration_test.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
use datadog_api_client::datadog::{APIKey, Configuration};
5+
use std::env;
6+
use std::sync::Mutex;
7+
8+
// Mutex to prevent env var tests from interfering with each other
9+
static ENV_MUTEX: Mutex<()> = Mutex::new(());
10+
11+
#[test]
12+
fn test_set_pat_stores_pat() {
13+
let mut config = Configuration::new();
14+
config.set_pat("my-pat-token".to_string());
15+
16+
let headers = config.auth_headers();
17+
assert!(headers
18+
.iter()
19+
.any(|(k, v)| k == "Authorization" && v == "Bearer my-pat-token"));
20+
}
21+
22+
#[test]
23+
fn test_auth_headers_with_pat_returns_bearer() {
24+
let mut config = Configuration::new();
25+
config.set_pat("my-pat-token".to_string());
26+
27+
let headers = config.auth_headers();
28+
assert!(headers
29+
.iter()
30+
.any(|(k, v)| k == "Authorization" && v == "Bearer my-pat-token"));
31+
}
32+
33+
#[test]
34+
fn test_auth_headers_with_pat_and_api_keys_sends_all() {
35+
let mut config = Configuration::new();
36+
config.set_auth_key(
37+
"apiKeyAuth",
38+
APIKey {
39+
key: "my-api-key".to_string(),
40+
prefix: "".to_string(),
41+
},
42+
);
43+
config.set_auth_key(
44+
"appKeyAuth",
45+
APIKey {
46+
key: "my-app-key".to_string(),
47+
prefix: "".to_string(),
48+
},
49+
);
50+
config.set_pat("my-pat-token".to_string());
51+
52+
let headers = config.auth_headers();
53+
assert!(headers
54+
.iter()
55+
.any(|(k, v)| k == "Authorization" && v == "Bearer my-pat-token"));
56+
assert!(headers
57+
.iter()
58+
.any(|(k, v)| k == "DD-API-KEY" && v == "my-api-key"));
59+
assert!(headers
60+
.iter()
61+
.any(|(k, v)| k == "DD-APPLICATION-KEY" && v == "my-app-key"));
62+
}
63+
64+
#[test]
65+
fn test_auth_headers_without_pat_returns_api_keys() {
66+
let _lock = ENV_MUTEX.lock().unwrap();
67+
let old_pat = env::var("DD_BEARER_TOKEN").ok();
68+
env::remove_var("DD_BEARER_TOKEN");
69+
70+
let mut config = Configuration::new();
71+
config.set_auth_key(
72+
"apiKeyAuth",
73+
APIKey {
74+
key: "my-api-key".to_string(),
75+
prefix: "".to_string(),
76+
},
77+
);
78+
config.set_auth_key(
79+
"appKeyAuth",
80+
APIKey {
81+
key: "my-app-key".to_string(),
82+
prefix: "".to_string(),
83+
},
84+
);
85+
86+
let headers = config.auth_headers();
87+
assert!(headers
88+
.iter()
89+
.any(|(k, v)| k == "DD-API-KEY" && v == "my-api-key"));
90+
assert!(headers
91+
.iter()
92+
.any(|(k, v)| k == "DD-APPLICATION-KEY" && v == "my-app-key"));
93+
// No Authorization header should be present
94+
assert!(!headers.iter().any(|(k, _)| k == "Authorization"));
95+
96+
match old_pat {
97+
Some(v) => env::set_var("DD_BEARER_TOKEN", v),
98+
None => env::remove_var("DD_BEARER_TOKEN"),
99+
}
100+
}
101+
102+
#[test]
103+
fn test_dd_bearer_token_env_var() {
104+
let _lock = ENV_MUTEX.lock().unwrap();
105+
let old_pat = env::var("DD_BEARER_TOKEN").ok();
106+
107+
env::set_var("DD_BEARER_TOKEN", "env-pat-token");
108+
109+
let config = Configuration::default();
110+
111+
let headers = config.auth_headers();
112+
assert!(headers
113+
.iter()
114+
.any(|(k, v)| k == "Authorization" && v == "Bearer env-pat-token"));
115+
116+
// Restore env
117+
match old_pat {
118+
Some(v) => env::set_var("DD_BEARER_TOKEN", v),
119+
None => env::remove_var("DD_BEARER_TOKEN"),
120+
}
121+
}
122+
123+
#[test]
124+
fn test_empty_dd_pat_does_not_set_pat() {
125+
let _lock = ENV_MUTEX.lock().unwrap();
126+
let old_pat = env::var("DD_BEARER_TOKEN").ok();
127+
let old_app_key = env::var("DD_APP_KEY").ok();
128+
129+
env::set_var("DD_BEARER_TOKEN", "");
130+
env::set_var("DD_APP_KEY", "my-app-key");
131+
132+
let config = Configuration::default();
133+
134+
let headers = config.auth_headers();
135+
assert!(headers
136+
.iter()
137+
.any(|(k, v)| k == "DD-APPLICATION-KEY" && v == "my-app-key"));
138+
139+
// Restore env
140+
match old_pat {
141+
Some(v) => env::set_var("DD_BEARER_TOKEN", v),
142+
None => env::remove_var("DD_BEARER_TOKEN"),
143+
}
144+
match old_app_key {
145+
Some(v) => env::set_var("DD_APP_KEY", v),
146+
None => env::remove_var("DD_APP_KEY"),
147+
}
148+
}

0 commit comments

Comments
 (0)