From c9d8930864946362bed8702d21895aadee140de2 Mon Sep 17 00:00:00 2001 From: Tausif Rahman Date: Thu, 5 Mar 2026 17:38:07 +0000 Subject: [PATCH 1/2] CRED-2151: Add PAT auth support to Ruby API client - templates and tests --- .../src/generator/templates/api_client.j2 | 3 +- .../src/generator/templates/configuration.j2 | 13 ++-- spec/api_client_spec.rb | 71 +++++++++++++++++++ spec/configuration_spec.rb | 40 +++++++++++ 4 files changed, 119 insertions(+), 8 deletions(-) diff --git a/.generator/src/generator/templates/api_client.j2 b/.generator/src/generator/templates/api_client.j2 index 61d6ee4152fb..cfec795e44fa 100644 --- a/.generator/src/generator/templates/api_client.j2 +++ b/.generator/src/generator/templates/api_client.j2 @@ -143,7 +143,7 @@ module {{ module_name }} #Redact api and app key in the request header def sanitize_request_header(request_header) sanitized_headers= request_header.dup - keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY"] + keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY", "Authorization"] keys_to_redact.each do |key_to_redact| if sanitized_headers.key?(key_to_redact) sanitized_headers[key_to_redact] = "REDACTED" @@ -369,6 +369,7 @@ module {{ module_name }} Array(auth_names).each do |auth_name| auth_setting = @config.auth_settings[auth_name] next unless auth_setting + next if auth_setting[:value].nil? || auth_setting[:value].to_s.empty? case auth_setting[:in] when 'header' then header_params[auth_setting[:key]] = auth_setting[:value] when 'query' then query_params[auth_setting[:key]] = auth_setting[:value] diff --git a/.generator/src/generator/templates/configuration.j2 b/.generator/src/generator/templates/configuration.j2 index 6bfa8b2828b4..7672f13170f8 100644 --- a/.generator/src/generator/templates/configuration.j2 +++ b/.generator/src/generator/templates/configuration.j2 @@ -193,6 +193,9 @@ module {{ module_name }} {%- for name, schema in openapi.components.securitySchemes.items() if "x-env-name" in schema and schema.in == "header" and schema.type == "apiKey" %} @api_key['{{ name }}'] = ENV['{{ schema["x-env-name"] }}'] if ENV.key? '{{ schema["x-env-name"] }}' {%- endfor %} + {%- for name, schema in openapi.components.securitySchemes.items() if "x-env-name" in schema and schema.type == "http" and schema.scheme == "bearer" %} + @access_token = ENV['{{ schema["x-env-name"] }}'] if ENV.key? '{{ schema["x-env-name"] }}' + {%- endfor %} yield(self) if block_given? end @@ -284,18 +287,14 @@ module {{ module_name }} key: 'Authorization', value: basic_auth_token }, -{# {%- elif schema.type == "http" and schema.scheme == "bearer" %} +{%- elif schema.type == "http" and schema.scheme == "bearer" %} {{name}}: { - type: 'bearer', + type: 'http', in: 'header', - {% if schema.bearerFormat %} - format: '{{ schema.bearerFormat }}', - {% endif %} key: 'Authorization', - value: "Bearer #{access_token}" + value: access_token ? "Bearer #{access_token}" : nil }, -#} {%- elif schema.type == "oauth2" %} {{name}}: { diff --git a/spec/api_client_spec.rb b/spec/api_client_spec.rb index 2231a991b3c8..35dadd5e3763 100644 --- a/spec/api_client_spec.rb +++ b/spec/api_client_spec.rb @@ -206,4 +206,75 @@ expect(api_client.sanitize_filename('.\sun.gif')).to eq('sun.gif') end end + + describe '#update_params_for_auth!' do + let(:config) { DatadogAPIClient::Configuration.new } + let(:api_client) { DatadogAPIClient::APIClient.new(config) } + + context 'when all auth credentials are configured' do + before do + config.api_key = 'test_api_key' + config.application_key = 'test_app_key' + config.access_token = 'ddpat_test_pat' + end + + it 'sends all configured auth headers simultaneously' do + header_params = {} + query_params = {} + api_client.update_params_for_auth!(header_params, query_params, [:apiKeyAuth, :appKeyAuth, :bearerAuth]) + expect(header_params['Authorization']).to eq('Bearer ddpat_test_pat') + expect(header_params['DD-API-KEY']).to eq('test_api_key') + expect(header_params['DD-APPLICATION-KEY']).to eq('test_app_key') + end + end + + context 'when only bearer token is configured' do + before do + config.access_token = 'ddpat_test_pat' + end + + it 'sends only Bearer header, skips empty API key and app key' do + header_params = {} + query_params = {} + api_client.update_params_for_auth!(header_params, query_params, [:apiKeyAuth, :appKeyAuth, :bearerAuth]) + expect(header_params['Authorization']).to eq('Bearer ddpat_test_pat') + expect(header_params).not_to have_key('DD-API-KEY') + expect(header_params).not_to have_key('DD-APPLICATION-KEY') + end + end + + context 'when only API key and app key are configured' do + before do + config.api_key = 'test_api_key' + config.application_key = 'test_app_key' + end + + it 'sends API key and app key, no Bearer header' do + header_params = {} + query_params = {} + api_client.update_params_for_auth!(header_params, query_params, [:apiKeyAuth, :appKeyAuth, :bearerAuth]) + expect(header_params['DD-API-KEY']).to eq('test_api_key') + expect(header_params['DD-APPLICATION-KEY']).to eq('test_app_key') + expect(header_params).not_to have_key('Authorization') + end + end + end + + describe '#sanitize_request_header' do + let(:api_client) { DatadogAPIClient::APIClient.new } + + it 'redacts sensitive headers including Authorization' do + headers = { + 'DD-API-KEY' => 'secret_api_key', + 'DD-APPLICATION-KEY' => 'secret_app_key', + 'Authorization' => 'Bearer ddapp_secret_pat', + 'Content-Type' => 'application/json' + } + sanitized = api_client.sanitize_request_header(headers) + expect(sanitized['DD-API-KEY']).to eq('REDACTED') + expect(sanitized['DD-APPLICATION-KEY']).to eq('REDACTED') + expect(sanitized['Authorization']).to eq('REDACTED') + expect(sanitized['Content-Type']).to eq('application/json') + end + end end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 60a4d9df7f46..73858b6c550d 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -58,5 +58,45 @@ end end + describe '#access_token (bearer auth)' do + let(:config) { DatadogAPIClient::Configuration.new } + + it 'defaults to nil' do + expect(config.access_token).to be_nil + end + + it 'can be set directly' do + config.access_token = 'ddpat_test_token_123' + expect(config.access_token).to eq('ddpat_test_token_123') + end + + it 'loads from DD_BEARER_TOKEN environment variable' do + allow(ENV).to receive(:key?).and_call_original + allow(ENV).to receive(:key?).with('DD_BEARER_TOKEN').and_return(true) + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]).with('DD_BEARER_TOKEN').and_return('ddpat_env_token') + new_config = DatadogAPIClient::Configuration.new + expect(new_config.access_token).to eq('ddpat_env_token') + end + end + + describe '#auth_settings' do + let(:config) { DatadogAPIClient::Configuration.new } + + it 'includes bearerAuth with nil value when token not set' do + expect(config.auth_settings[:bearerAuth]).not_to be_nil + expect(config.auth_settings[:bearerAuth][:value]).to be_nil + end + + it 'includes bearerAuth with Bearer token when access_token is set' do + config.access_token = 'ddpat_my_token' + auth = config.auth_settings[:bearerAuth] + expect(auth[:type]).to eq('http') + expect(auth[:in]).to eq('header') + expect(auth[:key]).to eq('Authorization') + expect(auth[:value]).to eq('Bearer ddpat_my_token') + end + end + end From 1234f979485fddd878d879e41e54f31fd2792e4f Mon Sep 17 00:00:00 2001 From: Tausif Rahman Date: Thu, 5 Mar 2026 17:38:17 +0000 Subject: [PATCH 2/2] CRED-2151: Regenerated client code from templates --- lib/datadog_api_client/api_client.rb | 3 ++- lib/datadog_api_client/configuration.rb | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/datadog_api_client/api_client.rb b/lib/datadog_api_client/api_client.rb index 86b4f6dad628..9ef0d203f0e1 100644 --- a/lib/datadog_api_client/api_client.rb +++ b/lib/datadog_api_client/api_client.rb @@ -154,7 +154,7 @@ def calculate_retry_interval(response, backoff_base, backoff_multiplier, attempt #Redact api and app key in the request header def sanitize_request_header(request_header) sanitized_headers= request_header.dup - keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY"] + keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY", "Authorization"] keys_to_redact.each do |key_to_redact| if sanitized_headers.key?(key_to_redact) sanitized_headers[key_to_redact] = "REDACTED" @@ -380,6 +380,7 @@ def update_params_for_auth!(header_params, query_params, auth_names) Array(auth_names).each do |auth_name| auth_setting = @config.auth_settings[auth_name] next unless auth_setting + next if auth_setting[:value].nil? || auth_setting[:value].to_s.empty? case auth_setting[:in] when 'header' then header_params[auth_setting[:key]] = auth_setting[:value] when 'query' then query_params[auth_setting[:key]] = auth_setting[:value] diff --git a/lib/datadog_api_client/configuration.rb b/lib/datadog_api_client/configuration.rb index c505a45a5b17..42356a844060 100644 --- a/lib/datadog_api_client/configuration.rb +++ b/lib/datadog_api_client/configuration.rb @@ -408,6 +408,7 @@ def initialize @server_variables[:site] = ENV['DD_SITE'] if ENV.key? 'DD_SITE' @api_key['apiKeyAuth'] = ENV['DD_API_KEY'] if ENV.key? 'DD_API_KEY' @api_key['appKeyAuth'] = ENV['DD_APP_KEY'] if ENV.key? 'DD_APP_KEY' + @access_token = ENV['DD_BEARER_TOKEN'] if ENV.key? 'DD_BEARER_TOKEN' yield(self) if block_given? end @@ -503,6 +504,13 @@ def auth_settings key: 'DD-APPLICATION-KEY', value: api_key_with_prefix('appKeyAuth') }, + bearerAuth: + { + type: 'http', + in: 'header', + key: 'Authorization', + value: access_token ? "Bearer #{access_token}" : nil + }, } end