From f9ae141fa28026bf8d3137e4318502a0f5515145 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 15 May 2024 18:13:00 -0500 Subject: [PATCH 001/111] chore: update docs on tagging a release we have been using annotated tags, so this updates our docs to reflect that. --- docs/deployment/release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deployment/release.md b/docs/deployment/release.md index dcd2a0764..cee6c7e03 100644 --- a/docs/deployment/release.md +++ b/docs/deployment/release.md @@ -87,7 +87,7 @@ git checkout prod git reset --hard origin/prod -git tag YYYY.0M.R +git tag -a YYYY.0M.R git push origin YYYY.0M.R ``` From 66812b6e571867372e3ba35e9907bac48142ceea Mon Sep 17 00:00:00 2001 From: Andy Walker Date: Fri, 17 May 2024 13:00:42 -0700 Subject: [PATCH 002/111] Roadmap revisions - added Improved UX for application error states milestone - shifted milestones for the Benefits admin tool to later in the year. --- docs/enrollment-pathways/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/enrollment-pathways/README.md b/docs/enrollment-pathways/README.md index 29fd0b295..6374e96dd 100644 --- a/docs/enrollment-pathways/README.md +++ b/docs/enrollment-pathways/README.md @@ -30,16 +30,17 @@ timeline Q2
Now : Deploy low-income riders enrollment pathway : Support for expiring benefits (low-income) - : Benefits admin tool (Agency configuration) : Improved UX for agency card enrollment + : Improved UX for application error states %% : Release enhancements to Veterans pathway Q3
Next + : Benefits admin tool (Agency configuration) : Benefits admin tool (Agency users) - : Benefits admin tool (In-person eligibility verification) : Release Medicare cardholder enrollment pathway Q4
Planned + : Benefits admin tool (In-person eligibility verification) : Release riders with disabilities enrollment pathway %% Cal-ITP Benefits Epics (2025) From 734517dce203369a296052c4e2722d2b3be519fc Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Fri, 3 May 2024 15:23:13 +0000 Subject: [PATCH 003/111] feat(enrollment): update TransitAgency and EligibilityType models --- benefits/core/migrations/local_fixtures.json | 21 +++++++++++--------- benefits/core/models.py | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 05abb266e..c66dfd8b2 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -69,7 +69,8 @@ "fields": { "name": "senior", "label": "(MST) Senior Discount", - "group_id": "group123" + "group_id": "group123", + "enrollment_success_template": "enrollment/success--mst.html" } }, { @@ -78,7 +79,8 @@ "fields": { "name": "veteran", "label": "(MST) Veteran Discount", - "group_id": "group123" + "group_id": "group123", + "enrollment_success_template": "enrollment/success--mst.html" } }, { @@ -88,7 +90,8 @@ "name": "courtesy_card", "label": "(MST) Courtesy Card Discount", "group_id": "group123", - "enrollment_index_template": "enrollment/index--agency-card.html" + "enrollment_index_template": "enrollment/index--agency-card.html", + "enrollment_success_template": "enrollment/success--mst-courtesy-card.html" } }, { @@ -97,7 +100,8 @@ "fields": { "name": "senior", "label": "(SacRT) Senior Discount", - "group_id": "group123" + "group_id": "group123", + "enrollment_success_template": "enrollment/success--sacrt.html" } }, { @@ -106,7 +110,8 @@ "fields": { "name": "senior", "label": "(SBMTD) Senior Discount", - "group_id": "group123" + "group_id": "group123", + "enrollment_success_template": "enrollment/success--sbmtd.html" } }, { @@ -116,7 +121,8 @@ "name": "mobility_pass", "label": "(SBMTD) Mobility Pass Discount", "group_id": "group123", - "enrollment_index_template": "enrollment/index--agency-card.html" + "enrollment_index_template": "enrollment/index--agency-card.html", + "enrollment_success_template": "enrollment/success--sbmtd-mobility-pass.html" } }, { @@ -340,7 +346,6 @@ "jws_signing_alg": "RS256", "index_template": "core/index--mst.html", "eligibility_index_template": "eligibility/index--mst.html", - "enrollment_success_template": "enrollment/success--mst.html", "eligibility_types": [1, 7, 2, 3], "eligibility_verifiers": [1, 7, 2, 3] } @@ -363,7 +368,6 @@ "jws_signing_alg": "RS256", "index_template": "core/index--sacrt.html", "eligibility_index_template": "eligibility/index--sacrt.html", - "enrollment_success_template": "enrollment/success--sacrt.html", "eligibility_types": [4], "eligibility_verifiers": [4] } @@ -386,7 +390,6 @@ "jws_signing_alg": "RS256", "index_template": "core/index--sbmtd.html", "eligibility_index_template": "eligibility/index--sbmtd.html", - "enrollment_success_template": "enrollment/success--sbmtd.html", "eligibility_types": [5, 6], "eligibility_verifiers": [5, 6] } diff --git a/benefits/core/models.py b/benefits/core/models.py index 22abac492..b0c378448 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -116,6 +116,7 @@ class EligibilityType(models.Model): expiration_reenrollment_days = models.PositiveSmallIntegerField(null=True, blank=True) enrollment_index_template = models.TextField(default="enrollment/index.html") reenrollment_error_template = models.TextField(null=True, blank=True) + enrollment_success_template = models.TextField(default="enrollment/success.html") def __str__(self): return self.label @@ -276,7 +277,6 @@ class TransitAgency(models.Model): jws_signing_alg = models.TextField() index_template = models.TextField() eligibility_index_template = models.TextField() - enrollment_success_template = models.TextField() def __str__(self): return self.long_name From c76170368d9df1834aa0fe10e754a92c305ea16b Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Tue, 14 May 2024 16:47:12 +0000 Subject: [PATCH 004/111] feat(enrollment): update TransitAgency and EligibilityType migrations --- ..._move_enrollment_success_template_field.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 benefits/core/migrations/0011_move_enrollment_success_template_field.py diff --git a/benefits/core/migrations/0011_move_enrollment_success_template_field.py b/benefits/core/migrations/0011_move_enrollment_success_template_field.py new file mode 100644 index 000000000..9c00fbb0f --- /dev/null +++ b/benefits/core/migrations/0011_move_enrollment_success_template_field.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.3 on 2024-05-17 19:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0010_alter_secret_name_field_blank"), + ] + + operations = [ + migrations.RemoveField( + model_name="transitagency", + name="enrollment_success_template", + ), + migrations.AddField( + model_name="eligibilitytype", + name="enrollment_success_template", + field=models.TextField(default="enrollment/success.html"), + ), + ] From d58eaa2c60a4a4736bc3c6c81d27f2be1ea2b7e0 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Tue, 14 May 2024 16:39:10 +0000 Subject: [PATCH 005/111] feat(enrollment): update enrollment view --- benefits/enrollment/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index b6191e811..dab1e56ee 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -24,7 +24,6 @@ ROUTE_TOKEN = "enrollment:token" TEMPLATE_RETRY = "enrollment/retry.html" -TEMPLATE_SUCCESS = "enrollment/success.html" logger = logging.getLogger(__name__) @@ -152,7 +151,7 @@ def success(request): request.path = "/enrollment/success" session.update(request, origin=reverse(ROUTE_SUCCESS)) - agency = session.agency(request) + eligibility = session.eligibility(request) verifier = session.verifier(request) if session.logged_in(request) and verifier.auth_provider.supports_sign_out: @@ -160,4 +159,4 @@ def success(request): # if they click the logout button, they are taken to the new route session.update(request, origin=reverse(ROUTE_LOGGED_OUT)) - return TemplateResponse(request, agency.enrollment_success_template) + return TemplateResponse(request, eligibility.enrollment_success_template) From 5cd8e4e8984a71b9ddb4befb965e66a652b7b140 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Mon, 20 May 2024 17:47:09 +0000 Subject: [PATCH 006/111] feat(enrollment): update success message copy --- .../success--mst-courtesy-card.html | 21 +++++++++++++++++++ .../templates/enrollment/success--mst.html | 2 +- .../templates/enrollment/success--sacrt.html | 4 ++-- .../success--sbmtd-mobility-pass.html | 21 +++++++++++++++++++ .../templates/enrollment/success--sbmtd.html | 4 ++-- .../templates/enrollment/success.html | 16 ++++++++++---- 6 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 benefits/enrollment/templates/enrollment/success--mst-courtesy-card.html create mode 100644 benefits/enrollment/templates/enrollment/success--sbmtd-mobility-pass.html diff --git a/benefits/enrollment/templates/enrollment/success--mst-courtesy-card.html b/benefits/enrollment/templates/enrollment/success--mst-courtesy-card.html new file mode 100644 index 000000000..1fffece99 --- /dev/null +++ b/benefits/enrollment/templates/enrollment/success--mst-courtesy-card.html @@ -0,0 +1,21 @@ +{% extends "enrollment/success.html" %} +{% load i18n %} + +{% block headline-message %} + {% blocktranslate trimmed %} + You can now use your contactless card to tap to ride with a reduced fare! + {% endblocktranslate %} +{% endblock headline-message %} + +{% block success-message %} + {% blocktranslate trimmed %} + Your contactless card is now enrolled in an MST Courtesy Card transit benefit. When boarding an MST bus, tap this card and you will be + charged a reduced fare. You will need to re-enroll if you choose to change the card you use to pay for transit service. + {% endblocktranslate %} +{% endblock success-message %} + +{% block thank-you-message %} + {% blocktranslate trimmed %} + You were not charged anything today. Thank you for using Cal-ITP Benefits! + {% endblocktranslate %} +{% endblock thank-you-message %} diff --git a/benefits/enrollment/templates/enrollment/success--mst.html b/benefits/enrollment/templates/enrollment/success--mst.html index b6ea0e1a8..83ce6f92e 100644 --- a/benefits/enrollment/templates/enrollment/success--mst.html +++ b/benefits/enrollment/templates/enrollment/success--mst.html @@ -3,7 +3,7 @@ {% block success-message %} {% blocktranslate trimmed %} - You were not charged anything today. When boarding an MST fixed route bus, tap this card when you board and you will be + You were not charged anything today. When boarding an MST bus, tap this card and you will be charged a reduced fare. You will need to re-enroll if you choose to change the card you use to pay for transit service. {% endblocktranslate %} {% endblock success-message %} diff --git a/benefits/enrollment/templates/enrollment/success--sacrt.html b/benefits/enrollment/templates/enrollment/success--sacrt.html index 6dfdb13c5..79fc21f38 100644 --- a/benefits/enrollment/templates/enrollment/success--sacrt.html +++ b/benefits/enrollment/templates/enrollment/success--sacrt.html @@ -3,7 +3,7 @@ {% block success-message %} {% blocktranslate trimmed %} - You were not charged anything today. When boarding SacRT light rail, tap this card when you board and you will be charged - a reduced fare. You will need to re-enroll if you choose to change the card you use to pay for transit service. + You were not charged anything today. When boarding SacRT light rail, tap this card and you will be + charged a reduced fare. You will need to re-enroll if you choose to change the card you use to pay for transit service. {% endblocktranslate %} {% endblock success-message %} diff --git a/benefits/enrollment/templates/enrollment/success--sbmtd-mobility-pass.html b/benefits/enrollment/templates/enrollment/success--sbmtd-mobility-pass.html new file mode 100644 index 000000000..111c3dfb1 --- /dev/null +++ b/benefits/enrollment/templates/enrollment/success--sbmtd-mobility-pass.html @@ -0,0 +1,21 @@ +{% extends "enrollment/success.html" %} +{% load i18n %} + +{% block headline-message %} + {% blocktranslate trimmed %} + You can now use your contactless card to tap to ride with a reduced fare! + {% endblocktranslate %} +{% endblock headline-message %} + +{% block success-message %} + {% blocktranslate trimmed %} + Your contactless card is now enrolled in an SBMTD Reduced Fare Mobility ID transit benefit. When boarding an SBMTD bus, tap this card and you will be + charged a reduced fare. You will need to re-enroll if you choose to change the card you use to pay for transit service. + {% endblocktranslate %} +{% endblock success-message %} + +{% block thank-you-message %} + {% blocktranslate trimmed %} + You were not charged anything today. Thank you for using Cal-ITP Benefits! + {% endblocktranslate %} +{% endblock thank-you-message %} diff --git a/benefits/enrollment/templates/enrollment/success--sbmtd.html b/benefits/enrollment/templates/enrollment/success--sbmtd.html index 08559b21f..1902838d7 100644 --- a/benefits/enrollment/templates/enrollment/success--sbmtd.html +++ b/benefits/enrollment/templates/enrollment/success--sbmtd.html @@ -3,7 +3,7 @@ {% block success-message %} {% blocktranslate trimmed %} - You were not charged anything today. When boarding an SBMTD bus, tap this card when you board and you will be - charged a reduced fare. If you change the card you use to pay for transit service, you will need to re-enroll. + You were not charged anything today. When boarding an SBMTD bus, tap this card and you will be + charged a reduced fare. You will need to re-enroll if you choose to change the card you use to pay for transit service. {% endblocktranslate %} {% endblock success-message %} diff --git a/benefits/enrollment/templates/enrollment/success.html b/benefits/enrollment/templates/enrollment/success.html index f4b161840..36931292c 100644 --- a/benefits/enrollment/templates/enrollment/success.html +++ b/benefits/enrollment/templates/enrollment/success.html @@ -13,26 +13,34 @@ {% block headline %}
-

{% translate "Success! Your transit benefit is now connected to your card." %}

+

+ {% block headline-message %} + {% translate "Success! Your transit benefit is now connected to your card." %} + {% endblock headline-message %} +

{% endblock headline %} {% block inner-content %}
-
+
{# djlint:off #} {% if enrollment.supports_expiration %}

{% translate "Your benefit will expire on" %} {{ enrollment.expires|date }}.

{% else %} -

+

{% endif %} {% block success-message %} {% endblock success-message %}

{# djlint:on #} -

{% translate "Thank you for using Cal-ITP Benefits!" %}

+

+ {% block thank-you-message %} + {% translate "Thank you for using Cal-ITP Benefits!" %} + {% endblock thank-you-message %} +

Date: Thu, 16 May 2024 19:47:46 +0000 Subject: [PATCH 007/111] feat(enrollment): update spanish translations --- benefits/locale/en/LC_MESSAGES/django.po | 47 ++++++++++--- benefits/locale/es/LC_MESSAGES/django.po | 84 ++++++++++++++++++------ 2 files changed, 102 insertions(+), 29 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 9cf9c9aff..381a429cd 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-05-15 16:02+0000\n" +"POT-Creation-Date: 2024-05-16 19:45+0000\n" "Language: English\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -645,23 +645,50 @@ msgid "" msgstr "" msgid "" -"You were not charged anything today. When boarding an MST fixed route bus, " -"tap this card when you board and you will be charged a reduced fare. You " -"will need to re-enroll if you choose to change the card you use to pay for " -"transit service." +"You can now use your contactless card to tap to ride with a reduced fare!" +msgstr "" + +msgid "" +"Your contactless card is now enrolled in an MST Courtesy Card transit " +"benefit. When boarding an MST bus, tap this card and you will be charged a " +"reduced fare. You will need to re-enroll if you choose to change the card " +"you use to pay for transit service." +msgstr "" + +msgid "" +"You were not charged anything today. Thank you for using Cal-ITP Benefits!" +msgstr "" + +msgid "" +"You were not charged anything today. When boarding an MST bus, tap this card " +"and you will be charged a reduced fare. You will need to re-enroll if you " +"choose to change the card you use to pay for transit service." +msgstr "" + +msgid "" +"Your contactless card is now enrolled in a SacRT light rail transit benefit. " +"When boarding SacRT light rail, tap this card and you will be charged a " +"reduced fare. You will need to re-enroll if you choose to change the card " +"you use to pay for transit service." msgstr "" msgid "" "You were not charged anything today. When boarding SacRT light rail, tap " -"this card when you board and you will be charged a reduced fare. You will " -"need to re-enroll if you choose to change the card you use to pay for " -"transit service." +"this card and you will be charged a reduced fare. You will need to re-enroll " +"if you choose to change the card you use to pay for transit service." +msgstr "" + +msgid "" +"Your contactless card is now enrolled in an SBMTD Reduced Fare Mobility ID " +"transit benefit. When boarding an SBMTD bus, tap this card and you will be " +"charged a reduced fare. You will need to re-enroll if you choose to change " +"the card you use to pay for transit service." msgstr "" msgid "" "You were not charged anything today. When boarding an SBMTD bus, tap this " -"card when you board and you will be charged a reduced fare. If you change " -"the card you use to pay for transit service, you will need to re-enroll." +"card and you will be charged a reduced fare. You will need to re-enroll if " +"you choose to change the card you use to pay for transit service." msgstr "" msgid "Success" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 1fb37ac4f..191a90987 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-05-15 16:02+0000\n" +"POT-Creation-Date: 2024-05-16 19:45+0000\n" "Language: Español\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -806,34 +806,80 @@ msgstr "" "agencia de tránsito para obtener ayuda." msgid "" -"You were not charged anything today. When boarding an MST fixed route bus, " -"tap this card when you board and you will be charged a reduced fare. You " -"will need to re-enroll if you choose to change the card you use to pay for " -"transit service." +"You can now use your contactless card to tap to ride with a reduced fare!" msgstr "" -"No se le cobró nada hoy. Al abordar un autobús de ruta fija de MST, toque " -"esta tarjeta cuando suba y se le cobrará una tarifa reducida. Deberá volver " -"a inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " +"¡Ahora puede usar su tarjeta sin contacto para tocar y viajar con una tarifa " +"reducida!" + +msgid "" +"Your contactless card is now enrolled in an MST Courtesy Card transit " +"benefit. When boarding an MST bus, tap this card and you will be charged a " +"reduced fare. You will need to re-enroll if you choose to change the card " +"you use to pay for transit service." +msgstr "" +"Su tarjeta sin contacto ahora está inscrita en el beneficio de tránsito de " +"la tarjeta de cortesía de MST. Cuando suba a un autobús de MST, toque el " +"lector con esta tarjeta y se le cobrará una tarifa reducida. Deberá volver a " +"inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " +"tránsito." + +msgid "" +"You were not charged anything today. Thank you for using Cal-ITP Benefits!" +msgstr "No se le cobró nada hoy. ¡Gracias por usar Cal-ITP Benefits!" + +msgid "" +"You were not charged anything today. When boarding an MST bus, tap this card " +"and you will be charged a reduced fare. You will need to re-enroll if you " +"choose to change the card you use to pay for transit service." +msgstr "" +"No se le cobró nada hoy. Al abordar un autobús de CST, toque el lector con " +"esta tarjeta y se le cobrará una tarifa reducida. Deberá volver a " +"inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " +"tránsito." + +msgid "" +"Your contactless card is now enrolled in a SacRT light rail transit benefit. " +"When boarding SacRT light rail, tap this card and you will be charged a " +"reduced fare. You will need to re-enroll if you choose to change the card " +"you use to pay for transit service." +msgstr "" +"Su tarjeta sin contacto ahora está inscrita en el beneficio de tránsito de " +"tren ligero de SacRT. Cuando suba a un tren ligero de SacRT, toque el lector " +"con esta tarjeta y se le cobrará una tarifa reducida. Deberá volver a " +"inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " "tránsito." msgid "" "You were not charged anything today. When boarding SacRT light rail, tap " -"this card when you board and you will be charged a reduced fare. You will " -"need to re-enroll if you choose to change the card you use to pay for " -"transit service." +"this card and you will be charged a reduced fare. You will need to re-enroll " +"if you choose to change the card you use to pay for transit service." msgstr "" -"No se le cobró nada hoy. Al abordar un tren ligero SacRT, toque esta tarjeta " -"cuando suba y se le cobrará una tarifa reducida. Deberá volver a inscribirse " -"si elige cambiar la tarjeta que usa para pagar el servicio de tránsito." +"No se le cobró nada hoy. Al abordar un tren ligero de SaCRT, toque el lector " +"con esta tarjeta y se le cobrará una tarifa reducida. Deberá volver a " +"inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " +"tránsito." + +msgid "" +"Your contactless card is now enrolled in an SBMTD Reduced Fare Mobility ID " +"transit benefit. When boarding an SBMTD bus, tap this card and you will be " +"charged a reduced fare. You will need to re-enroll if you choose to change " +"the card you use to pay for transit service." +msgstr "" +"Su tarjeta sin contacto ahora está inscrita en el beneficio de tránsito de " +"Reduced Fare Mobility ID de SBMTD. Cuando suba a un autobús de SBMTD, toque " +"el lector con esta tarjeta y se le cobrará una tarifa reducida. Deberá " +"volver a inscribirse si elige cambiar la tarjeta que usa para pagar el " +"servicio de tránsito." msgid "" "You were not charged anything today. When boarding an SBMTD bus, tap this " -"card when you board and you will be charged a reduced fare. If you change " -"the card you use to pay for transit service, you will need to re-enroll." +"card and you will be charged a reduced fare. You will need to re-enroll if " +"you choose to change the card you use to pay for transit service." msgstr "" -"No se le cobró nada hoy. Al abordar un autobús de SBMTD, toque esta tarjeta " -"cuando suba y se le cobrará una tarifa reducida. Deberá volver a inscribirse " -"si elige cambiar la tarjeta que usa para pagar el servicio de tránsito." +"No se le cobró nada hoy. Al abordar un autobús de SBMTD, toque el lector con " +"esta tarjeta y se le cobrará una tarifa reducida. Deberá volver a " +"inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " +"tránsito." msgid "Success" msgstr "Éxito" From 3e67b99159bcd6efb87a133645261a2381202753 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Thu, 16 May 2024 22:35:19 +0000 Subject: [PATCH 008/111] feat(enrollment): update fixtures and tests --- tests/pytest/conftest.py | 8 ++++++-- tests/pytest/core/test_models.py | 7 +++++++ tests/pytest/enrollment/test_views.py | 21 ++++++++++++--------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index 2dfb4aa48..a388856ac 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -100,7 +100,12 @@ def model_AuthProvider_without_verification_no_sign_out(model_AuthProvider): @pytest.fixture def model_EligibilityType(): - eligibility = EligibilityType.objects.create(name="test", label="Test Eligibility Type", group_id="1234") + eligibility = EligibilityType.objects.create( + name="test", + label="Test Eligibility Type", + group_id="1234", + enrollment_success_template="enrollment/success.html", + ) return eligibility @@ -202,7 +207,6 @@ def model_TransitAgency(model_PemData, model_EligibilityType, model_EligibilityV jws_signing_alg="alg", index_template="core/agency-index.html", eligibility_index_template="eligibility/index.html", - enrollment_success_template="enrollment/success.html", ) # add many-to-many relationships after creation, need ID on both sides diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py index 0d2b8cf6d..c3470bdb8 100644 --- a/tests/pytest/core/test_models.py +++ b/tests/pytest/core/test_models.py @@ -247,6 +247,13 @@ def test_EligibilityType_enrollment_index_template(model_EligibilityType): assert model_EligibilityType.enrollment_index_template == "test/enrollment.html" +@pytest.mark.django_db +def test_EligibilityType_enrollment_success_template(): + new_eligibility_type = EligibilityType.objects.create() + + assert new_eligibility_type.enrollment_success_template == "enrollment/success.html" + + class SampleFormClass: """A class for testing EligibilityVerifier form references.""" diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index dfec565db..056373c44 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -14,7 +14,6 @@ ROUTE_RETRY, ROUTE_SUCCESS, ROUTE_TOKEN, - TEMPLATE_SUCCESS, TEMPLATE_RETRY, ) @@ -174,7 +173,7 @@ def test_index_eligible_post_valid_form_customer_already_enrolled( funding_source_id=mocked_funding_source.id, group_id=model_EligibilityType.group_id ) assert response.status_code == 200 - assert response.template_name == TEMPLATE_SUCCESS + assert response.template_name == model_EligibilityType.enrollment_success_template mocked_analytics_module.returned_success.assert_called_once() assert model_EligibilityType.group_id in mocked_analytics_module.returned_success.call_args.args @@ -195,7 +194,7 @@ def test_index_eligible_post_valid_form_success( funding_source_id=mocked_funding_source.id, group_id=model_EligibilityType.group_id ) assert response.status_code == 200 - assert response.template_name == TEMPLATE_SUCCESS + assert response.template_name == model_EligibilityType.enrollment_success_template mocked_analytics_module.returned_success.assert_called_once() assert model_EligibilityType.group_id in mocked_analytics_module.returned_success.call_args.args @@ -286,38 +285,42 @@ def test_success_no_verifier(client): @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_auth_required") -def test_success_authentication_logged_in(mocker, client, model_TransitAgency): +def test_success_authentication_logged_in(mocker, client, model_TransitAgency, model_EligibilityType): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.logged_in.return_value = True mock_session.agency.return_value = model_TransitAgency + mock_session.eligibility.return_value = model_EligibilityType path = reverse(ROUTE_SUCCESS) response = client.get(path) assert response.status_code == 200 - assert response.template_name == TEMPLATE_SUCCESS + assert response.template_name == model_EligibilityType.enrollment_success_template assert {"origin": reverse(ROUTE_LOGGED_OUT)} in mock_session.update.call_args @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_auth_required") -def test_success_authentication_not_logged_in(mocker, client, model_TransitAgency): +def test_success_authentication_not_logged_in(mocker, client, model_TransitAgency, model_EligibilityType): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.logged_in.return_value = False mock_session.agency.return_value = model_TransitAgency + mock_session.eligibility.return_value = model_EligibilityType path = reverse(ROUTE_SUCCESS) response = client.get(path) assert response.status_code == 200 - assert response.template_name == TEMPLATE_SUCCESS + assert response.template_name == model_EligibilityType.enrollment_success_template @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier_auth_not_required") -def test_success_no_authentication(client): +def test_success_no_authentication(mocker, client, model_EligibilityType): + mock_session = mocker.patch("benefits.enrollment.views.session") + mock_session.eligibility.return_value = model_EligibilityType path = reverse(ROUTE_SUCCESS) response = client.get(path) assert response.status_code == 200 - assert response.template_name == TEMPLATE_SUCCESS + assert response.template_name == model_EligibilityType.enrollment_success_template From 9e12645b8c77af4ea99ce6671259c81ecd1b5296 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 30 Apr 2024 22:11:54 +0000 Subject: [PATCH 009/111] refactor: show system enrollment error page if 500 during linking --- .../templates/enrollment/system_error.html | 0 benefits/enrollment/views.py | 10 +++++++ tests/pytest/enrollment/test_views.py | 29 +++++++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 benefits/enrollment/templates/enrollment/system_error.html diff --git a/benefits/enrollment/templates/enrollment/system_error.html b/benefits/enrollment/templates/enrollment/system_error.html new file mode 100644 index 000000000..e69de29bb diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index dab1e56ee..aa55298d9 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -24,6 +24,7 @@ ROUTE_TOKEN = "enrollment:token" TEMPLATE_RETRY = "enrollment/retry.html" +TEMPLATE_SYSTEM_ERROR = "enrollment/system_error.html" logger = logging.getLogger(__name__) @@ -87,6 +88,9 @@ def index(request): if e.response.status_code == 409: analytics.returned_success(request, eligibility.group_id) return success(request) + elif e.response.status_code >= 500: + analytics.returned_error(request, str(e)) + return system_error(request) else: analytics.returned_error(request, str(e)) raise Exception(f"{e}: {e.response.json()}") @@ -144,6 +148,12 @@ def retry(request): return TemplateResponse(request, TEMPLATE_RETRY) +@decorator_from_middleware(EligibleSessionRequired) +def system_error(request): + """View handler for an enrollment system error.""" + return TemplateResponse(request, TEMPLATE_SYSTEM_ERROR) + + @pageview_decorator @decorator_from_middleware(VerifierSessionRequired) def success(request): diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 056373c44..8df0bd4e2 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -14,6 +14,7 @@ ROUTE_RETRY, ROUTE_SUCCESS, ROUTE_TOKEN, + TEMPLATE_SYSTEM_ERROR, TEMPLATE_RETRY, ) @@ -124,12 +125,36 @@ def test_index_eligible_post_invalid_form(client, invalid_form_data): @pytest.mark.django_db +@pytest.mark.parametrize("status_code", [500, 501, 502, 503, 504]) @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") -def test_index_eligible_post_valid_form_http_error(mocker, client, card_tokenize_form_data): +def test_index_eligible_post_valid_form_http_error_500( + mocker, client, mocked_analytics_module, card_tokenize_form_data, status_code +): + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + + mock_error = {"message": "Mock error message"} + mock_error_response = mocker.Mock(status_code=status_code, **mock_error) + mock_error_response.json.return_value = mock_error + mock_client.link_concession_group_funding_source.side_effect = HTTPError( + response=mock_error_response, + ) + + path = reverse(ROUTE_INDEX) + response = client.post(path, card_tokenize_form_data) + + assert response.status_code == 200 + assert response.template_name == TEMPLATE_SYSTEM_ERROR + mocked_analytics_module.returned_error.assert_called_once() + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") +def test_index_eligible_post_valid_form_http_error_400(mocker, client, card_tokenize_form_data): mock_client_cls = mocker.patch("benefits.enrollment.views.Client") mock_client = mock_client_cls.return_value - # any status_code that isn't 409 is considered an error + # any 400 level status_code that isn't 409 is considered an error mock_error = {"message": "Mock error message"} mock_error_response = mocker.Mock(status_code=400, **mock_error) mock_error_response.json.return_value = mock_error From 9605c5d07a1929754664a0442ddbc0e5cf7a31a7 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 13 May 2024 17:47:41 +0000 Subject: [PATCH 010/111] chore: remove style class that no longer exists --- benefits/templates/error.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/templates/error.html b/benefits/templates/error.html index 4ec55f37b..677c9dda1 100644 --- a/benefits/templates/error.html +++ b/benefits/templates/error.html @@ -8,7 +8,7 @@ {% block main-content %}
-

+

{% include "core/includes/icon.html" with name="sadbus" %} {% block headline %} {% endblock headline %} From 8dee229fcf789d0e340f891ec8e46a504ecc9548 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 13 May 2024 21:31:07 +0000 Subject: [PATCH 011/111] feat: implement system enrollment error page template follows implementation of retry page template. CTA goes to agency index. --- .../templates/enrollment/system_error.html | 43 +++++++++++++++++++ benefits/enrollment/views.py | 3 ++ benefits/static/css/styles.css | 5 ++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/benefits/enrollment/templates/enrollment/system_error.html b/benefits/enrollment/templates/enrollment/system_error.html index e69de29bb..3ca701390 100644 --- a/benefits/enrollment/templates/enrollment/system_error.html +++ b/benefits/enrollment/templates/enrollment/system_error.html @@ -0,0 +1,43 @@ +{% extends "core/base.html" %} +{% load i18n %} + +{% block classes %} + {{ block.super | add:" system-enrollment-error" }} +{% endblock classes %} + +{% block page-title %} + {% translate "System enrollment error" %} +{% endblock page-title %} + +{% block main-content %} +
+

+ {% include "core/includes/icon.html" with name="bankcardquestion" %} + {% translate "Our enrollment system is not working right now." %} +

+ +
+
+

{% translate "We’re working to solve the problem. Please wait 48 hours and try to enroll again, or contact your transit agency for help." %}

+
+
+ +
+
{% include "core/includes/agency-links.html" %}
+
+ +
+
+ {% if authentication and authentication.sign_out_link_template %} + {% url "oauth:logout" as sign_out_url %} + {% translate "Sign out of" as button_text %} + + {% else %} + {% include "core/includes/button--origin.html" %} + {% endif %} +
+
+
+{% endblock main-content %} diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index aa55298d9..73592c0b7 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -90,6 +90,9 @@ def index(request): return success(request) elif e.response.status_code >= 500: analytics.returned_error(request, str(e)) + + # overwrite origin so that CTA takes user to agency index + session.update(request, origin=agency.index_url) return system_error(request) else: analytics.returned_error(request, str(e)) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 6927754d9..91b71c0b3 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -470,9 +470,10 @@ footer .footer-links li a.footer-link:visited { } } -/* Sign in with Login.gov (white logo) on Eligibility Start */ +/* Sign in with Login.gov (white logo) on Eligibility Start, System Enrollment Error */ -.eligibility-start .btn.btn-lg.btn-primary.login { +.eligibility-start .btn.btn-lg.btn-primary.login, +.system-enrollment-error .btn.btn-lg.btn-primary.login { padding: 10px 0; } From d8c2e700cc478d8a4a0a237005e77d1a307f04fd Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 13 May 2024 21:47:55 +0000 Subject: [PATCH 012/111] feat(sentry): ensure 500 error during link still sends Sentry event --- benefits/enrollment/views.py | 2 ++ tests/pytest/enrollment/test_views.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 73592c0b7..7d35f18ea 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -10,6 +10,7 @@ from django.utils.decorators import decorator_from_middleware from littlepay.api.client import Client from requests.exceptions import HTTPError +import sentry_sdk from benefits.core import session from benefits.core.middleware import EligibleSessionRequired, VerifierSessionRequired, pageview_decorator @@ -90,6 +91,7 @@ def index(request): return success(request) elif e.response.status_code >= 500: analytics.returned_error(request, str(e)) + sentry_sdk.capture_exception(e) # overwrite origin so that CTA takes user to agency index session.update(request, origin=agency.index_url) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 8df0bd4e2..41066589f 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -34,6 +34,11 @@ def mocked_analytics_module(mocked_analytics_module): return mocked_analytics_module(benefits.enrollment.views) +@pytest.fixture +def mocked_sentry_sdk_module(mocker): + return mocker.patch.object(benefits.enrollment.views, "sentry_sdk") + + @pytest.fixture def mocked_funding_source(): return FundingSourceResponse( @@ -128,7 +133,7 @@ def test_index_eligible_post_invalid_form(client, invalid_form_data): @pytest.mark.parametrize("status_code", [500, 501, 502, 503, 504]) @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") def test_index_eligible_post_valid_form_http_error_500( - mocker, client, mocked_analytics_module, card_tokenize_form_data, status_code + mocker, client, mocked_analytics_module, mocked_sentry_sdk_module, card_tokenize_form_data, status_code ): mock_client_cls = mocker.patch("benefits.enrollment.views.Client") mock_client = mock_client_cls.return_value @@ -146,6 +151,7 @@ def test_index_eligible_post_valid_form_http_error_500( assert response.status_code == 200 assert response.template_name == TEMPLATE_SYSTEM_ERROR mocked_analytics_module.returned_error.assert_called_once() + mocked_sentry_sdk_module.capture_exception.assert_called_once() @pytest.mark.django_db From 852416ba63cd259c64db4d3660c110453d179e9a Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 May 2024 16:00:26 +0000 Subject: [PATCH 013/111] chore: run makemessages helper script --- benefits/locale/en/LC_MESSAGES/django.po | 14 ++++++++++++++ benefits/locale/es/LC_MESSAGES/django.po | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 381a429cd..cb8caceeb 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -706,6 +706,20 @@ msgstr "" msgid "If you are on a public or shared computer, don’t forget to sign out of " msgstr "" +msgid "System enrollment error" +msgstr "" + +msgid "Our enrollment system is not working right now." +msgstr "" + +msgid "" +"We’re working to solve the problem. Please wait 48 hours and try to enroll " +"again, or contact your transit agency for help." +msgstr "" + +msgid "Sign out of" +msgstr "" + msgid "Start over" msgstr "" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 191a90987..9ac16f18a 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -897,6 +897,20 @@ msgid "If you are on a public or shared computer, don’t forget to sign out of msgstr "" "Si está en una computadora pública o compartida, no olvide cerrar sesión en " +msgid "System enrollment error" +msgstr "" + +msgid "Our enrollment system is not working right now." +msgstr "" + +msgid "" +"We’re working to solve the problem. Please wait 48 hours and try to enroll " +"again, or contact your transit agency for help." +msgstr "" + +msgid "Sign out of" +msgstr "" + msgid "Start over" msgstr "Comenzar de nuevo" From 1138b278641de4ee55e37d65f46c2b17435e5bfc Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 May 2024 18:51:10 +0000 Subject: [PATCH 014/111] test: add coverage for session origin getting updated --- tests/pytest/enrollment/test_views.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 41066589f..49e25c708 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -131,10 +131,19 @@ def test_index_eligible_post_invalid_form(client, invalid_form_data): @pytest.mark.django_db @pytest.mark.parametrize("status_code", [500, 501, 502, 503, 504]) -@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") +@pytest.mark.usefixtures("mocked_session_eligibility") def test_index_eligible_post_valid_form_http_error_500( - mocker, client, mocked_analytics_module, mocked_sentry_sdk_module, card_tokenize_form_data, status_code + mocker, + client, + mocked_session_agency, + mocked_analytics_module, + mocked_sentry_sdk_module, + card_tokenize_form_data, + status_code, ): + mock_session = mocker.patch("benefits.enrollment.views.session") + mock_session.agency.return_value = mocked_session_agency.return_value + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") mock_client = mock_client_cls.return_value @@ -150,6 +159,7 @@ def test_index_eligible_post_valid_form_http_error_500( assert response.status_code == 200 assert response.template_name == TEMPLATE_SYSTEM_ERROR + assert {"origin": mocked_session_agency.return_value.index_url} in mock_session.update.call_args mocked_analytics_module.returned_error.assert_called_once() mocked_sentry_sdk_module.capture_exception.assert_called_once() From deb9f68272493f1893641125a2e87e4536978be8 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 May 2024 18:53:47 +0000 Subject: [PATCH 015/111] refactor: move session update into system_error view function we always want the agency index URL to be the origin when returning this template. --- benefits/enrollment/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 7d35f18ea..ee81973ab 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -93,8 +93,6 @@ def index(request): analytics.returned_error(request, str(e)) sentry_sdk.capture_exception(e) - # overwrite origin so that CTA takes user to agency index - session.update(request, origin=agency.index_url) return system_error(request) else: analytics.returned_error(request, str(e)) @@ -156,6 +154,11 @@ def retry(request): @decorator_from_middleware(EligibleSessionRequired) def system_error(request): """View handler for an enrollment system error.""" + + # overwrite origin so that CTA takes user to agency index + agency = session.agency(request) + session.update(request, origin=agency.index_url) + return TemplateResponse(request, TEMPLATE_SYSTEM_ERROR) From 95ec1066f193891a09e27f8e1d2b11090e9aa1fc Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 20 May 2024 21:50:00 +0000 Subject: [PATCH 016/111] chore(copy): update page title, translations for system enrollment error --- .../templates/enrollment/system_error.html | 10 ++++++---- benefits/locale/en/LC_MESSAGES/django.po | 4 ++-- benefits/locale/es/LC_MESSAGES/django.po | 13 ++++++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/benefits/enrollment/templates/enrollment/system_error.html b/benefits/enrollment/templates/enrollment/system_error.html index 3ca701390..3324701cd 100644 --- a/benefits/enrollment/templates/enrollment/system_error.html +++ b/benefits/enrollment/templates/enrollment/system_error.html @@ -6,7 +6,7 @@ {% endblock classes %} {% block page-title %} - {% translate "System enrollment error" %} + {% translate "Enrollment system down" %} {% endblock page-title %} {% block main-content %} @@ -18,12 +18,14 @@

-

{% translate "We’re working to solve the problem. Please wait 48 hours and try to enroll again, or contact your transit agency for help." %}

+

+ {% translate "We’re working to solve the problem. Please wait 48 hours and try to enroll again, or contact your transit agency for help." %} +

-
{% include "core/includes/agency-links.html" %}
+
{% include "core/includes/agency-links.html" %}
@@ -32,7 +34,7 @@

{% url "oauth:logout" as sign_out_url %} {% translate "Sign out of" as button_text %} {% else %} {% include "core/includes/button--origin.html" %} diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index cb8caceeb..9bcdc3a4e 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-05-16 19:45+0000\n" +"POT-Creation-Date: 2024-05-20 21:48+0000\n" "Language: English\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -706,7 +706,7 @@ msgstr "" msgid "If you are on a public or shared computer, don’t forget to sign out of " msgstr "" -msgid "System enrollment error" +msgid "Enrollment system down" msgstr "" msgid "Our enrollment system is not working right now." diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 9ac16f18a..527cde109 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-05-16 19:45+0000\n" +"POT-Creation-Date: 2024-05-20 21:48+0000\n" "Language: Español\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -897,19 +897,22 @@ msgid "If you are on a public or shared computer, don’t forget to sign out of msgstr "" "Si está en una computadora pública o compartida, no olvide cerrar sesión en " -msgid "System enrollment error" -msgstr "" +msgid "Enrollment system down" +msgstr "El sistema de inscripción no funciona" msgid "Our enrollment system is not working right now." -msgstr "" +msgstr "Nuestro sistema de inscripción no está funcionando en este momento." msgid "" "We’re working to solve the problem. Please wait 48 hours and try to enroll " "again, or contact your transit agency for help." msgstr "" +"Estamos trabajando para resolver el problema. Espere 48 horas e intente " +"inscribirse nuevamente, o contacte a su agencia de tránsito para obtener " +"ayuda." msgid "Sign out of" -msgstr "" +msgstr "Cierre sesión de" msgid "Start over" msgstr "Comenzar de nuevo" From 223fd4f137a3dc67c95f32ed932675f6c252d5ef Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 21 May 2024 11:21:28 +0000 Subject: [PATCH 017/111] chore(ci): only tag Docker image with SHA we're moving away from using the branch-based tags and associated webhooks, so we won't need this image tag --- .github/workflows/deploy.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5dadca811..7209de623 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -61,6 +61,4 @@ jobs: context: . file: appcontainer/Dockerfile push: true - tags: | - ghcr.io/${{ github.repository }}:${{ github.ref_name }} - ghcr.io/${{ github.repository }}:${{ github.sha }} + tags: ghcr.io/${{ github.repository }}:${{ github.sha }} From 98fc86a7445e80e2d75a66a66bcc3fe6e432fd4b Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 21 May 2024 11:55:56 +0000 Subject: [PATCH 018/111] ci(deploy): azure/webapps-deploy deploys new image replaces the old webhooks-based deployment --- .github/workflows/deploy.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7209de623..790c295a3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -62,3 +62,10 @@ jobs: file: appcontainer/Dockerfile push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }} + + - name: Deploy to Azure Web App + uses: azure/webapps-deploy@v2 + with: + app-name: ${{ vars.AZURE_WEBAPP_NAME }} + images: ghcr.io/${{ github.repository }}:${{ github.sha }} + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} From 9f4863573d6ac859abfbbea7e874209e9bd10486 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 1 May 2024 23:13:22 +0000 Subject: [PATCH 019/111] refactor: show system enrollment error page if 500 during /token --- .../templates/enrollment/index.html | 6 +++++ benefits/enrollment/urls.py | 1 + benefits/enrollment/views.py | 17 ++++++++++-- tests/pytest/enrollment/test_views.py | 26 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/benefits/enrollment/templates/enrollment/index.html b/benefits/enrollment/templates/enrollment/index.html index f75e42303..0a4d8c318 100644 --- a/benefits/enrollment/templates/enrollment/index.html +++ b/benefits/enrollment/templates/enrollment/index.html @@ -41,6 +41,12 @@

$.ajax({ dataType: "script", attrs: { nonce: "{{ request.csp_nonce }}"}, url: "{{ card_tokenize_url }}" }) .done(function() { $.get("{{ access_token_url }}", function(data) { + if (data.redirect) { + // https://stackoverflow.com/a/42469170 + // use 'assign' because 'replace' was giving strange Back button behavior + window.location.assign(data.redirect); + } + $(".loading").remove(); // remove invisible and add back visible, so we aren't left with // a div with an empty class attribute diff --git a/benefits/enrollment/urls.py b/benefits/enrollment/urls.py index f74eb2452..fb3ddd5c1 100644 --- a/benefits/enrollment/urls.py +++ b/benefits/enrollment/urls.py @@ -15,4 +15,5 @@ path("reenrollment-error", views.reenrollment_error, name="reenrollment-error"), path("retry", views.retry, name="retry"), path("success", views.success, name="success"), + path("error", views.system_error, name="system-error"), ] diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index ee81973ab..7592abeae 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -22,6 +22,7 @@ ROUTE_REENROLLMENT_ERROR = "enrollment:reenrollment-error" ROUTE_RETRY = "enrollment:retry" ROUTE_SUCCESS = "enrollment:success" +ROUTE_SYSTEM_ERROR = "enrollment:system-error" ROUTE_TOKEN = "enrollment:token" TEMPLATE_RETRY = "enrollment/retry.html" @@ -44,8 +45,20 @@ def token(request): audience=payment_processor.audience, ) client.oauth.ensure_active_token(client.token) - response = client.request_card_tokenization_access() - session.update(request, enrollment_token=response.get("access_token"), enrollment_token_exp=response.get("expires_at")) + + try: + response = client.request_card_tokenization_access() + except Exception as e: + if isinstance(e, HTTPError) and e.response.status_code >= 500: + sentry_sdk.capture_exception(e) + data = {"redirect": reverse(ROUTE_SYSTEM_ERROR)} + return JsonResponse(data) + else: + raise e + else: + session.update( + request, enrollment_token=response.get("access_token"), enrollment_token_exp=response.get("expires_at") + ) data = {"token": session.enrollment_token(request)} diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 49e25c708..231ba31fa 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -13,6 +13,7 @@ ROUTE_REENROLLMENT_ERROR, ROUTE_RETRY, ROUTE_SUCCESS, + ROUTE_SYSTEM_ERROR, ROUTE_TOKEN, TEMPLATE_SYSTEM_ERROR, TEMPLATE_RETRY, @@ -102,6 +103,31 @@ def test_token_valid(mocker, client): assert data["token"] == "enrollment_token" +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") +def test_token_http_error_500(mocker, client): + mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False) + + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + + mock_error = {"message": "Mock error message"} + mock_error_response = mocker.Mock(status_code=500, **mock_error) + mock_error_response.json.return_value = mock_error + mock_client.request_card_tokenization_access.side_effect = HTTPError( + response=mock_error_response, + ) + + path = reverse(ROUTE_TOKEN) + response = client.get(path) + + assert response.status_code == 200 + data = response.json() + assert "token" not in data + assert "redirect" in data + assert data["redirect"] == reverse(ROUTE_SYSTEM_ERROR) + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") def test_index_eligible_get(client, model_EligibilityType): From 2320959270aeb03b9e876167183b9c43322045aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 21:13:05 +0000 Subject: [PATCH 020/111] --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/cypress/package-lock.json | 14 +++++++------- tests/cypress/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cypress/package-lock.json b/tests/cypress/package-lock.json index 2ce820a19..a12606005 100644 --- a/tests/cypress/package-lock.json +++ b/tests/cypress/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "AGPL-3.0-or-later", "devDependencies": { - "cypress": "^13.9.0" + "cypress": "^13.10.0" } }, "node_modules/@colors/colors": { @@ -537,9 +537,9 @@ } }, "node_modules/cypress": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz", - "integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.10.0.tgz", + "integrity": "sha512-tOhwRlurVOQbMduX+KonoMeQILs2cwR3yHGGENoFvvSoLUBHmJ8b9/n21gFSDqjlOJ+SRVcwuh+fG/JDsHsT6Q==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2358,9 +2358,9 @@ } }, "cypress": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz", - "integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.10.0.tgz", + "integrity": "sha512-tOhwRlurVOQbMduX+KonoMeQILs2cwR3yHGGENoFvvSoLUBHmJ8b9/n21gFSDqjlOJ+SRVcwuh+fG/JDsHsT6Q==", "dev": true, "requires": { "@cypress/request": "^3.0.0", diff --git a/tests/cypress/package.json b/tests/cypress/package.json index 64cf2d147..a834b6afb 100644 --- a/tests/cypress/package.json +++ b/tests/cypress/package.json @@ -12,6 +12,6 @@ "license": "AGPL-3.0-or-later", "private": true, "devDependencies": { - "cypress": "^13.9.0" + "cypress": "^13.10.0" } } From 3d90770d59fbb39adf3388f866a884e46c1556f9 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 21 May 2024 20:21:44 +0000 Subject: [PATCH 021/111] feat(analytics): send analytic event when 500 error during /token --- benefits/enrollment/analytics.py | 14 ++++++++++++++ benefits/enrollment/views.py | 1 + tests/pytest/enrollment/test_analytics.py | 10 ++++++++++ tests/pytest/enrollment/test_views.py | 4 +++- 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/pytest/enrollment/test_analytics.py diff --git a/benefits/enrollment/analytics.py b/benefits/enrollment/analytics.py index 3fc60b4ab..b92e4d349 100644 --- a/benefits/enrollment/analytics.py +++ b/benefits/enrollment/analytics.py @@ -16,6 +16,15 @@ def __init__(self, request, status, error=None, payment_group=None): self.update_event_properties(payment_group=payment_group) +class FailedAccessTokenRequestEvent(core.Event): + """Analytics event representing a failure to acquire an access token for card tokenization.""" + + def __init__(self, request, status_code=None): + super().__init__(request, "failed access token request") + if status_code is not None: + self.update_event_properties(status_code=status_code) + + def returned_error(request, error): """Send the "returned enrollment" analytics event with an error status and message.""" core.send_event(ReturnedEnrollmentEvent(request, status="error", error=error)) @@ -29,3 +38,8 @@ def returned_retry(request): def returned_success(request, payment_group): """Send the "returned enrollment" analytics event with a success status.""" core.send_event(ReturnedEnrollmentEvent(request, status="success", payment_group=payment_group)) + + +def failed_access_token_request(request, status_code=None): + """Send the "failed access token request" analytics event with the response status code.""" + core.send_event(FailedAccessTokenRequestEvent(request, status_code=status_code)) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 7592abeae..5211f5653 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -50,6 +50,7 @@ def token(request): response = client.request_card_tokenization_access() except Exception as e: if isinstance(e, HTTPError) and e.response.status_code >= 500: + analytics.failed_access_token_request(request, e.response.status_code) sentry_sdk.capture_exception(e) data = {"redirect": reverse(ROUTE_SYSTEM_ERROR)} return JsonResponse(data) diff --git a/tests/pytest/enrollment/test_analytics.py b/tests/pytest/enrollment/test_analytics.py new file mode 100644 index 000000000..d774586a9 --- /dev/null +++ b/tests/pytest/enrollment/test_analytics.py @@ -0,0 +1,10 @@ +import pytest + +from benefits.enrollment.analytics import FailedAccessTokenRequestEvent + + +@pytest.mark.django_db +def test_FailedAccessTokenRequestEvent_sets_status_code(app_request): + event = FailedAccessTokenRequestEvent(app_request, 500) + + assert event.event_properties["status_code"] == 500 diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 231ba31fa..a5c93f366 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -105,7 +105,7 @@ def test_token_valid(mocker, client): @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") -def test_token_http_error_500(mocker, client): +def test_token_http_error_500(mocker, client, mocked_analytics_module): mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False) mock_client_cls = mocker.patch("benefits.enrollment.views.Client") @@ -126,6 +126,8 @@ def test_token_http_error_500(mocker, client): assert "token" not in data assert "redirect" in data assert data["redirect"] == reverse(ROUTE_SYSTEM_ERROR) + mocked_analytics_module.failed_access_token_request.assert_called_once() + assert 500 in mocked_analytics_module.failed_access_token_request.call_args.args @pytest.mark.django_db From 2953bb60f63fd923189e1f3bcd01999b66242b45 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 21 May 2024 20:35:18 +0000 Subject: [PATCH 022/111] test(sentry): add assertion for sentry method call --- tests/pytest/enrollment/test_views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index a5c93f366..da557956f 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -105,7 +105,7 @@ def test_token_valid(mocker, client): @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") -def test_token_http_error_500(mocker, client, mocked_analytics_module): +def test_token_http_error_500(mocker, client, mocked_analytics_module, mocked_sentry_sdk_module): mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False) mock_client_cls = mocker.patch("benefits.enrollment.views.Client") @@ -128,6 +128,7 @@ def test_token_http_error_500(mocker, client, mocked_analytics_module): assert data["redirect"] == reverse(ROUTE_SYSTEM_ERROR) mocked_analytics_module.failed_access_token_request.assert_called_once() assert 500 in mocked_analytics_module.failed_access_token_request.call_args.args + mocked_sentry_sdk_module.capture_exception.assert_called_once() @pytest.mark.django_db From 3769a007b42af8e310d4008fd342bf119676829c Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 21 May 2024 22:36:44 +0000 Subject: [PATCH 023/111] refactor: send sentry notification if any exception occurs during /token --- benefits/enrollment/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 5211f5653..9c59c5120 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -49,9 +49,10 @@ def token(request): try: response = client.request_card_tokenization_access() except Exception as e: + sentry_sdk.capture_exception(e) + if isinstance(e, HTTPError) and e.response.status_code >= 500: analytics.failed_access_token_request(request, e.response.status_code) - sentry_sdk.capture_exception(e) data = {"redirect": reverse(ROUTE_SYSTEM_ERROR)} return JsonResponse(data) else: From 746f32246847aeacad52490a1183344788b5a753 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 May 2024 16:14:04 +0000 Subject: [PATCH 024/111] refactor: show server error page if 400 or other exception during /token --- benefits/core/urls.py | 1 + benefits/enrollment/views.py | 20 +++++++++++++----- tests/pytest/enrollment/test_views.py | 29 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/benefits/core/urls.py b/benefits/core/urls.py index b8c32a639..2b7157ecc 100644 --- a/benefits/core/urls.py +++ b/benefits/core/urls.py @@ -50,4 +50,5 @@ def to_url(self, agency): path("", views.agency_index, name="agency_index"), path("/publickey", views.agency_public_key, name="agency_public_key"), path("logged_out", views.logged_out, name="logged_out"), + path("error", views.server_error, name="server-error"), ] diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 9c59c5120..b6d489860 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -21,6 +21,7 @@ ROUTE_INDEX = "enrollment:index" ROUTE_REENROLLMENT_ERROR = "enrollment:reenrollment-error" ROUTE_RETRY = "enrollment:retry" +ROUTE_SERVER_ERROR = "core:server-error" ROUTE_SUCCESS = "enrollment:success" ROUTE_SYSTEM_ERROR = "enrollment:system-error" ROUTE_TOKEN = "enrollment:token" @@ -51,12 +52,21 @@ def token(request): except Exception as e: sentry_sdk.capture_exception(e) - if isinstance(e, HTTPError) and e.response.status_code >= 500: - analytics.failed_access_token_request(request, e.response.status_code) - data = {"redirect": reverse(ROUTE_SYSTEM_ERROR)} - return JsonResponse(data) + if isinstance(e, HTTPError): + status_code = e.response.status_code + + if e.response.status_code >= 500: + redirect = reverse(ROUTE_SYSTEM_ERROR) + else: + redirect = reverse(ROUTE_SERVER_ERROR) else: - raise e + status_code = None + redirect = reverse(ROUTE_SERVER_ERROR) + + analytics.failed_access_token_request(request, status_code) + + data = {"redirect": redirect} + return JsonResponse(data) else: session.update( request, enrollment_token=response.get("access_token"), enrollment_token_exp=response.get("expires_at") diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index da557956f..3519d9a97 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -12,6 +12,7 @@ ROUTE_INDEX, ROUTE_REENROLLMENT_ERROR, ROUTE_RETRY, + ROUTE_SERVER_ERROR, ROUTE_SUCCESS, ROUTE_SYSTEM_ERROR, ROUTE_TOKEN, @@ -131,6 +132,34 @@ def test_token_http_error_500(mocker, client, mocked_analytics_module, mocked_se mocked_sentry_sdk_module.capture_exception.assert_called_once() +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") +def test_token_http_error_400(mocker, client, mocked_analytics_module, mocked_sentry_sdk_module): + mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False) + + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + + mock_error = {"message": "Mock error message"} + mock_error_response = mocker.Mock(status_code=400, **mock_error) + mock_error_response.json.return_value = mock_error + mock_client.request_card_tokenization_access.side_effect = HTTPError( + response=mock_error_response, + ) + + path = reverse(ROUTE_TOKEN) + response = client.get(path) + + assert response.status_code == 200 + data = response.json() + assert "token" not in data + assert "redirect" in data + assert data["redirect"] == reverse(ROUTE_SERVER_ERROR) + mocked_analytics_module.failed_access_token_request.assert_called_once() + assert 400 in mocked_analytics_module.failed_access_token_request.call_args.args + mocked_sentry_sdk_module.capture_exception.assert_called_once() + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") def test_index_eligible_get(client, model_EligibilityType): From 70624c18ee986677b91de2e327332bafd5671f5c Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 May 2024 16:38:00 +0000 Subject: [PATCH 025/111] test: add coverage for misconfigured payment processor client id --- tests/pytest/enrollment/test_views.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 3519d9a97..b87e4db28 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -1,6 +1,7 @@ import time import pytest +from authlib.integrations.base_client.errors import UnsupportedTokenTypeError from django.urls import reverse from littlepay.api.funding_sources import FundingSourceResponse from requests import HTTPError @@ -160,6 +161,28 @@ def test_token_http_error_400(mocker, client, mocked_analytics_module, mocked_se mocked_sentry_sdk_module.capture_exception.assert_called_once() +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") +def test_token_misconfigured_client_id(mocker, client, mocked_analytics_module, mocked_sentry_sdk_module): + mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False) + + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + + mock_client.request_card_tokenization_access.side_effect = UnsupportedTokenTypeError() + + path = reverse(ROUTE_TOKEN) + response = client.get(path) + + assert response.status_code == 200 + data = response.json() + assert "token" not in data + assert "redirect" in data + assert data["redirect"] == reverse(ROUTE_SERVER_ERROR) + mocked_analytics_module.failed_access_token_request.assert_called_once() + mocked_sentry_sdk_module.capture_exception.assert_called_once() + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") def test_index_eligible_get(client, model_EligibilityType): From 15b81a55b86b5a6038a4045000447b1d34b96a9d Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 May 2024 22:15:55 +0000 Subject: [PATCH 026/111] chore: add logging statement for /token exceptions --- benefits/enrollment/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index b6d489860..9be6cf0f3 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -50,6 +50,7 @@ def token(request): try: response = client.request_card_tokenization_access() except Exception as e: + logger.debug("Error occurred while requesting access token", exc_info=e) sentry_sdk.capture_exception(e) if isinstance(e, HTTPError): From 93e5130820888f93eb2a1b9d475cd27a84560b65 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 May 2024 22:27:18 +0000 Subject: [PATCH 027/111] feat: handle errors from calling `ensure_active_token` a misconfigured API URL could cause an error to be raised there --- benefits/enrollment/views.py | 2 +- tests/pytest/enrollment/test_views.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 9be6cf0f3..388ab9b29 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -45,9 +45,9 @@ def token(request): client_secret=payment_processor.client_secret, audience=payment_processor.audience, ) - client.oauth.ensure_active_token(client.token) try: + client.oauth.ensure_active_token(client.token) response = client.request_card_tokenization_access() except Exception as e: logger.debug("Error occurred while requesting access token", exc_info=e) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index b87e4db28..ddf3e37b5 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -183,6 +183,28 @@ def test_token_misconfigured_client_id(mocker, client, mocked_analytics_module, mocked_sentry_sdk_module.capture_exception.assert_called_once() +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") +def test_token_connection_error(mocker, client, mocked_analytics_module, mocked_sentry_sdk_module): + mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False) + + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + + mock_client.oauth.ensure_active_token.side_effect = ConnectionError() + + path = reverse(ROUTE_TOKEN) + response = client.get(path) + + assert response.status_code == 200 + data = response.json() + assert "token" not in data + assert "redirect" in data + assert data["redirect"] == reverse(ROUTE_SERVER_ERROR) + mocked_analytics_module.failed_access_token_request.assert_called_once() + mocked_sentry_sdk_module.capture_exception.assert_called_once() + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") def test_index_eligible_get(client, model_EligibilityType): From 1ca7cce7e5a345483568f2467f25a3291822aa89 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 May 2024 22:30:00 +0000 Subject: [PATCH 028/111] fix: prevent enrollment button from being enabled if redirecting --- benefits/enrollment/templates/enrollment/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/benefits/enrollment/templates/enrollment/index.html b/benefits/enrollment/templates/enrollment/index.html index 0a4d8c318..3c1a55efb 100644 --- a/benefits/enrollment/templates/enrollment/index.html +++ b/benefits/enrollment/templates/enrollment/index.html @@ -45,6 +45,7 @@

// https://stackoverflow.com/a/42469170 // use 'assign' because 'replace' was giving strange Back button behavior window.location.assign(data.redirect); + return; } $(".loading").remove(); From 8506b7bf3fe222a2010abf1657e7f87fdc9efe4c Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 May 2024 22:33:51 +0000 Subject: [PATCH 029/111] feat: handle errors from littlepay Client instantiation --- benefits/enrollment/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 388ab9b29..786352c04 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -39,14 +39,14 @@ def token(request): if not session.enrollment_token_valid(request): agency = session.agency(request) payment_processor = agency.payment_processor - client = Client( - base_url=payment_processor.api_base_url, - client_id=payment_processor.client_id, - client_secret=payment_processor.client_secret, - audience=payment_processor.audience, - ) try: + client = Client( + base_url=payment_processor.api_base_url, + client_id=payment_processor.client_id, + client_secret=payment_processor.client_secret, + audience=payment_processor.audience, + ) client.oauth.ensure_active_token(client.token) response = client.request_card_tokenization_access() except Exception as e: From 64c404b89579b1d43e6a4e48a64c03b17ccce4c2 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 23 May 2024 12:10:34 -0500 Subject: [PATCH 030/111] docs: add details about tag annotation --- docs/deployment/release.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/deployment/release.md b/docs/deployment/release.md index cee6c7e03..3fa542cad 100644 --- a/docs/deployment/release.md +++ b/docs/deployment/release.md @@ -88,7 +88,11 @@ git checkout prod git reset --hard origin/prod git tag -a YYYY.0M.R +``` + +Git will open your default text editor and prompt you for the tag annotation. For the tag annotation, use the title of the `release`-tagged Issue that kicked off the release. Finally, after closing the text editor: +```bash git push origin YYYY.0M.R ``` From 2b4ef008de1d0a430870026a965527823a331099 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 23 May 2024 20:29:16 +0000 Subject: [PATCH 031/111] feat: update onError callback to send to system error or server error rather than retry page --- benefits/enrollment/forms.py | 4 ++-- benefits/enrollment/templates/enrollment/index.html | 6 +++++- benefits/enrollment/views.py | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/benefits/enrollment/forms.py b/benefits/enrollment/forms.py index 9606d7085..d9f72e146 100644 --- a/benefits/enrollment/forms.py +++ b/benefits/enrollment/forms.py @@ -19,12 +19,12 @@ class CardTokenizeSuccessForm(forms.Form): class CardTokenizeFailForm(forms.Form): """Form to indicate card tokenization failure to server.""" - id = "form-card-tokenize-fail" method = "POST" - def __init__(self, action_url, *args, **kwargs): + def __init__(self, action_url, id, *args, **kwargs): # init super with an empty data dict # binds and makes immutable this form's data # since there are no form fields, the form is also marked as valid super().__init__({}, *args, **kwargs) + self.id = id self.action_url = action_url diff --git a/benefits/enrollment/templates/enrollment/index.html b/benefits/enrollment/templates/enrollment/index.html index 3c1a55efb..4f58d5908 100644 --- a/benefits/enrollment/templates/enrollment/index.html +++ b/benefits/enrollment/templates/enrollment/index.html @@ -94,7 +94,11 @@

/* 400 or 500 will return. */ amplitude.getInstance().logEvent(closedEvent, {status: "error", error: response}); - var form = $("form#{{ form_retry }}"); + if (response.status >= 500) { + var form = $("form#{{ form_system_error }}"); + } else { + var form = $("form#{{ form_server_error }}"); + } form.submit(); }, onCancel: function () { diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 786352c04..d2f30d9d1 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -132,18 +132,22 @@ def index(request): # GET enrollment index else: - tokenize_retry_form = forms.CardTokenizeFailForm(ROUTE_RETRY) + tokenize_retry_form = forms.CardTokenizeFailForm(ROUTE_RETRY, "form-card-tokenize-fail-retry") + tokenize_server_error_form = forms.CardTokenizeFailForm(ROUTE_SERVER_ERROR, "form-card-tokenize-fail-server-error") + tokenize_system_error_form = forms.CardTokenizeFailForm(ROUTE_SYSTEM_ERROR, "form-card-tokenize-fail-system-error") tokenize_success_form = forms.CardTokenizeSuccessForm(auto_id=True, label_suffix="") context = { - "forms": [tokenize_retry_form, tokenize_success_form], + "forms": [tokenize_retry_form, tokenize_server_error_form, tokenize_system_error_form, tokenize_success_form], "cta_button": "tokenize_card", "card_tokenize_env": agency.payment_processor.card_tokenize_env, "card_tokenize_func": agency.payment_processor.card_tokenize_func, "card_tokenize_url": agency.payment_processor.card_tokenize_url, "token_field": "card_token", "form_retry": tokenize_retry_form.id, + "form_server_error": tokenize_server_error_form.id, "form_success": tokenize_success_form.id, + "form_system_error": tokenize_system_error_form.id, } logger.debug(f'card_tokenize_url: {context["card_tokenize_url"]}') From 1844bcef3c39a7353dac99897492c404c908e837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 23:11:54 +0000 Subject: [PATCH 032/111] chore(deps): bump django-admin-sortable2 from 2.1.10 to 2.2.1 Bumps [django-admin-sortable2](https://github.com/jrief/django-admin-sortable2) from 2.1.10 to 2.2.1. - [Release notes](https://github.com/jrief/django-admin-sortable2/releases) - [Changelog](https://github.com/jrief/django-admin-sortable2/blob/master/CHANGELOG.md) - [Commits](https://github.com/jrief/django-admin-sortable2/compare/2.1.10...2.2.1) --- updated-dependencies: - dependency-name: django-admin-sortable2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f7470832f..93db62af3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "azure-identity==1.16.0", "Django==5.0.6", "django-csp==3.8", - "django-admin-sortable2==2.1.10", + "django-admin-sortable2==2.2.1", "django-google-sso==6.2.0", "eligibility-api==2023.9.1", "calitp-littlepay==2024.4.1", From 668ccd8c0044f707cdbd07b78995f8b77c3a8493 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 23:20:22 +0000 Subject: [PATCH 033/111] --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 655941a89..cc12261f7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ mdx_truly_sane_lists mkdocs==1.6.0 mkdocs-awesome-pages-plugin mkdocs-macros-plugin -mkdocs-material==9.5.22 +mkdocs-material==9.5.24 mkdocs-redirects From 0bcf28065997239735a4666bdb1cda713fc49cb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 16:25:06 +0000 Subject: [PATCH 034/111] --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 93db62af3..22f46ec80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "django-google-sso==6.2.0", "eligibility-api==2023.9.1", "calitp-littlepay==2024.4.1", - "requests==2.31.0", + "requests==2.32.2", "sentry-sdk==2.1.1", "six==1.16.0", ] From c172c564a34686b5fbbc031cbeccbc9a88e4f3f2 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Fri, 24 May 2024 19:05:43 +0000 Subject: [PATCH 035/111] ci: add initial workflow definition for checking makemigrations run when files under /benefits are changed in a pull request or push --- .github/workflows/check-makemigrations.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/workflows/check-makemigrations.yml diff --git a/.github/workflows/check-makemigrations.yml b/.github/workflows/check-makemigrations.yml new file mode 100644 index 000000000..44808935e --- /dev/null +++ b/.github/workflows/check-makemigrations.yml @@ -0,0 +1,8 @@ +name: Check for up-to-date Django migrations +on: + pull_request: + paths: + - "benefits/**" + push: + paths: + - "benefits/**" From f42e6a3166f313c837b2f8dc0a29eff06f1633fb Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Fri, 24 May 2024 19:06:37 +0000 Subject: [PATCH 036/111] ci: add steps needed to set up environment --- .github/workflows/check-makemigrations.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/check-makemigrations.yml b/.github/workflows/check-makemigrations.yml index 44808935e..3eab290d2 100644 --- a/.github/workflows/check-makemigrations.yml +++ b/.github/workflows/check-makemigrations.yml @@ -6,3 +6,24 @@ on: push: paths: - "benefits/**" + +jobs: + check-makemigrations: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Install system packages + run: | + sudo apt-get update -y + sudo apt-get install -y gettext + + - uses: actions/setup-python@v5 + with: + python-version-file: .github/workflows/.python-version + cache: pip + cache-dependency-path: "**/pyproject.toml" + + - name: Install Python dependencies + run: pip install -e .[dev,test] From 52e0906ce05d9546e5835ac9d6e1fa0b9fc2cb35 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Fri, 24 May 2024 19:07:50 +0000 Subject: [PATCH 037/111] ci: add step to run helper script and check output fail if expected message is not present ("No changes detected") --- .github/workflows/check-makemigrations.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/check-makemigrations.yml b/.github/workflows/check-makemigrations.yml index 3eab290d2..f79537f60 100644 --- a/.github/workflows/check-makemigrations.yml +++ b/.github/workflows/check-makemigrations.yml @@ -27,3 +27,12 @@ jobs: - name: Install Python dependencies run: pip install -e .[dev,test] + + - name: Run ./bin/makemigrations.sh + run: | + if ./bin/makemigrations.sh | grep -q 'No changes detected'; + then + exit 0; + else + exit 1; + fi From bdde33bc8b173ea75b3e0f92433720bb16e66837 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 24 May 2024 17:44:51 -0700 Subject: [PATCH 038/111] feat(terraform): config for new GitHub Actions based deploy using https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_source_control --- terraform/app_service.tf | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 1b3679022..f77fa6fbb 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -25,10 +25,6 @@ resource "azurerm_linux_web_app" "main" { site_config { ftps_state = "Disabled" vnet_route_all_enabled = true - application_stack { - docker_image = "ghcr.io/cal-itp/benefits" - docker_image_tag = local.env_name - } } identity { @@ -52,8 +48,7 @@ resource "azurerm_linux_web_app" "main" { # app setting used solely for refreshing secrets - see https://github.com/MicrosoftDocs/azure-docs/issues/79855#issuecomment-1265664801 "change_me_to_refresh_secrets" = "change me in the portal to refresh all secrets", - "DOCKER_ENABLE_CI" = "true", - "DOCKER_REGISTRY_SERVER_URL" = "https://ghcr.io/", + "DOCKER_ENABLE_CI" = "false", "WEBSITE_HTTPLOGGING_RETENTION_DAYS" = "99999", "WEBSITE_TIME_ZONE" = "America/Los_Angeles", "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false", @@ -110,6 +105,22 @@ resource "azurerm_linux_web_app" "main" { } } +resource "azurerm_app_service_source_control" "main" { + app_id = azurerm_linux_web_app.main.id + repo_url = "https://github.com/cal-itp/benefits" + branch = local.env_name + rollback_enabled = true + + github_action_configuration { + generate_workflow_file = false + + container_configuration { + image_name = "cal-itp/benefits" + registry_url = "https://ghcr.io/" + } + } +} + resource "azurerm_app_service_custom_hostname_binding" "main" { hostname = local.hostname app_service_name = azurerm_linux_web_app.main.name From 54a88b3b6e8436d6b48f86c255bdaec30efa02dd Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 24 May 2024 17:51:34 -0700 Subject: [PATCH 039/111] docs(deployment): update with new GitHub Actions info remove outdated references to webhooks and branch tags --- docs/deployment/README.md | 1 - docs/deployment/infrastructure.md | 5 ++--- docs/deployment/workflows.md | 5 ++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/deployment/README.md b/docs/deployment/README.md index c3e7046cc..e7a50a493 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -30,7 +30,6 @@ Docker images for each of the deploy branches are available from GitHub Containe - [Repository Package page](https://github.com/cal-itp/benefits/pkgs/container/benefits) - Image path: `ghcr.io/cal-itp/benefits` -- Image tags: `dev`, `test`, `prod` [oet]: https://techblog.cdt.ca.gov/2020/06/cdt-taking-the-lead-in-digital-transformation/ [app-service-containers]: https://docs.microsoft.com/en-us/azure/app-service/configure-custom-container diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index 14047ae23..e16212a24 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -180,11 +180,10 @@ Use the following shorthand for conveying the Resource Type as part of the Resou ## Azure environment setup -The following steps are required to set up the environment, with linked issues to automate them: +The following steps are required to set up the environment: - `terraform apply` -- Set up Slack notifications by [creating a Slack email](https://slack.com/help/articles/206819278-Send-emails-to-Slack) for the [#benefits-notify](https://cal-itp.slack.com/archives/C022HHSEE3F) channel, then [setting it as a Secret in the Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/secrets/quick-create-portal#add-a-secret-to-key-vault) named `slack-benefits-notify-email` +- Set up Slack notifications by [creating a Slack email](https://slack.com/help/articles/206819278-Send-emails-to-Slack) for the [#notify-benefits](https://cal-itp.slack.com/archives/C022HHSEE3F) channel, then [setting it as a Secret in the Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/secrets/quick-create-portal#add-a-secret-to-key-vault) named `slack-benefits-notify-email` - Set required [App Service configuration](../configuration/environment-variables.md) and [configuration](../configuration/data.md) by setting values in Key Vault (the mapping is defined in [app_service.tf](https://github.com/cal-itp/benefits/blob/dev/terraform/app_service.tf)) -- [Set up webhook from GitHub](https://github.com/cal-itp/benefits/settings/hooks) to [App Service Deployment Center](https://learn.microsoft.com/en-us/azure/app-service/deploy-ci-cd-custom-container?tabs=acr&pivots=container-linux) for the `Packages` event This is not a complete step-by-step guide; more a list of things to remember. This may be useful as part of [incident response](https://docs.google.com/document/d/1qtev8qItPiTB4Tp9FQ87XsLtWZ4HlNXqoe9vF2VuGcY/edit#). diff --git a/docs/deployment/workflows.md b/docs/deployment/workflows.md index 2c7d7f47c..ff5b21f42 100644 --- a/docs/deployment/workflows.md +++ b/docs/deployment/workflows.md @@ -23,16 +23,15 @@ Using the `github.actor` and built-in `GITHUB_TOKEN` secret ### 3. Build and push image to GitHub Container Registry (GHCR) -Build the root [`Dockerfile`][dockerfile], tagging with both the branch name (e.g. `dev`) and the SHA from the HEAD commit. +Build the root [`Dockerfile`][dockerfile], tagging with the SHA from the HEAD commit. Push this image:tag into [GHCR][ghcr]. ### 4. App Service deploy -Each Azure App Service instance is configured to [listen to a webhook from GitHub, then deploy the image][webhook]. +Push the new image:tag to the Azure App Service instance. [deploy]: https://github.com/cal-itp/benefits/blob/dev/.github/workflows/deploy.yml [dockerfile]: https://github.com/cal-itp/benefits/blob/dev/Dockerfile [ghcr]: https://github.com/features/packages [gh-actions-trigger]: https://docs.github.com/en/actions/reference/events-that-trigger-workflows -[webhook]: https://docs.microsoft.com/en-us/azure/app-service/deploy-ci-cd-custom-container From 82dc82f8798cc61713fbd7da84d3f05d55ed30f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 21:14:49 +0000 Subject: [PATCH 040/111] chore(deps): bump mkdocs-material from 9.5.24 to 9.5.25 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.24 to 9.5.25. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.24...9.5.25) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index cc12261f7..8eab93ae0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ mdx_truly_sane_lists mkdocs==1.6.0 mkdocs-awesome-pages-plugin mkdocs-macros-plugin -mkdocs-material==9.5.24 +mkdocs-material==9.5.25 mkdocs-redirects From 410382638585c4d81e1722f457c6bf7cca7b6be6 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 28 May 2024 18:18:36 +0000 Subject: [PATCH 041/111] chore: add comment clarifying why early return is needed --- benefits/enrollment/templates/enrollment/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/enrollment/templates/enrollment/index.html b/benefits/enrollment/templates/enrollment/index.html index 3c1a55efb..b18c56b09 100644 --- a/benefits/enrollment/templates/enrollment/index.html +++ b/benefits/enrollment/templates/enrollment/index.html @@ -45,7 +45,7 @@

// https://stackoverflow.com/a/42469170 // use 'assign' because 'replace' was giving strange Back button behavior window.location.assign(data.redirect); - return; + return; // exit early so the rest of this function doesn't execute } $(".loading").remove(); From ce12a50e3efb5626aed4c78f9c1bb9d91da64fde Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 28 May 2024 18:26:15 +0000 Subject: [PATCH 042/111] chore: move route name into the app it belongs to --- benefits/core/views.py | 1 + benefits/enrollment/views.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benefits/core/views.py b/benefits/core/views.py index 9bd16880c..359cf19cb 100644 --- a/benefits/core/views.py +++ b/benefits/core/views.py @@ -12,6 +12,7 @@ ROUTE_ELIGIBILITY = "eligibility:index" ROUTE_HELP = "core:help" ROUTE_LOGGED_OUT = "core:logged_out" +ROUTE_SERVER_ERROR = "core:server-error" TEMPLATE_INDEX = "core/index.html" TEMPLATE_AGENCY = "core/agency-index.html" diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 786352c04..20deab2e0 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -14,14 +14,13 @@ from benefits.core import session from benefits.core.middleware import EligibleSessionRequired, VerifierSessionRequired, pageview_decorator -from benefits.core.views import ROUTE_LOGGED_OUT +from benefits.core.views import ROUTE_LOGGED_OUT, ROUTE_SERVER_ERROR from . import analytics, forms ROUTE_INDEX = "enrollment:index" ROUTE_REENROLLMENT_ERROR = "enrollment:reenrollment-error" ROUTE_RETRY = "enrollment:retry" -ROUTE_SERVER_ERROR = "core:server-error" ROUTE_SUCCESS = "enrollment:success" ROUTE_SYSTEM_ERROR = "enrollment:system-error" ROUTE_TOKEN = "enrollment:token" From 8fbb9876dff7f7fba87d4a74112cc03d238675e4 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Tue, 21 May 2024 21:23:59 +0000 Subject: [PATCH 043/111] refactor(oauth): update view to handle integer claims --- benefits/oauth/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index 86db149fe..ae24c2f40 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -69,11 +69,12 @@ def authorize(request): if userinfo: claim_value = userinfo.get(verifier_claim) - # the claim comes back in userinfo like { "claim": "True" | "False" } + # the claim comes back in userinfo like { "claim": "1" | "0" } + claim_value = int(claim_value) if claim_value else None if claim_value is None: logger.warning(f"userinfo did not contain: {verifier_claim}") - elif claim_value.lower() == "true": - # if userinfo contains our claim and the flag is true, store the *claim* + elif claim_value == 1: + # if userinfo contains our claim and the flag is 1 (true), store the *claim* stored_claim = verifier_claim session.update(request, oauth_token=id_token, oauth_claim=stored_claim) From b08e2ec79cfab4777fe9528c424ac73b40ea19d8 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Wed, 22 May 2024 19:16:20 +0000 Subject: [PATCH 044/111] refactor(oauth): update tests to handle integer claims --- tests/pytest/oauth/test_views.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 0f7493fc0..3c322ff71 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -99,14 +99,11 @@ def test_authorize_success(mocked_oauth_create_client, mocked_analytics_module, @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") -@pytest.mark.parametrize("flag", ["true", "True", "tRuE"]) -def test_authorize_success_with_claim_true( - mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request, flag -): +def test_authorize_success_with_claim_true(mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request): verifier = mocked_session_verifier_auth_required.return_value verifier.auth_provider.claim = "claim" mocked_oauth_client = mocked_oauth_create_client.return_value - mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": flag}} + mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "1"}} result = authorize(app_request) @@ -118,14 +115,15 @@ def test_authorize_success_with_claim_true( @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") -@pytest.mark.parametrize("flag", ["false", "False", "fAlSe"]) def test_authorize_success_with_claim_false( - mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request, flag + mocked_session_verifier_auth_required, + mocked_oauth_create_client, + app_request, ): verifier = mocked_session_verifier_auth_required.return_value verifier.auth_provider.claim = "claim" mocked_oauth_client = mocked_oauth_create_client.return_value - mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": flag}} + mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "0"}} result = authorize(app_request) From 540e46ead7de17ecaa24f70cf51ab280275b0983 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 21:05:59 +0000 Subject: [PATCH 045/111] chore(deps): bump requests from 2.32.2 to 2.32.3 Bumps [requests](https://github.com/psf/requests) from 2.32.2 to 2.32.3. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.3) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 22f46ec80..adca4fcbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "django-google-sso==6.2.0", "eligibility-api==2023.9.1", "calitp-littlepay==2024.4.1", - "requests==2.32.2", + "requests==2.32.3", "sentry-sdk==2.1.1", "six==1.16.0", ] From b55ab5ef5f547f63855090cea519703f960eff80 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 30 May 2024 20:18:51 +0000 Subject: [PATCH 046/111] fix(test): assertions for missing claim on AuthProvider were not correct the test was giving us a false positive because the verifier fixture is also not behaving correctly when returning `uses_auth_verification`. the test now fails, but the assertions are accurate based on what we have in `benefits.oauth.views.authorize` and `benefits.oauth.middleware`. --- tests/pytest/oauth/test_views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 3c322ff71..f03ddb626 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -145,10 +145,9 @@ def test_authorize_success_without_verifier_claim( result = authorize(app_request) - mocked_oauth_client.authorize_access_token.assert_called_with(app_request) assert session.oauth_claim(app_request) is None - assert result.status_code == 302 - assert result.url == reverse(ROUTE_CONFIRM) + assert result.status_code == 200 + assert result.template_name == TEMPLATE_USER_ERROR @pytest.mark.django_db From cea5e5da74c3a1e1e01b798e43f4d94ec75e30f4 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 30 May 2024 19:57:27 +0000 Subject: [PATCH 047/111] refactor(tests): rename some verifier fixtures to clarify intent the difference between "requiring auth" and "using auth verification": the former means the verifier has an AuthProvider, and the latter means the verifier has an AuthProvider that has a scope and claim. these fixtures were not using that terminology correctly. --- tests/pytest/conftest.py | 12 +++--- tests/pytest/eligibility/test_verify.py | 20 +++++----- tests/pytest/eligibility/test_views.py | 14 +++++-- tests/pytest/enrollment/test_views.py | 6 +-- tests/pytest/oauth/test_analytics.py | 12 +++--- .../test_middleware_authverifier_required.py | 2 +- tests/pytest/oauth/test_views.py | 40 ++++++++++--------- 7 files changed, 60 insertions(+), 46 deletions(-) diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index a388856ac..f5c9c2d0b 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -274,7 +274,7 @@ def mocked_session_verifier_oauth(mocker, model_EligibilityVerifier_AuthProvider @pytest.fixture -def mocked_session_verifier_auth_required( +def mocked_session_verifier_uses_auth_verification( mocker, model_EligibilityVerifier_AuthProvider_with_verification, mocked_session_verifier_oauth ): mock_verifier = mocker.Mock(spec=model_EligibilityVerifier_AuthProvider_with_verification) @@ -291,11 +291,11 @@ def mocked_session_verifier_auth_required( @pytest.fixture -def mocked_session_verifier_auth_not_required(mocked_session_verifier_auth_required): - # mocked_session_verifier_auth_required.return_value is the Mock(spec=model_EligibilityVerifier) from that fixture - mocked_session_verifier_auth_required.return_value.is_auth_required = False - mocked_session_verifier_auth_required.return_value.uses_auth_verification = False - return mocked_session_verifier_auth_required +def mocked_session_verifier_does_not_use_auth_verification(mocked_session_verifier_uses_auth_verification): + # mocked_session_verifier_uses_auth_verification.return_value is the Mock(spec=model_EligibilityVerifier) from that fixture + mocked_session_verifier_uses_auth_verification.return_value.is_auth_required = False + mocked_session_verifier_uses_auth_verification.return_value.uses_auth_verification = False + return mocked_session_verifier_uses_auth_verification @pytest.fixture diff --git a/tests/pytest/eligibility/test_verify.py b/tests/pytest/eligibility/test_verify.py index 2e73c496d..37086ea1b 100644 --- a/tests/pytest/eligibility/test_verify.py +++ b/tests/pytest/eligibility/test_verify.py @@ -52,10 +52,12 @@ def test_eligibility_from_api_no_verified_types( @pytest.mark.django_db -def test_eligibility_from_oauth_auth_not_required(mocked_session_verifier_auth_not_required, model_TransitAgency): - # mocked_session_verifier_auth_not_required is Mocked version of the session.verifier() function +def test_eligibility_from_oauth_does_not_use_auth_verification( + mocked_session_verifier_does_not_use_auth_verification, model_TransitAgency +): + # mocked_session_verifier_does_not_use_auth_verification is Mocked version of the session.verifier() function # call it (with a None request) to return a verifier object - verifier = mocked_session_verifier_auth_not_required(None) + verifier = mocked_session_verifier_does_not_use_auth_verification(None) types = eligibility_from_oauth(verifier, "claim", model_TransitAgency) @@ -63,10 +65,10 @@ def test_eligibility_from_oauth_auth_not_required(mocked_session_verifier_auth_n @pytest.mark.django_db -def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_auth_required, model_TransitAgency): - # mocked_session_verifier_auth_required is Mocked version of the session.verifier() function +def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_uses_auth_verification, model_TransitAgency): + # mocked_session_verifier_uses_auth_verification is Mocked version of the session.verifier() function # call it (with a None request) to return a verifier object - verifier = mocked_session_verifier_auth_required(None) + verifier = mocked_session_verifier_uses_auth_verification(None) verifier.auth_claim = "claim" types = eligibility_from_oauth(verifier, "some_other_claim", model_TransitAgency) @@ -76,11 +78,11 @@ def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_auth @pytest.mark.django_db def test_eligibility_from_oauth_auth_claim_match( - mocked_session_verifier_auth_required, model_EligibilityType, model_TransitAgency + mocked_session_verifier_uses_auth_verification, model_EligibilityType, model_TransitAgency ): - # mocked_session_verifier_auth_required is Mocked version of the session.verifier() function + # mocked_session_verifier_uses_auth_verification is Mocked version of the session.verifier() function # call it (with a None request) to return a verifier object - verifier = mocked_session_verifier_auth_required.return_value + verifier = mocked_session_verifier_uses_auth_verification.return_value verifier.auth_provider.claim = "claim" verifier.eligibility_type = model_EligibilityType diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index 4cc08407e..353c99ccf 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -171,7 +171,9 @@ def test_index_calls_session_logout(client, session_logout_spy): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_auth_required") +@pytest.mark.usefixtures( + "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_uses_auth_verification" +) def test_start_verifier_auth_required_logged_in(mocker, client): mock_session = mocker.patch("benefits.eligibility.views.session") mock_session.logged_in.return_value = True @@ -183,7 +185,9 @@ def test_start_verifier_auth_required_logged_in(mocker, client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_auth_required") +@pytest.mark.usefixtures( + "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_uses_auth_verification" +) def test_start_verifier_auth_required_not_logged_in(mocker, client): mock_session = mocker.patch("benefits.eligibility.views.session") mock_session.logged_in.return_value = False @@ -196,7 +200,7 @@ def test_start_verifier_auth_required_not_logged_in(mocker, client): @pytest.mark.django_db @pytest.mark.usefixtures( - "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_auth_not_required" + "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_does_not_use_auth_verification" ) def test_start_verifier_auth_not_required(client): path = reverse(ROUTE_START) @@ -226,7 +230,9 @@ def test_confirm_get_unverified(mocker, client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility", "mocked_session_verifier_auth_not_required") +@pytest.mark.usefixtures( + "mocked_session_agency", "mocked_session_eligibility", "mocked_session_verifier_does_not_use_auth_verification" +) def test_confirm_get_verified(client, mocked_session_update): path = reverse(ROUTE_CONFIRM) response = client.get(path) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index da557956f..e4db5a812 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -354,7 +354,7 @@ def test_success_no_verifier(client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_success_authentication_logged_in(mocker, client, model_TransitAgency, model_EligibilityType): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.logged_in.return_value = True @@ -370,7 +370,7 @@ def test_success_authentication_logged_in(mocker, client, model_TransitAgency, m @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_success_authentication_not_logged_in(mocker, client, model_TransitAgency, model_EligibilityType): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.logged_in.return_value = False @@ -385,7 +385,7 @@ def test_success_authentication_not_logged_in(mocker, client, model_TransitAgenc @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier_auth_not_required") +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier_does_not_use_auth_verification") def test_success_no_authentication(mocker, client, model_EligibilityType): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.eligibility.return_value = model_EligibilityType diff --git a/tests/pytest/oauth/test_analytics.py b/tests/pytest/oauth/test_analytics.py index 1d1149282..166d1fc11 100644 --- a/tests/pytest/oauth/test_analytics.py +++ b/tests/pytest/oauth/test_analytics.py @@ -4,8 +4,8 @@ @pytest.mark.django_db -def test_OAuthEvent_checks_verifier_uses_auth_verification(app_request, mocked_session_verifier_auth_required): - mocked_verifier = mocked_session_verifier_auth_required(app_request) +def test_OAuthEvent_checks_verifier_uses_auth_verification(app_request, mocked_session_verifier_uses_auth_verification): + mocked_verifier = mocked_session_verifier_uses_auth_verification(app_request) OAuthEvent(app_request, "event type") @@ -13,8 +13,10 @@ def test_OAuthEvent_checks_verifier_uses_auth_verification(app_request, mocked_s @pytest.mark.django_db -def test_OAuthEvent_verifier_client_name_when_uses_auth_verification(app_request, mocked_session_verifier_auth_required): - mocked_verifier = mocked_session_verifier_auth_required(app_request) +def test_OAuthEvent_verifier_client_name_when_uses_auth_verification( + app_request, mocked_session_verifier_uses_auth_verification +): + mocked_verifier = mocked_session_verifier_uses_auth_verification(app_request) mocked_verifier.auth_provider.client_name = "ClientName" event = OAuthEvent(app_request, "event type") @@ -24,7 +26,7 @@ def test_OAuthEvent_verifier_client_name_when_uses_auth_verification(app_request @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_not_required") +@pytest.mark.usefixtures("mocked_session_verifier_does_not_use_auth_verification") def test_OAuthEvent_verifier_no_client_name_when_does_not_use_auth_verification(app_request): event = OAuthEvent(app_request, "event type") diff --git a/tests/pytest/oauth/test_middleware_authverifier_required.py b/tests/pytest/oauth/test_middleware_authverifier_required.py index 5d1440279..6fc479574 100644 --- a/tests/pytest/oauth/test_middleware_authverifier_required.py +++ b/tests/pytest/oauth/test_middleware_authverifier_required.py @@ -21,7 +21,7 @@ def test_authverifier_required_no_verifier(app_request, mocked_view, decorated_v @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_not_required") +@pytest.mark.usefixtures("mocked_session_verifier_does_not_use_auth_verification") def test_authverifier_required_no_authverifier(app_request, mocked_view, decorated_view): response = decorated_view(app_request) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index f03ddb626..ab54c7a1a 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -16,7 +16,7 @@ def mocked_analytics_module(mocked_analytics_module): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_login_no_oauth_client(mocked_oauth_create_client, app_request): mocked_oauth_create_client.return_value = None @@ -33,7 +33,9 @@ def test_login_no_session_verifier(app_request): @pytest.mark.django_db -def test_login(mocked_oauth_create_client, mocked_session_verifier_auth_required, mocked_analytics_module, app_request): +def test_login( + mocked_oauth_create_client, mocked_session_verifier_uses_auth_verification, mocked_analytics_module, app_request +): assert not session.logged_in(app_request) mocked_oauth_client = mocked_oauth_create_client.return_value @@ -41,7 +43,7 @@ def test_login(mocked_oauth_create_client, mocked_session_verifier_auth_required login(app_request) - mocked_verifier = mocked_session_verifier_auth_required.return_value + mocked_verifier = mocked_session_verifier_uses_auth_verification.return_value mocked_oauth_create_client.assert_called_once_with(mocked_verifier.auth_provider.client_name) mocked_oauth_client.authorize_redirect.assert_called_with(app_request, "https://testserver/oauth/authorize") mocked_analytics_module.started_sign_in.assert_called_once() @@ -49,7 +51,7 @@ def test_login(mocked_oauth_create_client, mocked_session_verifier_auth_required @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_authorize_no_oauth_client(mocked_oauth_create_client, app_request): mocked_oauth_create_client.return_value = None @@ -66,7 +68,7 @@ def test_authorize_no_session_verifier(app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_authorize_fail(mocked_oauth_create_client, app_request): mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_access_token.return_value = None @@ -82,7 +84,7 @@ def test_authorize_fail(mocked_oauth_create_client, app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_authorize_success(mocked_oauth_create_client, mocked_analytics_module, app_request): mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token"} @@ -99,8 +101,10 @@ def test_authorize_success(mocked_oauth_create_client, mocked_analytics_module, @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") -def test_authorize_success_with_claim_true(mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request): - verifier = mocked_session_verifier_auth_required.return_value +def test_authorize_success_with_claim_true( + mocked_session_verifier_uses_auth_verification, mocked_oauth_create_client, app_request +): + verifier = mocked_session_verifier_uses_auth_verification.return_value verifier.auth_provider.claim = "claim" mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "1"}} @@ -116,11 +120,11 @@ def test_authorize_success_with_claim_true(mocked_session_verifier_auth_required @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") def test_authorize_success_with_claim_false( - mocked_session_verifier_auth_required, + mocked_session_verifier_uses_auth_verification, mocked_oauth_create_client, app_request, ): - verifier = mocked_session_verifier_auth_required.return_value + verifier = mocked_session_verifier_uses_auth_verification.return_value verifier.auth_provider.claim = "claim" mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "0"}} @@ -136,9 +140,9 @@ def test_authorize_success_with_claim_false( @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") def test_authorize_success_without_verifier_claim( - mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request + mocked_session_verifier_uses_auth_verification, mocked_oauth_create_client, app_request ): - verifier = mocked_session_verifier_auth_required.return_value + verifier = mocked_session_verifier_uses_auth_verification.return_value verifier.auth_provider.claim = "" mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} @@ -160,9 +164,9 @@ def test_authorize_success_without_verifier_claim( ], ) def test_authorize_success_without_claim_in_response( - mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request, access_token_response + mocked_session_verifier_uses_auth_verification, mocked_oauth_create_client, app_request, access_token_response ): - verifier = mocked_session_verifier_auth_required.return_value + verifier = mocked_session_verifier_uses_auth_verification.return_value verifier.auth_provider.claim = "claim" mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_access_token.return_value = access_token_response @@ -176,7 +180,7 @@ def test_authorize_success_without_claim_in_response( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_cancel(mocked_analytics_module, app_request): unverified_route = reverse(ROUTE_UNVERIFIED) @@ -196,7 +200,7 @@ def test_cancel_no_session_verifier(app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_logout_no_oauth_client(mocked_oauth_create_client, app_request): mocked_oauth_create_client.return_value = None @@ -213,7 +217,7 @@ def test_logout_no_session_verifier(app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_logout(mocker, mocked_oauth_create_client, mocked_analytics_module, app_request): # logout internally calls deauthorize_redirect # this mocks that function and a success response @@ -240,7 +244,7 @@ def test_logout(mocker, mocked_oauth_create_client, mocked_analytics_module, app @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") def test_post_logout(app_request, mocked_analytics_module): origin = reverse(ROUTE_INDEX) session.update(app_request, origin=origin) From c8f7949f3504a72c2628eb2195fcbd52c0cf9136 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 30 May 2024 20:15:43 +0000 Subject: [PATCH 048/111] refactor(tests): use model objects instead of Mock objects for verifier this ensures we use the actual implementation of uses_auth_verification instead of it returning a Mock object which is evaluated to True --- tests/pytest/conftest.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index f5c9c2d0b..60e12ef7c 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -275,11 +275,10 @@ def mocked_session_verifier_oauth(mocker, model_EligibilityVerifier_AuthProvider @pytest.fixture def mocked_session_verifier_uses_auth_verification( - mocker, model_EligibilityVerifier_AuthProvider_with_verification, mocked_session_verifier_oauth + model_EligibilityVerifier_AuthProvider_with_verification, mocked_session_verifier_oauth ): - mock_verifier = mocker.Mock(spec=model_EligibilityVerifier_AuthProvider_with_verification) + mock_verifier = model_EligibilityVerifier_AuthProvider_with_verification mock_verifier.name = model_EligibilityVerifier_AuthProvider_with_verification.name - mock_verifier.is_auth_required = True mock_verifier.auth_provider.sign_out_button_template = ( model_EligibilityVerifier_AuthProvider_with_verification.auth_provider.sign_out_button_template ) @@ -291,11 +290,12 @@ def mocked_session_verifier_uses_auth_verification( @pytest.fixture -def mocked_session_verifier_does_not_use_auth_verification(mocked_session_verifier_uses_auth_verification): - # mocked_session_verifier_uses_auth_verification.return_value is the Mock(spec=model_EligibilityVerifier) from that fixture - mocked_session_verifier_uses_auth_verification.return_value.is_auth_required = False - mocked_session_verifier_uses_auth_verification.return_value.uses_auth_verification = False - return mocked_session_verifier_uses_auth_verification +def mocked_session_verifier_does_not_use_auth_verification( + mocked_session_verifier_uses_auth_verification, model_AuthProvider_without_verification +): + mocked_verifier = mocked_session_verifier_uses_auth_verification + mocked_verifier.auth_provider = model_AuthProvider_without_verification + return mocked_verifier @pytest.fixture From 2f4ccda66a88a0459d0d7f4ed61eb53b90b4cf0e Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 30 May 2024 20:47:26 +0000 Subject: [PATCH 049/111] fix(test): use fixture that matches intent of the test --- tests/pytest/eligibility/test_views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index 353c99ccf..6c05e2158 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -230,9 +230,7 @@ def test_confirm_get_unverified(mocker, client): @pytest.mark.django_db -@pytest.mark.usefixtures( - "mocked_session_agency", "mocked_session_eligibility", "mocked_session_verifier_does_not_use_auth_verification" -) +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility", "mocked_session_verifier") def test_confirm_get_verified(client, mocked_session_update): path = reverse(ROUTE_CONFIRM) response = client.get(path) From 1d4eb88a91d1052d5eaf6477ec34d1134e1397d0 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 30 May 2024 20:48:44 +0000 Subject: [PATCH 050/111] fix(test): remove test that has been revealed to not be meaningful when the fixture was a Mock object, the test saw `uses_auth_verification` as a function, but now that the fixture is using an actual model object, the `@property` decorator obscures the function and makes it look like a `bool` type. the intent of the test was to ensure the function is called, but that is already covered by the `bool` being `True` which is already a part of other tests. --- tests/pytest/oauth/test_analytics.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/pytest/oauth/test_analytics.py b/tests/pytest/oauth/test_analytics.py index 166d1fc11..61d1afbfa 100644 --- a/tests/pytest/oauth/test_analytics.py +++ b/tests/pytest/oauth/test_analytics.py @@ -3,15 +3,6 @@ from benefits.oauth.analytics import OAuthEvent -@pytest.mark.django_db -def test_OAuthEvent_checks_verifier_uses_auth_verification(app_request, mocked_session_verifier_uses_auth_verification): - mocked_verifier = mocked_session_verifier_uses_auth_verification(app_request) - - OAuthEvent(app_request, "event type") - - mocked_verifier.uses_auth_verification.assert_called_once - - @pytest.mark.django_db def test_OAuthEvent_verifier_client_name_when_uses_auth_verification( app_request, mocked_session_verifier_uses_auth_verification From bd15f2b6b93ea32bd701a61344d3ceb4e648ae80 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 30 May 2024 23:21:19 +0000 Subject: [PATCH 051/111] docs: remove remaining documentation referencing GitHub Webhooks --- docs/deployment/README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/deployment/README.md b/docs/deployment/README.md index e7a50a493..066173080 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -10,11 +10,8 @@ The Django application gets built into a [Docker image][dockerfile] with [NGINX] The application is deployed to an [Azure Web App Container][az-webapp] using three separate environments for `dev`, `test`, and `prod`. -A [GitHub Action][gh-actions] per environment is responsible for building that branch's image and pushing to [GitHub Container -Registry (GHCR)][ghcr]. - -GitHub POSTs a [webhook][gh-webhooks] to the Azure Web App when an [image is published to GHCR][gh-webhook-event], telling -Azure to restart the app and pull the latest image. +The [Deploy](deploy-workflow) workflow is responsible for building that branch's image and pushing to [GitHub Container +Registry (GHCR)][ghcr]. It also deploys to the Azure Web App, telling Azure to restart the app and pull the latest image. You can view what Git commit is deployed for a given environment by visitng the URL path `/static/sha.txt`. @@ -34,9 +31,7 @@ Docker images for each of the deploy branches are available from GitHub Containe [oet]: https://techblog.cdt.ca.gov/2020/06/cdt-taking-the-lead-in-digital-transformation/ [app-service-containers]: https://docs.microsoft.com/en-us/azure/app-service/configure-custom-container [app-service]: https://docs.microsoft.com/en-us/azure/app-service/overview +[deploy-workflow]: https://github.com/cal-itp/benefits/blob/dev/.github/workflows/deploy.yml [dockerfile]: https://github.com/cal-itp/benefits/blob/dev/Dockerfile [az-webapp]: https://azure.microsoft.com/en-us/services/app-service/containers/ -[gh-actions]: https://docs.github.com/en/actions -[gh-webhook-event]: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#package -[gh-webhooks]: https://docs.github.com/en/github-ae@latest/developers/webhooks-and-events/webhooks [ghcr]: https://github.com/features/packages From fc291ed866e52c07681a64bd81977554e0188f96 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 3 Jun 2024 20:50:07 +0000 Subject: [PATCH 052/111] chore: reuse variable for status code --- benefits/enrollment/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 20deab2e0..ca23c1fb1 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -55,7 +55,7 @@ def token(request): if isinstance(e, HTTPError): status_code = e.response.status_code - if e.response.status_code >= 500: + if status_code >= 500: redirect = reverse(ROUTE_SYSTEM_ERROR) else: redirect = reverse(ROUTE_SERVER_ERROR) From 8dc8e2a30afcb3ebdc5abb3529cc323bb3459ad7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 21:58:43 +0000 Subject: [PATCH 053/111] chore(deps): bump dawidd6/action-download-artifact from 3 to 4 Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 3 to 4. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index 226870d5d..888cf4a9e 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -92,7 +92,7 @@ jobs: uses: actions/checkout@v4 - name: Download coverage report - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@v4 with: workflow: tests-pytest.yml branch: dev From 9e06bc150e5f8f5400107cc8424a040a6acec97d Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 3 Jun 2024 23:18:27 +0000 Subject: [PATCH 054/111] chore(compose): remove obsolete version key --- compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/compose.yml b/compose.yml index b39a881f3..663aaf00d 100644 --- a/compose.yml +++ b/compose.yml @@ -1,5 +1,4 @@ name: benefits -version: "3.8" services: client: From 5b2348041da94cba144460a9f46ba03194802141 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 4 Jun 2024 10:52:02 -0500 Subject: [PATCH 055/111] ci: remove path filters from triggers we want the migration check to be a required check but not for it to block PRs that don't match the path filter, so we have to remove the path filters --- .github/workflows/check-makemigrations.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/check-makemigrations.yml b/.github/workflows/check-makemigrations.yml index f79537f60..dacef159d 100644 --- a/.github/workflows/check-makemigrations.yml +++ b/.github/workflows/check-makemigrations.yml @@ -1,11 +1,5 @@ name: Check for up-to-date Django migrations -on: - pull_request: - paths: - - "benefits/**" - push: - paths: - - "benefits/**" +on: [push, pull_request] jobs: check-makemigrations: From 4d7269081b3a59c4df6fd1b36a333ed9e04ebbf0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:07:01 +0000 Subject: [PATCH 056/111] chore(deps): bump dawidd6/action-download-artifact from 4 to 5 Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 4 to 5. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index 888cf4a9e..50a092a00 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -92,7 +92,7 @@ jobs: uses: actions/checkout@v4 - name: Download coverage report - uses: dawidd6/action-download-artifact@v4 + uses: dawidd6/action-download-artifact@v5 with: workflow: tests-pytest.yml branch: dev From b5b561ef3f258029d69b1cd9cfee2ec70b224eb8 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 5 Jun 2024 09:40:55 -0700 Subject: [PATCH 057/111] ci(codeql): guard against empty language array --- .github/workflows/codeql.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d3365e533..4db37eac5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,6 +37,7 @@ jobs: name: CodeQL Analyze runs-on: ubuntu-latest needs: setup + if: ${{ fromJSON(needs.setup.outputs.languages) != [] }} permissions: actions: read contents: read From c0ff6f92e4eaa87d9c51952f85a7bc9463b48741 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 4 Jun 2024 22:05:47 +0000 Subject: [PATCH 058/111] refactor: delete app setting meant for refreshing secrets the Azure portal now provides a way to pull the latest reference values --- terraform/app_service.tf | 3 --- 1 file changed, 3 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index f77fa6fbb..a332b29b9 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -45,9 +45,6 @@ resource "azurerm_linux_web_app" "main" { } app_settings = { - # app setting used solely for refreshing secrets - see https://github.com/MicrosoftDocs/azure-docs/issues/79855#issuecomment-1265664801 - "change_me_to_refresh_secrets" = "change me in the portal to refresh all secrets", - "DOCKER_ENABLE_CI" = "false", "WEBSITE_HTTPLOGGING_RETENTION_DAYS" = "99999", "WEBSITE_TIME_ZONE" = "America/Los_Angeles", From b9ed7ebdb127e3b9b612af621674d7779110109f Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Wed, 29 May 2024 17:18:10 +0000 Subject: [PATCH 059/111] feat(analytics): add optional error_code event property --- benefits/oauth/analytics.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/benefits/oauth/analytics.py b/benefits/oauth/analytics.py index 4565d9fa3..5f1b60ddf 100644 --- a/benefits/oauth/analytics.py +++ b/benefits/oauth/analytics.py @@ -32,8 +32,10 @@ def __init__(self, request): class FinishedSignInEvent(OAuthEvent): """Analytics event representing the end of the OAuth sign in flow.""" - def __init__(self, request): + def __init__(self, request, error=None): super().__init__(request, "finished sign in") + if error is not None: + self.update_event_properties(error_code=error) class StartedSignOutEvent(OAuthEvent): @@ -61,9 +63,9 @@ def canceled_sign_in(request): core.send_event(CanceledSignInEvent(request)) -def finished_sign_in(request): +def finished_sign_in(request, error=None): """Send the "finished sign in" analytics event.""" - core.send_event(FinishedSignInEvent(request)) + core.send_event(FinishedSignInEvent(request, error)) def started_sign_out(request): From e5056e38af3da13da16fa740342ced120fdfe95b Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Wed, 29 May 2024 17:19:07 +0000 Subject: [PATCH 060/111] feat(oauth): add optional error_code to authorize view --- benefits/oauth/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index ae24c2f40..3c0c5c0f4 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -64,6 +64,8 @@ def authorize(request): verifier_claim = verifier.auth_provider.claim stored_claim = None + error_claim = None + if verifier_claim: userinfo = token.get("userinfo") @@ -76,10 +78,11 @@ def authorize(request): elif claim_value == 1: # if userinfo contains our claim and the flag is 1 (true), store the *claim* stored_claim = verifier_claim + elif claim_value >= 10: + error_claim = claim_value session.update(request, oauth_token=id_token, oauth_claim=stored_claim) - - analytics.finished_sign_in(request) + analytics.finished_sign_in(request, error=error_claim) return redirect(ROUTE_CONFIRM) From fec172b72f5b006221ffe6ef892fa0ad91c952f9 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 5 Jun 2024 14:34:23 -0700 Subject: [PATCH 061/111] fix(ci): correct invalid syntax --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4db37eac5..f556c359a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: name: CodeQL Analyze runs-on: ubuntu-latest needs: setup - if: ${{ fromJSON(needs.setup.outputs.languages) != [] }} + if: ${{ needs.setup.outputs.languages != "[]" }} permissions: actions: read contents: read From c10e577aa73d57f5f102c430fd94d02f69cf9bd2 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 5 Jun 2024 14:54:45 -0700 Subject: [PATCH 062/111] fix(terraform): match config to state for source_control don't need the github_action_configuration, but removing this dropped the DOCKER_REGISTRY_SERVER_URL setting, so adding it back --- terraform/app_service.tf | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index a332b29b9..8b04a7943 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -45,7 +45,7 @@ resource "azurerm_linux_web_app" "main" { } app_settings = { - "DOCKER_ENABLE_CI" = "false", + "DOCKER_REGISTRY_SERVER_URL" = "https://ghcr.io/" "WEBSITE_HTTPLOGGING_RETENTION_DAYS" = "99999", "WEBSITE_TIME_ZONE" = "America/Los_Angeles", "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false", @@ -103,19 +103,9 @@ resource "azurerm_linux_web_app" "main" { } resource "azurerm_app_service_source_control" "main" { - app_id = azurerm_linux_web_app.main.id - repo_url = "https://github.com/cal-itp/benefits" - branch = local.env_name - rollback_enabled = true - - github_action_configuration { - generate_workflow_file = false - - container_configuration { - image_name = "cal-itp/benefits" - registry_url = "https://ghcr.io/" - } - } + app_id = azurerm_linux_web_app.main.id + repo_url = "https://github.com/cal-itp/benefits" + branch = local.env_name } resource "azurerm_app_service_custom_hostname_binding" "main" { From 77c4981f0b6164385e5ce849f68202327e7bebdf Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 5 Jun 2024 14:55:33 -0700 Subject: [PATCH 063/111] fix(terraform): remove unused? app setting this is always re-added by plan/apply, so apparently it isn't saved anyway --- terraform/app_service.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 8b04a7943..c150ceaca 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -46,7 +46,6 @@ resource "azurerm_linux_web_app" "main" { app_settings = { "DOCKER_REGISTRY_SERVER_URL" = "https://ghcr.io/" - "WEBSITE_HTTPLOGGING_RETENTION_DAYS" = "99999", "WEBSITE_TIME_ZONE" = "America/Los_Angeles", "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false", "WEBSITES_PORT" = "8000", From 2e12047c01ae3d5424431cf22623e9a847919074 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Mon, 3 Jun 2024 20:53:47 +0000 Subject: [PATCH 064/111] feat(tests): add test for error in claim --- tests/pytest/oauth/test_views.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index ab54c7a1a..2aa4b7900 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -137,6 +137,27 @@ def test_authorize_success_with_claim_false( assert result.url == reverse(ROUTE_CONFIRM) +@pytest.mark.django_db +def test_authorize_success_with_claim_error( + mocked_session_verifier_uses_auth_verification, + mocked_oauth_create_client, + mocked_analytics_module, + app_request, +): + verifier = mocked_session_verifier_uses_auth_verification.return_value + verifier.auth_provider.claim = "claim" + mocked_oauth_client = mocked_oauth_create_client.return_value + mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "10"}} + + result = authorize(app_request) + + mocked_oauth_client.authorize_access_token.assert_called_with(app_request) + mocked_analytics_module.finished_sign_in.assert_called_with(app_request, error=10) + assert session.oauth_claim(app_request) is None + assert result.status_code == 302 + assert result.url == reverse(ROUTE_CONFIRM) + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") def test_authorize_success_without_verifier_claim( From 75cfeb075bb3e0fb25988b4629b245a77cac1caa Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Tue, 4 Jun 2024 15:31:57 +0000 Subject: [PATCH 065/111] feat(tests): add tests for FinishedSignInEvent --- tests/pytest/oauth/test_analytics.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/pytest/oauth/test_analytics.py b/tests/pytest/oauth/test_analytics.py index 61d1afbfa..26f80a1b6 100644 --- a/tests/pytest/oauth/test_analytics.py +++ b/tests/pytest/oauth/test_analytics.py @@ -1,6 +1,6 @@ import pytest -from benefits.oauth.analytics import OAuthEvent +from benefits.oauth.analytics import OAuthEvent, FinishedSignInEvent @pytest.mark.django_db @@ -22,3 +22,15 @@ def test_OAuthEvent_verifier_no_client_name_when_does_not_use_auth_verification( event = OAuthEvent(app_request, "event type") assert "auth_provider" not in event.event_properties + + +@pytest.mark.django_db +def test_FinishedSignInEvent_with_error(app_request): + event = FinishedSignInEvent(app_request, error=10) + assert event.event_properties["error_code"] == 10 + + +@pytest.mark.django_db +def test_FinishedSignInEvent_without_error(app_request): + event = FinishedSignInEvent(app_request) + assert "error_code" not in event.event_properties From cbd95cc47f586084a9a70a2fc0ae7465a2483832 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:51:10 +0000 Subject: [PATCH 066/111] chore(deps-dev): bump cypress from 13.10.0 to 13.11.0 in /tests/cypress Bumps [cypress](https://github.com/cypress-io/cypress) from 13.10.0 to 13.11.0. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v13.10.0...v13.11.0) --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/cypress/package-lock.json | 14 +++++++------- tests/cypress/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cypress/package-lock.json b/tests/cypress/package-lock.json index a12606005..90de5d31c 100644 --- a/tests/cypress/package-lock.json +++ b/tests/cypress/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "AGPL-3.0-or-later", "devDependencies": { - "cypress": "^13.10.0" + "cypress": "^13.11.0" } }, "node_modules/@colors/colors": { @@ -537,9 +537,9 @@ } }, "node_modules/cypress": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.10.0.tgz", - "integrity": "sha512-tOhwRlurVOQbMduX+KonoMeQILs2cwR3yHGGENoFvvSoLUBHmJ8b9/n21gFSDqjlOJ+SRVcwuh+fG/JDsHsT6Q==", + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.11.0.tgz", + "integrity": "sha512-NXXogbAxVlVje4XHX+Cx5eMFZv4Dho/2rIcdBHg9CNPFUGZdM4cRdgIgM7USmNYsC12XY0bZENEQ+KBk72fl+A==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2358,9 +2358,9 @@ } }, "cypress": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.10.0.tgz", - "integrity": "sha512-tOhwRlurVOQbMduX+KonoMeQILs2cwR3yHGGENoFvvSoLUBHmJ8b9/n21gFSDqjlOJ+SRVcwuh+fG/JDsHsT6Q==", + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.11.0.tgz", + "integrity": "sha512-NXXogbAxVlVje4XHX+Cx5eMFZv4Dho/2rIcdBHg9CNPFUGZdM4cRdgIgM7USmNYsC12XY0bZENEQ+KBk72fl+A==", "dev": true, "requires": { "@cypress/request": "^3.0.0", diff --git a/tests/cypress/package.json b/tests/cypress/package.json index a834b6afb..254c8c948 100644 --- a/tests/cypress/package.json +++ b/tests/cypress/package.json @@ -12,6 +12,6 @@ "license": "AGPL-3.0-or-later", "private": true, "devDependencies": { - "cypress": "^13.10.0" + "cypress": "^13.11.0" } } From 310c59123c5fd5dd436ef1e8f9dfc16c5fb7c3df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:40:40 +0000 Subject: [PATCH 067/111] chore(deps): bump sentry-sdk from 2.1.1 to 2.4.0 Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.1.1 to 2.4.0. - [Release notes](https://github.com/getsentry/sentry-python/releases) - [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-python/compare/2.1.1...2.4.0) --- updated-dependencies: - dependency-name: sentry-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index adca4fcbf..68348be38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "eligibility-api==2023.9.1", "calitp-littlepay==2024.4.1", "requests==2.32.3", - "sentry-sdk==2.1.1", + "sentry-sdk==2.4.0", "six==1.16.0", ] From c2d745396b04e9c108d5e5abbb88c525092ad048 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 6 Jun 2024 14:24:36 -0700 Subject: [PATCH 068/111] fi(ci): correct invalid syntax --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f556c359a..91b63e312 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: name: CodeQL Analyze runs-on: ubuntu-latest needs: setup - if: ${{ needs.setup.outputs.languages != "[]" }} + if: ${{ needs.setup.outputs.languages != '[]' }} permissions: actions: read contents: read From 6c33e11b4bf862700f4103cab6d61952682a6db4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:40:34 +0000 Subject: [PATCH 069/111] chore(deps): bump authlib from 1.3.0 to 1.3.1 Bumps [authlib](https://github.com/lepture/authlib) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/lepture/authlib/releases) - [Changelog](https://github.com/lepture/authlib/blob/master/docs/changelog.rst) - [Commits](https://github.com/lepture/authlib/compare/v1.3.0...v1.3.1) --- updated-dependencies: - dependency-name: authlib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 68348be38..23c87d024 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = { file = "LICENSE" } classifiers = ["Programming Language :: Python :: 3 :: Only"] requires-python = ">=3.9" dependencies = [ - "Authlib==1.3.0", + "Authlib==1.3.1", "azure-keyvault-secrets==4.8.0", "azure-identity==1.16.0", "Django==5.0.6", From 1b7aafb9fe92c015d686ca5ce53106a421680b12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:58:20 +0000 Subject: [PATCH 070/111] chore(deps): bump sentry-sdk from 2.4.0 to 2.5.0 Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/getsentry/sentry-python/releases) - [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-python/compare/2.4.0...2.5.0) --- updated-dependencies: - dependency-name: sentry-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 68348be38..2338462a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "eligibility-api==2023.9.1", "calitp-littlepay==2024.4.1", "requests==2.32.3", - "sentry-sdk==2.4.0", + "sentry-sdk==2.5.0", "six==1.16.0", ] From 2286ef114794335337df54567bffb41ca7360c2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:58:28 +0000 Subject: [PATCH 071/111] chore(deps): bump mkdocs-material from 9.5.25 to 9.5.26 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.25 to 9.5.26. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.25...9.5.26) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 8eab93ae0..67c855732 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ mdx_truly_sane_lists mkdocs==1.6.0 mkdocs-awesome-pages-plugin mkdocs-macros-plugin -mkdocs-material==9.5.25 +mkdocs-material==9.5.26 mkdocs-redirects From cb52498844c8ceb93b976e642cec740da054f6cd Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Fri, 7 Jun 2024 16:27:37 +0000 Subject: [PATCH 072/111] chore: qa spanish copy for agency card --- benefits/locale/en/LC_MESSAGES/django.po | 9 +-------- benefits/locale/es/LC_MESSAGES/django.po | 25 +++++++----------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 9bcdc3a4e..b6c6ec264 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-05-20 21:48+0000\n" +"POT-Creation-Date: 2024-06-07 16:22+0000\n" "Language: English\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -665,13 +665,6 @@ msgid "" "choose to change the card you use to pay for transit service." msgstr "" -msgid "" -"Your contactless card is now enrolled in a SacRT light rail transit benefit. " -"When boarding SacRT light rail, tap this card and you will be charged a " -"reduced fare. You will need to re-enroll if you choose to change the card " -"you use to pay for transit service." -msgstr "" - msgid "" "You were not charged anything today. When boarding SacRT light rail, tap " "this card and you will be charged a reduced fare. You will need to re-enroll " diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 527cde109..098c2858b 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-05-20 21:48+0000\n" +"POT-Creation-Date: 2024-06-07 16:22+0000\n" "Language: Español\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -95,7 +95,8 @@ msgstr "" msgid "The contactless symbol is four curved lines, like this:" msgstr "" -"El símbolo sin contacto está compuesto por cuatro líneas curvas, como este:" +"El símbolo de 'sin contacto' está compuesto por cuatro líneas curvas, como " +"este:" msgctxt "image alt text" msgid "Four curved lines on contactless-enabled cards" @@ -123,7 +124,7 @@ msgid "" "noreferrer\">contact %(short_name)s." msgstr "" "También puede conseguir su beneficio de tránsito pasando por el proceso de " -"solicitud de su %(short_name)s. Para recibir actualizaciones sobre opciones " +"solicitud de %(short_name)s. Para recibir actualizaciones sobre opciones " "adicionales, vuelva a consultar este sitio web o contacte a %(short_name)s." @@ -138,7 +139,7 @@ msgid "" msgstr "" "Login.gov es un servicio que ofrece acceso seguro y privado a programas " "gubernamentales como beneficios, servicios y aplicaciones federales. Con una " -"cuenta de Login.gov puede iniciar sesión en varios sitios web " +"cuenta de Login.gov puede iniciar una sesión en varios sitios web " "gubernamentales con la misma dirección de correo electrónico y contraseña." msgid "" @@ -194,7 +195,7 @@ msgid "" msgstr "" "Login.gov también necesitará verificar su identidad llamando o enviando un " "mensaje de texto a su teléfono. Si Login.gov no puede verificar su número de " -"teléfono, usted puede verificarlo por correo." +"teléfono, usted puede verificar su identidad por correo." msgid "" "Please visit the Login.gov help center for ." msgid "Questions?" -msgstr "¿Tienes preguntas?" +msgstr "¿Tiene preguntas?" msgid "" "If you need assistance with this website, please reach out to the customer " @@ -837,18 +838,6 @@ msgstr "" "inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " "tránsito." -msgid "" -"Your contactless card is now enrolled in a SacRT light rail transit benefit. " -"When boarding SacRT light rail, tap this card and you will be charged a " -"reduced fare. You will need to re-enroll if you choose to change the card " -"you use to pay for transit service." -msgstr "" -"Su tarjeta sin contacto ahora está inscrita en el beneficio de tránsito de " -"tren ligero de SacRT. Cuando suba a un tren ligero de SacRT, toque el lector " -"con esta tarjeta y se le cobrará una tarifa reducida. Deberá volver a " -"inscribirse si elige cambiar la tarjeta que usa para pagar el servicio de " -"tránsito." - msgid "" "You were not charged anything today. When boarding SacRT light rail, tap " "this card and you will be charged a reduced fare. You will need to re-enroll " From 7e44e5955351890cd6fb997246f2e7f431865878 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 7 Jun 2024 18:21:18 +0000 Subject: [PATCH 073/111] chore(terraform): update pipeline version --- terraform/pipeline/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/pipeline/deploy.yml b/terraform/pipeline/deploy.yml index dc1fc1e9e..5e7c34b26 100644 --- a/terraform/pipeline/deploy.yml +++ b/terraform/pipeline/deploy.yml @@ -9,7 +9,7 @@ steps: - task: TerraformInstaller@0 displayName: Install Terraform inputs: - terraformVersion: 1.3.7 + terraformVersion: 1.8.5 # https://github.com/microsoft/azure-pipelines-terraform/tree/main/Tasks/TerraformTask/TerraformTaskV3#readme - task: TerraformTaskV3@3 displayName: Terraform init From ffc07c22673eeac7ebff6a9afa329cf53633d81f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:27:41 +0000 Subject: [PATCH 074/111] chore(deps): bump calitp-littlepay from 2024.4.1 to 2024.6.1 Bumps [calitp-littlepay](https://github.com/cal-itp/littlepay) from 2024.4.1 to 2024.6.1. - [Release notes](https://github.com/cal-itp/littlepay/releases) - [Commits](https://github.com/cal-itp/littlepay/compare/2024.04.1...2024.06.1) --- updated-dependencies: - dependency-name: calitp-littlepay dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2338462a5..631bc015c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "django-admin-sortable2==2.2.1", "django-google-sso==6.2.0", "eligibility-api==2023.9.1", - "calitp-littlepay==2024.4.1", + "calitp-littlepay==2024.6.1", "requests==2.32.3", "sentry-sdk==2.5.0", "six==1.16.0", From 2016887e714ce87a3411181df6446cadfc5b4f56 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Fri, 7 Jun 2024 13:47:21 -0500 Subject: [PATCH 075/111] chore: update version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7a54ce24a..bf5f180d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "benefits" -version = "2024.05.1" +version = "2024.06.1" description = "Cal-ITP Benefits is an application that enables automated eligibility verification and enrollment for transit benefits onto customers’ existing contactless bank (credit/debit) cards." readme = "README.md" license = { file = "LICENSE" } From 128a16d82db763d32f2f080e0e94ad0a9c35fda4 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Mon, 10 Jun 2024 21:21:09 +0000 Subject: [PATCH 076/111] refactor(tests): update eligibility type enrollment index template test --- tests/pytest/core/test_models.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py index c3470bdb8..63c6ae3e7 100644 --- a/tests/pytest/core/test_models.py +++ b/tests/pytest/core/test_models.py @@ -238,13 +238,15 @@ def test_EligibilityType_supports_expiration(model_EligibilityType_supports_expi @pytest.mark.django_db -def test_EligibilityType_enrollment_index_template(model_EligibilityType): - assert model_EligibilityType.enrollment_index_template == "enrollment/index.html" +def test_EligibilityType_enrollment_index_template(): + new_eligibility_type = EligibilityType.objects.create() + + assert new_eligibility_type.enrollment_index_template == "enrollment/index.html" - model_EligibilityType.enrollment_index_template = "test/enrollment.html" - model_EligibilityType.save() + new_eligibility_type.enrollment_index_template = "test/enrollment.html" + new_eligibility_type.save() - assert model_EligibilityType.enrollment_index_template == "test/enrollment.html" + assert new_eligibility_type.enrollment_index_template == "test/enrollment.html" @pytest.mark.django_db From f9568a1201d2b98c4afde35b37d599f29f397ad6 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 10 Jun 2024 22:32:43 +0000 Subject: [PATCH 077/111] feat: add a check for up-to-date Django message files use the same workflow to minimize time spent on required checks --- .github/workflows/check-makemigrations.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-makemigrations.yml b/.github/workflows/check-makemigrations.yml index dacef159d..bccc042da 100644 --- a/.github/workflows/check-makemigrations.yml +++ b/.github/workflows/check-makemigrations.yml @@ -1,4 +1,4 @@ -name: Check for up-to-date Django migrations +name: Check for up-to-date Django migrations and messages on: [push, pull_request] jobs: @@ -26,7 +26,23 @@ jobs: run: | if ./bin/makemigrations.sh | grep -q 'No changes detected'; then - exit 0; + exit 0; else - exit 1; + exit 1; + fi + + - name: Run ./bin/makemessages.sh + run: | + ./bin/makemessages.sh + + set -x # show commands + + git add benefits + + # message files are up-to-date if the only differences are from the updated timestamp + if echo $(git diff --cached --shortstat) | grep -q '2 files changed, 2 insertions(+), 2 deletions(-)'; + then + exit 0; + else + exit 1; fi From 74251f22b18f8dce50e5dd6f8c260b38c48ea706 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 10 Jun 2024 22:34:17 +0000 Subject: [PATCH 078/111] refactor: rename the workflow file --- ...check-makemigrations.yml => check-migrations-and-messages.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{check-makemigrations.yml => check-migrations-and-messages.yml} (100%) diff --git a/.github/workflows/check-makemigrations.yml b/.github/workflows/check-migrations-and-messages.yml similarity index 100% rename from .github/workflows/check-makemigrations.yml rename to .github/workflows/check-migrations-and-messages.yml From 798aa8388c476f1ef3d34fce77b7175c3095ef5a Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 10 Jun 2024 22:45:46 +0000 Subject: [PATCH 079/111] chore: rename the job --- .github/workflows/check-migrations-and-messages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-migrations-and-messages.yml b/.github/workflows/check-migrations-and-messages.yml index bccc042da..b8cdac094 100644 --- a/.github/workflows/check-migrations-and-messages.yml +++ b/.github/workflows/check-migrations-and-messages.yml @@ -2,7 +2,7 @@ name: Check for up-to-date Django migrations and messages on: [push, pull_request] jobs: - check-makemigrations: + check-migrations-and-messages: runs-on: ubuntu-latest steps: - name: Check out code From f6fafef2f0d53d52cb0e5a37f512a8a85e7f12d1 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 4 Jun 2024 02:59:52 +0000 Subject: [PATCH 080/111] docs(logic): lay out the overview and initial flow placeholder sections for each phase --- docs/development/.pages | 1 + docs/development/application-logic.md | 102 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 docs/development/application-logic.md diff --git a/docs/development/.pages b/docs/development/.pages index 8b2801411..f3088a77b 100644 --- a/docs/development/.pages +++ b/docs/development/.pages @@ -3,6 +3,7 @@ nav: - commits-branches-merging.md - docker-dynamic-ports.md - linting-pre-commit.md + - application-logic.md - models-migrations.md - i18n.md - test-server.md diff --git a/docs/development/application-logic.md b/docs/development/application-logic.md new file mode 100644 index 000000000..34995bf7b --- /dev/null +++ b/docs/development/application-logic.md @@ -0,0 +1,102 @@ +# Application logic + +!!! info "See also" + + More specific user flow diagrams: [Enrollment pathways](../enrollment-pathways/README.md) + +This page describes how Cal-ITP Benefits defines user flows through the following high-level _phases_: + +1. [Initial setup](#initial-setup) +1. [Identity proofing](#identity-proofing) +1. [Eligibility verification](#eligibility-verification) +1. [Enrollment](#enrollment) + +```mermaid +flowchart LR + start((Start)) + entry[Initial setup] + identity[Identity proofing] + eligibility[Eligibility verification] + enrollment[Enrollment] + complete((End)) + style complete stroke-width:2px + + start --> entry + entry --> identity + identity --> eligibility + eligibility --> enrollment + enrollment --> complete +``` + +The structure of the source code in [`benefits/`](https://github.com/cal-itp/benefits/tree/dev/benefits) +generally follows from these phases: + +- [`benefits/core/`](https://github.com/cal-itp/benefits/tree/dev/benefits/core) implements shared logic and models, and + defines the user's entrypoint into the app +- [`benefits/oauth/`](https://github.com/cal-itp/benefits/tree/dev/benefits/oauth) implements identity proofing +- [`benefits/eligibility/`](https://github.com/cal-itp/benefits/tree/dev/benefits/eligibility) implements eligibility + verification +- [`benefits/enrollment/`](https://github.com/cal-itp/benefits/tree/dev/benefits/enrollment) implements enrollment + +Each of these directories contains a standalone Django app registered in the [settings](../configuration/README.md#django-settings). + +All of the common logic and [database models and migrations](./models-migrations.md) are defined in `benefits.core`, and this +app is imported by the other apps. + +## Initial setup + +In this phase, the user makes the initial selections that will configure the rest of their journey. + +!!! example "Entrypoint" + + [`benefits/core/views.py`][core-views] + +!!! example "Key supporting files" + + [`benefits/core/models.py`][core-models] + + [`benefits/core/session.py`][core-session] + +```mermaid +flowchart LR + session[(session)] + start((Start)) + pick_agency["`Agency picker + modal`"] + agency(("`Agency + selected`")) + eligibility(("`Eligibility type + selected`")) + next>"`_Next phase_`"] + style next stroke-width:2px + + start -- "1a. Lands on index" --> pick_agency + start -- "1b. Lands on agency index" --> agency + %% invisible links help with diagram layout + start ~~~ session + start ~~~ agency + + pick_agency -- 2. Chooses agency --> agency + agency -- 3. Chooses enrollment pathway --> eligibility + + eligibility -- 4. continue --> next + + agency -. update -.-> session + eligibility -. update -.-> session +``` + +Depending upon the choice of enrollment pathway, the _Next phase_ above may be: + +- [Identity proofing](#identity-proofing), for all flows that require user PII (such as for verifying age). +- [Eligibility verification](#eligibility-verification), for Agency card flows that require a physical card from the transit + agency. + +## Identity proofing + +## Eligibility verification + +## Enrollment + +[core-models]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/models.py +[core-session]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/session.py +[core-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/views.py From e06741b11354a70f5861e7c0726bb3466b25a06e Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 4 Jun 2024 19:33:01 +0000 Subject: [PATCH 081/111] docs(logic): describe identity proofing phase --- docs/development/application-logic.md | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/development/application-logic.md b/docs/development/application-logic.md index 34995bf7b..df9236122 100644 --- a/docs/development/application-logic.md +++ b/docs/development/application-logic.md @@ -93,6 +93,51 @@ Depending upon the choice of enrollment pathway, the _Next phase_ above may be: ## Identity proofing +In this phase, Cal-ITP Benefits takes the user through an [OpenID Connect (OIDC)](https://openid.net/developers/how-connect-works/) +flow as a Client (RP) of the CDT Identity Gateway (the Identity Provider or IDP), via Login.gov. + +The CDT Identity Gateway transforms PII from Login.gov into anonymized boolean claims that are later used in +[eligibility verification](#eligibility-verification). + +!!! example "Entrypoint" + + [`benefits/oauth/views.py`][oauth-views] + +!!! example "Key supporting files" + + [`benefits/oauth/client.py`][oauth-client] + + [`benefits/oauth/redirects.py`][oauth-redirects] + +```mermaid +flowchart LR + session[(session)] + + start((Initial setup)) + style start stroke-dasharray: 5 5 + + benefits[Benefits app] + idg[["`CDT + Identity Gateway`"]] + logingov[[Login.gov]] + claims((Claims received)) + + next>"`_Eligibility + verification_`"] + style next stroke-width:2px + + start -- 1. Clicks login button --> benefits + %% invisible links help with diagram layout + start ~~~ session + + benefits -- 2. OIDC authorize_redirect --> idg + idg <-. "3. PII exchange" .-> logingov + idg -- 4. OIDC token authorization --> claims + + claims -- 5. continue --> next + claims -. update .-> session +``` + ## Eligibility verification ## Enrollment @@ -100,3 +145,6 @@ Depending upon the choice of enrollment pathway, the _Next phase_ above may be: [core-models]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/models.py [core-session]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/session.py [core-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/views.py +[oauth-client]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/client.py +[oauth-redirects]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/redirects.py +[oauth-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/views.py From 32f63c89463d09b7ef6bd4a4fcb71fd498d06cac Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 4 Jun 2024 23:03:08 +0000 Subject: [PATCH 082/111] docs(logic): describe eligibility verification phase --- docs/development/application-logic.md | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/development/application-logic.md b/docs/development/application-logic.md index df9236122..d52aea069 100644 --- a/docs/development/application-logic.md +++ b/docs/development/application-logic.md @@ -140,11 +140,57 @@ flowchart LR ## Eligibility verification +In this phase, Cal-ITP Benefits verifies the user's claims using one of two methods: + +- Claims validation, using claims previously stored in the user's session during [Identity proofing](#identity-proofing) +- Eligibility API verification, using non-PII claims provided by the user in an HTML form submission + +!!! example "Entrypoint" + + [`benefits/eligibility/views.py`][eligibility-views] + +!!! example "Key supporting files" + + [`benefits/eligibility/verify.py`][eligibility-verify] + +```mermaid +flowchart LR + session[(session)] + + start(("`Previous + phase`")) + style start stroke-dasharray: 5 5 + + claims[Session claims check] + form[HTTP form POST] + server[[Eligibility Verification server]] + eligible{Eligible?} + + next>"`_Enrollment_`"] + style next stroke-width:2px + + stop{{Stop}} + + start -- Eligibility API verification --> form + form -- Eligibility API call --> server + server --> eligible + + start -- Claims validation --> claims + session -. read .-> claims + claims --> eligible + + eligible -- Yes --> next + eligible -- No --> stop + eligible -. update .-> session +``` + ## Enrollment [core-models]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/models.py [core-session]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/session.py [core-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/views.py +[eligibility-verify]: https://github.com/cal-itp/benefits/blob/dev/benefits/eligibility/verify.py +[eligibility-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/eligibility/views.py [oauth-client]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/client.py [oauth-redirects]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/redirects.py [oauth-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/views.py From eb0d23ad944a4c050d6c5b8f9ad9a5a499d2f2c4 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 5 Jun 2024 05:16:07 +0000 Subject: [PATCH 083/111] docs(logic): describe enrollment phase --- docs/development/application-logic.md | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/development/application-logic.md b/docs/development/application-logic.md index d52aea069..5f56466d5 100644 --- a/docs/development/application-logic.md +++ b/docs/development/application-logic.md @@ -186,11 +186,64 @@ flowchart LR ## Enrollment +In this final phase, the user registers their contactless payment card with a concession group configured within the +payment processor (Littlepay). + +**_Cal-ITP Benefits never processes, transmits, nor stores the user's payment card details._** + +!!! example "Entrypoint" + + [`benefits/enrollment/views.py`][enrollment-views] + +!!! example "Supporting packages" + + [`cal-itp/littlepay`][littlepay] + +```mermaid +sequenceDiagram +autonumber +%% Enrollment phase + actor user as User + participant benefits as Benefits app + participant littlepay as Littlepay + +user->>benefits: starts enrollment phase + activate user +benefits-->>user: display enrollment index +user->>littlepay: GET tokenization lib (AJAX) +littlepay-->>user: tokenization lib .js +user->>benefits: GET card tokenization access token (AJAX) + deactivate user + activate benefits +benefits->>littlepay: GET API access token +littlepay-->>benefits: access token +benefits->>littlepay: GET card tokenization access token +littlepay-->>benefits: access token +benefits-->>user: access token + deactivate benefits + activate user +user->>user: click to initiate payment card collection +user-->>user: display Littlepay overlay +user->>littlepay: provides debit or credit card details +littlepay-->>user: card token +user->>benefits: POST back card token + deactivate user + activate benefits +benefits->>littlepay: GET API access token +littlepay-->>benefits: access token +benefits->>littlepay: GET funding source from card token +littlepay-->>benefits: funding source +benefits->>littlepay: enroll funding source in group + deactivate benefits +``` + [core-models]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/models.py [core-session]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/session.py [core-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/views.py [eligibility-verify]: https://github.com/cal-itp/benefits/blob/dev/benefits/eligibility/verify.py [eligibility-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/eligibility/views.py +[enrollment-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/enrollment/views.py +[littlepay]: https://github.com/cal-itp/littlepay [oauth-client]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/client.py [oauth-redirects]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/redirects.py [oauth-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/views.py From 42657ce04cd526f5b62a08540ed446335b4c1f2b Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 5 Jun 2024 21:26:37 +0000 Subject: [PATCH 084/111] docs(logic): outline Django request pipeline --- docs/development/application-logic.md | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/development/application-logic.md b/docs/development/application-logic.md index 5f56466d5..74860c9c3 100644 --- a/docs/development/application-logic.md +++ b/docs/development/application-logic.md @@ -43,6 +43,41 @@ Each of these directories contains a standalone Django app registered in the [se All of the common logic and [database models and migrations](./models-migrations.md) are defined in `benefits.core`, and this app is imported by the other apps. +## Django request pipeline + +Each request to the Benefits app is ultimately a [Django request](https://docs.djangoproject.com/en/5.0/ref/request-response/) +and goes through the [Django HTTP request pipeline](https://docs.djangoproject.com/en/5.0/topics/http/). + +Benefits uses middleware to pre- and post-process requests for (view) access control, session configuration, and analytics. +Benefits also uses context processors to enrich the Django template context with data needed for rendering on the front-end. + +!!! example "Key supporting files" + + [`benefits/core/context_processors.py`][core-context-processors] + + [`benefits/core/middleware.py`][core-middleware] + +In general, the flow of a Django request looks like: + +```mermaid +flowchart LR + user((User)) + style user stroke-width:2px + + pre_middleware[Request middleware] + view_middleware[View-specific middleware] + context[Context processors] + view[View function] + post_middleware[Response middleware] + + user -- Request --> pre_middleware + pre_middleware -- Request --> view_middleware + view_middleware -- Request --> context + context -- Request --> view + view -- Response --> post_middleware + post_middleware -- Response --> user +``` + ## Initial setup In this phase, the user makes the initial selections that will configure the rest of their journey. @@ -237,6 +272,8 @@ benefits->>littlepay: enroll funding source in group deactivate benefits ``` +[core-context-processors]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/context_processors.py +[core-middleware]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/middleware.py [core-models]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/models.py [core-session]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/session.py [core-views]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/views.py From 570c512224010224d2d8b6d295278ed4aa78abde Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 10 Jun 2024 16:34:33 -0700 Subject: [PATCH 085/111] docs(logic): clarify Relaying Party definition Co-authored-by: Luis Alvergue --- docs/development/application-logic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/application-logic.md b/docs/development/application-logic.md index 74860c9c3..7797dc243 100644 --- a/docs/development/application-logic.md +++ b/docs/development/application-logic.md @@ -129,7 +129,7 @@ Depending upon the choice of enrollment pathway, the _Next phase_ above may be: ## Identity proofing In this phase, Cal-ITP Benefits takes the user through an [OpenID Connect (OIDC)](https://openid.net/developers/how-connect-works/) -flow as a Client (RP) of the CDT Identity Gateway (the Identity Provider or IDP), via Login.gov. +flow as a Client (the Relying Party or RP) of the CDT Identity Gateway (the Identity Provider or IDP), via Login.gov. The CDT Identity Gateway transforms PII from Login.gov into anonymized boolean claims that are later used in [eligibility verification](#eligibility-verification). From a9d62818f3e5e3d866f414154e1f390b9839a1d2 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 11 Jun 2024 01:43:06 +0000 Subject: [PATCH 086/111] docs(logic): add analytics events use dots rather than arrows for ancillary requests --- docs/development/application-logic.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/development/application-logic.md b/docs/development/application-logic.md index 7797dc243..1c49dbf58 100644 --- a/docs/development/application-logic.md +++ b/docs/development/application-logic.md @@ -95,6 +95,8 @@ In this phase, the user makes the initial selections that will configure the res ```mermaid flowchart LR session[(session)] + analytics[[analytics]] + start((Start)) pick_agency["`Agency picker modal`"] @@ -116,8 +118,9 @@ flowchart LR eligibility -- 4. continue --> next - agency -. update -.-> session - eligibility -. update -.-> session + agency -. update -.-o session + eligibility -. update -.-o session + eligibility -. selected eligibility verifier -.-o analytics ``` Depending upon the choice of enrollment pathway, the _Next phase_ above may be: @@ -147,6 +150,7 @@ The CDT Identity Gateway transforms PII from Login.gov into anonymized boolean c ```mermaid flowchart LR session[(session)] + analytics[[analytics]] start((Initial setup)) style start stroke-dasharray: 5 5 @@ -166,11 +170,14 @@ flowchart LR start ~~~ session benefits -- 2. OIDC authorize_redirect --> idg + benefits -. started sign in -.-o analytics + idg <-. "3. PII exchange" .-> logingov idg -- 4. OIDC token authorization --> claims claims -- 5. continue --> next - claims -. update .-> session + claims -. update .-o session + claims -. finished sign in -.-o analytics ``` ## Eligibility verification @@ -191,6 +198,7 @@ In this phase, Cal-ITP Benefits verifies the user's claims using one of two meth ```mermaid flowchart LR session[(session)] + analytics[[analytics]] start(("`Previous phase`")) @@ -208,15 +216,18 @@ flowchart LR start -- Eligibility API verification --> form form -- Eligibility API call --> server + form -. started eligibility -.-o analytics server --> eligible start -- Claims validation --> claims - session -. read .-> claims + session -.-o claims claims --> eligible + claims -. started eligibility -.-o analytics eligible -- Yes --> next eligible -- No --> stop - eligible -. update .-> session + eligible -. update .-o session + eligible -. returned eligibility -.-o analytics ``` ## Enrollment @@ -241,6 +252,7 @@ autonumber actor user as User participant benefits as Benefits app participant littlepay as Littlepay + participant analytics as Analytics user->>benefits: starts enrollment phase activate user @@ -259,8 +271,10 @@ benefits-->>user: access token activate user user->>user: click to initiate payment card collection user-->>user: display Littlepay overlay +user-->>analytics: started payment connection user->>littlepay: provides debit or credit card details littlepay-->>user: card token +user-->>analytics: closed payment connection user->>benefits: POST back card token deactivate user activate benefits @@ -269,6 +283,7 @@ littlepay-->>benefits: access token benefits->>littlepay: GET funding source from card token littlepay-->>benefits: funding source benefits->>littlepay: enroll funding source in group +benefits-->>analytics: returned enrollment deactivate benefits ``` From bc99c825289fd0e03c3d2e3b19d7db581a82ef06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:56:43 +0000 Subject: [PATCH 087/111] chore(deps): bump django-google-sso from 6.2.0 to 6.2.1 Bumps [django-google-sso](https://github.com/megalus/django-google-sso) from 6.2.0 to 6.2.1. - [Release notes](https://github.com/megalus/django-google-sso/releases) - [Changelog](https://github.com/megalus/django-google-sso/blob/main/CHANGELOG.md) - [Commits](https://github.com/megalus/django-google-sso/compare/v6.2.0...v6.2.1) --- updated-dependencies: - dependency-name: django-google-sso dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bf5f180d7..100dbf651 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "Django==5.0.6", "django-csp==3.8", "django-admin-sortable2==2.2.1", - "django-google-sso==6.2.0", + "django-google-sso==6.2.1", "eligibility-api==2023.9.1", "calitp-littlepay==2024.6.1", "requests==2.32.3", From 4c09fee920a7de75804d025b96f1ee6c331cf790 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 10 Jun 2024 16:47:46 -0700 Subject: [PATCH 088/111] refactor(container): move app root out of /home --- .devcontainer/devcontainer.json | 2 +- .devcontainer/postAttach.sh | 2 +- appcontainer/Dockerfile | 2 +- appcontainer/nginx.conf | 8 ++++---- compose.yml | 4 ++-- terraform/app_service.tf | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7cb87f035..7cda99910 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "dockerComposeFile": ["../compose.yml"], "service": "dev", "runServices": ["dev", "docs", "server"], - "workspaceFolder": "/home/calitp/app", + "workspaceFolder": "/calitp/app", "postStartCommand": ["/bin/bash", "bin/reset_db.sh"], "postAttachCommand": ["/bin/bash", ".devcontainer/postAttach.sh"], "customizations": { diff --git a/.devcontainer/postAttach.sh b/.devcontainer/postAttach.sh index 29495649d..b5c5440ca 100755 --- a/.devcontainer/postAttach.sh +++ b/.devcontainer/postAttach.sh @@ -3,5 +3,5 @@ set -eu # initialize pre-commit -git config --global --add safe.directory /home/calitp/app +git config --global --add safe.directory /calitp/app pre-commit install --overwrite diff --git a/appcontainer/Dockerfile b/appcontainer/Dockerfile index 4aa3cf9df..9e6cb3b98 100644 --- a/appcontainer/Dockerfile +++ b/appcontainer/Dockerfile @@ -5,7 +5,7 @@ RUN python -m pip install --upgrade pip # overwrite default nginx.conf COPY appcontainer/nginx.conf /etc/nginx/nginx.conf -COPY appcontainer/proxy.conf /home/calitp/run/proxy.conf +COPY appcontainer/proxy.conf /calitp/run/proxy.conf # copy source files COPY manage.py manage.py diff --git a/appcontainer/nginx.conf b/appcontainer/nginx.conf index 27f322248..b0fe492bd 100644 --- a/appcontainer/nginx.conf +++ b/appcontainer/nginx.conf @@ -22,7 +22,7 @@ http { upstream app_server { # fail_timeout=0 means we always retry an upstream even if it failed # to return a good HTTP response - server unix:/home/calitp/run/gunicorn.sock fail_timeout=0; + server unix:/calitp/run/gunicorn.sock fail_timeout=0; } # maps $binary_ip_address to $limit variable if request is of type POST @@ -67,7 +67,7 @@ http { # path for static files location /static/ { - alias /home/calitp/app/static/; + alias /calitp/app/static/; expires 1y; add_header Cache-Control public; } @@ -81,12 +81,12 @@ http { # case-insensitive regex matches path location ~* ^/(eligibility/confirm)$ { limit_req zone=rate_limit; - include /home/calitp/run/proxy.conf; + include /calitp/run/proxy.conf; } # app path location @proxy_to_app { - include /home/calitp/run/proxy.conf; + include /calitp/run/proxy.conf; } } } diff --git a/compose.yml b/compose.yml index 663aaf00d..c1a400b30 100644 --- a/compose.yml +++ b/compose.yml @@ -23,7 +23,7 @@ services: ports: - "${DJANGO_LOCAL_PORT:-8000}:8000" volumes: - - ./:/home/calitp/app + - ./:/calitp/app docs: image: benefits_client:dev @@ -32,7 +32,7 @@ services: ports: - "8001" volumes: - - ./:/home/calitp/app + - ./:/calitp/app server: image: ghcr.io/cal-itp/eligibility-server:dev diff --git a/terraform/app_service.tf b/terraform/app_service.tf index c150ceaca..15dd3e504 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -11,7 +11,7 @@ resource "azurerm_service_plan" "main" { } locals { - data_mount = "/home/calitp/app/data" + data_mount = "/calitp/app/data" } resource "azurerm_linux_web_app" "main" { From b018c42bd4ab7b702df5f8715a28ff734c61b3ab Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 11 Jun 2024 16:34:42 -0700 Subject: [PATCH 089/111] feat(dependabot): default PR status to In review --- .github/workflows/add-to-project-dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/add-to-project-dependabot.yml b/.github/workflows/add-to-project-dependabot.yml index ae42b5f6c..9ba3a8251 100644 --- a/.github/workflows/add-to-project-dependabot.yml +++ b/.github/workflows/add-to-project-dependabot.yml @@ -18,7 +18,7 @@ jobs: - uses: EndBug/project-fields@v2 with: operation: set - fields: Effort - values: 1 + fields: Effort,Status + values: 1,In review project_url: https://github.com/orgs/cal-itp/projects/${{ secrets.GH_PROJECT }} github_token: ${{ secrets.GH_PROJECTS_TOKEN }} From db2d0620d1d25c7c3a92c533c6b5f25623507b8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:32:07 +0000 Subject: [PATCH 090/111] chore(deps): bump azure-identity from 1.16.0 to 1.16.1 Bumps [azure-identity](https://github.com/Azure/azure-sdk-for-python) from 1.16.0 to 1.16.1. - [Release notes](https://github.com/Azure/azure-sdk-for-python/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-python/blob/main/doc/esrp_release.md) - [Commits](https://github.com/Azure/azure-sdk-for-python/compare/azure-identity_1.16.0...azure-identity_1.16.1) --- updated-dependencies: - dependency-name: azure-identity dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 100dbf651..91bb712ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ requires-python = ">=3.9" dependencies = [ "Authlib==1.3.1", "azure-keyvault-secrets==4.8.0", - "azure-identity==1.16.0", + "azure-identity==1.16.1", "Django==5.0.6", "django-csp==3.8", "django-admin-sortable2==2.2.1", From f64f3425c675e673ca742c61c06801e93ff50818 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 19:45:06 +0000 Subject: [PATCH 091/111] chore(deps): bump sentry-sdk from 2.5.0 to 2.5.1 Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.5.0 to 2.5.1. - [Release notes](https://github.com/getsentry/sentry-python/releases) - [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-python/compare/2.5.0...2.5.1) --- updated-dependencies: - dependency-name: sentry-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 91bb712ee..e8bff0969 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "eligibility-api==2023.9.1", "calitp-littlepay==2024.6.1", "requests==2.32.3", - "sentry-sdk==2.5.0", + "sentry-sdk==2.5.1", "six==1.16.0", ] From b4e9f444bfef11a72176f7197999f18776fbc4db Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 10 Jun 2024 16:19:08 +0000 Subject: [PATCH 092/111] refactor(oauth): move client registration to happen during view import this way we avoid reading from the database during any AppConfig.ready implementations, which Django warns us about because they are executed as a part of management commands as well. --- benefits/oauth/apps.py | 12 ------------ benefits/oauth/views.py | 5 ++++- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/benefits/oauth/apps.py b/benefits/oauth/apps.py index 8c9acf873..68d7be30f 100644 --- a/benefits/oauth/apps.py +++ b/benefits/oauth/apps.py @@ -9,15 +9,3 @@ class OAuthAppConfig(AppConfig): name = "benefits.oauth" label = "oauth" verbose_name = "Benefits OAuth" - - def ready(self): - # delay import until the ready() function is called, signaling that - # Django has loaded all the apps and models - from .client import oauth, register_providers - - # wrap registration in try/catch - # even though we are in a ready() function, sometimes it's called early? - try: - register_providers(oauth) - except Exception: - pass diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index 3c0c5c0f4..ad3b27d8b 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -6,7 +6,7 @@ from benefits.core import session from . import analytics, redirects -from .client import oauth +from .client import oauth, register_providers from .middleware import VerifierUsesAuthVerificationSessionRequired @@ -20,6 +20,9 @@ ROUTE_POST_LOGOUT = "oauth:post_logout" +register_providers(oauth) + + @decorator_from_middleware(VerifierUsesAuthVerificationSessionRequired) def login(request): """View implementing OIDC authorize_redirect.""" From c9e74946284eef1dd751bc7b988daf65a317290d Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Mon, 10 Jun 2024 17:40:03 +0000 Subject: [PATCH 093/111] refactor(oauth): change to approach of registering client in login view the register method seems to be idempotent, so it's fine to call it like this. the approach of registering during view import made importing the module complicated (e.g. when importing from the module for our tests) --- benefits/oauth/client.py | 28 +++++++++-------------- benefits/oauth/views.py | 8 +++---- tests/pytest/oauth/test_app.py | 23 ------------------- tests/pytest/oauth/test_client.py | 38 +++++++++++-------------------- 4 files changed, 27 insertions(+), 70 deletions(-) delete mode 100644 tests/pytest/oauth/test_app.py diff --git a/benefits/oauth/client.py b/benefits/oauth/client.py index 3be706194..67e3d759c 100644 --- a/benefits/oauth/client.py +++ b/benefits/oauth/client.py @@ -6,9 +6,6 @@ from authlib.integrations.django_client import OAuth -from benefits.core.models import AuthProvider - - logger = logging.getLogger(__name__) oauth = OAuth() @@ -42,23 +39,20 @@ def _authorize_params(scheme): return params -def register_providers(oauth_registry): +def register_provider(oauth_registry, provider): """ - Register OAuth clients into the given registry, using configuration from AuthProvider models. + Register OAuth clients into the given registry, using configuration from AuthProvider model. Adapted from https://stackoverflow.com/a/64174413. """ - logger.info("Registering OAuth clients") - - providers = AuthProvider.objects.all() + logger.debug(f"Registering OAuth client: {provider.client_name}") - for provider in providers: - logger.debug(f"Registering OAuth client: {provider.client_name}") + client = oauth_registry.register( + provider.client_name, + client_id=provider.client_id, + server_metadata_url=_server_metadata_url(provider.authority), + client_kwargs=_client_kwargs(provider.scope), + authorize_params=_authorize_params(provider.scheme), + ) - oauth_registry.register( - provider.client_name, - client_id=provider.client_id, - server_metadata_url=_server_metadata_url(provider.authority), - client_kwargs=_client_kwargs(provider.scope), - authorize_params=_authorize_params(provider.scheme), - ) + return client diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index ad3b27d8b..ec310b56f 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -6,7 +6,7 @@ from benefits.core import session from . import analytics, redirects -from .client import oauth, register_providers +from .client import oauth, register_provider from .middleware import VerifierUsesAuthVerificationSessionRequired @@ -20,14 +20,12 @@ ROUTE_POST_LOGOUT = "oauth:post_logout" -register_providers(oauth) - - @decorator_from_middleware(VerifierUsesAuthVerificationSessionRequired) def login(request): """View implementing OIDC authorize_redirect.""" verifier = session.verifier(request) - oauth_client = oauth.create_client(verifier.auth_provider.client_name) + + oauth_client = register_provider(oauth, verifier.auth_provider) if not oauth_client: raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") diff --git a/tests/pytest/oauth/test_app.py b/tests/pytest/oauth/test_app.py deleted file mode 100644 index 93b1aaeb6..000000000 --- a/tests/pytest/oauth/test_app.py +++ /dev/null @@ -1,23 +0,0 @@ -import benefits -from benefits.oauth.apps import OAuthAppConfig - - -def test_ready_registers_clients(mocker): - mock_registry = mocker.patch("benefits.oauth.client.oauth") - mock_register_providers = mocker.patch("benefits.oauth.client.register_providers") - - app = OAuthAppConfig("oauth", benefits) - app.ready() - - mock_register_providers.assert_called_once_with(mock_registry) - - -def test_ready_register_exception(mocker): - mocker.patch("benefits.oauth.client.oauth") - mocker.patch("benefits.oauth.client.register_providers", side_effect=Exception) - - app = OAuthAppConfig("oauth", benefits) - app.ready() - - # we expect no Exception to be raised - assert app diff --git a/tests/pytest/oauth/test_client.py b/tests/pytest/oauth/test_client.py index 4c6286cd6..4cc25dd51 100644 --- a/tests/pytest/oauth/test_client.py +++ b/tests/pytest/oauth/test_client.py @@ -1,7 +1,7 @@ import pytest from benefits.core.models import AuthProvider -from benefits.oauth.client import _client_kwargs, _server_metadata_url, _authorize_params, register_providers +from benefits.oauth.client import _client_kwargs, _server_metadata_url, _authorize_params, register_provider def test_client_kwargs(): @@ -39,33 +39,21 @@ def test_authorize_params_no_scheme(): @pytest.mark.django_db -def test_register_providers(mocker, mocked_oauth_registry): - mock_providers = [] - - for i in range(3): - p = mocker.Mock(spec=AuthProvider) - p.client_name = f"client_name_{i}" - p.client_id = f"client_id_{i}" - mock_providers.append(p) - - mocked_client_provider = mocker.patch("benefits.oauth.client.AuthProvider") - mocked_client_provider.objects.all.return_value = mock_providers +def test_register_provider(mocker, mocked_oauth_registry): + mocked_client_provider = mocker.Mock(spec=AuthProvider) + mocked_client_provider.client_name = "client_name_1" + mocked_client_provider.client_id = "client_id_1" mocker.patch("benefits.oauth.client._client_kwargs", return_value={"client": "kwargs"}) mocker.patch("benefits.oauth.client._server_metadata_url", return_value="https://metadata.url") mocker.patch("benefits.oauth.client._authorize_params", return_value={"scheme": "test_scheme"}) - register_providers(mocked_oauth_registry) - - mocked_client_provider.objects.all.assert_called_once() - - for provider in mock_providers: - i = mock_providers.index(provider) + register_provider(mocked_oauth_registry, mocked_client_provider) - mocked_oauth_registry.register.assert_any_call( - f"client_name_{i}", - client_id=f"client_id_{i}", - server_metadata_url="https://metadata.url", - client_kwargs={"client": "kwargs"}, - authorize_params={"scheme": "test_scheme"}, - ) + mocked_oauth_registry.register.assert_any_call( + "client_name_1", + client_id="client_id_1", + server_metadata_url="https://metadata.url", + client_kwargs={"client": "kwargs"}, + authorize_params={"scheme": "test_scheme"}, + ) From 770f8e185a192f802f1bec47b31c5b6e48a2b763 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 21:17:59 +0000 Subject: [PATCH 094/111] feat: ensure client is registered for authorize view currently the only way to get to the authorize view is from the login view, so the client should already be registered. But it doesn't hurt to call it here either. --- benefits/oauth/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index ec310b56f..e99c94a85 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -44,7 +44,7 @@ def login(request): def authorize(request): """View implementing OIDC token authorization.""" verifier = session.verifier(request) - oauth_client = oauth.create_client(verifier.auth_provider.client_name) + oauth_client = register_provider(oauth, verifier.auth_provider) if not oauth_client: raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") From 08dcebe856edd1bcee4eec2e0b2a8078cee57646 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 21:40:21 +0000 Subject: [PATCH 095/111] chore(deps): bump dawidd6/action-download-artifact from 5 to 6 Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 5 to 6. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index 50a092a00..49306225b 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -92,7 +92,7 @@ jobs: uses: actions/checkout@v4 - name: Download coverage report - uses: dawidd6/action-download-artifact@v5 + uses: dawidd6/action-download-artifact@v6 with: workflow: tests-pytest.yml branch: dev From 4d2e1ec854acb45f25b9b770492becdd636fc7c4 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 21:59:14 +0000 Subject: [PATCH 096/111] refactor(oauth): attempt to create client and register if needed taking this approach in hopes of it being a more readable solution. --- benefits/oauth/client.py | 14 ++++++++++- benefits/oauth/views.py | 6 ++--- tests/pytest/oauth/test_client.py | 40 +++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/benefits/oauth/client.py b/benefits/oauth/client.py index 67e3d759c..923b29ab6 100644 --- a/benefits/oauth/client.py +++ b/benefits/oauth/client.py @@ -39,7 +39,7 @@ def _authorize_params(scheme): return params -def register_provider(oauth_registry, provider): +def _register_provider(oauth_registry, provider): """ Register OAuth clients into the given registry, using configuration from AuthProvider model. @@ -56,3 +56,15 @@ def register_provider(oauth_registry, provider): ) return client + + +def create_client(oauth_registry, provider): + """ + Returns an OAuth client, registering it if needed. + """ + client = oauth_registry.create_client(provider.client_name) + + if client is None: + client = _register_provider(oauth_registry, provider) + + return client diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index e99c94a85..9ce8297bf 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -6,7 +6,7 @@ from benefits.core import session from . import analytics, redirects -from .client import oauth, register_provider +from .client import oauth, create_client from .middleware import VerifierUsesAuthVerificationSessionRequired @@ -25,7 +25,7 @@ def login(request): """View implementing OIDC authorize_redirect.""" verifier = session.verifier(request) - oauth_client = register_provider(oauth, verifier.auth_provider) + oauth_client = create_client(oauth, verifier.auth_provider) if not oauth_client: raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") @@ -44,7 +44,7 @@ def login(request): def authorize(request): """View implementing OIDC token authorization.""" verifier = session.verifier(request) - oauth_client = register_provider(oauth, verifier.auth_provider) + oauth_client = create_client(oauth, verifier.auth_provider) if not oauth_client: raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") diff --git a/tests/pytest/oauth/test_client.py b/tests/pytest/oauth/test_client.py index 4cc25dd51..3bfe63df8 100644 --- a/tests/pytest/oauth/test_client.py +++ b/tests/pytest/oauth/test_client.py @@ -1,7 +1,7 @@ import pytest from benefits.core.models import AuthProvider -from benefits.oauth.client import _client_kwargs, _server_metadata_url, _authorize_params, register_provider +from benefits.oauth.client import _client_kwargs, _server_metadata_url, _authorize_params, _register_provider, create_client def test_client_kwargs(): @@ -48,7 +48,7 @@ def test_register_provider(mocker, mocked_oauth_registry): mocker.patch("benefits.oauth.client._server_metadata_url", return_value="https://metadata.url") mocker.patch("benefits.oauth.client._authorize_params", return_value={"scheme": "test_scheme"}) - register_provider(mocked_oauth_registry, mocked_client_provider) + _register_provider(mocked_oauth_registry, mocked_client_provider) mocked_oauth_registry.register.assert_any_call( "client_name_1", @@ -57,3 +57,39 @@ def test_register_provider(mocker, mocked_oauth_registry): client_kwargs={"client": "kwargs"}, authorize_params={"scheme": "test_scheme"}, ) + + +@pytest.mark.django_db +def test_create_client_already_registered(mocker, mocked_oauth_registry): + mocked_client_provider = mocker.Mock(spec=AuthProvider) + mocked_client_provider.client_name = "client_name_1" + mocked_client_provider.client_id = "client_id_1" + + create_client(mocked_oauth_registry, mocked_client_provider) + + mocked_oauth_registry.create_client.assert_any_call("client_name_1") + mocked_oauth_registry.register.assert_not_called() + + +@pytest.mark.django_db +def test_create_client_already_not_registered_yet(mocker, mocked_oauth_registry): + mocked_client_provider = mocker.Mock(spec=AuthProvider) + mocked_client_provider.client_name = "client_name_1" + mocked_client_provider.client_id = "client_id_1" + + mocker.patch("benefits.oauth.client._client_kwargs", return_value={"client": "kwargs"}) + mocker.patch("benefits.oauth.client._server_metadata_url", return_value="https://metadata.url") + mocker.patch("benefits.oauth.client._authorize_params", return_value={"scheme": "test_scheme"}) + + mocked_oauth_registry.create_client.return_value = None + + create_client(mocked_oauth_registry, mocked_client_provider) + + mocked_oauth_registry.create_client.assert_any_call("client_name_1") + mocked_oauth_registry.register.assert_any_call( + "client_name_1", + client_id="client_id_1", + server_metadata_url="https://metadata.url", + client_kwargs={"client": "kwargs"}, + authorize_params={"scheme": "test_scheme"}, + ) From ff98a62f1e6546f5e9e894fd4090f82ac9a110c2 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 12 Jun 2024 14:00:26 -0700 Subject: [PATCH 097/111] fix(terraform): turn on manual integration we don't want Azure continuous deployment / webhooks --- terraform/app_service.tf | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 15dd3e504..e025e2d31 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -102,9 +102,10 @@ resource "azurerm_linux_web_app" "main" { } resource "azurerm_app_service_source_control" "main" { - app_id = azurerm_linux_web_app.main.id - repo_url = "https://github.com/cal-itp/benefits" - branch = local.env_name + app_id = azurerm_linux_web_app.main.id + repo_url = "https://github.com/cal-itp/benefits" + branch = local.env_name + use_manual_integration = true } resource "azurerm_app_service_custom_hostname_binding" "main" { From f7951f7b0460006fa77796b1e73c0f618ea6a6e9 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 12 Jun 2024 14:01:23 -0700 Subject: [PATCH 098/111] fix(terraform): add container config block it seems like Deployment Center is stuck with pending changes --- terraform/app_service.tf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index e025e2d31..af9392351 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -106,6 +106,14 @@ resource "azurerm_app_service_source_control" "main" { repo_url = "https://github.com/cal-itp/benefits" branch = local.env_name use_manual_integration = true + + github_action_configuration { + generate_workflow_file = false + container_configuration { + registry_url = "https://ghcr.io/" + image_name = "cal-itp/benefits" + } + } } resource "azurerm_app_service_custom_hostname_binding" "main" { From 06186fa04ff990978f6b6fe10049aecea576a834 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 22:12:06 +0000 Subject: [PATCH 099/111] docs(oauth): update details around client registration --- docs/configuration/oauth.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/configuration/oauth.md b/docs/configuration/oauth.md index 6966a5d10..b8f33ba66 100644 --- a/docs/configuration/oauth.md +++ b/docs/configuration/oauth.md @@ -31,14 +31,9 @@ The [data migration file](./data.md) contains sample values for an `AuthProvider The [`benefits.oauth.client`][oauth-client] module defines helpers for registering OAuth clients, and creating instances for use in e.g. views. -- `register_providers(oauth_registry)` uses data from `AuthProvider` instances to register clients into the given registry - `oauth` is an `authlib.integrations.django_client.OAuth` instance -Providers are registered into this instance once in the [`OAuthAppConfig.ready()`][oauth-app-ready] function at application -startup. +Consumers call `benefits.oauth.client.create_client(oauth, provider)` with the name of a client to obtain an Authlib client +instance. If that client name has not been registered yet, `_register_provider(oauth_registry, provider)` uses data from the given `AuthProvider` instance to register the client into this instance and returns the client object. -Consumers call `oauth.create_client(client_name)` with the name of a previously registered client to obtain an Authlib client -instance. - -[oauth-app-ready]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/__init__.py [oauth-client]: https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/client.py From b28fc6ddcf9753f9294189cad303799f2211eca8 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 13 Jun 2024 10:42:05 -0700 Subject: [PATCH 100/111] Revert "fix(terraform): turn on manual integration" This reverts commit ff98a62f1e6546f5e9e894fd4090f82ac9a110c2. --- terraform/app_service.tf | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index af9392351..920df890b 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -102,10 +102,9 @@ resource "azurerm_linux_web_app" "main" { } resource "azurerm_app_service_source_control" "main" { - app_id = azurerm_linux_web_app.main.id - repo_url = "https://github.com/cal-itp/benefits" - branch = local.env_name - use_manual_integration = true + app_id = azurerm_linux_web_app.main.id + repo_url = "https://github.com/cal-itp/benefits" + branch = local.env_name github_action_configuration { generate_workflow_file = false From 806fe02afeac51862c75a9b08a8e0866ebb670e3 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 13 Jun 2024 11:27:01 -0700 Subject: [PATCH 101/111] fix(terraform): remove source control block this doesn't seem to be configurable via Terraform in a way that makes the UI match what we expect --- terraform/app_service.tf | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 920df890b..981e6fd26 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -101,20 +101,6 @@ resource "azurerm_linux_web_app" "main" { } } -resource "azurerm_app_service_source_control" "main" { - app_id = azurerm_linux_web_app.main.id - repo_url = "https://github.com/cal-itp/benefits" - branch = local.env_name - - github_action_configuration { - generate_workflow_file = false - container_configuration { - registry_url = "https://ghcr.io/" - image_name = "cal-itp/benefits" - } - } -} - resource "azurerm_app_service_custom_hostname_binding" "main" { hostname = local.hostname app_service_name = azurerm_linux_web_app.main.name From 4ed3c96b9cdff415d17d4227ab95cbc314b0a2f0 Mon Sep 17 00:00:00 2001 From: Andy Walker Date: Thu, 13 Jun 2024 08:49:30 -0700 Subject: [PATCH 102/111] Roadmap updates - Moved Benefits admin (in-person eligibility) to Q3 from Q4 - Moved Medicare cardholder enrollments to Q4 from Q3 - Changed references to "payment processor" to "transit processor," the more correct term. --- docs/enrollment-pathways/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/enrollment-pathways/README.md b/docs/enrollment-pathways/README.md index 6374e96dd..a1aed2fe6 100644 --- a/docs/enrollment-pathways/README.md +++ b/docs/enrollment-pathways/README.md @@ -37,10 +37,10 @@ timeline Q3
Next : Benefits admin tool (Agency configuration) : Benefits admin tool (Agency users) - : Release Medicare cardholder enrollment pathway + : Benefits admin tool (In-person eligibility verification) Q4
Planned - : Benefits admin tool (In-person eligibility verification) + : Release Medicare cardholder enrollment pathway : Release riders with disabilities enrollment pathway %% Cal-ITP Benefits Epics (2025) @@ -51,8 +51,8 @@ timeline : Implement evolved organizing principles for app UX Q2 - : Support for multiple payment processors - : Integration with all MSA payment processors + : Support for multiple transit processors + : Integration with all MSA transit processors %%{ init: { From 76a5b1ed3e751350eb62a2912ae6cb21fab8cffb Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 17:59:10 +0000 Subject: [PATCH 103/111] docs(infra): move Environments section up, move some sections under it no content edit, just moving things around --- docs/deployment/infrastructure.md | 161 +++++++++++++++--------------- 1 file changed, 81 insertions(+), 80 deletions(-) diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index e16212a24..36025f12b 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -2,9 +2,68 @@ The infrastructure is configured as code via [Terraform](https://www.terraform.io/), for [various reasons](https://techcommunity.microsoft.com/t5/fasttrack-for-azure/the-benefits-of-infrastructure-as-code/ba-p/2069350). -## Architecture +## Environments + +Within the `CDT Digital CA` directory ([how to switch](https://learn.microsoft.com/en-us/azure/devtest/offer/how-to-change-directory-tenants-visual-studio-azure)), there are two [Subscriptions](https://learn.microsoft.com/en-us/microsoft-365/enterprise/subscriptions-licenses-accounts-and-tenants-for-microsoft-cloud-offerings?view=o365-worldwide#subscriptions), with Resource Groups under each. Each environment corresponds to a single Resource Group, [Terraform Workspace](https://developer.hashicorp.com/terraform/language/state/workspaces), and branch. + +| Environment | Subscription | Resource Group | Workspace | Branch | +| ----------- | --------------------- | ----------------------------- | --------- | ------ | +| Dev | `CDT/ODI Development` | `RG-CDT-PUB-VIP-CALITP-D-001` | `dev` | `dev` | +| Test | `CDT/ODI Development` | `RG-CDT-PUB-VIP-CALITP-T-001` | `test` | `test` | +| Prod | `CDT/ODI Production` | `RG-CDT-PUB-VIP-CALITP-P-001` | `default` | `prod` | + +All resources in these Resource Groups should be reflected in Terraform in this repository. The exceptions are: + +- Secrets, such as values under [Key Vault](https://azure.microsoft.com/en-us/services/key-vault/). [`prevent_destroy`](https://developer.hashicorp.com/terraform/tutorials/state/resource-lifecycle#prevent-resource-deletion) is used on these Resources. +- [Things managed by DevSecOps](#ownership) + +You'll see these referenced in Terraform as [data sources](https://developer.hashicorp.com/terraform/language/data-sources). + +For browsing the [Azure portal](https://portal.azure.com), you can [switch your `Default subscription filter`](https://docs.microsoft.com/en-us/azure/azure-portal/set-preferences). -### System interconnections +### Ownership + +The following things in Azure are managed by the California Department of Technology (CDT)'s DevSecOps (OET) team: + +- Subcriptions +- [Resource Groups](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal) +- Networking +- Front Door + - Web Application Firewall (WAF) + - Distributed denial-of-service (DDoS) protection +- IAM +- Service connections + +### Architecture + +#### Benefits application + +```mermaid +flowchart LR + internet[Public internet] + frontdoor[Front Door] + django[Django application] + interconnections[Other system interconnections] + + internet --> Cloudflare + Cloudflare --> frontdoor + django <--> interconnections + + subgraph Azure + frontdoor --> NGINX + + subgraph App Service + subgraph Custom container + direction TB + NGINX --> django + end + end + end +``` + +[Front Door](https://docs.microsoft.com/en-us/azure/frontdoor/front-door-overview) also includes the [Web Application Firewall (WAF)](https://docs.microsoft.com/en-us/azure/web-application-firewall/afds/afds-overview) and handles TLS termination. Front Door is managed by the DevSecOps team. + +#### System interconnections ```mermaid flowchart LR @@ -42,64 +101,35 @@ flowchart LR idg -->|User attributes| benefits ``` -### Benefits application - -```mermaid -flowchart LR - internet[Public internet] - frontdoor[Front Door] - django[Django application] - interconnections[Other system interconnections] - - internet --> Cloudflare - Cloudflare --> frontdoor - django <--> interconnections +### Naming conventions - subgraph Azure - frontdoor --> NGINX +The DevSecOps team sets the following naming convention for Resources: - subgraph App Service - subgraph Custom container - direction TB - NGINX --> django - end - end - end +``` +<>-<>-<>-<>-<>-<><>-<>-<> ``` -[Front Door](https://docs.microsoft.com/en-us/azure/frontdoor/front-door-overview) also includes the [Web Application Firewall (WAF)](https://docs.microsoft.com/en-us/azure/web-application-firewall/afds/afds-overview) and handles TLS termination. Front Door is managed by the DevSecOps team. - -## Ownership - -The following things in Azure are managed by the California Department of Technology (CDT)'s DevSecOps (OET) team: - -- Subcriptions -- [Resource Groups](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal) -- Networking -- Front Door - - Web Application Firewall (WAF) - - Distributed denial-of-service (DDoS) protection -- IAM -- Service connections - -## Environments - -Within the `CDT Digital CA` directory ([how to switch](https://learn.microsoft.com/en-us/azure/devtest/offer/how-to-change-directory-tenants-visual-studio-azure)), there are two [Subscriptions](https://learn.microsoft.com/en-us/microsoft-365/enterprise/subscriptions-licenses-accounts-and-tenants-for-microsoft-cloud-offerings?view=o365-worldwide#subscriptions), with Resource Groups under each. Each environment corresponds to a single Resource Group, [Terraform Workspace](https://developer.hashicorp.com/terraform/language/state/workspaces), and branch. +#### Sample Names -| Environment | Subscription | Resource Group | Workspace | Branch | -| ----------- | --------------------- | ----------------------------- | --------- | ------ | -| Dev | `CDT/ODI Development` | `RG-CDT-PUB-VIP-CALITP-D-001` | `dev` | `dev` | -| Test | `CDT/ODI Development` | `RG-CDT-PUB-VIP-CALITP-T-001` | `test` | `test` | -| Prod | `CDT/ODI Production` | `RG-CDT-PUB-VIP-CALITP-P-001` | `default` | `prod` | +- `RG-CDT-PUB-VIP-BNSCN-E-D-001` +- `ASP-CDT-PUB-VIP-BNSCN-EL-P-001` +- `AS-CDT-PUB-VIP-BNSCN-EL-D-001` -All resources in these Resource Groups should be reflected in Terraform in this repository. The exceptions are: +#### Resource Types -- Secrets, such as values under [Key Vault](https://azure.microsoft.com/en-us/services/key-vault/). [`prevent_destroy`](https://developer.hashicorp.com/terraform/tutorials/state/resource-lifecycle#prevent-resource-deletion) is used on these Resources. -- [Things managed by DevSecOps](#ownership) +Use the following shorthand for conveying the Resource Type as part of the Resource Name: -You'll see these referenced in Terraform as [data sources](https://developer.hashicorp.com/terraform/language/data-sources). +| Resource | Convention | +| ---------------- | ---------- | +| App Service | `AS` | +| App Service Plan | `ASP` | +| Virtual Network | `VNET` | +| Resource Group | `RG` | +| Virtual Machine | `VM` | +| Database | `DB` | +| Subnet | `SNET` | +| Front Door | `FD` | -For browsing the [Azure portal](https://portal.azure.com), you can [switch your `Default subscription filter`](https://docs.microsoft.com/en-us/azure/azure-portal/set-preferences). ## Making changes @@ -149,35 +179,6 @@ lifecycle { } ``` -### Naming conventions - -The DevSecOps team sets the following naming convention for Resources: - -``` -<>-<>-<>-<>-<>-<><>-<>-<> -``` - -#### Sample Names - -- `RG-CDT-PUB-VIP-BNSCN-E-D-001` -- `ASP-CDT-PUB-VIP-BNSCN-EL-P-001` -- `AS-CDT-PUB-VIP-BNSCN-EL-D-001` - -#### Resource Types - -Use the following shorthand for conveying the Resource Type as part of the Resource Name: - -| Resource | Convention | -| ---------------- | ---------- | -| App Service | `AS` | -| App Service Plan | `ASP` | -| Virtual Network | `VNET` | -| Resource Group | `RG` | -| Virtual Machine | `VM` | -| Database | `DB` | -| Subnet | `SNET` | -| Front Door | `FD` | - ## Azure environment setup The following steps are required to set up the environment: From 7c863a8264bfce3bcb9a249617b60ffdf3eef2f1 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 18:04:14 +0000 Subject: [PATCH 104/111] docs(infra): content edits to sections under Environments --- docs/deployment/infrastructure.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index 36025f12b..c1c74dd57 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -4,7 +4,9 @@ The infrastructure is configured as code via [Terraform](https://www.terraform.i ## Environments -Within the `CDT Digital CA` directory ([how to switch](https://learn.microsoft.com/en-us/azure/devtest/offer/how-to-change-directory-tenants-visual-studio-azure)), there are two [Subscriptions](https://learn.microsoft.com/en-us/microsoft-365/enterprise/subscriptions-licenses-accounts-and-tenants-for-microsoft-cloud-offerings?view=o365-worldwide#subscriptions), with Resource Groups under each. Each environment corresponds to a single Resource Group, [Terraform Workspace](https://developer.hashicorp.com/terraform/language/state/workspaces), and branch. +Within the `CDT Digital CA` directory, there are two [Subscriptions](https://learn.microsoft.com/en-us/microsoft-365/enterprise/subscriptions-licenses-accounts-and-tenants-for-microsoft-cloud-offerings?view=o365-worldwide#subscriptions), with Resource Groups under each. (Refer to Azure's documentation for [switching directories](https://learn.microsoft.com/en-us/azure/devtest/offer/how-to-change-directory-tenants-visual-studio-azure).) + +Each of our environments corresponds to a single Resource Group, [Terraform Workspace](https://developer.hashicorp.com/terraform/language/state/workspaces), and branch. | Environment | Subscription | Resource Group | Workspace | Branch | | ----------- | --------------------- | ----------------------------- | --------- | ------ | @@ -17,10 +19,6 @@ All resources in these Resource Groups should be reflected in Terraform in this - Secrets, such as values under [Key Vault](https://azure.microsoft.com/en-us/services/key-vault/). [`prevent_destroy`](https://developer.hashicorp.com/terraform/tutorials/state/resource-lifecycle#prevent-resource-deletion) is used on these Resources. - [Things managed by DevSecOps](#ownership) -You'll see these referenced in Terraform as [data sources](https://developer.hashicorp.com/terraform/language/data-sources). - -For browsing the [Azure portal](https://portal.azure.com), you can [switch your `Default subscription filter`](https://docs.microsoft.com/en-us/azure/azure-portal/set-preferences). - ### Ownership The following things in Azure are managed by the California Department of Technology (CDT)'s DevSecOps (OET) team: @@ -34,8 +32,12 @@ The following things in Azure are managed by the California Department of Techno - IAM - Service connections +You'll see these referenced in Terraform as [data sources](https://developer.hashicorp.com/terraform/language/data-sources), meaning they are managed outside of Terraform. + ### Architecture +These diagrams show a high-level view of the architecture per environment, including some external systems (e.g. analytics, error monitoring, eligibility servers). + #### Benefits application ```mermaid @@ -44,14 +46,11 @@ flowchart LR frontdoor[Front Door] django[Django application] interconnections[Other system interconnections] - internet --> Cloudflare Cloudflare --> frontdoor django <--> interconnections - subgraph Azure frontdoor --> NGINX - subgraph App Service subgraph Custom container direction TB From e0cfacabe76f2df3ec90fbe7da72db4e0445f0a6 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 19:02:58 +0000 Subject: [PATCH 105/111] docs(infra): add introductory section --- docs/deployment/infrastructure.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index c1c74dd57..f16e1468d 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -2,6 +2,12 @@ The infrastructure is configured as code via [Terraform](https://www.terraform.io/), for [various reasons](https://techcommunity.microsoft.com/t5/fasttrack-for-azure/the-benefits-of-infrastructure-as-code/ba-p/2069350). +## Getting started + +Since the Benefits app is deployed into a Microsoft Azure account provided by the California Department of Technology (CDT)'s Office of Enterprise Technology (OET) team, you'll need to request access from them to the `CDT Digital CA` directory so you can get into the [Azure portal](https://portal.azure.com), and to the `California Department of Technology` directory so you can access [Azure DevOps](https://calenterprise.visualstudio.com/CDT.OET.CAL-ITP). + +The Azure portal is where you can view the infrastructure resources for Benefits. Azure DevOps is where our [infrastructure pipeline](https://github.com/cal-itp/benefits/blob/dev/terraform/azure-pipelines.yml) is run to build and deploy those infrastructure resources. + ## Environments Within the `CDT Digital CA` directory, there are two [Subscriptions](https://learn.microsoft.com/en-us/microsoft-365/enterprise/subscriptions-licenses-accounts-and-tenants-for-microsoft-cloud-offerings?view=o365-worldwide#subscriptions), with Resource Groups under each. (Refer to Azure's documentation for [switching directories](https://learn.microsoft.com/en-us/azure/devtest/offer/how-to-change-directory-tenants-visual-studio-azure).) From 5234ef861afa6bfe45af06cfa6c3f7d55518619d Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 19:05:54 +0000 Subject: [PATCH 106/111] docs(infra): content edits to sections about making changes --- docs/deployment/infrastructure.md | 45 +++++++++++++++++++------------ 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index f16e1468d..fdcea51bc 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -138,16 +138,9 @@ Use the following shorthand for conveying the Resource Type as part of the Resou ## Making changes -[![Build Status](https://calenterprise.visualstudio.com/CDT.OET.CAL-ITP/_apis/build/status/cal-itp.benefits%20Infra?branchName=dev)](https://calenterprise.visualstudio.com/CDT.OET.CAL-ITP/_build/latest?definitionId=828&branchName=dev) - -Terraform is [`plan`](https://www.terraform.io/cli/commands/plan)'d when code is pushed to any branch on GitHub, then [`apply`](https://www.terraform.io/cli/commands/apply)'d when merged to `dev`. While other automation for this project is done through GitHub Actions, we use an Azure Pipeline (above) for a couple of reasons: - -- Easier authentication with the Azure API using a service connnection -- Log output is hidden, avoiding accidentally leaking secrets +### Set up for local development -### Local development - -1. Get access to the Azure account through the DevSecOps team. +1. [Get access to the Azure account through the DevSecOps team.](#getting-started) 1. Install dependencies: - [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) @@ -167,6 +160,11 @@ Terraform is [`plan`](https://www.terraform.io/cli/commands/plan)'d when code is ``` 1. Create a local `terraform.tfvars` file (ignored by git) from the sample; fill in the `*_OBJECT_ID` variables with values from the Azure Pipeline definition. + +### Development process + +When configuration changes to infrastructure resources are needed, they should be made to the resource definitions in Terraform and submitted via pull request. + 1. Make changes to Terraform files. 1. Preview the changes, as necessary. @@ -174,20 +172,33 @@ Terraform is [`plan`](https://www.terraform.io/cli/commands/plan)'d when code is terraform plan ``` -1. [Submit the changes via pull request.](../development/commits-branches-merging/) +1. [Submit the changes via pull request.](../../development/commits-branches-merging) -For Azure resources, you need to [ignore changes](https://www.terraform.io/language/meta-arguments/lifecycle#ignore_changes) to tags, since they are [automatically created by Azure Policy](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/tag-policies). +!!! info "Azure tags" + For Azure resources, you need to [ignore changes](https://www.terraform.io/language/meta-arguments/lifecycle#ignore_changes) to tags, since they are [automatically created by an Azure Policy managed by CDT](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/tag-policies). -```hcl -lifecycle { - ignore_changes = [tags] -} -``` + ```hcl + lifecycle { + ignore_changes = [tags] + } + ``` + +### Infrastructure pipeline + +[![Build Status](https://calenterprise.visualstudio.com/CDT.OET.CAL-ITP/_apis/build/status/cal-itp.benefits%20Infra?branchName=dev)](https://calenterprise.visualstudio.com/CDT.OET.CAL-ITP/_build/latest?definitionId=828&branchName=dev) + +When code is pushed to any branch on GitHub, our infrastructure pipeline in Azure DevOps runs [`terraform plan`](https://www.terraform.io/cli/commands/plan). When the pull request is merged into `dev`, the pipeline runs [`terraform apply`](https://www.terraform.io/cli/commands/apply). + +While other automation for this project is done through GitHub Actions, we use an Azure Pipeline for a couple of reasons: + +- Easier authentication with the Azure API using a service connnection +- Log output is hidden, avoiding accidentally leaking secrets ## Azure environment setup -The following steps are required to set up the environment: +These steps were followed when setting up our Azure deployment for the first time: +- CDT team creates the [resources that they own](#ownership) - `terraform apply` - Set up Slack notifications by [creating a Slack email](https://slack.com/help/articles/206819278-Send-emails-to-Slack) for the [#notify-benefits](https://cal-itp.slack.com/archives/C022HHSEE3F) channel, then [setting it as a Secret in the Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/secrets/quick-create-portal#add-a-secret-to-key-vault) named `slack-benefits-notify-email` - Set required [App Service configuration](../configuration/environment-variables.md) and [configuration](../configuration/data.md) by setting values in Key Vault (the mapping is defined in [app_service.tf](https://github.com/cal-itp/benefits/blob/dev/terraform/app_service.tf)) From d5c8d1c7669ba67964ee09ebc50e3b7a96ad4597 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 19:17:45 +0000 Subject: [PATCH 107/111] chore: whitespace from formatter --- docs/deployment/infrastructure.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index fdcea51bc..1cb0196fd 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -52,11 +52,14 @@ flowchart LR frontdoor[Front Door] django[Django application] interconnections[Other system interconnections] + internet --> Cloudflare Cloudflare --> frontdoor django <--> interconnections + subgraph Azure frontdoor --> NGINX + subgraph App Service subgraph Custom container direction TB @@ -135,7 +138,6 @@ Use the following shorthand for conveying the Resource Type as part of the Resou | Subnet | `SNET` | | Front Door | `FD` | - ## Making changes ### Set up for local development From 232a7f91b44d6f019cb42f8d6c9f9b6ba0dc5ca3 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 12 Jun 2024 22:53:51 +0000 Subject: [PATCH 108/111] docs(infra): move some sections up one level --- docs/deployment/infrastructure.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index 1cb0196fd..e6512a8a0 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -40,11 +40,11 @@ The following things in Azure are managed by the California Department of Techno You'll see these referenced in Terraform as [data sources](https://developer.hashicorp.com/terraform/language/data-sources), meaning they are managed outside of Terraform. -### Architecture +## Architecture These diagrams show a high-level view of the architecture per environment, including some external systems (e.g. analytics, error monitoring, eligibility servers). -#### Benefits application +### Benefits application ```mermaid flowchart LR @@ -71,7 +71,7 @@ flowchart LR [Front Door](https://docs.microsoft.com/en-us/azure/frontdoor/front-door-overview) also includes the [Web Application Firewall (WAF)](https://docs.microsoft.com/en-us/azure/web-application-firewall/afds/afds-overview) and handles TLS termination. Front Door is managed by the DevSecOps team. -#### System interconnections +### System interconnections ```mermaid flowchart LR @@ -109,7 +109,7 @@ flowchart LR idg -->|User attributes| benefits ``` -### Naming conventions +## Naming conventions The DevSecOps team sets the following naming convention for Resources: @@ -117,13 +117,13 @@ The DevSecOps team sets the following naming convention for Resources: <>-<>-<>-<>-<>-<><>-<>-<> ``` -#### Sample Names +### Sample Names - `RG-CDT-PUB-VIP-BNSCN-E-D-001` - `ASP-CDT-PUB-VIP-BNSCN-EL-P-001` - `AS-CDT-PUB-VIP-BNSCN-EL-D-001` -#### Resource Types +### Resource Types Use the following shorthand for conveying the Resource Type as part of the Resource Name: From 6587a7dffcff05062b9790952de32ee63da64930 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 13 Jun 2024 13:21:46 -0700 Subject: [PATCH 109/111] feat(terraform): enable app service storage Azure will manage the /home directory and restore it when the app restarts --- terraform/app_service.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 981e6fd26..029ee3797 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -47,7 +47,7 @@ resource "azurerm_linux_web_app" "main" { app_settings = { "DOCKER_REGISTRY_SERVER_URL" = "https://ghcr.io/" "WEBSITE_TIME_ZONE" = "America/Los_Angeles", - "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false", + "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "true", "WEBSITES_PORT" = "8000", "ANALYTICS_KEY" = local.is_dev ? null : "${local.secret_prefix}analytics-key)", From 3f234b23b026ab7bf3269db323c5bb4d7ad88eea Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Fri, 14 Jun 2024 17:24:53 +0000 Subject: [PATCH 110/111] fix: relax condition so that empty strings will also be filtered out when the admin interface saves back a blank TextField, it saves it as an empty string. --- benefits/core/context_processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/core/context_processors.py b/benefits/core/context_processors.py index d010ab284..0d54ae5f1 100644 --- a/benefits/core/context_processors.py +++ b/benefits/core/context_processors.py @@ -15,7 +15,7 @@ def unique_values(original_list): def _agency_context(agency): return { "eligibility_index_url": agency.eligibility_index_url, - "help_templates": unique_values([v.help_template for v in agency.active_verifiers if v.help_template is not None]), + "help_templates": unique_values([v.help_template for v in agency.active_verifiers if v.help_template]), "info_url": agency.info_url, "long_name": agency.long_name, "phone": agency.phone, From bb589e3d29fe08f7c8e9a25669a346ad0bcaba39 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 17 Jun 2024 11:20:51 -0700 Subject: [PATCH 111/111] ci(dependencies): add pre-commit PRs to project --- .github/workflows/add-to-project-dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/add-to-project-dependabot.yml b/.github/workflows/add-to-project-dependabot.yml index 9ba3a8251..0f02b6cac 100644 --- a/.github/workflows/add-to-project-dependabot.yml +++ b/.github/workflows/add-to-project-dependabot.yml @@ -8,7 +8,7 @@ jobs: add-to-project-dependabot: runs-on: ubuntu-latest # see https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#responding-to-events - if: github.actor == 'dependabot[bot]' + if: github.actor == 'dependabot[bot]' || github.actor == 'pre-commit-ci[bot]' steps: - uses: actions/add-to-project@main with: