Skip to content

Commit 25f983f

Browse files
Merge pull request #692 from srosenthal-dd/stephen.rosenthal/authn-mappings-oauth
authn-mappings: send OAuth bearer token, document user_access_manage scope | DAL-546
2 parents a34a52d + 00f7a4c commit 25f983f

3 files changed

Lines changed: 46 additions & 12 deletions

File tree

src/client.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -697,11 +697,11 @@ mod tests {
697697
/// Same coverage for the no-auth variant. Asserts the UA is overridden
698698
/// AND that no `Authorization` header leaks through, even when a bearer
699699
/// token exists in the config — that's the contract of `make_api_no_auth!`.
700+
/// Uses `ApplicationSecurityAPI` (ASM WAF custom rules), which is still a
701+
/// genuinely no-auth production call site as of this test.
700702
#[tokio::test]
701703
async fn test_make_api_no_auth_sends_pup_user_agent() {
702-
use datadog_api_client::datadogV2::api_authn_mappings::{
703-
AuthNMappingsAPI, ListAuthNMappingsOptionalParams,
704-
};
704+
use datadog_api_client::datadogV2::api_application_security::ApplicationSecurityAPI;
705705
let _lock = lock_env().await;
706706
let mut server = mockito::Server::new_async().await;
707707
let mock = server
@@ -719,10 +719,8 @@ mod tests {
719719
// Set a token so the Authorization-absent assertion meaningfully
720720
// exercises that `make_api_no_auth!` actively suppresses bearer.
721721
cfg.access_token = Some("test-bearer-token".into());
722-
let api: AuthNMappingsAPI = crate::make_api_no_auth!(AuthNMappingsAPI, &cfg);
723-
let resp = api
724-
.list_authn_mappings(ListAuthNMappingsOptionalParams::default())
725-
.await;
722+
let api: ApplicationSecurityAPI = crate::make_api_no_auth!(ApplicationSecurityAPI, &cfg);
723+
let resp = api.list_application_security_waf_custom_rules().await;
726724
assert!(
727725
resp.is_ok(),
728726
"make_api_no_auth! request leaked Authorization or wrong UA: {:?}",

src/commands/authn_mappings.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::formatter;
99
use crate::util;
1010

1111
pub async fn list(cfg: &Config) -> Result<()> {
12-
let api = crate::make_api_no_auth!(AuthNMappingsAPI, cfg);
12+
let api = crate::make_api!(AuthNMappingsAPI, cfg);
1313
let resp = api
1414
.list_authn_mappings(ListAuthNMappingsOptionalParams::default())
1515
.await
@@ -18,7 +18,7 @@ pub async fn list(cfg: &Config) -> Result<()> {
1818
}
1919

2020
pub async fn get(cfg: &Config, mapping_id: &str) -> Result<()> {
21-
let api = crate::make_api_no_auth!(AuthNMappingsAPI, cfg);
21+
let api = crate::make_api!(AuthNMappingsAPI, cfg);
2222
let resp = api
2323
.get_authn_mapping(mapping_id.to_string())
2424
.await
@@ -28,7 +28,7 @@ pub async fn get(cfg: &Config, mapping_id: &str) -> Result<()> {
2828

2929
pub async fn create(cfg: &Config, file: &str) -> Result<()> {
3030
let body: AuthNMappingCreateRequest = util::read_json_file(file)?;
31-
let api = crate::make_api_no_auth!(AuthNMappingsAPI, cfg);
31+
let api = crate::make_api!(AuthNMappingsAPI, cfg);
3232
let resp = api
3333
.create_authn_mapping(body)
3434
.await
@@ -38,7 +38,7 @@ pub async fn create(cfg: &Config, file: &str) -> Result<()> {
3838

3939
pub async fn update(cfg: &Config, mapping_id: &str, file: &str) -> Result<()> {
4040
let body: AuthNMappingUpdateRequest = util::read_json_file(file)?;
41-
let api = crate::make_api_no_auth!(AuthNMappingsAPI, cfg);
41+
let api = crate::make_api!(AuthNMappingsAPI, cfg);
4242
let resp = api
4343
.update_authn_mapping(mapping_id.to_string(), body)
4444
.await
@@ -47,7 +47,7 @@ pub async fn update(cfg: &Config, mapping_id: &str, file: &str) -> Result<()> {
4747
}
4848

4949
pub async fn delete(cfg: &Config, mapping_id: &str) -> Result<()> {
50-
let api = crate::make_api_no_auth!(AuthNMappingsAPI, cfg);
50+
let api = crate::make_api!(AuthNMappingsAPI, cfg);
5151
api.delete_authn_mapping(mapping_id.to_string())
5252
.await
5353
.map_err(|e| anyhow::anyhow!("failed to delete AuthN mapping: {e:?}"))?;
@@ -115,6 +115,38 @@ mod tests {
115115
std::env::remove_var("DD_TOKEN_STORAGE");
116116
}
117117

118+
#[tokio::test]
119+
async fn test_authn_mappings_list_accepts_oauth_bearer_token() {
120+
let _lock = lock_env().await;
121+
std::env::set_var("DD_TOKEN_STORAGE", "file");
122+
let mut server = mockito::Server::new_async().await;
123+
let mut cfg = test_config(&server.url());
124+
// Simulate OAuth-only auth: bearer token configured, no API/APP keys.
125+
cfg.api_key = None;
126+
cfg.app_key = None;
127+
cfg.access_token = Some("oauth-bearer-token".into());
128+
std::env::remove_var("DD_API_KEY");
129+
std::env::remove_var("DD_APP_KEY");
130+
131+
let _mock = server
132+
.mock("GET", mockito::Matcher::Any)
133+
.match_header("Authorization", "Bearer oauth-bearer-token")
134+
.with_status(200)
135+
.with_header("content-type", "application/json")
136+
.with_body(r#"{"data":[]}"#)
137+
.create_async()
138+
.await;
139+
140+
let result = super::list(&cfg).await;
141+
assert!(
142+
result.is_ok(),
143+
"authn mappings list with OAuth bearer failed: {:?}",
144+
result.err()
145+
);
146+
cleanup_env();
147+
std::env::remove_var("DD_TOKEN_STORAGE");
148+
}
149+
118150
#[tokio::test]
119151
async fn test_authn_mappings_list_error() {
120152
let _lock = lock_env().await;

src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,10 @@ enum Commands {
617617
///
618618
/// AUTHENTICATION:
619619
/// Requires either OAuth2 authentication or API keys.
620+
/// list/get work with default OAuth scopes. create/update/delete
621+
/// require the user_access_manage scope, which is not requested by
622+
/// default -- opt in with:
623+
/// pup auth login --extra-scopes user_access_manage
620624
#[command(name = "authn-mappings", verbatim_doc_comment)]
621625
AuthnMappings {
622626
#[command(subcommand)]

0 commit comments

Comments
 (0)