Skip to content

Commit 894712c

Browse files
committed
feat(url): add split_basic_auth() helper for URL-embedded HTTP basic auth
Extracts userinfo (user[:password]) from a URL like https://alice:secret@host/path, returns the URL with the userinfo stripped from the netloc plus a headers dict carrying the matching Authorization: Basic header. Pass both into lib.url.fetch() or lib.url.fetch_json() so apps can accept HTTP basic auth via the URL itself instead of exposing separate --username / --password arguments. Keeps credentials out of ps listings, out of the request line and out of any proxy access log. The php-fpm-status check plugin already follows this pattern with its own local helper; this lib function generalises it for reuse so the haproxy-status plugin (and any future HTTP-basic-auth plugin) does not have to duplicate the base64-and-strip dance.
1 parent f815718 commit 894712c

2 files changed

Lines changed: 43 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11-
tbd
11+
### Added
12+
13+
* url.py: add `split_basic_auth(url)` helper that extracts userinfo from a URL like `https://user:secret@host/path`, returns the URL with the userinfo stripped from the netloc, plus a headers dict carrying the matching `Authorization: Basic ...` entry. Pass both into `lib.url.fetch()` / `lib.url.fetch_json()`. This lets apps accept HTTP basic auth via the URL itself instead of exposing separate `--username` / `--password` arguments, which keeps the credentials out of `ps` listings, out of the request line, and out of any proxy access log
1214

1315

1416
## [v3.1.1] - 2026-04-14

url.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
"""Get for example HTML or JSON from an URL."""
1212

1313
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
14-
__version__ = '2026041201'
14+
__version__ = '2026041403'
1515

16+
import base64
1617
import json
1718
import re
1819
import ssl
@@ -308,6 +309,44 @@ def get_latest_version_from_github(user, repo, key='tag_name'):
308309
return True, result.get(key, False)
309310

310311

312+
def split_basic_auth(url):
313+
"""Extract userinfo from `url` and return a `(url, headers)` tuple.
314+
315+
The returned URL has any `user[:password]@` prefix stripped from
316+
its netloc so the credentials never reach the request line or
317+
any proxy log. If userinfo is present, `headers` contains the
318+
matching `Authorization: Basic ...` entry; otherwise it is an
319+
empty dict.
320+
321+
Pass the returned `url` and `headers` to `lib.url.fetch()` /
322+
`lib.url.fetch_json()` so plugins can accept HTTP basic auth via
323+
the URL (e.g. `https://user:secret@host/path`) instead of
324+
exposing separate `--username` / `--password` arguments.
325+
326+
>>> split_basic_auth('https://example.com/path')
327+
('https://example.com/path', {})
328+
>>> u, h = split_basic_auth('https://alice:secret@example.com/path')
329+
>>> u
330+
'https://example.com/path'
331+
>>> h
332+
{'Authorization': 'Basic YWxpY2U6c2VjcmV0'}
333+
"""
334+
parsed = urllib.parse.urlparse(url)
335+
if not parsed.username:
336+
return url, {}
337+
338+
user = urllib.parse.unquote(parsed.username)
339+
password = urllib.parse.unquote(parsed.password or '')
340+
token = txt.to_text(base64.b64encode(txt.to_bytes(f'{user}:{password}')))
341+
342+
netloc = parsed.hostname or ''
343+
if parsed.port is not None:
344+
netloc = f'{netloc}:{parsed.port}'
345+
stripped = urllib.parse.urlunparse(parsed._replace(netloc=netloc))
346+
347+
return stripped, {'Authorization': f'Basic {token}'}
348+
349+
311350
def strip_tags(html):
312351
"""
313352
Strips all HTML tags from a given string.

0 commit comments

Comments
 (0)