From f60db376371672e6c1035adfd0f28fbc3bdfe4fb Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Sat, 25 Sep 2021 11:11:15 +0000 Subject: [PATCH 1/5] Show asterisks for password --- tests/test_repository.py | 4 +++- twine/repository.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_repository.py b/tests/test_repository.py index 0c0098c7..af8be7ec 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -352,7 +352,9 @@ def test_package_is_uploaded_incorrect_repo_url(): [ (None, None, ["username: ", "password: "]), ("", "", ["username: ", "password: "]), - ("username", "password", ["username: username", "password: "]), + ("username", "password", ["username: username", "password: ********"]), + # Ctrl-V in Windows Command Prompt; see https://bugs.python.org/issue37426 + ("username", "\x16", ["username: username", "password: *"]), ], ) def test_logs_username_and_password(username, password, messages, caplog): diff --git a/twine/repository.py b/twine/repository.py index a0164fa5..ea320dc4 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -65,7 +65,7 @@ def __init__( (username or "", password or "") if username or password else None ) logger.info(f"username: {username if username else ''}") - logger.info(f"password: <{'hidden' if password else 'empty'}>") + logger.info(f"password: {'*' * len(password) if password else ''}") self.session.headers["User-Agent"] = self._make_user_agent_string() for scheme in ("http://", "https://"): From cd2d5f5b4e8aaccfbeb6eeaa85f98c1b7a286af0 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Tue, 5 Oct 2021 06:16:20 -0400 Subject: [PATCH 2/5] Revert "Show asterisks for password" This reverts commit f60db376371672e6c1035adfd0f28fbc3bdfe4fb. --- tests/test_repository.py | 4 +--- twine/repository.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_repository.py b/tests/test_repository.py index af8be7ec..0c0098c7 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -352,9 +352,7 @@ def test_package_is_uploaded_incorrect_repo_url(): [ (None, None, ["username: ", "password: "]), ("", "", ["username: ", "password: "]), - ("username", "password", ["username: username", "password: ********"]), - # Ctrl-V in Windows Command Prompt; see https://bugs.python.org/issue37426 - ("username", "\x16", ["username: username", "password: *"]), + ("username", "password", ["username: username", "password: "]), ], ) def test_logs_username_and_password(username, password, messages, caplog): diff --git a/twine/repository.py b/twine/repository.py index ea320dc4..a0164fa5 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -65,7 +65,7 @@ def __init__( (username or "", password or "") if username or password else None ) logger.info(f"username: {username if username else ''}") - logger.info(f"password: {'*' * len(password) if password else ''}") + logger.info(f"password: <{'hidden' if password else 'empty'}>") self.session.headers["User-Agent"] = self._make_user_agent_string() for scheme in ("http://", "https://"): From b640db962cf7d4508cf30c2bdf6c4c43fb3e3bf4 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Tue, 5 Oct 2021 06:42:11 -0400 Subject: [PATCH 3/5] Show warnings for username and password --- tests/test_auth.py | 24 ++++++++++++++++++++++++ twine/utils.py | 23 ++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/test_auth.py b/tests/test_auth.py index c8e470d5..7e59b2b8 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,3 +1,4 @@ +import getpass import logging import pytest @@ -202,3 +203,26 @@ def test_logs_config_values(config, caplog): "username set from config file", "password set from config file", ] + + +@pytest.mark.parametrize( + "password, warning", + [ + ("", "Your password is empty"), + ("\x16", "Your password contains control characters"), + ("entered\x16pw", "Your password contains control characters"), + ], +) +def test_warns_for_empty_password( + password, + warning, + monkeypatch, + entered_username, + config, + caplog, +): + monkeypatch.setattr(getpass, "getpass", lambda prompt: password) + + assert auth.Resolver(config, auth.CredentialInput()).password == password + + assert caplog.messages[0].startswith(f" {warning}") diff --git a/twine/utils.py b/twine/utils.py index 0378f40e..bf96fc9f 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -18,6 +18,7 @@ import logging import os import os.path +import unicodedata from typing import Any, Callable, DefaultDict, Dict, Optional, Sequence, Union from urllib.parse import urlparse from urllib.parse import urlunparse @@ -237,11 +238,31 @@ def get_userpass_value( if cli_value is not None: logger.info(f"{key} set by command options") return cli_value + elif config.get(key) is not None: logger.info(f"{key} set from config file") return config[key] + elif prompt_strategy: - return prompt_strategy() + warning = "" + value = prompt_strategy() + + if not value: + warning = f"Your {key} is empty" + elif any(unicodedata.category(c).startswith("C") for c in value): + # See https://www.unicode.org/reports/tr44/#General_Category_Values + # Most common case is "\x16" when pasting in Windows Command Prompt + warning = f"Your {key} contains control characters" + + if warning: + logger.warning(f" {warning}. Did you enter it correctly?") + # TODO: Link to new entry in Twine docs + logger.warning( + " See https://pypi.org/help/#invalid-auth for more information." + ) + + return value + else: return None From 0b5b6252bff653334eb78fc45148d25fad668c55 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Sat, 9 Oct 2021 18:11:16 -0400 Subject: [PATCH 4/5] Add note to docs about entering credentials --- docs/index.rst | 14 ++++++++++++++ twine/utils.py | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 9b2ae695..ead6d7d8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -88,6 +88,20 @@ Using Twine 4. Done! +.. _entering-credentials: + +.. note:: + + Like many other command line tools, Twine does not show any characters when + you enter your password. + + If you're using Windows and trying to paste your username, password, or + token in the Command Prompt or PowerShell, ``Ctrl-V`` and ``Shift+Insert`` + won't work. Instead, you can use "Edit > Paste" from the window menu, or + enable "Use Ctrl+Shift+C/V as Copy/Paste" in "Properties". This is a + `known issue `_ with Python's + ``getpass`` module. + More documentation on using Twine to upload packages to PyPI is in the `Python Packaging User Guide`_. diff --git a/twine/utils.py b/twine/utils.py index bf96fc9f..c302e134 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -256,9 +256,9 @@ def get_userpass_value( if warning: logger.warning(f" {warning}. Did you enter it correctly?") - # TODO: Link to new entry in Twine docs logger.warning( - " See https://pypi.org/help/#invalid-auth for more information." + " See https://twine.readthedocs.io/#entering-credentials " + "for more information." ) return value From d1084901e50396ed270f5f2506eb354f2b37c8b5 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Sat, 9 Oct 2021 21:04:17 -0400 Subject: [PATCH 5/5] Add changelog entry --- changelog/815.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/815.feature.rst diff --git a/changelog/815.feature.rst b/changelog/815.feature.rst new file mode 100644 index 00000000..aa819df6 --- /dev/null +++ b/changelog/815.feature.rst @@ -0,0 +1 @@ +Show more helpful messages for invalid passwords.