forked from LBRYCommunity/lbry-sdk
added InvalidPasswordError code when password is invalid
This commit is contained in:
parent
88263db831
commit
75d78bfa53
8 changed files with 116 additions and 41 deletions
|
@ -1,2 +1,5 @@
|
||||||
generate:
|
generate:
|
||||||
python generate.py > __init__.py
|
python generate.py generate > __init__.py
|
||||||
|
|
||||||
|
analyze:
|
||||||
|
python generate.py analyze
|
||||||
|
|
|
@ -53,6 +53,7 @@ Code | Name | Message
|
||||||
408 | Resolve | Failed to resolve '{url}'.
|
408 | Resolve | Failed to resolve '{url}'.
|
||||||
409 | ResolveTimeout | Failed to resolve '{url}' within the timeout.
|
409 | ResolveTimeout | Failed to resolve '{url}' within the timeout.
|
||||||
410 | KeyFeeAboveMaxAllowed | {message}
|
410 | KeyFeeAboveMaxAllowed | {message}
|
||||||
|
411 | InvalidPassword | Password is invalid.
|
||||||
**5xx** | Blob | **Blobs**
|
**5xx** | Blob | **Blobs**
|
||||||
500 | BlobNotFound | Blob not found.
|
500 | BlobNotFound | Blob not found.
|
||||||
501 | BlobPermissionDenied | Permission denied to read blob.
|
501 | BlobPermissionDenied | Permission denied to read blob.
|
||||||
|
|
|
@ -197,6 +197,12 @@ class KeyFeeAboveMaxAllowedError(WalletError):
|
||||||
super().__init__(f"{message}")
|
super().__init__(f"{message}")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidPasswordError(WalletError):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("Password is invalid.")
|
||||||
|
|
||||||
|
|
||||||
class BlobError(BaseError):
|
class BlobError(BaseError):
|
||||||
"""
|
"""
|
||||||
**Blobs**
|
**Blobs**
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
from textwrap import fill, indent
|
from textwrap import fill, indent
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,8 +78,9 @@ class ErrorClass:
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
def error_rows(lines):
|
def get_errors():
|
||||||
lines = iter(lines)
|
with open('README.md', 'r') as readme:
|
||||||
|
lines = iter(readme.readlines())
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith('## Exceptions Table'):
|
if line.startswith('## Exceptions Table'):
|
||||||
break
|
break
|
||||||
|
@ -86,7 +90,7 @@ def error_rows(lines):
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if not line:
|
if not line:
|
||||||
break
|
break
|
||||||
yield line
|
yield ErrorClass(*[c.strip() for c in line.split('|')])
|
||||||
|
|
||||||
|
|
||||||
def find_parent(stack, child):
|
def find_parent(stack, child):
|
||||||
|
@ -96,19 +100,50 @@ def find_parent(stack, child):
|
||||||
return parent
|
return parent
|
||||||
|
|
||||||
|
|
||||||
def main(out):
|
def generate(out):
|
||||||
with open('README.md', 'r') as readme:
|
|
||||||
lines = readme.readlines()
|
|
||||||
out.write('from .base import BaseError\n')
|
out.write('from .base import BaseError\n')
|
||||||
stack = {}
|
stack = {}
|
||||||
for row in error_rows(lines):
|
for error in get_errors():
|
||||||
error = ErrorClass(*[c.strip() for c in row.split('|')])
|
|
||||||
error.render(out, find_parent(stack, error))
|
error.render(out, find_parent(stack, error))
|
||||||
if not error.is_leaf:
|
if not error.is_leaf:
|
||||||
assert error.code not in stack, f"Duplicate code: {error.code}"
|
assert error.code not in stack, f"Duplicate code: {error.code}"
|
||||||
stack[error.code] = error
|
stack[error.code] = error
|
||||||
|
|
||||||
|
|
||||||
|
def analyze():
|
||||||
|
errors = {e.class_name: [] for e in get_errors() if e.is_leaf}
|
||||||
|
here = Path(__file__).absolute().parents[0]
|
||||||
|
module = here.parent
|
||||||
|
for file_path in module.glob('**/*.py'):
|
||||||
|
if here in file_path.parents:
|
||||||
|
continue
|
||||||
|
with open(file_path) as src_file:
|
||||||
|
src = src_file.read()
|
||||||
|
for error in errors.keys():
|
||||||
|
found = src.count(error)
|
||||||
|
if found > 0:
|
||||||
|
errors[error].append((file_path, found))
|
||||||
|
|
||||||
|
print('Unused Errors:\n')
|
||||||
|
for error, used in errors.items():
|
||||||
|
if used:
|
||||||
|
print(f' - {error}')
|
||||||
|
for use in used:
|
||||||
|
print(f' {use[0].relative_to(module.parent)} {use[1]}')
|
||||||
|
print('')
|
||||||
|
|
||||||
|
print('')
|
||||||
|
print('Unused Errors:')
|
||||||
|
for error, used in errors.items():
|
||||||
|
if not used:
|
||||||
|
print(f' - {error}')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
parser = argparse.ArgumentParser()
|
||||||
main(sys.stdout)
|
parser.add_argument("action", choices=['generate', 'analyze'])
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.action == "analyze":
|
||||||
|
analyze()
|
||||||
|
elif args.action == "generate":
|
||||||
|
generate(sys.stdout)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import json
|
||||||
from torba.client.wallet import ENCRYPT_ON_DISK
|
from torba.client.wallet import ENCRYPT_ON_DISK
|
||||||
from lbry.testcase import CommandTestCase
|
from lbry.testcase import CommandTestCase
|
||||||
from lbry.wallet.dewies import dict_values_to_lbc
|
from lbry.wallet.dewies import dict_values_to_lbc
|
||||||
|
from lbry.error import InvalidPasswordError
|
||||||
|
|
||||||
|
|
||||||
class WalletCommands(CommandTestCase):
|
class WalletCommands(CommandTestCase):
|
||||||
|
@ -303,7 +304,7 @@ class WalletEncryptionAndSynchronization(CommandTestCase):
|
||||||
# sync_apply doesn't save password if encrypt-on-disk is False
|
# sync_apply doesn't save password if encrypt-on-disk is False
|
||||||
self.assertEqual(wallet2.encryption_password, None)
|
self.assertEqual(wallet2.encryption_password, None)
|
||||||
# need to use new password2 in sync_apply
|
# need to use new password2 in sync_apply
|
||||||
with self.assertRaises(ValueError): # wrong password
|
with self.assertRaises(InvalidPasswordError):
|
||||||
await daemon.jsonrpc_sync_apply('password', data=data['data'], blocking=True)
|
await daemon.jsonrpc_sync_apply('password', data=data['data'], blocking=True)
|
||||||
await daemon.jsonrpc_sync_apply('password2', data=data['data'], blocking=True)
|
await daemon.jsonrpc_sync_apply('password2', data=data['data'], blocking=True)
|
||||||
# sync_apply with new password2 also sets it as new local password
|
# sync_apply with new password2 also sets it as new local password
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from unittest import TestCase, mock
|
from unittest import TestCase, mock
|
||||||
from torba.client.hash import aes_decrypt, aes_encrypt, better_aes_decrypt, better_aes_encrypt
|
from torba.client.hash import aes_decrypt, aes_encrypt, better_aes_decrypt, better_aes_encrypt
|
||||||
|
from torba.client.errors import InvalidPasswordError
|
||||||
|
|
||||||
|
|
||||||
class TestAESEncryptDecrypt(TestCase):
|
class TestAESEncryptDecrypt(TestCase):
|
||||||
|
@ -34,9 +35,20 @@ class TestAESEncryptDecrypt(TestCase):
|
||||||
self.message
|
self.message
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_decrypt_error(self):
|
||||||
|
with self.assertRaises(InvalidPasswordError):
|
||||||
|
aes_decrypt('notbubblegum', aes_encrypt('bubblegum', self.message))
|
||||||
|
|
||||||
def test_better_encrypt_decrypt(self):
|
def test_better_encrypt_decrypt(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
b'valuable value',
|
b'valuable value',
|
||||||
better_aes_decrypt(
|
better_aes_decrypt(
|
||||||
'super secret',
|
'super secret',
|
||||||
better_aes_encrypt('super secret', b'valuable value')))
|
better_aes_encrypt('super secret', b'valuable value')))
|
||||||
|
|
||||||
|
def test_better_decrypt_error(self):
|
||||||
|
with self.assertRaises(InvalidPasswordError):
|
||||||
|
better_aes_decrypt(
|
||||||
|
'super secret but wrong',
|
||||||
|
better_aes_encrypt('super secret', b'valuable value')
|
||||||
|
)
|
||||||
|
|
|
@ -1,2 +1,8 @@
|
||||||
|
class InvalidPasswordError(Exception):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("Password is invalid.")
|
||||||
|
|
||||||
|
|
||||||
class InsufficientFundsError(Exception):
|
class InsufficientFundsError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -22,6 +22,7 @@ from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
from torba.client.util import bytes_to_int, int_to_bytes
|
from torba.client.util import bytes_to_int, int_to_bytes
|
||||||
from torba.client.constants import NULL_HASH32
|
from torba.client.constants import NULL_HASH32
|
||||||
|
from torba.client.errors import InvalidPasswordError
|
||||||
|
|
||||||
|
|
||||||
class TXRef:
|
class TXRef:
|
||||||
|
@ -136,6 +137,7 @@ def aes_encrypt(secret: str, value: str, init_vector: bytes = None) -> str:
|
||||||
|
|
||||||
|
|
||||||
def aes_decrypt(secret: str, value: str) -> typing.Tuple[str, bytes]:
|
def aes_decrypt(secret: str, value: str) -> typing.Tuple[str, bytes]:
|
||||||
|
try:
|
||||||
data = base64.b64decode(value.encode())
|
data = base64.b64decode(value.encode())
|
||||||
key = double_sha256(secret.encode())
|
key = double_sha256(secret.encode())
|
||||||
init_vector, data = data[:16], data[16:]
|
init_vector, data = data[:16], data[16:]
|
||||||
|
@ -143,6 +145,10 @@ def aes_decrypt(secret: str, value: str) -> typing.Tuple[str, bytes]:
|
||||||
unpadder = PKCS7(AES.block_size).unpadder()
|
unpadder = PKCS7(AES.block_size).unpadder()
|
||||||
result = unpadder.update(decryptor.update(data)) + unpadder.finalize()
|
result = unpadder.update(decryptor.update(data)) + unpadder.finalize()
|
||||||
return result.decode(), init_vector
|
return result.decode(), init_vector
|
||||||
|
except ValueError as e:
|
||||||
|
if e.args[0] == 'Invalid padding bytes.':
|
||||||
|
raise InvalidPasswordError()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def better_aes_encrypt(secret: str, value: bytes) -> bytes:
|
def better_aes_encrypt(secret: str, value: bytes) -> bytes:
|
||||||
|
@ -156,6 +162,7 @@ def better_aes_encrypt(secret: str, value: bytes) -> bytes:
|
||||||
|
|
||||||
|
|
||||||
def better_aes_decrypt(secret: str, value: bytes) -> bytes:
|
def better_aes_decrypt(secret: str, value: bytes) -> bytes:
|
||||||
|
try:
|
||||||
data = base64.b64decode(value)
|
data = base64.b64decode(value)
|
||||||
_, scryp_n, scrypt_r, scrypt_p, data = data.split(b':', maxsplit=4)
|
_, scryp_n, scrypt_r, scrypt_p, data = data.split(b':', maxsplit=4)
|
||||||
init_vector, data = data[:16], data[16:]
|
init_vector, data = data[:16], data[16:]
|
||||||
|
@ -163,6 +170,10 @@ def better_aes_decrypt(secret: str, value: bytes) -> bytes:
|
||||||
decryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).decryptor()
|
decryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).decryptor()
|
||||||
unpadder = PKCS7(AES.block_size).unpadder()
|
unpadder = PKCS7(AES.block_size).unpadder()
|
||||||
return unpadder.update(decryptor.update(data)) + unpadder.finalize()
|
return unpadder.update(decryptor.update(data)) + unpadder.finalize()
|
||||||
|
except ValueError as e:
|
||||||
|
if e.args[0] == 'Invalid padding bytes.':
|
||||||
|
raise InvalidPasswordError()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def scrypt(passphrase, salt, scrypt_n=1<<13, scrypt_r=16, scrypt_p=1):
|
def scrypt(passphrase, salt, scrypt_n=1<<13, scrypt_r=16, scrypt_p=1):
|
||||||
|
|
Loading…
Reference in a new issue