77import os
88import shutil
99import subprocess
10- import urllib .parse
1110from abc import ABC , abstractmethod
12- from typing import Any , Dict , List , NamedTuple , Optional , Tuple
11+ from typing import Dict , List , NamedTuple , Optional , Tuple
1312
1413from pip ._vendor .requests .auth import AuthBase , HTTPBasicAuth
15- from pip ._vendor .requests .models import Request , Response
14+ from pip ._vendor .requests .models import Request
1615from pip ._vendor .requests .utils import get_netrc_auth
1716
1817from 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
2619from pip ._internal .vcs .versioncontrol import AuthInfo
2720
2821logger = getLogger (__name__ )
@@ -198,11 +191,15 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au
198191
199192class MultiDomainBasicAuth (AuthBase ):
200193 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 ,
202198 ) -> None :
203199 self .prompting = prompting
204200 self .index_urls = index_urls
205201 self .passwords : Dict [str , AuthInfo ] = {}
202+ self .default_key_ring_user = default_key_ring_user
206203 # When the user is prompted to enter credentials and keyring is
207204 # available, we will offer to save them. If the user accepts,
208205 # this value is set to the credentials they entered. After the
@@ -235,29 +232,31 @@ def _get_index_url(self, url: str) -> Optional[str]:
235232 def _get_new_credentials (
236233 self ,
237234 original_url : str ,
238- allow_netrc : bool = True ,
239- allow_keyring : bool = False ,
240235 ) -> AuthInfo :
241236 """Find and return credentials for the specified URL."""
242237 # Split the credentials and netloc from the url.
243238 url , netloc , url_user_password = split_auth_netloc_from_url (
244239 original_url ,
245240 )
246241
247- # Start with the credentials embedded in the url
248242 username , password = url_user_password
249243 if username is not None and password is not None :
250244 logger .debug ("Found credentials in url for %s" , netloc )
251245 return url_user_password
252246
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+ )
261260
262261 # If an index URL was found, try its embedded credentials
263262 if index_url and index_url_user_password [0 ] is not None :
@@ -266,27 +265,57 @@ def _get_new_credentials(
266265 logger .debug ("Found credentials in index url for %s" , netloc )
267266 return index_url_user_password
268267
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 )
285309 if kr_auth :
286- logger .debug ("Found credentials in keyring for %s" , netloc )
310+ logger .debug ("Found credentials in keyring for %s" , index_url )
287311 return kr_auth
288312
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
290319
291320 def _get_url_and_credentials (
292321 self , original_url : str
@@ -346,107 +375,4 @@ def __call__(self, req: Request) -> Request:
346375 if username is not None and password is not None :
347376 # Send the basic auth with this request
348377 req = HTTPBasicAuth (username , password )(req )
349-
350- # Attach a hook to handle 401 responses
351- req .register_hook ("response" , self .handle_401 )
352-
353378 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