From 064fe81b2ed9138e1dd9a1a501edb76aa5887f96 Mon Sep 17 00:00:00 2001 From: scito Date: Mon, 2 Jan 2023 22:11:56 +0100 Subject: [PATCH] docker remove python3-opencv, but add required libs; add debug mode --- Dockerfile | 3 +- README.md | 20 ++++++++-- build.sh | 82 ++++++++++++++++++++------------------ src/extract_otp_secrets.py | 30 ++++++++++++-- 4 files changed, 88 insertions(+), 47 deletions(-) diff --git a/Dockerfile b/Dockerfile index 89acc6e..7d5f5fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,9 +15,10 @@ COPY . . ARG RUN_TESTS=true RUN apt-get update && apt-get install -y \ + libgl1 \ + libglib2.0-0 \ libsm6 \ libzbar0 \ - python3-opencv \ && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir -U -r \ requirements.txt \ diff --git a/README.md b/README.md index f5f01ff..7bdaa1f 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,13 @@ This script/project was renamed from extract_otp_secret_keys to extract_otp_secr git clone https://github.com/scito/extract_otp_secrets.git cd extract_otp_secrets pip install -U -r requirements.txt + +python src/extract_otp_secrets.py example_export.txt ``` -### Installation of shared libraries for ZBAR QR reader +In case this script is not starting properly, the debug mode can be activated by adding parameter `-d` in the command line. + +### Installation of shared system libraries For reading QR codes with `ZBAR` QR reader, the zbar library must be installed. If you do not use the `ZBAR` QR reader, you do not need to install the zbar shared library. Note: The `ZBAR` QR reader is the showed for me the best results and is thus default QR Reader. @@ -56,7 +60,13 @@ For a detailed installation documentation of [pyzbar](https://github.com/Natural #### Windows -The zbar DLLs are included with the Windows Python wheels. However, you might need additionally to install [Visual C++ Redistributable Packages for Visual Studio 2013](https://www.microsoft.com/en-US/download/details.aspx?id=40784). Install `vcredist_x64.exe` if using 64-bit Python, `vcredist_x86.exe` if using 32-bit Python. +##### zbar + +The zbar DLLs are included with the Windows Python wheels. However, you might need additionally to install [Visual C++ Redistributable Packages for Visual Studio 2013](https://www.microsoft.com/en-US/download/details.aspx?id=40784). Install `vcredist_x64.exe` if using 64-bit Python, `vcredist_x86.exe` if using 32-bit Python. For more information see [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) + +##### OpenCV + +OpenCV requires [Visual C++ redistributable 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145). For more information see [opencv-python](https://pypi.org/project/opencv-python/) ## Usage @@ -94,7 +104,9 @@ The zbar DLLs are included with the Windows Python wheels. However, you might ne ## Program help: arguments and options -
Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps
+
usage: extract_otp_secrets.py [-h] [--csv FILE] [--keepass FILE] [--json FILE] [--printqr] [--saveqr DIR] [--camera NUMBER] [--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}] [-i] [--no-color] [-d | -v | -q] [infile ...]
+
+Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps
 If no infiles are provided, a GUI window starts and QR codes are captured from the camera.
 
 positional arguments:
@@ -113,6 +125,7 @@ options:
                                 QR reader (default: ZBAR)
   -i, --ignore                  ignore duplicate otps
   --no-color, -n                do not use ANSI colors in console output
+  -d, --debug                   enter debug mode, do checks and quit
   -v, --verbose                 verbose output
   -q, --quiet                   no stdout output, except output set by -
 
@@ -460,6 +473,7 @@ docker build . -t extract_otp_secrets_only_txt --pull -f Dockerfile_only_txt --b
 ```
 
 Run tests in docker container:
+
 ```bash
 docker run --entrypoint /extract/run_pytest.sh --rm -v "$(pwd)":/files:ro extract_otp_secrets
 ```
diff --git a/build.sh b/build.sh
index ec35027..6a90aa2 100755
--- a/build.sh
+++ b/build.sh
@@ -230,19 +230,48 @@ cmd="$PIP install --use-pep517 -U -r requirements-dev.txt"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-cmd="$PIP install -U pipenv"
+# Lint
+
+LINT_OUT_FILE="tests/reports/flake8_results.txt"
+cmd="$FLAKE8 . --count --select=E9,F63,F7,F82 --show-source --statistics | tee $LINT_OUT_FILE"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-cmd="sudo $PIP install --use-pep517 -U -r requirements.txt"
+cmd="$FLAKE8 . --count --exit-zero --max-complexity=10 --max-line-length=200 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,protobuf_generated_python | tee -a $LINT_OUT_FILE"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-cmd="sudo $PIP install --use-pep517 -U -r requirements-dev.txt"
+# Type checking
+
+TYPE_CHECK_OUT_FILE="tests/reports/mypy_results.txt"
+cmd="$MYPY --install-types --non-interactive src/*.py tests/*.py"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-cmd="sudo $PIP install -U pipenv"
+# change to src as python -m mypy adds the current dir Python sys.path
+# execute in a subshell in order not to loose the exit code and not to change the dir in the currrent shell
+cmd="$MYPY --strict src/*.py tests/*.py | tee $TYPE_CHECK_OUT_FILE"
+if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
+eval "$cmd"
+
+# Test
+
+cmd="$PYTHON src/extract_otp_secrets.py example_export.txt"
+if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
+eval "$cmd"
+
+cmd="$PYTHON src/extract_otp_secrets.py - < example_export.txt"
+if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
+eval "$cmd"
+
+COVERAGE_OUT_FILE="tests/reports/pytest-coverage.txt"
+cmd="pytest --cov=extract_otp_secrets_test --junitxml=tests/reports/pytest.xml --cov-report html:tests/reports/html --cov-report=term-missing tests/ | tee $COVERAGE_OUT_FILE"
+if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
+eval "$cmd"
+
+# Pipenv
+
+cmd="$PIP install -U pipenv"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
@@ -254,31 +283,25 @@ eval "$cmd"
 
 $PIPENV run python --version
 
-# Lint
-
-LINT_OUT_FILE="tests/reports/flake8_results.txt"
-cmd="$FLAKE8 . --count --select=E9,F63,F7,F82 --show-source --statistics | tee $LINT_OUT_FILE"
+cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-cmd="$FLAKE8 . --count --exit-zero --max-complexity=10 --max-line-length=200 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,protobuf_generated_python | tee -a $LINT_OUT_FILE"
+# sudo pip
+
+cmd="sudo $PIP install --use-pep517 -U -r requirements.txt"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-# Type checking
-
-TYPE_CHECK_OUT_FILE="tests/reports/mypy_results.txt"
-cmd="$MYPY --install-types --non-interactive src/*.py tests/*.py"
+cmd="sudo $PIP install --use-pep517 -U -r requirements-dev.txt"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-# change to src as python -m mypy adds the current dir Python sys.path
-# execute in a subshell in order not to loose the exit code and not to change the dir in the currrent shell
-cmd="$MYPY --strict src/*.py tests/*.py | tee $TYPE_CHECK_OUT_FILE"
+cmd="sudo $PIP install -U pipenv"
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
 eval "$cmd"
 
-# pip install
+# pip -e install (must be after other pip installs in order to have this environment for development)
 
 cmd="$PIP install -U -e ."
 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
@@ -300,25 +323,6 @@ if $generate_result_files; then
     eval "$cmd"
 fi
 
-# Test
-
-cmd="$PYTHON src/extract_otp_secrets.py example_export.txt"
-if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
-eval "$cmd"
-
-cmd="$PYTHON src/extract_otp_secrets.py - < example_export.txt"
-if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
-eval "$cmd"
-
-COVERAGE_OUT_FILE="tests/reports/pytest-coverage.txt"
-cmd="pytest --cov=extract_otp_secrets_test --junitxml=tests/reports/pytest.xml --cov-report html:tests/reports/html --cov-report=term-missing tests/ | tee $COVERAGE_OUT_FILE"
-if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
-eval "$cmd"
-
-cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/"
-if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
-eval "$cmd"
-
 # Update Code Coverage in README.md
 
 # https://github.com/marketplace/actions/pytest-coverage-comment
@@ -356,7 +360,7 @@ if $build_docker; then
     if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
     eval "$cmd"
 
-    cmd="cat mple_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets - -c - > example_output.csv"
+    cmd="cat example_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets - -c - > example_output.csv"
     if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
     eval "$cmd"
 
@@ -373,10 +377,10 @@ if $build_docker; then
     eval "$cmd"
 
     if $run_gui; then
-        cmd="docker run --pull always --rm -v "$(pwd)":/files:ro --device=\"/dev/video0:/dev/video0\" --env=\"DISPLAY\" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets &"
+        cmd="docker run --rm -v "$(pwd)":/files:ro --device=\"/dev/video0:/dev/video0\" --env=\"DISPLAY\" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets &"
         if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
+        eval "$cmd"
     fi
-    eval "$cmd"
 fi
 
 if $run_gui; then
diff --git a/src/extract_otp_secrets.py b/src/extract_otp_secrets.py
index 84ffa74..61083f4 100644
--- a/src/extract_otp_secrets.py
+++ b/src/extract_otp_secrets.py
@@ -55,6 +55,8 @@ from qrcode import QRCode  # type: ignore
 import protobuf_generated_python.google_auth_pb2 as pb
 import colorama
 
+debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:]
+
 try:
     import cv2  # type: ignore # TODO use cv2 types if available
 
@@ -64,11 +66,12 @@ try:
         import pyzbar.pyzbar as zbar  # type: ignore
         from qreader import QReader  # type: ignore
     except ImportError as e:
-        raise SystemExit(f"""
+        print(f"""
 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: {e}""")
+Exception: {e}\n""", file=sys.stderr)
+        raise e
 
     # Types
     # workaround for PYTHON <= 3.9: Final[tuple[int]]
@@ -102,8 +105,10 @@ Exception: {e}""")
     TextPosition = Enum('TextPosition', ['LEFT', 'RIGHT'])
 
     qreader_available = True
-except ImportError:
+except ImportError as e:
     qreader_available = False
+    if debug_mode:
+        raise e
 
 # Workaround for PYTHON <= 3.9: Union[int, None] used instead of int | None
 
@@ -150,6 +155,9 @@ def main(sys_args: list[str]) -> None:
     if colored:
         colorama.just_fix_windows_console()
 
+    if args.debug:
+        sys.exit(0 if do_debug_checks() else 1)
+
     otps = extract_otps(args)
     write_csv(args, otps)
     write_keepass_csv(args, otps)
@@ -184,15 +192,19 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
     arg_parser.add_argument('-i', '--ignore', help='ignore duplicate otps', action='store_true')
     arg_parser.add_argument('--no-color', '-n', help='do not use ANSI colors in console output', action='store_true')
     output_group = arg_parser.add_mutually_exclusive_group()
+    output_group.add_argument('-d', '--debug', help='enter debug mode, do checks and quit', action='count')
     output_group.add_argument('-v', '--verbose', help='verbose output', action='count')
     output_group.add_argument('-q', '--quiet', help='no stdout output, except output set by -', action='store_true')
     args = arg_parser.parse_args(sys_args)
+    colored = not args.no_color
     if args.csv == '-' or args.json == '-' or args.keepass == '-':
         args.quiet = args.q = True
 
     verbose = args.verbose if args.verbose else LogLevel.NORMAL
+    if args.debug:
+        verbose = LogLevel.DEBUG
+        log_debug('Debug mode start')
     quiet = True if args.quiet else False
-    colored = not args.no_color
     if verbose: print(f"QReader installed: {qreader_available}")
     if qreader_available:
         if verbose >= LogLevel.VERBOSE: print(f"CV2 version: {cv2.__version__}")
@@ -706,6 +718,16 @@ def next_qr_mode(qr_mode: QRMode) -> QRMode:
     return QRMode((qr_mode.value + 1) % len(QRMode))
 
 
+def do_debug_checks() -> bool:
+    log_debug('Do debug checks')
+    log_debug('Try: import cv2')
+    import cv2  # noqa: F401 # This is only a debug import
+    log_debug('Try: import numpy as np')
+    import numpy as np  # noqa: F401 # This is only a debug import
+    print(color('\nDebug checks passed', colorama.Fore.GREEN))
+    return True
+
+
 # workaround for PYTHON <= 3.9 use: BaseException | None
 def log_debug(*values: object, sep: Optional[str] = ' ') -> None:
     if colored: