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
Exemple #2
0
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"]
Exemple #6
0
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"]
Exemple #8
0
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"
Exemple #9
0
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"]
Exemple #12
0
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
Exemple #13
0
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
Exemple #14
0
 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)
Exemple #15
0
 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)
Exemple #16
0
    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
Exemple #17
0
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
Exemple #18
0
    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")
Exemple #19
0
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()
Exemple #20
0
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")
Exemple #22
0
 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)
Exemple #23
0
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")
Exemple #24
0
 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)
Exemple #25
0
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,
        )
Exemple #26
0
 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)
Exemple #27
0
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,
        )
Exemple #28
0
 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")
Exemple #30
0
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