From e71989022d234f2733badabbb31e0b152fdd9e68 Mon Sep 17 00:00:00 2001 From: samathad2023 Date: Thu, 4 Apr 2024 18:09:22 -0700 Subject: [PATCH 01/14] flask upgrade --- app/formatters.py | 3 +- app/main/forms.py | 3 +- app/main/views/api_keys.py | 3 +- app/main/views/jobs.py | 2 +- app/main/views/sign_in.py | 2 +- poetry.lock | 104 ++++++++++++++++-------------- pyproject.toml | 8 +-- tests/app/main/views/test_send.py | 6 +- 8 files changed, 70 insertions(+), 61 deletions(-) diff --git a/app/formatters.py b/app/formatters.py index 2bb8854f3..29cb9a4ed 100644 --- a/app/formatters.py +++ b/app/formatters.py @@ -13,8 +13,9 @@ import humanize import markdown import pytz from bs4 import BeautifulSoup -from flask import Markup, render_template_string, url_for +from flask import render_template_string, url_for from flask.helpers import get_root_path +from markupsafe import Markup from notifications_utils.field import Field from notifications_utils.formatters import make_quotes_smart from notifications_utils.formatters import nl2br as utils_nl2br diff --git a/app/main/forms.py b/app/main/forms.py index 13a463a51..2d1820391 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -4,12 +4,13 @@ from itertools import chain from numbers import Number import pytz -from flask import Markup, render_template, request +from flask import render_template, request from flask_login import current_user from flask_wtf import FlaskForm as Form from flask_wtf.file import FileAllowed from flask_wtf.file import FileField as FileField_wtf from flask_wtf.file import FileSize +from markupsafe import Markup from notifications_utils.formatters import strip_all_whitespace from notifications_utils.insensitive_dict import InsensitiveDict from notifications_utils.recipients import InvalidPhoneError, validate_phone_number diff --git a/app/main/views/api_keys.py b/app/main/views/api_keys.py index 8cb28ba59..f93d2caa1 100644 --- a/app/main/views/api_keys.py +++ b/app/main/views/api_keys.py @@ -1,5 +1,6 @@ -from flask import Markup, abort, flash, redirect, render_template, request, url_for +from flask import abort, flash, redirect, render_template, request, url_for from flask_login import current_user +from markupsafe import Markup from app import ( api_key_api_client, diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index 0c0848e46..d914b10f0 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -3,7 +3,6 @@ from functools import partial from flask import ( - Markup, Response, abort, jsonify, @@ -15,6 +14,7 @@ from flask import ( url_for, ) from flask_login import current_user +from markupsafe import Markup from notifications_utils.template import EmailPreviewTemplate, SMSBodyPreviewTemplate from app import ( diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index 402e61a08..9f070175c 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -5,7 +5,6 @@ import uuid import jwt import requests from flask import ( - Markup, abort, current_app, flash, @@ -16,6 +15,7 @@ from flask import ( url_for, ) from flask_login import current_user +from markupsafe import Markup from notifications_utils.url_safe_token import generate_token from app import login_manager, user_api_client diff --git a/poetry.lock b/poetry.lock index 930c20232..4eca6b7c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,41 +171,41 @@ files = [ [[package]] name = "boto3" -version = "1.29.6" +version = "1.34.78" description = "The AWS SDK for Python" optional = false -python-versions = ">= 3.7" +python-versions = ">=3.8" files = [ - {file = "boto3-1.29.6-py3-none-any.whl", hash = "sha256:f4d19e01d176c3a5a05e4af733185ff1891b08a3c38d4a439800fa132aa6e9be"}, - {file = "boto3-1.29.6.tar.gz", hash = "sha256:d1d0d979a70bf9b0b13ae3b017f8523708ad953f62d16f39a602d67ee9b25554"}, + {file = "boto3-1.34.78-py3-none-any.whl", hash = "sha256:47a7899af97960493ed58754c838be658650c8fb231c658866f491965ddfc94f"}, + {file = "boto3-1.34.78.tar.gz", hash = "sha256:227487f9a40e7963aa108f4fabc81374d65e085891a2a442c190dfd976b86a9e"}, ] [package.dependencies] -botocore = ">=1.32.6,<1.33.0" +botocore = ">=1.34.78,<1.35.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.7.0,<0.8.0" +s3transfer = ">=0.10.0,<0.11.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.32.7" +version = "1.34.78" description = "Low-level, data-driven core of boto 3." optional = false -python-versions = ">= 3.7" +python-versions = ">=3.8" files = [ - {file = "botocore-1.32.7-py3-none-any.whl", hash = "sha256:58b33d02cafa23461c8a9d211b30e8cded992380a84de409379fd02811fa3e11"}, - {file = "botocore-1.32.7.tar.gz", hash = "sha256:c6795c731b04c8e3635588c44cfd1a4462fc5987859195522c96812cf3eceff9"}, + {file = "botocore-1.34.78-py3-none-any.whl", hash = "sha256:bc10738826a4970a6d3a40ac40b9799c02b1b661c0c741a67b915b500562ab3c"}, + {file = "botocore-1.34.78.tar.gz", hash = "sha256:889fcfd1813fad225a5a70940c58cd4bd7a6f5ba6c9769a1d41d0c670272b75d"}, ] [package.dependencies] jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.19.17)"] +crt = ["awscrt (==0.19.19)"] [[package]] name = "cachecontrol" @@ -801,13 +801,13 @@ flake8-plugin-utils = ">=1.3.2,<2.0.0" [[package]] name = "flask" -version = "2.3.3" +version = "3.0.2" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" files = [ - {file = "flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"}, - {file = "flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc"}, + {file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"}, + {file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"}, ] [package.dependencies] @@ -815,7 +815,7 @@ blinker = ">=1.6.2" click = ">=8.1.3" itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" -Werkzeug = ">=2.3.7" +Werkzeug = ">=3.0.0" [package.extras] async = ["asgiref (>=3.2)"] @@ -925,13 +925,13 @@ files = [ [[package]] name = "govuk-bank-holidays" -version = "0.13" +version = "0.14" description = "Tool to load UK bank holidays from GOV.UK" optional = false python-versions = ">=3.6" files = [ - {file = "govuk-bank-holidays-0.13.tar.gz", hash = "sha256:ffb6ac050701cb850fd2f08eb9c7c91753090f00525e69cebec4f869f6f0de41"}, - {file = "govuk_bank_holidays-0.13-py3-none-any.whl", hash = "sha256:2b91901be492235c4160a64d56d4d1945f6ca7f11c1ea4277395981866c35bef"}, + {file = "govuk-bank-holidays-0.14.tar.gz", hash = "sha256:ce85102423b72908957d25981f616494729686515d5d66c09a1d35a354ce20a6"}, + {file = "govuk_bank_holidays-0.14-py3-none-any.whl", hash = "sha256:da485c4a40c6c874c925916e492e3f20b807cffba7eed5f07fb69327aef6b10b"}, ] [package.dependencies] @@ -1210,6 +1210,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li optional = false python-versions = ">=3.6" files = [ + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, @@ -1219,6 +1220,7 @@ files = [ {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, @@ -1228,6 +1230,7 @@ files = [ {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, @@ -1253,8 +1256,8 @@ files = [ {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cfbac9f6149174f76df7e08c2e28b19d74aed90cad60383ad8671d3af7d0502f"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, @@ -1262,6 +1265,7 @@ files = [ {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, @@ -1512,7 +1516,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, ] [[package]] @@ -1608,19 +1612,19 @@ develop = false [package.dependencies] async-timeout = "^4.0.2" bleach = "^6.1.0" -blinker = "^1.6.2" -boto3 = "^1.28.66" -botocore = "^1.31.66" +blinker = "^1.7.0" +boto3 = "^1.34.77" +botocore = "^1.34.77" cachetools = "^5.3.0" certifi = "^2024.2.2" cffi = "^1.16.0" charset-normalizer = "^3.1.0" -click = "^8.1.3" +click = "^8.1.7" cryptography = "^42.0.4" -flask = "^2.3.2" +flask = "^3.0.2" flask-redis = "^0.4.0" geojson = "^3.0.1" -govuk-bank-holidays = "^0.13" +govuk-bank-holidays = "^0.14" idna = "^3.6" itsdangerous = "^2.1.2" jinja2 = "^3.1.3" @@ -1633,24 +1637,24 @@ phonenumbers = "^8.13.32" pycparser = "^2.21" python-dateutil = "^2.8.2" python-json-logger = "^2.0.7" -pytz = "^2023.3" +pytz = "^2024.1" pyyaml = "^6.0" redis = "^5.0.3" regex = "^2023.12.25" requests = "^2.31.0" -s3transfer = "^0.7.0" +s3transfer = "^0.10.1" shapely = "^2.0.1" six = "^1.16.0" smartypants = "^2.0.1" -urllib3 = "^1.26.18" +urllib3 = "^2.0.7" webencodings = "^0.5.1" werkzeug = "^3.0.1" [package.source] type = "git" url = "https://github.com/GSA/notifications-utils.git" -reference = "HEAD" -resolved_reference = "4cf526bc1fd9532507936c174418dbd3be52c925" +reference = "c2ca77d" +resolved_reference = "c2ca77dd3fa877fd049147fb484790749a5157ad" [[package]] name = "numpy" @@ -2344,13 +2348,13 @@ unidecode = ["Unidecode (>=1.1.1)"] [[package]] name = "pytz" -version = "2023.4" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, - {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -2378,6 +2382,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2619,20 +2624,20 @@ diagram = ["matplotlib (>=3.0.0)", "pydot (>=1.3.0)", "tqdm (>=v4.31.0)"] [[package]] name = "s3transfer" -version = "0.7.0" +version = "0.10.1" description = "An Amazon S3 Transfer Manager" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "s3transfer-0.7.0-py3-none-any.whl", hash = "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a"}, - {file = "s3transfer-0.7.0.tar.gz", hash = "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"}, + {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, + {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, ] [package.dependencies] -botocore = ">=1.12.36,<2.0a.0" +botocore = ">=1.33.2,<2.0a.0" [package.extras] -crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "setuptools" @@ -2810,19 +2815,20 @@ files = [ [[package]] name = "urllib3" -version = "1.26.18" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.8" files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" @@ -2930,4 +2936,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "fb81af6421e07a1640bd784f2e1934237a064b30705a0c02e6f06415493e05ff" +content-hash = "6b6022385be06b9867620076b1a210ccac6d8c577b8b69003454fb586918e8e9" diff --git a/pyproject.toml b/pyproject.toml index 40a1aac24..427d5298e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,19 +12,19 @@ ago = "~=0.0.95" beautifulsoup4 = "^4.12.3" blinker = "~=1.7" exceptiongroup = "==1.2.0" -flask = "~=2.3" +flask = "~=3.0" flask-basicauth = "~=0.2" flask-login = "^0.6" flask-talisman = "*" flask-wtf = "^1.2" -govuk-bank-holidays = "==0.13" +govuk-bank-holidays = "==0.14" gunicorn = {version = "==21.2.0", extras = ["eventlet"]} humanize = "~=4.9" itsdangerous = "~=2.1" jinja2 = "~=3.1" newrelic = "*" notifications-python-client = "==9.0.0" -notifications-utils = {git = "https://github.com/GSA/notifications-utils.git"} +notifications-utils = {git = "https://github.com/GSA/notifications-utils.git", rev = "c2ca77d"} pyexcel = "==0.7.0" pyexcel-io = "==0.6.6" pyexcel-ods3 = "==0.6.1" @@ -33,7 +33,7 @@ pyexcel-xlsx = "==0.6.0" openpyxl = "==3.0.10" pyproj = "==3.6.1" python-dotenv = "==1.0.1" -pytz = "~=2023.4" +pytz = "~=2024.1" rtreelib = "==0.2.0" werkzeug = "^3.0.1" wtforms = "~=3.1" diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index e15772833..ab35dd29b 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -979,7 +979,7 @@ def test_upload_valid_csv_shows_preview_and_table( '
A
', ( ' ' - '
' + "
" "
    " "
  • foo
  • foo
  • foo
  • " "
" @@ -992,7 +992,7 @@ def test_upload_valid_csv_shows_preview_and_table( '
B
', ( ' ' - '
' + "
" "
    " "
  • foo
  • foo
  • foo
  • " "
" @@ -1005,7 +1005,7 @@ def test_upload_valid_csv_shows_preview_and_table( '
C
', ( ' ' - '
' + "
" "
    " "
  • foo
  • foo
  • " "
" From 5dc23947e27ce5a9f38f1d856fc1e2d9239cfb4a Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Mon, 29 Apr 2024 15:44:33 -0400 Subject: [PATCH 02/14] Updated utils to 0.5.1 release in prep for Flask upgrade Signed-off-by: Carlo Costino --- poetry.lock | 14 +++++++------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3fa031aa1..69007e3b5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1680,7 +1680,7 @@ requests = ">=2.0.0" [[package]] name = "notifications-utils" -version = "0.4.6" +version = "0.5.1" description = "" optional = false python-versions = "^3.12.2" @@ -1692,7 +1692,7 @@ async-timeout = "^4.0.2" bleach = "^6.1.0" blinker = "^1.7.0" boto3 = "^1.34.83" -botocore = "^1.34.83" +botocore = "^1.34.92" cachetools = "^5.3.0" certifi = "^2024.2.2" cffi = "^1.16.0" @@ -1711,7 +1711,7 @@ markupsafe = "^2.1.5" mistune = "==0.8.4" numpy = "^1.24.2" ordered-set = "^4.1.0" -phonenumbers = "^8.13.34" +phonenumbers = "^8.13.35" pycparser = "^2.21" python-dateutil = "^2.8.2" python-json-logger = "^2.0.7" @@ -1721,7 +1721,7 @@ redis = "^5.0.3" regex = "^2023.12.25" requests = "^2.31.0" s3transfer = "^0.10.1" -shapely = "^2.0.1" +shapely = "^2.0.4" six = "^1.16.0" smartypants = "^2.0.1" urllib3 = "^2.2.1" @@ -1731,8 +1731,8 @@ werkzeug = "^3.0.1" [package.source] type = "git" url = "https://github.com/GSA/notifications-utils.git" -reference = "d20efc2" -resolved_reference = "d20efc29d68ecbb55ef964db890d17426cf34a0f" +reference = "HEAD" +resolved_reference = "e047ba3e37f9fd885baafe6944a4910285a506fb" [[package]] name = "numpy" @@ -3014,4 +3014,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "0c8a8aec981bc31179f4ad0bafbc48ca36b228949c3bcfc533e7cc8e5c1f79b8" +content-hash = "50b9a0e68826deee9bc4a308db2ae39df0ef5108cfc760a3a497fdd114b40560" diff --git a/pyproject.toml b/pyproject.toml index edfad6093..9050cdea4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ itsdangerous = "~=2.2" jinja2 = "~=3.1" newrelic = "*" notifications-python-client = "==9.0.0" -notifications-utils = {git = "https://github.com/GSA/notifications-utils.git",rev = "d20efc2"} +notifications-utils = {git = "https://github.com/GSA/notifications-utils.git"} pyexcel = "==0.7.0" pyexcel-io = "==0.6.6" pyexcel-ods3 = "==0.6.1" From fb66531c442722cce86123a55f1b1fd5a55c36fc Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Fri, 3 May 2024 12:10:59 -0400 Subject: [PATCH 03/14] 1334 - Updating content for Eligible partners --- app/templates/views/signedout.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html index 3289dd121..96ecb526d 100644 --- a/app/templates/views/signedout.html +++ b/app/templates/views/signedout.html @@ -142,6 +142,20 @@ Notify.gov Notify.gov is a new shared service currently being piloted by the Public Benefits Studio, within Technology Transformation Services at the General Services Administration.

+

+ Who's Eligible? +

+

+ Eligible partners include federal agencies as well as state, local, territorial, tribal agencies + administering federally funded programs. As part of the onboarding process, non-federal + programs must specify the federal program that the messages they want to send are related + to. TTS will also seek concurrence from the federal funder. +

+

+ We prioritize partnerships focused on use cases related to increasing transparency and + reducing the burden of navigating government programs for low income individuals and + families. +

From 5fe35b4497f72402b0ffca0e35bb02da47630e9a Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Mon, 6 May 2024 13:02:02 -0400 Subject: [PATCH 04/14] Updating several dependencies related to Flask upgrade Signed-off-by: Carlo Costino --- poetry.lock | 120 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/poetry.lock b/poetry.lock index 69007e3b5..f7962fa58 100644 --- a/poetry.lock +++ b/poetry.lock @@ -462,63 +462,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.0" +version = "7.5.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"}, - {file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"}, - {file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"}, - {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"}, - {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"}, - {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"}, - {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"}, - {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"}, - {file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"}, - {file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"}, - {file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"}, - {file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"}, - {file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"}, - {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"}, - {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"}, - {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"}, - {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"}, - {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"}, - {file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"}, - {file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"}, - {file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"}, - {file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"}, - {file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"}, - {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"}, - {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"}, - {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"}, - {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"}, - {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"}, - {file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"}, - {file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"}, - {file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"}, - {file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"}, - {file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"}, - {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"}, - {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"}, - {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"}, - {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"}, - {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"}, - {file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"}, - {file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"}, - {file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"}, - {file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"}, - {file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"}, - {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"}, - {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"}, - {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"}, - {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"}, - {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"}, - {file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"}, - {file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"}, - {file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"}, - {file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, + {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, + {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, + {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, + {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, + {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, + {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, + {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, + {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, ] [package.extras] @@ -1128,13 +1128,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -2952,13 +2952,13 @@ files = [ [[package]] name = "werkzeug" -version = "3.0.2" +version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.2-py3-none-any.whl", hash = "sha256:3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795"}, - {file = "werkzeug-3.0.2.tar.gz", hash = "sha256:e39b645a6ac92822588e7b39a692e7828724ceae0b0d702ef96701f90e70128d"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -3014,4 +3014,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "50b9a0e68826deee9bc4a308db2ae39df0ef5108cfc760a3a497fdd114b40560" +content-hash = "a511e7d8f022826d71a6a1bb47cc7587d81c7ad331f3a6c26a8538b1390929e3" diff --git a/pyproject.toml b/pyproject.toml index 9050cdea4..85d2fd4a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ pyproj = "==3.6.1" python-dotenv = "==1.0.1" pytz = "^2024.1" rtreelib = "==0.2.0" -werkzeug = "^3.0.1" +werkzeug = "^3.0.3" wtforms = "~=3.1" markdown = "^3.5.2" From 9ecbfb7e05ce21f26c89056ff7138c6e16c2a7f5 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 May 2024 10:43:01 -0700 Subject: [PATCH 05/14] notify-admin-1459 --- app/main/views/sign_in.py | 117 ++++++++++------------ poetry.lock | 2 + tests/app/main/views/test_sign_in.py | 144 +-------------------------- 3 files changed, 59 insertions(+), 204 deletions(-) diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index 532bd9500..7af9ce8ca 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -4,27 +4,15 @@ import uuid import jwt import requests -from flask import ( - Response, - abort, - current_app, - flash, - redirect, - render_template, - request, - session, - url_for, -) +from flask import Response, current_app, redirect, render_template, request, url_for from flask_login import current_user -from markupsafe import Markup from notifications_utils.url_safe_token import generate_token from app import login_manager, user_api_client from app.main import main -from app.main.forms import LoginForm from app.main.views.index import error from app.main.views.verify import activate_user -from app.models.user import InvitedUser, User +from app.models.user import User from app.utils import hide_from_search_engines from app.utils.login import is_safe_redirect_url from app.utils.time import is_less_than_days_ago @@ -129,6 +117,16 @@ def verify_email(user, redirect_url): ) +def _handle_e2e_tests(redirect_url): + current_app.logger.warning("E2E TESTS ARE ENABLED.") + current_app.logger.warning( + "If you are getting a 404 on signin, comment out E2E vars in .env file!" + ) + user = user_api_client.get_user_by_email(os.getenv("NOTIFY_E2E_TEST_EMAIL")) + activate_user(user["id"]) + return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url)) + + @main.route("/sign-in", methods=(["GET", "POST"])) @hide_from_search_engines def sign_in(): @@ -146,67 +144,60 @@ def sign_in(): redirect_url = request.args.get("next") if os.getenv("NOTIFY_E2E_TEST_EMAIL"): - current_app.logger.warning("E2E TESTS ARE ENABLED.") - current_app.logger.warning( - "If you are getting a 404 on signin, comment out E2E vars in .env file!" - ) - user = user_api_client.get_user_by_email(os.getenv("NOTIFY_E2E_TEST_EMAIL")) - activate_user(user["id"]) - return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url)) + return _handle_e2e_tests(redirect_url) - current_app.logger.info(f"current user is {current_user}") if current_user and current_user.is_authenticated: if redirect_url and is_safe_redirect_url(redirect_url): return redirect(redirect_url) return redirect(url_for("main.show_accounts_or_dashboard")) - form = LoginForm() - current_app.logger.info("Got the login form") - password_reset_url = url_for(".forgot_password", next=request.args.get("next")) + # form = LoginForm() + # current_app.logger.info("Got the login form") + # password_reset_url = url_for(".forgot_password", next=request.args.get("next")) - if form.validate_on_submit(): - user = User.from_email_address_and_password_or_none( - form.email_address.data, form.password.data - ) + # if form.validate_on_submit(): + # user = User.from_email_address_and_password_or_none( + # form.email_address.data, form.password.data + # ) - if user: - # add user to session to mark us as in the process of signing the user in - session["user_details"] = {"email": user.email_address, "id": user.id} + # if user: + # # add user to session to mark us as in the process of signing the user in + # session["user_details"] = {"email": user.email_address, "id": user.id} - if user.state == "pending": - return redirect( - url_for("main.resend_email_verification", next=redirect_url) - ) + # if user.state == "pending": + # return redirect( + # url_for("main.resend_email_verification", next=redirect_url) + # ) - if user.is_active: - if session.get("invited_user_id"): - invited_user = InvitedUser.from_session() - if user.email_address.lower() != invited_user.email_address.lower(): - flash("You cannot accept an invite for another person.") - session.pop("invited_user_id", None) - abort(403) - else: - invited_user.accept_invite() + # if user.is_active: + # if session.get("invited_user_id"): + # invited_user = InvitedUser.from_session() + # if user.email_address.lower() != invited_user.email_address.lower(): + # flash("You cannot accept an invite for another person.") + # session.pop("invited_user_id", None) + # abort(403) + # else: + # invited_user.accept_invite() - user.send_login_code() + # user.send_login_code() - if user.sms_auth: - return redirect(url_for(".two_factor_sms", next=redirect_url)) + # if user.sms_auth: + # return redirect(url_for(".two_factor_sms", next=redirect_url)) - if user.email_auth: - return redirect( - url_for(".two_factor_email_sent", next=redirect_url) - ) + # if user.email_auth: + # return redirect( + # url_for(".two_factor_email_sent", next=redirect_url) + # ) - # Vague error message for login in case of user not known, locked, inactive or password not verified - flash( - Markup( - ( - f"The email address or password you entered is incorrect." - f" Forgot your password?" - ) - ) - ) + # # Vague error message for login in case of user not known, locked, inactive or password not verified + # flash( + # Markup( + # ( + # f"The email address or password you entered is incorrect." + # f" Forgot your password?" + # ) + # ) + # ) other_device = current_user.logged_in_elsewhere() @@ -222,10 +213,10 @@ def sign_in(): url = url.replace("STATE", token) return render_template( "views/signin.html", - form=form, + # form=form, again=bool(redirect_url), other_device=other_device, - password_reset_url=password_reset_url, + # password_reset_url=password_reset_url, initial_signin_url=url, ) diff --git a/poetry.lock b/poetry.lock index 69007e3b5..d9cef6ee5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1278,6 +1278,7 @@ files = [ {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0"}, {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1"}, {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6241d4eee5f89453307c2f2bfa03b50362052ca0af1efecf9fef9a41a22bb4f"}, {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536"}, {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9"}, {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218"}, @@ -1594,6 +1595,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index e89cb6e7b..0b0638382 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -134,116 +134,7 @@ def test_logged_in_user_doesnt_do_evil_redirect(client_request): ) -@pytest.mark.parametrize( - "redirect_url", - [ - None, - f"/services/{SERVICE_ONE_ID}/templates", - ], -) -@pytest.mark.parametrize( - ("email_address", "password"), - [ - ("valid@example.gsa.gov", "val1dPassw0rd!"), - (" valid@example.gsa.gov ", " val1dPassw0rd! "), - ], -) -def test_process_sms_auth_sign_in_return_2fa_template( - client_request, - api_user_active, - mock_send_verify_code, - mock_get_user, - mock_get_user_by_email, - mock_verify_password, - email_address, - password, - redirect_url, -): - client_request.logout() - client_request.post( - "main.sign_in", - next=redirect_url, - _data={ - "email_address": email_address, - "password": password, - }, - _expected_redirect=url_for(".two_factor_sms", next=redirect_url), - ) - mock_verify_password.assert_called_with(api_user_active["id"], password) - mock_get_user_by_email.assert_called_with("valid@example.gsa.gov") - - -@pytest.mark.parametrize( - "redirect_url", - [ - None, - f"/services/{SERVICE_ONE_ID}/templates", - ], -) -def test_process_email_auth_sign_in_return_2fa_template( - client_request, - api_user_active_email_auth, - mock_send_verify_code, - mock_verify_password, - mocker, - redirect_url, -): - client_request.logout() - mocker.patch( - "app.user_api_client.get_user", return_value=api_user_active_email_auth - ) - mocker.patch( - "app.user_api_client.get_user_by_email", return_value=api_user_active_email_auth - ) - - client_request.post( - "main.sign_in", - next=redirect_url, - _data={ - "email_address": "valid@example.gsa.gov", - "password": "val1dPassw0rd!", - }, - _expected_redirect=url_for(".two_factor_email_sent", next=redirect_url), - ) - - mock_send_verify_code.assert_called_with( - api_user_active_email_auth["id"], "email", None, redirect_url - ) - mock_verify_password.assert_called_with( - api_user_active_email_auth["id"], "val1dPassw0rd!" - ) - - -def test_should_return_locked_out_true_when_user_is_locked( - client_request, - mock_get_user_by_email_locked, -): - client_request.logout() - page = client_request.post( - "main.sign_in", - _data={ - "email_address": "valid@example.gsa.gov", - "password": "whatIsMyPassword!", - }, - _expected_status=200, - ) - assert "The email address or password you entered is incorrect" in page.text - - -def test_should_return_200_when_user_does_not_exist( - client_request, - mock_get_user_by_email_not_found, -): - client_request.logout() - page = client_request.post( - "main.sign_in", - _data={"email_address": "notfound@gsa.gov", "password": "doesNotExist!"}, - _expected_status=200, - ) - - assert "The email address or password you entered is incorrect" in page.text - - +@pytest.mark.skip("TODO is this still relevant post login.gov switch?") def test_should_return_redirect_when_user_is_pending( client_request, mock_get_user_by_email_pending, @@ -273,6 +164,7 @@ def test_should_return_redirect_when_user_is_pending( f"/services/{SERVICE_ONE_ID}/templates", ], ) +@pytest.mark.skip("TODO is this still relevant post login.gov switch?") def test_should_attempt_redirect_when_user_is_pending( client_request, mock_get_user_by_email_pending, mock_verify_password, redirect_url ): @@ -288,37 +180,7 @@ def test_should_attempt_redirect_when_user_is_pending( ) -def test_email_address_is_treated_case_insensitively_when_signing_in_as_invited_user( - client_request, - mocker, - mock_verify_password, - api_user_active, - sample_invite, - mock_accept_invite, - mock_send_verify_code, - mock_get_invited_user_by_id, -): - client_request.logout() - sample_invite["email_address"] = "TEST@user.gsa.gov" - - mocker.patch( - "app.models.user.User.from_email_address_and_password_or_none", - return_value=User(api_user_active), - ) - - with client_request.session_transaction() as session: - session["invited_user_id"] = sample_invite["id"] - - client_request.post( - "main.sign_in", - _data={"email_address": "test@user.gsa.gov", "password": "val1dPassw0rd!"}, - ) - - assert mock_accept_invite.called - assert mock_send_verify_code.called - mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) - - +@pytest.mark.skip("TODO move this to register and update with login.gov") def test_when_signing_in_as_invited_user_you_cannot_accept_an_invite_for_another_email_address( client_request, mocker, From 78e8dc95fe7c17c7936c9167e84462a8b8070161 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 May 2024 13:12:27 -0700 Subject: [PATCH 06/14] fix tests --- app/main/views/register.py | 70 +++++++++++---------------- tests/app/main/views/test_register.py | 37 ++++++++------ 2 files changed, 49 insertions(+), 58 deletions(-) diff --git a/app/main/views/register.py b/app/main/views/register.py index 90b340b1c..5bd4d08c7 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -25,7 +25,6 @@ from app.main.forms import ( ) from app.main.views import sign_in from app.main.views.verify import activate_user -from app.models.service import Service from app.models.user import InvitedOrgUser, InvitedUser, User from app.utils import hide_from_search_engines, hilite @@ -156,41 +155,12 @@ def set_up_your_profile(): state = request.args.get("state") login_gov_error = request.args.get("error") if code and state: - _handle_login_dot_gov_invite(code, state) + _handle_login_dot_gov_invite(code, state, form) elif login_gov_error: current_app.logger.error(f"login.gov error: {login_gov_error}") raise Exception(f"Could not login with login.gov {login_gov_error}") # end login.gov - # create the user - # TODO we have to provide something for password until that column goes away - # TODO ideally we would set the user's preferred timezone here as well - - user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email) - if user is None: - user = User.register( - name=form.name.data, - email_address=user_email, - mobile_number=form.mobile_number.data, - password=str(uuid.uuid4()), - auth_type="sms_auth", - ) - - # activate the user - user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email) - activate_user(user["id"]) - usr = User.from_id(user["id"]) - usr.add_to_service( - invited_service.id, - invite_data["permissions"], - invite_data["folder_permissions"], - invite_data["from_user_id"], - ) - current_app.logger.debug( - hilite(f"Added user {usr.email_address} to service {invited_service.name}") - ) - return redirect(url_for("main.show_accounts_or_dashboard")) - return render_template("views/set-up-your-profile.html", form=form) @@ -208,26 +178,16 @@ def invited_user_accept_invite(invited_user_id): invited_user.accept_invite() -def _handle_login_dot_gov_invite(code, state): +def _handle_login_dot_gov_invite(code, state, form): access_token = sign_in._get_access_token(code, state) - print(f"access token {access_token}") user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token) - print(f"user_email {user_email} user_uuid {user_uuid}") - print(f"state = {state}") invite_data = state.encode("utf8") - print(f"encoded invite data {invite_data}") invite_data = base64.b64decode(invite_data) - print(f"decoded invite data = {invite_data}") invite_data = json.loads(invite_data) - print(f"final invite data {invite_data}") - invited_service = Service.from_id(invite_data["service_id"]) - print(f"invited service {invited_service}") invited_user_id = invite_data["invited_user_id"] invited_user_email_address = get_invited_user_email_address(invited_user_id) - print(f"invited_user_email_address = {invited_user_email_address}") if user_email.lower() != invited_user_email_address.lower(): - print(f"HITTING THE FLASH") flash("You cannot accept an invite for another person.") session.pop("invited_user_id", None) abort(403) @@ -239,3 +199,29 @@ def _handle_login_dot_gov_invite(code, state): ) ) current_app.logger.debug(hilite("ACCEPTED INVITE")) + user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email) + if user is None: + user = User.register( + name=form.name.data, + email_address=user_email, + mobile_number=form.mobile_number.data, + password=str(uuid.uuid4()), + auth_type="sms_auth", + ) + + # activate the user + user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email) + activate_user(user["id"]) + usr = User.from_id(user["id"]) + usr.add_to_service( + invite_data["service_id"], + invite_data["permissions"], + invite_data["folder_permissions"], + invite_data["from_user_id"], + ) + current_app.logger.debug( + hilite( + f"Added user {usr.email_address} to service {invite_data['service_id']}" + ) + ) + return redirect(url_for("main.show_accounts_or_dashboard")) diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index b82f7dde1..2a13ebb9d 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -5,8 +5,9 @@ from unittest.mock import ANY import pytest from flask import url_for +from app.main.forms import RegisterUserForm from app.main.views.register import _handle_login_dot_gov_invite -from app.models.user import InvitedUser, User +from app.models.user import User from tests.conftest import normalize_spaces @@ -549,11 +550,6 @@ def test_handle_login_dot_gov_invite_bad_email(client_request, mocker): return_value=["fake@fake.gov", "12345"], ) - mocker.patch( - "app.main.views.register.Service.from_id", - return_value={"service_from_id"}, - ) - mocker.patch( "app.main.views.register.get_invited_user_email_address", return_value="boo@fake.gov", @@ -570,8 +566,7 @@ def test_handle_login_dot_gov_invite_bad_email(client_request, mocker): invite_data = invite_data.encode("utf8") invite_data = base64.b64encode(invite_data) invite_data = invite_data.decode("utf8") - print(f"sending this as state {invite_data}") - _handle_login_dot_gov_invite("code", invite_data) + _handle_login_dot_gov_invite("code", invite_data, RegisterUserForm()) mock_flash.assert_called_once_with( "You cannot accept an invite for another person." ) @@ -590,23 +585,33 @@ def test_handle_login_dot_gov_invite_good_email(client_request, mocker): return_value=["fake@fake.gov", "12345"], ) - mocker.patch( - "app.main.views.register.Service.from_id", - return_value={"service_from_id"}, - ) - mocker.patch( "app.main.views.register.get_invited_user_email_address", return_value="fake@fake.gov", ) + mocker.patch( + "app.main.views.register.user_api_client.get_user_by_uuid_or_email", + return_value={"id": "abc"}, + ) + + mock_user = mocker.patch( + "app.main.views.register.User.add_to_service", + ) + mock_accept = mocker.patch("app.main.views.register.invited_user_accept_invite") - invite_data = {"service_id": "service", "invited_user_id": "invited_user"} + invite_data = { + "service_id": "service", + "invited_user_id": "invited_user", + "permissions": ["manage_everything"], + "folder_permissions": [], + "from_user_id": "xyz", + } invite_data = json.dumps(invite_data) invite_data = invite_data.encode("utf8") invite_data = base64.b64encode(invite_data) invite_data = invite_data.decode("utf8") - print(f"sending this as state {invite_data}") - _handle_login_dot_gov_invite("code", invite_data) + _handle_login_dot_gov_invite("code", invite_data, RegisterUserForm()) mock_accept.assert_called_once() + mock_user.assert_called_once_with("service", ["manage_everything"], [], "xyz") From 39b0a4ff5226a0a262f459b43fb93f39df93a832 Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Tue, 7 May 2024 09:56:34 -0400 Subject: [PATCH 07/14] Update several dependencies from Dependabot This changeset updates a few dependencies that Dependabot flagged for updates. We cannot merge the Dependabot PRs at the moment due to E2E test compatability issues. Signed-off-by: Carlo Costino --- poetry.lock | 24 ++++++++++++------------ pyproject.toml | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/poetry.lock b/poetry.lock index f7962fa58..bb92d1c04 100644 --- a/poetry.lock +++ b/poetry.lock @@ -149,13 +149,13 @@ css = ["tinycss2 (>=1.1.0,<1.3)"] [[package]] name = "blinker" -version = "1.8.1" +version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" files = [ - {file = "blinker-1.8.1-py3-none-any.whl", hash = "sha256:5f1cdeff423b77c31b89de0565cd03e5275a03028f44b2b15f912632a58cced6"}, - {file = "blinker-1.8.1.tar.gz", hash = "sha256:da44ec748222dcd0105ef975eed946da197d5bdf8bafb6aa92f5bc89da63fa25"}, + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] @@ -1895,18 +1895,18 @@ pip = "*" [[package]] name = "pip-audit" -version = "2.7.2" +version = "2.7.3" description = "A tool for scanning Python environments for known vulnerabilities" optional = false python-versions = ">=3.8" files = [ - {file = "pip_audit-2.7.2-py3-none-any.whl", hash = "sha256:49907430115baacb8bb7ffc1a2b689acfeac9d8534a43bffad3c73f8d8b32d52"}, - {file = "pip_audit-2.7.2.tar.gz", hash = "sha256:a12905e42dd452f43a2dbf895606d59c35348deed27b8cbaff8516423576fdfb"}, + {file = "pip_audit-2.7.3-py3-none-any.whl", hash = "sha256:46a11faee3323f76adf7987de8171daeb660e8f57d8088cc27fb1c1e5c7747b0"}, + {file = "pip_audit-2.7.3.tar.gz", hash = "sha256:08891bbf179bffe478521f150818112bae998424f58bf9285c0078965aef38bc"}, ] [package.dependencies] CacheControl = {version = ">=0.13.0", extras = ["filecache"]} -cyclonedx-python-lib = ">=5,<7" +cyclonedx-python-lib = ">=5,<8" html5lib = ">=1.1" packaging = ">=23.0.0" pip-api = ">=0.0.28" @@ -1918,7 +1918,7 @@ toml = ">=0.10" [package.extras] dev = ["build", "bump (>=1.3.2)", "pip-audit[doc,lint,test]"] doc = ["pdoc"] -lint = ["interrogate", "mypy", "ruff (<0.2.3)", "types-html5lib", "types-requests", "types-toml"] +lint = ["interrogate", "mypy", "ruff (<0.4.3)", "setuptools", "types-html5lib", "types-requests", "types-toml"] test = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)", "pretend", "pytest", "pytest-cov"] [[package]] @@ -2334,13 +2334,13 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-playwright" -version = "0.4.4" +version = "0.5.0" description = "A pytest wrapper with fixtures for Playwright to automate web browsers" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-playwright-0.4.4.tar.gz", hash = "sha256:5488db4cc49028491c5130af0a2bb6b1d0b222a202217f6d14491d4c9aa67ff9"}, - {file = "pytest_playwright-0.4.4-py3-none-any.whl", hash = "sha256:df306f3a60a8631a3cfde1b95a2ed5a89203a3408dfa1154de049ca7de87c90b"}, + {file = "pytest-playwright-0.5.0.tar.gz", hash = "sha256:f9f5ae8ade2f773e6e2cd85ec6bfff2ab287f7943108b3956fe5971324151622"}, + {file = "pytest_playwright-0.5.0-py3-none-any.whl", hash = "sha256:b382c870384419c025d66aea14518bab71fb9e79917d4808692cde70d8c5216a"}, ] [package.dependencies] @@ -3014,4 +3014,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "a511e7d8f022826d71a6a1bb47cc7587d81c7ad331f3a6c26a8538b1390929e3" +content-hash = "794a77ce83a38682d5a7def4212fe212cbec48573b4fdd1e2dad3c4669fa98ac" diff --git a/pyproject.toml b/pyproject.toml index 85d2fd4a2..04102164e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ package-mode = false python = "^3.12.2" ago = "~=0.0.95" beautifulsoup4 = "^4.12.3" -blinker = "~=1.7" +blinker = "~=1.8" exceptiongroup = "==1.2.1" flask = "~=3.0" flask-basicauth = "~=0.2" @@ -56,7 +56,7 @@ pre-commit = "^3.6.0" pytest = "^8.1.1" pytest-env = "^1.1.3" pytest-mock = "^3.14.0" -pytest-playwright = "^0.4.4" +pytest-playwright = "^0.5.0" pytest-xdist = "^3.5.0" radon = "^6.0.1" requests-mock = "^1.11.0" From 125ad7a2f75fd31275afd6fc8743e0e7dd3182e4 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Tue, 7 May 2024 11:11:12 -0700 Subject: [PATCH 08/14] remove logged in elsewhere check --- app/models/user.py | 4 ++- tests/app/main/views/test_sign_in.py | 45 ---------------------------- 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/app/models/user.py b/app/models/user.py index 7e9f10632..3261dec01 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -141,7 +141,9 @@ class User(JSONModel, UserMixin): ) def logged_in_elsewhere(self): - return session.get("current_session_id") != self.current_session_id + # This check is deprecated due to the transition to using login.gov. + return False + # return session.get("current_session_id") != self.current_session_id def activate(self): if self.is_pending: diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index 0b0638382..135f4a5ba 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -47,21 +47,6 @@ def test_sign_in_explains_session_timeout(client_request): ) -def test_sign_in_explains_other_browser(client_request, api_user_active, mocker): - api_user_active["current_session_id"] = str(uuid.UUID(int=1)) - mocker.patch("app.user_api_client.get_user", return_value=api_user_active) - - with client_request.session_transaction() as session: - session["current_session_id"] = str(uuid.UUID(int=2)) - - page = client_request.get("main.sign_in", next="/foo") - - assert ( - "We signed you out because you logged in to Notify on another device" - in page.text - ) - - def test_doesnt_redirect_to_sign_in_if_no_session_info( client_request, api_user_active, @@ -78,36 +63,6 @@ def test_doesnt_redirect_to_sign_in_if_no_session_info( client_request.get("main.add_service") -@pytest.mark.parametrize( - ("db_sess_id", "cookie_sess_id"), - [ - (None, None), - (None, uuid.UUID(int=1)), # BAD - cookie doesn't match db - ( - uuid.UUID(int=1), - None, - ), # BAD - has used other browsers before but this is a brand new browser with no cookie - ( - uuid.UUID(int=1), - uuid.UUID(int=2), - ), # BAD - this person has just signed in on a different browser - ], -) -def test_redirect_to_sign_in_if_logged_in_from_other_browser( - client_request, api_user_active, mocker, db_sess_id, cookie_sess_id -): - api_user_active["current_session_id"] = db_sess_id - mocker.patch("app.user_api_client.get_user", return_value=api_user_active) - with client_request.session_transaction() as session: - session["current_session_id"] = str(cookie_sess_id) - - client_request.get( - "main.choose_account", - _expected_status=302, - _expected_redirect=url_for("main.sign_in", next="/accounts"), - ) - - def test_logged_in_user_redirects_to_account(client_request): client_request.get( "main.sign_in", From 64aa45dcaa4d9ec189ff99794bf2c36f0dfa08b0 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Tue, 7 May 2024 12:37:04 -0700 Subject: [PATCH 09/14] remove commented out code --- app/main/views/sign_in.py | 50 --------------------------------------- 1 file changed, 50 deletions(-) diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index 7af9ce8ca..f9873c656 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -151,54 +151,6 @@ def sign_in(): return redirect(redirect_url) return redirect(url_for("main.show_accounts_or_dashboard")) - # form = LoginForm() - # current_app.logger.info("Got the login form") - # password_reset_url = url_for(".forgot_password", next=request.args.get("next")) - - # if form.validate_on_submit(): - # user = User.from_email_address_and_password_or_none( - # form.email_address.data, form.password.data - # ) - - # if user: - # # add user to session to mark us as in the process of signing the user in - # session["user_details"] = {"email": user.email_address, "id": user.id} - - # if user.state == "pending": - # return redirect( - # url_for("main.resend_email_verification", next=redirect_url) - # ) - - # if user.is_active: - # if session.get("invited_user_id"): - # invited_user = InvitedUser.from_session() - # if user.email_address.lower() != invited_user.email_address.lower(): - # flash("You cannot accept an invite for another person.") - # session.pop("invited_user_id", None) - # abort(403) - # else: - # invited_user.accept_invite() - - # user.send_login_code() - - # if user.sms_auth: - # return redirect(url_for(".two_factor_sms", next=redirect_url)) - - # if user.email_auth: - # return redirect( - # url_for(".two_factor_email_sent", next=redirect_url) - # ) - - # # Vague error message for login in case of user not known, locked, inactive or password not verified - # flash( - # Markup( - # ( - # f"The email address or password you entered is incorrect." - # f" Forgot your password?" - # ) - # ) - # ) - other_device = current_user.logged_in_elsewhere() token = generate_token( @@ -213,10 +165,8 @@ def sign_in(): url = url.replace("STATE", token) return render_template( "views/signin.html", - # form=form, again=bool(redirect_url), other_device=other_device, - # password_reset_url=password_reset_url, initial_signin_url=url, ) From 9c40a109003b43b3171f9f527859698127d73568 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Tue, 7 May 2024 13:58:59 -0700 Subject: [PATCH 10/14] cleanup --- app/main/views/register.py | 62 +++----- tests/app/main/views/test_accept_invite.py | 124 ---------------- tests/app/main/views/test_register.py | 156 --------------------- tests/app/test_navigation.py | 1 - 4 files changed, 20 insertions(+), 323 deletions(-) diff --git a/app/main/views/register.py b/app/main/views/register.py index 5bd4d08c7..1df991733 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -17,9 +17,8 @@ from flask_login import current_user from app import user_api_client from app.main import main -from app.main.forms import ( +from app.main.forms import ( # RegisterUserFromInviteForm, RegisterUserForm, - RegisterUserFromInviteForm, RegisterUserFromOrgInviteForm, SetupUserProfileForm, ) @@ -43,39 +42,10 @@ def register(): return render_template("views/register.html", form=form) -@main.route("/register-from-invite", methods=["GET", "POST"]) -# TODO This is deprecated, we are now handling invites in the -# login.gov workflow -def register_from_invite(): - invited_user = InvitedUser.from_session() - if not invited_user: - abort(404) - - form = RegisterUserFromInviteForm(invited_user) - - if form.validate_on_submit(): - if ( - form.service.data != invited_user.service - or form.email_address.data != invited_user.email_address - ): - abort(400) - _do_registration(form, send_email=False, send_sms=invited_user.sms_auth) - invited_user.accept_invite() - if invited_user.sms_auth: - return redirect(url_for("main.verify")) - else: - # we've already proven this user has email because they clicked the invite link, - # so just activate them straight away - return activate_user(session["user_details"]["id"]) - - return render_template( - "views/register-from-invite.html", invited_user=invited_user, form=form - ) - - @main.route("/register-from-org-invite", methods=["GET", "POST"]) # TODO This is deprecated, we are now handling invites in the -# login.gov workflow +# login.gov workflow. Leaving it here until we write the new +# org registration. def register_from_org_invite(): invited_org_user = InvitedOrgUser.from_session() if not invited_org_user: @@ -178,27 +148,35 @@ def invited_user_accept_invite(invited_user_id): invited_user.accept_invite() +def debug_msg(msg): + current_app.logger.debug(hilite(msg)) + + def _handle_login_dot_gov_invite(code, state, form): access_token = sign_in._get_access_token(code, state) + debug_msg("Got the access token for login.gov") user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token) + debug_msg( + f"Got the user_email {user_email} and user_uuid {user_uuid} from login.gov" + ) invite_data = state.encode("utf8") invite_data = base64.b64decode(invite_data) invite_data = json.loads(invite_data) invited_user_id = invite_data["invited_user_id"] invited_user_email_address = get_invited_user_email_address(invited_user_id) + debug_msg(f"email address from the invite_date is {invited_user_email_address}") if user_email.lower() != invited_user_email_address.lower(): + debug_msg("invited user email did not match expected email, abort(403)") flash("You cannot accept an invite for another person.") session.pop("invited_user_id", None) abort(403) else: invited_user_accept_invite() - current_app.logger.debug( - hilite( - f"INVITED USER {invited_user_email_address} to service {invite_data['service_id']}" - ) + debug_msg( + f"invited user {invited_user_email_address} to service {invite_data['service_id']}" ) - current_app.logger.debug(hilite("ACCEPTED INVITE")) + debug_msg("accepted invite") user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email) if user is None: user = User.register( @@ -208,10 +186,12 @@ def _handle_login_dot_gov_invite(code, state, form): password=str(uuid.uuid4()), auth_type="sms_auth", ) + debug_msg(f"registered user {form.name.data} with email {user_email}") # activate the user user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email) activate_user(user["id"]) + debug_msg("activated user") usr = User.from_id(user["id"]) usr.add_to_service( invite_data["service_id"], @@ -219,9 +199,7 @@ def _handle_login_dot_gov_invite(code, state, form): invite_data["folder_permissions"], invite_data["from_user_id"], ) - current_app.logger.debug( - hilite( - f"Added user {usr.email_address} to service {invite_data['service_id']}" - ) + debug_msg( + f"Added user {usr.email_address} to service {invite_data['service_id']}" ) return redirect(url_for("main.show_accounts_or_dashboard")) diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index 38202dfb2..a6d622c12 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -431,71 +431,6 @@ def test_existing_signed_out_user_accept_invite_redirects_to_sign_in( ) -@pytest.mark.usefixtures("_mock_no_users_for_service") -def test_new_user_accept_invite_calls_api_and_redirects_to_registration( - client_request, - service_one, - mock_check_invite_token, - mock_dont_get_user_by_email, - mock_add_user_to_service, - mock_get_service, - mocker, -): - client_request.logout() - client_request.get( - "main.accept_invite", - token="thisisnotarealtoken", - _expected_redirect="/register-from-invite", - ) - - mock_check_invite_token.assert_called_with("thisisnotarealtoken") - mock_dont_get_user_by_email.assert_called_with("invited_user@test.gsa.gov") - - -@pytest.mark.usefixtures("_mock_no_users_for_service") -def test_new_user_accept_invite_calls_api_and_views_registration_page( - client_request, - service_one, - sample_invite, - mock_check_invite_token, - mock_dont_get_user_by_email, - mock_get_invited_user_by_id, - mock_add_user_to_service, - mock_get_service, - mocker, -): - client_request.logout() - page = client_request.get( - "main.accept_invite", - token="thisisnotarealtoken", - _follow_redirects=True, - ) - - mock_check_invite_token.assert_called_with("thisisnotarealtoken") - mock_dont_get_user_by_email.assert_called_with("invited_user@test.gsa.gov") - mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) - - assert page.h1.string.strip() == "Create an account" - - assert normalize_spaces(page.select_one("main p").text) == ( - "Your account will be created with this email address: " - "invited_user@test.gsa.gov" - ) - - form = page.find("form") - name = form.find("input", id="name") - password = form.find("input", id="password") - service = form.find("input", type="hidden", id="service") - email = form.find("input", type="hidden", id="email_address") - - assert email - assert email.attrs["value"] == "invited_user@test.gsa.gov" - assert name - assert password - assert service - assert service.attrs["value"] == service_one["id"] - - def test_cancelled_invited_user_accepts_invited_redirect_to_cancelled_invitation( client_request, mock_get_user, @@ -562,65 +497,6 @@ def test_new_user_accept_invite_with_malformed_token( ) -@pytest.mark.usefixtures("_mock_no_users_for_service") -def test_new_user_accept_invite_completes_new_registration_redirects_to_verify( - client_request, - service_one, - sample_invite, - api_user_active, - mock_check_invite_token, - mock_dont_get_user_by_email, - mock_email_is_not_already_in_use, - mock_register_user, - mock_send_verify_code, - mock_get_invited_user_by_id, - mock_accept_invite, - mock_add_user_to_service, - mock_get_service, - mocker, -): - client_request.logout() - expected_redirect_location = "/register-from-invite" - - client_request.get( - "main.accept_invite", - token="thisisnotarealtoken", - _expected_redirect=expected_redirect_location, - ) - with client_request.session_transaction() as session: - assert session.get("invited_user_id") == sample_invite["id"] - - data = { - "service": sample_invite["service"], - "email_address": sample_invite["email_address"], - "from_user": sample_invite["from_user"], - "password": "longpassword", - "mobile_number": "+12027890123", - "name": "Invited User", - "auth_type": "email_auth", - } - - expected_redirect_location = "/verify" - client_request.post( - "main.register_from_invite", - _data=data, - _expected_redirect=expected_redirect_location, - ) - - mock_send_verify_code.assert_called_once_with(ANY, "sms", data["mobile_number"]) - mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) - - mock_register_user.assert_called_with( - data["name"], - data["email_address"], - data["mobile_number"], - data["password"], - data["auth_type"], - ) - - assert mock_accept_invite.call_count == 1 - - def test_signed_in_existing_user_cannot_use_anothers_invite( client_request, mocker, diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index 2a13ebb9d..b491a1b53 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -219,144 +219,6 @@ def test_register_with_existing_email_sends_emails( ) -@pytest.mark.parametrize( - ("email_address", "expected_value"), - [ - ("first.last@example.com", "First Last"), - ("first.middle.last@example.com", "First Middle Last"), - ("first.m.last@example.com", "First Last"), - ("first.last-last@example.com", "First Last-Last"), - ("first.o'last@example.com", "First O’Last"), - ("first.last+testing@example.com", "First Last"), - ("first.last+testing+testing@example.com", "First Last"), - ("first.last6@example.com", "First Last"), - ("first.last.212@example.com", "First Last"), - ("first.2.last@example.com", "First Last"), - ("first.2b.last@example.com", "First Last"), - ("first.1.2.3.last@example.com", "First Last"), - ("first.last.1.2.3@example.com", "First Last"), - # Instances where we can’t make a good-enough guess: - ("example123@example.com", None), - ("f.last@example.com", None), - ("f.m.last@example.com", None), - ], -) -def test_shows_name_on_registration_page_from_invite( - client_request, - fake_uuid, - email_address, - expected_value, - sample_invite, - mock_get_invited_user_by_id, -): - sample_invite["email_address"] = email_address - with client_request.session_transaction() as session: - session["invited_user_id"] = sample_invite - - page = client_request.get("main.register_from_invite") - assert page.select_one("input[name=name]").get("value") == expected_value - - -def test_shows_hidden_email_address_on_registration_page_from_invite( - client_request, - fake_uuid, - sample_invite, - mock_get_invited_user_by_id, -): - with client_request.session_transaction() as session: - session["invited_user_id"] = sample_invite - - page = client_request.get("main.register_from_invite") - assert normalize_spaces(page.select_one("main p").text) == ( - "Your account will be created with this email address: invited_user@test.gsa.gov" - ) - hidden_input = page.select_one("form .usa-sr-only input") - for attr, value in ( - ("type", "email"), - ("name", "username"), - ("id", "username"), - ("value", "invited_user@test.gsa.gov"), - ("disabled", "disabled"), - ("tabindex", "-1"), - ("aria-hidden", "true"), - ("autocomplete", "username"), - ): - assert hidden_input[attr] == value - - -@pytest.mark.parametrize( - "extra_data", - [ - {}, - # The username field is present in the page but the POST request - # should ignore it - {"username": "invited@user.com"}, - {"username": "anythingelse@example.com"}, - ], -) -def test_register_from_invite( - client_request, - fake_uuid, - mock_email_is_not_already_in_use, - mock_register_user, - mock_send_verify_code, - mock_accept_invite, - mock_get_invited_user_by_id, - sample_invite, - extra_data, -): - client_request.logout() - with client_request.session_transaction() as session: - session["invited_user_id"] = sample_invite["id"] - client_request.post( - "main.register_from_invite", - _data=dict( - name="Registered in another Browser", - email_address=sample_invite["email_address"], - mobile_number="+12024900460", - service=sample_invite["service"], - password="somreallyhardthingtoguess", - auth_type="sms_auth", - **extra_data, - ), - _expected_redirect=url_for("main.verify"), - ) - mock_register_user.assert_called_once_with( - "Registered in another Browser", - sample_invite["email_address"], - "+12024900460", - "somreallyhardthingtoguess", - "sms_auth", - ) - mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) - - -def test_register_from_invite_when_user_registers_in_another_browser( - client_request, - api_user_active, - mock_get_user_by_email, - mock_accept_invite, - mock_get_invited_user_by_id, - sample_invite, -): - client_request.logout() - sample_invite["email_address"] = api_user_active["email_address"] - with client_request.session_transaction() as session: - session["invited_user_id"] = sample_invite["id"] - client_request.post( - "main.register_from_invite", - _data={ - "name": "Registered in another Browser", - "email_address": api_user_active["email_address"], - "mobile_number": api_user_active["mobile_number"], - "service": sample_invite["service"], - "password": "somreallyhardthingtoguess", - "auth_type": "sms_auth", - }, - _expected_redirect=url_for("main.verify"), - ) - - @pytest.mark.parametrize( "invite_email_address", ["gov-user@gsa.gov", "non-gov-user@example.com"] ) @@ -520,24 +382,6 @@ def test_cannot_register_with_sms_auth_and_missing_mobile_number( assert err.attrs["data-error-label"] == "mobile_number" -def test_register_from_invite_form_doesnt_show_mobile_number_field_if_email_auth( - client_request, - sample_invite, - mock_get_invited_user_by_id, -): - client_request.logout() - sample_invite["auth_type"] = "email_auth" - with client_request.session_transaction() as session: - session["invited_user_id"] = sample_invite["id"] - - page = client_request.get("main.register_from_invite") - - assert ( - page.find("input", attrs={"name": "auth_type"}).attrs["value"] == "email_auth" - ) - assert page.find("input", attrs={"name": "mobile_number"}) is None - - def test_handle_login_dot_gov_invite_bad_email(client_request, mocker): mocker.patch( diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 4634a65cf..6f1cf58eb 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -146,7 +146,6 @@ EXCLUDED_ENDPOINTS = tuple( "received_text_messages_callback", "redact_template", "register", - "register_from_invite", "register_from_org_invite", "registration_continue", "remove_user_from_organization", From 1d9457f9a2e978727f0c3ca61b3ee0f6fb1b7ac1 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Tue, 7 May 2024 14:36:21 -0700 Subject: [PATCH 11/14] add debug statements --- app/main/views/register.py | 10 +++++++--- tests/app/main/views/test_register.py | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/main/views/register.py b/app/main/views/register.py index 1df991733..fbcf42a73 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -125,7 +125,7 @@ def set_up_your_profile(): state = request.args.get("state") login_gov_error = request.args.get("error") if code and state: - _handle_login_dot_gov_invite(code, state, form) + return _handle_login_dot_gov_invite(code, state, form) elif login_gov_error: current_app.logger.error(f"login.gov error: {login_gov_error}") raise Exception(f"Could not login with login.gov {login_gov_error}") @@ -153,16 +153,20 @@ def debug_msg(msg): def _handle_login_dot_gov_invite(code, state, form): - + debug_msg(f"enter _handle_login_dot_gov_invite with code {code} state {state}") access_token = sign_in._get_access_token(code, state) debug_msg("Got the access token for login.gov") user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token) debug_msg( f"Got the user_email {user_email} and user_uuid {user_uuid} from login.gov" ) + debug_msg(f"raw state {state}") invite_data = state.encode("utf8") + debug_msg(f"utf8 encoded state {invite_data}") invite_data = base64.b64decode(invite_data) + debug_msg(f"b64 decoded state {invite_data}") invite_data = json.loads(invite_data) + debug_msg(f"final state {invite_data}") invited_user_id = invite_data["invited_user_id"] invited_user_email_address = get_invited_user_email_address(invited_user_id) debug_msg(f"email address from the invite_date is {invited_user_email_address}") @@ -172,7 +176,7 @@ def _handle_login_dot_gov_invite(code, state, form): session.pop("invited_user_id", None) abort(403) else: - invited_user_accept_invite() + invited_user_accept_invite(invited_user_id) debug_msg( f"invited user {invited_user_email_address} to service {invite_data['service_id']}" ) diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index b491a1b53..f6a96d6b0 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -459,3 +459,30 @@ def test_handle_login_dot_gov_invite_good_email(client_request, mocker): _handle_login_dot_gov_invite("code", invite_data, RegisterUserForm()) mock_accept.assert_called_once() mock_user.assert_called_once_with("service", ["manage_everything"], [], "xyz") + + +# Taken from the API project in service_invite/rest.py +def get_user_data_url_safe(data): + data = json.dumps(data) + data = base64.b64encode(data.encode("utf8")) + return data.decode("utf8") + + +def get_decoded(state): + state = state.encode("utf8") + state = base64.b64decode(state) + state = json.loads(state) + return state + + +# Test that we can successfully decode the invited user +# data that is sent in the state param +def test_decode_state(): + invite_data = { + "from_user_id": "abc", + "service_id": "bcd", + "permissions": ["manage_everything"], + "folder_permissions": [], + } + state = get_user_data_url_safe(invite_data) + assert invite_data == get_decoded(state) From e51517347ffa8b1656533d88d10895b28ae64150 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 8 May 2024 11:06:10 -0700 Subject: [PATCH 12/14] convert to test fixture --- tests/app/main/views/test_register.py | 22 +++++++--------------- tests/conftest.py | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index f6a96d6b0..cf4fba5a2 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -461,14 +461,7 @@ def test_handle_login_dot_gov_invite_good_email(client_request, mocker): mock_user.assert_called_once_with("service", ["manage_everything"], [], "xyz") -# Taken from the API project in service_invite/rest.py -def get_user_data_url_safe(data): - data = json.dumps(data) - data = base64.b64encode(data.encode("utf8")) - return data.decode("utf8") - - -def get_decoded(state): +def decode_invite_data(state): state = state.encode("utf8") state = base64.b64decode(state) state = json.loads(state) @@ -477,12 +470,11 @@ def get_decoded(state): # Test that we can successfully decode the invited user # data that is sent in the state param -def test_decode_state(): - invite_data = { - "from_user_id": "abc", - "service_id": "bcd", - "permissions": ["manage_everything"], +def test_decode_state(encoded_invite_data): + assert decode_invite_data(encoded_invite_data) == { "folder_permissions": [], + "from_user_id": "xyz", + "invited_user_id": "invited_user", + "permissions": ["manage_everything"], + "service_id": "service", } - state = get_user_data_url_safe(invite_data) - assert invite_data == get_decoded(state) diff --git a/tests/conftest.py b/tests/conftest.py index ab86e3052..fe1ee0c02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import base64 import copy import json import os @@ -1901,6 +1902,25 @@ def sample_invite(mocker, service_one): ) +@pytest.fixture() +def encoded_invite_data(): + """ + This mimics what API does when it encodes invite data in + service_invite/rest.py + """ + invite_data = { + "service_id": "service", + "invited_user_id": "invited_user", + "permissions": ["manage_everything"], + "folder_permissions": [], + "from_user_id": "xyz", + } + invite_data = json.dumps(invite_data) + invite_data = invite_data.encode("utf8") + invite_data = base64.b64encode(invite_data) + return invite_data.decode("utf8") + + @pytest.fixture() def expired_invite(service_one): id_ = USER_ONE_ID From 852c0e57c8b0f2e2b6b327ee5dd7965a73f76356 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 8 May 2024 11:30:51 -0700 Subject: [PATCH 13/14] convert to test fixture --- app/main/views/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main/views/register.py b/app/main/views/register.py index fbcf42a73..e9e03a76a 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -17,7 +17,7 @@ from flask_login import current_user from app import user_api_client from app.main import main -from app.main.forms import ( # RegisterUserFromInviteForm, +from app.main.forms import ( RegisterUserForm, RegisterUserFromOrgInviteForm, SetupUserProfileForm, From 26988d255fad93a81e8279df26020479b6bc2dc8 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 8 May 2024 12:45:01 -0700 Subject: [PATCH 14/14] remove logged_in_elsewhere as per code review feedback --- app/main/views/sign_in.py | 3 --- app/models/user.py | 11 +---------- tests/app/models/test_user.py | 1 - 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index f9873c656..618a35654 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -151,8 +151,6 @@ def sign_in(): return redirect(redirect_url) return redirect(url_for("main.show_accounts_or_dashboard")) - other_device = current_user.logged_in_elsewhere() - token = generate_token( str(request.remote_addr), current_app.config["SECRET_KEY"], @@ -166,7 +164,6 @@ def sign_in(): return render_template( "views/signin.html", again=bool(redirect_url), - other_device=other_device, initial_signin_url=url, ) diff --git a/app/models/user.py b/app/models/user.py index 3261dec01..932e754c8 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -140,11 +140,6 @@ class User(JSONModel, UserMixin): set_by_id=set_by_id, ) - def logged_in_elsewhere(self): - # This check is deprecated due to the transition to using login.gov. - return False - # return session.get("current_session_id") != self.current_session_id - def activate(self): if self.is_pending: user_data = user_api_client.activate_user(self.id) @@ -198,7 +193,7 @@ class User(JSONModel, UserMixin): @property def is_authenticated(self): - return not self.logged_in_elsewhere() and super(User, self).is_authenticated + return super(User, self).is_authenticated @property def platform_admin(self): @@ -676,10 +671,6 @@ class InvitedOrgUser(JSONModel): class AnonymousUser(AnonymousUserMixin): - # set the anonymous user so that if a new browser hits us we don't error http://stackoverflow.com/a/19275188 - - def logged_in_elsewhere(self): - return False @property def default_organization(self): diff --git a/tests/app/models/test_user.py b/tests/app/models/test_user.py index 8c4c26907..1caf85f4a 100644 --- a/tests/app/models/test_user.py +++ b/tests/app/models/test_user.py @@ -6,7 +6,6 @@ from tests.conftest import SERVICE_ONE_ID, USER_ONE_ID def test_anonymous_user(notify_admin): assert AnonymousUser().is_authenticated is False - assert AnonymousUser().logged_in_elsewhere() is False assert AnonymousUser().default_organization.name is None assert AnonymousUser().default_organization.domains == [] assert AnonymousUser().default_organization.organization_type is None