add ignore duplicate entries option and add quiet test; fixed -k stdout

cv2_1
scito 1 year ago
parent 722009b172
commit b86c4f9a61

@ -385,12 +385,13 @@ if $run_gui; then
eval "$cmd"
fi
echo -e "\n${blueBold}#### Results ####${reset}"
line=$(printf '#%.0s' $(eval echo {1..$(( ($COLUMNS - 10) / 2))}))
echo -e "\n${blueBold}$line RESULTS $line${reset}"
cmd="cat $TYPE_CHECK_OUT_FILE $LINT_OUT_FILE $COVERAGE_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo -e "\n${greenBold}Sucessful${reset}"
echo -e "\n${greenBold}SUCCESS${reset}"
quit

@ -171,7 +171,7 @@ def main(sys_args: list[str]) -> None:
def parse_args(sys_args: list[str]) -> Args:
global verbose, quiet, colored
description_text = "Extracts one time password (OTP) secrets from export QR codes from two-factor authentication (2FA) apps"
description_text = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps"
if qreader_available:
description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera."
example_text = """examples:
@ -186,18 +186,19 @@ python extract_otp_secrets.py = < example_export.png"""
epilog=example_text)
arg_parser.add_argument('infile', help="""a) file or - for stdin with 'otpauth-migration://...' URLs separated by newlines, lines starting with # are ignored;
b) image file containing a QR code or = for stdin for an image containing a QR code""", nargs='*' if qreader_available else '+')
if qreader_available:
arg_parser.add_argument('--camera', '-C', help='camera number of system (default camera: 0)', default=0, type=int, metavar=('NUMBER'))
arg_parser.add_argument('--qr', '-Q', help=f'QR reader (default: {QRMode.ZBAR.name})', type=str, choices=[mode.name for mode in QRMode], default=QRMode.ZBAR.name)
arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE'))
arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal (requires qrcode module)', action='store_true')
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the given folder (requires qrcode module)', metavar=('DIR'))
if qreader_available:
arg_parser.add_argument('--camera', '-C', help='camera number of system (default camera: 0)', default=0, type=int, metavar=('NUMBER'))
arg_parser.add_argument('--qr', '-Q', help=f'QR reader (default: {QRMode.ZBAR.name})', type=str, choices=[mode.name for mode in QRMode], default=QRMode.ZBAR.name)
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('--verbose', '-v', help='verbose output', action='count')
output_group.add_argument('--quiet', '-q', help='no stdout output, except output set by -', action='store_true')
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)
if args.csv == '-' or args.json == '-' or args.keepass == '-':
args.quiet = args.q = True
@ -421,7 +422,6 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
new_otps_count = 0
# pylint: disable=no-member
for raw_otp in payload.otp_parameters:
new_otps_count += 1
if verbose: print(f"\n{len(otps) + 1}. Secret")
secret = convert_secret_from_bytes_to_base32_str(raw_otp.secret)
if verbose >= LogLevel.DEBUG: log_debug('OTP enum type:', get_enum_name_by_number(raw_otp, 'type'))
@ -435,15 +435,19 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
"counter": raw_otp.counter if raw_otp.type == 1 else None,
"url": otp_url
}
otps.append(otp)
if not quiet:
print_otp(otp)
if args.printqr:
print_qr(args, otp_url)
if args.saveqr:
save_qr(otp, args, len(otps))
if not quiet:
print()
if otp not in otps or not args.ignore:
otps.append(otp)
new_otps_count += 1
if not quiet:
print_otp(otp)
if args.printqr:
print_qr(args, otp_url)
if args.saveqr:
save_qr(otp, args, len(otps))
if not quiet:
print()
elif args.ignore and not quiet:
eprint(f"Ignored duplicate otp: {otp['name']}", f" / {otp['issuer']}\n" if otp['issuer'] else '\n', sep='')
return new_otps_count
@ -613,8 +617,11 @@ def write_keepass_csv(args: Args, otps: Otps) -> None:
if args.keepass and len(otps) > 0:
has_totp = has_otp_type(otps, 'totp')
has_hotp = has_otp_type(otps, 'hotp')
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp")
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp")
if args.keepass != '-':
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp")
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp")
else:
otp_filename_totp = otp_filename_hotp = '-'
if has_totp:
count_totp_entries = write_keepass_totp_csv(otp_filename_totp, otps)
if has_hotp:

@ -26,6 +26,7 @@ import pathlib
import re
import sys
import time
from typing import Optional
import colorama
import pytest
@ -354,6 +355,43 @@ def test_extract_saveqr(capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Pa
assert count_files_in_dir(tmp_path) == 6
def test_extract_ignored_duplicates(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-i', 'example_export.txt'])
# Assert
captured = capsys.readouterr()
expected_stdout = '''Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp
Counter: 4
Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
'''
expected_stderr = '''Ignored duplicate otp: pi@raspberrypi
Ignored duplicate otp: pi@raspberrypi / raspberrypi
'''
assert captured.out == expected_stdout
assert captured.err == expected_stderr
def test_normalize_bytes() -> None:
assert replace_escaped_octal_utf8_bytes_with_str(
'Before\\\\302\\\\277\\\\303\nname: enc: \\302\\277\\303\\244\\303\\204\\303\\251\\303\\211?\nAfter') == 'Before\\\\302\\\\277\\\\303\nname: enc: ¿äÄéÉ?\nAfter'
@ -421,7 +459,7 @@ def test_extract_help(capsys: pytest.CaptureFixture[str]) -> None:
captured = capsys.readouterr()
assert len(captured.out) > 0
assert "-h, --help" in captured.out and "--verbose, -v" in captured.out
assert "-h, --help" in captured.out and "-v, --verbose" in captured.out
assert captured.err == ''
assert e.type == SystemExit
assert e.value.code == 0
@ -469,12 +507,41 @@ def test_verbose_and_quiet(capsys: pytest.CaptureFixture[str]) -> None:
captured = capsys.readouterr()
assert len(captured.err) > 0
assert 'error: argument --quiet/-q: not allowed with argument --verbose/-v' in captured.err
assert 'error: argument -q/--quiet: not allowed with argument -v/--verbose' in captured.err
assert captured.out == ''
assert e.value.code == 2
assert e.type == SystemExit
@pytest.mark.parametrize("parameter,parameter_value,stdout_expected,stderr_expected", [
('-c', 'outfile', False, False),
('-c', '-', True, False),
('-k', 'outfile', False, False),
('-k', '-', True, False),
('-j', 'outfile', False, False),
('-s', 'outfile', False, False),
('-j', '-', True, False),
('-i', None, False, False),
('-p', None, True, False),
('-Q', 'CV2', False, False),
('-n', None, False, False),
])
def test_quiet(parameter: str, parameter_value: Optional[str], stdout_expected: bool, stderr_expected: bool, capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path) -> None:
args = ['-q', 'example_export.txt', 'example_export.png', parameter]
if parameter_value == 'outfile':
args.append(str(tmp_path / parameter_value))
elif parameter_value:
args.append(parameter_value)
# Act
extract_otp_secrets.main(args)
# Assert
captured = capsys.readouterr()
assert (captured.out == '' and not stdout_expected) or (len(captured.out) > 0 and stdout_expected)
assert (captured.err == '' and not stderr_expected) or (len(captured.err) > 0 and stderr_expected)
def test_wrong_data(capsys: pytest.CaptureFixture[str]) -> None:
with pytest.raises(SystemExit) as e:
# Act

@ -198,7 +198,7 @@ Type: totp
actual_output = out.getvalue()
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in actual_output and "--verbose, -v" in actual_output)
self.assertTrue("-h, --help" in actual_output and "-v, --verbose" in actual_output)
def test_extract_help_2(self) -> None:
out = io.StringIO()
@ -209,7 +209,7 @@ Type: totp
actual_output = out.getvalue()
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in actual_output and "--verbose, -v" in actual_output)
self.assertTrue("-h, --help" in actual_output and "-v, --verbose" in actual_output)
self.assertEqual(context.exception.code, 0)
def test_extract_help_3(self) -> None:
@ -218,7 +218,7 @@ Type: totp
extract_otp_secrets.main(['-h'])
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in "\n".join(actual_output) and "--verbose, -v" in "\n".join(actual_output))
self.assertTrue("-h, --help" in "\n".join(actual_output) and "-v, --verbose" in "\n".join(actual_output))
self.assertEqual(context.exception.code, 0)
def setUp(self) -> None:

Loading…
Cancel
Save