docker: fix authentication when using .netrc

From the requests documentation

> The netrc file overrides raw HTTP authentication headers set with headers=.

Which means that when the .netrc file contains the credentials for a docker
registry, it will override the bearer token authentication that the plugin
is setting using a raw "Authorization" header.

Instead, we create a new BearerAuth class and pass it to requests to use
for authenticating to the registry.
diff --git a/src/buildstream_plugins/sources/docker.py b/src/buildstream_plugins/sources/docker.py
index 45628fa..188e817 100644
--- a/src/buildstream_plugins/sources/docker.py
+++ b/src/buildstream_plugins/sources/docker.py
@@ -121,6 +121,27 @@
     return url
 
 
+# Handles authentication with a bearer token
+class BearerAuth(requests.auth.AuthBase):
+    def __init__(self, api_timeout=3):
+        self.token = None
+        self.api_timeout = api_timeout
+
+    def __call__(self, r):
+        if self.token:
+            r.headers["Authorization"] = "Bearer {}".format(self.token)
+        return r
+
+    def refresh_token(self, auth_challenge):
+        auth_vars = parse_bearer_authorization_challenge(auth_challenge)
+        # Respond to an Www-Authenticate challenge by requesting the necessary
+        # token from the 'realm' (endpoint) that we were given in the challenge.
+        request_url = "{realm}?service={service}&scope={scope}".format(**auth_vars)
+        response = requests.get(request_url, timeout=self.api_timeout)
+        response.raise_for_status()
+        self.token = response.json()["token"]
+
+
 # DockerManifestError
 #
 # Raised if something goes wrong while querying an image manifest from a remote
@@ -137,7 +158,7 @@
         self.endpoint = endpoint
         self.api_timeout = api_timeout
 
-        self.token = None
+        self.auth = BearerAuth(api_timeout)
 
     def _request(self, subpath, extra_headers=None, stream=False, _reauthorized=False):
         if not extra_headers:
@@ -146,32 +167,20 @@
         headers = {"content-type": "application/json"}
         headers.update(extra_headers)
 
-        if self.token:
-            headers["Authorization"] = "Bearer {}".format(self.token)
-
         url = urljoin(self.endpoint, "v2", subpath)
-        response = requests.get(url, headers=headers, stream=stream, timeout=self.api_timeout)
+        response = requests.get(url, headers=headers, stream=stream, timeout=self.api_timeout, auth=self.auth)
 
         if response.status_code == requests.codes["unauthorized"] and not _reauthorized:
             # This request requires (re)authorization. See:
             # https://docs.docker.com/registry/spec/auth/token/
             auth_challenge = response.headers["Www-Authenticate"]
-            auth_vars = parse_bearer_authorization_challenge(auth_challenge)
-            self._auth(auth_vars["realm"], auth_vars["service"], auth_vars["scope"])
+            self.auth.refresh_token(auth_challenge)
             return self._request(subpath, extra_headers=extra_headers, _reauthorized=True)
         else:
             response.raise_for_status()
 
             return response
 
-    def _auth(self, realm, service, scope):
-        # Respond to an Www-Authenticate challenge by requesting the necessary
-        # token from the 'realm' (endpoint) that we were given in the challenge.
-        request_url = "{}?service={}&scope={}".format(realm, service, scope)
-        response = requests.get(request_url, timeout=self.api_timeout)
-        response.raise_for_status()
-        self.token = response.json()["token"]
-
     # digest():
     #
     # Calculate a Docker-compatible digest of an arbitrary string of bytes.