Browse Source

client: fix basic auth with empty password

When authenticating using basic auth and only providing username (no
password) the default value of `None` was cast to string, resulting
in an attempt to authenticate using `<username>:None` instead of
providing an empty password.
This broke authentication when providing an auth token as username.
See https://github.com/iterative/dvc/issues/7898#issuecomment-1157564615
Daniele Trifirò 2 years ago
parent
commit
f37af59c5d
2 changed files with 28 additions and 1 deletions
  1. 1 1
      dulwich/client.py
  2. 27 0
      dulwich/tests/test_client.py

+ 1 - 1
dulwich/client.py

@@ -2181,7 +2181,7 @@ class Urllib3HttpGitClient(AbstractHttpGitClient):
         if username is not None:
             # No escaping needed: ":" is not allowed in username:
             # https://tools.ietf.org/html/rfc2617#section-2
-            credentials = "%s:%s" % (username, password)
+            credentials = f"{username}:{password or ''}"
             import urllib3.util
 
             basic_auth = urllib3.util.make_headers(basic_auth=credentials)

+ 27 - 0
dulwich/tests/test_client.py

@@ -1021,6 +1021,19 @@ class HttpGitClientTests(TestCase):
         expected_basic_auth = "Basic %s" % b64_credentials.decode("latin1")
         self.assertEqual(basic_auth, expected_basic_auth)
 
+    def test_init_username_set_no_password(self):
+        url = "https://github.com/jelmer/dulwich"
+
+        c = HttpGitClient(url, config=None, username="user")
+        self.assertEqual("user", c._username)
+        self.assertIs(c._password, None)
+
+        basic_auth = c.pool_manager.headers["authorization"]
+        auth_string = b"user:"
+        b64_credentials = base64.b64encode(auth_string)
+        expected_basic_auth = f"Basic {b64_credentials.decode('ascii')}"
+        self.assertEqual(basic_auth, expected_basic_auth)
+
     def test_init_no_username_passwd(self):
         url = "https://github.com/jelmer/dulwich"
 
@@ -1029,6 +1042,20 @@ class HttpGitClientTests(TestCase):
         self.assertIs(None, c._password)
         self.assertNotIn("authorization", c.pool_manager.headers)
 
+    def test_from_parsedurl_username_only(self):
+        username = "user"
+        url = f"https://{username}@github.com/jelmer/dulwich"
+
+        c = HttpGitClient.from_parsedurl(urlparse(url))
+        self.assertEqual(c._username, username)
+        self.assertEqual(c._password, None)
+
+        basic_auth = c.pool_manager.headers["authorization"]
+        auth_string = username.encode('ascii') + b":"
+        b64_credentials = base64.b64encode(auth_string)
+        expected_basic_auth = f"Basic {b64_credentials.decode('ascii')}"
+        self.assertEqual(basic_auth, expected_basic_auth)
+
     def test_from_parsedurl_on_url_with_quoted_credentials(self):
         original_username = "john|the|first"
         quoted_username = urlquote(original_username)