diff --git a/ethstaker_deposit/deposit.py b/ethstaker_deposit/deposit.py index c4200ff5..3bd37082 100644 --- a/ethstaker_deposit/deposit.py +++ b/ethstaker_deposit/deposit.py @@ -31,7 +31,7 @@ def check_python_version() -> None: ''' if sys.version_info < (3, 9): click.pause(load_text(['err_python_version'])) - sys.exit() + sys.exit(78) def check_connectivity() -> None: diff --git a/ethstaker_deposit/intl/en/utils/validation.json b/ethstaker_deposit/intl/en/utils/validation.json index 8a58b94a..9559e3f6 100644 --- a/ethstaker_deposit/intl/en/utils/validation.json +++ b/ethstaker_deposit/intl/en/utils/validation.json @@ -3,7 +3,9 @@ "msg_deposit_verification": "Verifying your deposit_data-*.json file(s):\t" }, "validate_password_strength": { - "msg_password_length": "The password length should be at least 8. Please retype" + "msg_password_length": "The password length should be at least 8. Please retype.", + "msg_password_utf8_win32": "Your terminal is not UTF-8 encoded. To ensure the keystore file can be imported on Linux, the password should contain only English-language characters. Please retype.", + "msg_password_utf8": "Your terminal is not UTF-8 encoded. To ensure the keystore file can be imported on Linux, the password should contain only English-language characters. Please retype.\nAlternatively, you can quit this program and relaunch it from a UTF-8 encoded terminal." }, "validate_int_range": { "err_not_positive_integer": "That is not a positive integer. Please retype." @@ -14,7 +16,7 @@ "validate_withdrawal_address": { "err_invalid_ECDSA_hex_addr": "The given execution address is not in hexadecimal encoded form.", "err_invalid_ECDSA_hex_addr_checksum": "The given execution address is not in checksum form.", - "err_missing_address": "An address must must be providing", + "err_missing_address": "An address must be provided", "msg_ECDSA_hex_addr_withdrawal": "**[Warning] you are setting an execution address as your withdrawal address. Please ensure that you have control over this address.**" }, "validate_partial_deposit_amount": { diff --git a/ethstaker_deposit/utils/validation.py b/ethstaker_deposit/utils/validation.py index b8524c90..4dd3e5e8 100644 --- a/ethstaker_deposit/utils/validation.py +++ b/ethstaker_deposit/utils/validation.py @@ -2,6 +2,7 @@ import click import json import re +import sys import concurrent.futures from typing import Any, Dict, Sequence @@ -138,6 +139,14 @@ def validate_deposit(deposit_data_dict: Dict[str, Any], credential: Credential = def validate_password_strength(password: str) -> str: if len(password) < 8: raise ValidationError(load_text(['msg_password_length'])) + + encoding = sys.stdin.encoding.lower() + if encoding != 'utf-8' and not password.isascii(): + if sys.platform == 'win32': + raise ValidationError(load_text(['msg_password_utf8_win32'])) + else: + raise ValidationError(load_text(['msg_password_utf8'])) + return password diff --git a/tests/test_deposit.py b/tests/test_deposit.py index a7f4048d..48d03907 100644 --- a/tests/test_deposit.py +++ b/tests/test_deposit.py @@ -12,7 +12,7 @@ def test_should_notify_user_and_exit_if_invalid_python_version(monkeypatch) -> None: exit_called = False - def _mock_sys_exit(): + def _mock_sys_exit(arg): nonlocal exit_called exit_called = True diff --git a/tests/test_utils/test_validation.py b/tests/test_utils/test_validation.py index ee00473d..1c0ecf71 100644 --- a/tests/test_utils/test_validation.py +++ b/tests/test_utils/test_validation.py @@ -1,4 +1,5 @@ import pytest +import sys from typing import ( Any, ) @@ -21,7 +22,38 @@ ('1234567', False), ] ) -def test_validate_password_strength(password, valid): +def test_validate_password_strength(password: str, valid: bool) -> None: + if valid: + validate_password_strength(password=password) + else: + with pytest.raises(ValidationError): + validate_password_strength(password=password) + + +@pytest.mark.parametrize( + 'password, encoding, platform, valid', + [ + ('Surströmming', 'utf-8', 'linux', True), + ('Surströmming', 'latin-1', 'linux', False), + ('Surstromming', 'latin-1', 'linux', True), + ('Surströmming', 'utf-8', 'darwin', True), + ('Surströmming', 'latin-1', 'darwin', False), + ('Surstromming', 'latin-1', 'darwin', True), + ('Surströmming', 'utf-8', 'win32', True), + ('Surströmming', 'latin-1', 'win32', False), + ('Surstromming', 'latin-1', 'win32', True), + ] +) +def test_validate_password_strength_encoding( + monkeypatch, password: str, encoding: str, platform: str, valid: bool +) -> None: + class MockStdin: + def __init__(self, encoding): + self.encoding = encoding + mock_stdin = MockStdin(encoding=encoding) + monkeypatch.setattr(sys, 'stdin', mock_stdin) + monkeypatch.setattr(sys, 'platform', platform) + if valid: validate_password_strength(password=password) else: @@ -119,7 +151,9 @@ def test_normalize_input_list(input, result): ('mainnet', 0, 0, valid_pubkey, invalid_signature, False), ] ) -def test_validate_signed_exit(chain, epoch, validator_index, pubkey, signature, result): +def test_validate_signed_exit( + chain: str, epoch: int, validator_index: int, pubkey: str, signature: str, result: bool +) -> None: chain_settings = get_chain_setting(chain) assert validate_signed_exit(validator_index, epoch, signature, pubkey, chain_settings) == result