Example #1
0
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"]
Example #2
0
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"]
Example #3
0
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"]
Example #4
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
Example #5
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
Example #6
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,
        )
Example #7
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,
        )
Example #8
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
Example #9
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