def test_delete_http_password(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() manager.delete_http_password("foo") assert dummy_keyring.get_password("poetry-repository-foo", "bar") is None assert config.get("http-basic.foo") is None
def test_set_http_password_with_unavailable_backend(config: Config, with_fail_keyring: None): manager = PasswordManager(config) assert not manager.keyring.is_available() manager.set_http_password("foo", "bar", "baz") auth = config.get("http-basic.foo") assert auth["username"] == "bar" assert auth["password"] == "baz"
def test_delete_http_password_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() manager.delete_http_password("foo") assert config.get("http-basic.foo") is None
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_delete_pypi_token(config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend): dummy_keyring.set_password("poetry-repository-foo", "__token__", "baz") manager = PasswordManager(config) assert manager.keyring.is_available() manager.delete_pypi_token("foo") assert dummy_keyring.get_password("poetry-repository-foo", "__token__") is None
def test_set_http_password_with_unavailable_backend(config, mock_unavailable_backend): manager = PasswordManager(config) assert not manager.keyring.is_available() manager.set_http_password("foo", "bar", "baz") auth = config.get("http-basic.foo") assert "bar" == auth["username"] assert "baz" == auth["password"]
def test_set_pypi_token(config: "Config", with_simple_keyring: None, dummy_keyring: "DummyBackend"): manager = PasswordManager(config) assert manager.keyring.is_available() manager.set_pypi_token("foo", "baz") assert config.get("pypi-token.foo") is None assert dummy_keyring.get_password("poetry-repository-foo", "__token__") == "baz"
def test_set_http_password(config, mock_available_backend, backend): manager = PasswordManager(config) assert manager.keyring.is_available() manager.set_http_password("foo", "bar", "baz") assert "baz" == backend.get_password("poetry-repository-foo", "bar") auth = config.get("http-basic.foo") assert "bar" == auth["username"] assert "password" not in auth
def test_set_http_password(config, with_simple_keyring, dummy_keyring): manager = PasswordManager(config) assert manager.keyring.is_available() manager.set_http_password("foo", "bar", "baz") assert "baz" == dummy_keyring.get_password("poetry-repository-foo", "bar") auth = config.get("http-basic.foo") assert "bar" == auth["username"] assert "password" not in auth
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 test_delete_http_password(config, mock_available_backend, backend): backend.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() manager.delete_http_password("foo") assert backend.get_password("poetry-repository-foo", "bar") is None assert config.get("http-basic.foo") is None
def test_set_http_password(config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend): manager = PasswordManager(config) assert manager.keyring.is_available() manager.set_http_password("foo", "bar", "baz") assert dummy_keyring.get_password("poetry-repository-foo", "bar") == "baz" auth = config.get("http-basic.foo") assert auth["username"] == "bar" assert "password" not in auth
def __init__( self, config: Config | None = None, io: IO | None = None, cache_id: str | None = None, disable_cache: bool = False, ) -> None: self._config = config or Config.create() self._io = io self._sessions_for_netloc: dict[str, requests.Session] = {} self._credentials: dict[str, HTTPAuthCredential] = {} self._certs: dict[str, dict[str, Path | None]] = {} self._configured_repositories: dict[ str, AuthenticatorRepositoryConfig] | None = None self._password_manager = PasswordManager(self._config) self._cache_control = (FileCache( str(REPOSITORY_CACHE_DIR / (cache_id or "_default_cache") / "_http")) if not disable_cache else None)
def __init__( self, config: Config | None = None, io: IO | None = None, cache_id: str | None = None, disable_cache: bool = False, ) -> None: self._config = config or Config.create() self._io = io self._sessions_for_netloc: dict[str, requests.Session] = {} self._credentials: dict[str, HTTPAuthCredential] = {} self._certs: dict[str, RepositoryCertificateConfig] = {} self._configured_repositories: dict[ str, AuthenticatorRepositoryConfig] | None = None self._password_manager = PasswordManager(self._config) self._cache_control = (FileCache( str(self._config.repository_cache_directory / (cache_id or "_default_cache") / "_http")) if not disable_cache else None) self.get_repository_config_for_url = functools.lru_cache(maxsize=None)( self._get_repository_config_for_url)
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
def handle(self) -> int | None: from pathlib import Path from poetry.core.pyproject.exceptions import PyProjectException from poetry.core.toml.file import TOMLFile from poetry.config.file_config_source import FileConfigSource from poetry.factory import Factory from poetry.locations import CONFIG_DIR config = Factory.create_config(self.io) config_file = TOMLFile(Path(CONFIG_DIR) / "config.toml") try: local_config_file = TOMLFile(self.poetry.file.parent / "poetry.toml") if local_config_file.exists(): config.merge(local_config_file.read()) except (RuntimeError, PyProjectException): local_config_file = TOMLFile(Path.cwd() / "poetry.toml") if self.option("local"): config.set_config_source(FileConfigSource(local_config_file)) if not config_file.exists(): config_file.path.parent.mkdir(parents=True, exist_ok=True) config_file.touch(mode=0o0600) if self.option("list"): self._list_configuration(config.all(), config.raw()) return 0 setting_key = self.argument("key") if not setting_key: return 0 if self.argument("value") and self.option("unset"): raise RuntimeError( "You can not combine a setting value with --unset") # show the value if no value is provided if not self.argument("value") and not self.option("unset"): m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key")) value: str | dict[str, Any] if m: if not m.group(1): value = {} if config.get("repositories") is not None: value = config.get("repositories") else: repo = config.get(f"repositories.{m.group(1)}") if repo is None: raise ValueError( f"There is no {m.group(1)} repository defined") value = repo self.line(str(value)) else: if setting_key not in self.unique_config_values: raise ValueError(f"There is no {setting_key} setting.") value = config.get(setting_key) if not isinstance(value, str): value = json.dumps(value) self.line(value) return 0 values: list[str] = self.argument("value") unique_config_values = self.unique_config_values if setting_key in unique_config_values: if self.option("unset"): config.config_source.remove_property(setting_key) return None return self._handle_single_value( config.config_source, setting_key, unique_config_values[setting_key], values, ) # handle repositories m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key")) if m: if not m.group(1): raise ValueError( "You cannot remove the [repositories] section") if self.option("unset"): repo = config.get(f"repositories.{m.group(1)}") if repo is None: raise ValueError( f"There is no {m.group(1)} repository defined") config.config_source.remove_property( f"repositories.{m.group(1)}") return 0 if len(values) == 1: url = values[0] config.config_source.add_property( f"repositories.{m.group(1)}.url", url) return 0 raise ValueError( "You must pass the url. " "Example: poetry config repositories.foo https://bar.com") # handle auth m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key")) if m: from poetry.utils.password_manager import PasswordManager password_manager = PasswordManager(config) if self.option("unset"): if m.group(1) == "http-basic": password_manager.delete_http_password(m.group(2)) elif m.group(1) == "pypi-token": password_manager.delete_pypi_token(m.group(2)) return 0 if m.group(1) == "http-basic": if len(values) == 1: username = values[0] # Only username, so we prompt for password password = self.secret("Password:"******"Expected one or two arguments " f"(username, password), got {len(values)}") else: username = values[0] password = values[1] password_manager.set_http_password(m.group(2), username, password) elif m.group(1) == "pypi-token": if len(values) != 1: raise ValueError( f"Expected only one argument (token), got {len(values)}" ) token = values[0] password_manager.set_pypi_token(m.group(2), token) return 0 # handle certs m = re.match(r"(?:certificates)\.([^.]+)\.(cert|client-cert)", self.argument("key")) if m: if self.option("unset"): config.auth_config_source.remove_property( f"certificates.{m.group(1)}.{m.group(2)}") return 0 if len(values) == 1: config.auth_config_source.add_property( f"certificates.{m.group(1)}.{m.group(2)}", values[0]) else: raise ValueError("You must pass exactly 1 value") return 0 raise ValueError(f"Setting {self.argument('key')} does not exist")
class Authenticator: def __init__( self, config: Config | None = None, io: IO | None = None, cache_id: str | None = None, disable_cache: bool = False, ) -> None: self._config = config or Config.create() self._io = io self._sessions_for_netloc: dict[str, requests.Session] = {} self._credentials: dict[str, HTTPAuthCredential] = {} self._certs: dict[str, RepositoryCertificateConfig] = {} self._configured_repositories: dict[ str, AuthenticatorRepositoryConfig] | None = None self._password_manager = PasswordManager(self._config) self._cache_control = (FileCache( str(self._config.repository_cache_directory / (cache_id or "_default_cache") / "_http")) if not disable_cache else None) self.get_repository_config_for_url = functools.lru_cache(maxsize=None)( self._get_repository_config_for_url) @property def cache(self) -> FileCache | None: return self._cache_control @property def is_cached(self) -> bool: return self._cache_control is not None def create_session(self) -> requests.Session: session = requests.Session() if not self.is_cached: return session session = CacheControl(sess=session, cache=self._cache_control) return session def get_session(self, url: str | None = None) -> requests.Session: if not url: return self.create_session() parsed_url = urllib.parse.urlsplit(url) netloc = parsed_url.netloc if netloc not in self._sessions_for_netloc: logger.debug("Creating new session for %s", netloc) self._sessions_for_netloc[netloc] = self.create_session() return self._sessions_for_netloc[netloc] def close(self) -> None: for session in self._sessions_for_netloc.values(): if session is not None: with contextlib.suppress(AttributeError): session.close() def __del__(self) -> None: self.close() def delete_cache(self, url: str) -> None: if self.is_cached: self._cache_control.delete(key=url) def authenticated_url(self, url: str) -> str: parsed = urllib.parse.urlparse(url) credential = self.get_credentials_for_url(url) if credential.username is not None and credential.password is not None: username = urllib.parse.quote(credential.username, safe="") password = urllib.parse.quote(credential.password, safe="") return ( f"{parsed.scheme}://{username}:{password}@{parsed.netloc}{parsed.path}" ) return url def request(self, method: str, url: str, raise_for_status: bool = True, **kwargs: Any) -> requests.Response: request = requests.Request(method, url) credential = self.get_credentials_for_url(url) if credential.username is not None or credential.password is not None: request = requests.auth.HTTPBasicAuth(credential.username or "", credential.password or "")(request) session = self.get_session(url=url) prepared_request = session.prepare_request(request) proxies = kwargs.get("proxies", {}) stream = kwargs.get("stream") certs = self.get_certs_for_url(url) verify = kwargs.get("verify") or certs.cert or certs.verify cert = kwargs.get("cert") or certs.client_cert if cert is not None: cert = str(cert) verify = str(verify) if isinstance(verify, Path) else verify settings = session.merge_environment_settings( # type: ignore[no-untyped-call] prepared_request.url, proxies, stream, verify, cert) # Send the request. send_kwargs = { "timeout": kwargs.get("timeout", REQUESTS_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: if raise_for_status: resp.raise_for_status() return resp if not is_last_attempt: attempt += 1 delay = 0.5 * attempt logger.debug(f"Retrying HTTP request in {delay} seconds.") time.sleep(delay) continue # this should never really be hit under any sane circumstance raise PoetryException("Failed HTTP {} request", method.upper()) def get(self, url: str, **kwargs: Any) -> requests.Response: return self.request("get", url, **kwargs) def post(self, url: str, **kwargs: Any) -> requests.Response: return self.request("post", url, **kwargs) def _get_credentials_for_repository( self, repository: AuthenticatorRepositoryConfig, username: str | None = None) -> HTTPAuthCredential: # cache repository credentials by repository url to avoid multiple keyring # backend queries when packages are being downloaded from the same source key = f"{repository.url}#username={username or ''}" if key not in self._credentials: self._credentials[key] = repository.get_http_credentials( password_manager=self._password_manager, username=username) return self._credentials[key] def _get_credentials_for_url(self, url: str, exact_match: bool = False ) -> HTTPAuthCredential: repository = self.get_repository_config_for_url(url, exact_match) credential = (self._get_credentials_for_repository( repository=repository) if repository is not None else HTTPAuthCredential()) if credential.password is None: parsed_url = urllib.parse.urlsplit(url) netloc = parsed_url.netloc credential = self._password_manager.keyring.get_credential( url, netloc, username=credential.username) return HTTPAuthCredential(username=credential.username, password=credential.password) return credential def get_credentials_for_git_url(self, url: str) -> HTTPAuthCredential: parsed_url = urllib.parse.urlsplit(url) if parsed_url.scheme not in {"http", "https"}: return HTTPAuthCredential() key = f"git+{url}" if key not in self._credentials: self._credentials[key] = self._get_credentials_for_url(url, True) return self._credentials[key] def get_credentials_for_url(self, url: str) -> HTTPAuthCredential: parsed_url = urllib.parse.urlsplit(url) netloc = parsed_url.netloc if url not in self._credentials: if "@" not in netloc: # no credentials were provided in the url, try finding the # best repository configuration self._credentials[url] = self._get_credentials_for_url(url) 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) user, password = auth.split(":", 1) if ":" in auth else (auth, "") self._credentials[url] = HTTPAuthCredential( urllib.parse.unquote(user), urllib.parse.unquote(password), ) return self._credentials[url] def get_pypi_token(self, name: str) -> str | None: return self._password_manager.get_pypi_token(name) def get_http_auth( self, name: str, username: str | None = None) -> HTTPAuthCredential | None: if name == "pypi": repository = AuthenticatorRepositoryConfig( name, "https://upload.pypi.org/legacy/") else: if name not in self.configured_repositories: return None repository = self.configured_repositories[name] return self._get_credentials_for_repository(repository=repository, username=username) def get_certs_for_repository(self, name: str) -> RepositoryCertificateConfig: if name.lower() == "pypi" or name not in self.configured_repositories: return RepositoryCertificateConfig() return self.configured_repositories[name].certs(self._config) @property def configured_repositories( self) -> dict[str, AuthenticatorRepositoryConfig]: if self._configured_repositories is None: self._configured_repositories = {} for repository_name in self._config.get("repositories", []): url = self._config.get(f"repositories.{repository_name}.url") self._configured_repositories[ repository_name] = AuthenticatorRepositoryConfig( repository_name, url) return self._configured_repositories def reset_credentials_cache(self) -> None: self.get_repository_config_for_url.cache_clear() self._credentials = {} def add_repository(self, name: str, url: str) -> None: self.configured_repositories[name] = AuthenticatorRepositoryConfig( name, url) self.reset_credentials_cache() def get_certs_for_url(self, url: str) -> RepositoryCertificateConfig: if url not in self._certs: self._certs[url] = self._get_certs_for_url(url) return self._certs[url] def _get_repository_config_for_url( self, url: str, exact_match: bool = False) -> AuthenticatorRepositoryConfig | None: parsed_url = urllib.parse.urlsplit(url) candidates_netloc_only = [] candidates_path_match = [] for repository in self.configured_repositories.values(): if exact_match: if parsed_url.path == repository.path: return repository continue if repository.netloc == parsed_url.netloc: if parsed_url.path.startswith(repository.path) or commonprefix( (parsed_url.path, repository.path)): candidates_path_match.append(repository) continue candidates_netloc_only.append(repository) if candidates_path_match: candidates = candidates_path_match elif candidates_netloc_only: candidates = candidates_netloc_only else: return None if len(candidates) > 1: logger.debug( "Multiple source configurations found for %s - %s", parsed_url.netloc, ", ".join(c.name for c in candidates), ) # prefer the more specific path candidates.sort( key=lambda c: len(commonprefix([parsed_url.path, c.path])), reverse=True) return candidates[0] def _get_certs_for_url(self, url: str) -> RepositoryCertificateConfig: selected = self.get_repository_config_for_url(url) if selected: return selected.certs(config=self._config) return RepositoryCertificateConfig()
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
def test_get_pypi_token(config, with_simple_keyring, dummy_keyring): dummy_keyring.set_password("poetry-repository-foo", "__token__", "baz") manager = PasswordManager(config) assert manager.keyring.is_available() assert "baz" == manager.get_pypi_token("foo")
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)
def test_get_pypi_token(config, mock_available_backend, backend): backend.set_password("poetry-repository-foo", "__token__", "baz") manager = PasswordManager(config) assert manager.keyring.is_available() assert "baz" == manager.get_pypi_token("foo")
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)
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, )
def __init__(self, config: Config, io: IO | None = None) -> None: self._config = config self._io = io self._session = None self._credentials = {} self._password_manager = PasswordManager(self._config)
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, )
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 test_get_pypi_token_with_unavailable_backend(config, with_fail_keyring): config.auth_config_source.add_property("pypi-token.foo", "baz") manager = PasswordManager(config) assert not manager.keyring.is_available() assert "baz" == manager.get_pypi_token("foo")
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