From 362835874f8fc5dee981d310abd139776948d61d Mon Sep 17 00:00:00 2001 From: Ryan Ahearn Date: Mon, 17 Apr 2023 15:17:43 -0400 Subject: [PATCH 1/6] Add reset state script for development terraform --- terraform/development/providers.tf | 2 +- terraform/development/reset.sh | 59 ++++++++++++++++++++++++++++++ terraform/development/run.sh | 2 +- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100755 terraform/development/reset.sh diff --git a/terraform/development/providers.tf b/terraform/development/providers.tf index 59bb98a70..d8ae4488e 100644 --- a/terraform/development/providers.tf +++ b/terraform/development/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { cloudfoundry = { source = "cloudfoundry-community/cloudfoundry" - version = "0.50.5" + version = "0.50.7" } } } diff --git a/terraform/development/reset.sh b/terraform/development/reset.sh new file mode 100755 index 000000000..7e38fd1b6 --- /dev/null +++ b/terraform/development/reset.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +username=`whoami` +org="gsa-tts-benefits-studio-prototyping" + +usage=" +$0: Reset terraform state so run.sh can be run again or for a new username + +Usage: + $0 -h + $0 [-u ] + +Options: +-h: show help and exit +-u : your username. Default: $username + +Notes: +* Requires cf-cli@8 +" + +while getopts ":hu:" opt; do + case "$opt" in + u) + username=${OPTARG} + ;; + h) + echo "$usage" + exit 0 + ;; + esac +done + +read -p "Are you sure you want to import terraform state and remove existing service keys for $username (y/n)? " verify + +if [[ $verify != "y" ]]; then + exit 0 +fi + +# ensure we're in the correct directory +cd $(dirname $0) + +service_account="$username-terraform" + +if [[ ! -s "secrets.auto.tfvars" ]]; then + # create user in notify-local-dev space to create s3 buckets + ../create_service_account.sh -s notify-local-dev -u $service_account > secrets.auto.tfvars +fi + +echo "Importing terraform state for $username" +terraform init + +key_name=$username-admin-dev-key + +cf t -s notify-local-dev +terraform import -var "username=$username" module.logo_upload_bucket.cloudfoundry_service_instance.bucket $(cf service --guid $username-csv-upload-bucket) +cf delete-service-key -f $username-logo-upload-bucket $key_name +cf delete-service-key -f $username-csv-upload-bucket $key_name + +./run.sh -u $username diff --git a/terraform/development/run.sh b/terraform/development/run.sh index 5e4309e42..fdc8c928f 100755 --- a/terraform/development/run.sh +++ b/terraform/development/run.sh @@ -8,7 +8,7 @@ $0: Create development infrastructure Usage: $0 -h - $0 [-u ] [-k] + $0 [-u ] [-k] [-d] Options: -h: show help and exit From ab4d3e356f7ad51e01dd6b6ac5767a0c75d2bcf0 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Tue, 25 Apr 2023 09:54:25 -0600 Subject: [PATCH 2/6] Tests pinning ubuntu version in Github CI. --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c5144cb72..27c611579 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -85,7 +85,7 @@ jobs: cmd_options: "-I" a11y-scan: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - uses: ./.github/actions/setup-project From 2d1d8e26c8508a90ea7fb564caa4da7a8378314c Mon Sep 17 00:00:00 2001 From: Ryan Ahearn Date: Tue, 25 Apr 2023 19:36:18 -0400 Subject: [PATCH 3/6] Create events logging for creating and removing user invites --- app/config.py | 1 + app/event_handlers.py | 10 ++++++++++ app/main/views/manage_users.py | 13 +++++++++++++ 3 files changed, 24 insertions(+) diff --git a/app/config.py b/app/config.py index 2779364df..3487c370c 100644 --- a/app/config.py +++ b/app/config.py @@ -91,6 +91,7 @@ class Development(Config): HTTP_PROTOCOL = 'http' ASSET_DOMAIN = '' ASSET_PATH = '/static/' + NOTIFY_LOG_LEVEL = 'DEBUG' # Buckets CSV_UPLOAD_BUCKET = _s3_credentials_from_env('CSV') diff --git a/app/event_handlers.py b/app/event_handlers.py index 0cdf76088..4884ab6c8 100644 --- a/app/event_handlers.py +++ b/app/event_handlers.py @@ -8,6 +8,8 @@ EVENT_SCHEMAS = { "update_user_mobile_number": {"user_id", "updated_by_id", "original_mobile_number", "new_mobile_number"}, "remove_user_from_service": {"user_id", "removed_by_id", "service_id"}, "add_user_to_service": {"user_id", "invited_by_id", "service_id", "ui_permissions"}, + "invite_user_to_service": {"email_address", "invited_by_id", "service_id", "ui_permissions"}, + "cancel_user_invite_to_service": {"email_address", "canceled_by_id", "service_id"}, "set_user_permissions": {"user_id", "service_id", "original_ui_permissions", "new_ui_permissions", "set_by_id"}, "archive_user": {"user_id", "archived_by_id"}, "archive_service": {"service_id", "archived_by_id"}, @@ -32,6 +34,14 @@ def create_remove_user_from_service_event(**kwargs): _send_event('remove_user_from_service', **kwargs) +def create_invite_user_to_service_event(**kwargs): + _send_event('invite_user_to_service', **kwargs) + + +def create_cancel_user_invite_to_service_event(**kwargs): + _send_event('cancel_user_invite_to_service', **kwargs) + + def create_add_user_to_service_event(**kwargs): _send_event('add_user_to_service', **kwargs) diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index 83971dd8b..52cb5c7d8 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -12,7 +12,9 @@ from notifications_python_client.errors import HTTPError from app import current_service, service_api_client from app.event_handlers import ( + create_cancel_user_invite_to_service_event, create_email_change_event, + create_invite_user_to_service_event, create_mobile_number_change_event, create_remove_user_from_service_event, ) @@ -91,6 +93,12 @@ def invite_user(service_id, user_id=None): form.login_authentication.data, form.folder_permissions.data, ) + create_invite_user_to_service_event( + email_address=email_address, + invited_by_id=current_user.id, + service_id=service_id, + ui_permissions=form.permissions, + ) flash('Invite sent to {}'.format(invited_user.email_address), 'default_with_tick') return redirect(url_for('.manage_users', service_id=service_id)) @@ -314,6 +322,11 @@ def cancel_invited_user(service_id, invited_user_id): current_service.cancel_invite(invited_user_id) invited_user = InvitedUser.by_id_and_service_id(service_id, invited_user_id) + create_cancel_user_invite_to_service_event( + email_address=invited_user.email_address, + canceled_by_id=current_user.id, + service_id=service_id, + ) flash(f'Invitation cancelled for {invited_user.email_address}', 'default_with_tick') return redirect(url_for('main.manage_users', service_id=service_id)) From 99c94851c03b2f4d9a4a0657fc85124a82d51c36 Mon Sep 17 00:00:00 2001 From: Ryan Ahearn Date: Tue, 25 Apr 2023 19:36:38 -0400 Subject: [PATCH 4/6] Create events logging for updating email and phone number for yourself --- app/main/views/user_profile.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/main/views/user_profile.py b/app/main/views/user_profile.py index 42c538024..09b3e2843 100644 --- a/app/main/views/user_profile.py +++ b/app/main/views/user_profile.py @@ -15,6 +15,7 @@ from notifications_python_client.errors import HTTPError from notifications_utils.url_safe_token import check_token from app import user_api_client +from app.event_handlers import create_email_change_event, create_mobile_number_change_event from app.main import main from app.main.forms import ( ChangeEmailForm, @@ -91,6 +92,12 @@ def user_profile_email_authenticate(): if form.validate_on_submit(): user_api_client.send_change_email_verification(current_user.id, session[NEW_EMAIL]) + create_email_change_event( + user_id=current_user.id, + updated_by_id=current_user.id, + original_email_address=current_user.email, + new_email_address=session[NEW_EMAIL], + ) return render_template('views/change-email-continue.html', new_email=session[NEW_EMAIL]) @@ -170,6 +177,12 @@ def user_profile_mobile_number_authenticate(): if form.validate_on_submit(): session[NEW_MOBILE_PASSWORD_CONFIRMED] = True current_user.send_verify_code(to=session[NEW_MOBILE]) + create_mobile_number_change_event( + user_id=current_user.id, + updated_by_id=current_user.id, + original_mobile_number=current_user.mobile_number, + new_mobile_number=session[NEW_MOBILE] + ) return redirect(url_for('.user_profile_mobile_number_confirm')) return render_template( From a42617f776038dc10aa16bd36cb1d54479d8b907 Mon Sep 17 00:00:00 2001 From: Ryan Ahearn Date: Wed, 26 Apr 2023 11:14:25 -0400 Subject: [PATCH 5/6] Add logging around auth{entication,orization} calls --- app/main/views/user_profile.py | 7 +++++-- app/models/user.py | 29 +++++++++++++++++++++++----- app/notify_client/user_api_client.py | 13 +++++++++++-- tests/app/models/test_user.py | 2 +- tests/conftest.py | 2 +- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/app/main/views/user_profile.py b/app/main/views/user_profile.py index 09b3e2843..d461b72bc 100644 --- a/app/main/views/user_profile.py +++ b/app/main/views/user_profile.py @@ -15,7 +15,10 @@ from notifications_python_client.errors import HTTPError from notifications_utils.url_safe_token import check_token from app import user_api_client -from app.event_handlers import create_email_change_event, create_mobile_number_change_event +from app.event_handlers import ( + create_email_change_event, + create_mobile_number_change_event, +) from app.main import main from app.main.forms import ( ChangeEmailForm, @@ -95,7 +98,7 @@ def user_profile_email_authenticate(): create_email_change_event( user_id=current_user.id, updated_by_id=current_user.id, - original_email_address=current_user.email, + original_email_address=current_user.email_address, new_email_address=session[NEW_EMAIL], ) return render_template('views/change-email-continue.html', diff --git a/app/models/user.py b/app/models/user.py index 31ca63290..b99e30a2e 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,6 +1,6 @@ from datetime import datetime -from flask import abort, request, session +from flask import abort, current_app, request, session from flask_login import AnonymousUserMixin, UserMixin, login_user, logout_user from notifications_python_client.errors import HTTPError from notifications_utils.timezones import convert_utc_to_local_timezone @@ -175,6 +175,7 @@ class User(JSONModel, UserMixin): # Update the db so the server also knows the user is logged out. self.update(current_session_id=None) logout_user() + current_app.logger.info(f"Logged out {self.id}") @property def sms_auth(self): @@ -201,7 +202,9 @@ class User(JSONModel, UserMixin): @property def is_gov_user(self): - return is_gov_user(self.email_address) + is_gov = is_gov_user(self.email_address) + current_app.logger.info(f"User {self.id} is_gov_user: {is_gov}") + return is_gov @property def is_authenticated(self): @@ -212,6 +215,7 @@ class User(JSONModel, UserMixin): @property def platform_admin(self): + current_app.logger.warn(f"Checking User {self.id} for platform admin: {self._platform_admin}") return self._platform_admin and not session.get('disable_platform_admin_view', False) def has_permissions(self, *permissions, restrict_admin_usage=False, allow_org_user=False): @@ -228,32 +232,45 @@ class User(JSONModel, UserMixin): # use @user_is_platform_admin for platform admin only pages raise NotImplementedError + log_msg = f"has_permissions user: {self.id} service: {service_id}" # platform admins should be able to do most things (except eg send messages, or create api keys) if self.platform_admin and not restrict_admin_usage: + current_app.logger.warn(f"{log_msg} true because user is platform_admin") return True if org_id: - return self.belongs_to_organisation(org_id) + value = self.belongs_to_organisation(org_id) + current_app.logger.warn(f"{log_msg} org: {org_id} returning {value}") + return value if not permissions and self.belongs_to_service(service_id): + current_app.logger.warn(f"{log_msg} True because belongs_to_service") return True if any( self.permissions_for_service(service_id) & set(permissions) ): + current_app.logger.warn(f"{log_msg} permissions valid") return True from app.models.service import Service - return allow_org_user and self.belongs_to_organisation( + org_value = allow_org_user and self.belongs_to_organisation( Service.from_id(service_id).organisation_id ) + current_app.logger.warn(f"{log_msg} returning {org_value}") + return org_value def permissions_for_service(self, service_id): return self._permissions.get(service_id, set()) def has_permission_for_service(self, service_id, permission): - return permission in self._permissions.get(service_id, []) + has_permission = permission in self.permissions_for_service(service_id) + current_app.logger.warn( + f"has_permission_for_service user: {self.id} service: {service_id} " + f"permission: {permission} retuning {has_permission}" + ) + return has_permission def has_template_folder_permission(self, template_folder, service=None): if self.platform_admin: @@ -547,11 +564,13 @@ class InvitedUser(JSONModel): return cls.by_id(invited_user_id) if invited_user_id else None def has_permissions(self, *permissions): + current_app.logger.warn(f"Checking invited user {self.id} for permissions: {permissions}") if self.status == 'cancelled': return False return set(self.permissions) > set(permissions) def has_permission_for_service(self, service_id, permission): + current_app.logger.warn(f"Checking invited user {self.id} for permission: {permission} on service {service_id}") if self.status == 'cancelled': return False return self.service == service_id and permission in self.permissions diff --git a/app/notify_client/user_api_client.py b/app/notify_client/user_api_client.py index caed3ff97..94a437d0f 100644 --- a/app/notify_client/user_api_client.py +++ b/app/notify_client/user_api_client.py @@ -1,3 +1,4 @@ +from flask import current_app from notifications_python_client.errors import HTTPError from app.notify_client import NotifyAdminAPIClient, cache @@ -83,13 +84,16 @@ class UserApiClient(NotifyAdminAPIClient): @cache.delete('user-{user_id}') def verify_password(self, user_id, password): try: + current_app.logger.warn(f"Checking password for {user_id}") url = "/user/{}/verify/password".format(user_id) data = {"password": password} self.post(url, data=data) return True except HTTPError as e: if e.status_code == 400 or e.status_code == 404: + current_app.logger.error(f"Password for {user_id} was invalid") return False + raise def send_verify_code(self, user_id, code_type, to, next_string=None): data = {'to': to} @@ -98,6 +102,7 @@ class UserApiClient(NotifyAdminAPIClient): if code_type == 'email': data['email_auth_link_host'] = self.admin_url endpoint = '/user/{0}/{1}-code'.format(user_id, code_type) + current_app.logger.warn(f"Sending verify_code {code_type} to {user_id}") self.post(endpoint, data=data) def send_verify_email(self, user_id, to): @@ -118,24 +123,28 @@ class UserApiClient(NotifyAdminAPIClient): data = {'code_type': code_type, 'code': code} endpoint = '/user/{}/verify/code'.format(user_id) try: + current_app.logger.warn(f"Checking verify code for {user_id}") self.post(endpoint, data=data) return True, '' except HTTPError as e: if e.status_code == 400 or e.status_code == 404: + current_app.logger.error(f"Verify code for {user_id} was invalid") return False, e.message - raise e + raise @cache.delete('user-{user_id}') def complete_webauthn_login_attempt(self, user_id, is_successful): data = {'successful': is_successful} endpoint = f'/user/{user_id}/complete/webauthn-login' try: + current_app.logger.warn(f"Sending webauthn-login for {user_id}") self.post(endpoint, data=data) return True, '' except HTTPError as e: if e.status_code == 403: + current_app.logger.error(f"Webauthn-login attempt for {user_id} was invalid") return False, e.message - raise e + raise def get_users_for_service(self, service_id): endpoint = '/service/{}/users'.format(service_id) diff --git a/tests/app/models/test_user.py b/tests/app/models/test_user.py index 523220579..e778b2f19 100644 --- a/tests/app/models/test_user.py +++ b/tests/app/models/test_user.py @@ -71,7 +71,7 @@ def test_platform_admin_flag_set_in_session( mocker.patch.dict('app.models.user.session', values=session_dict, clear=True) - assert User({'platform_admin': is_platform_admin}).platform_admin == expected_result + assert User({'id': 1, 'platform_admin': is_platform_admin}).platform_admin == expected_result def test_has_live_services( diff --git a/tests/conftest.py b/tests/conftest.py index c8fd1461a..361200905 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1000,7 +1000,7 @@ def active_user_no_settings_permission(): def api_user_locked(fake_uuid): return create_user( id=fake_uuid, - failed_login_count=5, + failed_login_count=10, password_changed_at=None, ) From 4fc95a4e28ce1ffc873eb47ba7e28fd3e65c9a46 Mon Sep 17 00:00:00 2001 From: Ryan Ahearn Date: Wed, 26 Apr 2023 11:24:01 -0400 Subject: [PATCH 6/6] Update utils for improved logging --- Pipfile | 24 ++-- Pipfile.lock | 320 +++++++++++++++++++++++----------------------- app/main/forms.py | 18 +-- 3 files changed, 178 insertions(+), 184 deletions(-) diff --git a/Pipfile b/Pipfile index 8d4518606..dff2cbe7c 100644 --- a/Pipfile +++ b/Pipfile @@ -4,20 +4,20 @@ verify_ssl = true name = "pypi" [packages] -ago = "==0.0.93" -blinker = "==1.4" +ago = "~=0.0.95" +blinker = "~=1.4" fido2 = "==0.9.3" -flask = "~=2.2" -flask-basicauth = "==0.2.0" +flask = "~=2.2.3" +flask-basicauth = "~=0.2" flask-login = "~=0.6" -flask-wtf = "==1.0.1" +flask-wtf = "~=1.1" gds-metrics = {version = "==0.2.4", ref = "6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72", git = "https://github.com/alphagov/gds_metrics_python.git"} govuk-bank-holidays = "==0.11" govuk-frontend-jinja = {version = "==0.5.8-alpha", git = "https://github.com/alphagov/govuk-frontend-jinja.git"} gunicorn = {version = "==20.1.0", extras = ["eventlet"], ref = "1299ea9e967a61ae2edebe191082fd169b864c64", git = "https://github.com/benoitc/gunicorn.git"} -humanize = "==4.1.0" -itsdangerous = "==2.1.2" -jinja2 = "==3.1.2" +humanize = "~=4.1" +itsdangerous = "~=2.1" +jinja2 = "~=3.1" notifications-python-client = "==6.3.0" prometheus-client = "==0.14.1" pyexcel = "==0.7.0" @@ -30,11 +30,11 @@ pyproj = "==3.3.1" python-dotenv = "==0.20.0" pytz = "==2022.1" rtreelib = "==0.2.0" -werkzeug = "~=2.2" -wtforms = "==3.0.1" +werkzeug = "~=2.2.3" +wtforms = "~=3.0" newrelic = "*" -notifications-utils = {editable = true, ref = "main", git = "https://github.com/GSA/notifications-utils.git"} flask-talisman = "*" +notifications-utils = {editable = true, ref = "main", git = "https://github.com/GSA/notifications-utils.git"} [dev-packages] isort = "==5.10.1" @@ -48,7 +48,7 @@ flake8 = "==4.0.1" flake8-bugbear = "==22.4.25" flake8-print = "==5.0.0" moto = "==3.1.7" -requests-mock = "==1.9.3" +requests-mock = "==1.10.0" # used for creating manifest file locally jinja2-cli = {version = "==0.8.2", extras = ["yaml"]} pip-audit = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 05870a0cb..55e244e95 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d1e97eea3a07575f72e3fc44938e44799021c5b50b75522a9a378fd8a188b02a" + "sha256": "da9318b2c1d82b4c1e26e81d4db361481880bb38108a0ec492cef762d2bb66d2" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "ago": { "hashes": [ - "sha256:6f060596a90c6ec33c2e806bcc3146e7e7bfda7e90ca5c11b579c5bf03baf6fa", - "sha256:9d1956edd8103c266d968ae2a7eaf2f23470b6384e655aaaf54d1158408178ad" + "sha256:d2010f5eac3df544ec48ec116102e068591a345b1a580f32973db8a505fca744", + "sha256:f95171f81060b712f2ca05111fc4b38ef958a5cd1ad3c06b633f9b82de92eeaf" ], "index": "pypi", - "version": "==0.0.93" + "version": "==0.0.95" }, "async-timeout": { "hashes": [ @@ -42,26 +42,27 @@ }, "blinker": { "hashes": [ - "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" + "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213", + "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0" ], "index": "pypi", - "version": "==1.4" + "version": "==1.6.2" }, "boto3": { "hashes": [ - "sha256:043f8981d10c4e7c48736df4381dac557b46c5b369b0a450d8f3d7f5fdd24db5", - "sha256:b00f416832bc59863b96175045d2ebe067d9222289bce677c48fd72c006eaaad" + "sha256:827342a6b94b18daed937b0743f43958d1d3b9d5423ec995c19ae74b82cd54bc", + "sha256:f87d694c351eba1dfd19b5bef5892a1047e7adb09c57c2c00049de209a8ab55d" ], "markers": "python_version >= '3.7'", - "version": "==1.26.102" + "version": "==1.26.121" }, "botocore": { "hashes": [ - "sha256:4bae8f502507da18ff37c61cb18745cfb11d87a61dd0ea27e346adadff92aa3f", - "sha256:58b11c630d2044ea732ba4c403d29fab51e954465f9b3f7099cbf5ac0ce7ab47" + "sha256:4030b2cde8c8c7b58c657c27e5e4ec0edb7a0cab84e31b0fe752dddee2735d68", + "sha256:955c1dd244b6286d9e17dc525d1459a2a74a1c4e519f35006c72f184fbce0760" ], "markers": "python_version >= '3.7'", - "version": "==1.29.102" + "version": "==1.29.121" }, "cachetools": { "hashes": [ @@ -247,28 +248,28 @@ }, "cryptography": { "hashes": [ - "sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405", - "sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356", - "sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472", - "sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41", - "sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a", - "sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88", - "sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554", - "sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db", - "sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778", - "sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c", - "sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917", - "sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797", - "sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160", - "sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c", - "sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c", - "sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2", - "sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4", - "sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122", - "sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94" + "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440", + "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288", + "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b", + "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958", + "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b", + "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d", + "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a", + "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404", + "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b", + "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e", + "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2", + "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c", + "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b", + "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9", + "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b", + "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636", + "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99", + "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e", + "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9" ], "markers": "python_version >= '3.6'", - "version": "==40.0.1" + "version": "==40.0.2" }, "dnspython": { "hashes": [ @@ -308,11 +309,11 @@ }, "flask": { "hashes": [ - "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d", - "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d" + "sha256:13f6329ddbfff11340939cd11919daf150a01358ded4b7e81c03c055dfecb559", + "sha256:77504c4c097f56ac5f29b00f9009213010cf9d2923a288c0e0564a5db2bb53d6" ], "index": "pypi", - "version": "==2.2.3" + "version": "==2.2.4" }, "flask-basicauth": { "hashes": [ @@ -347,11 +348,11 @@ }, "flask-wtf": { "hashes": [ - "sha256:34fe5c6fee0f69b50e30f81a3b7ea16aa1492a771fe9ad0974d164610c09a6c9", - "sha256:9d733658c80be551ce7d5bc13c7a7ac0d80df509be1e23827c847d9520f4359a" + "sha256:41c4244e9ae626d63bed42ae4785b90667b885b1535d5a4095e1f63060d12aa9", + "sha256:7887d6f1ebb3e17bf648647422f0944c9a469d0fcf63e3b66fb9a83037e38b2c" ], "index": "pypi", - "version": "==1.0.1" + "version": "==1.1.1" }, "gds-metrics": { "git": "https://github.com/alphagov/gds_metrics_python.git", @@ -455,11 +456,11 @@ }, "humanize": { "hashes": [ - "sha256:3a119b242ec872c029d8b7bf8435a61a5798f124b244a08013aec5617302f80e", - "sha256:953b393f5bd67e19d47a4c0fd20c3a3537853967b307e49729c4755d3551753c" + "sha256:401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50", + "sha256:5f1f22bc65911eb1a6ffe7659bd6598e33dcfeeb904eb16ee1e705a09bf75916" ], "index": "pypi", - "version": "==4.1.0" + "version": "==4.6.0" }, "idna": { "hashes": [ @@ -471,11 +472,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20", - "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09" + "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed", + "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705" ], "markers": "python_version < '3.10'", - "version": "==6.1.0" + "version": "==6.6.0" }, "itsdangerous": { "hashes": [ @@ -656,24 +657,24 @@ }, "newrelic": { "hashes": [ - "sha256:15d3088d9ab4d708e7b3826e651c2402f2cb9c72689e47a0badc8281ab20bfe9", - "sha256:2249a25b1ce967267604cb0ce3268256fb25da481312f3c7b04df90245708131", - "sha256:2fc9807a1e3277e1dbddb7cd84e00b1f70faea602f0bbe53109e0e68b0c20e3c", - "sha256:432a6fa9c0051154f4110f4203831f464c8ba5bc842e709639391175d4ba50e6", - "sha256:4f48e481ebb7d873fd16a7fe0df30383c834e75daa6b0b514e147b8d683d922d", - "sha256:69a7ed5788fb6347e96f7df18a641ea242d5dac7ccb76bbaa869851cade335c4", - "sha256:6fc4169f66e80f4b497d16be3759feaf5be08ec389b8f42b8b62ce632de9eb3f", - "sha256:95484f2ca36952831b47e3b054e808317a0a12597ca9ef5166789a425545de44", - "sha256:b5c53a5922c92d742eff37da87c113a18762cd06e310bfc430df02603298def9", - "sha256:d02acde140c6d4f549f36a2bda64025a18efc5b6457c8fa505146c7bcaaac23b", - "sha256:d39f6f3ffc458337e22ef4d4e7bacf5b0b2712feb7668538299d029bc87f9b7a", - "sha256:d7b510f3889fe57330bb85de78abcac2d0711b49e9da74fcd1b936a57b139238", - "sha256:eb3a27fd7b9d51941fb20c452aa3b3b2dd52fe652cda2d5d269dcc14f64ade6e", - "sha256:ec8d38d9f7c30c464cddd594cb390cf66a2b573d08e09ac231d3d349a26e0f96", - "sha256:f79a599b53894870dfdfcd88fa7ca7e81cac77f4a253ca0c08c58f400bb0a5ab" + "sha256:1bc307d06e2033637e7b484af22f540ca041fb23a54b311bcd5968ca1a64e4ef", + "sha256:435ac9e3791f78e05c9da8107a6ef49c13e62ac302696858fa2411198fe201ff", + "sha256:6662ec79493f23f9d0995a015177c87508bea4c541f7c9f17a61b503b82e1367", + "sha256:67902b3c53fa497dba887068166261d114ac2347c8a4908d735d7594cca163dc", + "sha256:6b4db0e7544232d4e6e835a02ee28637970576f8dce82ffcaa3d675246e822d5", + "sha256:796ed5ff44b04b41e051dc0112e5016e53a37e39e95023c45ff7ecd34c254a7d", + "sha256:84d1f71284efa5f1cae696161e0c3cb65eaa2f53116fe5e7c5a62be7d15d9536", + "sha256:9355f209ba8d82fd0f9d78d7cc1d9bef0ae4677b3cfed7b7aaec521adbe87559", + "sha256:9c0d5153b7363d5cb5cac7f8d1a4e03669b074afee2dda201851a67c7bed1e32", + "sha256:bcd3219e1e816a0fdb51ac993cac6744e6a835c13ee72e21d86bcbc2d16628ce", + "sha256:c4a0556c6ece49132ab1c32bfe398047a8311f9a8b6862b482495d132fcb0ad4", + "sha256:caccdf201735df80b470ddf772f60a154f2c07c0c1b2b3f6e999d55e79ce601e", + "sha256:d21af16cee1e0caf4c73c4c1b2d7ba9f33fe6a870d93135dc8b23ac592f49b38", + "sha256:da8f2dc31e182768fe314d8ceb6f42acd09956708846f8ae71f07f044a3aa05e", + "sha256:ef9c178329f8c04f0574908c1f04ff1f18b9eba55b869744583fee3eac48e571" ], "index": "pypi", - "version": "==8.7.1" + "version": "==8.8.0" }, "notifications-python-client": { "hashes": [ @@ -685,41 +686,41 @@ "notifications-utils": { "editable": true, "git": "https://github.com/GSA/notifications-utils.git", - "ref": "44127eac47d0825d083e51d5a2580a520ea2ee49" + "ref": "c7a85c4d1e8f975d349c1555f416ae20f28a2ce1" }, "numpy": { "hashes": [ - "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22", - "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f", - "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9", - "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96", - "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0", - "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a", - "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281", - "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04", - "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468", - "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253", - "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756", - "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a", - "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb", - "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d", - "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0", - "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910", - "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978", - "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5", - "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f", - "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a", - "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5", - "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2", - "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d", - "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95", - "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5", - "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d", - "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780", - "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa" + "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187", + "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812", + "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7", + "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4", + "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6", + "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0", + "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4", + "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570", + "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4", + "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f", + "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80", + "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289", + "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385", + "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078", + "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c", + "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463", + "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3", + "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950", + "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155", + "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7", + "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c", + "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096", + "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17", + "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf", + "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4", + "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02", + "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c", + "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b" ], "markers": "python_version >= '3.8'", - "version": "==1.24.2" + "version": "==1.24.3" }, "openpyxl": { "hashes": [ @@ -737,10 +738,10 @@ }, "phonenumbers": { "hashes": [ - "sha256:1b7c75c4a5e62885a0be0a6b073b7e3f4ce6672fd42ab7b4a6b46359c40591d0", - "sha256:4ba92a917fb8f6e3fe33fd04b9d84299852ce237289129ee4eda47a043a5c6c4" + "sha256:421b69fd6d6650372000a6c47ab5b5c5d7b438b33f7b317739e728eff1ec1886", + "sha256:fe071b8324473e72a54b52e602d059c15b999ec9900fff9e42c01b422aeca662" ], - "version": "==8.13.8" + "version": "==8.13.10" }, "prometheus-client": { "hashes": [ @@ -939,11 +940,11 @@ }, "requests": { "hashes": [ - "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", - "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b", + "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059" ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==2.28.2" + "markers": "python_version >= '3.7'", + "version": "==2.29.0" }, "rtreelib": { "hashes": [ @@ -963,11 +964,11 @@ }, "setuptools": { "hashes": [ - "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a", - "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078" + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" ], "markers": "python_version >= '3.7'", - "version": "==67.6.1" + "version": "==67.7.2" }, "shapely": { "hashes": [ @@ -1027,13 +1028,6 @@ ], "version": "==2.0.1" }, - "statsd": { - "hashes": [ - "sha256:99763da81bfea8daf6b3d22d11aaccb01a8d0f52ea521daab37e758a4ca7d128", - "sha256:c2676519927f7afade3723aca9ca8ea986ef5b059556a980a867721ca69df093" - ], - "version": "==4.0.1" - }, "texttable": { "hashes": [ "sha256:290348fb67f7746931bcdfd55ac7584ecd4e5b0846ab164333f0794b121760f2", @@ -1107,11 +1101,11 @@ "develop": { "attrs": { "hashes": [ - "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", - "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" ], - "markers": "python_version >= '3.6'", - "version": "==22.2.0" + "markers": "python_version >= '3.7'", + "version": "==23.1.0" }, "bandit": { "hashes": [ @@ -1131,19 +1125,19 @@ }, "boto3": { "hashes": [ - "sha256:043f8981d10c4e7c48736df4381dac557b46c5b369b0a450d8f3d7f5fdd24db5", - "sha256:b00f416832bc59863b96175045d2ebe067d9222289bce677c48fd72c006eaaad" + "sha256:827342a6b94b18daed937b0743f43958d1d3b9d5423ec995c19ae74b82cd54bc", + "sha256:f87d694c351eba1dfd19b5bef5892a1047e7adb09c57c2c00049de209a8ab55d" ], "markers": "python_version >= '3.7'", - "version": "==1.26.102" + "version": "==1.26.121" }, "botocore": { "hashes": [ - "sha256:4bae8f502507da18ff37c61cb18745cfb11d87a61dd0ea27e346adadff92aa3f", - "sha256:58b11c630d2044ea732ba4c403d29fab51e954465f9b3f7099cbf5ac0ce7ab47" + "sha256:4030b2cde8c8c7b58c657c27e5e4ec0edb7a0cab84e31b0fe752dddee2735d68", + "sha256:955c1dd244b6286d9e17dc525d1459a2a74a1c4e519f35006c72f184fbce0760" ], "markers": "python_version >= '3.7'", - "version": "==1.29.102" + "version": "==1.29.121" }, "cachecontrol": { "extras": [ @@ -1316,28 +1310,28 @@ }, "cryptography": { "hashes": [ - "sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405", - "sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356", - "sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472", - "sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41", - "sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a", - "sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88", - "sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554", - "sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db", - "sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778", - "sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c", - "sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917", - "sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797", - "sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160", - "sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c", - "sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c", - "sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2", - "sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4", - "sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122", - "sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94" + "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440", + "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288", + "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b", + "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958", + "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b", + "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d", + "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a", + "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404", + "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b", + "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e", + "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2", + "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c", + "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b", + "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9", + "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b", + "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636", + "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99", + "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e", + "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9" ], "markers": "python_version >= '3.6'", - "version": "==40.0.1" + "version": "==40.0.2" }, "cyclonedx-python-lib": { "hashes": [ @@ -1634,11 +1628,11 @@ }, "packaging": { "hashes": [ - "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", - "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], "markers": "python_version >= '3.7'", - "version": "==23.0" + "version": "==23.1" }, "pbr": { "hashes": [ @@ -1650,11 +1644,11 @@ }, "pip": { "hashes": [ - "sha256:236bcb61156d76c4b8a05821b988c7b8c35bf0da28a4b614e8d6ab5212c25c6f", - "sha256:cd015ea1bfb0fcef59d8a286c1f8bebcb983f6317719d415dc5351efb7cd7024" + "sha256:0e7c86f486935893c708287b30bd050a36ac827ec7fe5e43fe7cb198dd835fba", + "sha256:3ef6ac33239e4027d9a5598a381b9d30880a1477e50039db2eac6e8a8f6d1b18" ], "markers": "python_version >= '3.7'", - "version": "==23.0.1" + "version": "==23.1.2" }, "pip-api": { "hashes": [ @@ -1721,11 +1715,11 @@ }, "pygments": { "hashes": [ - "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", - "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717" + "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", + "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" ], - "markers": "python_version >= '3.6'", - "version": "==2.14.0" + "markers": "python_version >= '3.7'", + "version": "==2.15.1" }, "pyparsing": { "hashes": [ @@ -1838,19 +1832,19 @@ }, "requests": { "hashes": [ - "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", - "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b", + "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059" ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==2.28.2" + "markers": "python_version >= '3.7'", + "version": "==2.29.0" }, "requests-mock": { "hashes": [ - "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970", - "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba" + "sha256:2fdbb637ad17ee15c06f33d31169e71bf9fe2bdb7bc9da26185be0dd8d842699", + "sha256:59c9c32419a9fb1ae83ec242d98e889c45bd7d7a65d48375cc243ec08441658b" ], "index": "pypi", - "version": "==1.9.3" + "version": "==1.10.0" }, "responses": { "hashes": [ @@ -1862,11 +1856,11 @@ }, "rich": { "hashes": [ - "sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333", - "sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15" + "sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a", + "sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.3.3" + "version": "==13.3.4" }, "s3transfer": { "hashes": [ @@ -1878,11 +1872,11 @@ }, "setuptools": { "hashes": [ - "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a", - "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078" + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" ], "markers": "python_version >= '3.7'", - "version": "==67.6.1" + "version": "==67.7.2" }, "six": { "hashes": [ @@ -1909,11 +1903,11 @@ }, "soupsieve": { "hashes": [ - "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955", - "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a" + "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8", + "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea" ], "markers": "python_version >= '3.7'", - "version": "==2.4" + "version": "==2.4.1" }, "stevedore": { "hashes": [ diff --git a/app/main/forms.py b/app/main/forms.py index fed38965b..b50b9a75b 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -1103,7 +1103,7 @@ class TwoFactorForm(StripWhitespaceForm): sms_code = SMSCode('Text message code') - def validate(self): + def validate(self, extra_validators=None): if not self.sms_code.validate(self): return False @@ -1478,8 +1478,8 @@ class AdminProviderRatioForm(Form): for provider in providers }) - def validate(self): - if not super().validate(): + def validate(self, extra_validators=None): + if not super().validate(extra_validators): return False total = sum( @@ -1513,7 +1513,7 @@ class ServiceContactDetailsForm(StripWhitespaceForm): # This is a text field because the number provided by the user can also be a short code phone_number = GovukTextInputField("Phone number") - def validate(self): + def validate(self, extra_validators=None): if self.contact_details_type.data == 'url': self.url.validators = [DataRequired(), URL(message='Must be a valid URL')] @@ -1530,7 +1530,7 @@ class ServiceContactDetailsForm(StripWhitespaceForm): raise ValidationError('Must be a valid phone number') self.phone_number.validators = [DataRequired(), Length(min=5, max=20), valid_phone_number] - return super().validate() + return super().validate(extra_validators) class ServiceReplyToEmailForm(StripWhitespaceForm): @@ -1802,8 +1802,8 @@ class CallbackForm(StripWhitespaceForm): Length(min=10, message='Must be at least 10 characters')] ) - def validate(self): - return super().validate() or self.url.data == '' + def validate(self, extra_validators=None): + return super().validate(extra_validators) or self.url.data == '' class SMSPrefixForm(StripWhitespaceForm): @@ -2027,7 +2027,7 @@ class TemplateAndFoldersSelectionForm(Form): def is_selected(self, template_folder_id): return template_folder_id in (self.templates_and_folders.data or []) - def validate(self): + def validate(self, extra_validators=None): self.op = request.form.get('operation') self.is_move_op = self.op in {'move-to-existing-folder', 'move-to-new-folder'} @@ -2037,7 +2037,7 @@ class TemplateAndFoldersSelectionForm(Form): if not (self.is_add_folder_op or self.is_move_op or self.is_add_template_op): return False - return super().validate() + return super().validate(extra_validators) def get_folder_name(self): if self.op == 'add-new-folder':