Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,11 +697,11 @@ mod tests {
/// Same coverage for the no-auth variant. Asserts the UA is overridden
/// AND that no `Authorization` header leaks through, even when a bearer
/// token exists in the config — that's the contract of `make_api_no_auth!`.
/// Uses `ApplicationSecurityAPI` (ASM WAF custom rules), which is still a
/// genuinely no-auth production call site as of this test.
#[tokio::test]
async fn test_make_api_no_auth_sends_pup_user_agent() {
use datadog_api_client::datadogV2::api_authn_mappings::{
AuthNMappingsAPI, ListAuthNMappingsOptionalParams,
};
use datadog_api_client::datadogV2::api_application_security::ApplicationSecurityAPI;
let _lock = lock_env().await;
let mut server = mockito::Server::new_async().await;
let mock = server
Expand All @@ -719,10 +719,8 @@ mod tests {
// Set a token so the Authorization-absent assertion meaningfully
// exercises that `make_api_no_auth!` actively suppresses bearer.
cfg.access_token = Some("test-bearer-token".into());
let api: AuthNMappingsAPI = crate::make_api_no_auth!(AuthNMappingsAPI, &cfg);
let resp = api
.list_authn_mappings(ListAuthNMappingsOptionalParams::default())
.await;
let api: ApplicationSecurityAPI = crate::make_api_no_auth!(ApplicationSecurityAPI, &cfg);
let resp = api.list_application_security_waf_custom_rules().await;
assert!(
resp.is_ok(),
"make_api_no_auth! request leaked Authorization or wrong UA: {:?}",
Expand Down
42 changes: 37 additions & 5 deletions src/commands/authn_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::formatter;
use crate::util;

pub async fn list(cfg: &Config) -> Result<()> {
let api = crate::make_api_no_auth!(AuthNMappingsAPI, cfg);
let api = crate::make_api!(AuthNMappingsAPI, cfg);
Comment thread
platinummonkey marked this conversation as resolved.
Comment thread
platinummonkey marked this conversation as resolved.
let resp = api
.list_authn_mappings(ListAuthNMappingsOptionalParams::default())
.await
Expand All @@ -18,7 +18,7 @@ pub async fn list(cfg: &Config) -> Result<()> {
}

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

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

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

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

#[tokio::test]
async fn test_authn_mappings_list_accepts_oauth_bearer_token() {
let _lock = lock_env().await;
std::env::set_var("DD_TOKEN_STORAGE", "file");
let mut server = mockito::Server::new_async().await;
let mut cfg = test_config(&server.url());
// Simulate OAuth-only auth: bearer token configured, no API/APP keys.
cfg.api_key = None;
cfg.app_key = None;
cfg.access_token = Some("oauth-bearer-token".into());
std::env::remove_var("DD_API_KEY");
std::env::remove_var("DD_APP_KEY");

let _mock = server
.mock("GET", mockito::Matcher::Any)
.match_header("Authorization", "Bearer oauth-bearer-token")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"data":[]}"#)
.create_async()
.await;

let result = super::list(&cfg).await;
assert!(
result.is_ok(),
"authn mappings list with OAuth bearer failed: {:?}",
result.err()
);
cleanup_env();
std::env::remove_var("DD_TOKEN_STORAGE");
}

#[tokio::test]
async fn test_authn_mappings_list_error() {
let _lock = lock_env().await;
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,10 @@ enum Commands {
///
/// AUTHENTICATION:
/// Requires either OAuth2 authentication or API keys.
/// list/get work with default OAuth scopes. create/update/delete
/// require the user_access_manage scope, which is not requested by
/// default -- opt in with:
/// pup auth login --extra-scopes user_access_manage
#[command(name = "authn-mappings", verbatim_doc_comment)]
AuthnMappings {
#[command(subcommand)]
Expand Down