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