Skip to content

Commit 3ce3db9

Browse files
authored
Enable automatic proxy logon with implicit credentials. (#253)
Fixes #248
1 parent a0aef33 commit 3ce3db9

File tree

3 files changed

+47
-15
lines changed

3 files changed

+47
-15
lines changed

src/_native/bits.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,17 @@ static HRESULT _job_setproxy(IBackgroundCopyJob *job) {
229229
}
230230

231231

232-
static HRESULT _job_setcredentials(IBackgroundCopyJob *job, wchar_t *username, wchar_t *password) {
232+
static HRESULT _job_setcredentials(
233+
IBackgroundCopyJob *job,
234+
BG_AUTH_TARGET target,
235+
wchar_t *username,
236+
wchar_t *password
237+
) {
233238
IBackgroundCopyJob2 *job2 = NULL;
234239
HRESULT hr;
235240
BG_AUTH_CREDENTIALS creds = {
236-
.Target = BG_AUTH_TARGET_SERVER,
237-
.Scheme = BG_AUTH_SCHEME_BASIC,
241+
.Target = target,
242+
.Scheme = username ? BG_AUTH_SCHEME_BASIC : BG_AUTH_SCHEME_NEGOTIATE,
238243
.Credentials = {
239244
.Basic = {
240245
.UserName = username,
@@ -243,10 +248,6 @@ static HRESULT _job_setcredentials(IBackgroundCopyJob *job, wchar_t *username, w
243248
}
244249
};
245250

246-
if (!username && !password) {
247-
return S_OK;
248-
}
249-
250251
if (FAILED(hr = _inject_hr[3])
251252
|| FAILED(hr = job->QueryInterface(__uuidof(IBackgroundCopyJob2), (void **)&job2))) {
252253
return hr;
@@ -285,7 +286,14 @@ PyObject *bits_begin(PyObject *, PyObject *args, PyObject *kwargs) {
285286
error_from_bits_hr(bcm, hr, "Setting proxy");
286287
goto done;
287288
}
288-
if ((username || password) && FAILED(hr = _job_setcredentials(job, username, password))) {
289+
// Setting proxy credentials to NULL will automatically infer credentials
290+
// if needed. It's a good default (provided users have not configured a
291+
// malicious proxy server, which we can't do anything about here anyway).
292+
if (FAILED(hr = _job_setcredentials(job, BG_AUTH_TARGET_PROXY, NULL, NULL))) {
293+
error_from_bits_hr(bcm, hr, "Setting proxy credentials");
294+
goto done;
295+
}
296+
if (FAILED(hr = _job_setcredentials(job, BG_AUTH_TARGET_SERVER, username, password))) {
289297
error_from_bits_hr(bcm, hr, "Adding basic credentials to download job");
290298
goto done;
291299
}
@@ -387,7 +395,7 @@ PyObject *bits_retry_with_auth(PyObject *, PyObject *args, PyObject *kwargs) {
387395
HRESULT hr;
388396
PyObject *r = NULL;
389397

390-
if (FAILED(hr = _job_setcredentials(job, username, password))) {
398+
if (FAILED(hr = _job_setcredentials(job, BG_AUTH_TARGET_SERVER, username, password))) {
391399
error_from_bits_hr(bcm, hr, "Adding basic credentials to download job");
392400
goto done;
393401
}

src/_native/winhttp.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ static bool winhttp_apply_proxy(HINTERNET hSession, HINTERNET hRequest, const wc
190190
// Now resolve the proxy required for the specified URL
191191
CHECK_WINHTTP(WinHttpGetProxyForUrl(hSession, url, &proxy_opt, &proxy_info));
192192

193+
// Enable proxy servers to automatically login with implicit credentials
194+
// This is only used if the proxy sends a 407 response, otherwise, they are
195+
// ignored.
196+
CHECK_WINHTTP(WinHttpSetCredentials(
197+
hRequest,
198+
WINHTTP_AUTH_TARGET_PROXY,
199+
WINHTTP_AUTH_SCHEME_NEGOTIATE,
200+
NULL, NULL, NULL
201+
));
202+
193203
// Apply the proxy settings to the request
194204
CHECK_WINHTTP(WinHttpSetOption(
195205
hRequest,
@@ -285,6 +295,17 @@ PyObject *winhttp_urlopen(PyObject *, PyObject *args, PyObject *kwargs) {
285295
0
286296
);
287297
}
298+
299+
// Allow proxies to automatically log in (we'll set the default credentials
300+
// in winhttp_apply_proxy(), but this setting has to go on the session).
301+
opt = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
302+
CHECK_WINHTTP(WinHttpSetOption(
303+
hSession,
304+
WINHTTP_OPTION_AUTOLOGON_POLICY,
305+
&opt,
306+
sizeof(opt)
307+
));
308+
288309
CHECK_WINHTTP(hSession);
289310

290311
hConnection = WinHttpConnect(
@@ -456,7 +477,7 @@ PyObject *winhttp_urlopen(PyObject *, PyObject *args, PyObject *kwargs) {
456477
PyObject *winhttp_isconnected(PyObject *, PyObject *, PyObject *) {
457478
INetworkListManager *nlm = NULL;
458479
VARIANT_BOOL connected;
459-
480+
460481
HRESULT hr = CoCreateInstance(
461482
CLSID_NetworkListManager,
462483
NULL,

tests/test_urlutils.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,15 @@ def test_bits_errors(localserver, tmp_path, inject_error):
295295

296296
# Inject an error when adding credentials
297297
inject_error(0, 0, 0, 0xA0000001)
298-
# No credentials specified, so does not raise
299-
try:
298+
# Implicit credentials are always specified
299+
with pytest.raises(OSError) as ex:
300300
job = _native.bits_begin(conn, "PyManager Test", url, dest)
301-
finally:
302-
_native.bits_cancel(conn, job)
303-
# Add credentials to cause injected error
301+
# Original error is ours
302+
assert ex.value.__context__.winerror & 0xFFFFFFFF == 0xA0000001
303+
# The final error is the missing message
304+
assert ex.value.winerror & 0xFFFFFFFF == ERROR_MR_MID_NOT_FOUND
305+
306+
# Add credentials also causes injected error
304307
with pytest.raises(OSError) as ex:
305308
job = _native.bits_begin(conn, "PyManager Test", url, dest, "x", "y")
306309
# Original error is ours

0 commit comments

Comments
 (0)