diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 045b0a43..3e78b87a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -50,7 +50,7 @@ jobs: - name: Install apt dependencies run: sudo apt-get update && sudo apt-get install -y python3-dev openssl libssl-dev gcc pkg-config libffi-dev libxml2-dev libxmlsec1-dev - name: Install dependencies - run: pip install -r piptools_requirements.txt && pip install -r requirements.txt + run: pip install -r piptools_requirements.txt && pip install -r piptools_requirements3.txt && pip install -r requirements3.txt - name: Run python unit tests run: make test_unit test-integration: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f372f3f7..c4d97ab4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,3 +7,18 @@ repos: additional_dependencies: - flake8 - flake8-tidy-imports +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.4.1 + hooks: + - id: mypy + additional_dependencies: + - --no-compile + - cffi==1.15.1 + - cryptography==41.0.1 + - pycparser==2.21 + - types-pyopenssl==23.2.0.1 + - types-pytz==2023.3.0.0 + - types-pyyaml==6.0.12.10 + - types-redis==4.6.0.0 + - types-requests==2.31.0.1 + - types-urllib3==1.26.25.13 diff --git a/Dockerfile b/Dockerfile index 7f716cc2..b8648f26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,12 +22,13 @@ COPY package.json /srv/confidant/ RUN npm install grunt-cli && \ npm install -COPY piptools_requirements.txt requirements.txt /srv/confidant/ +COPY piptools_requirements.txt piptools_requirements3.txt requirements3.txt /srv/confidant/ ENV PATH=/venv/bin:$PATH RUN virtualenv /venv --python=/usr/bin/python3.8 && \ pip install --no-cache -r piptools_requirements.txt && \ - pip install --no-cache -r requirements.txt + pip install --no-cache -r piptools_requirements3.txt && \ + pip install --no-cache -r requirements3.txt COPY .jshintrc Gruntfile.js /srv/confidant/ COPY confidant/public /srv/confidant/confidant/public diff --git a/actions_run_integration.sh b/actions_run_integration.sh index 183423fc..8aa42da6 100755 --- a/actions_run_integration.sh +++ b/actions_run_integration.sh @@ -2,5 +2,5 @@ cd /srv/confidant apt-get update && apt-get install -y python3-dev openssl libssl-dev gcc pkg-config libffi-dev libxml2-dev libxmlsec1-dev -pip install -r piptools_requirements.txt && pip install -r requirements.txt +pip install -r piptools_requirements.txt && pip install -r piptools_requirements3.txt && pip install -r requirements3.txt make test_integration diff --git a/piptools_requirements.txt b/piptools_requirements.txt index 578d5559..bf73b319 100644 --- a/piptools_requirements.txt +++ b/piptools_requirements.txt @@ -1,9 +1,11 @@ # -# This file is autogenerated by pip-compile with python 3.6 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: # -# pip-compile +# cd ~/src/confidant-private/upstream && run-piptools # -pip==21.3.1 -setuptools==59.1.1 +pip==23.1.2 + # via -r /code/piptools/bootstrap_ins/requirements.in +setuptools==68.0.0 + # via -r /code/piptools/bootstrap_ins/requirements.in diff --git a/piptools_requirements3.txt b/piptools_requirements3.txt new file mode 100644 index 00000000..bf73b319 --- /dev/null +++ b/piptools_requirements3.txt @@ -0,0 +1,11 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# cd ~/src/confidant-private/upstream && run-piptools +# + +pip==23.1.2 + # via -r /code/piptools/bootstrap_ins/requirements.in +setuptools==68.0.0 + # via -r /code/piptools/bootstrap_ins/requirements.in diff --git a/requirements.in b/requirements.in index 6f63a699..bf5f4166 100644 --- a/requirements.in +++ b/requirements.in @@ -3,13 +3,13 @@ # License: BSD # Upstream url: http://github.com/mitsuhiko/flask/ # Use: For API. -Flask +Flask==1.1.4 # Flask Scripting support for Flask # License: BSD # Upstream url: http://github.com/techniq/flask-script # Use: For CLI scripts. -Flask-Script +Flask-Script==2.0.5 # Dispatching system for parties to subscribe to events # Supports per-endpoint timing in flask @@ -22,14 +22,14 @@ blinker # License: Apache2 # Upstream url: https://github.com/boto/botocore # Use: For boto3 -botocore>=1.4.58 +botocore==1.12.227 # Boto3 is the Amazon Web Services (AWS) Software Development Kit (SDK) # for Python. # License: Apache2 # Upstream url: https://github.com/boto/boto3 # Use: For KMS -boto3>1.5.0 +boto3==1.9.227 # A Pythonic interface for Amazon's DynamoDB that supports Python 2 and 3. # License: MIT @@ -54,20 +54,21 @@ cffi>1.10.0 # License: BSD # Upstream url: https://github.com/fengsp/flask-session # Use: For shared sessions -Flask-Session +Flask-Session==0.2.3 # Redis # License: MIT # Upstream url: https://github.com/andymccurdy/redis-py # Use: For shared sessions redis +types-redis # Flask extension that configures the Flask application to redirect all # incoming requests to https # License: BSD # Upstream url: https://github.com/kennethreitz/flask-sslify # Use: For SSL redirection and HSTS -Flask-SSLify +Flask-SSLify==0.1.5 # Authomatic is a framework agnostic library for Python web applications # with a minimalistic but powerful interface which simplifies @@ -88,6 +89,7 @@ python3-saml>=1.15.0 # Upstream url: http://python-requests.org # Use: REST calls to external services requests>=2.22.0,<3.0.0 +types-requests # Provides enhanced HTTPS support for httplib and urllib2 using PyOpenSSL # License: BSD @@ -116,6 +118,7 @@ guard # Upstream url: http://pyyaml.org/wiki/PyYAML # Use: For parsing users.yaml PyYAML +types-PyYAML # License: MIT # Upstream url: http://gunicorn.org/ @@ -190,8 +193,12 @@ pytest-gevent # Timezones pytz +types-pytz # for jwt pyjwt>=2.6.0 jwcrypto cerberus + +# for typing +mypy diff --git a/requirements.txt b/requirements3.txt similarity index 60% rename from requirements.txt rename to requirements3.txt index 39ac99f7..48f4817f 100644 --- a/requirements.txt +++ b/requirements3.txt @@ -1,17 +1,16 @@ # -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: # -# pip-compile +# cd ~/src/confidant-private/upstream && run-piptools # - -attrs==19.3.0 - # via - # pytest - # toastedmarshmallow -authomatic==1.0.0 +async-timeout==4.0.2 + # via redis +attrs==23.1.0 + # via toastedmarshmallow +authomatic==1.2.1 # via -r requirements.in -blinker==1.4 +blinker==1.6.2 # via -r requirements.in boto3==1.9.227 # via @@ -25,27 +24,31 @@ botocore==1.12.227 # s3transfer cerberus==1.3.4 # via -r requirements.in -certifi==2019.6.16 +certifi==2023.5.7 # via requests -cffi==1.14.5 +cffi==1.15.1 # via # -r requirements.in # cryptography -chardet==3.0.4 +charset-normalizer==3.1.0 # via requests click==7.1.2 # via flask -coverage[toml]==6.2 +coverage[toml]==7.2.7 # via pytest-cov -cryptography==36.0.1 +cryptography==41.0.1 # via # -r requirements.in # jwcrypto # pyopenssl -deprecated==1.2.13 + # types-pyopenssl + # types-redis +deprecated==1.2.14 # via jwcrypto docutils==0.15.2 # via botocore +exceptiongroup==1.1.1 + # via pytest flask==1.1.4 # via # -r requirements.in @@ -58,21 +61,21 @@ flask-session==0.2.3 # via -r requirements.in flask-sslify==0.1.5 # via -r requirements.in -gevent==21.8.0 +gevent==22.10.2 # via # -r requirements.in # pytest-gevent -greenlet==1.1.2 +greenlet==2.0.2 # via # -r requirements.in # gevent guard==1.0.1 # via -r requirements.in -gunicorn==19.9.0 +gunicorn==20.1.0 # via -r requirements.in -idna==2.8 +idna==3.4 # via requests -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest isodate==0.6.1 # via python3-saml @@ -80,47 +83,47 @@ itsdangerous==1.1.0 # via flask jinja2==2.11.3 # via flask -jmespath==0.9.4 +jmespath==0.10.0 # via # boto3 # botocore -jwcrypto==1.4.2 +jwcrypto==1.5.0 # via -r requirements.in kmsauth==0.6.0 # via -r requirements.in -lru-dict==1.1.6 +lru-dict==1.2.0 # via -r requirements.in -lxml==4.6.5 +lxml==4.9.2 # via # python3-saml # xmlsec markupsafe==2.0.1 # via jinja2 +mypy==1.4.1 + # via -r requirements.in +mypy-extensions==1.0.0 + # via mypy ndg-httpsclient==0.5.1 # via -r requirements.in -packaging==20.0 - # via pytest -pluggy==0.13.1 +packaging==23.1 # via pytest -py==1.10.0 +pluggy==1.2.0 # via pytest -pyasn1==0.4.6 +pyasn1==0.5.0 # via # -r requirements.in # ndg-httpsclient -pycparser==2.19 +pycparser==2.21 # via cffi -pyjwt==2.6.0 +pyjwt==2.7.0 # via -r requirements.in pynamodb==3.4.1 # via -r requirements.in -pyopenssl==19.0.0 +pyopenssl==23.2.0 # via # -r requirements.in # ndg-httpsclient -pyparsing==2.4.6 - # via packaging -pytest==6.2.5 +pytest==7.4.0 # via # -r requirements.in # pytest-cov @@ -128,63 +131,84 @@ pytest==6.2.5 # pytest-gevent # pytest-lazy-fixture # pytest-mock -pytest-cov==3.0.0 +pytest-cov==4.1.0 # via -r requirements.in -pytest-env==0.6.2 +pytest-env==0.8.2 # via -r requirements.in -pytest-gevent==1.0.0 +pytest-gevent==1.1.0 # via -r requirements.in -pytest-lazy-fixture==0.6.1 +pytest-lazy-fixture==0.6.3 # via -r requirements.in -pytest-mock==3.3.0 +pytest-mock==3.11.1 # via -r requirements.in -python-dateutil==2.8.0 +python-dateutil==2.8.2 # via # botocore # pynamodb -python-json-logger==0.1.11 +python-json-logger==2.0.7 # via -r requirements.in python3-saml==1.15.0 # via -r requirements.in -pytz==2019.1 +pytz==2023.3 # via -r requirements.in -pyyaml==5.4 +pyyaml==6.0 # via -r requirements.in -redis==2.10.3 +redis==4.6.0 # via -r requirements.in -requests==2.22.0 +requests==2.31.0 # via -r requirements.in s3transfer==0.2.1 # via boto3 -six==1.12.0 +six==1.16.0 # via # isodate - # packaging # pynamodb - # pyopenssl # python-dateutil -statsd==3.2.1 +statsd==4.0.1 # via -r requirements.in toastedmarshmallow==2.15.2.post1 # via -r requirements.in -toml==0.10.2 - # via pytest -tomli==1.2.3 - # via coverage -urllib3==1.25.3 +tomli==2.0.1 + # via + # coverage + # mypy + # pytest +types-pyopenssl==23.2.0.1 + # via types-redis +types-pytz==2023.3.0.0 + # via -r requirements.in +types-pyyaml==6.0.12.10 + # via -r requirements.in +types-redis==4.6.0.0 + # via -r requirements.in +types-requests==2.31.0.1 + # via -r requirements.in +types-urllib3==1.26.25.13 + # via types-requests +typing-extensions==4.7.0 + # via mypy +urllib3==1.25.11 # via # botocore # requests werkzeug==1.0.1 # via flask -wrapt==1.14.1 +wrapt==1.15.0 # via deprecated xmlsec==1.3.13 # via python3-saml -zope-event==4.5.0 +zope-event==5.0 # via gevent -zope-interface==5.4.0 +zope-interface==6.0 # via gevent -# The following packages are considered to be unsafe in a requirements file: -# setuptools +pip==23.1.2 + # via -r piptools_requirements3.txt +setuptools==68.0.0 + # via + # -r piptools_requirements3.txt + # cerberus + # gevent + # gunicorn + # zope-event + # zope-interface diff --git a/setup.cfg b/setup.cfg index ed141c66..3556b7ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,3 +20,14 @@ title = "confidant Coverage" [coverage:run] # Properly determine code coverage given that there is gevent monkey patching concurrency = gevent + +[mypy] +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +ignore_errors = true + +[mypy-tests.*] +ignore_errors = false +disallow_untyped_defs = false +disallow_incomplete_defs = false diff --git a/tests/unit/confidant/authnz/authnz_test.py b/tests/unit/confidant/authnz/authnz_test.py index 8cf35614..e886d3cd 100644 --- a/tests/unit/confidant/authnz/authnz_test.py +++ b/tests/unit/confidant/authnz/authnz_test.py @@ -1,4 +1,6 @@ import pytest +from pytest_mock.plugin import MockerFixture +from typing import Union from werkzeug.exceptions import Forbidden, Unauthorized from confidant import authnz @@ -6,11 +8,11 @@ @pytest.fixture(autouse=True) -def mock_email_suffix(mocker): +def mock_email_suffix(mocker: MockerFixture): mocker.patch('confidant.authnz.settings.USER_EMAIL_SUFFIX', '') -def test_get_logged_in_user(mocker): +def test_get_logged_in_user(mocker: MockerFixture): mocker.patch('confidant.authnz.settings.USER_EMAIL_SUFFIX', 'example.com') app = create_app() with app.test_request_context('/v1/user/email'): @@ -21,7 +23,7 @@ def test_get_logged_in_user(mocker): assert authnz.get_logged_in_user() == 'unittestuser' -def test_get_logged_in_user_from_session(mocker): +def test_get_logged_in_user_from_session(mocker: MockerFixture): mocker.patch('confidant.authnz.settings.USER_EMAIL_SUFFIX', 'example.com') app = create_app() with app.test_request_context('/v1/user/email'): @@ -32,7 +34,7 @@ def test_get_logged_in_user_from_session(mocker): assert authnz.get_logged_in_user() == 'unittestuser@example.com' -def test_user_is_user_type(mocker): +def test_user_is_user_type(mocker: MockerFixture): mocker.patch('confidant.authnz.settings.USE_AUTH', False) assert authnz.user_is_user_type('anything') is True @@ -53,7 +55,7 @@ def test_user_is_user_type(mocker): assert authnz.user_is_user_type('user') is False -def test_require_csrf_token(mocker): +def test_require_csrf_token(mocker: MockerFixture): mock_fn = mocker.Mock() mock_fn.__name__ = 'mock_fn' mock_fn.return_value = 'unittestval' @@ -82,7 +84,7 @@ def test_require_csrf_token(mocker): wrapped() -def test_user_is_service(mocker): +def test_user_is_service(mocker: MockerFixture): mocker.patch('confidant.authnz.settings.USE_AUTH', False) assert authnz.user_is_service('anything') is True @@ -97,7 +99,7 @@ def test_user_is_service(mocker): assert authnz.user_is_service('notconfidant-unitttest') is False -def test_service_in_account(mocker): +def test_service_in_account(mocker: MockerFixture): # If we aren't scoping, this should pass assert authnz.service_in_account(None) is True @@ -109,7 +111,7 @@ def test_service_in_account(mocker): assert authnz.service_in_account('confidant-unitttest') is True -def test_account_for_key_alias(mocker): +def test_account_for_key_alias(mocker: MockerFixture): mocker.patch( 'confidant.authnz.settings.SCOPED_AUTH_KEYS', {'sandbox-auth-key': 'sandbox'}, @@ -118,7 +120,7 @@ def test_account_for_key_alias(mocker): assert authnz.account_for_key_alias('non-existent') is None -def test__get_kms_auth_data_from_auth(mocker): +def test__get_kms_auth_data_from_auth(mocker: MockerFixture): app = create_app() with app.test_request_context('/fake'): auth_mock = mocker.patch('confidant.authnz.request') @@ -140,7 +142,7 @@ def test__get_kms_auth_data_from_auth(mocker): authnz._get_kms_auth_data() -def test__get_kms_auth_data_from_headers(mocker): +def test__get_kms_auth_data_from_headers(mocker: MockerFixture): app = create_app() with app.test_request_context('/fake'): auth_mock = mocker.patch('confidant.authnz.request') @@ -163,7 +165,7 @@ def test__get_kms_auth_data_from_headers(mocker): authnz._get_kms_auth_data() -def test_redirect_to_logout_if_no_auth(mocker): +def test_redirect_to_logout_if_no_auth(mocker: MockerFixture): mock_fn = mocker.Mock() mock_fn.__name__ = 'mock_fn' mock_fn.return_value = 'unittestval' @@ -188,7 +190,7 @@ def test_redirect_to_logout_if_no_auth(mocker): @pytest.fixture() -def mock_header_auth(mocker): +def mock_header_auth(mocker: MockerFixture): mocker.patch('confidant.authnz.settings.USE_AUTH', True) mocker.patch('confidant.authnz.settings.USER_AUTH_MODULE', 'header') mocker.patch( @@ -205,7 +207,10 @@ def mock_header_auth(mocker): ) -def test_header_auth_will_extract_from_request(mocker, mock_header_auth): +def test_header_auth_will_extract_from_request( + mocker: MockerFixture, + mock_header_auth: None +): app = create_app() with app.test_request_context('/fake'): # No headers given: an error @@ -221,7 +226,10 @@ def test_header_auth_will_extract_from_request(mocker, mock_header_auth): assert authnz.get_logged_in_user() == 'unittestuser@example.com' -def test_header_auth_will_log_in(mocker, mock_header_auth): +def test_header_auth_will_log_in( + mocker: MockerFixture, + mock_header_auth: None +): app = create_app() with app.test_request_context('/fake'): request_mock = mocker.patch('confidant.authnz.userauth.request') @@ -235,7 +243,7 @@ def test_header_auth_will_log_in(mocker, mock_header_auth): assert resp.headers['Location'] == '/' -def test_require_auth(mocker): +def test_require_auth(mocker: MockerFixture): mocker.patch( 'confidant.authnz.settings.KMS_AUTH_USER_TYPES', ['user', 'service'], @@ -259,12 +267,13 @@ def test_require_auth(mocker): with pytest.raises(Forbidden): wrapped() - def extract_username_field(username, field): + def extract_username_field(username: str, field: str) -> Union[str, None]: username_arr = username.split('/') if field == 'from': return username_arr[2] elif field == 'user_type': return username_arr[1] + return None validator_mock = mocker.MagicMock() mocker.patch('confidant.authnz._get_validator', return_value=validator_mock) @@ -346,7 +355,7 @@ def extract_username_field(username, field): wrapped() -def test_require_logout_for_goodbye(mocker): +def test_require_logout_for_goodbye(mocker: MockerFixture): mock_fn = mocker.Mock() mock_fn.__name__ = 'mock_fn' mock_fn.return_value = 'unittestval' diff --git a/tests/unit/confidant/authnz/rbac_test.py b/tests/unit/confidant/authnz/rbac_test.py index c16c6602..5b1c6256 100644 --- a/tests/unit/confidant/authnz/rbac_test.py +++ b/tests/unit/confidant/authnz/rbac_test.py @@ -1,8 +1,10 @@ +from pytest_mock.plugin import MockerFixture + from confidant.app import create_app from confidant.authnz import rbac -def test_default_acl(mocker): +def test_default_acl(mocker: MockerFixture): mocker.patch('confidant.settings.USE_AUTH', True) app = create_app() with app.test_request_context('/fake'): diff --git a/tests/unit/confidant/encrypted_settings_test.py b/tests/unit/confidant/encrypted_settings_test.py index 83abbc32..1648bbdd 100644 --- a/tests/unit/confidant/encrypted_settings_test.py +++ b/tests/unit/confidant/encrypted_settings_test.py @@ -1,3 +1,5 @@ +from pytest_mock.plugin import MockerFixture + from confidant.encrypted_settings import EncryptedSettings @@ -22,7 +24,7 @@ def test_get_registered_default(): assert enc_set.get_secret('Bar') == 'Baz' -def test_bootstrap(mocker): +def test_bootstrap(mocker: MockerFixture): mocker.patch( 'confidant.encrypted_settings.cryptolib.decrypt_datakey', return_value=b'1cVUbJT58SbMt4Wk4xmEZoNhZGdWO_vg1IJiXwc6HGs==', diff --git a/tests/unit/confidant/models/credential_test.py b/tests/unit/confidant/models/credential_test.py index 2e19d6da..3fa8cb64 100644 --- a/tests/unit/confidant/models/credential_test.py +++ b/tests/unit/confidant/models/credential_test.py @@ -1,9 +1,10 @@ from datetime import datetime +from pytest_mock.plugin import MockerFixture from confidant.models.credential import Credential, CredentialArchive -def test_equals(mocker): +def test_equals(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.Credential' '._get_decrypted_credential_pairs', @@ -26,7 +27,7 @@ def test_equals(mocker): assert cred1.equals(cred2) is True -def test_not_equals(mocker): +def test_not_equals(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.Credential' '._get_decrypted_credential_pairs', @@ -47,7 +48,7 @@ def test_not_equals(mocker): assert cred1.equals(cred2) is False -def test_not_equals_different_tags(mocker): +def test_not_equals_different_tags(mocker: MockerFixture): decrypted_pairs_mock = mocker.patch( 'confidant.models.credential.Credential.decrypted_credential_pairs' ) @@ -69,7 +70,7 @@ def test_not_equals_different_tags(mocker): assert cred1.equals(cred2) is False -def test_diff(mocker): +def test_diff(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.Credential' '._get_decrypted_credential_pairs', @@ -129,7 +130,7 @@ def test_diff(mocker): assert old.diff(new) == expectedDiff -def test_credential_archive(mocker): +def test_credential_archive(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.Credential' '._get_decrypted_credential_pairs', @@ -146,7 +147,7 @@ def test_credential_archive(mocker): assert cred.id == archive_cred.id -def test_next_rotation_date_no_rotation_required(mocker): +def test_next_rotation_date_no_rotation_required(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.settings.TAGS_EXCLUDING_ROTATION', ['ADMIN_PRIV'], @@ -154,7 +155,7 @@ def test_next_rotation_date_no_rotation_required(mocker): assert Credential(tags=['ADMIN_PRIV']).next_rotation_date is None -def test_next_rotation_date_never_rotated(mocker): +def test_next_rotation_date_never_rotated(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.settings.TAGS_EXCLUDING_ROTATION', [], @@ -163,7 +164,7 @@ def test_next_rotation_date_never_rotated(mocker): assert cred.next_rotation_date <= datetime.utcnow() -def test_next_rotation_date_last_rotation_present(mocker): +def test_next_rotation_date_last_rotation_present(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.settings.TAGS_EXCLUDING_ROTATION', [], @@ -183,7 +184,7 @@ def test_next_rotation_date_last_rotation_present(mocker): assert cred.next_rotation_date == datetime(2020, 1, 31) -def test_exempt_from_rotation(mocker): +def test_exempt_from_rotation(mocker: MockerFixture): mocker.patch( 'confidant.models.credential.settings.TAGS_EXCLUDING_ROTATION', ['ADMIN_PRIV'], diff --git a/tests/unit/confidant/models/service_test.py b/tests/unit/confidant/models/service_test.py index 1f38f348..9484d8f6 100644 --- a/tests/unit/confidant/models/service_test.py +++ b/tests/unit/confidant/models/service_test.py @@ -1,9 +1,10 @@ from datetime import datetime +from pytest_mock.plugin import MockerFixture from confidant.models.service import Service -def test_equals(mocker): +def test_equals(mocker: MockerFixture): service1 = Service( id='test', credentials=['abc', 'def'], @@ -19,7 +20,7 @@ def test_equals(mocker): assert service1.equals(service2) is True -def test_not_equals(mocker): +def test_not_equals(mocker: MockerFixture): service1 = Service( id='test', credentials=['abc', 'def'], @@ -35,7 +36,7 @@ def test_not_equals(mocker): assert service1.equals(service2) is False -def test_diff(mocker): +def test_diff(mocker: MockerFixture): modified_by = 'test@example.com' modified_date_old = datetime.now modified_date_new = datetime.now @@ -82,4 +83,5 @@ def test_diff(mocker): 'added': modified_date_new, }, } + assert old.diff(new) == expected_diff diff --git a/tests/unit/confidant/routes/certificates_test.py b/tests/unit/confidant/routes/certificates_test.py index 094c9db4..5d52c77b 100644 --- a/tests/unit/confidant/routes/certificates_test.py +++ b/tests/unit/confidant/routes/certificates_test.py @@ -1,10 +1,11 @@ import json +from pytest_mock.plugin import MockerFixture from confidant.app import create_app from confidant.services import certificatemanager -def test_get_certificate(mocker): +def test_get_certificate(mocker: MockerFixture): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) @@ -54,12 +55,14 @@ def test_get_certificate(mocker): ('confidant.routes.certificates.certificatemanager.get_ca'), return_value=ca_object, ) - ca_object.issue_certificate_with_key = mocker.Mock( - return_value={ - 'certificate': 'test_certificate', - 'certificate_chain': 'test_certificate_chain', - 'key': 'test_key', - }, + issue_certificate_with_key_return_value = { + 'certificate': 'test_certificate', + 'certificate_chain': 'test_certificate_chain', + 'key': 'test_key', + } + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority.issue_certificate_with_key', # noqa: E501 + return_value=issue_certificate_with_key_return_value ) ret = app.test_client().get( '/v1/certificates/development/test.example.com', @@ -72,7 +75,8 @@ def test_get_certificate(mocker): 'certificate_chain': 'test_certificate_chain', 'key': 'test_key', } - ca_object.issue_certificate_with_key = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority.issue_certificate_with_key', # noqa: E501 side_effect=certificatemanager.CertificateNotReadyError(), ) ret = app.test_client().get( @@ -83,7 +87,7 @@ def test_get_certificate(mocker): assert ret.headers['Retry-After'] == '2' -def test_get_certificate_from_csr(mocker): +def test_get_certificate_from_csr(mocker: MockerFixture): ca_object = certificatemanager.CertificateAuthority('development') key = ca_object.generate_key() csr = ca_object.generate_csr(key, 'test.example.com') @@ -170,10 +174,12 @@ def test_get_certificate_from_csr(mocker): ('confidant.routes.certificates.certificatemanager.get_ca'), return_value=ca_object, ) - ca_object.issue_certificate = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority.issue_certificate', # noqa: E501 return_value='test-certificate-arn', ) - ca_object.get_certificate_from_arn = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority.get_certificate_from_arn', # noqa: E501 return_value={ 'certificate': 'test_certificate', 'certificate_chain': 'test_certificate_chain', @@ -196,7 +202,7 @@ def test_get_certificate_from_csr(mocker): } -def test_list_cas(mocker): +def test_list_cas(mocker: MockerFixture): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) @@ -242,7 +248,7 @@ def test_list_cas(mocker): } -def test_get_ca(mocker): +def test_get_ca(mocker: MockerFixture): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) @@ -270,7 +276,8 @@ def test_get_ca(mocker): ('confidant.routes.certificates.certificatemanager.get_ca'), return_value=ca_object, ) - ca_object.get_certificate_authority_certificate = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority.get_certificate_authority_certificate', # noqa: E501 return_value={ 'ca': 'development', 'certificate': 'test_certificate', diff --git a/tests/unit/confidant/routes/credentials_test.py b/tests/unit/confidant/routes/credentials_test.py index 2e5936af..43810036 100644 --- a/tests/unit/confidant/routes/credentials_test.py +++ b/tests/unit/confidant/routes/credentials_test.py @@ -1,6 +1,8 @@ import json import pytz from datetime import datetime +from pytest_mock.plugin import MockerFixture +from typing import List, Union import pytest from unittest import mock @@ -11,7 +13,7 @@ @pytest.fixture() -def credential(mocker): +def credential(mocker: MockerFixture) -> Credential: return Credential( id='1234', revision=1, @@ -30,7 +32,7 @@ def credential(mocker): @pytest.fixture() -def archive_credential(mocker): +def archive_credential(mocker: MockerFixture) -> Credential: return Credential( id='123-1', revision=1, @@ -49,7 +51,7 @@ def archive_credential(mocker): @pytest.fixture() -def credential_list(mocker): +def credential_list(mocker: MockerFixture) -> List[Credential]: credentials = [ Credential( id='1234', @@ -83,7 +85,10 @@ def credential_list(mocker): return credentials -def test_get_credential_list(mocker, credential_list): +def test_get_credential_list( + mocker: MockerFixture, + credential_list: List[Credential] +): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) @@ -165,7 +170,7 @@ def test_get_credential_list(mocker, credential_list): assert json_data['next_page'] is None -def test_get_credential(mocker, credential): +def test_get_credential(mocker: MockerFixture, credential: Credential): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) @@ -182,7 +187,10 @@ def test_get_credential(mocker, credential): ret = app.test_client().get('/v1/credentials/1234', follow_redirects=False) assert ret.status_code == 403 - def acl_module_check(resource_type, action, resource_id): + def acl_module_check( + resource_type: str, + action: str, + resource_id: int) -> Union[bool, None]: if action == 'metadata': if resource_id == '5678': return False @@ -198,6 +206,7 @@ def acl_module_check(resource_type, action, resource_id): return True else: return False + return None mocker.patch( 'confidant.routes.credentials.acl_module_check', @@ -282,7 +291,7 @@ def acl_module_check(resource_type, action, resource_id): assert ret.status_code == 404 -def test_diff_credential(mocker, credential): +def test_diff_credential(mocker: MockerFixture, credential: Credential): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) @@ -349,7 +358,7 @@ def test_diff_credential(mocker, credential): assert ret.status_code == 404 -def test_create_credential(mocker, credential): +def test_create_credential(mocker: MockerFixture, credential: Credential): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) mocker.patch( @@ -446,7 +455,7 @@ def test_create_credential(mocker, credential): assert mock_save.call_count == 2 -def test_update_credential(mocker, credential): +def test_update_credential(mocker: MockerFixture, credential: Credential): credential.last_rotation_date = datetime(2020, 1, 1, tzinfo=pytz.UTC) app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) @@ -573,7 +582,11 @@ def test_update_credential(mocker, credential): assert 'next_rotation_date' in json_data -def test_revise_credential(mocker, credential, archive_credential): +def test_revise_credential( + mocker: MockerFixture, + credential: Credential, + archive_credential: Credential +): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) mocker.patch( diff --git a/tests/unit/confidant/routes/identity_test.py b/tests/unit/confidant/routes/identity_test.py index 7655e91e..329cb997 100644 --- a/tests/unit/confidant/routes/identity_test.py +++ b/tests/unit/confidant/routes/identity_test.py @@ -1,8 +1,11 @@ +from pytest_mock.plugin import MockerFixture +from typing import Union + from confidant.authnz import UserUnknownError from confidant.app import create_app -def test_get_user_info(mocker): +def test_get_user_info(mocker: MockerFixture): mocker.patch('confidant.settings.USE_AUTH', False) mocker.patch( 'confidant.routes.identity.authnz.get_logged_in_user', @@ -14,7 +17,7 @@ def test_get_user_info(mocker): assert ret.json == {'email': 'test@example.com'} -def test_get_user_info_no_user(mocker): +def test_get_user_info_no_user(mocker: MockerFixture): mocker.patch('confidant.settings.USE_AUTH', False) mocker.patch( 'confidant.routes.identity.authnz.get_logged_in_user', @@ -26,8 +29,8 @@ def test_get_user_info_no_user(mocker): assert ret.json == {'email': None} -def test_get_client_config(mocker): - def acl_module_check(resource_type, action): +def test_get_client_config(mocker: MockerFixture): + def acl_module_check(resource_type: str, action: str) -> Union[bool, None]: if resource_type == 'credential': if action == 'create': return False @@ -38,6 +41,7 @@ def acl_module_check(resource_type, action): return True elif action == 'list': return False + return None mocker.patch('confidant.routes.identity.acl_module_check', acl_module_check) mocker.patch('confidant.settings.USE_AUTH', False) diff --git a/tests/unit/confidant/routes/jwks_test.py b/tests/unit/confidant/routes/jwks_test.py index a3e1af88..96424881 100644 --- a/tests/unit/confidant/routes/jwks_test.py +++ b/tests/unit/confidant/routes/jwks_test.py @@ -1,7 +1,9 @@ +from pytest_mock.plugin import MockerFixture + from confidant.app import create_app -def test_get_token_override_user(mocker): +def test_get_token_override_user(mocker: MockerFixture): mocker.patch('confidant.settings.USE_AUTH', False) mocker.patch('confidant.routes.jwks.acl_module_check', return_value=True) mocker.patch('confidant.routes.identity.authnz.get_logged_in_user', @@ -21,7 +23,7 @@ def test_get_token_override_user(mocker): assert ret.status_code == 200 -def test_get_token_no_override(mocker): +def test_get_token_no_override(mocker: MockerFixture): mocker.patch('confidant.settings.USE_AUTH', False) mocker.patch('confidant.routes.jwks.acl_module_check', return_value=True) mocker.patch('confidant.routes.identity.authnz.get_logged_in_user', @@ -41,7 +43,7 @@ def test_get_token_no_override(mocker): assert ret.status_code == 200 -def test_get_token_override_user_not_authorized(mocker): +def test_get_token_override_user_not_authorized(mocker: MockerFixture): mocker.patch('confidant.settings.USE_AUTH', False) mocker.patch('confidant.routes.jwks.acl_module_check', return_value=False) diff --git a/tests/unit/confidant/routes/services_test.py b/tests/unit/confidant/routes/services_test.py index 2c2014cf..076baeb6 100644 --- a/tests/unit/confidant/routes/services_test.py +++ b/tests/unit/confidant/routes/services_test.py @@ -2,13 +2,15 @@ import pytest from unittest import mock +from pytest_mock.plugin import MockerFixture +from typing import List from confidant.app import create_app from confidant.models.service import Service @pytest.fixture() -def services_list(mocker): +def services_list(mocker: MockerFixture) -> List[Service]: services = [ Service( id='something-production-iad', @@ -32,7 +34,7 @@ def services_list(mocker): return services -def test_get_services_list(mocker, services_list): +def test_get_services_list(mocker: MockerFixture, services_list: List[Service]): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) diff --git a/tests/unit/confidant/scripts/archive_test.py b/tests/unit/confidant/scripts/archive_test.py index d36eadb1..3d3d708f 100644 --- a/tests/unit/confidant/scripts/archive_test.py +++ b/tests/unit/confidant/scripts/archive_test.py @@ -1,5 +1,8 @@ from datetime import datetime, timedelta import pytest +from pytest_mock.plugin import MockerFixture +from typing import Dict, List +from unittest.mock import MagicMock from confidant.models.service import Service from confidant.models.credential import Credential, CredentialArchive @@ -8,35 +11,38 @@ @pytest.fixture -def now(): +def now() -> datetime: return datetime.now() @pytest.fixture -def old_date(): +def old_date() -> datetime: return datetime.now() - timedelta(30) @pytest.fixture() -def save_mock(mocker): +def save_mock(mocker: MockerFixture) -> MagicMock: return mocker.patch('confidant.scripts.archive.credentialmanager.' '_save_credentials_to_archive') @pytest.fixture() -def delete_mock(mocker): +def delete_mock(mocker: MockerFixture) -> MagicMock: return mocker.patch('confidant.scripts.archive.credentialmanager.' '_delete_credentials') @pytest.fixture() -def archive_mock(mocker): +def archive_mock(mocker: MockerFixture) -> MagicMock: return mocker.patch('confidant.scripts.archive.credentialmanager.' 'archive_credentials') @pytest.fixture -def credentials(mocker, now): +def credentials( + mocker: MockerFixture, + now: datetime +) -> Dict[str, List[Credential]]: gmd_mock = mocker.Mock(return_value='test') gmd_mock.range_keyname = 'test' mocker.patch( @@ -82,7 +88,7 @@ def credentials(mocker, now): revision2.delete = mocker.Mock() archive_revision2 = CredentialArchive.from_credential(revision2) - def from_credential(credential): + def from_credential(credential: Credential): if credential.id == '1234': return archive_credential elif credential.id == '1234-1': @@ -100,7 +106,10 @@ def from_credential(credential): @pytest.fixture -def old_disabled_credentials(credentials, old_date): +def old_disabled_credentials( + credentials: Dict[str, List[Credential]], + old_date: datetime +) -> Dict[str, List[Credential]]: for credential in credentials['credentials']: credential.modified_date = old_date credential.enabled = False @@ -117,7 +126,7 @@ def old_disabled_credentials(credentials, old_date): @pytest.fixture -def no_mapped_service(mocker): +def no_mapped_service(mocker: MockerFixture): mocked = mocker.patch.object(Service, 'data_type_date_index') mocked.query = mocker.Mock(return_value=[]) mocker.patch( @@ -127,7 +136,7 @@ def no_mapped_service(mocker): @pytest.fixture -def mapped_service(mocker): +def mapped_service(mocker: MockerFixture): mocked = mocker.patch.object(Service, 'data_type_date_index') mocked.query = mocker.Mock( return_value=[Service(id='test-service', revision=1, enabled=True)], @@ -138,7 +147,10 @@ def mapped_service(mocker): ) -def test_save_credentials_to_archive(mocker, credentials): +def test_save_credentials_to_archive( + mocker: MockerFixture, + credentials: Dict[str, List[Credential]] +): mocker.patch('pynamodb.models.BatchWrite.commit') save_mock = mocker.patch('pynamodb.models.BatchWrite.save') credentialmanager._save_credentials_to_archive(credentials['credentials'], @@ -150,7 +162,10 @@ def test_save_credentials_to_archive(mocker, credentials): assert save_mock.called is True -def test_delete(mocker, credentials): +def test_delete( + mocker: MockerFixture, + credentials: Dict[str, List[Credential]] +): mocker.patch('pynamodb.models.BatchWrite.commit') delete_mock = mocker.patch('pynamodb.models.BatchWrite.delete') credentialmanager._delete_credentials(credentials['credentials'], @@ -163,11 +178,11 @@ def test_delete(mocker, credentials): def test_archive_old_disabled_unmapped_credential( - mocker, - old_disabled_credentials, - no_mapped_service, - save_mock, - delete_mock, + mocker: MockerFixture, + old_disabled_credentials: Dict[str, List[Credential]], + no_mapped_service: None, + save_mock: MagicMock, + delete_mock: MagicMock, ): mocker.patch( 'confidant.scripts.archive.Credential.data_type_date_index.query', @@ -193,11 +208,11 @@ def test_archive_old_disabled_unmapped_credential( def test_archive_old_disabled_unmapped_credential_no_force( - mocker, - old_disabled_credentials, - no_mapped_service, - save_mock, - delete_mock, + mocker: MockerFixture, + old_disabled_credentials: Dict[str, List[Credential]], + no_mapped_service: None, + save_mock: MagicMock, + delete_mock: MagicMock, ): mocker.patch( 'confidant.scripts.archive.Credential.batch_get', @@ -219,11 +234,11 @@ def test_archive_old_disabled_unmapped_credential_no_force( def test_archive_old_disabled_mapped_credential( - mocker, - old_disabled_credentials, - mapped_service, - save_mock, - delete_mock, + mocker: MockerFixture, + old_disabled_credentials: Dict[str, List[Credential]], + mapped_service: None, + save_mock: MagicMock, + delete_mock: MagicMock, ): mocker.patch( 'confidant.scripts.archive.Credential.batch_get', @@ -238,7 +253,7 @@ def test_archive_old_disabled_mapped_credential( assert delete_mock.called is False -def test_run_no_archive_table(mocker): +def test_run_no_archive_table(mocker: MockerFixture): mocker.patch( 'confidant.scripts.archive.settings.DYNAMODB_TABLE_ARCHIVE', None, @@ -247,16 +262,16 @@ def test_run_no_archive_table(mocker): assert ac.run(days=10, force=True, ids=None) == 1 -def test_run_bad_args(mocker): +def test_run_bad_args(mocker: MockerFixture): ac = ArchiveCredentials() assert ac.run(days=None, force=True, ids=None) == 1 assert ac.run(days=10, force=True, ids='1234') == 1 def test_run_days_new_enabled_credential( - mocker, - credentials, - archive_mock, + mocker: MockerFixture, + credentials: Dict[str, List[Credential]], + archive_mock: MagicMock, ): mocker.patch( 'confidant.scripts.archive.Credential.data_type_date_index.query', @@ -271,9 +286,9 @@ def test_run_days_new_enabled_credential( def test_run_days_old_disabled_credentials( - mocker, - old_disabled_credentials, - archive_mock, + mocker: MockerFixture, + old_disabled_credentials: Dict[str, List[Credential]], + archive_mock: MagicMock, ): mocker.patch( 'confidant.scripts.archive.Credential.data_type_date_index.query', @@ -289,9 +304,9 @@ def test_run_days_old_disabled_credentials( def test_run_ids_new_enabled_credentials( - mocker, - credentials, - archive_mock, + mocker: MockerFixture, + credentials: Dict[str, List[Credential]], + archive_mock: MagicMock, ): mocker.patch( 'confidant.scripts.archive.Credential.batch_get', @@ -312,9 +327,9 @@ def test_run_ids_new_enabled_credentials( def test_run_ids_old_disabled_credentials( - mocker, - old_disabled_credentials, - archive_mock, + mocker: MockerFixture, + old_disabled_credentials: Dict[str, List[Credential]], + archive_mock: MagicMock, ): mocker.patch( 'confidant.scripts.archive.Credential.batch_get', diff --git a/tests/unit/confidant/scripts/restore_test.py b/tests/unit/confidant/scripts/restore_test.py index 5fb7def0..1208ba19 100644 --- a/tests/unit/confidant/scripts/restore_test.py +++ b/tests/unit/confidant/scripts/restore_test.py @@ -1,32 +1,38 @@ from datetime import datetime, timedelta import pytest +from pytest_mock.plugin import MockerFixture +from typing import Dict, List +from unittest.mock import MagicMock from confidant.models.credential import Credential, CredentialArchive from confidant.scripts.restore import RestoreCredentials @pytest.fixture -def now(): +def now() -> datetime: return datetime.now() @pytest.fixture -def old_date(): +def old_date() -> datetime: return datetime.now() - timedelta(30) @pytest.fixture() -def save_mock(mocker): +def save_mock(mocker: MockerFixture) -> MagicMock: return mocker.patch('confidant.scripts.restore.RestoreCredentials.save') @pytest.fixture() -def restore_mock(mocker): +def restore_mock(mocker: MockerFixture) -> MagicMock: return mocker.patch('confidant.scripts.restore.RestoreCredentials.restore') @pytest.fixture -def credentials(mocker, now): +def credentials( + mocker: MockerFixture, + now: datetime +) -> Dict[str, List[Credential]]: gmd_mock = mocker.Mock(return_value='test') gmd_mock.range_keyname = 'test' mocker.patch( @@ -68,7 +74,7 @@ def credentials(mocker, now): ) revision2 = Credential.from_archive_credential(archive_revision2) - def from_archive_credential(archive_credential): + def from_archive_credential(archive_credential: CredentialArchive): if archive_credential.id == '1234': return credential elif archive_credential.id == '1234-1': @@ -90,7 +96,10 @@ def from_archive_credential(archive_credential): @pytest.fixture -def old_disabled_credentials(credentials, old_date): +def old_disabled_credentials( + credentials: Dict[str, List[Credential]], + old_date: datetime +) -> Dict[str, List[Credential]]: for credential in credentials['credentials']: credential.modified_date = old_date credential.enabled = False @@ -106,7 +115,7 @@ def old_disabled_credentials(credentials, old_date): return credentials -def test_save(mocker, credentials): +def test_save(mocker: MockerFixture, credentials: Dict[str, List[Credential]]): rc = RestoreCredentials() save_mock = mocker.patch('pynamodb.models.BatchWrite.save') mocker.patch('pynamodb.models.BatchWrite.commit') @@ -129,9 +138,9 @@ def test_save(mocker, credentials): def test_restore_credentials( - mocker, - old_disabled_credentials, - save_mock, + mocker: MockerFixture, + old_disabled_credentials: Dict[str, List[Credential]], + save_mock: MagicMock, ): mocker.patch( 'confidant.scripts.restore.CredentialArchive.batch_get', @@ -147,9 +156,9 @@ def test_restore_credentials( def test_restore_old_disabled_unmapped_credential_no_force( - mocker, - old_disabled_credentials, - save_mock, + mocker: MockerFixture, + old_disabled_credentials: Dict[str, List[Credential]], + save_mock: MagicMock, ): mocker.patch( 'confidant.scripts.restore.CredentialArchive.batch_get', @@ -164,7 +173,7 @@ def test_restore_old_disabled_unmapped_credential_no_force( ) -def test_run_no_archive_table(mocker): +def test_run_no_archive_table(mocker: MockerFixture): mocker.patch( 'confidant.scripts.restore.settings.DYNAMODB_TABLE_ARCHIVE', None, @@ -173,16 +182,16 @@ def test_run_no_archive_table(mocker): assert rc.run(_all=True, force=True, ids=None) == 1 -def test_run_bad_args(mocker): +def test_run_bad_args(mocker: MockerFixture): rc = RestoreCredentials() assert rc.run(_all=False, force=True, ids=None) == 1 assert rc.run(_all=True, force=True, ids='1234') == 1 def test_run_all( - mocker, - credentials, - restore_mock, + mocker: MockerFixture, + credentials: Dict[str, List[Credential]], + restore_mock: MagicMock, ): mocker.patch( 'confidant.scripts.restore.CredentialArchive.data_type_date_index.query', # noqa:E501 @@ -197,9 +206,9 @@ def test_run_all( def test_run_ids( - mocker, - credentials, - restore_mock, + mocker: MockerFixture, + credentials: Dict[str, List[Credential]], + restore_mock: MagicMock, ): mocker.patch( 'confidant.scripts.restore.CredentialArchive.batch_get', diff --git a/tests/unit/confidant/services/certificatemanager_test.py b/tests/unit/confidant/services/certificatemanager_test.py index 6d136470..41b23bad 100644 --- a/tests/unit/confidant/services/certificatemanager_test.py +++ b/tests/unit/confidant/services/certificatemanager_test.py @@ -1,13 +1,14 @@ import datetime import pytest +from pytest_mock.plugin import MockerFixture from cryptography.hazmat.primitives import hashes from confidant.services import certificatemanager @pytest.fixture() -def ca_object(mocker): +def ca_object(mocker: MockerFixture) -> certificatemanager.CertificateAuthority: ca_object = certificatemanager.CertificateAuthority('development') ca_object.settings['csr_country_name'] = 'US' ca_object.settings['csr_state_or_province_name'] = 'California' @@ -39,26 +40,27 @@ def test_certificate_cache(): assert cache.get(cache_id) is None -def test_generate_key(ca_object): +def test_generate_key(ca_object: certificatemanager.CertificateAuthority): + print(type(ca_object)) ca_object.settings['key_size'] = 1024 key = ca_object.generate_key() assert key.key_size == 1024 -def test_encode_key(ca_object): +def test_encode_key(ca_object: certificatemanager.CertificateAuthority): key = ca_object.generate_key() encoded_key = ca_object.encode_key(key) assert encoded_key.startswith('-----BEGIN RSA PRIVATE KEY-----') -def test_generate_x509_name(ca_object): +def test_generate_x509_name(ca_object: certificatemanager.CertificateAuthority): x509_name = ca_object.generate_x509_name('test.example.com') assert x509_name.rfc4514_string() == ( 'O=Example Inc.,L=San Francisco,ST=California,C=US,CN=test.example.com' ) -def test_generate_csr(ca_object): +def test_generate_csr(ca_object: certificatemanager.CertificateAuthority): key = ca_object.generate_key() san = ['test2.example.com', 'test3.example.com'] csr = ca_object.generate_csr(key, 'test.example.com', san) @@ -68,33 +70,37 @@ def test_generate_csr(ca_object): ) -def test_encode_csr(ca_object): +def test_encode_csr(ca_object: certificatemanager.CertificateAuthority): key = ca_object.generate_key() csr = ca_object.generate_csr(key, 'test.example.com') encoded_csr = ca_object.encode_csr(csr) assert encoded_csr.startswith('-----BEGIN CERTIFICATE REQUEST-----') -def test_decode_csr(ca_object): +def test_decode_csr(ca_object: certificatemanager.CertificateAuthority): encoded_csr = '-----BEGIN CERTIFICATE REQUEST-----\nMIICwDCCAagCAQAwajEZMBcGA1UEAwwQdGVzdC5leGFtcGxlLmNvbTELMAkGA1UE\nBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lz\nY28xEzARBgNVBAoMCkx5ZnQsIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDqAFNwMlG3DiPJzUgSfzInRlAYdZzycz/mRsw5Boucii4jBQpLfhp/\nwjkbClAuuwLIija5yv95zChxbJPJ6Je1FtcXtbXAEVjWnf+B1s/OEA+uSO8IoGiL\nsYRNFqXI2hyzcqMshnxc90+qfMB+/eAv17t0fkMjT028N5I/Rvqh0RQx9l+0AbvH\nPtNBzNSWj9s/Oy4mEaXary/S3VZPd+38hpXc3HQINczmSKQTG/pPKwQcg+dQMjQz\nvlPuntrgvy5S2mK5D0xOCfLUfNVT7qb89/Rd9siZw7VMzL/XDkNtVZEEuJAL16PN\n/1zPQO6jxqNes0PqeWz0brsrx6LqhxiPAgMBAAGgETAPBgkqhkiG9w0BCQ4xAjAA\nMA0GCSqGSIb3DQEBCwUAA4IBAQBZaU01DoLf4Ldum/gOrjc+R1lqgXna6Thu/DHs\nAKbPyztjQjRwGApoPUXqRs6MYpB8XJOal4rsYazybxRNsiIQV/yNtlToVsz86lys\nPP85zzk7nZTT28gMew/iuS7H0in4XJz3LWdDxVIk+P4ktiqTOQSQyqMBGM+Rw93Y\nBDCfk1/pigxis0umyfp6Ho/qfdKEr4MYi2UZfTIl8F8dLq+PKPzqK+sEEOBDOUtP\nc4Edeg3PL1XROiwv3uPhtfaIe1iVD4IjWxNN06anoa29xMmJ/vXkaqYLQSd+FKHe\ny00DRxiYx7zqqfByUAUV3pPRwytFMit5bsOEAlhYmTRc2PEx\n-----END CERTIFICATE REQUEST-----\n' # noqa:E501 csr = ca_object.decode_csr(encoded_csr) assert csr.is_signature_valid is True -def test_get_csr_common_name(ca_object): +def test_get_csr_common_name( + ca_object: certificatemanager.CertificateAuthority +): key = ca_object.generate_key() csr = ca_object.generate_csr(key, 'test.example.com') assert ca_object.get_csr_common_name(csr) == 'test.example.com' -def test_get_csr_san(ca_object): +def test_get_csr_san(ca_object: certificatemanager.CertificateAuthority): key = ca_object.generate_key() san = ['test1.example.com', 'test2.example.com'] csr = ca_object.generate_csr(key, 'test.example.com', san) assert ca_object.get_csr_san(csr) == san -def test_encode_san_dns_names(ca_object): +def test_encode_san_dns_names( + ca_object: certificatemanager.CertificateAuthority +): san = ['test1.example.com', 'test2.example.com'] dns_names = ca_object.encode_san_dns_names(san) assert len(dns_names) == len(san) @@ -102,7 +108,9 @@ def test_encode_san_dns_names(ca_object): assert dns_name.value in san -def test_generate_self_signed_certificate(ca_object): +def test_generate_self_signed_certificate( + ca_object: certificatemanager.CertificateAuthority +): key = ca_object.generate_key() san = ['test1.example.com', 'test2.example.com'] cert = ca_object.generate_self_signed_certificate( @@ -119,7 +127,7 @@ def test_generate_self_signed_certificate(ca_object): assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) -def test_encode_certificate(ca_object): +def test_encode_certificate(ca_object: certificatemanager.CertificateAuthority): key = ca_object.generate_key() cert = ca_object.generate_self_signed_certificate( key, @@ -130,7 +138,10 @@ def test_encode_certificate(ca_object): assert encoded_cert.startswith('-----BEGIN CERTIFICATE-----') -def test_issue_certificate(mocker, ca_object): +def test_issue_certificate( + mocker: MockerFixture, + ca_object: certificatemanager.CertificateAuthority +): client_mock = mocker.patch( 'confidant.clients.get_boto_client', autospec=True, @@ -146,7 +157,10 @@ def test_issue_certificate(mocker, ca_object): assert ca_object.issue_certificate(encoded_csr, 7) == 'test' -def test__get_cached_certificate_with_key(mocker, ca_object): +def test__get_cached_certificate_with_key( + mocker: MockerFixture, + ca_object: certificatemanager.CertificateAuthority +): ca_object.settings['certificate_use_cache'] = False assert ca_object._get_cached_certificate_with_key('test') == {} ca_object.settings['certificate_use_cache'] = True @@ -163,32 +177,42 @@ def test__get_cached_certificate_with_key(mocker, ca_object): type(item).response = mocker.PropertyMock( side_effect=[None, {'hello': 'world'}] ) - cache.get = mocker.MagicMock(return_value=item) + mocker.patch( + 'confidant.services.certificatemanager.CertificateCache.get', + return_value=item + ) with pytest.raises(certificatemanager.CertificateNotReadyError): ca_object._get_cached_certificate_with_key('test1') -def test_issue_certificate_with_key(mocker, ca_object): +def test_issue_certificate_with_key( + mocker: MockerFixture, + ca_object: certificatemanager.CertificateAuthority +): ca_object.settings['self_sign'] = True data = ca_object.issue_certificate_with_key('test.example.com', 7) assert data['certificate'].startswith('-----BEGIN CERTIFICATE-----') assert data['certificate_chain'].startswith('-----BEGIN CERTIFICATE-----') assert data['key'].startswith('-----BEGIN RSA PRIVATE KEY-----') - ca_object._get_cached_certificate_with_key = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority._get_cached_certificate_with_key', # noqa:E501 return_value={'hello': 'world'}, ) data = ca_object.issue_certificate_with_key('test.example.com', 7) assert data == {'hello': 'world'} - ca_object._get_cached_certificate_with_key = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority._get_cached_certificate_with_key', # noqa:E501 return_value={}, ) ca_object.settings['self_sign'] = False - ca_object.issue_certificate = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority.issue_certificate', # noqa:E501 return_value='test-certificate-arn', ) - ca_object.get_certificate_from_arn = mocker.Mock( + mocker.patch( + 'confidant.services.certificatemanager.CertificateAuthority.get_certificate_from_arn', # noqa:E501 return_value={ 'certificate': 'test_certificate', 'certificate_chain': 'test_certificate_chain', @@ -200,7 +224,10 @@ def test_issue_certificate_with_key(mocker, ca_object): assert data['key'].startswith('-----BEGIN RSA PRIVATE KEY-----') -def test_get_certificate_from_arn_no_exception(mocker, ca_object): +def test_get_certificate_from_arn_no_exception( + mocker: MockerFixture, + ca_object: certificatemanager.CertificateAuthority +): time_mock = mocker.patch('time.sleep') client_mock = mocker.patch( 'confidant.clients.get_boto_client', @@ -217,7 +244,10 @@ def test_get_certificate_from_arn_no_exception(mocker, ca_object): assert data == {'certificate': 'test', 'certificate_chain': 'test_chain'} -def test_get_certificate_from_arn_with_exception(mocker, ca_object): +def test_get_certificate_from_arn_with_exception( + mocker: MockerFixture, + ca_object: certificatemanager.CertificateAuthority +): class RequestInProgressException(Exception): pass @@ -238,7 +268,10 @@ class RequestInProgressException(Exception): assert data == {'certificate': 'test', 'certificate_chain': 'test_chain'} -def test_get_certificate_authority_certificate(mocker, ca_object): +def test_get_certificate_authority_certificate( + mocker: MockerFixture, + ca_object: certificatemanager.CertificateAuthority +): client_mock = mocker.patch( 'confidant.clients.get_boto_client', autospec=True, diff --git a/tests/unit/confidant/services/credentialmanager_test.py b/tests/unit/confidant/services/credentialmanager_test.py index eaaeeca8..d84f9706 100644 --- a/tests/unit/confidant/services/credentialmanager_test.py +++ b/tests/unit/confidant/services/credentialmanager_test.py @@ -2,6 +2,8 @@ from confidant.services import credentialmanager from pynamodb.exceptions import DoesNotExist +from pytest_mock.plugin import MockerFixture + def test_get_revision_ids_for_credential(): credential = Credential( @@ -17,7 +19,7 @@ def test_get_revision_ids_for_credential(): ] -def test_get_latest_blind_credential_revision(mocker): +def test_get_latest_blind_credential_revision(mocker: MockerFixture): get = mocker.patch( 'confidant.models.blind_credential.BlindCredential.get' ) @@ -26,7 +28,7 @@ def test_get_latest_blind_credential_revision(mocker): assert res == 2 -def test_get_latest_credential_revision(mocker): +def test_get_latest_credential_revision(mocker: MockerFixture): get = mocker.patch( 'confidant.models.credential.Credential.get' ) @@ -35,7 +37,7 @@ def test_get_latest_credential_revision(mocker): assert res == 2 -def test_check_credential_pair_values(mocker): +def test_check_credential_pair_values(mocker: MockerFixture): cred_pairs_success = { 'A': '1' } diff --git a/tests/unit/confidant/services/jwkmanager_test.py b/tests/unit/confidant/services/jwkmanager_test.py index 09251abc..1e531f17 100644 --- a/tests/unit/confidant/services/jwkmanager_test.py +++ b/tests/unit/confidant/services/jwkmanager_test.py @@ -1,14 +1,19 @@ import confidant.services.jwkmanager import datetime +from jwcrypto import jwk + import pytest +from pytest_mock.plugin import MockerFixture + +from typing import Dict, Union from unittest.mock import patch, Mock from confidant.services.jwkmanager import jwk_manager -def test_set_key(test_key_pair): +def test_set_key(test_key_pair: jwk.JWK): test_private_key = test_key_pair.export_to_pem(private_key=True, password=None) kid = jwk_manager.set_key('test', @@ -17,7 +22,7 @@ def test_set_key(test_key_pair): assert kid == 'test-key' -def test_set_key_encrypted(test_encrypted_key): +def test_set_key_encrypted(test_encrypted_key: str): kid = jwk_manager.set_key('test', 'test-key', test_encrypted_key, passphrase='123456') assert kid == 'test-key' @@ -27,11 +32,17 @@ def test_set_key_encrypted(test_encrypted_key): Mock(wraps=datetime.datetime)) @patch.object(confidant.services.jwkmanager, 'JWT_ACTIVE_SIGNING_KEYS', {'test': '0h7R8dL0rU-b3p3onft_BPfuRW1Ld7YjsFnOWJuFXUE'}) -def test_get_jwt(test_key_pair, test_jwk_payload, test_jwt): +def test_get_jwt( + mocker: MockerFixture, + test_key_pair: jwk.JWK, + test_jwk_payload: Dict[str, Union[str, bool]], + test_jwt: str +): test_private_key = test_key_pair.export_to_pem(private_key=True, password=None) - confidant.services.jwkmanager.datetime.now.return_value = \ - datetime.datetime( + mocker.patch( + 'confidant.services.jwkmanager.datetime.now', + return_value=datetime.datetime( year=2020, month=10, day=10, @@ -40,6 +51,7 @@ def test_get_jwt(test_key_pair, test_jwk_payload, test_jwt): second=0, microsecond=0 ) + ) jwk_manager.set_key('test', test_key_pair.thumbprint(), test_private_key.decode('utf-8')) @@ -53,11 +65,17 @@ def test_get_jwt(test_key_pair, test_jwk_payload, test_jwt): @patch.object(confidant.services.jwkmanager, 'JWT_CACHING_ENABLED', True) @patch.object(confidant.services.jwkmanager, 'JWT_ACTIVE_SIGNING_KEYS', {'test': '0h7R8dL0rU-b3p3onft_BPfuRW1Ld7YjsFnOWJuFXUE'}) -def test_get_jwt_caches_jwt(test_key_pair, test_jwk_payload, test_jwt): +def test_get_jwt_caches_jwt( + mocker: MockerFixture, + test_key_pair: jwk.JWK, + test_jwk_payload: Dict[str, Union[str, bool]], + test_jwt: str +): test_private_key = test_key_pair.export_to_pem(private_key=True, password=None) - confidant.services.jwkmanager.datetime.now.return_value = \ - datetime.datetime( + mocker.patch( + 'confidant.services.jwkmanager.datetime.now', + return_value=datetime.datetime( year=2020, month=10, day=10, @@ -66,14 +84,16 @@ def test_get_jwt_caches_jwt(test_key_pair, test_jwk_payload, test_jwt): second=0, microsecond=0 ) + ) jwk_manager.set_key('test', test_key_pair.thumbprint(), test_private_key.decode('utf-8')) result = jwk_manager.get_jwt('test', test_jwk_payload) - confidant.services.jwkmanager.datetime.now.return_value = \ - datetime.datetime( + mocker.patch( + 'confidant.services.jwkmanager.datetime.now', + return_value=datetime.datetime( year=2020, month=10, day=10, @@ -82,6 +102,7 @@ def test_get_jwt_caches_jwt(test_key_pair, test_jwk_payload, test_jwt): second=0, microsecond=0 ) + ) cached_result = jwk_manager.get_jwt('test', test_jwk_payload) assert result == test_jwt @@ -93,11 +114,17 @@ def test_get_jwt_caches_jwt(test_key_pair, test_jwk_payload, test_jwt): @patch.object(confidant.services.jwkmanager, 'JWT_CACHING_ENABLED', False) @patch.object(confidant.services.jwkmanager, 'JWT_ACTIVE_SIGNING_KEYS', {'test': '0h7R8dL0rU-b3p3onft_BPfuRW1Ld7YjsFnOWJuFXUE'}) -def test_get_jwt_does_not_cache_jwt(test_key_pair, test_jwk_payload, test_jwt): +def test_get_jwt_does_not_cache_jwt( + mocker: MockerFixture, + test_key_pair: jwk.JWK, + test_jwk_payload: Dict[str, Union[str, bool]], + test_jwt: str +): test_private_key = test_key_pair.export_to_pem(private_key=True, password=None) - confidant.services.jwkmanager.datetime.now.return_value = \ - datetime.datetime( + mocker.patch( + 'confidant.services.jwkmanager.datetime.now', + return_value=datetime.datetime( year=2020, month=10, day=10, @@ -106,14 +133,16 @@ def test_get_jwt_does_not_cache_jwt(test_key_pair, test_jwk_payload, test_jwt): second=0, microsecond=0 ) + ) jwk_manager.set_key('test', test_key_pair.thumbprint(), test_private_key.decode('utf-8')) result = jwk_manager.get_jwt('test', test_jwk_payload) - confidant.services.jwkmanager.datetime.now.return_value = \ - datetime.datetime( + mocker.patch( + 'confidant.services.jwkmanager.datetime.now', + return_value=datetime.datetime( year=2020, month=10, day=10, @@ -122,13 +151,17 @@ def test_get_jwt_does_not_cache_jwt(test_key_pair, test_jwk_payload, test_jwt): second=1, microsecond=0 ) + ) not_cached_result = jwk_manager.get_jwt('test', test_jwk_payload) assert result == test_jwt assert result != not_cached_result -def test_get_jwt_raises_no_key_id(test_key_pair, test_jwk_payload): +def test_get_jwt_raises_no_key_id( + test_key_pair: jwk.JWK, + test_jwk_payload: Dict[str, Union[str, bool]] +): test_private_key = test_key_pair.export_to_pem(private_key=True, password=None) jwk_manager.set_key('test', 'test-key', test_private_key.decode('utf-8')) @@ -136,8 +169,12 @@ def test_get_jwt_raises_no_key_id(test_key_pair, test_jwk_payload): jwk_manager.get_jwt('non-existent', test_jwk_payload) -def test_get_jwks(test_key_pair, test_jwk_payload, test_jwt, - test_jwks): +def test_get_jwks( + test_key_pair: jwk.JWK, + test_jwk_payload: Dict[str, Union[str, bool]], + test_jwt: str, + test_jwks: Dict[str, str] +): test_private_key = test_key_pair.export_to_pem(private_key=True, password=None) jwk_manager.set_key('testing', @@ -148,8 +185,11 @@ def test_get_jwks(test_key_pair, test_jwk_payload, test_jwt, assert result[0] == test_jwks -def test_get_jwks_not_found(test_key_pair, test_jwk_payload, - test_jwt): +def test_get_jwks_not_found( + test_key_pair: jwk.JWK, + test_jwk_payload: Dict[str, Union[str, bool]], + test_jwt: str, +): result = jwk_manager.get_jwks('non-existent') assert not result @@ -158,13 +198,18 @@ def test_get_jwks_not_found(test_key_pair, test_jwk_payload, Mock(wraps=datetime.datetime)) @patch.object(confidant.services.jwkmanager, 'JWT_ACTIVE_SIGNING_KEYS', {'test': '0h7R8dL0rU-b3p3onft_BPfuRW1Ld7YjsFnOWJuFXUE'}) -def test_get_jwt_with_ca(test_jwk_payload, test_jwt, - test_certificate_authorities): +def test_get_jwt_with_ca( + mocker: MockerFixture, + test_jwk_payload: Dict[str, Union[str, bool]], + test_jwt: str, + test_certificate_authorities: str +): with patch.object(confidant.services.jwkmanager, 'JWT_CERTIFICATE_AUTHORITIES', test_certificate_authorities): - confidant.services.jwkmanager.datetime.now.return_value = \ - datetime.datetime( + mocker.patch( + 'confidant.services.jwkmanager.datetime.now', + return_value=datetime.datetime( year=2020, month=10, day=10, @@ -173,6 +218,7 @@ def test_get_jwt_with_ca(test_jwk_payload, test_jwt, second=0, microsecond=0 ) + ) result = jwk_manager.get_jwt('test', test_jwk_payload) assert result == test_jwt diff --git a/tests/unit/confidant/services/keymanager_test.py b/tests/unit/confidant/services/keymanager_test.py index 4a9605d4..9d7173f8 100644 --- a/tests/unit/confidant/services/keymanager_test.py +++ b/tests/unit/confidant/services/keymanager_test.py @@ -1,7 +1,9 @@ from confidant.services import keymanager +from pytest_mock.plugin import MockerFixture -def test_get_key_id(mocker): + +def test_get_key_id(mocker: MockerFixture): mocker.patch('confidant.services.keymanager._KEY_METADATA', {}) mock_auth_client = mocker.Mock() mock_auth_client.describe_key = mocker.Mock( @@ -14,7 +16,7 @@ def test_get_key_id(mocker): assert keymanager.get_key_id('mockalias') == 'mockid' -def test_get_key_id_cached(mocker): +def test_get_key_id_cached(mocker: MockerFixture): mocker.patch( 'confidant.services.keymanager._KEY_METADATA', {'mockalias': {'KeyMetadata': {'KeyId': 'mockid'}}} @@ -29,7 +31,7 @@ def test_get_key_id_cached(mocker): assert keymanager.get_key_id('mockalias') == 'mockid' -def test_create_datakey_mocked(mocker): +def test_create_datakey_mocked(mocker: MockerFixture): fernet_mock = mocker.patch('cryptography.fernet.Fernet.generate_key') fernet_mock.return_value = 'mocked_fernet_key' mocker.patch('confidant.services.keymanager.settings.USE_ENCRYPTION', False) @@ -46,7 +48,7 @@ def test_create_datakey_mocked(mocker): assert ret['ciphertext'] == 'mocked_fernet_key' -def test_decrypt_datakey_mocked(mocker): +def test_decrypt_datakey_mocked(mocker: MockerFixture): mocker.patch('confidant.services.keymanager.settings.USE_ENCRYPTION', False) ret = keymanager.decrypt_datakey('mocked_fernet_key') @@ -54,7 +56,7 @@ def test_decrypt_datakey_mocked(mocker): assert ret == 'mocked_fernet_key' -def test_create_datakey_with_encryption(mocker): +def test_create_datakey_with_encryption(mocker: MockerFixture): cd_mock = mocker.patch( 'confidant.services.keymanager.cryptolib.create_datakey' ) @@ -72,7 +74,7 @@ def test_create_datakey_with_encryption(mocker): assert cmd_mock.called is False -def test_decrypt_datakey_with_encryption(mocker): +def test_decrypt_datakey_with_encryption(mocker: MockerFixture): dd_mock = mocker.patch( 'confidant.services.keymanager.cryptolib.decrypt_datakey' ) diff --git a/tests/unit/confidant/services/servicemanager_test.py b/tests/unit/confidant/services/servicemanager_test.py index b9b3c328..cdbb70c8 100644 --- a/tests/unit/confidant/services/servicemanager_test.py +++ b/tests/unit/confidant/services/servicemanager_test.py @@ -4,8 +4,10 @@ from pynamodb.exceptions import DoesNotExist +from pytest_mock.plugin import MockerFixture -def test_get_latest_service_revision(mocker): + +def test_get_latest_service_revision(mocker: MockerFixture): get = mocker.patch( 'confidant.models.service.Service.get' )