From f19fde48c6b3f65c164c931286e72a0d254c1fb3 Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Tue, 29 Mar 2022 20:44:03 +0200 Subject: Add the -P param and improve tests --- CHANGELOG.md | 22 +++++ COPYING | 2 +- LICENSE | 2 +- otp2289/__init__.py | 2 +- otp2289/__main__.py | 105 ++++++++++++++++-------- tests/test_generator.py | 152 ++++++++++++++++++++++------------- tests/test_main.py | 207 +++++++++++++++++++++++++++++++++++++++--------- tests/test_server.py | 77 +++++++++++------- tests/test_static.py | 57 ++++++++----- 9 files changed, 450 insertions(+), 176 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4288634 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +## [1.1.0](https://github.com/blackm0re/pyotp2289/tree/1.1.0) (2022-03-29) + +[Full Changelog](https://github.com/blackm0re/pyotp2289/compare/1.0.0...1.1.0) + +**Changes:** + +- (CLI) a password can now be set using the *OTP2289_PASSWORD* env. variable + +- (CLI) a new parameter *-P* / *--force-password-prompt* can be used in order to force password prompt + +- improved tests for CLI + +- use *setuptools* instead of the deprecated *distutils* + + +# [1.0.0](https://github.com/blackm0re/pyotp2289/tree/1.0.0) (2020-04-07) + +**Changes:** + +- Initial release diff --git a/COPYING b/COPYING index 968627b..e7fb5f0 100644 --- a/COPYING +++ b/COPYING @@ -1,6 +1,6 @@ SPDX-License-Identifier: BSD-2-Clause-FreeBSD -Copyright (c) 2020, Simeon Simeonov +Copyright (c) 2020-2022, Simeon Simeonov All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/LICENSE b/LICENSE index 968627b..e7fb5f0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ SPDX-License-Identifier: BSD-2-Clause-FreeBSD -Copyright (c) 2020, Simeon Simeonov +Copyright (c) 2020-2022, Simeon Simeonov All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/otp2289/__init__.py b/otp2289/__init__.py index 608595b..59694a7 100644 --- a/otp2289/__init__.py +++ b/otp2289/__init__.py @@ -40,7 +40,7 @@ from .server import ( ) __author__ = 'Simeon Simeonov' -__version__ = '1.1.0-beta1' +__version__ = '1.1.0' __license__ = 'BSD 2-Clause' diff --git a/otp2289/__main__.py b/otp2289/__main__.py index 471ae24..78e60e8 100644 --- a/otp2289/__main__.py +++ b/otp2289/__main__.py @@ -135,6 +135,48 @@ def generate_otp_range(args: argparse.Namespace) -> str: ) +def get_password(args: argparse.Namespace) -> str: + """ + Extract the provided password using the defined argparse arguments + + :param args: The arguments assigned from argparse + :type args: argparse.Namespace + + :raises KeyboardInterrupt: If the password prompt is interrupted + + :return: The extrated password string + :rtype: str + """ + if args.force_password_prompt: + while True: + password = getpass.getpass() + if not args.initiate_new_sequence or password == getpass.getpass( + 'Repeat password: ' + ): + break + eprint('The passwords do not match') + return password + + if not args.password: + password = os.environ.get('OTP2289_PASSWORD') + if password is not None: + return password + while True: + password = getpass.getpass() + if not args.initiate_new_sequence or password == getpass.getpass( + 'Repeat password: ' + ): + break + eprint('The passwords do not match') + return password + + if os.path.isfile(args.password): + with io.open(args.password, 'r', encoding='utf-8') as fp: + return fp.readline().strip() + + return args.password + + def get_rnd_seed() -> str: """ Returns a random seed in the format: @@ -192,6 +234,7 @@ def main(args=None): description='The following options are available', ) group = parser.add_mutually_exclusive_group(required=True) + password_group = parser.add_mutually_exclusive_group() group.add_argument( '--generate-otp-range', action='store_true', @@ -217,6 +260,28 @@ def main(args=None): 'and always outputs hex (ignores -f).' ), ) + password_group.add_argument( + '-P', + '--force-password-prompt', + dest='force_password_prompt', + action='store_true', + help=( + 'Force password prompt even if the env. variable ' + '"OTP2289_PASSWORD" is set' + ), + ) + password_group.add_argument( + '-p', + '--password', + metavar='', + type=str, + dest='password', + default='', + help=( + 'The password or path to password file ' + '(default & recommended: prompt for passwd)' + ), + ) parser.add_argument( '-a', '--hash-algorithm', @@ -253,18 +318,6 @@ def main(args=None): default=500, help='The step. Default for initiating a new sequence is: 500', ) - parser.add_argument( - '-p', - '--password', - metavar='', - type=str, - dest='password', - default='', - help=( - 'The password or path to password file ' - '(default & recommended: prompt for passwd)' - ), - ) parser.add_argument( '-q', '--quiet', @@ -303,26 +356,14 @@ def main(args=None): ) args = parser.parse_args(args) # handle the password before everything else - if not args.password: - try: - while True: - args.password = getpass.getpass() - if ( - not args.initiate_new_sequence - or args.password == getpass.getpass('Repeat password: ') - ): - break - eprint('The passwords do not match') - except KeyboardInterrupt: - eprint(os.linesep + 'Prompt terminated') - sys.exit(errno.EACCES) - elif os.path.isfile(args.password): - try: - with io.open(args.password, 'r', encoding='utf-8') as fp: - args.password = fp.readline().strip() - except Exception as exp: - eprint(f'Unable to open password file: {exp}') - sys.exit(1) + try: + args.password = get_password(args) + except KeyboardInterrupt: + eprint(os.linesep + 'Prompt terminated') + sys.exit(errno.EACCES) + except Exception as exp: + eprint(f'Unable to fetch password: {exp}') + sys.exit(1) try: if args.initiate_new_sequence: print(initiate_new_sequence(args)) diff --git a/tests/test_generator.py b/tests/test_generator.py index 50274e2..aa795a3 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020, Simeon Simeonov +# Copyright (c) 2020-2022, Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,9 +31,11 @@ import otp2289 def test_caller_exceptions(): """Tests the exceptions when calling an initialized object""" - gen = otp2289.OTPGenerator('This is a test.'.encode(), - 'TeSt', - otp2289.OTP_ALGO_MD5) + gen = otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeSt', + otp2289.OTP_ALGO_MD5, + ) with pytest.raises(otp2289.OTPGeneratorException) as exc_info: gen.generate_otp_words('3') assert exc_info.type is otp2289.OTPGeneratorException @@ -62,50 +64,72 @@ def test_constructor_exceptions(): """ # test the otp2289.OTPGenerator __init__ and validators with pytest.raises(otp2289.OTPGeneratorException) as exc_info: - otp2289.OTPGenerator('This is a test.'.encode(), - 'TeStø'.encode(), - otp2289.OTP_ALGO_MD5) + otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeStø'.encode(), + otp2289.OTP_ALGO_MD5, + ) assert exc_info.type is otp2289.OTPGeneratorException assert exc_info.value.args[0] == 'Seed must be a string' with pytest.raises(otp2289.OTPGeneratorException) as exc_info: - otp2289.OTPGenerator('This is a test.'.encode(), - 'TeStøtEsTteSTteStTest', - otp2289.OTP_ALGO_SHA1) + otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeStøtEsTteSTteStTest', + otp2289.OTP_ALGO_SHA1, + ) assert exc_info.type is otp2289.OTPGeneratorException - assert exc_info.value.args[0] == ('The seed MUST be of 1 to 16 ' - 'characters in length') + assert exc_info.value.args[0] == ( + 'The seed MUST be of 1 to 16 characters in length' + ) with pytest.raises(otp2289.OTPGeneratorException) as exc_info: - otp2289.OTPGenerator('This is a test.'.encode(), - 'TeStø', - otp2289.OTP_ALGO_SHA1) + otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeStø', + otp2289.OTP_ALGO_SHA1, + ) assert exc_info.type is otp2289.OTPGeneratorException - assert exc_info.value.args[0] == ('The seed MUST consist of purely ' - 'alphanumeric characters') + assert exc_info.value.args[0] == ( + 'The seed MUST consist of purely alphanumeric characters' + ) with pytest.raises(otp2289.OTPGeneratorException) as exc_info: - otp2289.OTPGenerator('This is a test.'.encode(), 'TeSt', 9) + otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeSt', + 9, + ) assert exc_info.type is otp2289.OTPGeneratorException assert exc_info.value.args[0] == ( - 'hash_algo is not among the known algorithms') + 'hash_algo is not among the known algorithms' + ) with pytest.raises(otp2289.OTPGeneratorException) as exc_info: - otp2289.OTPGenerator('This is a test.'.encode(), 'TeSt', b'md5') + otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeSt', + b'md5', + ) assert exc_info.type is otp2289.OTPGeneratorException assert exc_info.value.args[0] == 'hash_algo must be an int or a str' # test the package structure as well with pytest.raises(otp2289.generator.OTPGeneratorException) as exc_info: - otp2289.generator.OTPGenerator('This is a test.'.encode(), - 'TeSt', - 'foo') + otp2289.generator.OTPGenerator( + 'This is a test.'.encode(), + 'TeSt', + 'foo', + ) assert exc_info.type is otp2289.generator.OTPGeneratorException - assert exc_info.value.args[0] == ('foo is not supported by this version ' - 'of the hashlib module') + assert exc_info.value.args[0] == ( + 'foo is not supported by this version of the hashlib module' + ) with pytest.raises(otp2289.OTPGeneratorException) as exc_info: otp2289.OTPGenerator('1234567', 'TeSt', otp2289.OTP_ALGO_MD5) assert exc_info.type is otp2289.OTPGeneratorException assert exc_info.value.args[0] == 'Password must be a byte-string' with pytest.raises(otp2289.OTPGeneratorException) as exc_info: - otp2289.OTPGenerator('1234567'.encode(), - 'TeSt', - otp2289.OTP_ALGO_MD5) + otp2289.OTPGenerator( + '1234567'.encode(), + 'TeSt', + otp2289.OTP_ALGO_MD5, + ) assert exc_info.type is otp2289.OTPGeneratorException assert exc_info.value.args[0] == 'Password must be longer than 10 bytes' @@ -118,9 +142,11 @@ def test_md5(): """ # We could run this in a loop, but I guess "Readability counts." # pass='This is a test.', seed='TeSt' - gen = otp2289.OTPGenerator('This is a test.'.encode(), - 'TeSt', - otp2289.OTP_ALGO_MD5) + gen = otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeSt', + otp2289.OTP_ALGO_MD5, + ) res_words = gen.generate_otp_words(0) res_hex = gen.generate_otp_hexdigest(0) assert isinstance(res_words, str) @@ -131,16 +157,20 @@ def test_md5(): assert gen.generate_otp_hexdigest(1) == '0x7965e05436f5029f' assert gen.generate_otp_words(1) == 'EASE OIL FUM CURE AWRY AVIS' assert gen.generate_otp_hexdigest_from_challenge('otp-md5 1 TeSt') == ( - '0x7965e05436f5029f') + '0x7965e05436f5029f' + ) assert gen.generate_otp_words_from_challenge('otp-md5 1 TeSt') == ( - 'EASE OIL FUM CURE AWRY AVIS') + 'EASE OIL FUM CURE AWRY AVIS' + ) # step 99 assert gen.generate_otp_hexdigest(99) == '0x50fe1962c4965880' assert gen.generate_otp_words(99) == 'BAIL TUFT BITS GANG CHEF THY' assert gen.generate_otp_hexdigest_from_challenge('otp-md5 99 TeSt') == ( - '0x50fe1962c4965880') + '0x50fe1962c4965880' + ) assert gen.generate_otp_words_from_challenge('otp-md5 99 TeSt') == ( - 'BAIL TUFT BITS GANG CHEF THY') + 'BAIL TUFT BITS GANG CHEF THY' + ) # iterator test hexdigests = list(gen.hexdigest_range(105)) # testing the range itself words = list(gen.words_range(99)) @@ -153,9 +183,11 @@ def test_md5(): assert words[1] == 'EASE OIL FUM CURE AWRY AVIS' assert words[99] == 'BAIL TUFT BITS GANG CHEF THY' # pass='AbCdEfGhIjK', seed='alpha1' - gen = otp2289.OTPGenerator('AbCdEfGhIjK'.encode(), - 'alpha1', - otp2289.OTP_ALGO_MD5) + gen = otp2289.OTPGenerator( + 'AbCdEfGhIjK'.encode(), + 'alpha1', + otp2289.OTP_ALGO_MD5, + ) assert gen.generate_otp_hexdigest(0) == '0x87066dd9644bf206' assert gen.generate_otp_words(0) == 'FULL PEW DOWN ONCE MORT ARC' assert gen.generate_otp_hexdigest(1) == '0x7cd34c1040add14b' @@ -163,9 +195,11 @@ def test_md5(): assert gen.generate_otp_hexdigest(99) == '0x5aa37a81f212146c' assert gen.generate_otp_words(99) == 'BODE HOP JAKE STOW JUT RAP' # pass="OTP's are good", seed='correct' - gen = otp2289.OTPGenerator("OTP's are good".encode(), - 'correct', - otp2289.OTP_ALGO_MD5) + gen = otp2289.OTPGenerator( + "OTP's are good".encode(), + 'correct', + otp2289.OTP_ALGO_MD5, + ) assert gen.generate_otp_hexdigest(0) == '0xf205753943de4cf9' assert gen.generate_otp_words(0) == 'ULAN NEW ARMY FUSE SUIT EYED' assert gen.generate_otp_hexdigest(1) == '0xddcdac956f234937' @@ -181,9 +215,11 @@ def test_sha1(): Those are the tests from 'RFC-2289 Appendix C - OTP Verification Examples' """ # pass='This is a test.', seed='TeSt' - gen = otp2289.OTPGenerator('This is a test.'.encode(), - 'TeSt', - otp2289.OTP_ALGO_SHA1) + gen = otp2289.OTPGenerator( + 'This is a test.'.encode(), + 'TeSt', + otp2289.OTP_ALGO_SHA1, + ) # step=0 res_hex = gen.generate_otp_hexdigest(0) res_words = gen.generate_otp_words(0) @@ -194,15 +230,19 @@ def test_sha1(): assert gen.generate_otp_hexdigest(1) == '0x63d936639734385b' assert gen.generate_otp_words(1) == 'CART OTTO HIVE ODE VAT NUT' assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 1 TeSt') == ( - '0x63d936639734385b') + '0x63d936639734385b' + ) assert gen.generate_otp_words_from_challenge('otp-sha1 1 TeSt') == ( - 'CART OTTO HIVE ODE VAT NUT') + 'CART OTTO HIVE ODE VAT NUT' + ) assert gen.generate_otp_hexdigest(99) == '0x87fec7768b73ccf9' assert gen.generate_otp_words(99) == 'GAFF WAIT SKID GIG SKY EYED' assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 99 TeSt') == ( - '0x87fec7768b73ccf9') + '0x87fec7768b73ccf9' + ) assert gen.generate_otp_words_from_challenge('otp-sha1 99 TeSt') == ( - 'GAFF WAIT SKID GIG SKY EYED') + 'GAFF WAIT SKID GIG SKY EYED' + ) # iterator test hexdigests = list(gen.hexdigest_range(105)) words = list(gen.words_range(99)) @@ -215,9 +255,11 @@ def test_sha1(): assert words[1] == 'CART OTTO HIVE ODE VAT NUT' assert words[99] == 'GAFF WAIT SKID GIG SKY EYED' # pass='AbCdEfGhIjK', seed='alpha1' - gen = otp2289.OTPGenerator('AbCdEfGhIjK'.encode(), - 'alpha1', - otp2289.OTP_ALGO_SHA1) + gen = otp2289.OTPGenerator( + 'AbCdEfGhIjK'.encode(), + 'alpha1', + otp2289.OTP_ALGO_SHA1, + ) assert gen.generate_otp_hexdigest(0) == '0xad85f658ebe383c9' assert gen.generate_otp_words(0) == 'LEST OR HEEL SCOT ROB SUIT' assert gen.generate_otp_hexdigest(1) == '0xd07ce229b5cf119b' @@ -225,9 +267,11 @@ def test_sha1(): assert gen.generate_otp_hexdigest(99) == '0x27bc71035aaf3dc6' assert gen.generate_otp_words(99) == 'MAY STAR TIN LYON VEDA STAN' # pass="OTP's are good", seed='correct' - gen = otp2289.OTPGenerator("OTP's are good".encode(), - 'correct', - otp2289.OTP_ALGO_SHA1) + gen = otp2289.OTPGenerator( + "OTP's are good".encode(), + 'correct', + otp2289.OTP_ALGO_SHA1, + ) assert gen.generate_otp_hexdigest(0) == '0xd51f3e99bf8e6f0b' assert gen.generate_otp_words(0) == 'RUST WELT KICK FELL TAIL FRAU' assert gen.generate_otp_hexdigest(1) == '0x82aeb52d943774e4' diff --git a/tests/test_main.py b/tests/test_main.py index b48f625..a331274 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020, Simeon Simeonov +# Copyright (c) 2020-2022, Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -25,6 +25,7 @@ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for otp2289.__main__""" import os +import unittest.mock import pytest @@ -33,28 +34,34 @@ from otp2289.__main__ import main def test_main_generate_otp_response(capsys): """tests main""" - args = ['--generate-otp-response', - '-a', - 'sha1', - '-i', - '99', - '-s', - 'TesT', - '-p', - 'This is a test.'] + args = [ + '--generate-otp-response', + '-a', + 'sha1', + '-i', + '99', + '-s', + 'TesT', + '-p', + 'This is a test.', + ] with pytest.raises(SystemExit) as exit_info: main(args) captured = capsys.readouterr() - assert captured.out == (f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' - f'0x87fec7768b73ccf9{os.linesep}') + assert captured.out == ( + f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' + f'0x87fec7768b73ccf9{os.linesep}' + ) assert exit_info.type == SystemExit assert exit_info.value.code == 0 args.extend(['-f', 'token']) with pytest.raises(SystemExit) as exit_info: main(args) captured = capsys.readouterr() - assert captured.out == (f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' - f'GAFF WAIT SKID GIG SKY EYED{os.linesep}') + assert captured.out == ( + f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' + f'GAFF WAIT SKID GIG SKY EYED{os.linesep}' + ) assert exit_info.type == SystemExit assert exit_info.value.code == 0 args.append('-q') @@ -66,61 +73,187 @@ def test_main_generate_otp_response(capsys): assert exit_info.value.code == 0 +def test_main_generate_otp_response_env_passwd(capsys): + """tests main by fetching password from the env. var. 'OTP2289_PASSWORD'""" + args = [ + '--generate-otp-response', + '-a', + 'sha1', + '-i', + '99', + '-s', + 'TesT', + ] + with unittest.mock.patch.dict( + os.environ, {'OTP2289_PASSWORD': 'This is a test.'} + ): + with pytest.raises(SystemExit) as exit_info: + main(args) + captured = capsys.readouterr() + assert captured.out == ( + f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' + f'0x87fec7768b73ccf9{os.linesep}' + ) + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + args.extend(['-f', 'token']) + with pytest.raises(SystemExit) as exit_info: + main(args) + captured = capsys.readouterr() + assert captured.out == ( + f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' + f'GAFF WAIT SKID GIG SKY EYED{os.linesep}' + ) + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + args.append('-q') + with pytest.raises(SystemExit) as exit_info: + main(args) + captured = capsys.readouterr() + assert captured.out == f'GAFF WAIT SKID GIG SKY EYED{os.linesep}' + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + + def test_main_generate_otp_range(capsys): """tests main""" - args = ['--generate-otp-range', + args = [ + '--generate-otp-range', + '-i', + '2', + '-s', + 'TesT', + '-r', + '5', + '-p', + 'This is a test.', + ] + with pytest.raises(SystemExit) as exit_info: + main(args) + captured = capsys.readouterr() + assert captured.out == ( + f'Seed: TesT, Step: 2, Hash: md5, Range: 3' + f'{os.linesep}' + f'2: 0x4049f8b161669b7b{os.linesep}' + f'1: 0x7965e05436f5029f{os.linesep}' + f'0: 0x9e876134d90499dd{os.linesep}' + ) + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + args.append('-q') + with pytest.raises(SystemExit) as exit_info: + main(args) + captured = capsys.readouterr() + assert captured.out == ( + f'2: 0x4049f8b161669b7b{os.linesep}' + f'1: 0x7965e05436f5029f{os.linesep}' + f'0: 0x9e876134d90499dd{os.linesep}' + ) + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + args.extend(['-f', 'token']) + with pytest.raises(SystemExit) as exit_info: + main(args) + captured = capsys.readouterr() + assert captured.out == ( + f'2: THY AVON NO NECK COKE MOLL{os.linesep}' + f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}' + f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}' + ) + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + + +@pytest.mark.parametrize( + 'args', + [ + [ + '--generate-otp-range', + '-i', + '2', + '-s', + 'TesT', + '-r', + '5', + ], + [ + '--generate-otp-range', '-i', '2', '-s', 'TesT', '-r', '5', - '-p', - 'This is a test.'] + '-P', + ], + ], +) +@unittest.mock.patch('getpass.getpass', lambda *args: 'This is a test.') +def test_main_generate_otp_range_passwd_prompt(capsys, args): + """tests main by prompting for password (with or without -P)""" + args = [ + '--generate-otp-range', + '-i', + '2', + '-s', + 'TesT', + '-r', + '5', + ] with pytest.raises(SystemExit) as exit_info: main(args) captured = capsys.readouterr() - assert captured.out == (f'Seed: TesT, Step: 2, Hash: md5, Range: 3' - f'{os.linesep}' - f'2: 0x4049f8b161669b7b{os.linesep}' - f'1: 0x7965e05436f5029f{os.linesep}' - f'0: 0x9e876134d90499dd{os.linesep}') + assert captured.out == ( + f'Seed: TesT, Step: 2, Hash: md5, Range: 3' + f'{os.linesep}' + f'2: 0x4049f8b161669b7b{os.linesep}' + f'1: 0x7965e05436f5029f{os.linesep}' + f'0: 0x9e876134d90499dd{os.linesep}' + ) assert exit_info.type == SystemExit assert exit_info.value.code == 0 args.append('-q') with pytest.raises(SystemExit) as exit_info: main(args) captured = capsys.readouterr() - assert captured.out == (f'2: 0x4049f8b161669b7b{os.linesep}' - f'1: 0x7965e05436f5029f{os.linesep}' - f'0: 0x9e876134d90499dd{os.linesep}') + assert captured.out == ( + f'2: 0x4049f8b161669b7b{os.linesep}' + f'1: 0x7965e05436f5029f{os.linesep}' + f'0: 0x9e876134d90499dd{os.linesep}' + ) assert exit_info.type == SystemExit assert exit_info.value.code == 0 args.extend(['-f', 'token']) with pytest.raises(SystemExit) as exit_info: main(args) captured = capsys.readouterr() - assert captured.out == (f'2: THY AVON NO NECK COKE MOLL{os.linesep}' - f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}' - f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}') + assert captured.out == ( + f'2: THY AVON NO NECK COKE MOLL{os.linesep}' + f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}' + f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}' + ) assert exit_info.type == SystemExit assert exit_info.value.code == 0 def test_main_initiate(capsys): """tests main""" - args = ['--initiate-new-sequence', - '-i', - '500', - '-s', - 'TesT', - '-p', - 'This is a test.'] + args = [ + '--initiate-new-sequence', + '-i', + '500', + '-s', + 'TesT', + '-p', + 'This is a test.', + ] with pytest.raises(SystemExit) as exit_info: main(args) captured = capsys.readouterr() - assert captured.out == (f'Seed: TesT, Step: 500, Hash: md5{os.linesep}' - f'0x2b8d82b6ac14346c{os.linesep}') + assert captured.out == ( + f'Seed: TesT, Step: 500, Hash: md5{os.linesep}' + f'0x2b8d82b6ac14346c{os.linesep}' + ) assert exit_info.type == SystemExit assert exit_info.value.code == 0 args.append('-q') diff --git a/tests/test_server.py b/tests/test_server.py index 8d24cb8..3d3b77d 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020, Simeon Simeonov +# Copyright (c) 2020-2022, Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -33,41 +33,50 @@ import otp2289 def test_state_caller_exceptions(): """Tests the exceptions when calling the OTPState objects""" - state = otp2289.OTPState('0x7965e05436f5029f', - 1, - 'TeSt', - otp2289.OTP_ALGO_MD5) + state = otp2289.OTPState( + '0x7965e05436f5029f', + 1, + 'TeSt', + otp2289.OTP_ALGO_MD5, + ) with pytest.raises(otp2289.OTPInvalidResponse) as exc_info: state.response_validates('bla') assert exc_info.type is otp2289.OTPInvalidResponse assert exc_info.value.args[0] == ( - 'The response is neither a valid token or hex') + 'The response is neither a valid token or hex' + ) def test_state_constructor_exceptions(): """Tests the exceptions when initializing new OTPState objects""" with pytest.raises(otp2289.OTPStateException) as exc_info: - otp2289.OTPState('0x7965e05436f5029t', - 1, - 'TeStø'.encode(), - otp2289.OTP_ALGO_MD5) + otp2289.OTPState( + '0x7965e05436f5029t', + 1, + 'TeStø'.encode(), + otp2289.OTP_ALGO_MD5, + ) assert exc_info.type is otp2289.OTPStateException assert exc_info.value.args[0] == 'Seed must be a string' with pytest.raises(otp2289.OTPStateException) as exc_info: - otp2289.OTPState('0x7965e05436f5029t', - '1', - 'TeSt', - otp2289.OTP_ALGO_MD5) + otp2289.OTPState( + '0x7965e05436f5029t', + '1', + 'TeSt', + otp2289.OTP_ALGO_MD5, + ) assert exc_info.type is otp2289.OTPStateException assert exc_info.value.args[0] == 'Step value MUST be an int' def test_state_validation_md5(): """Tests the OTPState validation functionality for MD5""" - state = otp2289.OTPState('0x7965e05436f5029f', - 1, - 'TeSt', - otp2289.OTP_ALGO_MD5) + state = otp2289.OTPState( + '0x7965e05436f5029f', + 1, + 'TeSt', + otp2289.OTP_ALGO_MD5, + ) assert state.validated is False assert state.response_validates('0x9e876134d90499dd') is True assert state.response_validates('INCH SEA ANNE LONG AHEM TOUR') is True @@ -76,10 +85,12 @@ def test_state_validation_md5(): def test_state_validation_sha1(): """Tests the OTPState validation functionality for SHA1""" - state = otp2289.OTPState('0x63d936639734385b', - 1, - 'TeSt', - otp2289.OTP_ALGO_SHA1) + state = otp2289.OTPState( + '0x63d936639734385b', + 1, + 'TeSt', + otp2289.OTP_ALGO_SHA1, + ) assert state.validated is False assert state.response_validates('0xbb9e6ae1979d8ff4') is True assert state.response_validates('MILT VARY MAST OK SEES WENT') is True @@ -88,14 +99,20 @@ def test_state_validation_sha1(): def test_store(): """Tests the OTPStore functionality""" - store_data = {'sgs': {'ot_hex': '0x7965e05436f5029f', - 'current_step': 1, - 'seed': 'TeSt', - 'hash_algo': 'md5'}, - 'blackmore': {'ot_hex': '0x63d936639734385b', - 'current_step': 1, - 'seed': 'TeSt', - 'hash_algo': 'sha1'}} + store_data = { + 'sgs': { + 'ot_hex': '0x7965e05436f5029f', + 'current_step': 1, + 'seed': 'TeSt', + 'hash_algo': 'md5', + }, + 'blackmore': { + 'ot_hex': '0x63d936639734385b', + 'current_step': 1, + 'seed': 'TeSt', + 'hash_algo': 'sha1', + }, + } store = otp2289.OTPStore(store_data) assert len(store) == 2 assert isinstance(json.dumps(store.to_dict()), str) # serializable? diff --git a/tests/test_static.py b/tests/test_static.py index dc6e2d7..a942374 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020, Simeon Simeonov +# Copyright (c) 2020-2022, Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -33,42 +33,59 @@ import otp2289 def test_bytes_and_tokens(): """Tests the official hex and tokens defined in RFC2289""" assert binascii.unhexlify('9e876134d90499dd') == ( - otp2289.OTPGenerator.tokens_to_bytes('INCH SEA ANNE LONG AHEM TOUR')) + otp2289.OTPGenerator.tokens_to_bytes('INCH SEA ANNE LONG AHEM TOUR') + ) assert binascii.unhexlify('7965e05436f5029f') == ( - otp2289.OTPGenerator.tokens_to_bytes('EASE OIL FUM CURE AWRY AVIS')) + otp2289.OTPGenerator.tokens_to_bytes('EASE OIL FUM CURE AWRY AVIS') + ) assert binascii.unhexlify('50fe1962c4965880') == ( - otp2289.OTPGenerator.tokens_to_bytes('BAIL TUFT BITS GANG CHEF THY')) + otp2289.OTPGenerator.tokens_to_bytes('BAIL TUFT BITS GANG CHEF THY') + ) assert binascii.unhexlify('87066dd9644bf206') == ( - otp2289.OTPGenerator.tokens_to_bytes('FULL PEW DOWN ONCE MORT ARC')) + otp2289.OTPGenerator.tokens_to_bytes('FULL PEW DOWN ONCE MORT ARC') + ) assert binascii.unhexlify('7cd34c1040add14b') == ( - otp2289.OTPGenerator.tokens_to_bytes('FACT HOOF AT FIST SITE KENT')) + otp2289.OTPGenerator.tokens_to_bytes('FACT HOOF AT FIST SITE KENT') + ) assert binascii.unhexlify('5aa37a81f212146c') == ( - otp2289.OTPGenerator.tokens_to_bytes('BODE HOP JAKE STOW JUT RAP')) + otp2289.OTPGenerator.tokens_to_bytes('BODE HOP JAKE STOW JUT RAP') + ) assert binascii.unhexlify('f205753943de4cf9') == ( - otp2289.OTPGenerator.tokens_to_bytes('ULAN NEW ARMY FUSE SUIT EYED')) + otp2289.OTPGenerator.tokens_to_bytes('ULAN NEW ARMY FUSE SUIT EYED') + ) assert binascii.unhexlify('ddcdac956f234937') == ( - otp2289.OTPGenerator.tokens_to_bytes('SKIM CULT LOB SLAM POE HOWL')) + otp2289.OTPGenerator.tokens_to_bytes('SKIM CULT LOB SLAM POE HOWL') + ) assert binascii.unhexlify('b203e28fa525be47') == ( - otp2289.OTPGenerator.tokens_to_bytes('LONG IVY JULY AJAR BOND LEE')) - + otp2289.OTPGenerator.tokens_to_bytes('LONG IVY JULY AJAR BOND LEE') + ) assert binascii.unhexlify('bb9e6ae1979d8ff4') == ( - otp2289.OTPGenerator.tokens_to_bytes('MILT VARY MAST OK SEES WENT')) + otp2289.OTPGenerator.tokens_to_bytes('MILT VARY MAST OK SEES WENT') + ) assert binascii.unhexlify('63d936639734385b') == ( - otp2289.OTPGenerator.tokens_to_bytes('CART OTTO HIVE ODE VAT NUT')) + otp2289.OTPGenerator.tokens_to_bytes('CART OTTO HIVE ODE VAT NUT') + ) assert binascii.unhexlify('87fec7768b73ccf9') == ( - otp2289.OTPGenerator.tokens_to_bytes('GAFF WAIT SKID GIG SKY EYED')) + otp2289.OTPGenerator.tokens_to_bytes('GAFF WAIT SKID GIG SKY EYED') + ) assert binascii.unhexlify('ad85f658ebe383c9') == ( - otp2289.OTPGenerator.tokens_to_bytes('LEST OR HEEL SCOT ROB SUIT')) + otp2289.OTPGenerator.tokens_to_bytes('LEST OR HEEL SCOT ROB SUIT') + ) assert binascii.unhexlify('d07ce229b5cf119b') == ( - otp2289.OTPGenerator.tokens_to_bytes('RITE TAKE GELD COST TUNE RECK')) + otp2289.OTPGenerator.tokens_to_bytes('RITE TAKE GELD COST TUNE RECK') + ) assert binascii.unhexlify('27bc71035aaf3dc6') == ( - otp2289.OTPGenerator.tokens_to_bytes('MAY STAR TIN LYON VEDA STAN')) + otp2289.OTPGenerator.tokens_to_bytes('MAY STAR TIN LYON VEDA STAN') + ) assert binascii.unhexlify('d51f3e99bf8e6f0b') == ( - otp2289.OTPGenerator.tokens_to_bytes('RUST WELT KICK FELL TAIL FRAU')) + otp2289.OTPGenerator.tokens_to_bytes('RUST WELT KICK FELL TAIL FRAU') + ) assert binascii.unhexlify('82aeb52d943774e4') == ( - otp2289.OTPGenerator.tokens_to_bytes('FLIT DOSE ALSO MEW DRUM DEFY')) + otp2289.OTPGenerator.tokens_to_bytes('FLIT DOSE ALSO MEW DRUM DEFY') + ) assert binascii.unhexlify('4f296a74fe1567ec') == ( - otp2289.OTPGenerator.tokens_to_bytes('AURA ALOE HURL WING BERG WAIT')) + otp2289.OTPGenerator.tokens_to_bytes('AURA ALOE HURL WING BERG WAIT') + ) def test_random_bytes(): -- cgit v1.3