Repository: pypa/pip

Issue:
Trusted Hosts Aren't Cached
**Environment**

* pip version: 20.0.2
* Python version: 3.7.3
* OS: CentOS Linux release 7.4.1708
* Virtualenv version: 16.6.1 (ran as "virtualenv --python=3.7 build/python_env")

**Description**
If a host is added as a trusted-host, it won't use the cache.

**Expected behavior**
trusted-hosts use the cache.
<!-- A clear and concise description of what you expected to happen. -->

**How to Reproduce**
<!-- Describe the steps to reproduce this bug. -->

1. pip config --site set global.index-url https://example.com/api/pypi/pypi/simple/
2. pip config --site set global.trusted-host example.com
3. pip install -r pip_requirements.txt

**Output**
Line from broken output:
```none
Collecting requests==2.23.0
  Downloading https://artifactory.rnclab.us.alcatel-lucent.com/artifactory/api/pypi/pypi/packages/1a/70/1935c770cb3be6e3a8b78ced23d7e0f3b187f5cbfab4749523ed65d7c9b1/requests-2.23.0-py2.py3-none-any.whl (58 kB)
     |████████████████████████████████| 58 kB 3.0 MB/s
```

Line from non-broken output (not configuring trusted-host):
```none
Collecting requests==2.23.0
  Using cached https://example.com/artifactory/api/pypi/pypi/packages/1a/70/1935c770cb3be6e3a8b78ced23d7e0f3b187f5cbfab4749523ed65d7c9b1/requests-2.23.0-py2.py3-none-any.whl (58 kB)
```



Discussion:
As you can see in commit 5bab65d1, a change was introduced that did not cache responses for any "unsafe" origin, such as http, or invalid https, even with the "trusted-host" option.

I also encounter this behavior as in my workplace I must use "trusted-host" as well, and it is pretty annoying that it does not cache any responses at all, so I have 2 options to offer:
1) Change this behavior to allow caching from any "trusted-host" source. Makes sense as you specifically said the host should be trusted. Maybe refrase the "trusted-host" docs to specify that. This option however changes default behavior and more drastic.
2) New command line option called `cache-trusted-host` that will do just that, then you can specify this option in pip config etc...

Out of these two option I prefer the first, but I think we should have a discussion about it.
In any way this is not a lot of diff, I can open a PR if needed :)

Resolving patch:
diff --git a/src/pip/_internal/network/session.py b/src/pip/_internal/network/session.py
--- a/src/pip/_internal/network/session.py
+++ b/src/pip/_internal/network/session.py
@@ -217,6 +217,14 @@ def cert_verify(self, conn, url, verify, cert):
         )
 
 
+class InsecureCacheControlAdapter(CacheControlAdapter):
+
+    def cert_verify(self, conn, url, verify, cert):
+        super(InsecureCacheControlAdapter, self).cert_verify(
+            conn=conn, url=url, verify=False, cert=cert
+        )
+
+
 class PipSession(requests.Session):
 
     timeout = None  # type: Optional[int]
@@ -263,8 +271,16 @@ def __init__(self, *args, **kwargs):
             backoff_factor=0.25,
         )
 
-        # We want to _only_ cache responses on securely fetched origins. We do
-        # this because we can't validate the response of an insecurely fetched
+        # Our Insecure HTTPAdapter disables HTTPS validation. It does not
+        # support caching so we'll use it for all http:// URLs.
+        # If caching is disabled, we will also use it for
+        # https:// hosts that we've marked as ignoring
+        # TLS errors for (trusted-hosts).
+        insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
+
+        # We want to _only_ cache responses on securely fetched origins or when
+        # the host is specified as trusted. We do this because
+        # we can't validate the response of an insecurely/untrusted fetched
         # origin, and we don't want someone to be able to poison the cache and
         # require manual eviction from the cache to fix it.
         if cache:
@@ -272,16 +288,13 @@ def __init__(self, *args, **kwargs):
                 cache=SafeFileCache(cache),
                 max_retries=retries,
             )
+            self._trusted_host_adapter = InsecureCacheControlAdapter(
+                cache=SafeFileCache(cache),
+                max_retries=retries,
+            )
         else:
             secure_adapter = HTTPAdapter(max_retries=retries)
-
-        # Our Insecure HTTPAdapter disables HTTPS validation. It does not
-        # support caching (see above) so we'll use it for all http:// URLs as
-        # well as any https:// host that we've marked as ignoring TLS errors
-        # for.
-        insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
-        # Save this for later use in add_insecure_host().
-        self._insecure_adapter = insecure_adapter
+            self._trusted_host_adapter = insecure_adapter
 
         self.mount("https://", secure_adapter)
         self.mount("http://", insecure_adapter)
@@ -310,12 +323,15 @@ def add_trusted_host(self, host, source=None, suppress_logging=False):
         if host_port not in self.pip_trusted_origins:
             self.pip_trusted_origins.append(host_port)
 
-        self.mount(build_url_from_netloc(host) + '/', self._insecure_adapter)
+        self.mount(
+            build_url_from_netloc(host) + '/',
+            self._trusted_host_adapter
+        )
         if not host_port[1]:
             # Mount wildcard ports for the same host.
             self.mount(
                 build_url_from_netloc(host) + ':',
-                self._insecure_adapter
+                self._trusted_host_adapter
             )
 
     def iter_secure_origins(self):


Test patch:
diff --git a/tests/unit/test_network_session.py b/tests/unit/test_network_session.py
--- a/tests/unit/test_network_session.py
+++ b/tests/unit/test_network_session.py
@@ -72,7 +72,7 @@ def test_http_cache_is_not_enabled(self, tmpdir):
 
         assert not hasattr(session.adapters["http://"], "cache")
 
-    def test_insecure_host_adapter(self, tmpdir):
+    def test_trusted_hosts_adapter(self, tmpdir):
         session = PipSession(
             cache=tmpdir.joinpath("test-cache"),
             trusted_hosts=["example.com"],
@@ -81,14 +81,14 @@ def test_insecure_host_adapter(self, tmpdir):
         assert "https://example.com/" in session.adapters
         # Check that the "port wildcard" is present.
         assert "https://example.com:" in session.adapters
-        # Check that the cache isn't enabled.
-        assert not hasattr(session.adapters["https://example.com/"], "cache")
+        # Check that the cache is enabled.
+        assert hasattr(session.adapters["https://example.com/"], "cache")
 
     def test_add_trusted_host(self):
         # Leave a gap to test how the ordering is affected.
         trusted_hosts = ['host1', 'host3']
         session = PipSession(trusted_hosts=trusted_hosts)
-        insecure_adapter = session._insecure_adapter
+        trusted_host_adapter = session._trusted_host_adapter
         prefix2 = 'https://host2/'
         prefix3 = 'https://host3/'
         prefix3_wildcard = 'https://host3:'
@@ -97,8 +97,8 @@ def test_add_trusted_host(self):
         assert session.pip_trusted_origins == [
             ('host1', None), ('host3', None)
         ]
-        assert session.adapters[prefix3] is insecure_adapter
-        assert session.adapters[prefix3_wildcard] is insecure_adapter
+        assert session.adapters[prefix3] is trusted_host_adapter
+        assert session.adapters[prefix3_wildcard] is trusted_host_adapter
 
         assert prefix2 not in session.adapters
 
@@ -108,8 +108,8 @@ def test_add_trusted_host(self):
             ('host1', None), ('host3', None), ('host2', None)
         ]
         # Check that prefix3 is still present.
-        assert session.adapters[prefix3] is insecure_adapter
-        assert session.adapters[prefix2] is insecure_adapter
+        assert session.adapters[prefix3] is trusted_host_adapter
+        assert session.adapters[prefix2] is trusted_host_adapter
 
         # Test that adding the same host doesn't create a duplicate.
         session.add_trusted_host('host3')
@@ -123,7 +123,7 @@ def test_add_trusted_host(self):
             ('host1', None), ('host3', None),
             ('host2', None), ('host4', 8080)
         ]
-        assert session.adapters[prefix4] is insecure_adapter
+        assert session.adapters[prefix4] is trusted_host_adapter
 
     def test_add_trusted_host__logging(self, caplog):
         """
diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py
--- a/tests/unit/test_req_file.py
+++ b/tests/unit/test_req_file.py
@@ -401,10 +401,13 @@ def test_set_finder_trusted_host(
             )
         assert list(finder.trusted_hosts) == ['host1', 'host2:8080']
         session = finder._link_collector.session
-        assert session.adapters['https://host1/'] is session._insecure_adapter
+        assert (
+            session.adapters['https://host1/']
+            is session._trusted_host_adapter
+        )
         assert (
             session.adapters['https://host2:8080/']
-            is session._insecure_adapter
+            is session._trusted_host_adapter
         )
 
         # Test the log message.