From a359bdd93074c4a73161a0b6c3f08e5c662867ad Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 18 Jun 2025 10:06:16 -0700 Subject: [PATCH 01/14] fix drift --- .github/workflows/drift.yml | 122 +++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 50 deletions(-) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index a92179fbe..ddf6a8685 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -44,58 +44,80 @@ jobs: exit $exit_code fi - # check_demo_drift: - # runs-on: ubuntu-latest - # name: Check for drift of demo terraform configuration - # environment: demo - # steps: - # - name: Checkout - # uses: actions/checkout@v4 - # with: - # ref: 'production' + check_demo_drift: + runs-on: ubuntu-latest + name: Check for drift of demo terraform configuration + environment: demo + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: 'production' - # # Looks like we need to install Terraform ourselves now! - # # https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348 - # - name: Setup Terraform - # uses: hashicorp/setup-terraform@v3 - # with: - # terraform_version: "^1.7.5" - # terraform_wrapper: false + # Looks like we need to install Terraform ourselves now! + # https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348 + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "^1.7.5" + terraform_wrapper: false - # - name: Check for drift - # uses: dflook/terraform-check@v1 - # env: - # AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} - # AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} - # TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }} - # TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }} - # with: - # path: terraform/demo + - name: Check for drift + env: + AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} + TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }} + TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }} + run: | + cd terraform/demo + terraform init + terraform plan -detailed-exitcode + exit_code=$? + if [ $exit_code -eq 0 ]; then + echo "No changes detected. Intrastructure is up-to-date." + elif [ $exit_code -eq 2 ]; then + echo "Changes detected. Infrastructure drift found." + exit 1 + else + echo "Error running terraform plan." + exit $exit_code + fi - # check_prod_drift: - # runs-on: ubuntu-latest - # name: Check for drift of production terraform configuration - # environment: production - # steps: - # - name: Checkout - # uses: actions/checkout@v4 - # with: - # ref: 'production' + check_prod_drift: + runs-on: ubuntu-latest + name: Check for drift of production terraform configuration + environment: production + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: 'production' - # # Looks like we need to install Terraform ourselves now! - # # https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348 - # - name: Setup Terraform - # uses: hashicorp/setup-terraform@v3 - # with: - # terraform_version: "^1.7.5" - # terraform_wrapper: false + # Looks like we need to install Terraform ourselves now! + # https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348 + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "^1.7.5" + terraform_wrapper: false - # - name: Check for drift - # uses: dflook/terraform-check@v1 - # env: - # AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} - # AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} - # TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }} - # TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }} - # with: - # path: terraform/production + - name: Check for drift + env: + AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} + TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }} + TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }} + run: | + cd terraform/production + terraform init + terraform plan -detailed-exitcode + exit_code=$? + if [ $exit_code -eq 0 ]; then + echo "No changes detected. Intrastructure is up-to-date." + elif [ $exit_code -eq 2 ]; then + echo "Changes detected. Infrastructure drift found." + exit 1 + else + echo "Error running terraform plan." + exit $exit_code + fi From b734b9cb9fd4690c974f47370e9e9d6fe65deb52 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 19 Jun 2025 11:03:04 -0700 Subject: [PATCH 02/14] fix input handling --- app/job/rest.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/job/rest.py b/app/job/rest.py index 0f80fab2d..457efb812 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -1,7 +1,8 @@ +import re from zoneinfo import ZoneInfo import dateutil -from flask import Blueprint, current_app, jsonify, request +from flask import Blueprint, abort, current_app, jsonify, request from app import db from app.aws.s3 import ( @@ -44,6 +45,15 @@ job_blueprint = Blueprint("job", __name__, url_prefix="/service/", methods=["GET"]) def get_job_by_service_and_job_id(service_id, job_id): job = dao_get_job_by_service_id_and_job_id(service_id, job_id) @@ -194,6 +204,10 @@ def get_recent_notifications_for_service_job(service_id, job_id): @job_blueprint.route("//notification_count", methods=["GET"]) def get_notification_count_for_job_id(service_id, job_id): + if is_suspicious_input(service_id) or is_suspicious_input(job_id): + abort(403) + if not is_valid_id(service_id) or not is_valid_id(job_id): + abort(403) dao_get_job_by_service_id_and_job_id(service_id, job_id) count = dao_get_notification_count_for_job_id(job_id=job_id) return jsonify(count=count), 200 From 69e7244485f4fe004be9b789fb18e40a0c85394a Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 19 Jun 2025 11:12:42 -0700 Subject: [PATCH 03/14] fix input handling --- app/job/rest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/job/rest.py b/app/job/rest.py index 457efb812..32e845b6a 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -45,12 +45,16 @@ job_blueprint = Blueprint("job", __name__, url_prefix="/service/ Date: Thu, 19 Jun 2025 11:31:37 -0700 Subject: [PATCH 04/14] fix input handling --- app/job/rest.py | 10 ++++++++-- tests/app/job/test_rest.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/job/rest.py b/app/job/rest.py index 32e845b6a..9a7de309c 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -47,7 +47,7 @@ register_errors(job_blueprint) def is_suspicious_input(input_str): if not isinstance(input_str, str): - return True + return False pattern = r"(?i)\b(OR|AND|UNION|SELECT|DROP|INSERT|UPDATE|DELETE|EXEC|TRUNCATE|CREATE|ALTER|--|/\*|\bpg_sleep\b|\bsleep\b)|[';]{2,}" # noqa return bool(re.search(pattern, input_str)) @@ -55,7 +55,7 @@ def is_suspicious_input(input_str): def is_valid_id(id): if not isinstance(id, str): return True - return bool(re.match(r"^[a-zA-Z0-9_-]{1,32}$", id)) + return bool(re.match(r"^[a-zA-Z0-9_-]{1,50}$", id)) @job_blueprint.route("/", methods=["GET"]) @@ -209,8 +209,14 @@ def get_recent_notifications_for_service_job(service_id, job_id): @job_blueprint.route("//notification_count", methods=["GET"]) def get_notification_count_for_job_id(service_id, job_id): if is_suspicious_input(service_id) or is_suspicious_input(job_id): + current_app.logger.error( + f"Service Id {service_id} is suspicious input or job id {job_id} is." + ) abort(403) if not is_valid_id(service_id) or not is_valid_id(job_id): + current_app.logger.error( + f"service id {service_id} or job_id {job_id} is not a valid id" + ) abort(403) dao_get_job_by_service_id_and_job_id(service_id, job_id) count = dao_get_notification_count_for_job_id(job_id=job_id) diff --git a/tests/app/job/test_rest.py b/tests/app/job/test_rest.py index f65ce2458..e39b24b54 100644 --- a/tests/app/job/test_rest.py +++ b/tests/app/job/test_rest.py @@ -16,6 +16,7 @@ from app.enums import ( NotificationType, TemplateType, ) +from app.job.rest import is_suspicious_input, is_valid_id from app.utils import utc_now from tests import create_admin_authorization_header from tests.app.db import ( @@ -586,6 +587,22 @@ def test_get_all_notifications_for_job_returns_correct_format( assert resp["notifications"][0]["status"] == sample_notification_with_job.status +def test_is_valid_id(sample_job): + returnVal = is_valid_id(sample_job.service_id) + assert returnVal is True + + returnVal = is_valid_id("abc pgsleep(1)") + assert returnVal is False + + +def test_is_suspicious_input(sample_job): + returnVal = is_suspicious_input(sample_job.id) + assert returnVal is False + + returnVal = is_suspicious_input("1 OR pg_sleep(1)") + assert returnVal is True + + def test_get_notification_count_for_job_id(admin_request, mocker, sample_job): mock_dao = mocker.patch( "app.job.rest.dao_get_notification_count_for_job_id", return_value=3 From 9bbe504fa3da1e9b6c8d7a57fb31305df7bc123d Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 19 Jun 2025 11:46:40 -0700 Subject: [PATCH 05/14] fix input handling --- app/job/rest.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/app/job/rest.py b/app/job/rest.py index 9a7de309c..938c19450 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -58,8 +58,17 @@ def is_valid_id(id): return bool(re.match(r"^[a-zA-Z0-9_-]{1,50}$", id)) +def check_suspicious_id(id): + if not is_valid_id(id): + abort(403) + if is_suspicious_input(id): + abort(403) + + @job_blueprint.route("/", methods=["GET"]) def get_job_by_service_and_job_id(service_id, job_id): + check_suspicious_id(service_id) + check_suspicious_id(job_id) job = dao_get_job_by_service_id_and_job_id(service_id, job_id) statistics = dao_get_notification_outcomes_for_job(service_id, job_id) data = JobSchema(session=db.session).dump(job) @@ -73,6 +82,9 @@ def get_job_by_service_and_job_id(service_id, job_id): @job_blueprint.route("//cancel", methods=["POST"]) def cancel_job(service_id, job_id): + check_suspicious_id(service_id) + check_suspicious_id(job_id) + job = dao_get_future_scheduled_job_by_id_and_service_id(job_id, service_id) job.job_status = JobStatus.CANCELLED dao_update_job(job) @@ -82,6 +94,9 @@ def cancel_job(service_id, job_id): @job_blueprint.route("//notifications", methods=["GET"]) def get_all_notifications_for_service_job(service_id, job_id): + check_suspicious_id(service_id) + check_suspicious_id(job_id) + data = notifications_filter_schema.load(request.args) page = data["page"] if "page" in data else 1 page_size = ( @@ -143,6 +158,9 @@ def get_all_notifications_for_service_job(service_id, job_id): @job_blueprint.route("//recent_notifications", methods=["GET"]) def get_recent_notifications_for_service_job(service_id, job_id): + check_suspicious_id(service_id) + check_suspicious_id(job_id) + data = notifications_filter_schema.load(request.args) page = data["page"] if "page" in data else 1 page_size = ( @@ -208,16 +226,9 @@ def get_recent_notifications_for_service_job(service_id, job_id): @job_blueprint.route("//notification_count", methods=["GET"]) def get_notification_count_for_job_id(service_id, job_id): - if is_suspicious_input(service_id) or is_suspicious_input(job_id): - current_app.logger.error( - f"Service Id {service_id} is suspicious input or job id {job_id} is." - ) - abort(403) - if not is_valid_id(service_id) or not is_valid_id(job_id): - current_app.logger.error( - f"service id {service_id} or job_id {job_id} is not a valid id" - ) - abort(403) + check_suspicious_id(service_id) + check_suspicious_id(job_id) + dao_get_job_by_service_id_and_job_id(service_id, job_id) count = dao_get_notification_count_for_job_id(job_id=job_id) return jsonify(count=count), 200 @@ -225,6 +236,7 @@ def get_notification_count_for_job_id(service_id, job_id): @job_blueprint.route("", methods=["GET"]) def get_jobs_by_service(service_id): + check_suspicious_id(service_id) if request.args.get("limit_days"): try: limit_days = int(request.args["limit_days"]) @@ -258,6 +270,8 @@ def get_jobs_by_service(service_id): @job_blueprint.route("", methods=["POST"]) def create_job(service_id): + check_suspicious_id(service_id) + """Entry point from UI for one-off messages as well as CSV uploads.""" service = dao_fetch_service_by_id(service_id) if not service.active: @@ -277,6 +291,7 @@ def create_job(service_id): ) data["template"] = data.pop("template_id") + check_suspicious_id(data["template"]) template = dao_get_template_by_id(data["template"]) if data.get("valid") != "True": @@ -301,6 +316,7 @@ def create_job(service_id): dao_create_job(job) sender_id = data.get("sender_id") + check_suspicious_id(sender_id) # Kick off job in tasks.py if job.job_status == JobStatus.PENDING: process_job.apply_async( @@ -315,6 +331,8 @@ def create_job(service_id): @job_blueprint.route("/scheduled-job-stats", methods=["GET"]) def get_scheduled_job_stats(service_id): + check_suspicious_id(service_id) + count, soonest_scheduled_for = dao_get_scheduled_job_stats(service_id) return ( jsonify( From a51ce0397d5c4f4399c5c3d4db17c4024e95a4ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 22:14:42 +0000 Subject: [PATCH 06/14] Bump numpy from 2.3.0 to 2.3.1 --- updated-dependencies: - dependency-name: numpy dependency-version: 2.3.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 106 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/poetry.lock b/poetry.lock index 615c0d114..0dbdc0881 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3018,63 +3018,63 @@ files = [ [[package]] name = "numpy" -version = "2.3.0" +version = "2.3.1" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["main"] files = [ - {file = "numpy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3c9fdde0fa18afa1099d6257eb82890ea4f3102847e692193b54e00312a9ae9"}, - {file = "numpy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46d16f72c2192da7b83984aa5455baee640e33a9f1e61e656f29adf55e406c2b"}, - {file = "numpy-2.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a0be278be9307c4ab06b788f2a077f05e180aea817b3e41cebbd5aaf7bd85ed3"}, - {file = "numpy-2.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:99224862d1412d2562248d4710126355d3a8db7672170a39d6909ac47687a8a4"}, - {file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2393a914db64b0ead0ab80c962e42d09d5f385802006a6c87835acb1f58adb96"}, - {file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7729c8008d55e80784bd113787ce876ca117185c579c0d626f59b87d433ea779"}, - {file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:06d4fb37a8d383b769281714897420c5cc3545c79dc427df57fc9b852ee0bf58"}, - {file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c39ec392b5db5088259c68250e342612db82dc80ce044cf16496cf14cf6bc6f8"}, - {file = "numpy-2.3.0-cp311-cp311-win32.whl", hash = "sha256:ee9d3ee70d62827bc91f3ea5eee33153212c41f639918550ac0475e3588da59f"}, - {file = "numpy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c55b6a860b0eb44d42341438b03513cf3879cb3617afb749ad49307e164edd"}, - {file = "numpy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2e6a1409eee0cb0316cb64640a49a49ca44deb1a537e6b1121dc7c458a1299a8"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:389b85335838155a9076e9ad7f8fdba0827496ec2d2dc32ce69ce7898bde03ba"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9498f60cd6bb8238d8eaf468a3d5bb031d34cd12556af53510f05fcf581c1b7e"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:622a65d40d8eb427d8e722fd410ac3ad4958002f109230bc714fa551044ebae2"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b9446d9d8505aadadb686d51d838f2b6688c9e85636a0c3abaeb55ed54756459"}, - {file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:50080245365d75137a2bf46151e975de63146ae6d79f7e6bd5c0e85c9931d06a"}, - {file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c24bb4113c66936eeaa0dc1e47c74770453d34f46ee07ae4efd853a2ed1ad10a"}, - {file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d8d294287fdf685281e671886c6dcdf0291a7c19db3e5cb4178d07ccf6ecc67"}, - {file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6295f81f093b7f5769d1728a6bd8bf7466de2adfa771ede944ce6711382b89dc"}, - {file = "numpy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:e6648078bdd974ef5d15cecc31b0c410e2e24178a6e10bf511e0557eed0f2570"}, - {file = "numpy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0898c67a58cdaaf29994bc0e2c65230fd4de0ac40afaf1584ed0b02cd74c6fdd"}, - {file = "numpy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:bd8df082b6c4695753ad6193018c05aac465d634834dca47a3ae06d4bb22d9ea"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb"}, - {file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0"}, - {file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f"}, - {file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8"}, - {file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270"}, - {file = "numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f"}, - {file = "numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5"}, - {file = "numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808"}, - {file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8"}, - {file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad"}, - {file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b"}, - {file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555"}, - {file = "numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61"}, - {file = "numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb"}, - {file = "numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80b46117c7359de8167cc00a2c7d823bdd505e8c7727ae0871025a86d668283b"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:5814a0f43e70c061f47abd5857d120179609ddc32a613138cbb6c4e9e2dbdda5"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ef6c1e88fd6b81ac6d215ed71dc8cd027e54d4bf1d2682d362449097156267a2"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33a5a12a45bb82d9997e2c0b12adae97507ad7c347546190a18ff14c28bbca12"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:54dfc8681c1906d239e95ab1508d0a533c4a9505e52ee2d71a5472b04437ef97"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e017a8a251ff4d18d71f139e28bdc7c31edba7a507f72b1414ed902cbe48c74d"}, - {file = "numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e"}, + {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db"}, + {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb"}, + {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93"}, + {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115"}, + {file = "numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369"}, + {file = "numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff"}, + {file = "numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943"}, + {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25"}, + {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660"}, + {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952"}, + {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77"}, + {file = "numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab"}, + {file = "numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76"}, + {file = "numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d"}, + {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1"}, + {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1"}, + {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0"}, + {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8"}, + {file = "numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8"}, + {file = "numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42"}, + {file = "numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992"}, + {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c"}, + {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48"}, + {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee"}, + {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280"}, + {file = "numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e"}, + {file = "numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc"}, + {file = "numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb"}, + {file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"}, ] [[package]] @@ -5563,4 +5563,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.12.2" -content-hash = "b2f9070fdf23cc044290e3c44c1b9fcf39edd5dd3513d9d7d820ec39a44f39b3" +content-hash = "ad6a697b7c467a5744d676ed27d0c13d18c1437d7c5867cc09b54870ce76a54f" diff --git a/pyproject.toml b/pyproject.toml index 85d596bec..61f0c20fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ faker = "^37.4.0" async-timeout = "^5.0.1" bleach = "^6.1.0" geojson = "^3.2.0" -numpy = "^2.2.6" +numpy = "^2.3.1" ordered-set = "^4.1.0" phonenumbers = "^9.0.7" python-json-logger = "^3.3.0" From 51247ce88af00128a3b96f8f6533b745f9e8531f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:33:03 +0000 Subject: [PATCH 07/14] Bump cloudfoundry-client from 1.38.4 to 1.38.5 --- updated-dependencies: - dependency-name: cloudfoundry-client dependency-version: 1.38.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 615c0d114..229d50d6f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -959,14 +959,14 @@ testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] [[package]] name = "cloudfoundry-client" -version = "1.38.4" +version = "1.38.5" description = "A client library for CloudFoundry" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "cloudfoundry_client-1.38.4-py3-none-any.whl", hash = "sha256:95477e1cc65bd61be33d7fa2b9decf15ab5911649a90ac1eea404e7a1176e2dd"}, - {file = "cloudfoundry_client-1.38.4.tar.gz", hash = "sha256:a54b67ee5e291bf7c56c5b2df5cc18216ba21386822d2fdecc44bcf475fd20fb"}, + {file = "cloudfoundry_client-1.38.5-py3-none-any.whl", hash = "sha256:d85a1d7a0d733de0e3f47d4c0bf7c3979cb490b14eeb8decda189a81106d51a0"}, + {file = "cloudfoundry_client-1.38.5.tar.gz", hash = "sha256:d2292dd3a553de2d65ae5031c1cbdc2243f45771951f990135f240181aa4a206"}, ] [package.dependencies] From 059ff9b3bb7c2f6890da024b8c3d0069d145d03c Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Tue, 24 Jun 2025 09:04:56 -0700 Subject: [PATCH 08/14] clean up logs --- app/dao/jobs_dao.py | 6 +++--- app/dao/notifications_dao.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/dao/jobs_dao.py b/app/dao/jobs_dao.py index 2e479a77d..171522d6c 100644 --- a/app/dao/jobs_dao.py +++ b/app/dao/jobs_dao.py @@ -157,17 +157,17 @@ def dao_create_job(job): orig_time = job.created_at now_time = utc_now() diff_time = now_time - orig_time - current_app.logger.info( + current_app.logger.warning( f"#notify-debug-admin-1859 dao_create_job orig created at {orig_time} and now {now_time}" ) if diff_time.total_seconds() > 300: # It should be only a few seconds diff at most - current_app.logger.error( + current_app.logger.warning( "#notify-debug-admin-1859 Something is wrong with job.created_at!" ) if os.getenv("NOTIFY_ENVIRONMENT") not in ["test"]: job.created_at = now_time dao_update_job(job) - current_app.logger.error( + current_app.logger.warning( f"#notify-debug-admin-1859 Job created_at reset to {job.created_at}" ) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index 9a4f219a8..76c0baea0 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -112,17 +112,17 @@ def dao_create_notification(notification): except ValueError: orig_time = datetime.strptime(orig_time, "%Y-%m-%d") diff_time = now_time - orig_time - current_app.logger.error( + current_app.logger.warning( f"dao_create_notification orig created at: {orig_time} and now created at: {now_time}" ) if diff_time.total_seconds() > 300: - current_app.logger.error( + current_app.logger.warning( "Something is wrong with notification.created_at in email!" ) if os.getenv("NOTIFY_ENVIRONMENT") not in ["test"]: notification.created_at = now_time dao_update_notification(notification) - current_app.logger.error( + current_app.logger.warning( f"Email notification created_at reset to {notification.created_at}" ) From 07adcd33eae6e709416cfb882287cb6a419678f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:25:42 +0000 Subject: [PATCH 09/14] Bump eventlet from 0.40.0 to 0.40.1 Bumps [eventlet](https://github.com/eventlet/eventlet) from 0.40.0 to 0.40.1. - [Changelog](https://github.com/eventlet/eventlet/blob/master/NEWS) - [Commits](https://github.com/eventlet/eventlet/compare/0.40.0...0.40.1) --- updated-dependencies: - dependency-name: eventlet dependency-version: 0.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 962255a3c..f43044061 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1329,14 +1329,14 @@ pgp = ["gpg"] [[package]] name = "eventlet" -version = "0.40.0" +version = "0.40.1" description = "Highly concurrent networking library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "eventlet-0.40.0-py3-none-any.whl", hash = "sha256:496915bc92d054236bad872d5143112a13b0216a7bbeeb832e1a858ae131fe8a"}, - {file = "eventlet-0.40.0.tar.gz", hash = "sha256:f659d735e06795a26167b666008798c7a203fcd8119b08b84036e41076432ff1"}, + {file = "eventlet-0.40.1-py3-none-any.whl", hash = "sha256:378f36ac9b3f4d28f2e921fd8897ebc9797d017e34b96c259ac87c032d1c1bf3"}, + {file = "eventlet-0.40.1.tar.gz", hash = "sha256:aee74de74ac6634a1dac1ed58dc93b5dc2abaef3c7b5e76fd7f195f1662f25ef"}, ] [package.dependencies] @@ -5563,4 +5563,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.12.2" -content-hash = "ad6a697b7c467a5744d676ed27d0c13d18c1437d7c5867cc09b54870ce76a54f" +content-hash = "ebd4eb6d04a21cf0b8f63c8143a70cd28317736be2e9e0f93d97eba253eeb5fe" diff --git a/pyproject.toml b/pyproject.toml index 61f0c20fb..15de5aef6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ click-didyoumean = "==0.3.1" click-plugins = "==1.1.1" click-repl = "==0.3.0" deprecated = "==1.2.18" -eventlet = "==0.40.0" +eventlet = "==0.40.1" expiringdict = "==1.2.2" flask = "~=3.1" flask-bcrypt = "==1.0.1" From 6d1284af2e54eb94ba2f9f6741fdb215a544013d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:54:27 +0000 Subject: [PATCH 10/14] Bump python-dotenv from 1.1.0 to 1.1.1 Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v1.1.0...v1.1.1) --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index f43044061..96d163d5e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3873,14 +3873,14 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.1.0" +version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, - {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, ] [package.extras] @@ -5563,4 +5563,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.12.2" -content-hash = "ebd4eb6d04a21cf0b8f63c8143a70cd28317736be2e9e0f93d97eba253eeb5fe" +content-hash = "1cf618979de62024de4aae6b031e84e625ea604c451a58b890c2013a088ee44a" diff --git a/pyproject.toml b/pyproject.toml index 15de5aef6..cb43e9452 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ packaging = "==25.0" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.10" pyjwt = "==2.10.1" -python-dotenv = "==1.1.0" +python-dotenv = "==1.1.1" sqlalchemy = "==2.0.41" werkzeug = "^3.0.6" faker = "^37.4.0" From 77b3c1afa673a17b3cb8a3b4689ee8d49e848c4f Mon Sep 17 00:00:00 2001 From: Kenneth Kehl Date: Wed, 25 Jun 2025 08:44:39 -0700 Subject: [PATCH 11/14] Update app/job/rest.py Co-authored-by: ccostino --- app/job/rest.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/job/rest.py b/app/job/rest.py index 938c19450..3e06aeb53 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -58,11 +58,12 @@ def is_valid_id(id): return bool(re.match(r"^[a-zA-Z0-9_-]{1,50}$", id)) -def check_suspicious_id(id): - if not is_valid_id(id): - abort(403) - if is_suspicious_input(id): - abort(403) +def check_suspicious_id(*args): + for id in args: + if not is_valid_id(id): + abort(403) + if is_suspicious_input(id): + abort(403) @job_blueprint.route("/", methods=["GET"]) From 5b2d8e064855c6bb0f9f7b9cb14fcdcf41eb3da4 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 25 Jun 2025 08:54:44 -0700 Subject: [PATCH 12/14] code review feedback --- app/job/rest.py | 15 +++++---------- tests/app/job/test_rest.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/job/rest.py b/app/job/rest.py index 3e06aeb53..5189c6917 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -68,8 +68,7 @@ def check_suspicious_id(*args): @job_blueprint.route("/", methods=["GET"]) def get_job_by_service_and_job_id(service_id, job_id): - check_suspicious_id(service_id) - check_suspicious_id(job_id) + check_suspicious_id(service_id, job_id) job = dao_get_job_by_service_id_and_job_id(service_id, job_id) statistics = dao_get_notification_outcomes_for_job(service_id, job_id) data = JobSchema(session=db.session).dump(job) @@ -83,8 +82,7 @@ def get_job_by_service_and_job_id(service_id, job_id): @job_blueprint.route("//cancel", methods=["POST"]) def cancel_job(service_id, job_id): - check_suspicious_id(service_id) - check_suspicious_id(job_id) + check_suspicious_id(service_id, job_id) job = dao_get_future_scheduled_job_by_id_and_service_id(job_id, service_id) job.job_status = JobStatus.CANCELLED @@ -95,8 +93,7 @@ def cancel_job(service_id, job_id): @job_blueprint.route("//notifications", methods=["GET"]) def get_all_notifications_for_service_job(service_id, job_id): - check_suspicious_id(service_id) - check_suspicious_id(job_id) + check_suspicious_id(service_id, job_id) data = notifications_filter_schema.load(request.args) page = data["page"] if "page" in data else 1 @@ -159,8 +156,7 @@ def get_all_notifications_for_service_job(service_id, job_id): @job_blueprint.route("//recent_notifications", methods=["GET"]) def get_recent_notifications_for_service_job(service_id, job_id): - check_suspicious_id(service_id) - check_suspicious_id(job_id) + check_suspicious_id(service_id, job_id) data = notifications_filter_schema.load(request.args) page = data["page"] if "page" in data else 1 @@ -227,8 +223,7 @@ def get_recent_notifications_for_service_job(service_id, job_id): @job_blueprint.route("//notification_count", methods=["GET"]) def get_notification_count_for_job_id(service_id, job_id): - check_suspicious_id(service_id) - check_suspicious_id(job_id) + check_suspicious_id(service_id, job_id) dao_get_job_by_service_id_and_job_id(service_id, job_id) count = dao_get_notification_count_for_job_id(job_id=job_id) diff --git a/tests/app/job/test_rest.py b/tests/app/job/test_rest.py index e39b24b54..dbba6d729 100644 --- a/tests/app/job/test_rest.py +++ b/tests/app/job/test_rest.py @@ -5,6 +5,7 @@ from unittest.mock import ANY from zoneinfo import ZoneInfo import pytest +import werkzeug from freezegun import freeze_time import app.celery.tasks @@ -16,7 +17,7 @@ from app.enums import ( NotificationType, TemplateType, ) -from app.job.rest import is_suspicious_input, is_valid_id +from app.job.rest import check_suspicious_id, is_suspicious_input, is_valid_id from app.utils import utc_now from tests import create_admin_authorization_header from tests.app.db import ( @@ -595,6 +596,15 @@ def test_is_valid_id(sample_job): assert returnVal is False +def test_check_suspicious_id(sample_job): + # This should be good + check_suspicious_id(sample_job.id, sample_job.service_id) + + # This should be bad + with pytest.raises(werkzeug.exceptions.Forbidden): + check_suspicious_id(sample_job.id, "what is this???") + + def test_is_suspicious_input(sample_job): returnVal = is_suspicious_input(sample_job.id) assert returnVal is False From ddcb34af86a53e50017d1cea5dd963b2d7fd1d30 Mon Sep 17 00:00:00 2001 From: alexjanousekGSA Date: Wed, 25 Jun 2025 13:11:08 -0400 Subject: [PATCH 13/14] Updated readme --- README.md | 68 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 2a6902de6..f090b6eac 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It's cloned from the brilliant work of the team at This repo contains: - A public-facing REST API for Notify.gov, which teams can integrate with using - [API clients built by UK](https://www.notifications.service.gov.uk/documentation). + [API clients built by UK](https://www.notifications.service.gov.uk/documentation). - An internal-only REST API built using Flask to manage services, users, templates, etc., which the [Notify.gov Admin UI](http://github.com/18F/notifications-admin) talks to. @@ -18,7 +18,6 @@ This repo contains: Our other repositories are: -- [notifications-admin](https://github.com/GSA/notifications-admin) - [us-notify-compliance](https://github.com/GSA/us-notify-compliance/) - [notify-python-demo](https://github.com/GSA/notify-python-demo) @@ -53,7 +52,7 @@ recommended. This helps avoid some known installation issues. Start by following the installation instructions on the Homebrew homepage. **Note:** You will also need Xcode or the Xcode Command Line Tools installed. The -quickest way to do this is is by installing the command line tools in the shell: +quickest way to do this is by installing the command line tools in the shell: ```sh xcode-select –-install @@ -71,11 +70,13 @@ Your system `$PATH` environment variable is likely set in one of these locations: For BASH shells: + - `~/.bashrc` - `~/.bash_profile` - `~/.profile` For ZSH shells: + - `~/.zshrc` - `~/.zprofile` @@ -85,7 +86,7 @@ environments. Which file you need to modify depends on whether or not you are running an interactive shell or a login shell (see [this Stack Overflow post](https://stackoverflow.com/questions/18186929/what-are-the-differences-between-a-login-shell-and-interactive-shell) -for an explanation of the differences). If you're still not sure, please ask +for an explanation of the differences). If you're still not sure, please ask the team for help! Once you determine which file you'll need to modify, add these lines before any @@ -147,7 +148,7 @@ tfenv use 1.7.x # x = the patch version installed #### Python Installation Now we're going to install a tool to help us manage Python versions and -virtual environments on our system. First, we'll install +virtual environments on our system. First, we'll install [pyenv](https://github.com/pyenv/pyenv) and one of its plugins, [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv), with Homebrew: @@ -201,10 +202,6 @@ requires a version number to be included with it when installing it: brew install postgresql@15 ``` -_NOTE: This project currently works with PostgreSQL version 15.x; version 12.x is currently used in our hosted environments._ - -_NOTE: If you have a pre-existing instance of PSQL installed because of another product like PGAdmin, your database configuration may differ from the instructions above, which uses Homebrew to install and configure PostgreSQL. If this is the case for you, you may have to either account for slightly different user permissions with the database, or uninstall PGAdmin and/or PostgreSQL itself, and reinstall it with Homebrew to follow the steps above._ - You'll now need to modify (or create, if it doesn't already exist) the `$PATH` environment variable to include the PostgreSQL binaries. Open the file you have worked with before to adjust your shell environment with the previous steps and @@ -224,6 +221,10 @@ this, which will include the PostgreSQL binaries: export PATH="/opt/homebrew/opt/postgresql@15/bin:$PATH" ``` +_NOTE: This project currently works with PostgreSQL version 15.x; version 12.x is currently used in our hosted environments._ + +_NOTE: If you have a pre-existing instance of PSQL installed because of another product like PGAdmin, your database configuration may differ from the instructions above, which uses Homebrew to install and configure PostgreSQL. If this is the case for you, you may have to either account for slightly different user permissions with the database, or uninstall PGAdmin and/or PostgreSQL itself, and reinstall it with Homebrew to follow the steps above._ + _NOTE: You don't want to overwrite your existing `$PATH` environment variable! Hence the reason why it is included on the end like this; paths are separated by a colon._ #### Starting PostgreSQL and Redis @@ -268,7 +269,7 @@ pyenv virtualenv 3.12.2 notify-api pyenv local notify-api ``` -_If you're not sure which version of Python was installed with `pyenv`, you can check by running `pyenv versions` and it'll list everything available currently._ +_NOTE: If you're not sure which version of Python was installed with `pyenv`, you can check by running `pyenv versions` and it'll list everything available currently._ Now [log into cloud.gov](https://cloud.gov/docs/getting-started/setup/#set-up-the-command-line) in the command line by using this command: @@ -276,6 +277,7 @@ in the command line by using this command: ```sh cf login -a api.fr.cloud.gov --sso ``` + If you are offered a choice of orgs, select `gsa-tts-benefits-studio`. For the space, choose `notify-local-dev` to start with (assuming you are setting up local development). @@ -317,7 +319,7 @@ we'll use `3.13` in our example here since we recently upgraded to this version: pyenv install 3.13 ``` -Next, delete the virtual environment you previously had set up. If you followed +Next, delete the virtual environment you previously had set up. If you followed the instructions above with the first-time set up, you can do this with `pyenv`: ```sh @@ -336,10 +338,9 @@ pyenv local notify-api At this point, proceed with the rest of the instructions here in the README and you'll be set with an upgraded version of Python. -_If you're not sure about the details of your current virtual environment, you can run `poetry env info` to get more information. If you've been using `pyenv` for everything, you can also see all available virtual environments with `pyenv virtualenvs`._ +_NOTE: If you're not sure about the details of your current virtual environment, you can run `poetry env info` to get more information. If you've been using `pyenv` for everything, you can also see all available virtual environments with `pyenv virtualenvs`._ - -#### Poetry upgrades #### +#### Poetry upgrades If you are doing a new project setup, then after you install poetry you need to install the export plugin @@ -356,7 +357,7 @@ poetry self add poetry-export-plugin ### Final environment setup -There's one final thing to adjust in the newly created `.env` file. This +There's one final thing to adjust in the newly created `.env` file. This project has support for end-to-end (E2E) tests and has some additional checks for the presence of an E2E test user so that it can be authenticated properly. @@ -380,8 +381,8 @@ variable to something else, preferably a lengthy passphrase.** With those two environment variable set, the database migrations will run properly and an E2E test user will be ready to go for use in the admin project. -_Note: Whatever you set these two environment variables to, you'll need to -match their values on the admin side. Please see the admin README and +_Note: Whatever you set these two environment variables to, you'll need to +match their values on the admin side. Please see the admin README and documentation for more details._ ## Running the Project and Routine Maintenance @@ -407,7 +408,7 @@ make run-procfile If it runs correctly, you will be able to visit http://127.0.0.1:6011/ and see JSON from the API in your web browser. -This will run all of the services within the same shell session. If you need to +This will run all of the services within the same shell session. If you need to run them separately to help with debugging or tracing logs, you can do so by opening three sepearate shell sessions and running one of these commands in each one separately: @@ -419,16 +420,27 @@ one separately: ## Python Dependency Management We're using [`Poetry`](https://python-poetry.org/) for managing our Python -dependencies and local virtual environments. When it comes to managing the -Python dependencies, there are a couple of things to bear in mind. +dependencies and local virtual environments. -For situations where you manually manipulate the `pyproject.toml` file, you -should use the `make py-lock` command to sync the `poetry.lock` file. This will +This project has two key dependency files that must be managed together: + +- `pyproject.toml` - Contains the dependency specifications +- `poetry.lock` - Contains the exact versions of all dependencies (including transitive ones) + +### Managing Dependencies + +There are two approaches for updating dependencies: + +#### 1. Manual manipulation of `pyproject.toml` + +If you manually edit the `pyproject.toml` file, you should use the `make py-lock` command to sync the `poetry.lock` file. This will ensure that you don't inadvertently bring in other transitive dependency updates that have not been fully tested with the project yet. -If you're just trying to update a dependency to a newer (or the latest) version, -you should let Poetry take care of that for you by running the following: +#### 2. Using Poetry to update dependencies (recommended) + +If you're updating a dependency to a newer (or the latest) version, +let Poetry handle it by running: ```sh poetry update [...] @@ -441,9 +453,9 @@ will do the following for you: - Install the new versions - Update and sync the `poetry.lock` file -In either situation, once you are finished and have verified the dependency -changes are working, please be sure to commit both the `pyproject.toml` and -`poetry.lock` files. +**Important:** In either situation, once you are finished and have verified the dependency +changes are working, you must commit both the `pyproject.toml` and +`poetry.lock` files together. ## Known Installation Issues @@ -513,7 +525,7 @@ instructions above for more details. - [Pull Requests](.docs/all.md#pull-requests) - [Getting Started](.docs/all.md#getting-started) - [Description](.docs/all.md#description) - - [TODO (optional)](.docs/all.md#todo-(optional)) + - [TODO (optional)](<.docs/all.md#todo-(optional)>) - [Security Considerations](.docs/all.md#security-considerations) - [Code Reviews](.docs/all.md#code-reviews) - [For the reviewer](.docs/all.md#for-the-reviewer) From b52518ca0849b42f316aeac4104a4e2f0b1d818e Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 25 Jun 2025 10:37:07 -0700 Subject: [PATCH 14/14] make regex verbose --- app/job/rest.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/app/job/rest.py b/app/job/rest.py index 5189c6917..831c3f2f4 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -48,7 +48,35 @@ register_errors(job_blueprint) def is_suspicious_input(input_str): if not isinstance(input_str, str): return False - pattern = r"(?i)\b(OR|AND|UNION|SELECT|DROP|INSERT|UPDATE|DELETE|EXEC|TRUNCATE|CREATE|ALTER|--|/\*|\bpg_sleep\b|\bsleep\b)|[';]{2,}" # noqa + + pattern = re.compile( + r""" + (?i) # case insensite + \b # word boundary + ( # start of group for SQL keywords + OR # match SQL keyword OR + |AND + |UNION + |SELECT + |DROP + |INSERT + |UPDATE + |DELETE + |EXEC + |TRUNCATE + |CREATE + |ALTER + |-- # match SQL single-line comment + |/\* # match SQL multi-line comment + |\bpg_sleep\b # Match PostgreSQL 'pg_sleep' function + + |\bsleep\b # Match SQL Server 'sleep' function + ) # End SQL keywords and function group + | # OR operator to include an alternate pattern + [';]{2,} # Match two or more consecutive single quotes or semi-colons + """, + re.VERBOSE, + ) return bool(re.search(pattern, input_str))