def test_get_http_auth(config, with_simple_keyring, dummy_keyring): dummy_keyring.set_password("poetry-repository-foo", "bar", "baz") config.auth_config_source.add_property("http-basic.foo", {"username": "******"}) manager = PasswordManager(config) assert manager.keyring.is_available() auth = manager.get_http_auth("foo") assert "bar" == auth["username"] assert "baz" == auth["password"]
def test_get_http_auth_from_environment_variables(environ, config, with_simple_keyring): os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "******" os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "******" manager = PasswordManager(config) auth = manager.get_http_auth("foo") assert "bar" == auth["username"] assert "baz" == auth["password"]
def test_get_http_auth_with_unavailable_backend(config, with_fail_keyring): config.auth_config_source.add_property( "http-basic.foo", {"username": "******", "password": "******"} ) manager = PasswordManager(config) assert not manager.keyring.is_available() auth = manager.get_http_auth("foo") assert "bar" == auth["username"] assert "baz" == auth["password"]
def get_http_credentials( self, password_manager: PasswordManager, username: str | None = None) -> HTTPAuthCredential: # try with the repository name via the password manager credential = HTTPAuthCredential( **(password_manager.get_http_auth(self.name) or {})) if credential.password is None: # fallback to url and netloc based keyring entries credential = password_manager.keyring.get_credential( self.url, self.netloc, username=credential.username) if credential.password is not None: return HTTPAuthCredential(username=credential.username, password=credential.password) return credential
class Authenticator(object): def __init__(self, config, io=None): # type: (Config, Optional[IO]) -> None self._config = config self._io = io self._session = None self._credentials = {} self._password_manager = PasswordManager(self._config) def _log(self, message, level="debug"): # type: (str, str) -> None if self._io is not None: self._io.write_line("<{level:s}>{message:s}</{level:s}>".format( message=message, level=level)) else: getattr(logger, level, logger.debug)(message) @property def session(self): # type: () -> requests.Session if self._session is None: self._session = requests.Session() return self._session def request(self, method, url, **kwargs): # type: (str, str, Any) -> requests.Response request = requests.Request(method, url) username, password = self.get_credentials_for_url(url) if username is not None and password is not None: request = requests.auth.HTTPBasicAuth(username, password)(request) session = self.session prepared_request = session.prepare_request(request) proxies = kwargs.get("proxies", {}) stream = kwargs.get("stream") verify = kwargs.get("verify") cert = kwargs.get("cert") settings = session.merge_environment_settings(prepared_request.url, proxies, stream, verify, cert) # Send the request. send_kwargs = { "timeout": kwargs.get("timeout"), "allow_redirects": kwargs.get("allow_redirects", True), } send_kwargs.update(settings) attempt = 0 while True: is_last_attempt = attempt >= 5 try: resp = session.send(prepared_request, **send_kwargs) except (requests.exceptions.ConnectionError, OSError) as e: if is_last_attempt: raise e else: if resp.status_code not in [502, 503, 504] or is_last_attempt: resp.raise_for_status() return resp if not is_last_attempt: attempt += 1 delay = 0.5 * attempt self._log("Retrying HTTP request in {} seconds.".format(delay), level="debug") time.sleep(delay) continue # this should never really be hit under any sane circumstance raise PoetryException("Failed HTTP {} request", method.upper()) def get_credentials_for_url( self, url): # type: (str) -> Tuple[Optional[str], Optional[str]] parsed_url = urllib.parse.urlsplit(url) netloc = parsed_url.netloc credentials = self._credentials.get(netloc, (None, None)) if credentials == (None, None): if "@" not in netloc: credentials = self._get_credentials_for_netloc_from_config( netloc) else: # Split from the right because that's how urllib.parse.urlsplit() # behaves if more than one @ is present (which can be checked using # the password attribute of urlsplit()'s return value). auth, netloc = netloc.rsplit("@", 1) if ":" in auth: # Split from the left because that's how urllib.parse.urlsplit() # behaves if more than one : is present (which again can be checked # using the password attribute of the return value) credentials = auth.split(":", 1) else: credentials = auth, None credentials = tuple( None if x is None else urllib.parse.unquote(x) for x in credentials) if credentials[0] is not None or credentials[1] is not None: credentials = (credentials[0] or "", credentials[1] or "") self._credentials[netloc] = credentials return credentials[0], credentials[1] def _get_credentials_for_netloc_from_config( self, netloc): # type: (str) -> Tuple[Optional[str], Optional[str]] credentials = (None, None) for repository_name in self._config.get("repositories", []): repository_config = self._config.get( "repositories.{}".format(repository_name)) if not repository_config: continue url = repository_config.get("url") if not url: continue parsed_url = urllib.parse.urlsplit(url) if netloc == parsed_url.netloc: auth = self._password_manager.get_http_auth(repository_name) if auth is None: continue return auth["username"], auth["password"] return credentials
class Publisher: """ Registers and publishes packages to remote repositories. """ def __init__(self, poetry, io): self._poetry = poetry self._package = poetry.package self._io = io self._uploader = Uploader(poetry, io) self._password_manager = PasswordManager(poetry.config) @property def files(self): return self._uploader.files def publish(self, repository_name, username, password, cert=None, client_cert=None): if repository_name: self._io.write_line("Publishing <c1>{}</c1> (<b>{}</b>) " "to <info>{}</info>".format( self._package.pretty_name, self._package.pretty_version, repository_name, )) else: self._io.write_line("Publishing <c1>{}</c1> (<b>{}</b>) " "to <info>PyPI</info>".format( self._package.pretty_name, self._package.pretty_version)) if not repository_name: url = "https://upload.pypi.org/legacy/" repository_name = "pypi" else: # Retrieving config information repository = self._poetry.config.get( "repositories.{}".format(repository_name)) if repository is None: raise RuntimeError( "Repository {} is not defined".format(repository_name)) url = repository["url"] if not (username and password): # Check if we have a token first token = self._password_manager.get_pypi_token(repository_name) if token: logger.debug( "Found an API token for {}.".format(repository_name)) username = "******" password = token else: auth = self._password_manager.get_http_auth(repository_name) if auth: logger.debug( "Found authentication information for {}.".format( repository_name)) username = auth["username"] password = auth["password"] resolved_client_cert = client_cert or get_client_cert( self._poetry.config, repository_name) # Requesting missing credentials but only if there is not a client cert defined. if not resolved_client_cert: if username is None: username = self._io.ask("Username:"******"Password:") self._uploader.auth(username, password) return self._uploader.upload( url, cert=cert or get_cert(self._poetry.config, repository_name), client_cert=resolved_client_cert, )
class Publisher: """ Registers and publishes packages to remote repositories. """ def __init__(self, poetry: "Poetry", io: Union["BufferedIO", "ConsoleIO"]) -> None: self._poetry = poetry self._package = poetry.package self._io = io self._uploader = Uploader(poetry, io) self._password_manager = PasswordManager(poetry.config) @property def files(self) -> List[Path]: return self._uploader.files def publish( self, repository_name: Optional[str], username: Optional[str], password: Optional[str], cert: Optional[Path] = None, client_cert: Optional[Path] = None, dry_run: Optional[bool] = False, ) -> None: if not repository_name: url = "https://upload.pypi.org/legacy/" repository_name = "pypi" else: # Retrieving config information url = self._poetry.config.get( "repositories.{}.url".format(repository_name)) if url is None: raise RuntimeError( "Repository {} is not defined".format(repository_name)) if not (username and password): # Check if we have a token first token = self._password_manager.get_pypi_token(repository_name) if token: logger.debug( "Found an API token for {}.".format(repository_name)) username = "******" password = token else: auth = self._password_manager.get_http_auth(repository_name) if auth: logger.debug( "Found authentication information for {}.".format( repository_name)) username = auth["username"] password = auth["password"] resolved_client_cert = client_cert or get_client_cert( self._poetry.config, repository_name) # Requesting missing credentials but only if there is not a client cert defined. if not resolved_client_cert: if username is None: username = self._io.ask("Username:"******"Password:"******"Publishing <c1>{}</c1> (<c2>{}</c2>) " "to <info>{}</info>".format( self._package.pretty_name, self._package.pretty_version, "PyPI" if repository_name == "pypi" else repository_name, )) self._uploader.upload( url, cert=cert or get_cert(self._poetry.config, repository_name), client_cert=resolved_client_cert, dry_run=dry_run, )
class Authenticator: def __init__(self, config: "Config", io: Optional["IO"] = None) -> None: self._config = config self._io = io self._session = None self._credentials = {} self._password_manager = PasswordManager(self._config) def _log(self, message: str, level: str = "debug") -> None: if self._io is not None: self._io.write_line(f"<{level}>{message}</{level}>") else: getattr(logger, level, logger.debug)(message) @property def session(self) -> requests.Session: if self._session is None: self._session = requests.Session() return self._session def __del__(self) -> None: if self._session is not None: self._session.close() def request(self, method: str, url: str, **kwargs: Any) -> requests.Response: request = requests.Request(method, url) username, password = self.get_credentials_for_url(url) if username is not None and password is not None: request = requests.auth.HTTPBasicAuth(username, password)(request) session = self.session prepared_request = session.prepare_request(request) proxies = kwargs.get("proxies", {}) stream = kwargs.get("stream") verify = kwargs.get("verify") cert = kwargs.get("cert") settings = session.merge_environment_settings(prepared_request.url, proxies, stream, verify, cert) # Send the request. send_kwargs = { "timeout": kwargs.get("timeout"), "allow_redirects": kwargs.get("allow_redirects", True), } send_kwargs.update(settings) attempt = 0 while True: is_last_attempt = attempt >= 5 try: resp = session.send(prepared_request, **send_kwargs) except (requests.exceptions.ConnectionError, OSError) as e: if is_last_attempt: raise e else: if resp.status_code not in [502, 503, 504] or is_last_attempt: resp.raise_for_status() return resp if not is_last_attempt: attempt += 1 delay = 0.5 * attempt self._log(f"Retrying HTTP request in {delay} seconds.", level="debug") time.sleep(delay) continue # this should never really be hit under any sane circumstance raise PoetryException("Failed HTTP {} request", method.upper()) def get_credentials_for_url( self, url: str) -> Tuple[Optional[str], Optional[str]]: parsed_url = urllib.parse.urlsplit(url) netloc = parsed_url.netloc credentials = self._credentials.get(netloc, (None, None)) if credentials == (None, None): if "@" not in netloc: credentials = self._get_credentials_for_netloc(netloc) else: # Split from the right because that's how urllib.parse.urlsplit() # behaves if more than one @ is present (which can be checked using # the password attribute of urlsplit()'s return value). auth, netloc = netloc.rsplit("@", 1) # Split from the left because that's how urllib.parse.urlsplit() # behaves if more than one : is present (which again can be checked # using the password attribute of the return value) credentials = auth.split(":", 1) if ":" in auth else (auth, None) credentials = tuple( None if x is None else urllib.parse.unquote(x) for x in credentials) if credentials[0] is not None or credentials[1] is not None: credentials = (credentials[0] or "", credentials[1] or "") self._credentials[netloc] = credentials return credentials[0], credentials[1] def get_pypi_token(self, name: str) -> str: return self._password_manager.get_pypi_token(name) def get_http_auth(self, name: str) -> Optional[Dict[str, str]]: return self._get_http_auth(name, None) def _get_http_auth(self, name: str, netloc: Optional[str]) -> Optional[Dict[str, str]]: if name == "pypi": url = "https://upload.pypi.org/legacy/" else: url = self._config.get(f"repositories.{name}.url") if not url: return None parsed_url = urllib.parse.urlsplit(url) if netloc is None or netloc == parsed_url.netloc: auth = self._password_manager.get_http_auth(name) if auth is None or auth["password"] is None: username = auth["username"] if auth else None auth = self._get_credentials_for_netloc_from_keyring( url, parsed_url.netloc, username) return auth def _get_credentials_for_netloc( self, netloc: str) -> Tuple[Optional[str], Optional[str]]: for repository_name in self._config.get("repositories", []): auth = self._get_http_auth(repository_name, netloc) if auth is None: continue return auth["username"], auth["password"] return None, None def _get_credentials_for_netloc_from_keyring( self, url: str, netloc: str, username: Optional[str]) -> Optional[Dict[str, str]]: import keyring cred = keyring.get_credential(url, username) if cred is not None: return { "username": cred.username, "password": cred.password, } cred = keyring.get_credential(netloc, username) if cred is not None: return { "username": cred.username, "password": cred.password, } if username: return { "username": username, "password": None, } return None
class Authenticator(object): def __init__(self, config, io): # type: (Config, IO) -> None self._config = config self._io = io self._session = None self._credentials = {} self._password_manager = PasswordManager(self._config) @property def session(self): # type: () -> Session from requests import Session # noqa if self._session is None: self._session = Session() return self._session def request(self, method, url, **kwargs): # type: (str, str, Any) -> Response from requests import Request # noqa from requests.auth import HTTPBasicAuth request = Request(method, url) username, password = self._get_credentials_for_url(url) if username is not None and password is not None: request = HTTPBasicAuth(username, password)(request) session = self.session prepared_request = session.prepare_request(request) proxies = kwargs.get("proxies", {}) stream = kwargs.get("stream") verify = kwargs.get("verify") cert = kwargs.get("cert") settings = session.merge_environment_settings(prepared_request.url, proxies, stream, verify, cert) # Send the request. send_kwargs = { "timeout": kwargs.get("timeout"), "allow_redirects": kwargs.get("allow_redirects", True), } send_kwargs.update(settings) resp = session.send(prepared_request, **send_kwargs) resp.raise_for_status() return resp def _get_credentials_for_url( self, url): # type: (str) -> Tuple[Optional[str], Optional[str]] parsed_url = urlparse.urlsplit(url) netloc = parsed_url.netloc credentials = self._credentials.get(netloc, (None, None)) if credentials == (None, None): if "@" not in netloc: credentials = self._get_credentials_for_netloc_from_config( netloc) else: # Split from the right because that's how urllib.parse.urlsplit() # behaves if more than one @ is present (which can be checked using # the password attribute of urlsplit()'s return value). auth, netloc = netloc.rsplit("@", 1) if ":" in auth: # Split from the left because that's how urllib.parse.urlsplit() # behaves if more than one : is present (which again can be checked # using the password attribute of the return value) credentials = auth.split(":", 1) else: credentials = auth, None credentials = tuple(None if x is None else urlparse.unquote(x) for x in credentials) if credentials[0] is not None or credentials[1] is not None: credentials = (credentials[0] or "", credentials[1] or "") self._credentials[netloc] = credentials return credentials[0], credentials[1] def _get_credentials_for_netloc_from_config( self, netloc): # type: (str) -> Tuple[Optional[str], Optional[str]] credentials = (None, None) for repository_name in self._config.get("http-basic", {}): repository_config = self._config.get( "repositories.{}".format(repository_name)) if not repository_config: continue url = repository_config.get("url") if not url: continue parsed_url = urlparse.urlsplit(url) if netloc == parsed_url.netloc: auth = self._password_manager.get_http_auth(repository_name) if auth is None: continue return auth["username"], auth["password"] return credentials