diff --git a/.ds.baseline b/.ds.baseline index f82c96729..b84a53351 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -527,7 +527,7 @@ "filename": "tests/app/main/views/organizations/test_organization_invites.py", "hashed_secret": "bdbb156d25d02fd7792865824201dda1c60f4473", "is_verified": false, - "line_number": 265, + "line_number": 274, "is_secret": false }, { @@ -535,7 +535,7 @@ "filename": "tests/app/main/views/organizations/test_organization_invites.py", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 273, + "line_number": 282, "is_secret": false } ], @@ -545,7 +545,7 @@ "filename": "tests/app/main/views/test_accept_invite.py", "hashed_secret": "07f0a6c13923fc3b5f0c57ffa2d29b715eb80d71", "is_verified": false, - "line_number": 607, + "line_number": 626, "is_secret": false } ], @@ -565,7 +565,7 @@ "filename": "tests/app/main/views/test_register.py", "hashed_secret": "bdbb156d25d02fd7792865824201dda1c60f4473", "is_verified": false, - "line_number": 116, + "line_number": 122, "is_secret": false }, { @@ -573,7 +573,7 @@ "filename": "tests/app/main/views/test_register.py", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 192, + "line_number": 201, "is_secret": false }, { @@ -581,7 +581,7 @@ "filename": "tests/app/main/views/test_register.py", "hashed_secret": "bb5b7caa27d005d38039e3797c3ddb9bcd22c3c8", "is_verified": false, - "line_number": 260, + "line_number": 274, "is_secret": false } ], @@ -591,7 +591,7 @@ "filename": "tests/app/main/views/test_sign_in.py", "hashed_secret": "8b8b69116ee882b5e987e330f55db81aba0636f9", "is_verified": false, - "line_number": 90, + "line_number": 97, "is_secret": false } ], @@ -601,7 +601,7 @@ "filename": "tests/app/main/views/test_two_factor.py", "hashed_secret": "dc66ad927c29e31c6c374231f57a4684b0687bfe", "is_verified": false, - "line_number": 267, + "line_number": 290, "is_secret": false } ], @@ -702,5 +702,5 @@ } ] }, - "generated_at": "2024-07-09T16:26:26Z" + "generated_at": "2024-07-11T16:37:23Z" } diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index d8ebf7350..edd14c6ea 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -166,6 +166,8 @@ jobs: - uses: pypa/gh-action-pip-audit@v1.0.8 with: inputs: requirements.txt + ignore-vulns: | + PYSEC-2024-60 - name: Run npm audit run: make npm-audit diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index ae58ff226..53bf069b6 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -36,7 +36,7 @@ from notifications_utils.recipients import format_phone_number_human_readable @socketio.on("fetch_daily_stats") def handle_fetch_daily_stats(): - service_id = session.get('service_id') + service_id = session.get("service_id") if service_id: date_range = get_stats_date_range() daily_stats = service_api_client.get_service_notification_statistics_by_day( diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index 42a4de090..dddf838a1 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -312,7 +312,7 @@ def get_status_filters(service, message_type, statistics): filters = [ # key, label, option ("requested", "total", "sending,delivered,failed"), - ("pending", "pending", "pending"), + ("pending", "pending", "sending,pending"), ("delivered", "delivered", "delivered"), ("failed", "failed", "failed"), ] diff --git a/app/main/views/sign_out.py b/app/main/views/sign_out.py index 5ec96e189..82ba5497e 100644 --- a/app/main/views/sign_out.py +++ b/app/main/views/sign_out.py @@ -1,7 +1,7 @@ import os import requests -from flask import current_app, redirect, url_for +from flask import current_app, redirect, session, url_for from flask_login import current_user from app.main import main @@ -25,12 +25,16 @@ def _sign_out_at_login_dot_gov(): @main.route("/sign-out", methods=(["GET", "POST"])) def sign_out(): - # An AnonymousUser does not have an id - current_app.logger.info("HIT THE REGULAR SIGN OUT") + if current_user.is_authenticated: - # TODO This doesn't work yet, due to problems above. + current_user.deactivate() + session.clear() current_user.sign_out() + + session.permanent = False + login_dot_gov_logout_url = os.getenv("LOGIN_DOT_GOV_LOGOUT_URL") if login_dot_gov_logout_url: + current_app.config["SESSION_PERMANENT"] = False return redirect(login_dot_gov_logout_url) return redirect(url_for("main.index")) diff --git a/app/main/views/verify.py b/app/main/views/verify.py index f6dec6fa2..fbe510643 100644 --- a/app/main/views/verify.py +++ b/app/main/views/verify.py @@ -79,5 +79,4 @@ def activate_user(user_id): else: activated_user = user.activate() activated_user.login() - return redirect(url_for("main.add_service", first="first")) diff --git a/app/models/user.py b/app/models/user.py index 932e754c8..c22fce4b7 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -147,6 +147,13 @@ class User(JSONModel, UserMixin): else: return self + def deactivate(self): + if self.is_active: + user_data = user_api_client.deactivate_user(self.id) + return self.__class__(user_data["data"]) + else: + return self + def login(self): login_user(self) session["user_id"] = self.id diff --git a/app/notify_client/__init__.py b/app/notify_client/__init__.py index 2cad0b68a..3bbb35733 100644 --- a/app/notify_client/__init__.py +++ b/app/notify_client/__init__.py @@ -1,3 +1,5 @@ +import os + from flask import abort, has_request_context, request from flask_login import current_user from notifications_python_client import __version__ @@ -54,16 +56,40 @@ class NotifyAdminAPIClient(BaseAPIClient): ): abort(403) + def check_inactive_user(self, *args): + still_signing_in = False + for arg in args: + arg = str(arg) + if ( + "get-login-gov-user" in arg + or "user/email" in arg + or "/activate" in arg + or "/email-code" in arg + ): + still_signing_in = True + # TODO: Update this once E2E tests are managed by a feature flag or some other main config option. + if os.getenv("NOTIFY_E2E_TEST_EMAIL"): + # allow end-to-end tests to skip check + pass + elif still_signing_in is True: + # we are not full signed in yet + pass + elif not current_user or not current_user.is_active: + abort(403) + def post(self, *args, **kwargs): self.check_inactive_service() + self.check_inactive_user(args) return super().post(*args, **kwargs) def put(self, *args, **kwargs): self.check_inactive_service() + self.check_inactive_user() return super().put(*args, **kwargs) def delete(self, *args, **kwargs): self.check_inactive_service() + self.check_inactive_user() return super().delete(*args, **kwargs) diff --git a/app/notify_client/user_api_client.py b/app/notify_client/user_api_client.py index e85225838..06d6385b2 100644 --- a/app/notify_client/user_api_client.py +++ b/app/notify_client/user_api_client.py @@ -217,6 +217,10 @@ class UserApiClient(NotifyAdminAPIClient): def activate_user(self, user_id): return self.post("/user/{}/activate".format(user_id), data=None) + @cache.delete("user-{user_id}") + def deactivate_user(self, user_id): + return self.post("/user/{}/deactivate".format(user_id), data=None) + def send_change_email_verification(self, user_id, new_email): endpoint = "/user/{}/change-email-verification".format(user_id) data = {"email": new_email} diff --git a/poetry.lock b/poetry.lock index ab48092ee..4f96d67a3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -182,17 +182,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.139" +version = "1.34.143" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.139-py3-none-any.whl", hash = "sha256:98b2a12bcb30e679fa9f60fc74145a39db5ec2ca7b7c763f42896e3bd9b3a38d"}, - {file = "boto3-1.34.139.tar.gz", hash = "sha256:32b99f0d76ec81fdca287ace2c9744a2eb8b92cb62bf4d26d52a4f516b63a6bf"}, + {file = "boto3-1.34.143-py3-none-any.whl", hash = "sha256:0d16832f23e6bd3ae94e35ea8e625529850bfad9baccd426de96ad8f445d8e03"}, + {file = "boto3-1.34.143.tar.gz", hash = "sha256:b590ce80c65149194def43ebf0ea1cf0533945502507837389a8d22e3ecbcf05"}, ] [package.dependencies] -botocore = ">=1.34.139,<1.35.0" +botocore = ">=1.34.143,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -201,13 +201,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.139" +version = "1.34.143" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.139-py3-none-any.whl", hash = "sha256:dd1e085d4caa2a4c1b7d83e3bc51416111c8238a35d498e9d3b04f3b63b086ba"}, - {file = "botocore-1.34.139.tar.gz", hash = "sha256:df023d8cf8999d574214dad4645cb90f9d2ccd1494f6ee2b57b1ab7522f6be77"}, + {file = "botocore-1.34.143-py3-none-any.whl", hash = "sha256:094aea179e8aaa1bc957ad49cc27d93b189dd3a1f3075d8b0ca7c445a2a88430"}, + {file = "botocore-1.34.143.tar.gz", hash = "sha256:059f032ec05733a836e04e869c5a15534420102f93116f3bc9a5b759b0651caf"}, ] [package.dependencies] @@ -473,63 +473,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.4" +version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, - {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, - {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, - {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, - {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, - {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, - {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, - {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, - {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, - {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, - {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, - {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, - {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, - {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.extras] @@ -591,19 +591,19 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "cyclonedx-python-lib" -version = "7.5.0" +version = "7.5.1" description = "Python library for CycloneDX" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "cyclonedx_python_lib-7.5.0-py3-none-any.whl", hash = "sha256:0bb301bfee57d21a76a1288c3670d5aca9924bbe212d13d09e264dfde8cc7389"}, - {file = "cyclonedx_python_lib-7.5.0.tar.gz", hash = "sha256:28ef507c1a803e39f6932f328ca26f0fd21efbd9539175492b16325e400b4e1a"}, + {file = "cyclonedx_python_lib-7.5.1-py3-none-any.whl", hash = "sha256:9fc2c2e5facfd9530ede1f4525c903d29d91945688c5689b6d5fab46381dcab9"}, + {file = "cyclonedx_python_lib-7.5.1.tar.gz", hash = "sha256:00cfe1e58452698650ae08b8f4389f7b1ec203a3e1c50cbf6ca6d320941dfb3f"}, ] [package.dependencies] license-expression = ">=30,<31" packageurl-python = ">=0.11,<2" -py-serializable = ">=1.0.3,<2" +py-serializable = ">=1.1.0,<2.0.0" sortedcontainers = ">=2.4.0,<3.0.0" [package.extras] @@ -1112,13 +1112,13 @@ lxml = ["lxml"] [[package]] name = "humanize" -version = "4.9.0" +version = "4.10.0" description = "Python humanize utilities" optional = false python-versions = ">=3.8" files = [ - {file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"}, - {file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"}, + {file = "humanize-4.10.0-py3-none-any.whl", hash = "sha256:39e7ccb96923e732b5c2e27aeaa3b10a8dfeeba3eb965ba7b74a3eb0e30040a6"}, + {file = "humanize-4.10.0.tar.gz", hash = "sha256:06b6eb0293e4b85e8d385397c5868926820db32b9b654b932f57fa41c23c9978"}, ] [package.extras] @@ -1126,13 +1126,13 @@ tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "identify" -version = "2.5.36" +version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -1581,13 +1581,13 @@ files = [ [[package]] name = "moto" -version = "5.0.10" +version = "5.0.11" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "moto-5.0.10-py2.py3-none-any.whl", hash = "sha256:9ffae2f64cc8fe95b9a12d63ae7268a7d6bea9993b922905b5abd8197d852cd0"}, - {file = "moto-5.0.10.tar.gz", hash = "sha256:eff37363221c93ea44f95721ae0ddb56f977fe70437a041b6cc641ee90266279"}, + {file = "moto-5.0.11-py2.py3-none-any.whl", hash = "sha256:bdba9bec0afcde9f99b58c5271d6458dbfcda0a0a1e9beaecd808d2591db65ea"}, + {file = "moto-5.0.11.tar.gz", hash = "sha256:606b641f4c6ef69f28a84147d6d6806d052011e7ae7b0fe46ae8858e7a27a0a3"}, ] [package.dependencies] @@ -1685,6 +1685,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -1701,40 +1702,40 @@ files = [ [[package]] name = "newrelic" -version = "9.11.0" +version = "9.12.0" description = "New Relic Python Agent" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "newrelic-9.11.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:87670d872c3abc36203e10f93d266c8f36ad2bd06fb54e790001a409f9e2f40f"}, - {file = "newrelic-9.11.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:11653fd14f55999c5058b4dde8c721833076c0bd3efe668296725a622e9e7de8"}, - {file = "newrelic-9.11.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:72dd3eb190c62bb54aa59029f0d6ac1420c2050b3aaf88d947fc7f62ec58d97f"}, - {file = "newrelic-9.11.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02eab15af4a08b870bcfdbc56390ecbb9dcacd144fe77f39a26d1be207bd30f0"}, - {file = "newrelic-9.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f477cdda9b998205084b822089b3ee4a8a2d9cd66b6f12487c9f9002566c5cb"}, - {file = "newrelic-9.11.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec4173cd0f83420e6f61f92955065f1d460075af5e5bf88a5fea746e3cc180"}, - {file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8664e3b9e6ee0f78806b0cf7c90656a1a86d13232c2e0be18a1b1eb452f3f5d1"}, - {file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f1e473eb0505cb91ab9a4155321eabe13a2f6b93fb3c41d6f10e5486276be60"}, - {file = "newrelic-9.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f95eb366ff714bce32476d256551b853247a72398ec46a89148ef5108509aa8"}, - {file = "newrelic-9.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553674a66ef2c2206852b415b74e3c2fb7ed2b92e9800b68394d577f6aa1133e"}, - {file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:21e7b52d5b214bba3534ced166e6ec991117772815020bec38b0571fdcecbaf4"}, - {file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10cb7f7a78c49580602b90f367f3378264e495f2f3706734f88ced7e7ca9b033"}, - {file = "newrelic-9.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34b25d1beaf19825409f3d915a5bafa87b7b9230415821422be1e78e988750b7"}, - {file = "newrelic-9.11.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02139458aefba86a4572cb8214f91a942103d24d5502395f64d6d7a4ad3f25"}, - {file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3283885bcf31d9cbf8facb0004508a4eaa652a62471e0b724d26f9738a291979"}, - {file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0d43a0891bf71333f6a6253cf87dea2c9009e22699a2acfd93608125a33b1936"}, - {file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7903ba71ce5a4b2840f6d3c63ecd0fb3a018d2aceb915b48133c13c4a60185f"}, - {file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d88fa17a515fb002eb14570800e4bfa69ac87ac27e6e2a96bc2bc9b60c80057a"}, - {file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ceac1d8f13da38fa1b41c8202a91d3b4345e06adb655deaae0df08911fda56f"}, - {file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ffc0d8d490de0f12df70db637481aaadb8a43fb6d71ba8866dc14242aa5edad4"}, - {file = "newrelic-9.11.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6e1bb0df8ff2b54195baac41fddc0e15ea1bdf1deb6af49153487696355181"}, - {file = "newrelic-9.11.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5d2d0814e1aa9de5bd55797ff8c426d98200ba46ca14dbca15557d0f17cfb4e"}, - {file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b33539345c7cf349b65a176a30ab38e2998b071512a7450f5c5b89ac6c097006"}, - {file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c073f4c26539d6d74fbf4bac7f5046cac578975fb2cf77b156f802f1b39835e"}, - {file = "newrelic-9.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76eb4cc599645a38a459b0002696d9c84844fecb02cf07bc18a4a91f737e438e"}, - {file = "newrelic-9.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d08587e694f5c517e55fb7119f924c64569d2e7ec4968ef761fc1f7bd1f40c"}, - {file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bc5c1b8a51946f64c34fc5fa29ce0221c4927a65c7f4435b3b8adeb29b9812d2"}, - {file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2010ed2793294a7e3c1057ec301d48997ed05dcef114d4c25120ac771f66bac1"}, - {file = "newrelic-9.11.0.tar.gz", hash = "sha256:94369792d61ccf21469c35cf66886c32350a180d8e782c0d28ec66411db29474"}, + {file = "newrelic-9.12.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c349e3b611e8da446aa8045c92e986d77bcd945903bfa08092b9a7c217036fd9"}, + {file = "newrelic-9.12.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:2c8168a2bd5db45566471306ef962e925ab2c9fa92079c3f5863d4a4585dfcbd"}, + {file = "newrelic-9.12.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4c73d470a61a9f09a204fd47a4822af0d1e52ccac36a6737f72e0cbb2a22dba6"}, + {file = "newrelic-9.12.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:0a3debb761aca68491f14fb6e5bc0100eeef1ab314073ab4696d55cd906b4bec"}, + {file = "newrelic-9.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c8c4b3d103db423640fda4d6c6b58c79558097ebd111a62e957408a4cf1c71"}, + {file = "newrelic-9.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923e83e40e30fc7ca0f441bb9c745274f7236869bfbe65da487714bfcd4f46c0"}, + {file = "newrelic-9.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f2bed7bfbcbd0e95b6ad1c82e30098d79678cbc6410fc2f88c439e6786c6640a"}, + {file = "newrelic-9.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b4c343c0cf2a0b59467f9daf0f303d28dad6795dc75bc54582d3198e1d2b4da"}, + {file = "newrelic-9.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d93402402a32905950d6e646ec220bdb10a522e896c219941c92e474cfa2cdb"}, + {file = "newrelic-9.12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a614a1da090cbfbd9f5ab3fcdafc253408d76ffc0a22a73cc16fd5c97b67b97"}, + {file = "newrelic-9.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d5040601cedf308faa818cc9fc5c8e48283bdcb4c02a2e1e468e67e037200f83"}, + {file = "newrelic-9.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0fff006b6d6eb86a25483a4aed216f98293ec44c29b497c1f18f23f05e059991"}, + {file = "newrelic-9.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43cccc52c3ec9c0aa457d3d14557bb19383dbad1afe018d9063c0a7ffbe29232"}, + {file = "newrelic-9.12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df75838fd766252282070a5dcdc78906c4b9e0934280c601815c5eb1cc6ce6ff"}, + {file = "newrelic-9.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d8f3f8f8d27a1bafc3c4cc930a762d96119897f1808bccd162597b510e236de9"}, + {file = "newrelic-9.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:98d5bd7222dd96b0fc194dd2142827e9b70959527f2480fef61da82857b00cb2"}, + {file = "newrelic-9.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f83f8023a12e8b4ec217e59e1a56375ad141a2f7a620df363a688e1f1d3e93b"}, + {file = "newrelic-9.12.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec7a46b5c4e77374a1c01b637fca63c187218c153be075bc806663881c53a03e"}, + {file = "newrelic-9.12.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:12e721c19e78a7e7a1443c327acf133d94b0c12add6ec514235a668656732011"}, + {file = "newrelic-9.12.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:353a11bfa737043309025a0949cb6d7cfd7c7209cdee7abe8af774af8f44586f"}, + {file = "newrelic-9.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae6dfffca34591771bc4b6c493c68c15209d2b4e3d79c46239204014a20e53d"}, + {file = "newrelic-9.12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be6315beacb0ac7ac99c24e38b8ef072e3930f4d06970fb2fa84da0a990c3467"}, + {file = "newrelic-9.12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4cb87e19a2e522417e2b421b799fe0c20cedf953a1e061fbf50bb21683e2420f"}, + {file = "newrelic-9.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bde6db956d363d8d846d3d0c76d4a4a539c809cebb40e45a53d099a39cdd0ea3"}, + {file = "newrelic-9.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e94cacdeb15ddfc0a1f8353d896a1069da1302416b4afbf67a953f0706235be2"}, + {file = "newrelic-9.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434be59492f52c9b8401adada597a5dca037cf003374d5dd461bca1db64d3ca7"}, + {file = "newrelic-9.12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:16de826ddc4af4cf45fe607aeca7117dd08c948edd41aaf90b86c596a3f0eaac"}, + {file = "newrelic-9.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:61253b5cf43787b0c19d00662eb7e60d4c1018c09374e9b0825338e831cad900"}, + {file = "newrelic-9.12.0.tar.gz", hash = "sha256:e8c1ed86f9c2f0954817d4405a4fa1cb09b0cc720b3c702fa2cac1c4fffeaec1"}, ] [package.extras] @@ -1841,13 +1842,13 @@ dev = ["black", "mypy", "pytest"] [[package]] name = "packageurl-python" -version = "0.15.2" +version = "0.15.3" description = "A purl aka. Package URL parser and builder" optional = false python-versions = ">=3.7" files = [ - {file = "packageurl_python-0.15.2-py3-none-any.whl", hash = "sha256:6b81641aeedf0a73377d88a8a640e45a2a0848ffdf5447d24eeef8526c41ac92"}, - {file = "packageurl_python-0.15.2.tar.gz", hash = "sha256:9cd10eeedbc6680728c10a1585c6dd7bbad4ef4b389d80cd0ac223205e9c87df"}, + {file = "packageurl_python-0.15.3-py3-none-any.whl", hash = "sha256:96624702032239e70e61b950e14460a5b5f87ac21fc68f119414047b94f0de27"}, + {file = "packageurl_python-0.15.3.tar.gz", hash = "sha256:82e1150f1fc228e25e7b3be1c641ef96b6a0811526c0b4e4f7882a181e862607"}, ] [package.extras] @@ -1902,24 +1903,24 @@ files = [ [[package]] name = "pip" -version = "24.1.1" +version = "24.1.2" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" files = [ - {file = "pip-24.1.1-py3-none-any.whl", hash = "sha256:efca15145a95e95c00608afeab66311d40bfb73bb2266a855befd705e6bb15a0"}, - {file = "pip-24.1.1.tar.gz", hash = "sha256:5aa64f65e1952733ee0a9a9b1f52496ebdb3f3077cc46f80a16d983b58d1180a"}, + {file = "pip-24.1.2-py3-none-any.whl", hash = "sha256:7cd207eed4c60b0f411b444cd1464198fe186671c323b6cd6d433ed80fc9d247"}, + {file = "pip-24.1.2.tar.gz", hash = "sha256:e5458a0b89f2755e0ee8c0c77613fe5273e05f337907874d64f13171a898a7ff"}, ] [[package]] name = "pip-api" -version = "0.0.33" +version = "0.0.34" description = "An unofficial, importable pip API" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pip-api-0.0.33.tar.gz", hash = "sha256:1c2522ae21efcb034d89cc99f6cf1025293b31c63c29ee98b23f03a85f36bdae"}, - {file = "pip_api-0.0.33-py3-none-any.whl", hash = "sha256:b8d6eb5a87d3a9e112a20a8e9d24a6fc12d4e1c94d7595eeaf74be11ad47276c"}, + {file = "pip_api-0.0.34-py3-none-any.whl", hash = "sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb"}, + {file = "pip_api-0.0.34.tar.gz", hash = "sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625"}, ] [package.dependencies] @@ -2043,13 +2044,13 @@ virtualenv = ">=20.10.0" [[package]] name = "py-serializable" -version = "1.0.3" +version = "1.1.0" description = "Library for serializing and deserializing Python Objects to and from JSON and XML." optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "py_serializable-1.0.3-py3-none-any.whl", hash = "sha256:afba815f465b9fe7ab1c1a56d1aa8880c8a9e67a6e28b7ed62d4696fa369caf8"}, - {file = "py_serializable-1.0.3.tar.gz", hash = "sha256:da3cb4b1f3cc5cc5ebecdd3dadbabd5f65d764357366fa64ee9cbaf0d4b70dcf"}, + {file = "py_serializable-1.1.0-py3-none-any.whl", hash = "sha256:ae7ae4326b0d037b7e710f6e8bb1a97ece4ac2895a1f443a17ffd17f85547d76"}, + {file = "py_serializable-1.1.0.tar.gz", hash = "sha256:3311ab39063b131caca0fb75e2038153682e55576c67f24a2de72d402dccb6e0"}, ] [package.dependencies] @@ -3116,4 +3117,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "0ac6a41e91fc7c667eac240bbd5af3d18d61100e9d3bcb651e4ec82978d24c9b" +content-hash = "78cfb6ff10239c4ad8e34c059e01c9ee7603d591c43cb06dd0a1acf8fa51f510" diff --git a/pyproject.toml b/pyproject.toml index cb4ccd40e..06e8ffe35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ flask-talisman = "*" flask-wtf = "^1.2" govuk-bank-holidays = "^0.14" gunicorn = {version = "==22.0.0", extras = ["eventlet"]} -humanize = "~=4.9" +humanize = "~=4.10" itsdangerous = "~=2.2" jinja2 = "~=3.1" newrelic = "*" @@ -39,8 +39,8 @@ wtforms = "~=3.1" markdown = "^3.5.2" async-timeout = "^4.0.3" bleach = "^6.1.0" -boto3 = "^1.34.139" -botocore = "^1.34.139" +boto3 = "^1.34.143" +botocore = "^1.34.143" cachetools = "^5.3.3" cffi = "^1.16.0" cryptography = "^42.0.8" diff --git a/tests/app/main/test_errorhandlers.py b/tests/app/main/test_errorhandlers.py index d810a67a5..57537762a 100644 --- a/tests/app/main/test_errorhandlers.py +++ b/tests/app/main/test_errorhandlers.py @@ -68,6 +68,7 @@ def test_csrf_redirects_to_sign_in_page_if_not_signed_in(client_request, mocker) csrf_err = CSRFError("400 Bad Request: The CSRF tokens do not match.") mocker.patch("app.main.views.index.render_template", side_effect=csrf_err) + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.get_url( "/privacy", diff --git a/tests/app/main/views/accounts/test_choose_accounts.py b/tests/app/main/views/accounts/test_choose_accounts.py index 3354104b2..1ead9dd0d 100644 --- a/tests/app/main/views/accounts/test_choose_accounts.py +++ b/tests/app/main/views/accounts/test_choose_accounts.py @@ -289,9 +289,10 @@ def test_choose_account_should_not_show_back_to_service_link_if_no_service_in_se def test_choose_account_should_not_show_back_to_service_link_if_not_signed_in( - client_request, - mock_get_service, + client_request, mock_get_service, mocker ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: diff --git a/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py b/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py index d28d58f22..f1d536377 100644 --- a/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py +++ b/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py @@ -107,9 +107,10 @@ def test_show_accounts_or_dashboard_doesnt_redirect_to_org_dashboard_if_user_not def test_show_accounts_or_dashboard_redirects_if_not_logged_in( - client_request, - notify_admin, + client_request, notify_admin, mocker ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.get( "main.show_accounts_or_dashboard", diff --git a/tests/app/main/views/organizations/test_organization_invites.py b/tests/app/main/views/organizations/test_organization_invites.py index c9e5da264..ebefe9178 100644 --- a/tests/app/main/views/organizations/test_organization_invites.py +++ b/tests/app/main/views/organizations/test_organization_invites.py @@ -116,6 +116,7 @@ def test_cancelled_invite_opened_by_user( mock_get_organization, fake_uuid, ): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mock_get_user = mocker.patch( "app.user_api_client.get_user", return_value=api_user_active @@ -145,8 +146,9 @@ def test_cancelled_invite_opened_by_user( def test_user_invite_already_accepted( - client_request, mock_check_org_accepted_invite_token + client_request, mock_check_org_accepted_invite_token, mocker ): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.get( "main.accept_org_invite", @@ -169,7 +171,9 @@ def test_existing_user_invite_already_is_member_of_organization( mock_accept_org_invite, mock_add_user_to_organization, mock_update_user_attribute, + mocker, ): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mock_update_user_attribute.reset_mock() client_request.get( @@ -201,7 +205,9 @@ def test_existing_user_invite_not_a_member_of_organization( mock_accept_org_invite, mock_add_user_to_organization, mock_update_user_attribute, + mocker, ): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mock_update_user_attribute.reset_mock() client_request.get( @@ -232,7 +238,9 @@ def test_user_accepts_invite( mock_check_org_invite_token, mock_dont_get_user_by_email, mock_get_users_for_organization, + mocker, ): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.get( "main.accept_org_invite", @@ -246,8 +254,9 @@ def test_user_accepts_invite( def test_registration_from_org_invite_404s_if_user_not_in_session( - client_request, + client_request, mocker ): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.get( "main.register_from_org_invite", @@ -262,7 +271,7 @@ def test_registration_from_org_invite_404s_if_user_not_in_session( { "name": "Bad Mobile", "mobile_number": "not good", - "password": "validPassword!", + "password": "validPassword!", # noqa }, "The string supplied did not seem to be a phone number", ), @@ -270,7 +279,7 @@ def test_registration_from_org_invite_404s_if_user_not_in_session( { "name": "Bad Password", "mobile_number": "+12021234123", - "password": "password", + "password": "password", # noqa }, "Choose a password that’s harder to guess", ), @@ -282,7 +291,9 @@ def test_registration_from_org_invite_has_bad_data( data, error, mock_get_invited_org_user_by_id, + mocker, ): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index a6d622c12..1fc355abb 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -133,6 +133,8 @@ def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard( mock_get_user, mock_update_user_attribute, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() expected_service = service_one["id"] expected_permissions = { @@ -176,6 +178,8 @@ def test_existing_user_with_no_permissions_or_folder_permissions_accept_invite( mock_get_user, mock_update_user_attribute, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() expected_service = service_one["id"] @@ -205,6 +209,8 @@ def test_if_existing_user_accepts_twice_they_redirect_to_sign_in( mock_get_service, mock_update_user_attribute, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() # Logging out updates the current session ID to `None` mock_update_user_attribute.reset_mock() @@ -321,6 +327,8 @@ def test_existing_user_of_service_get_redirected_to_signin( mock_accept_invite, mock_update_user_attribute, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() sample_invite["email_address"] = api_user_active["email_address"] mocker.patch("app.models.user.Users.client_method", return_value=[api_user_active]) @@ -355,6 +363,8 @@ def test_accept_invite_redirects_if_api_raises_an_error_that_they_are_already_pa mock_get_user, mock_update_user_attribute, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mocker.patch( @@ -397,6 +407,8 @@ def test_existing_signed_out_user_accept_invite_redirects_to_sign_in( mock_get_user, mock_update_user_attribute, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() expected_service = service_one["id"] expected_permissions = { @@ -438,7 +450,10 @@ def test_cancelled_invited_user_accepts_invited_redirect_to_cancelled_invitation sample_invite, mock_check_invite_token, mock_update_user_attribute, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mock_update_user_attribute.reset_mock() sample_invite["status"] = "cancelled" @@ -466,6 +481,8 @@ def test_new_user_accept_invite_with_malformed_token( service_one, mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mocker.patch( api_endpoint, @@ -585,6 +602,8 @@ def test_new_invited_user_verifies_and_added_to_service( "app.main.views.verify.service_api_client.retrieve_service_invite_data", return_value={}, ) + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() # visit accept token page @@ -604,7 +623,7 @@ def test_new_invited_user_verifies_and_added_to_service( "service": sample_invite["service"], "email_address": sample_invite["email_address"], "from_user": sample_invite["from_user"], - "password": "longpassword", + "password": "longpassword", # noqa "mobile_number": "+12027890123", "name": "Invited User", "auth_type": "sms_auth", @@ -673,6 +692,8 @@ def test_new_invited_user_is_redirected_to_correct_place( "app.main.views.verify.service_api_client.retrieve_service_invite_data", return_value={}, ) + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mocker.patch( "app.service_api_client.get_service", diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index e9036de22..475d7514e 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -1931,14 +1931,14 @@ def test_fetch_daily_stats( ) with app.test_client() as client: with client.session_transaction() as sess: - sess['service_id'] = service_id + sess["service_id"] = service_id socketio_client = SocketIOTestClient(app, socketio, flask_test_client=client) connected = socketio_client.is_connected() assert connected, "Client should be connected" - socketio_client.emit('fetch_daily_stats') + socketio_client.emit("fetch_daily_stats") received = socketio_client.get_received() mock_service_api.assert_called_once_with( @@ -1967,8 +1967,13 @@ def test_fetch_daily_stats( SERVICE_ONE_ID, USER_ONE_ID, {"start_date": "2024-01-01", "days": 7}, - {"service_id": SERVICE_ONE_ID, "user_id": USER_ONE_ID, "start_date": "2024-01-01", "days": 7}, - {"id": USER_ONE_ID, "name": "Test User"} + { + "service_id": SERVICE_ONE_ID, + "user_id": USER_ONE_ID, + "start_date": "2024-01-01", + "days": 7, + }, + {"id": USER_ONE_ID, "name": "Test User"}, ), ], ) @@ -2001,15 +2006,15 @@ def test_fetch_daily_stats_by_user( with app.test_client() as client: with client.session_transaction() as sess: - sess['service_id'] = service_id - sess['user_id'] = user_id + sess["service_id"] = service_id + sess["user_id"] = user_id socketio_client = SocketIOTestClient(app, socketio, flask_test_client=client) connected = socketio_client.is_connected() assert connected, "Client should be connected" - socketio_client.emit('fetch_daily_stats_by_user') + socketio_client.emit("fetch_daily_stats_by_user") received = socketio_client.get_received() mock_service_api.assert_called_once_with( diff --git a/tests/app/main/views/test_headers.py b/tests/app/main/views/test_headers.py index 211601d16..e31b12220 100644 --- a/tests/app/main/views/test_headers.py +++ b/tests/app/main/views/test_headers.py @@ -6,6 +6,8 @@ def test_owasp_useful_headers_set( mocker, mock_get_service_and_organization_counts, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() response = client_request.get_response(".index") diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 154106250..a4d57a89c 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -9,9 +9,10 @@ from tests.conftest import SERVICE_ONE_ID, normalize_spaces def test_non_logged_in_user_can_see_homepage( - client_request, - mock_get_service_and_organization_counts, + client_request, mock_get_service_and_organization_counts, mocker ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.get("main.index", _test_page_title=False) @@ -69,11 +70,10 @@ def test_robots(client_request): ) @freeze_time("2012-12-12 12:12") # So we don’t go out of business hours def test_hiding_pages_from_search_engines( - client_request, - mock_get_service_and_organization_counts, - endpoint, - kwargs, + client_request, mock_get_service_and_organization_counts, endpoint, kwargs, mocker ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() response = client_request.get_response(f"main.{endpoint}", **kwargs) assert "X-Robots-Tag" in response.headers @@ -103,11 +103,8 @@ def test_hiding_pages_from_search_engines( "billing_details", ], ) -def test_static_pages( - client_request, - mock_get_organization_by_domain, - view, -): +def test_static_pages(client_request, mock_get_organization_by_domain, view, mocker): + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") request = partial(client_request.get, "main.{}".format(view)) # Check the page loads when user is signed in @@ -130,9 +127,9 @@ def test_static_pages( ) -def test_guidance_pages_link_to_service_pages_when_signed_in( - client_request, -): +def test_guidance_pages_link_to_service_pages_when_signed_in(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") request = partial(client_request.get, "main.edit_and_format_messages") selector = ".list-number li a" @@ -170,7 +167,9 @@ def test_guidance_pages_link_to_service_pages_when_signed_in( ("callbacks", "documentation"), ], ) -def test_old_static_pages_redirect(client_request, view, expected_view): +def test_old_static_pages_redirect(client_request, view, expected_view, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.get( "main.{}".format(view), @@ -243,7 +242,10 @@ def test_sms_price( mock_get_service_and_organization_counts, current_date, expected_rate, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with freeze_time(current_date): diff --git a/tests/app/main/views/test_platform_admin.py b/tests/app/main/views/test_platform_admin.py index f5e7862a7..e617b27f8 100644 --- a/tests/app/main/views/test_platform_admin.py +++ b/tests/app/main/views/test_platform_admin.py @@ -27,7 +27,9 @@ from tests.conftest import SERVICE_ONE_ID, SERVICE_TWO_ID, normalize_spaces "main.trial_services", ], ) -def test_should_redirect_if_not_logged_in(client_request, endpoint): +def test_should_redirect_if_not_logged_in(client_request, endpoint, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.get( endpoint, diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index 19d8c5a4b..b3d70deb5 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -10,9 +10,9 @@ from app.models.user import User from tests.conftest import normalize_spaces -def test_render_register_returns_template_with_form( - client_request, -): +def test_render_register_returns_template_with_form(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.get_url("/register") @@ -58,7 +58,10 @@ def test_register_creates_new_user_and_redirects_to_continue_page( mock_login, phone_number_to_register_with, password, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() user_data = { "name": "Some One Valid", @@ -89,9 +92,9 @@ def test_register_creates_new_user_and_redirects_to_continue_page( # ) -def test_register_continue_handles_missing_session_sensibly( - client_request, -): +def test_register_continue_handles_missing_session_sensibly(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() # session is not set client_request.get( @@ -105,7 +108,10 @@ def test_process_register_returns_200_when_mobile_number_is_invalid( mock_send_verify_code, mock_get_user_by_email_not_found, mock_login, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.post( "main.register", @@ -113,7 +119,7 @@ def test_process_register_returns_200_when_mobile_number_is_invalid( "name": "Bad Mobile", "email_address": "bad_mobile@example.gsa.gov", "mobile_number": "not good", - "password": "validPassword!", + "password": "validPassword!", # noqa }, _expected_status=200, ) @@ -122,9 +128,10 @@ def test_process_register_returns_200_when_mobile_number_is_invalid( def test_should_return_200_when_email_is_not_gov_uk( - client_request, - mock_get_organizations, + client_request, mock_get_organizations, mocker ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.post( "main.register", @@ -163,6 +170,8 @@ def test_should_add_user_details_to_session( mock_login, email_address, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.post( "main.register", @@ -178,10 +187,10 @@ def test_should_add_user_details_to_session( def test_should_return_200_if_password_is_on_list_of_commonly_used_passwords( - client_request, - mock_get_user_by_email, - mock_login, + client_request, mock_get_user_by_email, mock_login, mocker ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.post( "main.register", @@ -189,7 +198,7 @@ def test_should_return_200_if_password_is_on_list_of_commonly_used_passwords( "name": "Bad Mobile", "email_address": "bad_mobile@example.gsa.gov", "mobile_number": "+12021234123", - "password": "password", + "password": "password", # noqa }, _expected_status=200, ) @@ -202,7 +211,10 @@ def test_register_with_existing_email_sends_emails( api_user_active, mock_get_user_by_email, mock_send_already_registered_email, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() user_data = { "name": "Already Hasaccount", @@ -244,6 +256,8 @@ def test_register_from_email_auth_invite( "app.main.views.verify.service_api_client.retrieve_service_invite_data", return_value={}, ) + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mock_login_user = mocker.patch("app.models.user.login_user") sample_invite["auth_type"] = "email_auth" @@ -257,7 +271,7 @@ def test_register_from_email_auth_invite( "name": "invited user", "email_address": sample_invite["email_address"], "mobile_number": "2028675301", - "password": "FSLKAJHFNvdzxgfyst", + "password": "FSLKAJHFNvdzxgfyst", # noqa "service": sample_invite["service"], "auth_type": "email_auth", } @@ -331,6 +345,8 @@ def test_can_register_email_auth_without_phone_number( "app.main.views.verify.service_api_client.retrieve_service_invite_data", return_value={}, ) + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() sample_invite["auth_type"] = "email_auth" with client_request.session_transaction() as session: @@ -364,7 +380,10 @@ def test_cannot_register_with_sms_auth_and_missing_mobile_number( mock_send_verify_code, mock_get_user_by_email_not_found, mock_login, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.post( "main.register", diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index efa01deb7..319b41c81 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -7,7 +7,9 @@ from app.models.user import User from tests.conftest import SERVICE_ONE_ID, normalize_spaces -def test_render_sign_in_template_for_new_user(client_request): +def test_render_sign_in_template_for_new_user(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.get("main.sign_in") assert normalize_spaces(page.select_one("h1").text) == "Sign in" @@ -25,7 +27,9 @@ def test_render_sign_in_template_for_new_user(client_request): assert "Sign in again" not in normalize_spaces(page.text) -def test_sign_in_explains_session_timeout(client_request): +def test_sign_in_explains_session_timeout(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.get("main.sign_in", next="/foo") assert ( @@ -81,7 +85,10 @@ def test_should_return_redirect_when_user_is_pending( mock_get_user_by_email_pending, api_user_pending, mock_verify_password, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.post( "main.sign_in", @@ -107,8 +114,14 @@ def test_should_return_redirect_when_user_is_pending( ) @pytest.mark.skip("TODO is this still relevant post login.gov switch?") def test_should_attempt_redirect_when_user_is_pending( - client_request, mock_get_user_by_email_pending, mock_verify_password, redirect_url + client_request, + mock_get_user_by_email_pending, + mock_verify_password, + redirect_url, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() client_request.post( "main.sign_in", @@ -132,6 +145,8 @@ def test_when_signing_in_as_invited_user_you_cannot_accept_an_invite_for_another mock_send_verify_code, mock_get_invited_user_by_id, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") sample_invite["email_address"] = "some_other_user@user.gsa.gov" mocker.patch( diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py index d70c5d0f8..5ec23edf3 100644 --- a/tests/app/main/views/test_sign_out.py +++ b/tests/app/main/views/test_sign_out.py @@ -86,12 +86,14 @@ MOCK_JOBS = { } -def test_render_sign_out_redirects_to_sign_in(client_request): +def test_render_sign_out_redirects_to_sign_in(client_request, mocker): # TODO with the change to using login.gov, we no longer redirect directly to the sign in page. # Instead we redirect to login.gov which redirects us to the sign in page. However, the # test for the expected redirect being "/" is buried in conftest and looks fragile. # After we move to login.gov officially and get rid of other forms of signing it, it should # be refactored. + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") with client_request.session_transaction() as session: assert session client_request.get( @@ -99,7 +101,7 @@ def test_render_sign_out_redirects_to_sign_in(client_request): _expected_status=302, ) with client_request.session_transaction() as session: - assert not session + assert session.permanent is False def test_sign_out_user( @@ -119,6 +121,8 @@ def test_sign_out_user( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") with client_request.session_transaction() as session: assert session.get("user_id") is not None # Check we are logged in @@ -141,13 +145,15 @@ def test_sign_out_user( assert session.get("user_id") is None -def test_sign_out_of_two_sessions(client_request): +def test_sign_out_of_two_sessions(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.get( "main.sign_out", _expected_status=302, ) with client_request.session_transaction() as session: - assert not session + assert session.permanent is False client_request.get( "main.sign_out", _expected_status=302, diff --git a/tests/app/main/views/test_two_factor.py b/tests/app/main/views/test_two_factor.py index 148aece98..d1f38d1ab 100644 --- a/tests/app/main/views/test_two_factor.py +++ b/tests/app/main/views/test_two_factor.py @@ -26,8 +26,10 @@ def mock_email_validated_recently(mocker): ("email_resent", "page_title"), [(None, "Check your email"), (True, "Email resent")] ) def test_two_factor_email_sent_page( - client_request, email_resent, page_title, redirect_url, request_url + client_request, email_resent, page_title, redirect_url, request_url, mocker ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() page = client_request.get( f"main.{request_url}", @@ -55,6 +57,8 @@ def test_two_factor_email_sent_page( def test_should_render_two_factor_page( client_request, api_user_active, mock_get_user_by_email, mocker, redirect_url ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() # TODO this lives here until we work out how to # reassign the session after it is lost mid register process @@ -86,7 +90,10 @@ def test_should_login_user_and_should_redirect_to_next_url( mock_check_verify_code, mock_create_event, mock_email_validated_recently, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: @@ -115,6 +122,8 @@ def test_should_send_email_and_redirect_to_info_page_if_user_needs_to_revalidate mock_send_verify_code, mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mocker.patch("app.user_api_client.get_user", return_value=api_user_active) @@ -149,7 +158,10 @@ def test_should_login_user_and_not_redirect_to_external_url( mock_get_services_with_one_service, mock_create_event, mock_email_validated_recently, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: @@ -182,7 +194,10 @@ def test_should_login_user_and_redirect_to_show_accounts( mock_create_event, mock_email_validated_recently, platform_admin, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: @@ -206,6 +221,8 @@ def test_should_return_200_with_sms_code_error_when_sms_code_is_wrong( mock_check_verify_code_code_not_found, mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: @@ -232,7 +249,10 @@ def test_should_login_user_when_multiple_valid_codes_exist( mock_get_services_with_one_service, mock_create_event, mock_email_validated_recently, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: @@ -257,7 +277,10 @@ def test_two_factor_sms_should_set_password_when_new_password_exists_in_session( mock_update_user_password, mock_create_event, mock_email_validated_recently, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: @@ -285,7 +308,10 @@ def test_two_factor_sms_returns_error_when_user_is_locked( mock_get_locked_user, mock_check_verify_code_code_not_found, mock_get_services_with_one_service, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with client_request.session_transaction() as session: @@ -320,6 +346,8 @@ def test_two_factor_sms_should_activate_pending_user( mock_activate_user, mock_email_validated_recently, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mocker.patch("app.user_api_client.get_user", return_value=api_user_pending) mocker.patch("app.service_api_client.get_services", return_value={"data": []}) @@ -344,6 +372,8 @@ def test_valid_two_factor_email_link_shows_interstitial( extra_args, expected_encoded_next_arg, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") mock_check_code = mocker.patch("app.user_api_client.check_verify_code") encoded_token = valid_token.replace("%2E", ".") token_url = url_for( @@ -400,7 +430,10 @@ def test_two_factor_email_link_has_expired( mock_send_verify_code, fake_uuid, redirect_url, + mocker, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() with set_config(notify_admin, "EMAIL_EXPIRY_SECONDS", -1): @@ -419,7 +452,9 @@ def test_two_factor_email_link_has_expired( assert mock_send_verify_code.called is False -def test_two_factor_email_link_is_invalid(client_request): +def test_two_factor_email_link_is_invalid(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() token = 12345 page = client_request.post( @@ -443,8 +478,14 @@ def test_two_factor_email_link_is_invalid(client_request): ], ) def test_two_factor_email_link_is_already_used( - client_request, valid_token, mocker, mock_send_verify_code, redirect_url + client_request, + valid_token, + mocker, + mock_send_verify_code, + redirect_url, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mocker.patch( "app.user_api_client.check_verify_code", @@ -467,8 +508,13 @@ def test_two_factor_email_link_is_already_used( def test_two_factor_email_link_when_user_is_locked_out( - client_request, valid_token, mocker, mock_send_verify_code + client_request, + valid_token, + mocker, + mock_send_verify_code, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() mocker.patch( "app.user_api_client.check_verify_code", return_value=(False, "Code not found") diff --git a/tests/app/main/views/test_verify.py b/tests/app/main/views/test_verify.py index 3c4a93725..4f2e39feb 100644 --- a/tests/app/main/views/test_verify.py +++ b/tests/app/main/views/test_verify.py @@ -204,6 +204,8 @@ def test_verify_email_redirects_to_sign_in_if_user_active( mock_send_verify_code, mock_check_verify_code, ): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() token_data = {"user_id": api_user_active["id"], "secret_code": 12345} mocker.patch( diff --git a/tests/app/notify_client/test_service_api_client.py b/tests/app/notify_client/test_service_api_client.py index 815f3e5e2..bab8bdc39 100644 --- a/tests/app/notify_client/test_service_api_client.py +++ b/tests/app/notify_client/test_service_api_client.py @@ -529,6 +529,9 @@ def test_deletes_caches_when_modifying_templates( def test_deletes_cached_users_when_archiving_service( mocker, mock_get_service_templates ): + mocker.patch( + "app.notify_client.service_api_client.ServiceAPIClient.check_inactive_user" + ) mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") mock_redis_delete_by_pattern = mocker.patch( "app.extensions.RedisClient.delete_by_pattern" diff --git a/tests/app/utils/test_user.py b/tests/app/utils/test_user.py index 20d4b1d71..9d35aa507 100644 --- a/tests/app/utils/test_user.py +++ b/tests/app/utils/test_user.py @@ -94,7 +94,9 @@ def test_restrict_admin_usage( index() -def test_no_user_returns_redirect_to_sign_in(client_request): +def test_no_user_returns_redirect_to_sign_in(client_request, mocker): + + mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user") client_request.logout() @user_has_permissions()