Skip to content

Commit

Permalink
support glob expansion for infiles, fixes #328
Browse files Browse the repository at this point in the history
Glob expansion for infiles is useful in environments where there is no expansion in the shell, e.g. on Windows.
  • Loading branch information
scito committed Dec 1, 2024
1 parent a284555 commit b54974e
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 75 deletions.
23 changes: 15 additions & 8 deletions src/extract_otp_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import base64
import csv
import fileinput
import glob
import json
import os
import platform
Expand Down Expand Up @@ -527,14 +528,20 @@ def extract_otps_from_files(args: Args) -> Otps:

files_count = urls_count = otps_count = 0
if verbose: print(f"Input files: {args.infile}")
for infile in args.infile:
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
files_count += 1
for line in get_otp_urls_from_file(infile, args):
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
if line.startswith('#') or line == '': continue
urls_count += 1
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
for infile_raw in args.infile:
expanded_infiles = glob.glob(infile_raw)
if not expanded_infiles:
expanded_infiles = [infile_raw]
if verbose >= LogLevel.DEBUG: log_debug(f"Could not expand input files, fallback to infile")
if verbose >= LogLevel.DEBUG: log_debug(f"Expanded input files: {expanded_infiles}")
for infile in expanded_infiles:
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
files_count += 1
for line in get_otp_urls_from_file(infile, args):
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
if line.startswith('#') or line == '': continue
urls_count += 1
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
if verbose: print(f"Extracted {otps_count} otp{'s'[:otps_count != 1]} from {urls_count} otp url{'s'[:urls_count != 1]} by reading {files_count} infile{'s'[:files_count != 1]}")
return otps

Expand Down
4 changes: 3 additions & 1 deletion tests/data/print_verbose_output-n-vvv.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ QReader installed: True
CV2 version: 4.10.0
QR reading mode: ZBAR

Version: extract_otp_secrets 2.8.1.post17+git.3dc7d1c2.dirty Linux x86_64 Python 3.11.9 (CPython/called as script)
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script)

Input files: ['example_export.txt']

DEBUG: Expanded 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/
Expand Down
4 changes: 3 additions & 1 deletion tests/data/print_verbose_output-vvv.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ QReader installed: True
CV2 version: 4.10.0
QR reading mode: ZBAR

Version: extract_otp_secrets 2.8.1.post17+git.3dc7d1c2.dirty Linux x86_64 Python 3.11.9 (CPython/called as script)
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script)

Input files: ['example_export.txt']

DEBUG: Expanded 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/
Expand Down
154 changes: 89 additions & 65 deletions tests/extract_otp_secrets_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -869,19 +869,8 @@ def test_wrong_content(capsys: pytest.CaptureFixture[str]) -> None:
# Assert
captured = capsys.readouterr()

expected_stderr = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''

assert captured.out == ''
assert captured.err == expected_stderr
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG


def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
Expand All @@ -891,19 +880,8 @@ def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
# Assert
captured = capsys.readouterr()

expected_stderr = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''

assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured.err == expected_stderr
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG


def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
Expand All @@ -913,19 +891,8 @@ def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
# Assert
captured = capsys.readouterr()

expected_stderr = f'''{colorama.Fore.RED}
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given{colorama.Fore.RESET}
{colorama.Fore.RED}
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
'''

assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured.err == expected_stderr
assert captured.err == EXPECTED_STDERR_COLORED_OTP_URL_WRONG


def test_one_wrong_line(capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch) -> None:
Expand Down Expand Up @@ -997,6 +964,46 @@ def test_img_qr_reader_from_file_happy_path(capsys: pytest.CaptureFixture[str])
assert captured.err == ''


@pytest.mark.qreader
def test_img_qr_reader_but_no_otp_from_file(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-n', 'tests/data/qr_but_without_otp.png'])

# Assert
captured = capsys.readouterr()

assert captured.out == ''
assert captured.err == EXPECTED_STDERR_NO_OTP_URL


@pytest.mark.qreader
def test_img_qr_reader_from_wildcard(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-n', 'tests/data/*.png'])

# Assert
captured = capsys.readouterr()

assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
assert normalize_testfile_path(captured.err) == EXPECTED_STDERR_NO_OTP_URL


def normalize_testfile_path(text: str):
return text.replace('tests/data\\', 'tests/data/') if sys.platform.startswith("win") else text


@pytest.mark.qreader
def test_img_qr_reader_from_multiple_files(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-n', 'tests/data/test_googleauth_export.png', 'tests/data/text_masquerading_as_image.jpeg'])

# Assert
captured = capsys.readouterr()

assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
assert captured.err == EXPECTED_STDERR_BAD_IMAGE


@pytest.mark.qreader
def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode: str) -> None:
# Act
Expand Down Expand Up @@ -1041,24 +1048,7 @@ def test_img_qr_reader_from_stdin(capsys: pytest.CaptureFixture[str], monkeypatc
# Assert
captured = capsys.readouterr()

expected_stdout = '''Name: Test1:[email protected]
Secret: JBSWY3DPEHPK3PXP
Issuer: Test1
Type: totp
Name: Test2:[email protected]
Secret: JBSWY3DPEHPK3PXQ
Issuer: Test2
Type: totp
Name: Test3:[email protected]
Secret: JBSWY3DPEHPK3PXR
Issuer: Test3
Type: totp
'''

assert captured.out == expected_stdout
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
assert captured.err == ''


Expand Down Expand Up @@ -1143,19 +1133,9 @@ def test_non_image_file(capsys: pytest.CaptureFixture[str]) -> None:

# Assert
captured = capsys.readouterr()
expected_stderr = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/text_masquerading_as_image.jpeg
input: This is just a text file masquerading as an image file.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/text_masquerading_as_image.jpeg
url: This is just a text file masquerading as an image file.
'''

assert captured.err == expected_stderr
assert captured.out == ''
assert captured.err == EXPECTED_STDERR_BAD_IMAGE


def test_next_valid_qr_mode() -> None:
Expand Down Expand Up @@ -1209,3 +1189,47 @@ def test_next_valid_qr_mode() -> None:
Type: totp
'''

EXPECTED_STDERR_OTP_URL_WRONG = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''

EXPECTED_STDERR_COLORED_OTP_URL_WRONG = f'''{colorama.Fore.RED}
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given{colorama.Fore.RESET}
{colorama.Fore.RED}
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
'''

EXPECTED_STDERR_NO_OTP_URL = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/qr_but_without_otp.png
input: NOT A otpauth-migration:// URL
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/qr_but_without_otp.png
url: NOT A otpauth-migration:// URL
'''

EXPECTED_STDERR_BAD_IMAGE = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/text_masquerading_as_image.jpeg
input: This is just a text file masquerading as an image file.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/text_masquerading_as_image.jpeg
url: This is just a text file masquerading as an image file.
'''

0 comments on commit b54974e

Please sign in to comment.