Skip to content

Commit 027cc15

Browse files
committed
CRED-2151: Add PAT auth support to Ruby API client
1 parent 3f580d9 commit 027cc15

File tree

6 files changed

+141
-11
lines changed

6 files changed

+141
-11
lines changed

.generator/src/generator/templates/api_client.j2

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ module {{ module_name }}
143143
#Redact api and app key in the request header
144144
def sanitize_request_header(request_header)
145145
sanitized_headers= request_header.dup
146-
keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY"]
146+
keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY", "Authorization"]
147147
keys_to_redact.each do |key_to_redact|
148148
if sanitized_headers.key?(key_to_redact)
149149
sanitized_headers[key_to_redact] = "REDACTED"
@@ -366,9 +366,18 @@ module {{ module_name }}
366366
# @param [Hash] query_params Query parameters
367367
# @param [String] auth_names Authentication scheme name
368368
def update_params_for_auth!(header_params, query_params, auth_names)
369-
Array(auth_names).each do |auth_name|
369+
auth_names = Array(auth_names)
370+
371+
# When a bearer token (PAT) is configured, use Bearer auth exclusively
372+
# instead of API key + App key.
373+
if @config.access_token
374+
auth_names = [:bearerAuth]
375+
end
376+
377+
auth_names.each do |auth_name|
370378
auth_setting = @config.auth_settings[auth_name]
371379
next unless auth_setting
380+
next if auth_setting[:value].nil? || auth_setting[:value].to_s.empty?
372381
case auth_setting[:in]
373382
when 'header' then header_params[auth_setting[:key]] = auth_setting[:value]
374383
when 'query' then query_params[auth_setting[:key]] = auth_setting[:value]

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ module {{ module_name }}
193193
{%- for name, schema in openapi.components.securitySchemes.items() if "x-env-name" in schema and schema.in == "header" and schema.type == "apiKey" %}
194194
@api_key['{{ name }}'] = ENV['{{ schema["x-env-name"] }}'] if ENV.key? '{{ schema["x-env-name"] }}'
195195
{%- endfor %}
196+
{%- for name, schema in openapi.components.securitySchemes.items() if "x-env-name" in schema and schema.type == "http" and schema.scheme == "bearer" %}
197+
@access_token = ENV['{{ schema["x-env-name"] }}'] if ENV.key? '{{ schema["x-env-name"] }}'
198+
{%- endfor %}
196199

197200
yield(self) if block_given?
198201
end
@@ -284,18 +287,14 @@ module {{ module_name }}
284287
key: 'Authorization',
285288
value: basic_auth_token
286289
},
287-
{# {%- elif schema.type == "http" and schema.scheme == "bearer" %}
290+
{%- elif schema.type == "http" and schema.scheme == "bearer" %}
288291
{{name}}:
289292
{
290-
type: 'bearer',
293+
type: 'http',
291294
in: 'header',
292-
{% if schema.bearerFormat %}
293-
format: '{{ schema.bearerFormat }}',
294-
{% endif %}
295295
key: 'Authorization',
296-
value: "Bearer #{access_token}"
296+
value: access_token ? "Bearer #{access_token}" : nil
297297
},
298-
#}
299298
{%- elif schema.type == "oauth2" %}
300299
{{name}}:
301300
{

lib/datadog_api_client/api_client.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def calculate_retry_interval(response, backoff_base, backoff_multiplier, attempt
154154
#Redact api and app key in the request header
155155
def sanitize_request_header(request_header)
156156
sanitized_headers= request_header.dup
157-
keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY"]
157+
keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY", "Authorization"]
158158
keys_to_redact.each do |key_to_redact|
159159
if sanitized_headers.key?(key_to_redact)
160160
sanitized_headers[key_to_redact] = "REDACTED"
@@ -377,9 +377,18 @@ def build_request_url(path, opts = {})
377377
# @param [Hash] query_params Query parameters
378378
# @param [String] auth_names Authentication scheme name
379379
def update_params_for_auth!(header_params, query_params, auth_names)
380-
Array(auth_names).each do |auth_name|
380+
auth_names = Array(auth_names)
381+
382+
# When a bearer token (PAT) is configured, use Bearer auth exclusively
383+
# instead of API key + App key.
384+
if @config.access_token
385+
auth_names = [:bearerAuth]
386+
end
387+
388+
auth_names.each do |auth_name|
381389
auth_setting = @config.auth_settings[auth_name]
382390
next unless auth_setting
391+
next if auth_setting[:value].nil? || auth_setting[:value].to_s.empty?
383392
case auth_setting[:in]
384393
when 'header' then header_params[auth_setting[:key]] = auth_setting[:value]
385394
when 'query' then query_params[auth_setting[:key]] = auth_setting[:value]

lib/datadog_api_client/configuration.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ def initialize
408408
@server_variables[:site] = ENV['DD_SITE'] if ENV.key? 'DD_SITE'
409409
@api_key['apiKeyAuth'] = ENV['DD_API_KEY'] if ENV.key? 'DD_API_KEY'
410410
@api_key['appKeyAuth'] = ENV['DD_APP_KEY'] if ENV.key? 'DD_APP_KEY'
411+
@access_token = ENV['DD_BEARER_TOKEN'] if ENV.key? 'DD_BEARER_TOKEN'
411412

412413
yield(self) if block_given?
413414
end
@@ -503,6 +504,13 @@ def auth_settings
503504
key: 'DD-APPLICATION-KEY',
504505
value: api_key_with_prefix('appKeyAuth')
505506
},
507+
bearerAuth:
508+
{
509+
type: 'http',
510+
in: 'header',
511+
key: 'Authorization',
512+
value: access_token ? "Bearer #{access_token}" : nil
513+
},
506514
}
507515
end
508516

spec/api_client_spec.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,69 @@
206206
expect(api_client.sanitize_filename('.\sun.gif')).to eq('sun.gif')
207207
end
208208
end
209+
210+
describe '#update_params_for_auth!' do
211+
let(:config) { DatadogAPIClient::Configuration.new }
212+
let(:api_client) { DatadogAPIClient::APIClient.new(config) }
213+
214+
context 'when bearer token (PAT) is configured' do
215+
before do
216+
config.api_key = 'test_api_key'
217+
config.application_key = 'test_app_key'
218+
config.access_token = 'ddpat_test_pat'
219+
end
220+
221+
it 'sends only Bearer Authorization header, no API key or app key' do
222+
header_params = {}
223+
query_params = {}
224+
api_client.update_params_for_auth!(header_params, query_params, [:apiKeyAuth, :appKeyAuth])
225+
expect(header_params['Authorization']).to eq('Bearer ddpat_test_pat')
226+
expect(header_params).not_to have_key('DD-API-KEY')
227+
expect(header_params).not_to have_key('DD-APPLICATION-KEY')
228+
end
229+
230+
it 'sends only Bearer even when bearerAuth is already in auth_names' do
231+
header_params = {}
232+
query_params = {}
233+
api_client.update_params_for_auth!(header_params, query_params, [:apiKeyAuth, :appKeyAuth, :bearerAuth])
234+
expect(header_params['Authorization']).to eq('Bearer ddpat_test_pat')
235+
expect(header_params).not_to have_key('DD-API-KEY')
236+
expect(header_params).not_to have_key('DD-APPLICATION-KEY')
237+
end
238+
end
239+
240+
context 'when bearer token is not configured' do
241+
before do
242+
config.api_key = 'test_api_key'
243+
config.application_key = 'test_app_key'
244+
end
245+
246+
it 'uses API key and app key, no Bearer header' do
247+
header_params = {}
248+
query_params = {}
249+
api_client.update_params_for_auth!(header_params, query_params, [:apiKeyAuth, :appKeyAuth])
250+
expect(header_params['DD-API-KEY']).to eq('test_api_key')
251+
expect(header_params['DD-APPLICATION-KEY']).to eq('test_app_key')
252+
expect(header_params).not_to have_key('Authorization')
253+
end
254+
end
255+
end
256+
257+
describe '#sanitize_request_header' do
258+
let(:api_client) { DatadogAPIClient::APIClient.new }
259+
260+
it 'redacts sensitive headers including Authorization' do
261+
headers = {
262+
'DD-API-KEY' => 'secret_api_key',
263+
'DD-APPLICATION-KEY' => 'secret_app_key',
264+
'Authorization' => 'Bearer ddapp_secret_pat',
265+
'Content-Type' => 'application/json'
266+
}
267+
sanitized = api_client.sanitize_request_header(headers)
268+
expect(sanitized['DD-API-KEY']).to eq('REDACTED')
269+
expect(sanitized['DD-APPLICATION-KEY']).to eq('REDACTED')
270+
expect(sanitized['Authorization']).to eq('REDACTED')
271+
expect(sanitized['Content-Type']).to eq('application/json')
272+
end
273+
end
209274
end

spec/configuration_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,45 @@
5858
end
5959
end
6060

61+
describe '#access_token (bearer auth)' do
62+
let(:config) { DatadogAPIClient::Configuration.new }
63+
64+
it 'defaults to nil' do
65+
expect(config.access_token).to be_nil
66+
end
67+
68+
it 'can be set directly' do
69+
config.access_token = 'ddpat_test_token_123'
70+
expect(config.access_token).to eq('ddpat_test_token_123')
71+
end
72+
73+
it 'loads from DD_BEARER_TOKEN environment variable' do
74+
allow(ENV).to receive(:key?).and_call_original
75+
allow(ENV).to receive(:key?).with('DD_BEARER_TOKEN').and_return(true)
76+
allow(ENV).to receive(:[]).and_call_original
77+
allow(ENV).to receive(:[]).with('DD_BEARER_TOKEN').and_return('ddpat_env_token')
78+
new_config = DatadogAPIClient::Configuration.new
79+
expect(new_config.access_token).to eq('ddpat_env_token')
80+
end
81+
end
82+
83+
describe '#auth_settings' do
84+
let(:config) { DatadogAPIClient::Configuration.new }
85+
86+
it 'includes bearerAuth with nil value when token not set' do
87+
expect(config.auth_settings[:bearerAuth]).not_to be_nil
88+
expect(config.auth_settings[:bearerAuth][:value]).to be_nil
89+
end
90+
91+
it 'includes bearerAuth with Bearer token when access_token is set' do
92+
config.access_token = 'ddpat_my_token'
93+
auth = config.auth_settings[:bearerAuth]
94+
expect(auth[:type]).to eq('http')
95+
expect(auth[:in]).to eq('header')
96+
expect(auth[:key]).to eq('Authorization')
97+
expect(auth[:value]).to eq('Bearer ddpat_my_token')
98+
end
99+
end
100+
61101

62102
end

0 commit comments

Comments
 (0)