Skip to content

Commit 0c22ca5

Browse files
committed
Ignore http credentials with empty usernames
See-also: jaraco/keyring#668 jaraco/keyring#687
1 parent a923878 commit 0c22ca5

File tree

5 files changed

+36
-42
lines changed

5 files changed

+36
-42
lines changed

docs/repositories.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,17 @@ You can prevent this by adding double dashes to prevent any following argument f
511511
poetry config -- http-basic.pypi myUsername -myPasswordStartingWithDash
512512
```
513513

514+
{{% note %}}
515+
In some cases like that of [Gemfury](https://gemfury.com/help/errors/repo-url-password/) repositories, it might be
516+
required to set an empty password. This is supported by Poetry.
517+
518+
```bash
519+
poetry config http-basic.foo <TOKEN> ""
520+
```
521+
522+
**Note:** Usernames cannot be empty. Attempting to use an empty username can result in an unpredictable failure.
523+
{{% /note %}}
524+
514525
## Certificates
515526

516527
### Custom certificate authority and mutual TLS authentication

src/poetry/utils/authenticator.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ def get_http_credentials(
8181
self, password_manager: PasswordManager
8282
) -> HTTPAuthCredential:
8383
# try with the repository name via the password manager
84-
credential = HTTPAuthCredential(
85-
**(password_manager.get_http_auth(self.name) or {})
86-
)
84+
credential = password_manager.get_http_auth(self.name)
8785

8886
if credential.password is not None:
8987
return credential

src/poetry/utils/password_manager.py

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -192,29 +192,17 @@ def delete_pypi_token(self, repo_name: str) -> None:
192192

193193
self.keyring.delete_password(repo_name, "__token__")
194194

195-
def get_http_auth(self, repo_name: str) -> dict[str, str | None] | None:
195+
def get_http_auth(self, repo_name: str) -> HTTPAuthCredential:
196196
username = self._config.get(f"http-basic.{repo_name}.username")
197197
password = self._config.get(f"http-basic.{repo_name}.password")
198198

199-
# we only return None if both values are None or ""
200-
# password can be None at this stage with the username ""
201-
if (username is password is None) or (username == password == ""):
202-
return None
203-
204-
if not password:
205-
if self.use_keyring:
206-
password = self.keyring.get_password(repo_name, username)
207-
elif not username:
208-
# at this tage if username is "" or None, auth is invalid
209-
return None
199+
if not username:
200+
return HTTPAuthCredential()
210201

211-
if not username and not password:
212-
return None
202+
if password is None and self.use_keyring:
203+
password = self.keyring.get_password(repo_name, username)
213204

214-
return {
215-
"username": username or "",
216-
"password": password or "",
217-
}
205+
return HTTPAuthCredential(username=username, password=password)
218206

219207
def set_http_password(self, repo_name: str, username: str, password: str) -> None:
220208
auth = {"username": username}
@@ -229,15 +217,12 @@ def set_http_password(self, repo_name: str, username: str, password: str) -> Non
229217

230218
def delete_http_password(self, repo_name: str) -> None:
231219
auth = self.get_http_auth(repo_name)
232-
if not auth:
233-
return
234220

235-
username = auth.get("username")
236-
if username is None:
221+
if auth.username is None:
237222
return
238223

239224
with suppress(PoetryKeyringError):
240-
self.keyring.delete_password(repo_name, username)
225+
self.keyring.delete_password(repo_name, auth.username)
241226

242227
self._config.auth_config_source.remove_property(f"http-basic.{repo_name}")
243228

@@ -246,5 +231,5 @@ def get_credential(
246231
) -> HTTPAuthCredential:
247232
if self.use_keyring:
248233
return self.keyring.get_credential(*names, username=username)
249-
else:
250-
return HTTPAuthCredential(username=username, password=None)
234+
235+
return HTTPAuthCredential(username=username, password=None)

tests/utils/test_authenticator.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def test_authenticator_uses_empty_strings_as_default_password(
153153
assert request.headers["Authorization"] == f"Basic {basic_auth}"
154154

155155

156-
def test_authenticator_uses_empty_strings_as_default_username(
156+
def test_authenticator_ignores_empty_strings_as_default_username(
157157
config: Config,
158158
mock_remote: None,
159159
repo: dict[str, dict[str, str]],
@@ -170,8 +170,7 @@ def test_authenticator_uses_empty_strings_as_default_username(
170170
authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz")
171171

172172
request = http.last_request()
173-
basic_auth = base64.b64encode(b":bar").decode()
174-
assert request.headers["Authorization"] == f"Basic {basic_auth}"
173+
assert request.headers["Authorization"] is None
175174

176175

177176
def test_authenticator_falls_back_to_keyring_url(

tests/utils/test_password_manager.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import pytest
1010

11+
from poetry.utils.password_manager import HTTPAuthCredential
1112
from poetry.utils.password_manager import PasswordManager
1213
from poetry.utils.password_manager import PoetryKeyring
1314
from poetry.utils.password_manager import PoetryKeyringError
@@ -40,7 +41,7 @@ def test_set_http_password(
4041
("username", "password", "is_valid"),
4142
[
4243
("bar", "baz", True),
43-
("", "baz", True),
44+
("", "baz", False),
4445
("bar", "", True),
4546
("", "", False),
4647
],
@@ -62,10 +63,10 @@ def test_get_http_auth(
6263

6364
if is_valid:
6465
assert auth is not None
65-
assert auth["username"] == username
66-
assert auth["password"] == password
66+
assert auth.username == username
67+
assert auth.password == password
6768
else:
68-
assert auth is None
69+
assert auth.username is auth.password is None
6970

7071

7172
def test_delete_http_password(
@@ -134,7 +135,7 @@ def test_set_http_password_with_unavailable_backend(
134135
("username", "password", "is_valid"),
135136
[
136137
("bar", "baz", True),
137-
("", "baz", True),
138+
("", "baz", False),
138139
("bar", "", True),
139140
("", "", False),
140141
],
@@ -156,10 +157,10 @@ def test_get_http_auth_with_unavailable_backend(
156157

157158
if is_valid:
158159
assert auth is not None
159-
assert auth["username"] == username
160-
assert auth["password"] == password
160+
assert auth.username == username
161+
assert auth.password == password
161162
else:
162-
assert auth is None
163+
assert auth.username is auth.password is None
163164

164165

165166
def test_delete_http_password_with_unavailable_backend(
@@ -304,7 +305,7 @@ def test_get_http_auth_from_environment_variables(
304305
manager = PasswordManager(config)
305306

306307
auth = manager.get_http_auth("foo")
307-
assert auth == {"username": "bar", "password": "baz"}
308+
assert auth == HTTPAuthCredential(username="bar", password="baz")
308309

309310

310311
def test_get_http_auth_does_not_call_keyring_when_credentials_in_environment_variables(
@@ -317,7 +318,7 @@ def test_get_http_auth_does_not_call_keyring_when_credentials_in_environment_var
317318
manager.keyring = MagicMock()
318319

319320
auth = manager.get_http_auth("foo")
320-
assert auth == {"username": "bar", "password": "baz"}
321+
assert auth == HTTPAuthCredential(username="bar", password="baz")
321322
manager.keyring.get_password.assert_not_called()
322323

323324

@@ -335,7 +336,7 @@ def test_get_http_auth_does_not_call_keyring_when_password_in_environment_variab
335336
manager.keyring = MagicMock()
336337

337338
auth = manager.get_http_auth("foo")
338-
assert auth == {"username": "bar", "password": "baz"}
339+
assert auth == HTTPAuthCredential(username="bar", password="baz")
339340
manager.keyring.get_password.assert_not_called()
340341

341342

0 commit comments

Comments
 (0)