Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ repos:
rev: 19.10b0
hooks:
- id: black
additional_dependencies:
- click >=6.5,<8.1 # https://github.com/pallets/click/issues/2225

- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
Expand Down
56 changes: 53 additions & 3 deletions poetry/installation/authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,22 @@

from poetry.exceptions import PoetryException
from poetry.utils._compat import urlparse
from poetry.utils.helpers import get_cert
from poetry.utils.helpers import get_client_cert
from poetry.utils.password_manager import PasswordManager


if TYPE_CHECKING:
from typing import Any
from typing import Dict
from typing import Iterator
from typing import Optional
from typing import Tuple

from clikit.api.io import IO

from poetry.config.config import Config
from poetry.utils._compat import Path


logger = logging.getLogger()
Expand All @@ -29,7 +34,9 @@ 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._certs = {}
self._password_manager = PasswordManager(self._config)

def _log(self, message, level="debug"): # type: (str, str) -> None
Expand All @@ -44,7 +51,14 @@ def _log(self, message, level="debug"): # type: (str, str) -> None

@property
def session(self): # type: () -> requests.Session
return requests.Session()
if self._session is None:
self._session = requests.Session()

return self._session

def __del__(self): # type: () -> None
if self._session is not None:
self._session.close()

def request(
self, method, url, **kwargs
Expand All @@ -60,8 +74,16 @@ def request(

proxies = kwargs.get("proxies", {})
stream = kwargs.get("stream")
verify = kwargs.get("verify")
cert = kwargs.get("cert")

certs = self.get_certs_for_url(url)
verify = kwargs.get("verify") or certs.get("verify")
cert = kwargs.get("cert") or certs.get("cert")

if cert is not None:
cert = str(cert)

if verify is not None:
verify = str(verify)

settings = session.merge_environment_settings(
prepared_request.url, proxies, stream, verify, cert
Expand Down Expand Up @@ -163,3 +185,31 @@ def _get_credentials_for_netloc_from_config(
return auth["username"], auth["password"]

return credentials

def get_certs_for_url(self, url): # type: (str) -> Dict[str, Optional[Path]]
parsed_url = urlparse.urlsplit(url)

netloc = parsed_url.netloc

return self._certs.setdefault(
netloc, self._get_certs_for_netloc_from_config(netloc),
)

def _get_repository_netlocs(self,): # type: () -> Iterator[Tuple[str, str]]
for repository_name in self._config.get("repositories", []):
url = self._config.get("repositories.{}.url".format(repository_name))
parsed_url = urlparse.urlsplit(url)
yield repository_name, parsed_url.netloc

def _get_certs_for_netloc_from_config(
self, netloc
): # type: (str) -> Dict[str, Optional[Path]]
certs = {"cert": None, "verify": None}

for (repository_name, repository_netloc) in self._get_repository_netlocs():
if netloc == repository_netloc:
certs["cert"] = get_client_cert(self._config, repository_name)
certs["verify"] = get_cert(self._config, repository_name)
break

return certs
36 changes: 36 additions & 0 deletions tests/installation/test_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from poetry.installation.authenticator import Authenticator
from poetry.io.null_io import NullIO
from poetry.utils._compat import Path


@pytest.fixture()
Expand Down Expand Up @@ -199,3 +200,38 @@ def test_authenticator_uses_env_provided_credentials(
request = http.last_request()

assert "Basic YmFyOmJheg==" == request.headers["Authorization"]


@pytest.mark.parametrize(
"cert,client_cert",
[
(None, None),
(None, "path/to/provided/client-cert"),
("/path/to/provided/cert", None),
("/path/to/provided/cert", "path/to/provided/client-cert"),
],
)
def test_authenticator_uses_certs_from_config_if_not_provided(
config, mock_remote, http, mocker, cert, client_cert,
):
configured_cert = "/path/to/cert"
configured_client_cert = "/path/to/client-cert"
config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar/simple/"}},
"http-basic": {"foo": {"username": "bar", "password": "baz"}},
"certificates": {
"foo": {"cert": configured_cert, "client-cert": configured_client_cert}
},
}
)

authenticator = Authenticator(config, NullIO())
session_send = mocker.patch.object(authenticator.session, "send")
authenticator.request(
"get", "https://foo.bar/files/foo-0.1.0.tar.gz", verify=cert, cert=client_cert,
)
kwargs = session_send.call_args[1]

assert Path(kwargs["verify"]) == Path(cert or configured_cert)
assert Path(kwargs["cert"]) == Path(client_cert or configured_client_cert)