7
7
import os
8
8
import shutil
9
9
import subprocess
10
- import urllib .parse
11
10
from abc import ABC , abstractmethod
12
- from typing import Any , Dict , List , NamedTuple , Optional , Tuple
11
+ from typing import Dict , List , NamedTuple , Optional , Tuple
13
12
14
13
from pip ._vendor .requests .auth import AuthBase , HTTPBasicAuth
15
- from pip ._vendor .requests .models import Request , Response
14
+ from pip ._vendor .requests .models import Request
16
15
from pip ._vendor .requests .utils import get_netrc_auth
17
16
18
17
from pip ._internal .utils .logging import getLogger
19
- from pip ._internal .utils .misc import (
20
- ask ,
21
- ask_input ,
22
- ask_password ,
23
- remove_auth_from_url ,
24
- split_auth_netloc_from_url ,
25
- )
18
+ from pip ._internal .utils .misc import remove_auth_from_url , split_auth_netloc_from_url
26
19
from pip ._internal .vcs .versioncontrol import AuthInfo
27
20
28
21
logger = getLogger (__name__ )
@@ -198,11 +191,15 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au
198
191
199
192
class MultiDomainBasicAuth (AuthBase ):
200
193
def __init__ (
201
- self , prompting : bool = True , index_urls : Optional [List [str ]] = None
194
+ self ,
195
+ prompting : bool = True ,
196
+ index_urls : Optional [List [str ]] = None ,
197
+ default_key_ring_user : Optional [str ] = None ,
202
198
) -> None :
203
199
self .prompting = prompting
204
200
self .index_urls = index_urls
205
201
self .passwords : Dict [str , AuthInfo ] = {}
202
+ self .default_key_ring_user = default_key_ring_user
206
203
# When the user is prompted to enter credentials and keyring is
207
204
# available, we will offer to save them. If the user accepts,
208
205
# this value is set to the credentials they entered. After the
@@ -235,29 +232,31 @@ def _get_index_url(self, url: str) -> Optional[str]:
235
232
def _get_new_credentials (
236
233
self ,
237
234
original_url : str ,
238
- allow_netrc : bool = True ,
239
- allow_keyring : bool = False ,
240
235
) -> AuthInfo :
241
236
"""Find and return credentials for the specified URL."""
242
237
# Split the credentials and netloc from the url.
243
238
url , netloc , url_user_password = split_auth_netloc_from_url (
244
239
original_url ,
245
240
)
246
241
247
- # Start with the credentials embedded in the url
248
242
username , password = url_user_password
249
243
if username is not None and password is not None :
250
244
logger .debug ("Found credentials in url for %s" , netloc )
251
245
return url_user_password
252
246
253
- # Find a matching index url for this request
254
- index_url = self ._get_index_url (url )
255
- if index_url :
256
- # Split the credentials from the url.
257
- index_info = split_auth_netloc_from_url (index_url )
258
- if index_info :
259
- index_url , _ , index_url_user_password = index_info
260
- logger .debug ("Found index url %s" , index_url )
247
+ def split_index_url_on_url_and_credentials (url ):
248
+ if not url :
249
+ return url , None
250
+ index_info = split_auth_netloc_from_url (url )
251
+ if not index_info :
252
+ return url , None
253
+ index_url , _ , index_url_user_password = index_info
254
+ logger .debug ("Found index url %s" , index_url )
255
+ return index_url , index_url_user_password
256
+
257
+ index_url , index_url_user_password = split_index_url_on_url_and_credentials (
258
+ self ._get_index_url (url )
259
+ )
261
260
262
261
# If an index URL was found, try its embedded credentials
263
262
if index_url and index_url_user_password [0 ] is not None :
@@ -266,27 +265,57 @@ def _get_new_credentials(
266
265
logger .debug ("Found credentials in index url for %s" , netloc )
267
266
return index_url_user_password
268
267
269
- # Get creds from netrc if we still don't have them
270
- if allow_netrc :
271
- netrc_auth = get_netrc_auth (original_url )
272
- if netrc_auth :
273
- logger .debug ("Found credentials in netrc for %s" , netloc )
274
- return netrc_auth
275
-
276
- # If we don't have a password and keyring is available, use it.
277
- if allow_keyring :
278
- # The index url is more specific than the netloc, so try it first
279
- # fmt: off
280
- kr_auth = (
281
- get_keyring_auth (index_url , username ) or
282
- get_keyring_auth (netloc , username )
283
- )
284
- # fmt: on
268
+ netrc_auth = get_netrc_auth (original_url )
269
+ if netrc_auth is not None :
270
+ logger .debug ("Found credentials in netrc for %s" , netloc )
271
+ return netrc_auth
272
+
273
+ kr_auth = self ._find_key_ring_credentials (
274
+ index_url , index_url_user_password , netloc , username
275
+ )
276
+ if kr_auth is not None :
277
+ return kr_auth
278
+
279
+ return username , password
280
+
281
+ def _find_key_ring_credentials (
282
+ self ,
283
+ index_url : Optional [str ],
284
+ index_url_user_password : Optional [str ],
285
+ netloc : str ,
286
+ artifact_username : str ,
287
+ ) -> Optional [AuthInfo ]:
288
+ def get_key_ring_user () -> Optional [str ]:
289
+ if artifact_username is not None :
290
+ return artifact_username
291
+ if index_url_user_password :
292
+ if (
293
+ index_url_user_password [0 ] is not None
294
+ and index_url_user_password [1 ] is None
295
+ ):
296
+ logger .debug ("Found key ring username in index_url" )
297
+ return index_url_user_password [0 ]
298
+ if artifact_username is None and self .default_key_ring_user is not None :
299
+ logger .debug ("Using default_key_ring_user" )
300
+ return self .default_key_ring_user
301
+ return None
302
+
303
+ key_ring_user = get_key_ring_user ()
304
+ if key_ring_user is None :
305
+ return None
306
+
307
+ if index_url is not None :
308
+ kr_auth = get_keyring_auth (index_url , key_ring_user )
285
309
if kr_auth :
286
- logger .debug ("Found credentials in keyring for %s" , netloc )
310
+ logger .debug ("Found credentials in keyring for %s" , index_url )
287
311
return kr_auth
288
312
289
- return username , password
313
+ kr_auth = get_keyring_auth (netloc , key_ring_user )
314
+ if kr_auth :
315
+ logger .debug ("Found credentials in keyring for %s" , netloc )
316
+ return kr_auth
317
+
318
+ return None
290
319
291
320
def _get_url_and_credentials (
292
321
self , original_url : str
@@ -346,107 +375,4 @@ def __call__(self, req: Request) -> Request:
346
375
if username is not None and password is not None :
347
376
# Send the basic auth with this request
348
377
req = HTTPBasicAuth (username , password )(req )
349
-
350
- # Attach a hook to handle 401 responses
351
- req .register_hook ("response" , self .handle_401 )
352
-
353
378
return req
354
-
355
- # Factored out to allow for easy patching in tests
356
- def _prompt_for_password (
357
- self , netloc : str
358
- ) -> Tuple [Optional [str ], Optional [str ], bool ]:
359
- username = ask_input (f"User for { netloc } : " )
360
- if not username :
361
- return None , None , False
362
- auth = get_keyring_auth (netloc , username )
363
- if auth and auth [0 ] is not None and auth [1 ] is not None :
364
- return auth [0 ], auth [1 ], False
365
- password = ask_password ("Password: " )
366
- return username , password , True
367
-
368
- # Factored out to allow for easy patching in tests
369
- def _should_save_password_to_keyring (self ) -> bool :
370
- if not get_keyring_provider ().has_keyring :
371
- return False
372
- return ask ("Save credentials to keyring [y/N]: " , ["y" , "n" ]) == "y"
373
-
374
- def handle_401 (self , resp : Response , ** kwargs : Any ) -> Response :
375
- # We only care about 401 responses, anything else we want to just
376
- # pass through the actual response
377
- if resp .status_code != 401 :
378
- return resp
379
-
380
- # We are not able to prompt the user so simply return the response
381
- if not self .prompting :
382
- return resp
383
-
384
- parsed = urllib .parse .urlparse (resp .url )
385
-
386
- # Query the keyring for credentials:
387
- username , password = self ._get_new_credentials (
388
- resp .url ,
389
- allow_netrc = False ,
390
- allow_keyring = True ,
391
- )
392
-
393
- # Prompt the user for a new username and password
394
- save = False
395
- if not username and not password :
396
- username , password , save = self ._prompt_for_password (parsed .netloc )
397
-
398
- # Store the new username and password to use for future requests
399
- self ._credentials_to_save = None
400
- if username is not None and password is not None :
401
- self .passwords [parsed .netloc ] = (username , password )
402
-
403
- # Prompt to save the password to keyring
404
- if save and self ._should_save_password_to_keyring ():
405
- self ._credentials_to_save = Credentials (
406
- url = parsed .netloc ,
407
- username = username ,
408
- password = password ,
409
- )
410
-
411
- # Consume content and release the original connection to allow our new
412
- # request to reuse the same one.
413
- resp .content
414
- resp .raw .release_conn ()
415
-
416
- # Add our new username and password to the request
417
- req = HTTPBasicAuth (username or "" , password or "" )(resp .request )
418
- req .register_hook ("response" , self .warn_on_401 )
419
-
420
- # On successful request, save the credentials that were used to
421
- # keyring. (Note that if the user responded "no" above, this member
422
- # is not set and nothing will be saved.)
423
- if self ._credentials_to_save :
424
- req .register_hook ("response" , self .save_credentials )
425
-
426
- # Send our new request
427
- new_resp = resp .connection .send (req , ** kwargs )
428
- new_resp .history .append (resp )
429
-
430
- return new_resp
431
-
432
- def warn_on_401 (self , resp : Response , ** kwargs : Any ) -> None :
433
- """Response callback to warn about incorrect credentials."""
434
- if resp .status_code == 401 :
435
- logger .warning (
436
- "401 Error, Credentials not correct for %s" ,
437
- resp .request .url ,
438
- )
439
-
440
- def save_credentials (self , resp : Response , ** kwargs : Any ) -> None :
441
- """Response callback to save credentials on success."""
442
- keyring = get_keyring_provider ()
443
- assert keyring .has_keyring , "should never reach here without keyring"
444
-
445
- creds = self ._credentials_to_save
446
- self ._credentials_to_save = None
447
- if creds and resp .status_code < 400 :
448
- try :
449
- logger .info ("Saving credentials to keyring" )
450
- keyring .save_auth_info (creds .url , creds .username , creds .password )
451
- except Exception :
452
- logger .exception ("Failed to save credentials" )
0 commit comments