Skip to content

Commit 3a70f73

Browse files
committed
Move to explicit method call for Basic Auth credentials in push client
While URLs do support passing Basic Auth credentials using the `http://user:[email protected]/` syntax, the username and password in that syntax have to follow the usual rules for URL encoding of characters per RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986#section-2.1). Rather than place the burden of correctly performing that encoding on users of this gem, we decided to have a separate method for supplying Basic Auth credentials, with no requirement to URL encode the characters in them. Signed-off-by: Chris Sinjakli <[email protected]>
1 parent 391aa20 commit 3a70f73

File tree

2 files changed

+67
-15
lines changed

2 files changed

+67
-15
lines changed

lib/prometheus/client/push.rb

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,21 @@ def initialize(job:, gateway: DEFAULT_GATEWAY, grouping_key: {}, **kwargs)
3939
@gateway = gateway || DEFAULT_GATEWAY
4040
@grouping_key = grouping_key
4141
@path = build_path(job, grouping_key)
42+
4243
@uri = parse("#{@gateway}#{@path}")
44+
validate_no_basic_auth!(@uri)
4345

4446
@http = Net::HTTP.new(@uri.host, @uri.port)
4547
@http.use_ssl = (@uri.scheme == 'https')
4648
@http.open_timeout = kwargs[:open_timeout] if kwargs[:open_timeout]
4749
@http.read_timeout = kwargs[:read_timeout] if kwargs[:read_timeout]
4850
end
4951

52+
def basic_auth(user, password)
53+
@user = user
54+
@password = password
55+
end
56+
5057
def add(registry)
5158
synchronize do
5259
request(Net::HTTP::Post, registry)
@@ -113,7 +120,7 @@ def request(req_class, registry = nil)
113120

114121
req = req_class.new(@uri)
115122
req.content_type = Formats::Text::CONTENT_TYPE
116-
req.basic_auth(@uri.user, @uri.password) if @uri.user
123+
req.basic_auth(@user, @password) if @user
117124
req.body = Formats::Text.marshal(registry) if registry
118125

119126
response = @http.request(req)
@@ -126,6 +133,39 @@ def synchronize
126133
@mutex.synchronize { yield }
127134
end
128135

136+
def validate_no_basic_auth!(uri)
137+
if uri.user || uri.password
138+
raise ArgumentError, <<~EOF
139+
Setting Basic Auth credentials in the gateway URL is not supported, please call the `basic_auth` method.
140+
141+
Received username `#{uri.user}` in gateway URL. Instead of passing
142+
Basic Auth credentials like this:
143+
144+
```
145+
push = Prometheus::Client::Push.new(job: "my-job", gateway: "http://user:password@localhost:9091")
146+
```
147+
148+
please pass them like this instead:
149+
150+
```
151+
push = Prometheus::Client::Push.new(job: "my-job", gateway: "http://localhost:9091")
152+
push.basic_auth("user", "password")
153+
```
154+
155+
While URLs do support passing Basic Auth credentials using the
156+
`http://user:[email protected]/` syntax, the username and
157+
password in that syntax have to follow the usual rules for URL
158+
encoding of characters per RFC 3986
159+
(https://datatracker.ietf.org/doc/html/rfc3986#section-2.1).
160+
161+
Rather than place the burden of correctly performing that encoding
162+
on users of this gem, we decided to have a separate method for
163+
supplying Basic Auth credentials, with no requirement to URL encode
164+
the characters in them.
165+
EOF
166+
end
167+
end
168+
129169
def validate_no_label_clashes!(registry)
130170
# There's nothing to check if we don't have a grouping key
131171
return if @grouping_key.empty?

spec/prometheus/client/push_spec.rb

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -271,23 +271,35 @@
271271
end
272272

273273
context 'Basic Auth support' do
274-
let(:gateway) { 'https://super:secret@localhost:9091' }
274+
context 'when credentials are passed in the gateway URL' do
275+
let(:gateway) { 'https://super:secret@localhost:9091' }
275276

276-
it 'sets Basic Auth header when requested' do
277-
request = double(:request)
278-
expect(request).to receive(:content_type=).with(content_type)
279-
expect(request).to receive(:basic_auth).with('super', 'secret')
280-
expect(request).to receive(:body=).with(data)
281-
expect(Net::HTTP::Put).to receive(:new).with(uri).and_return(request)
277+
it "raises an ArgumentError explaining why we don't support that mechanism" do
278+
expect { push }.to raise_error ArgumentError, /in the gateway URL.*username `super`/m
279+
end
280+
end
282281

283-
http = double(:http)
284-
expect(http).to receive(:use_ssl=).with(true)
285-
expect(http).to receive(:open_timeout=).with(5)
286-
expect(http).to receive(:read_timeout=).with(30)
287-
expect(http).to receive(:request).with(request).and_return(response)
288-
expect(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
282+
context 'when credentials are passed to the separate `basic_auth` method' do
283+
let(:gateway) { 'https://localhost:9091' }
284+
285+
it 'passes the credentials on to the HTTP client' do
286+
request = double(:request)
287+
expect(request).to receive(:content_type=).with(content_type)
288+
expect(request).to receive(:basic_auth).with('super', 'secret')
289+
expect(request).to receive(:body=).with(data)
290+
expect(Net::HTTP::Put).to receive(:new).with(uri).and_return(request)
291+
292+
http = double(:http)
293+
expect(http).to receive(:use_ssl=).with(true)
294+
expect(http).to receive(:open_timeout=).with(5)
295+
expect(http).to receive(:read_timeout=).with(30)
296+
expect(http).to receive(:request).with(request).and_return(response)
297+
expect(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
298+
299+
push.basic_auth("super", "secret")
289300

290-
push.send(:request, Net::HTTP::Put, registry)
301+
push.send(:request, Net::HTTP::Put, registry)
302+
end
291303
end
292304
end
293305

0 commit comments

Comments
 (0)