2022-09-03 15:38:47 +02:00
# pytest for extract_otp_secret_keys.py
2022-09-03 14:31:09 +02:00
# Run tests:
2022-09-03 15:38:47 +02:00
# pytest
2022-09-03 14:31:09 +02:00
# Author: Scito (https://scito.ch)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
2022-12-24 01:59:35 +01:00
from utils import read_csv , read_csv_str , read_json , read_json_str , remove_files , remove_dir_with_files , read_file_to_str , read_binary_file_as_stream , file_exits
2022-09-09 13:13:13 +02:00
from os import path
2022-12-18 21:45:32 +01:00
from pytest import raises , mark
2022-12-24 01:59:35 +01:00
from io import StringIO , BytesIO
from sys import implementation , stdin
2022-09-03 14:31:09 +02:00
import extract_otp_secret_keys
2022-09-03 16:12:28 +02:00
2022-12-18 17:41:35 +01:00
def test_extract_stdout ( capsys ):
# Act
extract_otp_secret_keys . main ([ 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured . err == ''
2022-12-24 01:59:35 +01:00
def test_extract_non_existent_file ( capsys ):
# Act
with raises ( SystemExit ) as e :
extract_otp_secret_keys . main ([ 'test/non_existent_file.txt' ])
# Assert
captured = capsys . readouterr ()
expected_stderr = ' \n ERROR: Input file provided is non-existent or not a file. \n input file: test/non_existent_file.txt \n '
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
2022-12-18 17:41:35 +01:00
def test_extract_stdin_stdout ( capsys , monkeypatch ):
2022-12-18 21:45:32 +01:00
# Arrange
2022-12-18 17:41:35 +01:00
monkeypatch . setattr ( 'sys.stdin' , StringIO ( read_file_to_str ( 'example_export.txt' )))
# Act
extract_otp_secret_keys . main ([ '-' ])
# Assert
captured = capsys . readouterr ()
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured . err == ''
2022-09-09 13:13:13 +02:00
def test_extract_csv ( capsys ):
2022-09-03 15:38:47 +02:00
# Arrange
cleanup ()
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
# Act
extract_otp_secret_keys . main ([ '-q' , '-c' , 'test_example_output.csv' , 'example_export.txt' ])
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
# Assert
expected_csv = read_csv ( 'example_output.csv' )
actual_csv = read_csv ( 'test_example_output.csv' )
2022-09-03 14:31:09 +02:00
2022-09-03 16:12:28 +02:00
assert actual_csv == expected_csv
2022-09-03 14:31:09 +02:00
2022-09-09 13:13:13 +02:00
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
2022-09-03 15:38:47 +02:00
# Clean up
cleanup ()
2022-09-03 14:31:09 +02:00
2022-09-03 16:12:28 +02:00
2022-12-18 19:24:07 +01:00
def test_extract_csv_stdout ( capsys ):
# Arrange
cleanup ()
# Act
2022-12-18 21:45:32 +01:00
extract_otp_secret_keys . main ([ '-c' , '-' , 'example_export.txt' ])
# Assert
assert not file_exits ( 'test_example_output.csv' )
captured = capsys . readouterr ()
expected_csv = read_csv ( 'example_output.csv' )
actual_csv = read_csv_str ( captured . out )
assert actual_csv == expected_csv
assert captured . err == ''
# Clean up
cleanup ()
def test_extract_stdin_and_csv_stdout ( capsys , monkeypatch ):
# Arrange
cleanup ()
monkeypatch . setattr ( 'sys.stdin' , StringIO ( read_file_to_str ( 'example_export.txt' )))
# Act
extract_otp_secret_keys . main ([ '-c' , '-' , '-' ])
2022-12-18 19:24:07 +01:00
# Assert
assert not file_exits ( 'test_example_output.csv' )
captured = capsys . readouterr ()
expected_csv = read_csv ( 'example_output.csv' )
actual_csv = read_csv_str ( captured . out )
assert actual_csv == expected_csv
assert captured . err == ''
# Clean up
cleanup ()
2022-12-04 12:23:39 +01:00
def test_keepass_csv ( capsys ):
'''Two csv files .totp and .htop are generated.'''
# Arrange
cleanup ()
# Act
extract_otp_secret_keys . main ([ '-q' , '-k' , 'test_example_keepass_output.csv' , 'example_export.txt' ])
# Assert
expected_totp_csv = read_csv ( 'example_keepass_output.totp.csv' )
expected_hotp_csv = read_csv ( 'example_keepass_output.hotp.csv' )
actual_totp_csv = read_csv ( 'test_example_keepass_output.totp.csv' )
actual_hotp_csv = read_csv ( 'test_example_keepass_output.hotp.csv' )
assert actual_totp_csv == expected_totp_csv
assert actual_hotp_csv == expected_hotp_csv
assert not file_exits ( 'test_example_keepass_output.csv' )
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
# Clean up
cleanup ()
2022-12-18 19:24:07 +01:00
def test_keepass_csv_stdout ( capsys ):
'''Two csv files .totp and .htop are generated.'''
# Arrange
cleanup ()
# Act
2022-12-18 21:45:32 +01:00
extract_otp_secret_keys . main ([ '-k' , '-' , 'test/example_export_only_totp.txt' ])
2022-12-18 19:24:07 +01:00
# Assert
expected_totp_csv = read_csv ( 'example_keepass_output.totp.csv' )
expected_hotp_csv = read_csv ( 'example_keepass_output.hotp.csv' )
assert not file_exits ( 'test_example_keepass_output.totp.csv' )
assert not file_exits ( 'test_example_keepass_output.hotp.csv' )
assert not file_exits ( 'test_example_keepass_output.csv' )
captured = capsys . readouterr ()
actual_totp_csv = read_csv_str ( captured . out )
assert actual_totp_csv == expected_totp_csv
assert captured . err == ''
# Clean up
cleanup ()
2022-12-04 12:23:39 +01:00
def test_single_keepass_csv ( capsys ):
'''Does not add .totp or .hotp pre-suffix'''
# Arrange
cleanup ()
# Act
extract_otp_secret_keys . main ([ '-q' , '-k' , 'test_example_keepass_output.csv' , 'test/example_export_only_totp.txt' ])
# Assert
expected_totp_csv = read_csv ( 'example_keepass_output.totp.csv' )
actual_totp_csv = read_csv ( 'test_example_keepass_output.csv' )
assert actual_totp_csv == expected_totp_csv
assert not file_exits ( 'test_example_keepass_output.totp.csv' )
assert not file_exits ( 'test_example_keepass_output.hotp.csv' )
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
# Clean up
cleanup ()
2022-09-09 13:13:13 +02:00
def test_extract_json ( capsys ):
2022-09-03 15:38:47 +02:00
# Arrange
cleanup ()
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
# Act
extract_otp_secret_keys . main ([ '-q' , '-j' , 'test_example_output.json' , 'example_export.txt' ])
2022-09-03 14:31:09 +02:00
2022-09-03 23:47:43 +02:00
# Assert
2022-09-03 15:38:47 +02:00
expected_json = read_json ( 'example_output.json' )
actual_json = read_json ( 'test_example_output.json' )
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
assert actual_json == expected_json
2022-09-03 14:31:09 +02:00
2022-09-09 13:13:13 +02:00
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
2022-09-03 15:38:47 +02:00
# Clean up
cleanup ()
2022-09-03 16:12:28 +02:00
2022-12-18 19:24:07 +01:00
def test_extract_json_stdout ( capsys ):
# Arrange
cleanup ()
# Act
2022-12-18 21:45:32 +01:00
extract_otp_secret_keys . main ([ '-j' , '-' , 'example_export.txt' ])
2022-12-18 19:24:07 +01:00
# Assert
expected_json = read_json ( 'example_output.json' )
assert not file_exits ( 'test_example_output.json' )
captured = capsys . readouterr ()
actual_json = read_json_str ( captured . out )
assert actual_json == expected_json
assert captured . err == ''
# Clean up
cleanup ()
2022-09-07 21:58:03 +02:00
def test_extract_not_encoded_plus ( capsys ):
# Act
extract_otp_secret_keys . main ([ 'test/test_plus_problem_export.txt' ])
# Assert
captured = capsys . readouterr ()
2022-12-04 12:23:39 +01:00
expected_stdout = '''Name: SerenityLabs:test1@serenitylabs.co.uk
Secret: A4RFDYMF4GSLUIBQV4ZP67OJEZ2XUQVM
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
2022-12-04 12:23:39 +01:00
Name: SerenityLabs:test2@serenitylabs.co.uk
Secret: SCDDZ7PW5MOZLE3PQCAZM7L4S35K3UDX
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
2022-12-04 12:23:39 +01:00
Name: SerenityLabs:test3@serenitylabs.co.uk
Secret: TR76272RVYO6EAEY2FX7W7R7KUDEGPJ4
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
2022-12-04 12:23:39 +01:00
Name: SerenityLabs:test4@serenitylabs.co.uk
Secret: N2ILWSXSJUQUB7S6NONPJSC62NPG7EXN
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
'''
assert captured . out == expected_stdout
assert captured . err == ''
2022-12-19 16:39:28 +01:00
2022-09-03 23:47:43 +02:00
def test_extract_printqr ( capsys ):
# Act
extract_otp_secret_keys . main ([ '-p' , 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
expected_stdout = read_file_to_str ( 'test/printqr_output.txt' )
assert captured . out == expected_stdout
assert captured . err == ''
2022-09-09 13:13:13 +02:00
def test_extract_saveqr ( capsys ):
# Arrange
cleanup ()
# Act
extract_otp_secret_keys . main ([ '-q' , '-s' , 'testout/qr/' , 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
assert path . isfile ( 'testout/qr/1-piraspberrypi-raspberrypi.png' )
assert path . isfile ( 'testout/qr/2-piraspberrypi.png' )
assert path . isfile ( 'testout/qr/3-piraspberrypi.png' )
assert path . isfile ( 'testout/qr/4-piraspberrypi-raspberrypi.png' )
# Clean up
cleanup ()
2022-12-19 22:22:23 +01:00
@mark.skipif ( implementation . name == 'pypy' , reason = "Encoding problems in verbose mode in pypy." )
2022-09-04 07:42:27 +02:00
def test_extract_verbose ( capsys ):
# Act
extract_otp_secret_keys . main ([ '-v' , 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
expected_stdout = read_file_to_str ( 'test/print_verbose_output.txt' )
assert captured . out == expected_stdout
assert captured . err == ''
2022-09-09 13:13:13 +02:00
def test_extract_debug ( capsys ):
# Act
extract_otp_secret_keys . main ([ '-vv' , 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
expected_stdout = read_file_to_str ( 'test/print_verbose_output.txt' )
assert len ( captured . out ) > len ( expected_stdout )
assert "DEBUG: " in captured . out
assert captured . err == ''
def test_extract_help ( capsys ):
2022-12-24 01:59:35 +01:00
with raises ( SystemExit ) as e :
2022-09-09 13:13:13 +02:00
# Act
extract_otp_secret_keys . main ([ '-h' ])
# Assert
captured = capsys . readouterr ()
assert len ( captured . out ) > 0
assert "-h, --help" in captured . out and "--verbose, -v" in captured . out
assert captured . err == ''
2022-12-24 01:59:35 +01:00
assert e . type == SystemExit
assert e . value . code == 0
2022-09-09 13:13:13 +02:00
2022-12-04 12:23:39 +01:00
2022-12-18 21:45:32 +01:00
def test_extract_no_arguments ( capsys ):
# Act
2022-12-24 01:59:35 +01:00
with raises ( SystemExit ) as e :
2022-12-18 21:45:32 +01:00
extract_otp_secret_keys . main ([])
# Assert
captured = capsys . readouterr ()
expected_err_msg = 'error: the following arguments are required: infile'
assert expected_err_msg in captured . err
assert captured . out == ''
2022-12-24 01:59:35 +01:00
assert e . value . code == 2
assert e . type == SystemExit
2022-12-18 21:45:32 +01:00
2022-11-29 04:19:35 +08:00
def test_verbose_and_quiet ( capsys ):
2022-12-24 01:59:35 +01:00
with raises ( SystemExit ) as e :
2022-11-29 04:19:35 +08:00
# Act
extract_otp_secret_keys . main ([ '-v' , '-q' , 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
2022-12-18 19:24:07 +01:00
assert len ( captured . err ) > 0
2022-12-18 21:45:32 +01:00
assert 'error: argument --quiet/-q: not allowed with argument --verbose/-v' in captured . err
2022-12-18 19:24:07 +01:00
assert captured . out == ''
2022-12-24 01:59:35 +01:00
assert e . value . code == 2
assert e . type == SystemExit
2022-09-09 13:13:13 +02:00
2022-12-04 12:23:39 +01:00
2022-12-16 12:43:32 +01:00
def test_wrong_data ( capsys ):
2022-12-24 01:59:35 +01:00
with raises ( SystemExit ) as e :
2022-12-16 12:43:32 +01:00
# Act
extract_otp_secret_keys . main ([ 'test/test_export_wrong_data.txt' ])
# Assert
captured = capsys . readouterr ()
2022-12-18 19:24:07 +01:00
expected_stderr = '''
2022-12-16 12:43:32 +01:00
ERROR: Cannot decode otpauth-migration migration payload.
data=XXXX
'''
2022-12-18 19:24:07 +01:00
assert captured . err == expected_stderr
assert captured . out == ''
2022-12-24 01:59:35 +01:00
assert e . value . code == 1
assert e . type == SystemExit
2022-12-16 12:43:32 +01:00
def test_wrong_content ( capsys ):
2022-12-24 01:59:35 +01:00
with raises ( SystemExit ) as e :
2022-12-16 12:43:32 +01:00
# Act
extract_otp_secret_keys . main ([ 'test/test_export_wrong_content.txt' ])
# Assert
captured = capsys . readouterr ()
2022-12-18 19:24:07 +01:00
expected_stderr = '''
2022-12-16 12:43:32 +01:00
WARN: line is not a otpauth-migration:// URL
input file: test/test_export_wrong_content.txt
line "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."
Probably a wrong file was given
ERROR: no data query parameter in input URL
input file: test/test_export_wrong_content.txt
line "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."
Probably a wrong file was given
'''
2022-12-18 19:24:07 +01:00
assert captured . out == ''
assert captured . err == expected_stderr
2022-12-24 01:59:35 +01:00
assert e . value . code == 1
assert e . type == SystemExit
2022-12-16 12:43:32 +01:00
def test_wrong_prefix ( capsys ):
# Act
extract_otp_secret_keys . main ([ 'test/test_export_wrong_prefix.txt' ])
# Assert
captured = capsys . readouterr ()
2022-12-18 19:24:07 +01:00
expected_stderr = '''
2022-12-16 12:43:32 +01:00
WARN: line is not a otpauth-migration:// URL
input file: test/test_export_wrong_prefix.txt
line "QR-Code:otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B %2F%2F%2F%2F%2F 8B"
Probably a wrong file was given
2022-12-18 19:24:07 +01:00
'''
expected_stdout = '''Name: pi@raspberrypi
2022-12-16 12:43:32 +01:00
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
'''
assert captured . out == expected_stdout
2022-12-18 19:24:07 +01:00
assert captured . err == expected_stderr
2022-12-16 12:43:32 +01:00
2022-12-04 12:23:39 +01:00
def test_add_pre_suffix ( capsys ):
assert extract_otp_secret_keys . add_pre_suffix ( "name.csv" , "totp" ) == "name.totp.csv"
assert extract_otp_secret_keys . add_pre_suffix ( "name.csv" , "" ) == "name..csv"
assert extract_otp_secret_keys . add_pre_suffix ( "name" , "totp" ) == "name.totp"
2022-12-24 01:59:35 +01:00
def test_img_qr_reader_from_file_happy_path ( capsys ):
# Act
extract_otp_secret_keys . main ([ 'test/test_googleauth_export.png' ])
# Assert
captured = capsys . readouterr ()
expected_stdout = \
'''Name: Test1:test1@example1.com
Secret: JBSWY3DPEHPK3PXP
Issuer: Test1
Type: totp
Name: Test2:test2@example2.com
Secret: JBSWY3DPEHPK3PXQ
Issuer: Test2
Type: totp
Name: Test3:test3@example3.com
Secret: JBSWY3DPEHPK3PXR
Issuer: Test3
Type: totp
'''
assert captured . out == expected_stdout
assert captured . err == ''
def test_img_qr_reader_from_stdin ( capsys , monkeypatch ):
# Arrange
# sys.stdin.buffer should be monkey patched, but it does not work
monkeypatch . setattr ( 'sys.stdin' , read_binary_file_as_stream ( 'test/test_googleauth_export.png' ))
# Act
extract_otp_secret_keys . main ([ '=' ])
# Assert
captured = capsys . readouterr ()
expected_stdout = \
'''Name: Test1:test1@example1.com
Secret: JBSWY3DPEHPK3PXP
Issuer: Test1
Type: totp
Name: Test2:test2@example2.com
Secret: JBSWY3DPEHPK3PXQ
Issuer: Test2
Type: totp
Name: Test3:test3@example3.com
Secret: JBSWY3DPEHPK3PXR
Issuer: Test3
Type: totp
'''
assert captured . out == expected_stdout
assert captured . err == ''
def test_img_qr_reader_no_qr_code_in_image ( capsys ):
# Act
with raises ( SystemExit ) as e :
extract_otp_secret_keys . main ([ 'test/lena_std.tif' ])
# Assert
captured = capsys . readouterr ()
expected_stderr = ' \n ERROR: Unable to read QR Code from file. \n input file: test/lena_std.tif \n '
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
def test_img_qr_reader_nonexistent_file ( capsys ):
# Act
with raises ( SystemExit ) as e :
extract_otp_secret_keys . main ([ 'test/nonexistent.bmp' ])
# Assert
captured = capsys . readouterr ()
expected_stderr = ' \n ERROR: Input file provided is non-existent or not a file. \n input file: test/nonexistent.bmp \n '
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
def test_non_image_file ( capsys ):
# Act
with raises ( SystemExit ) as e :
extract_otp_secret_keys . main ([ 'test/text_masquerading_as_image.jpeg' ])
# Assert
captured = capsys . readouterr ()
expected_stderr = '''
WARN: line is not a otpauth-migration:// URL
input file: test/text_masquerading_as_image.jpeg
line "This is just a text file masquerading as an image file."
Probably a wrong file was given
ERROR: no data query parameter in input URL
input file: test/text_masquerading_as_image.jpeg
line "This is just a text file masquerading as an image file."
Probably a wrong file was given
'''
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
2022-09-03 15:38:47 +02:00
def cleanup ():
2022-12-04 12:23:39 +01:00
remove_files ( 'test_example_*.csv' )
remove_files ( 'test_example_*.json' )
2022-09-09 13:13:13 +02:00
remove_dir_with_files ( 'testout/' )
2022-12-18 17:41:35 +01:00
EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT = '''Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp
Counter: 4
2022-12-19 22:01:08 +01:00
Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
2022-12-18 17:41:35 +01:00
'''