From e754befb52959fbf6b7f8d0409d765284e1cc5f5 Mon Sep 17 00:00:00 2001 From: scito Date: Sat, 24 Dec 2022 15:30:17 +0100 Subject: [PATCH] refactor; update setup.py more verbose logging better error messages --- README.md | 17 +-- extract_otp_secret_keys.py | 138 +++++++++++++------------ setup.py | 4 +- test/print_verbose_output.txt | 22 ++++ test_extract_otp_secret_keys_pytest.py | 2 +- test_extract_qrcode_unittest.py | 2 +- 6 files changed, 108 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index e1e542a..eaa54c8 100644 --- a/README.md +++ b/README.md @@ -69,32 +69,33 @@ Known to work with For protobuf versions 3.14.0 or similar or Python 3.6, use the extract_otp_secret_keys version 1.4.0. -### Linux and macOS +### Shared libs installation for reading QR code images -For reading QR code images the zbar lib must be installed. +For reading QR code images the zbar library must be installed. +If you do not extract directly from images, you do not need to install the zbar shared library. -Detailed [installation documentation for pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar#installation). +For a detailed installation documentation of [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar#installation). #### Windows The zbar DLLs are included with the Windows Python wheels. On other operating systems, you will need to install the zbar shared library. -#### Mac OS X - - brew install zbar - #### Linux (Debian, Ubuntu, ...) sudo apt-get install libzbar0 #### Linux (OpenSUSE) - sudo zyper install libzbar0 + sudo zypper install libzbar0 #### Linux (Fedora) sudo dnf install libzbar0 +#### Mac OS X + + brew install zbar + ## Examples ### Printing otp secrets form text file diff --git a/extract_otp_secret_keys.py b/extract_otp_secret_keys.py index 02d62be..3f1d455 100644 --- a/extract_otp_secret_keys.py +++ b/extract_otp_secret_keys.py @@ -100,8 +100,11 @@ def extract_otps(args): otps = [] - i = j = 0 + i = j = k = 0 + if verbose: print('Input files: {}'.format(args.infile)) for infile in args.infile: + if verbose: print('Processing infile {}'.format(infile)) + k += 1 for line in get_lines_from_file(infile): if verbose: print(line) if line.startswith('#') or line == '': continue @@ -134,79 +137,83 @@ def extract_otps(args): print() otps.append(otp) - + if verbose: print('{} infile(s) processed'.format(k)) return otps def get_lines_from_file(filename): - lines = read_lines_from_text_file(filename) - if are_bytes(lines): - abort('\nBinary input was given in stdin, please use = instead of -.') - elif lines: - return lines + # stdin stream cannot be rewinded, thus distinguish, use - for utf-8 stdin and = for binary image stdin + if filename != '=': + check_file_exists(filename) + lines = read_lines_from_text_file(filename) + if lines: + return lines # could not process text file, try reading as image - return convert_img_to_line(filename) + if filename != '-': + return convert_img_to_line(filename) def read_lines_from_text_file(filename): - if filename != '=': - check_file_exists(filename) - finput = fileinput.input(filename) - try: - lines = [] - for line in (line.strip() for line in finput): - # TODO improve - # if verbose: print(line) - # if line.startswith('#') or line == '': - # continue - # unfortunately yield line leads to random test fails - lines.append(line) - return lines - except UnicodeDecodeError: - if filename == '-': - abort('\nERROR: Unable to open text file form stdin. ' - 'In case you want read an image file from stdin, you must use "=" instead of "-".') - else: # The file is probably an image, process below - return None - finally: - finput.close() + if verbose: print('Reading lines of {}'.format(filename)) + finput = fileinput.input(filename) + try: + lines = [] + for line in (line.strip() for line in finput): + if verbose: print(line) + if is_binary(line): + abort('\nBinary input was given in stdin, please use = instead of - as infile argument for images.') + # unfortunately yield line leads to random test fails + lines.append(line) + return lines + except UnicodeDecodeError: + if filename == '-': + abort('\nERROR: Unable to open text file form stdin. ' + 'In case you want read an image file from stdin, you must use "=" instead of "-".') + else: # The file is probably an image, process below + return None + finally: + finput.close() def convert_img_to_line(filename): - if filename != '-': - try: - if filename != '=': - image = imread(filename) - else: - try: - stdin = sys.stdin.buffer.read() - except AttributeError: - # Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer - stdin = sys.stdin.read() - try: - array = frombuffer(stdin, dtype='uint8') - except TypeError as e: - abort('\nERROR: Cannot read binary stdin buffer. Exception: {}'.format(str(e))) - image = imdecode(array, IMREAD_UNCHANGED) - - if image is None: - abort('\nERROR: Unable to open file for reading.\ninput file: {}'.format(filename)) - - # dynamic import of QReader since this module has a dependency to zbar lib + if verbose: print('Reading image {}'.format(filename)) + try: + if filename != '=': + image = imread(filename) + else: + try: + stdin = sys.stdin.buffer.read() + except AttributeError: + # Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer + stdin = sys.stdin.read() try: - from qreader import QReader - except ImportError as e: - abort('\nERROR: Cannot import QReader module probably due to missing zbar shared library. Exception:\n{}'.format(str(e))) + array = frombuffer(stdin, dtype='uint8') + except TypeError as e: + abort('\nERROR: Cannot read binary stdin buffer. Exception: {}'.format(str(e))) + image = imdecode(array, IMREAD_UNCHANGED) - decoder = QReader() - decoded_text = decoder.detect_and_decode(image=image) - if decoded_text is None: - abort('\nERROR: Unable to read QR Code from file.\ninput file: {}'.format(filename)) + if image is None: + abort('\nERROR: Unable to open file for reading.\ninput file: {}'.format(filename)) - return [decoded_text] - except Exception as e: - abort('\nERROR: Encountered exception "{}".\ninput file: {}'.format(str(e), filename)) + # dynamic import of QReader since this module has a dependency to zbar lib and import it only when necessary + try: + from qreader import QReader + except ImportError as e: + abort(''' +ERROR: Cannot import QReader module. This problem is probably due to the missing zbar shared library. +On Linux and macOS libzbar0 must be installed. +See in README.md for the installation of the libzbar0. +Exception: {}'''.format(str(e))) + + decoder = QReader() + decoded_text = decoder.detect_and_decode(image=image) + if decoded_text is None: + abort('\nERROR: Unable to read QR Code from file.\ninput file: {}'.format(filename)) + + return [decoded_text] + except Exception as e: + abort('\nERROR: Encountered exception "{}".\ninput file: {}'.format(str(e), filename)) def get_payload_from_line(line, i, infile): @@ -392,13 +399,12 @@ def check_file_exists(filename): '\ninput file: {}'.format(filename)) -def are_bytes(lines): - if lines and len(lines) > 0: - try: - lines[0].startswith('#') - return False - except (UnicodeDecodeError, AttributeError, TypeError): - return True +def is_binary(line): + try: + line.startswith('#') + return False + except (UnicodeDecodeError, AttributeError, TypeError): + return True def eprint(*args, **kwargs): diff --git a/setup.py b/setup.py index 2613dad..8c9d510 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,9 @@ setup( install_requires=[ 'protobuf', 'qrcode', - 'Pillow' + 'Pillow', + 'qreader', + 'opencv-python' ], project_urls={ diff --git a/test/print_verbose_output.txt b/test/print_verbose_output.txt index da20bf7..cf01dae 100644 --- a/test/print_verbose_output.txt +++ b/test/print_verbose_output.txt @@ -1,3 +1,24 @@ +Input files: ['example_export.txt'] +Processing infile example_export.txt +Reading lines of example_export.txt +# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/ +# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY +# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi +otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B + +# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY +otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D + +# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi +# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY +otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B + +# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4 +otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D + +# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY +# Name: "encoding: ¿äÄéÉ? (demo)" +otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D # 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/ # Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY # otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi @@ -136,3 +157,4 @@ Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Type: totp otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY +1 infile(s) processed diff --git a/test_extract_otp_secret_keys_pytest.py b/test_extract_otp_secret_keys_pytest.py index 58a9ec8..32ec68d 100644 --- a/test_extract_otp_secret_keys_pytest.py +++ b/test_extract_otp_secret_keys_pytest.py @@ -562,7 +562,7 @@ def test_img_qr_reader_from_stdin_wrong_symbol(capsys, monkeypatch): # Assert captured = capsys.readouterr() - expected_stderr = '\nBinary input was given in stdin, please use = instead of -.\n' + expected_stderr = '\nBinary input was given in stdin, please use = instead of - as infile argument for images.\n' assert captured.err == expected_stderr assert captured.out == '' diff --git a/test_extract_qrcode_unittest.py b/test_extract_qrcode_unittest.py index 2b62137..7f9c718 100644 --- a/test_extract_qrcode_unittest.py +++ b/test_extract_qrcode_unittest.py @@ -23,7 +23,7 @@ from utils import Capturing import extract_otp_secret_keys -class TestExtract(unittest.TestCase): +class TestQRImageExtract(unittest.TestCase): def test_img_qr_reader_happy_path(self): with Capturing() as actual_output: extract_otp_secret_keys.main(['test/test_googleauth_export.png'])