From bb3879f250cb4b9c70d9786e5c436f315e201e20 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Tue, 14 Jun 2022 21:23:40 +0000 Subject: [PATCH 001/106] feat(mobile): remove image from specific screens on mobile --- benefits/eligibility/views.py | 4 ++-- benefits/enrollment/views.py | 3 ++- benefits/static/css/styles.css | 6 +++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 0bf2a8fb36..71c8dbb51e 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -166,7 +166,7 @@ def confirm(request): content_title=_(verifier.form_content_title), paragraphs=[_(verifier.form_blurb)], form=forms.EligibilityVerificationForm(auto_id=True, label_suffix="", verifier=verifier), - classes="text-lg-center", + classes="text-lg-center no-image-mobile", ) # POST form submission, process form data @@ -236,7 +236,7 @@ def unverified(request): page = viewmodels.Page( title=_(verifier.unverified_title), - classes="with-agency-links", + classes="with-agency-links no-image-mobile", content_title=_(verifier.unverified_content_title), icon=viewmodels.Icon("idcardquestion", pgettext("image alt text", "core.icons.idcardquestion")), paragraphs=[_(verifier.unverified_blurb), _("eligibility.pages.unverified.p[1]")], diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 1a3bdf78a5..4870cc69e5 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -31,7 +31,7 @@ def _index(request): content_title=_("enrollment.pages.index.content_title"), icon=viewmodels.Icon("idcardcheck", pgettext("image alt text", "core.icons.idcardcheck")), paragraphs=[_("enrollment.pages.index.p[0]"), _("enrollment.pages.index.p[1]"), _("enrollment.pages.index.p[2]")], - classes="text-lg-center", + classes="text-lg-center no-image-mobile", forms=[tokenize_retry_form, tokenize_success_form], buttons=[ viewmodels.Button.primary( @@ -147,6 +147,7 @@ def success(request): verifier = session.verifier(request) icon = viewmodels.Icon("bankcardcheck", pgettext("image alt text", "core.icons.bankcardcheck")) page = viewmodels.Page( + classes="no-image-mobile", title=_("enrollment.pages.success.title"), content_title=_("enrollment.pages.success.content_title"), ) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 3e25f12e81..bc3ec6a4fe 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -447,7 +447,7 @@ footer.global-footer .footer-links a:active { /* Header */ .navbar.navbar-expand-sm.navbar-dark.bg-primary { - padding: 14.5px 1rem; + padding: 8.5px 1rem; } .navbar-brand { @@ -618,6 +618,10 @@ footer.global-footer .footer-links a:active { /* Mobile With Image */ + .no-image-mobile .col-lg-6.image { + display: none !important; + } + .with-image main .main-row .col-lg-6.image { background-position: center bottom; height: 240px; From 7999e8fde61a78efaa7d90264b4adf8a2e5ce08b Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Tue, 14 Jun 2022 21:31:55 +0000 Subject: [PATCH 002/106] fix(css): desktop image is taller than mobile image --- benefits/static/css/styles.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index bc3ec6a4fe..039b57e5b9 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -93,7 +93,7 @@ main .main-row .col-lg-6.image { .one-column-image { background: 48% 75% url("/static/img/ridertappingbankcard.png") no-repeat; background-size: cover; - height: 131px; + height: 292px; } footer { @@ -612,6 +612,10 @@ footer.global-footer .footer-links a:active { } @media (max-width: 992px) { + .one-column-image { + height: 131px; + } + .container.content .btn-lg { padding: 1.1875rem 0.813rem; } From 647587458a361787bc5b8e56a15da214f179cc47 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Tue, 14 Jun 2022 21:39:34 +0000 Subject: [PATCH 003/106] feat: add no-image-mobile to enrollment/retry, remove from eligibility --- benefits/eligibility/views.py | 4 ++-- benefits/enrollment/views.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 71c8dbb51e..0bf2a8fb36 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -166,7 +166,7 @@ def confirm(request): content_title=_(verifier.form_content_title), paragraphs=[_(verifier.form_blurb)], form=forms.EligibilityVerificationForm(auto_id=True, label_suffix="", verifier=verifier), - classes="text-lg-center no-image-mobile", + classes="text-lg-center", ) # POST form submission, process form data @@ -236,7 +236,7 @@ def unverified(request): page = viewmodels.Page( title=_(verifier.unverified_title), - classes="with-agency-links no-image-mobile", + classes="with-agency-links", content_title=_(verifier.unverified_content_title), icon=viewmodels.Icon("idcardquestion", pgettext("image alt text", "core.icons.idcardquestion")), paragraphs=[_(verifier.unverified_blurb), _("eligibility.pages.unverified.p[1]")], diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 4870cc69e5..c06c1c4377 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -124,6 +124,7 @@ def retry(request): if form.is_valid(): agency = session.agency(request) page = viewmodels.Page( + classes="no-image-mobile", title=_("enrollment.pages.retry.title"), icon=viewmodels.Icon("bankcardquestion", pgettext("image alt text", "core.icons.bankcardquestion")), content_title=_("enrollment.pages.retry.title"), From 39e9ca4cf7beb1b7d86dab01bcc01fe018ffdd18 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Wed, 15 Jun 2022 01:47:18 -0400 Subject: [PATCH 004/106] chore: reflect the sticky settings in Terraform --- terraform/app_service.tf | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 0593daaff0..10f81c71fe 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -10,7 +10,7 @@ resource "azurerm_service_plan" "main" { } } -# app_settings, sticky_settings, and storage_account are managed manually through the portal since they contain secrets +# app_settings and storage_account are managed manually through the portal since they contain secrets resource "azurerm_linux_web_app" "main" { name = "AS-CDT-PUB-VIP-CALITP-P-001" @@ -40,8 +40,32 @@ resource "azurerm_linux_web_app" "main" { } } + sticky_settings { + app_setting_names = [ + "APPINSIGHTS_INSTRUMENTATIONKEY", + "APPINSIGHTS_PROFILERFEATURE_VERSION", + "APPINSIGHTS_SNAPSHOTFEATURE_VERSION", + "APPLICATIONINSIGHTS_CONFIGURATION_CONTENT", + "APPLICATIONINSIGHTS_CONNECTION_STRING", + "ApplicationInsightsAgent_EXTENSION_VERSION", + "DJANGO_ALLOWED_HOSTS", + "DJANGO_OAUTH_AUTHORITY", + "DJANGO_OAUTH_CLIENT_ID", + "DJANGO_SECRET_KEY", + "DJANGO_TRUSTED_ORIGINS", + "DiagnosticServices_EXTENSION_VERSION", + "InstrumentationEngine_EXTENSION_VERSION", + "SnapshotDebugger_EXTENSION_VERSION", + "XDT_MicrosoftApplicationInsightsJava", + "XDT_MicrosoftApplicationInsights_BaseExtensions", + "XDT_MicrosoftApplicationInsights_Mode", + "XDT_MicrosoftApplicationInsights_NodeJS", + "XDT_MicrosoftApplicationInsights_PreemptSdk", + ] + } + lifecycle { - ignore_changes = [app_settings, sticky_settings, tags] + ignore_changes = [app_settings, tags] } } From a3df42e96d05023a62969abef71485fdcefdba32 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Wed, 15 Jun 2022 01:51:37 -0400 Subject: [PATCH 005/106] fix: disable the FTP service https://docs.microsoft.com/en-us/azure/app-service/deploy-ftp We aren't using this, so for better security, turn it off. --- terraform/app_service.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 0593daaff0..e00d17f305 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -20,7 +20,7 @@ resource "azurerm_linux_web_app" "main" { https_only = true site_config { - ftps_state = "AllAllowed" + ftps_state = "Disabled" } identity { @@ -51,7 +51,7 @@ resource "azurerm_linux_web_app_slot" "dev" { app_service_id = azurerm_linux_web_app.main.id site_config { - ftps_state = "AllAllowed" + ftps_state = "Disabled" vnet_route_all_enabled = true } @@ -88,7 +88,7 @@ resource "azurerm_linux_web_app_slot" "test" { app_service_id = azurerm_linux_web_app.main.id site_config { - ftps_state = "AllAllowed" + ftps_state = "Disabled" vnet_route_all_enabled = true } From c639af9ee16381c8d8ead505aeee5b4cd2842d32 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Wed, 15 Jun 2022 16:42:24 -0400 Subject: [PATCH 006/106] chore: tweak list of DJANGO_* environment variables that are sticky --- terraform/app_service.tf | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 10f81c71fe..32d8c22152 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -48,11 +48,9 @@ resource "azurerm_linux_web_app" "main" { "APPLICATIONINSIGHTS_CONFIGURATION_CONTENT", "APPLICATIONINSIGHTS_CONNECTION_STRING", "ApplicationInsightsAgent_EXTENSION_VERSION", - "DJANGO_ALLOWED_HOSTS", - "DJANGO_OAUTH_AUTHORITY", + "DJANGO_INIT_PATH", "DJANGO_OAUTH_CLIENT_ID", - "DJANGO_SECRET_KEY", - "DJANGO_TRUSTED_ORIGINS", + "DJANGO_OAUTH_CLIENT_NAME", "DiagnosticServices_EXTENSION_VERSION", "InstrumentationEngine_EXTENSION_VERSION", "SnapshotDebugger_EXTENSION_VERSION", From e44163a0da8b9f25821f026344c062fca6d12458 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 15 Jun 2022 22:06:06 +0000 Subject: [PATCH 007/106] fix(footer): new footer link color is 73B3E7 --- benefits/static/css/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 026d68d736..10fe13dd0c 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -111,7 +111,7 @@ footer { footer.global-footer .footer-links a { font-size: 16px; - color: #0590cd; + color: #73b3e7; } footer.global-footer .footer-links a:hover, From bf944138900f38d69d5902c2de6c0ce5a55dceda Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 15 Jun 2022 22:09:42 +0000 Subject: [PATCH 008/106] fix: add info_link to elig start page --- benefits/eligibility/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index bd2075deab..2ca2df7444 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -158,6 +158,7 @@ def start(request): ctx = page.context_dict() ctx["title"] = _(verifier.start_content_title) ctx["media"] = media + ctx["info_link"] = f"{reverse(ROUTE_HELP)}#about" return TemplateResponse(request, TEMPLATE_START, ctx) From af94df08ca940b4c435465f6b970fd4a1687600e Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 15 Jun 2022 22:20:49 +0000 Subject: [PATCH 009/106] fix(mobile): on mobile, hide the first info link --- benefits/locale/en/LC_MESSAGES/django.po | 2 +- benefits/locale/es/LC_MESSAGES/django.po | 2 +- benefits/static/css/styles.css | 18 ++---------------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 82102fe2da..0cdf9f2497 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -269,7 +269,7 @@ msgstr "" msgid "core.pages.agency_index.p[0]%(info_link)s" msgstr "You can tap your credit or debit card when you board, " "and your discount will automatically apply every time you ride." -" Learn more about Cal-ITP Benefits." +" Learn more about Cal-ITP Benefits." msgid "core.pages.agency_index.p[1]" msgstr "You will need your California driver’s license or ID " diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 876d2e1ef3..c5e5595667 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -254,7 +254,7 @@ msgstr "" msgid "core.pages.agency_index.p[0]%(info_link)s" -msgstr "Puede acercar su tarjeta de crédito o débito cuando aborde, y su descuento se aplicará automáticamente cada vez que viaje. Conozca más sobre beneficios de Cal-ITP." +msgstr "Puede acercar su tarjeta de crédito o débito cuando aborde, y su descuento se aplicará automáticamente cada vez que viaje. Conozca más sobre beneficios de Cal-ITP." msgid "core.pages.agency_index.p[1]" msgstr "Necesitará su licencia de conducir o tarjeta de identificación de California y su tarjeta sin contacto emitida por el banco para comenzar." diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 026d68d736..39463d3a89 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -704,22 +704,8 @@ footer.global-footer .footer-links a:active { float: right; } - .eligibility-start .main-content .container strong a { - display: block; - line-height: 15px; - letter-spacing: 0.05em; - padding: 6px; - text-align: left; - width: 100%; - margin: 18px 0; - font-size: 18px; - text-decoration: none !important; - letter-spacing: 0.2px; - border: 2px solid #046b99; - border-radius: 0.3rem; - font-weight: 500; - width: 212px; - line-height: 22.5px; + .eligibility-start .main-content .container strong .info-link { + display: none; } .media-list .media .media-body .media-body--links .btn-lg { From 8cd54df7c77bb931ad3dee5b82e956d24fab12f4 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 15 Jun 2022 22:45:55 +0000 Subject: [PATCH 010/106] fix(link): make link width only up to content --- benefits/static/css/styles.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 39463d3a89..9065f5c184 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -338,8 +338,9 @@ footer.global-footer .footer-links a:active { letter-spacing: 0.05em; padding: 0 0 6px 0; text-align: left; - width: 100%; margin: 0; + display: block; + width: fit-content; } .media-list .media .media-body .media-body--links .btn-lg:hover { From 8987d1e9765ad6e5b5e30e55985f7f1a5d18d018 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 00:47:21 +0000 Subject: [PATCH 011/106] refactor(oauth): client.instance() returns a new instance in anticipation of dynamic settings per-instance --- benefits/oauth/client.py | 27 +++++++++++---------------- tests/pytest/oauth/test_client.py | 12 ++++-------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/benefits/oauth/client.py b/benefits/oauth/client.py index c938a7833a..a804b1d8f3 100644 --- a/benefits/oauth/client.py +++ b/benefits/oauth/client.py @@ -5,25 +5,20 @@ from authlib.integrations.django_client import OAuth -_OAUTH_CLIENT = None - - logger = logging.getLogger(__name__) def instance(): """ - Get the OAuth client instance using the OAUTH_CLIENT_NAME setting. + Get an OAuth client instance using the OAUTH_CLIENT_NAME setting. """ - global _OAUTH_CLIENT - if not _OAUTH_CLIENT: - if settings.OAUTH_CLIENT_NAME: - logger.debug(f"Using OAuth client configuration: {settings.OAUTH_CLIENT_NAME}") - - _oauth = OAuth() - _oauth.register(settings.OAUTH_CLIENT_NAME) - _OAUTH_CLIENT = _oauth.create_client(settings.OAUTH_CLIENT_NAME) - else: - raise Exception("OAUTH_CLIENT_NAME is not configured") - - return _OAUTH_CLIENT + if settings.OAUTH_CLIENT_NAME: + logger.debug(f"Using OAuth client configuration: {settings.OAUTH_CLIENT_NAME}") + + oauth = OAuth() + oauth.register(settings.OAUTH_CLIENT_NAME) + client = oauth.create_client(settings.OAUTH_CLIENT_NAME) + else: + raise Exception("OAUTH_CLIENT_NAME is not configured") + + return client diff --git a/tests/pytest/oauth/test_client.py b/tests/pytest/oauth/test_client.py index 9df5167cce..c5e6b740c5 100644 --- a/tests/pytest/oauth/test_client.py +++ b/tests/pytest/oauth/test_client.py @@ -16,14 +16,10 @@ def test_instance_no_oauth_client_name(): @pytest.mark.django_db -def test_instance_oauth_client_name(): - assert not client._OAUTH_CLIENT - +def test_instance(): oauth_client = client.instance() - - assert oauth_client - assert client._OAUTH_CLIENT is oauth_client - oauth_client2 = client.instance() - assert oauth_client is oauth_client2 + assert oauth_client + assert oauth_client2 + assert oauth_client is not oauth_client2 From 12dffc0f1252dfc47a1dbe701580a56609ec9a89 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 00:50:41 +0000 Subject: [PATCH 012/106] feat(oauth): instance accepts scope parameter always send the 'openid' scope, allow for additional scopes via param remove hardcoded setting now handled here --- benefits/oauth/client.py | 14 +++++++++++++- benefits/settings.py | 2 +- tests/pytest/oauth/test_client.py | 27 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/benefits/oauth/client.py b/benefits/oauth/client.py index a804b1d8f3..f3d4fb3407 100644 --- a/benefits/oauth/client.py +++ b/benefits/oauth/client.py @@ -8,9 +8,14 @@ logger = logging.getLogger(__name__) -def instance(): +_OPENID_SCOPE = "openid" + + +def instance(scope=None): """ Get an OAuth client instance using the OAUTH_CLIENT_NAME setting. + + Optionally configure with a (space-separated) scope. """ if settings.OAUTH_CLIENT_NAME: logger.debug(f"Using OAuth client configuration: {settings.OAUTH_CLIENT_NAME}") @@ -21,4 +26,11 @@ def instance(): else: raise Exception("OAUTH_CLIENT_NAME is not configured") + scopes = [_OPENID_SCOPE] + + if scope: + scopes.append(scope) + + client.client_kwargs["scope"] = " ".join(scopes) + return client diff --git a/benefits/settings.py b/benefits/settings.py index 8a1ff77725..b11948e282 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -177,7 +177,7 @@ def _filter_empty(ls): OAUTH_CLIENT_NAME: { "client_id": OAUTH_CLIENT_ID, "server_metadata_url": f"{OAUTH_AUTHORITY}/.well-known/openid-configuration", - "client_kwargs": {"code_challenge_method": "S256", "scope": "openid"}, + "client_kwargs": {"code_challenge_method": "S256"}, } } diff --git a/tests/pytest/oauth/test_client.py b/tests/pytest/oauth/test_client.py index c5e6b740c5..03b805e0b1 100644 --- a/tests/pytest/oauth/test_client.py +++ b/tests/pytest/oauth/test_client.py @@ -23,3 +23,30 @@ def test_instance(): assert oauth_client assert oauth_client2 assert oauth_client is not oauth_client2 + + +@pytest.mark.django_db +def test_instance_scope(): + scope1 = "scope1" + oauth_client = client.instance(scope1) + client_scope = oauth_client.client_kwargs["scope"] + + assert scope1 in client_scope + assert client._OPENID_SCOPE in client_scope + + scope2 = "scope2" + oauth_client2 = client.instance(scope2) + client_scope = oauth_client2.client_kwargs["scope"] + + assert scope1 not in client_scope + assert scope2 in client_scope + assert client._OPENID_SCOPE in client_scope + + scope3 = " ".join((scope1, scope2)) + oauth_client3 = client.instance(scope3) + client_scope = oauth_client3.client_kwargs["scope"] + + assert scope1 in client_scope + assert scope2 in client_scope + assert scope3 in client_scope + assert client._OPENID_SCOPE in client_scope From 4a08963d3eb56419b5ae5902060411ead240830e Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 01:43:49 +0000 Subject: [PATCH 013/106] refactor(test): make the mocked instance method a fixture so we can spy on it directly --- tests/pytest/oauth/conftest.py | 9 ++++++--- tests/pytest/oauth/test_redirects.py | 3 ++- tests/pytest/oauth/test_views.py | 9 ++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/pytest/oauth/conftest.py b/tests/pytest/oauth/conftest.py index 4190f0dd47..beefe93fcd 100644 --- a/tests/pytest/oauth/conftest.py +++ b/tests/pytest/oauth/conftest.py @@ -5,6 +5,9 @@ @pytest.fixture def mocked_oauth_client(mocker): - mock_client = mocker.Mock(spec=DjangoOAuth2App) - mocker.patch("benefits.oauth.client.instance", return_value=mock_client) - return mock_client + return mocker.Mock(spec=DjangoOAuth2App) + + +@pytest.fixture +def mocked_oauth_client_instance(mocker, mocked_oauth_client): + return mocker.patch("benefits.oauth.client.instance", return_value=mocked_oauth_client) diff --git a/tests/pytest/oauth/test_redirects.py b/tests/pytest/oauth/test_redirects.py index f4c3443a8c..d35b2737ab 100644 --- a/tests/pytest/oauth/test_redirects.py +++ b/tests/pytest/oauth/test_redirects.py @@ -1,7 +1,8 @@ from benefits.oauth.redirects import deauthorize_redirect, generate_redirect_uri -def test_deauthorize_redirect(mocked_oauth_client): +def test_deauthorize_redirect(mocked_oauth_client_instance): + mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.load_server_metadata.return_value = {"end_session_endpoint": "https://server/endsession"} result = deauthorize_redirect("token", "https://localhost/redirect_uri") diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index ee4db0ad21..92d54efc2d 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -15,9 +15,10 @@ def mocked_analytics_module(mocked_analytics_module): return mocked_analytics_module(benefits.oauth.views) -def test_login(mocked_oauth_client, mocked_analytics_module, app_request): +def test_login(mocked_oauth_client_instance, mocked_analytics_module, app_request): assert not session.logged_in(app_request) + mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_redirect.return_value = HttpResponse("authorize redirect") login(app_request) @@ -27,7 +28,8 @@ def test_login(mocked_oauth_client, mocked_analytics_module, app_request): assert not session.logged_in(app_request) -def test_authorize_fail(mocked_oauth_client, app_request): +def test_authorize_fail(mocked_oauth_client_instance, app_request): + mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_access_token.return_value = None assert not session.logged_in(app_request) @@ -40,7 +42,8 @@ def test_authorize_fail(mocked_oauth_client, app_request): assert result.url == reverse(ROUTE_START) -def test_authorize_success(mocked_oauth_client, mocked_analytics_module, app_request): +def test_authorize_success(mocked_oauth_client_instance, mocked_analytics_module, app_request): + mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token"} result = authorize(app_request) From fdefea8d0cb694a69f260c70e382753540e7eedf Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 01:46:22 +0000 Subject: [PATCH 014/106] feat(oauth): config client with verifier's auth_scope --- benefits/oauth/views.py | 10 +++++++--- tests/pytest/oauth/test_views.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index 15e37e7e26..1e19b70dbe 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -2,8 +2,10 @@ from django.shortcuts import redirect from django.urls import reverse +from django.utils.decorators import decorator_from_middleware from benefits.core import session +from benefits.core.middleware import VerifierSessionRequired from . import analytics, client, redirects @@ -16,17 +18,19 @@ ROUTE_POST_LOGOUT = "oauth:post_logout" +@decorator_from_middleware(VerifierSessionRequired) def login(request): """View implementing OIDC authorize_redirect.""" - oauth_client = client.instance() - - analytics.started_sign_in(request) + verifier = session.verifier(request) + oauth_client = client.instance(verifier.auth_scope) route = reverse(ROUTE_AUTH) redirect_uri = redirects.generate_redirect_uri(request, route) logger.debug(f"OAuth authorize_redirect with redirect_uri: {redirect_uri}") + analytics.started_sign_in(request) + return oauth_client.authorize_redirect(request, redirect_uri) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 92d54efc2d..2901068560 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -15,6 +15,8 @@ def mocked_analytics_module(mocked_analytics_module): return mocked_analytics_module(benefits.oauth.views) +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_verifier_auth_required") def test_login(mocked_oauth_client_instance, mocked_analytics_module, app_request): assert not session.logged_in(app_request) @@ -28,6 +30,19 @@ def test_login(mocked_oauth_client_instance, mocked_analytics_module, app_reques assert not session.logged_in(app_request) +@pytest.mark.django_db +def test_login_scope(mocked_oauth_client_instance, mocked_session_verifier_auth_required, app_request): + mocked_oauth_client = mocked_oauth_client_instance.return_value + mocked_oauth_client.authorize_redirect.return_value = HttpResponse("authorize redirect") + + mocked_verifier = mocked_session_verifier_auth_required.return_value + mocked_verifier.auth_scope = "scope" + + login(app_request) + + mocked_oauth_client_instance.assert_called_once_with(mocked_verifier.auth_scope) + + def test_authorize_fail(mocked_oauth_client_instance, app_request): mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_access_token.return_value = None From 5623bcc7fbd096bb63f4c009b9878a3492826adb Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 02:07:47 +0000 Subject: [PATCH 015/106] feat(ci): label front-end PRs --- .github/workflows/labeler-front-end.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/labeler-front-end.yml diff --git a/.github/workflows/labeler-front-end.yml b/.github/workflows/labeler-front-end.yml new file mode 100644 index 0000000000..b60164d998 --- /dev/null +++ b/.github/workflows/labeler-front-end.yml @@ -0,0 +1,17 @@ +name: Label front-end + +on: + pull_request: + types: [opened] + paths: + - 'benefits/**/templates/**' + - 'benefits/static/**' + +jobs: + label-actions: + runs-on: ubuntu-latest + steps: + - name: add-label + uses: andymckay/labeler@master + with: + add-labels: "front-end" From 36edb1e527e4e2ad262c980cc7b7572c701ba52a Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 02:09:24 +0000 Subject: [PATCH 016/106] feat(ci): label back-end PRs --- .github/workflows/labeler-back-end.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/labeler-back-end.yml diff --git a/.github/workflows/labeler-back-end.yml b/.github/workflows/labeler-back-end.yml new file mode 100644 index 0000000000..66d4658be0 --- /dev/null +++ b/.github/workflows/labeler-back-end.yml @@ -0,0 +1,16 @@ +name: Label back-end + +on: + pull_request: + types: [opened] + paths: + - 'benefits/**/*.py' + +jobs: + label-actions: + runs-on: ubuntu-latest + steps: + - name: add-label + uses: andymckay/labeler@master + with: + add-labels: "back-end" From 439cd200205de3ba413ff7c552fe271cdaab0794 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 02:11:24 +0000 Subject: [PATCH 017/106] feat(ci): label docker PRs --- .github/workflows/labeler-docker.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/labeler-docker.yml diff --git a/.github/workflows/labeler-docker.yml b/.github/workflows/labeler-docker.yml new file mode 100644 index 0000000000..1a837bcde0 --- /dev/null +++ b/.github/workflows/labeler-docker.yml @@ -0,0 +1,18 @@ +name: Label docker + +on: + pull_request: + types: [opened] + paths: + - '.devcontainer/**' + - '.dockerignore' + - 'Dockerfile' + +jobs: + label-actions: + runs-on: ubuntu-latest + steps: + - name: add-label + uses: andymckay/labeler@master + with: + add-labels: "docker" From 18261e281f58660e16270ef83ead4189fd17b902 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 02:14:21 +0000 Subject: [PATCH 018/106] feat(ci): label i18n PRs --- .github/workflows/labeler-i18n.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/labeler-i18n.yml diff --git a/.github/workflows/labeler-i18n.yml b/.github/workflows/labeler-i18n.yml new file mode 100644 index 0000000000..99586a290b --- /dev/null +++ b/.github/workflows/labeler-i18n.yml @@ -0,0 +1,16 @@ +name: Label i18n + +on: + pull_request: + types: [opened] + paths: + - 'benefits/locale/**' + +jobs: + label-actions: + runs-on: ubuntu-latest + steps: + - name: add-label + uses: andymckay/labeler@master + with: + add-labels: "i18n" From 927fb2fc17f20d692ee748c51a8c4f5487efe02f Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 09:26:27 -0700 Subject: [PATCH 019/106] docs: improve readme description for anyone landing on the repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6466086cf2..ec0dd1f002 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Benefits -Transit benefits enrollment, minus the paperwork. +Cal-ITP Benefits is an application that enables automated eligibility verification and enrollment for transit benefits onto customers’ existing contactless bank (credit/debit) cards. View the technical documentation online: From fbcadc870c6ff731ec4c191fda116042984c8d2a Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 16 Jun 2022 18:44:43 +0000 Subject: [PATCH 020/106] fix: signout button on enrollment page --- benefits/static/css/styles.css | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 1223f4fa40..774a00d998 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -67,7 +67,6 @@ label { .main-content .main-primary a:not(.btn) { background: none; - text-decoration: underline !important; } /* making the sticky footer */ @@ -658,6 +657,14 @@ footer.global-footer .footer-links a:active { top: 308px; } + .no-image-mobile .signout-row { + top: 68px; + } + + .no-image-mobile .main-primary .container-fluid { + margin-top: 72px; + } + /* Eligibility Start */ .eligibility-start h1.headline { From 070c91074258f6bdef8a1e6417a523d4b9ad3c7b Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 Jun 2022 17:14:11 +0000 Subject: [PATCH 021/106] feat: add session support for storing and accessing oauth claims --- benefits/core/session.py | 13 +++++++++++++ tests/pytest/core/test_session.py | 2 ++ 2 files changed, 15 insertions(+) diff --git a/benefits/core/session.py b/benefits/core/session.py index ff8a0a30ce..7cb20185df 100644 --- a/benefits/core/session.py +++ b/benefits/core/session.py @@ -24,6 +24,7 @@ _LANG = "lang" _LIMITCOUNTER = "limitcounter" _LIMITUNTIL = "limituntil" +_OAUTH_CLAIM = "oauth_claim" _OAUTH_TOKEN = "oauth_token" _ORIGIN = "origin" _START = "start" @@ -61,6 +62,7 @@ def context_dict(request): _ENROLLMENT_TOKEN_EXP: enrollment_token_expiry(request), _LANG: language(request), _OAUTH_TOKEN: oauth_token(request), + _OAUTH_CLAIM: oauth_claim(request), _ORIGIN: origin(request), _LIMITUNTIL: rate_limit_time(request), _START: start(request), @@ -160,6 +162,12 @@ def oauth_token(request): return request.session.get(_OAUTH_TOKEN) +def oauth_claim(request): + """Get the oauth claim from the request's session, or None""" + logger.debug("Get session oauth claim") + return request.session.get(_OAUTH_CLAIM) + + def origin(request): """Get the origin for the request's session, or None.""" logger.debug("Get session origin") @@ -202,6 +210,7 @@ def reset(request): request.session[_ENROLLMENT_TOKEN] = None request.session[_ENROLLMENT_TOKEN_EXP] = None request.session[_OAUTH_TOKEN] = None + request.session[_OAUTH_CLAIM] = None request.session[_VERIFIER] = None if _UID not in request.session or not request.session[_UID]: @@ -262,6 +271,7 @@ def update( enrollment_token=None, enrollment_token_exp=None, oauth_token=None, + oauth_claim=None, origin=None, verifier=None, ): @@ -291,6 +301,9 @@ def update( if oauth_token is not None: logger.debug(f"Update session {_OAUTH_TOKEN}") request.session[_OAUTH_TOKEN] = oauth_token + if oauth_claim is not None: + logger.debug(f"Update session {_OAUTH_CLAIM}") + request.session[_OAUTH_CLAIM] = oauth_claim if origin is not None: logger.debug(f"Update session {_ORIGIN}") request.session[_ORIGIN] = origin diff --git a/tests/pytest/core/test_session.py b/tests/pytest/core/test_session.py index d45be4e51e..d312a06b03 100644 --- a/tests/pytest/core/test_session.py +++ b/tests/pytest/core/test_session.py @@ -241,10 +241,12 @@ def test_reset_enrollment(app_request): @pytest.mark.django_db def test_reset_oauth(app_request): app_request.session[session._OAUTH_TOKEN] = "oauthtoken456" + app_request.session[session._OAUTH_CLAIM] = "claim" session.reset(app_request) assert session.oauth_token(app_request) is None + assert session.oauth_claim(app_request) is None @pytest.mark.django_db From c268841d3bf1513c1dea073ea3c0ebb9c16d8a6f Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 Jun 2022 17:40:39 +0000 Subject: [PATCH 022/106] feat: add logic to 'authorize' view function to store claim in session --- benefits/oauth/views.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index 1e19b70dbe..5d48c2fa81 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -47,9 +47,16 @@ def authorize(request): logger.debug("OAuth access token authorized") - # we store the id_token in the user's session - # this is the minimal amount of information needed later to log the user out - session.update(request, oauth_token=token["id_token"]) + # We store the id_token in the user's session. This is the minimal amount of information needed later to log the user out. + id_token = token["id_token"] + # We store the returned claim in case it can be used later in eligibility verification. + verifier = session.verifier(request) + if verifier.auth_claim: + userinfo = token.get("userinfo") + claim_value = token.get("userinfo").get(verifier.auth_claim) if userinfo else None + claim = verifier.auth_claim if claim_value else None + + session.update(request, oauth_token=id_token, oauth_claim=claim) analytics.finished_sign_in(request) From 2d3c86c47bdef465e4195c3c45c9c8b5a9c0c9ea Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 Jun 2022 17:53:37 +0000 Subject: [PATCH 023/106] feat: add logic directly to eligibility view to check for auth claims this commit is to show the basic logic working (assuming you have the fixtures set up the right way). a later commit will refactor this logic into the EligibilityVerifier model. --- benefits/eligibility/views.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 2ca2df7444..0081d857c7 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -214,8 +214,15 @@ def confirm(request): eligibility = session.eligibility(request) return verified(request, [eligibility.name]) - # GET from an unverified user, present the form + # GET from an unverified user, see if verifier can get verified types and if not, present the form else: + # TODO: move this logic into EligibilityVerifier model + if verifier.requires_authentication: + oauth_claim = session.oauth_claim(request) + if oauth_claim and verifier.auth_claim == oauth_claim: + # TODO: refactor verifier to hold single eligibility_type + verified_types = list(map(lambda t: t.name, verifier.eligibility_types.all())) + return verified(request, verified_types) return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) From 069eab52143bd4bcfc4d9d76819e2914094f0718 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 Jun 2022 18:58:15 +0000 Subject: [PATCH 024/106] refactor(tests): update test_authorize_success to mock verifier the verifier is needed now in this code path since we'll try see if there's an auth_claim to keep track of. --- tests/pytest/oauth/test_views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 2901068560..18d1c26d45 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -57,6 +57,8 @@ def test_authorize_fail(mocked_oauth_client_instance, app_request): assert result.url == reverse(ROUTE_START) +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_verifier_auth_required") def test_authorize_success(mocked_oauth_client_instance, mocked_analytics_module, app_request): mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token"} From 11202f45681a87fc27e2b60447e2b31eaa9c7e78 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 14 Jun 2022 21:08:15 +0000 Subject: [PATCH 025/106] refactor: move api-based verification logic to EligibilityVerifier update tests to mock the logic that's been moved. --- benefits/core/models.py | 36 ++++++++++++++++++++ benefits/eligibility/api.py | 47 -------------------------- benefits/eligibility/views.py | 4 +-- tests/pytest/eligibility/test_api.py | 31 +++++++++++------ tests/pytest/eligibility/test_views.py | 6 ++-- 5 files changed, 61 insertions(+), 63 deletions(-) delete mode 100644 benefits/eligibility/api.py diff --git a/benefits/core/models.py b/benefits/core/models.py index a397dbf2c1..bd66574e1b 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -3,8 +3,10 @@ """ import logging +from django.conf import settings from django.db import models from django.urls import reverse +from eligibility_api.client import Client logger = logging.getLogger(__name__) @@ -108,6 +110,40 @@ def by_id(id): logger.debug(f"Get {EligibilityVerifier.__name__} by id: {id}") return EligibilityVerifier.objects.get(id=id) + def get_verified_types(self, form=None, agency=None): + if form is not None and agency is not None: + return self._get_api_verified_types(form, agency) + else: + return [] + + def _get_api_verified_types(self, form, agency): + sub, name = form.cleaned_data.get("sub"), form.cleaned_data.get("name") + + client = Client( + verify_url=self.api_url, + headers={self.api_auth_header: self.api_auth_key}, + issuer=settings.ALLOWED_HOSTS[0], + agency=agency.agency_id, + jws_signing_alg=agency.jws_signing_alg, + client_private_key=agency.private_key_data, + jwe_encryption_alg=self.jwe_encryption_alg, + jwe_cek_enc=self.jwe_cek_enc, + server_public_key=self.public_key_data, + ) + + # get the eligibility type names to check + types = list(map(lambda t: t.name, agency.types_to_verify(self))) + + response = client.verify(sub, name, types) + + if response.error and any(response.error): + form.add_api_errors(response.error) + return None + elif any(response.eligibility): + return list(response.eligibility) + else: + return [] + class PaymentProcessor(models.Model): """An entity that processes payments for transit agencies.""" diff --git a/benefits/eligibility/api.py b/benefits/eligibility/api.py deleted file mode 100644 index f5c0f1f095..0000000000 --- a/benefits/eligibility/api.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -The eligibility application: Eligibility Verification API helpers. -""" - -from django.conf import settings - -from eligibility_api.client import Client - -from benefits.core import session - - -def get_verified_types(request, form): - """ - Helper calls the eligibility verification API with user input. - Returns None and updates form with user input error(s). - Returns a list of verified eligibility types, or an empty list when no types were verified. - """ - - sub, name = form.cleaned_data.get("sub"), form.cleaned_data.get("name") - - agency = session.agency(request) - verifier = session.verifier(request) - - client = Client( - verify_url=verifier.api_url, - headers={verifier.api_auth_header: verifier.api_auth_key}, - issuer=settings.ALLOWED_HOSTS[0], - agency=agency.agency_id, - jws_signing_alg=agency.jws_signing_alg, - client_private_key=agency.private_key_data, - jwe_encryption_alg=verifier.jwe_encryption_alg, - jwe_cek_enc=verifier.jwe_cek_enc, - server_public_key=verifier.public_key_data, - ) - - # get the eligibility type names to check - types = list(map(lambda t: t.name, agency.types_to_verify(verifier))) - - response = client.verify(sub, name, types) - - if response.error and any(response.error): - form.add_api_errors(response.error) - return None - elif any(response.eligibility): - return list(response.eligibility) - else: - return [] diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 0081d857c7..5ddde9e41a 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -13,7 +13,7 @@ from benefits.core.middleware import AgencySessionRequired, LoginRequired, RateLimit, VerifierSessionRequired from benefits.core.models import EligibilityVerifier from benefits.core.views import ROUTE_HELP, TEMPLATE_PAGE -from . import analytics, api, forms +from . import analytics, forms ROUTE_INDEX = "eligibility:index" @@ -195,7 +195,7 @@ def confirm(request): return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) # form is valid, make Eligibility Verification request to get the verified types - verified_types = api.get_verified_types(request, form) + verified_types = verifier.get_verified_types(form=form, agency=session.agency(request)) # form was not valid, allow for correction/resubmission if verified_types is None: diff --git a/tests/pytest/eligibility/test_api.py b/tests/pytest/eligibility/test_api.py index 8fb5a31acb..6fc776e7c8 100644 --- a/tests/pytest/eligibility/test_api.py +++ b/tests/pytest/eligibility/test_api.py @@ -1,6 +1,5 @@ import pytest -from benefits.eligibility.api import get_verified_types from benefits.eligibility.forms import EligibilityVerificationForm @@ -11,43 +10,53 @@ def form(mocker): @pytest.fixture def mock_api_client_verify(mocker): - return mocker.patch("benefits.eligibility.api.Client.verify") + return mocker.patch("benefits.core.models.Client.verify") @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_eligibility_request_session") -def test_get_verified_types_error(mocker, app_request, mock_api_client_verify, form): +def test_get_verified_types_error(mocker, mocked_session_agency, mocked_session_verifier, mock_api_client_verify, form): api_errors = {"name": "Name error"} api_response = mocker.Mock(error=api_errors) mock_api_client_verify.return_value = api_response - response = get_verified_types(app_request, form) + agency = mocked_session_agency(None) + verifier = mocked_session_verifier(None) + + response = verifier.get_verified_types(form, agency) assert response is None form.add_api_errors.assert_called_once_with(api_errors) @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_eligibility_request_session") -def test_get_verified_types_verified_types(mocker, app_request, mock_api_client_verify, form): +def test_get_verified_types_verified_types( + mocker, mocked_session_agency, mocked_session_verifier, mock_api_client_verify, form +): verified_types = ["type1", "type2"] api_response = mocker.Mock(eligibility=verified_types, error=None) mock_api_client_verify.return_value = api_response - response = get_verified_types(app_request, form) + agency = mocked_session_agency(None) + verifier = mocked_session_verifier(None) + + response = verifier.get_verified_types(form, agency) assert response == verified_types form.add_api_errors.assert_not_called() @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_eligibility_request_session") -def test_get_verified_types_no_verified_types(mocker, app_request, mock_api_client_verify, form): +def test_get_verified_types_no_verified_types( + mocker, mocked_session_agency, mocked_session_verifier, mock_api_client_verify, form +): verified_types = [] api_response = mocker.Mock(eligibility=verified_types, error=None) mock_api_client_verify.return_value = api_response - response = get_verified_types(app_request, form) + agency = mocked_session_agency(None) + verifier = mocked_session_verifier(None) + + response = verifier.get_verified_types(form, agency) assert response == verified_types form.add_api_errors.assert_not_called() diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index e38999e5e6..c86d9ed46c 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -230,7 +230,7 @@ def test_confirm_post_recaptcha_fail(mocker, client, invalid_form_data): @pytest.mark.django_db @pytest.mark.usefixtures("mocked_eligibility_auth_request") def test_confirm_post_valid_form_eligibility_error(mocker, client, form_data, mocked_analytics_module): - mocker.patch("benefits.eligibility.views.api.get_verified_types", return_value=None) + mocker.patch("benefits.core.models.EligibilityVerifier.get_verified_types", return_value=None) path = reverse(ROUTE_CONFIRM) response = client.post(path, form_data) @@ -243,7 +243,7 @@ def test_confirm_post_valid_form_eligibility_error(mocker, client, form_data, mo @pytest.mark.django_db @pytest.mark.usefixtures("mocked_eligibility_auth_request") def test_confirm_post_valid_form_eligibility_unverified(mocker, client, form_data, mocked_analytics_module): - mocker.patch("benefits.eligibility.views.api.get_verified_types", return_value=[]) + mocker.patch("benefits.core.models.EligibilityVerifier.get_verified_types", return_value=[]) path = reverse(ROUTE_CONFIRM) response = client.post(path, form_data) @@ -261,7 +261,7 @@ def test_confirm_post_valid_form_eligibility_verified( # mocked_session_eligibility is a fixture that mocks benefits.core.session.eligibility(request) # call it here, passing a None request, to get the return value from the mock eligibility = mocked_session_eligibility(None) - mocker.patch("benefits.eligibility.views.api.get_verified_types", return_value=[eligibility]) + mocker.patch("benefits.core.models.EligibilityVerifier.get_verified_types", return_value=[eligibility]) path = reverse(ROUTE_CONFIRM) response = client.post(path, form_data) From c22c17baf8851852ed4bfde817f3f47f87f87841 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 15 Jun 2022 17:46:32 +0000 Subject: [PATCH 026/106] refactor: move claims-based verification from eligibility view to model the code was simplified a bit but is functionally equivalent. --- benefits/core/models.py | 5 ++++- benefits/eligibility/views.py | 13 +++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/benefits/core/models.py b/benefits/core/models.py index bd66574e1b..a0bac99aa5 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -110,9 +110,12 @@ def by_id(id): logger.debug(f"Get {EligibilityVerifier.__name__} by id: {id}") return EligibilityVerifier.objects.get(id=id) - def get_verified_types(self, form=None, agency=None): + def get_verified_types(self, form=None, agency=None, oauth_claim=None): if form is not None and agency is not None: return self._get_api_verified_types(form, agency) + elif oauth_claim is not None and self.requires_authentication and self.auth_claim == oauth_claim: + # TODO: refactor verifier to hold single eligibility_type + return list(map(lambda t: t.name, self.eligibility_types.all())) else: return [] diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 5ddde9e41a..88796eae52 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -216,14 +216,11 @@ def confirm(request): # GET from an unverified user, see if verifier can get verified types and if not, present the form else: - # TODO: move this logic into EligibilityVerifier model - if verifier.requires_authentication: - oauth_claim = session.oauth_claim(request) - if oauth_claim and verifier.auth_claim == oauth_claim: - # TODO: refactor verifier to hold single eligibility_type - verified_types = list(map(lambda t: t.name, verifier.eligibility_types.all())) - return verified(request, verified_types) - return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) + verified_types = verifier.get_verified_types(session.oauth_claim(request)) + if verified_types: + return verified(request, verified_types) + else: + return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) @decorator_from_middleware(AgencySessionRequired) From 137e5ba5ba160aa825ec4e0ce602431c606beaec Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 15 Jun 2022 21:33:53 +0000 Subject: [PATCH 027/106] refactor: move and rename test since it's testing model logic --- tests/pytest/{eligibility/test_api.py => core/test_models.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/pytest/{eligibility/test_api.py => core/test_models.py} (100%) diff --git a/tests/pytest/eligibility/test_api.py b/tests/pytest/core/test_models.py similarity index 100% rename from tests/pytest/eligibility/test_api.py rename to tests/pytest/core/test_models.py From 314abacfe68a9b3ae17e26847d492aa0af788eb2 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 15 Jun 2022 21:39:11 +0000 Subject: [PATCH 028/106] refactor(tests): use real fixture objects instead of mocked session --- tests/pytest/core/test_models.py | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py index 6fc776e7c8..fdf64b7b6c 100644 --- a/tests/pytest/core/test_models.py +++ b/tests/pytest/core/test_models.py @@ -14,49 +14,36 @@ def mock_api_client_verify(mocker): @pytest.mark.django_db -def test_get_verified_types_error(mocker, mocked_session_agency, mocked_session_verifier, mock_api_client_verify, form): +def test_get_verified_types_error(mocker, first_agency, first_verifier, mock_api_client_verify, form): api_errors = {"name": "Name error"} api_response = mocker.Mock(error=api_errors) mock_api_client_verify.return_value = api_response - agency = mocked_session_agency(None) - verifier = mocked_session_verifier(None) - - response = verifier.get_verified_types(form, agency) + response = first_verifier.get_verified_types(form, first_agency) assert response is None form.add_api_errors.assert_called_once_with(api_errors) @pytest.mark.django_db -def test_get_verified_types_verified_types( - mocker, mocked_session_agency, mocked_session_verifier, mock_api_client_verify, form -): +def test_get_verified_types_verified_types(mocker, first_agency, first_verifier, mock_api_client_verify, form): verified_types = ["type1", "type2"] api_response = mocker.Mock(eligibility=verified_types, error=None) mock_api_client_verify.return_value = api_response - agency = mocked_session_agency(None) - verifier = mocked_session_verifier(None) - - response = verifier.get_verified_types(form, agency) + response = first_verifier.get_verified_types(form, first_agency) assert response == verified_types form.add_api_errors.assert_not_called() @pytest.mark.django_db -def test_get_verified_types_no_verified_types( - mocker, mocked_session_agency, mocked_session_verifier, mock_api_client_verify, form -): +def test_get_verified_types_no_verified_types(mocker, first_agency, first_verifier, mock_api_client_verify, form): verified_types = [] api_response = mocker.Mock(eligibility=verified_types, error=None) mock_api_client_verify.return_value = api_response - agency = mocked_session_agency(None) - verifier = mocked_session_verifier(None) - - response = verifier.get_verified_types(form, agency) + response = first_verifier.get_verified_types(form, first_agency) assert response == verified_types form.add_api_errors.assert_not_called() From 808f673e14d18451466f1e3889378600605b0a94 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 15 Jun 2022 22:12:12 +0000 Subject: [PATCH 029/106] refactor: move verify logic back to eligibility app --- benefits/core/models.py | 39 ------------------ benefits/eligibility/verify.py | 40 +++++++++++++++++++ benefits/eligibility/views.py | 6 +-- .../test_verify.py} | 15 +++---- tests/pytest/eligibility/test_views.py | 6 +-- 5 files changed, 54 insertions(+), 52 deletions(-) create mode 100644 benefits/eligibility/verify.py rename tests/pytest/{core/test_models.py => eligibility/test_verify.py} (61%) diff --git a/benefits/core/models.py b/benefits/core/models.py index a0bac99aa5..a397dbf2c1 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -3,10 +3,8 @@ """ import logging -from django.conf import settings from django.db import models from django.urls import reverse -from eligibility_api.client import Client logger = logging.getLogger(__name__) @@ -110,43 +108,6 @@ def by_id(id): logger.debug(f"Get {EligibilityVerifier.__name__} by id: {id}") return EligibilityVerifier.objects.get(id=id) - def get_verified_types(self, form=None, agency=None, oauth_claim=None): - if form is not None and agency is not None: - return self._get_api_verified_types(form, agency) - elif oauth_claim is not None and self.requires_authentication and self.auth_claim == oauth_claim: - # TODO: refactor verifier to hold single eligibility_type - return list(map(lambda t: t.name, self.eligibility_types.all())) - else: - return [] - - def _get_api_verified_types(self, form, agency): - sub, name = form.cleaned_data.get("sub"), form.cleaned_data.get("name") - - client = Client( - verify_url=self.api_url, - headers={self.api_auth_header: self.api_auth_key}, - issuer=settings.ALLOWED_HOSTS[0], - agency=agency.agency_id, - jws_signing_alg=agency.jws_signing_alg, - client_private_key=agency.private_key_data, - jwe_encryption_alg=self.jwe_encryption_alg, - jwe_cek_enc=self.jwe_cek_enc, - server_public_key=self.public_key_data, - ) - - # get the eligibility type names to check - types = list(map(lambda t: t.name, agency.types_to_verify(self))) - - response = client.verify(sub, name, types) - - if response.error and any(response.error): - form.add_api_errors(response.error) - return None - elif any(response.eligibility): - return list(response.eligibility) - else: - return [] - class PaymentProcessor(models.Model): """An entity that processes payments for transit agencies.""" diff --git a/benefits/eligibility/verify.py b/benefits/eligibility/verify.py new file mode 100644 index 0000000000..8cecd289d8 --- /dev/null +++ b/benefits/eligibility/verify.py @@ -0,0 +1,40 @@ +from django.conf import settings + +from eligibility_api.client import Client + + +def eligibility_from_api(verifier, form, agency): + sub, name = form.cleaned_data.get("sub"), form.cleaned_data.get("name") + + client = Client( + verify_url=verifier.api_url, + headers={verifier.api_auth_header: verifier.api_auth_key}, + issuer=settings.ALLOWED_HOSTS[0], + agency=agency.agency_id, + jws_signing_alg=agency.jws_signing_alg, + client_private_key=agency.private_key_data, + jwe_encryption_alg=verifier.jwe_encryption_alg, + jwe_cek_enc=verifier.jwe_cek_enc, + server_public_key=verifier.public_key_data, + ) + + # get the eligibility type names to check + types = list(map(lambda t: t.name, agency.types_to_verify(verifier))) + + response = client.verify(sub, name, types) + + if response.error and any(response.error): + form.add_api_errors(response.error) + return None + elif any(response.eligibility): + return list(response.eligibility) + else: + return [] + + +def eligibility_from_oauth(verifier, oauth_claim): + if verifier.requires_authentication and verifier.auth_claim == oauth_claim: + # TODO: refactor verifier to hold single eligibility_type + return list(map(lambda t: t.name, verifier.eligibility_types.all())) + else: + return [] diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 88796eae52..3856b9bb9b 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -13,7 +13,7 @@ from benefits.core.middleware import AgencySessionRequired, LoginRequired, RateLimit, VerifierSessionRequired from benefits.core.models import EligibilityVerifier from benefits.core.views import ROUTE_HELP, TEMPLATE_PAGE -from . import analytics, forms +from . import analytics, forms, verify ROUTE_INDEX = "eligibility:index" @@ -195,7 +195,7 @@ def confirm(request): return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) # form is valid, make Eligibility Verification request to get the verified types - verified_types = verifier.get_verified_types(form=form, agency=session.agency(request)) + verified_types = verify.eligibility_from_api(verifier, form, session.agency(request)) # form was not valid, allow for correction/resubmission if verified_types is None: @@ -216,7 +216,7 @@ def confirm(request): # GET from an unverified user, see if verifier can get verified types and if not, present the form else: - verified_types = verifier.get_verified_types(session.oauth_claim(request)) + verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request)) if verified_types: return verified(request, verified_types) else: diff --git a/tests/pytest/core/test_models.py b/tests/pytest/eligibility/test_verify.py similarity index 61% rename from tests/pytest/core/test_models.py rename to tests/pytest/eligibility/test_verify.py index fdf64b7b6c..96aca4f70d 100644 --- a/tests/pytest/core/test_models.py +++ b/tests/pytest/eligibility/test_verify.py @@ -1,6 +1,7 @@ import pytest from benefits.eligibility.forms import EligibilityVerificationForm +from benefits.eligibility.verify import eligibility_from_api @pytest.fixture @@ -10,40 +11,40 @@ def form(mocker): @pytest.fixture def mock_api_client_verify(mocker): - return mocker.patch("benefits.core.models.Client.verify") + return mocker.patch("benefits.eligibility.verify.Client.verify") @pytest.mark.django_db -def test_get_verified_types_error(mocker, first_agency, first_verifier, mock_api_client_verify, form): +def test_eligibility_from_api_error(mocker, first_agency, first_verifier, mock_api_client_verify, form): api_errors = {"name": "Name error"} api_response = mocker.Mock(error=api_errors) mock_api_client_verify.return_value = api_response - response = first_verifier.get_verified_types(form, first_agency) + response = eligibility_from_api(first_verifier, form, first_agency) assert response is None form.add_api_errors.assert_called_once_with(api_errors) @pytest.mark.django_db -def test_get_verified_types_verified_types(mocker, first_agency, first_verifier, mock_api_client_verify, form): +def test_eligibility_from_api_verified_types(mocker, first_agency, first_verifier, mock_api_client_verify, form): verified_types = ["type1", "type2"] api_response = mocker.Mock(eligibility=verified_types, error=None) mock_api_client_verify.return_value = api_response - response = first_verifier.get_verified_types(form, first_agency) + response = eligibility_from_api(first_verifier, form, first_agency) assert response == verified_types form.add_api_errors.assert_not_called() @pytest.mark.django_db -def test_get_verified_types_no_verified_types(mocker, first_agency, first_verifier, mock_api_client_verify, form): +def test_eligibility_from_api_no_verified_types(mocker, first_agency, first_verifier, mock_api_client_verify, form): verified_types = [] api_response = mocker.Mock(eligibility=verified_types, error=None) mock_api_client_verify.return_value = api_response - response = first_verifier.get_verified_types(form, first_agency) + response = eligibility_from_api(first_verifier, form, first_agency) assert response == verified_types form.add_api_errors.assert_not_called() diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index c86d9ed46c..fa3d728b7c 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -230,7 +230,7 @@ def test_confirm_post_recaptcha_fail(mocker, client, invalid_form_data): @pytest.mark.django_db @pytest.mark.usefixtures("mocked_eligibility_auth_request") def test_confirm_post_valid_form_eligibility_error(mocker, client, form_data, mocked_analytics_module): - mocker.patch("benefits.core.models.EligibilityVerifier.get_verified_types", return_value=None) + mocker.patch("benefits.eligibility.verify.eligibility_from_api", return_value=None) path = reverse(ROUTE_CONFIRM) response = client.post(path, form_data) @@ -243,7 +243,7 @@ def test_confirm_post_valid_form_eligibility_error(mocker, client, form_data, mo @pytest.mark.django_db @pytest.mark.usefixtures("mocked_eligibility_auth_request") def test_confirm_post_valid_form_eligibility_unverified(mocker, client, form_data, mocked_analytics_module): - mocker.patch("benefits.core.models.EligibilityVerifier.get_verified_types", return_value=[]) + mocker.patch("benefits.eligibility.verify.eligibility_from_api", return_value=[]) path = reverse(ROUTE_CONFIRM) response = client.post(path, form_data) @@ -261,7 +261,7 @@ def test_confirm_post_valid_form_eligibility_verified( # mocked_session_eligibility is a fixture that mocks benefits.core.session.eligibility(request) # call it here, passing a None request, to get the return value from the mock eligibility = mocked_session_eligibility(None) - mocker.patch("benefits.core.models.EligibilityVerifier.get_verified_types", return_value=[eligibility]) + mocker.patch("benefits.eligibility.verify.eligibility_from_api", return_value=[eligibility]) path = reverse(ROUTE_CONFIRM) response = client.post(path, form_data) From bb4a981090fb38ad2f6a090b62551f608bceee72 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 15 Jun 2022 22:44:59 +0000 Subject: [PATCH 030/106] test: add coverage of eligibility_from_oauth --- tests/pytest/eligibility/test_verify.py | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/pytest/eligibility/test_verify.py b/tests/pytest/eligibility/test_verify.py index 96aca4f70d..663d0d3fcc 100644 --- a/tests/pytest/eligibility/test_verify.py +++ b/tests/pytest/eligibility/test_verify.py @@ -1,7 +1,7 @@ import pytest from benefits.eligibility.forms import EligibilityVerificationForm -from benefits.eligibility.verify import eligibility_from_api +from benefits.eligibility.verify import eligibility_from_api, eligibility_from_oauth @pytest.fixture @@ -48,3 +48,39 @@ def test_eligibility_from_api_no_verified_types(mocker, first_agency, first_veri assert response == verified_types form.add_api_errors.assert_not_called() + + +@pytest.mark.django_db +def test_eligibility_from_oauth_auth_not_required(mocked_session_verifier_auth_not_required): + # mocked_session_verifier_auth_not_required 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) + + types = eligibility_from_oauth(verifier, "claim") + + assert types == [] + + +@pytest.mark.django_db +def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_auth_required): + # mocked_session_verifier_auth_required 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.auth_claim = "claim" + + types = eligibility_from_oauth(verifier, "some_other_claim") + + assert types == [] + + +@pytest.mark.django_db +def test_eligibility_from_oauth_auth_claim_match(mocked_session_verifier_auth_required, first_eligibility): + # mocked_session_verifier_auth_required 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.auth_claim = "claim" + verifier.eligibility_types.all.return_value = [first_eligibility] + + types = eligibility_from_oauth(verifier, "claim") + + assert types == [first_eligibility.name] From 8c48337b1210a6daaef4cdc1dfd418929beb4350 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 15 Jun 2022 22:55:25 +0000 Subject: [PATCH 031/106] test: add coverage for new eligibility view logic --- tests/pytest/eligibility/test_views.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index fa3d728b7c..21e428c40d 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -270,3 +270,22 @@ def test_confirm_post_valid_form_eligibility_verified( mocked_analytics_module.returned_success.assert_called_once() assert response.status_code == 302 assert response.url == reverse(ROUTE_ENROLLMENT) + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_eligibility_auth_request") +def test_confirm_get_oauth_verified( + mocker, client, mocked_session_eligibility, mocked_session_update, mocked_analytics_module +): + # mocked_session_eligibility is a fixture that mocks benefits.core.session.eligibility(request) + # call it here, passing a None request, to get the return value from the mock + eligibility = mocked_session_eligibility(None) + mocker.patch("benefits.eligibility.verify.eligibility_from_oauth", return_value=[eligibility]) + + path = reverse(ROUTE_CONFIRM) + response = client.get(path) + + mocked_session_update.assert_called_once() + mocked_analytics_module.returned_success.assert_called_once() + assert response.status_code == 302 + assert response.url == reverse(ROUTE_ENROLLMENT) From d7d7cef256487263cbccb14100c4b96605ebb873 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 03:46:13 +0000 Subject: [PATCH 032/106] test: add coverage of oauth view logic for handling claim in response --- benefits/oauth/views.py | 2 ++ tests/pytest/oauth/test_views.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index 5d48c2fa81..b82b7d6878 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -55,6 +55,8 @@ def authorize(request): userinfo = token.get("userinfo") claim_value = token.get("userinfo").get(verifier.auth_claim) if userinfo else None claim = verifier.auth_claim if claim_value else None + else: + claim = None session.update(request, oauth_token=id_token, oauth_claim=claim) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 18d1c26d45..de1083b090 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -73,6 +73,38 @@ def test_authorize_success(mocked_oauth_client_instance, mocked_analytics_module assert result.url == reverse(ROUTE_CONFIRM) +@pytest.mark.django_db +def test_authorize_success_with_claim(mocked_session_verifier_auth_required, mocked_oauth_client, app_request): + # mocked_session_verifier_auth_required is a fixture that mocks benefits.core.session.verifier(request) + # call it here, passing a None request, to get the return value from the mock + verifier = mocked_session_verifier_auth_required(None) + verifier.auth_claim = "claim" + mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} + + result = authorize(app_request) + + mocked_oauth_client.authorize_access_token.assert_called_with(app_request) + assert session.oauth_claim(app_request) == "claim" + assert result.status_code == 302 + assert result.url == reverse(ROUTE_CONFIRM) + + +@pytest.mark.django_db +def test_authorize_success_without_claim(mocked_session_verifier_auth_required, mocked_oauth_client, app_request): + # mocked_session_verifier_auth_required is a fixture that mocks benefits.core.session.verifier(request) + # call it here, passing a None request, to get the return value from the mock + verifier = mocked_session_verifier_auth_required(None) + verifier.auth_claim = "" + mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} + + 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) + + def test_logout(mocker, mocked_analytics_module, app_request): # logout internally calls _deauthorize_redirect # this mocks that function and a success response From 7b6d5f79cf692bc3ae2dc7133a76439db4198038 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 03:52:43 +0000 Subject: [PATCH 033/106] fix(tests): fix false test passage for test_confirm_get_oauth_verified the test was inadvertently using a fixture that mocks session.eligible returning True, which meant it wasn't actually covering the logic. --- tests/pytest/eligibility/test_views.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index 21e428c40d..eeab3d90e3 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -274,13 +274,8 @@ def test_confirm_post_valid_form_eligibility_verified( @pytest.mark.django_db @pytest.mark.usefixtures("mocked_eligibility_auth_request") -def test_confirm_get_oauth_verified( - mocker, client, mocked_session_eligibility, mocked_session_update, mocked_analytics_module -): - # mocked_session_eligibility is a fixture that mocks benefits.core.session.eligibility(request) - # call it here, passing a None request, to get the return value from the mock - eligibility = mocked_session_eligibility(None) - mocker.patch("benefits.eligibility.verify.eligibility_from_oauth", return_value=[eligibility]) +def test_confirm_get_oauth_verified(mocker, client, first_eligibility, mocked_session_update, mocked_analytics_module): + mocker.patch("benefits.eligibility.verify.eligibility_from_oauth", return_value=[first_eligibility]) path = reverse(ROUTE_CONFIRM) response = client.get(path) From 4ce25d649d3a9119923d3f1bbdc57ec310183fe3 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 04:34:53 +0000 Subject: [PATCH 034/106] refactor(model): EligibilityVerifier now holds one eligibility_type this makes the relationship between eligibility_type and auth_claim closer to a one-to-one relationship. --- benefits/core/migrations/0001_initial.py | 7 +++++-- benefits/core/models.py | 4 ++-- benefits/eligibility/verify.py | 3 +-- fixtures/03_eligibilityverifier.json | 4 ++-- tests/pytest/eligibility/test_verify.py | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/benefits/core/migrations/0001_initial.py b/benefits/core/migrations/0001_initial.py index ac83c0da46..3d1adb8f4b 100644 --- a/benefits/core/migrations/0001_initial.py +++ b/benefits/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2022-06-13 23:20 +# Generated by Django 3.2.13 on 2022-06-16 04:12 from django.db import migrations, models import django.db.models.deletion @@ -78,7 +78,10 @@ class Migration(migrations.Migration): "auth_provider", models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to="core.authprovider"), ), - ("eligibility_types", models.ManyToManyField(to="core.EligibilityType")), + ( + "eligibility_type", + models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="core.eligibilitytype"), + ), ], ), migrations.CreateModel( diff --git a/benefits/core/models.py b/benefits/core/models.py index a397dbf2c1..dc537c8a9e 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -62,7 +62,7 @@ class EligibilityVerifier(models.Model): api_url = models.TextField() api_auth_header = models.TextField() api_auth_key = models.TextField() - eligibility_types = models.ManyToManyField(EligibilityType) + eligibility_type = models.ForeignKey(EligibilityType, on_delete=models.PROTECT) public_key = models.ForeignKey(PemData, help_text="The Verifier's public key, used to encrypt requests targeted at this Verifier and to verify signed responses from this verifier.", related_name="+", on_delete=models.PROTECT) # noqa: 503 jwe_cek_enc = models.TextField(help_text="The JWE-compatible Content Encryption Key (CEK) key-length and mode") jwe_encryption_alg = models.TextField(help_text="The JWE-compatible encryption algorithm") @@ -173,7 +173,7 @@ def types_to_verify(self, eligibility_verifier): """List of eligibility types to verify for this agency.""" # compute set intersection of agency and verifier type ids agency_types = set(self.eligibility_types.values_list("id", flat=True)) - verifier_types = set(eligibility_verifier.eligibility_types.values_list("id", flat=True)) + verifier_types = {eligibility_verifier.eligibility_type.id} supported_types = list(agency_types & verifier_types) return EligibilityType.get_many(supported_types) diff --git a/benefits/eligibility/verify.py b/benefits/eligibility/verify.py index 8cecd289d8..80bc5b5944 100644 --- a/benefits/eligibility/verify.py +++ b/benefits/eligibility/verify.py @@ -34,7 +34,6 @@ def eligibility_from_api(verifier, form, agency): def eligibility_from_oauth(verifier, oauth_claim): if verifier.requires_authentication and verifier.auth_claim == oauth_claim: - # TODO: refactor verifier to hold single eligibility_type - return list(map(lambda t: t.name, verifier.eligibility_types.all())) + return [verifier.eligibility_type.name] else: return [] diff --git a/fixtures/03_eligibilityverifier.json b/fixtures/03_eligibilityverifier.json index 15480809e7..00d182fa71 100644 --- a/fixtures/03_eligibilityverifier.json +++ b/fixtures/03_eligibilityverifier.json @@ -7,7 +7,7 @@ "api_url": "http://server:5000/verify", "api_auth_header": "X-Server-API-Key", "api_auth_key": "server-auth-token", - "eligibility_types": [1, 2], + "eligibility_type": 1, "public_key": 1, "jwe_cek_enc": "A256CBC-HS512", "jwe_encryption_alg": "RSA-OAEP", @@ -43,7 +43,7 @@ "api_url": "http://server:5000/verify", "api_auth_header": "X-Server-API-Key", "api_auth_key": "server-auth-token", - "eligibility_types": [1, 2], + "eligibility_type": 2, "public_key": 1, "jwe_cek_enc": "A256CBC-HS512", "jwe_encryption_alg": "RSA-OAEP", diff --git a/tests/pytest/eligibility/test_verify.py b/tests/pytest/eligibility/test_verify.py index 663d0d3fcc..a677118bb1 100644 --- a/tests/pytest/eligibility/test_verify.py +++ b/tests/pytest/eligibility/test_verify.py @@ -79,7 +79,7 @@ def test_eligibility_from_oauth_auth_claim_match(mocked_session_verifier_auth_re # call it (with a None request) to return a verifier object verifier = mocked_session_verifier_auth_required(None) verifier.auth_claim = "claim" - verifier.eligibility_types.all.return_value = [first_eligibility] + verifier.eligibility_type = first_eligibility types = eligibility_from_oauth(verifier, "claim") From 5f88649d14e4d2095652e5e67b783d83f8c82e8f Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 04:58:06 +0000 Subject: [PATCH 035/106] refactor: require that agency's types contain its verifiers types --- benefits/eligibility/verify.py | 4 ++-- benefits/eligibility/views.py | 2 +- fixtures/05_transitagency.json | 3 ++- tests/pytest/eligibility/test_verify.py | 12 ++++++------ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/benefits/eligibility/verify.py b/benefits/eligibility/verify.py index 80bc5b5944..5567f130d7 100644 --- a/benefits/eligibility/verify.py +++ b/benefits/eligibility/verify.py @@ -32,8 +32,8 @@ def eligibility_from_api(verifier, form, agency): return [] -def eligibility_from_oauth(verifier, oauth_claim): +def eligibility_from_oauth(verifier, oauth_claim, agency): if verifier.requires_authentication and verifier.auth_claim == oauth_claim: - return [verifier.eligibility_type.name] + return list(map(lambda t: t.name, agency.types_to_verify(verifier))) else: return [] diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 3856b9bb9b..1f61c22468 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -216,7 +216,7 @@ def confirm(request): # GET from an unverified user, see if verifier can get verified types and if not, present the form else: - verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request)) + verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request), session.agency(request)) if verified_types: return verified(request, verified_types) else: diff --git a/fixtures/05_transitagency.json b/fixtures/05_transitagency.json index 8a6d439a43..57f9337b71 100644 --- a/fixtures/05_transitagency.json +++ b/fixtures/05_transitagency.json @@ -12,7 +12,8 @@ "phone": "800-555-5555", "active": true, "eligibility_types": [ - 1 + 1, + 2 ], "eligibility_verifiers": [ 1, diff --git a/tests/pytest/eligibility/test_verify.py b/tests/pytest/eligibility/test_verify.py index a677118bb1..9da7f918c7 100644 --- a/tests/pytest/eligibility/test_verify.py +++ b/tests/pytest/eligibility/test_verify.py @@ -51,36 +51,36 @@ def test_eligibility_from_api_no_verified_types(mocker, first_agency, first_veri @pytest.mark.django_db -def test_eligibility_from_oauth_auth_not_required(mocked_session_verifier_auth_not_required): +def test_eligibility_from_oauth_auth_not_required(mocked_session_verifier_auth_not_required, first_agency): # mocked_session_verifier_auth_not_required 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) - types = eligibility_from_oauth(verifier, "claim") + types = eligibility_from_oauth(verifier, "claim", first_agency) assert types == [] @pytest.mark.django_db -def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_auth_required): +def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_auth_required, first_agency): # mocked_session_verifier_auth_required 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.auth_claim = "claim" - types = eligibility_from_oauth(verifier, "some_other_claim") + types = eligibility_from_oauth(verifier, "some_other_claim", first_agency) assert types == [] @pytest.mark.django_db -def test_eligibility_from_oauth_auth_claim_match(mocked_session_verifier_auth_required, first_eligibility): +def test_eligibility_from_oauth_auth_claim_match(mocked_session_verifier_auth_required, first_eligibility, first_agency): # mocked_session_verifier_auth_required 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.auth_claim = "claim" verifier.eligibility_type = first_eligibility - types = eligibility_from_oauth(verifier, "claim") + types = eligibility_from_oauth(verifier, "claim", first_agency) assert types == [first_eligibility.name] From d7f5348898246a89562659531324544e8d8f04c3 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 20:50:12 +0000 Subject: [PATCH 036/106] refactor(tests): use new mocked_oauth_client_instance fixture --- tests/pytest/oauth/test_views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index de1083b090..135a4af419 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -74,11 +74,12 @@ def test_authorize_success(mocked_oauth_client_instance, mocked_analytics_module @pytest.mark.django_db -def test_authorize_success_with_claim(mocked_session_verifier_auth_required, mocked_oauth_client, app_request): +def test_authorize_success_with_claim(mocked_session_verifier_auth_required, mocked_oauth_client_instance, app_request): # mocked_session_verifier_auth_required is a fixture that mocks benefits.core.session.verifier(request) # call it here, passing a None request, to get the return value from the mock verifier = mocked_session_verifier_auth_required(None) verifier.auth_claim = "claim" + mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} result = authorize(app_request) @@ -90,11 +91,12 @@ def test_authorize_success_with_claim(mocked_session_verifier_auth_required, moc @pytest.mark.django_db -def test_authorize_success_without_claim(mocked_session_verifier_auth_required, mocked_oauth_client, app_request): +def test_authorize_success_without_claim(mocked_session_verifier_auth_required, mocked_oauth_client_instance, app_request): # mocked_session_verifier_auth_required is a fixture that mocks benefits.core.session.verifier(request) # call it here, passing a None request, to get the return value from the mock verifier = mocked_session_verifier_auth_required(None) verifier.auth_claim = "" + mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} result = authorize(app_request) From 764d812718f2be4baede3b04f324ab07e6c7373d Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 16 Jun 2022 21:03:32 +0000 Subject: [PATCH 037/106] fix(button): fix style on enrollment success log out button --- benefits/static/css/styles.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 774a00d998..d3b6b76e95 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -382,8 +382,11 @@ footer.global-footer .footer-links a:active { /* Enrollment Success */ .enrollment-success #login { - padding: 0.3rem 0.6rem 0.1rem 0.6rem; - vertical-align: middle; + padding: 0.1rem 0.5rem 0.3rem 0.6rem; + display: inline-block; + padding-top: 0; + margin: 0; + width: inherit; } .enrollment-success #login .fallback-text { From 52c0df835f03b0e6c17911a93c47d55f4540d9e0 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 16 Jun 2022 21:47:29 +0000 Subject: [PATCH 038/106] fix: login.gov button on enrollment success, desktop --- benefits/static/css/styles.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index d3b6b76e95..5e54227e74 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -387,11 +387,14 @@ footer.global-footer .footer-links a:active { padding-top: 0; margin: 0; width: inherit; + border-radius: 0; } .enrollment-success #login .fallback-text { + padding-top: 14px; + margin: 8px auto 0 auto; + display: block; width: 7rem; - vertical-align: inherit; } .logged-out.enrollment-success .success-image { From e1ca532390fde28aa2bb6d8edf3ff25402b39c45 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 23:33:55 +0000 Subject: [PATCH 039/106] fix(copy): add sentence to start page to inform about age verification --- benefits/locale/en/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 0cdf9f2497..a414baf343 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -379,7 +379,7 @@ msgid "eligibility.pages.start.oauth.heading" msgstr "A Login.gov account with identity verification" msgid "eligibility.pages.start.oauth.details" -msgstr "Login.gov is a safe way to sign in to government services. If you do not have an account already, you will be able to create one. You will also need to provide proof of your identity. Some of the items you can use to do that include:" +msgstr "Login.gov is a safe way to sign in to government services. Benefits uses Login.gov to verify your age. If you do not have an account already, you will be able to create one. You will also need to verify your identity, which will require these items:" msgid "eligibility.pages.start.oauth.link_text" msgstr "Learn more about Login.gov" From 1d9264980f65a0a41ea5d6375bfe2ae57ffdfaa9 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 21:27:17 +0000 Subject: [PATCH 040/106] feat: helper script regenerates the migration file --- bin/makemigrations.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 bin/makemigrations.sh diff --git a/bin/makemigrations.sh b/bin/makemigrations.sh new file mode 100755 index 0000000000..7b5bcb355a --- /dev/null +++ b/bin/makemigrations.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -eu + +# remove existing migration file + +rm -f benefits/core/migrations/0001_initial.py + +# regenerate + +python manage.py makemigrations + +# reformat with black + +python -m black benefits/core/migrations/0001_initial.py From 311dcdbafe4bb5dacee22674646629c246fbed07 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 21:51:19 +0000 Subject: [PATCH 041/106] docs: describe models and migrations --- docs/development/models-migrations.md | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/development/models-migrations.md diff --git a/docs/development/models-migrations.md b/docs/development/models-migrations.md new file mode 100644 index 0000000000..6c900e170a --- /dev/null +++ b/docs/development/models-migrations.md @@ -0,0 +1,43 @@ +# Django models and migrations + +!!! example "Models and migrations" + + [`benefits/core/models.py`][core-models] + + [`benefits/core/migrations/0001_initial.py`][core-migrations] + +Cal-ITP Benefits defines a number of [models][core-models] in the core application, used throughout the codebase to configure +different parts of the UI and logic. + +The Cal-ITP Benefits database is a simple read-only Sqlite database, initialized from the [fixture configuration](../configuration/fixtures.md) files. + +## Migrations + +The database is rebuilt from scratch each time the container starts, so we maintain a single [migration][core-migrations] file. + +This file always represents the current schema of the database and matches the current structure of the model classes. + +## Updating models + +When models are updated, the migration should be updated as well. + +A simple helper script exists to regenerate the migration file based on the current state of models in the local directory: + +[`bin/makemigrations.sh`][makemigrations] + +```bash +bin/makemigrations.sh +``` + +This script: + +1. Deletes the existing migrations file +1. Runs the django `makemigrations` command +1. Formats the newly regenerated file with `black` + +This will result in a simple diff of changes on the same migration file. Commit these changes (including the timestamp!) along +with the model changes. + +[core-models]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/models.py +[core-migrations]: https://github.com/cal-itp/benefits/blob/dev/benefits/core/migrations/0001_initial.py +[makemigrations]: https://github.com/cal-itp/benefits/blob/dev/bin/makemigrations.sh From 9ae5255a38bf496cec3bb829a1feec29ac231e41 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 22:18:05 +0000 Subject: [PATCH 042/106] chore(models): remove help_text, simplify migrations help_text was forcing long lines that had to be manually excepted from flake8; since we don't use the admin backend, help_text is less useful maintain comments for the prior help_text in the models to keep the context in-code --- benefits/core/migrations/0001_initial.py | 68 +++++------------------- benefits/core/models.py | 45 +++++++++------- 2 files changed, 40 insertions(+), 73 deletions(-) diff --git a/benefits/core/migrations/0001_initial.py b/benefits/core/migrations/0001_initial.py index 3d1adb8f4b..b5f5eb6eb9 100644 --- a/benefits/core/migrations/0001_initial.py +++ b/benefits/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2022-06-16 04:12 +# Generated by Django 3.2.13 on 2022-06-16 22:13 from django.db import migrations, models import django.db.models.deletion @@ -36,12 +36,9 @@ class Migration(migrations.Migration): ("api_url", models.TextField()), ("api_auth_header", models.TextField()), ("api_auth_key", models.TextField()), - ( - "jwe_cek_enc", - models.TextField(help_text="The JWE-compatible Content Encryption Key (CEK) key-length and mode"), - ), - ("jwe_encryption_alg", models.TextField(help_text="The JWE-compatible encryption algorithm")), - ("jws_signing_alg", models.TextField(help_text="The JWS-compatible signing algorithm")), + ("jwe_cek_enc", models.TextField()), + ("jwe_encryption_alg", models.TextField()), + ("jws_signing_alg", models.TextField()), ("auth_scope", models.TextField(null=True)), ("auth_claim", models.TextField(null=True)), ("selection_label", models.TextField()), @@ -55,22 +52,10 @@ class Migration(migrations.Migration): ("form_blurb", models.TextField()), ("form_sub_label", models.TextField()), ("form_sub_placeholder", models.TextField()), - ( - "form_sub_pattern", - models.TextField( - help_text="A regular expression used to validate the 'sub' API field before sending to this verifier", - null=True, - ), - ), + ("form_sub_pattern", models.TextField(null=True)), ("form_name_label", models.TextField()), ("form_name_placeholder", models.TextField()), - ( - "form_name_max_length", - models.PositiveSmallIntegerField( - help_text="The maximum length accepted for the 'name' API field before sending to this verifier", - null=True, - ), - ), + ("form_name_max_length", models.PositiveSmallIntegerField(null=True)), ("unverified_title", models.TextField()), ("unverified_content_title", models.TextField()), ("unverified_blurb", models.TextField()), @@ -105,8 +90,8 @@ class Migration(migrations.Migration): name="PemData", fields=[ ("id", models.AutoField(primary_key=True, serialize=False)), - ("text", models.TextField(help_text="The data in utf-8 encoded PEM text format.")), - ("label", models.TextField(help_text="Human description of the PEM data.")), + ("text", models.TextField()), + ("label", models.TextField()), ], ), migrations.CreateModel( @@ -121,7 +106,7 @@ class Migration(migrations.Migration): ("info_url", models.URLField()), ("phone", models.TextField()), ("active", models.BooleanField(default=False)), - ("jws_signing_alg", models.TextField(help_text="The JWS-compatible signing algorithm.")), + ("jws_signing_alg", models.TextField()), ("eligibility_types", models.ManyToManyField(to="core.EligibilityType")), ("eligibility_verifiers", models.ManyToManyField(to="core.EligibilityVerifier")), ( @@ -130,53 +115,28 @@ class Migration(migrations.Migration): ), ( "private_key", - models.ForeignKey( - help_text="The Agency's private key, used to sign tokens created on behalf of this Agency.", - on_delete=django.db.models.deletion.PROTECT, - related_name="+", - to="core.pemdata", - ), + models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="+", to="core.pemdata"), ), ], ), migrations.AddField( model_name="paymentprocessor", name="client_cert", - field=models.ForeignKey( - help_text="The certificate used for client certificate authentication to the API.", - on_delete=django.db.models.deletion.PROTECT, - related_name="+", - to="core.pemdata", - ), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="+", to="core.pemdata"), ), migrations.AddField( model_name="paymentprocessor", name="client_cert_private_key", - field=models.ForeignKey( - help_text="The private key, used to sign the certificate.", - on_delete=django.db.models.deletion.PROTECT, - related_name="+", - to="core.pemdata", - ), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="+", to="core.pemdata"), ), migrations.AddField( model_name="paymentprocessor", name="client_cert_root_ca", - field=models.ForeignKey( - help_text="The root CA bundle, used to verify the server.", - on_delete=django.db.models.deletion.PROTECT, - related_name="+", - to="core.pemdata", - ), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="+", to="core.pemdata"), ), migrations.AddField( model_name="eligibilityverifier", name="public_key", - field=models.ForeignKey( - help_text="The Verifier's public key, used to encrypt requests targeted at this Verifier and to verify signed responses from this verifier.", # noqa - on_delete=django.db.models.deletion.PROTECT, - related_name="+", - to="core.pemdata", - ), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="+", to="core.pemdata"), ), ] diff --git a/benefits/core/models.py b/benefits/core/models.py index dc537c8a9e..c012db0f77 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -14,8 +14,10 @@ class PemData(models.Model): """API Certificate or Key in PEM format.""" id = models.AutoField(primary_key=True) - text = models.TextField(help_text="The data in utf-8 encoded PEM text format.") - label = models.TextField(help_text="Human description of the PEM data.") + # The data in utf-8 encoded PEM text format + text = models.TextField() + # Human description of the PEM data + label = models.TextField() def __str__(self): return self.label @@ -56,17 +58,20 @@ def get_many(ids): class EligibilityVerifier(models.Model): """An entity that verifies eligibility.""" - # fmt: off id = models.AutoField(primary_key=True) name = models.TextField() api_url = models.TextField() api_auth_header = models.TextField() api_auth_key = models.TextField() eligibility_type = models.ForeignKey(EligibilityType, on_delete=models.PROTECT) - public_key = models.ForeignKey(PemData, help_text="The Verifier's public key, used to encrypt requests targeted at this Verifier and to verify signed responses from this verifier.", related_name="+", on_delete=models.PROTECT) # noqa: 503 - jwe_cek_enc = models.TextField(help_text="The JWE-compatible Content Encryption Key (CEK) key-length and mode") - jwe_encryption_alg = models.TextField(help_text="The JWE-compatible encryption algorithm") - jws_signing_alg = models.TextField(help_text="The JWS-compatible signing algorithm") + # public key is used to encrypt requests targeted at this Verifier and to verify signed responses from this verifier + public_key = models.ForeignKey(PemData, related_name="+", on_delete=models.PROTECT) + # The JWE-compatible Content Encryption Key (CEK) key-length and mode + jwe_cek_enc = models.TextField() + # The JWE-compatible encryption algorithm + jwe_encryption_alg = models.TextField() + # The JWS-compatible signing algorithm + jws_signing_alg = models.TextField() auth_provider = models.ForeignKey(AuthProvider, on_delete=models.PROTECT, null=True) auth_scope = models.TextField(null=True) auth_claim = models.TextField(null=True) @@ -81,14 +86,15 @@ class EligibilityVerifier(models.Model): form_blurb = models.TextField() form_sub_label = models.TextField() form_sub_placeholder = models.TextField() - form_sub_pattern = models.TextField(null=True, help_text="A regular expression used to validate the 'sub' API field before sending to this verifier") # noqa: 503 + # A regular expression used to validate the 'sub' API field before sending to this verifier + form_sub_pattern = models.TextField(null=True) form_name_label = models.TextField() form_name_placeholder = models.TextField() - form_name_max_length = models.PositiveSmallIntegerField(null=True, help_text="The maximum length accepted for the 'name' API field before sending to this verifier") # noqa: 503 + # The maximum length accepted for the 'name' API field before sending to this verifier + form_name_max_length = models.PositiveSmallIntegerField(null=True) unverified_title = models.TextField() unverified_content_title = models.TextField() unverified_blurb = models.TextField() - # fmt: on def __str__(self): return self.name @@ -112,7 +118,6 @@ def by_id(id): class PaymentProcessor(models.Model): """An entity that processes payments for transit agencies.""" - # fmt: off id = models.AutoField(primary_key=True) name = models.TextField() api_base_url = models.TextField() @@ -122,13 +127,15 @@ class PaymentProcessor(models.Model): card_tokenize_url = models.TextField() card_tokenize_func = models.TextField() card_tokenize_env = models.TextField() - client_cert = models.ForeignKey(PemData, help_text="The certificate used for client certificate authentication to the API.", related_name="+", on_delete=models.PROTECT) # noqa: 503 - client_cert_private_key = models.ForeignKey(PemData, help_text="The private key, used to sign the certificate.", related_name="+", on_delete=models.PROTECT) # noqa: 503 - client_cert_root_ca = models.ForeignKey(PemData, help_text="The root CA bundle, used to verify the server.", related_name="+", on_delete=models.PROTECT) # noqa: 503 + # The certificate used for client certificate authentication to the API + client_cert = models.ForeignKey(PemData, related_name="+", on_delete=models.PROTECT) + # The private key, used to sign the certificate + client_cert_private_key = models.ForeignKey(PemData, related_name="+", on_delete=models.PROTECT) + # The root CA bundle, used to verify the server. + client_cert_root_ca = models.ForeignKey(PemData, related_name="+", on_delete=models.PROTECT) customer_endpoint = models.TextField() customers_endpoint = models.TextField() group_endpoint = models.TextField() - # fmt: on def __str__(self): return self.name @@ -137,7 +144,6 @@ def __str__(self): class TransitAgency(models.Model): """An agency offering transit service.""" - # fmt: off id = models.AutoField(primary_key=True) slug = models.TextField() short_name = models.TextField() @@ -150,9 +156,10 @@ class TransitAgency(models.Model): eligibility_types = models.ManyToManyField(EligibilityType) eligibility_verifiers = models.ManyToManyField(EligibilityVerifier) payment_processor = models.ForeignKey(PaymentProcessor, on_delete=models.PROTECT) - private_key = models.ForeignKey(PemData, help_text="The Agency's private key, used to sign tokens created on behalf of this Agency.", related_name="+", on_delete=models.PROTECT) # noqa: 503 - jws_signing_alg = models.TextField(help_text="The JWS-compatible signing algorithm.") - # fmt: on + # The Agency's private key, used to sign tokens created on behalf of this Agency + private_key = models.ForeignKey(PemData, related_name="+", on_delete=models.PROTECT) + # The JWS-compatible signing algorithm + jws_signing_alg = models.TextField() def __str__(self): return self.long_name From c131e71190c9226d8978498be3ff3f05b80d7922 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 16 Jun 2022 22:32:15 +0000 Subject: [PATCH 043/106] fix: add third bullet point copy --- benefits/locale/en/LC_MESSAGES/django.po | 2 +- benefits/locale/es/LC_MESSAGES/django.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 0cdf9f2497..7d2fa259f8 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -394,7 +394,7 @@ msgid "eligibility.pages.start.oauth.required_items[1]" msgstr "Your Social Security number" msgid "eligibility.pages.start.oauth.required_items[2]" -msgstr "Don’t have access to these items?" +msgstr "A phone number with a phone plan associated with your name" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signin" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index c5e5595667..ef152d18bc 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -379,7 +379,7 @@ msgid "eligibility.pages.start.oauth.required_items[1]" msgstr "TODO: Your Social Security number" msgid "eligibility.pages.start.oauth.required_items[2]" -msgstr "TODO: Don’t have access to these items?" +msgstr "TODO: A phone number with a phone plan associated with your name" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signin" From 1055e16e3c920ec0e73fc41f54b7514a7662ff4a Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Fri, 17 Jun 2022 04:16:57 +0000 Subject: [PATCH 044/106] fix: add no-image-mobile for all 3 branches of enrollment/success --- benefits/enrollment/views.py | 4 +-- benefits/static/css/styles.css | 48 +++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index f4642ca740..e7b23f6916 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -156,10 +156,10 @@ def success(request): if session.logged_in(request): page.buttons = [viewmodels.Button.logout()] - page.classes = ["logged-in"] + page.classes = ["no-image-mobile", "logged-in"] page.icon = icon else: - page.classes = ["logged-out"] + page.classes = ["no-image-mobile", "logged-out"] page.content_title = _("enrollment.pages.success.logout.title") page.noimage = True else: diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 5e54227e74..7d031a998b 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -7,6 +7,10 @@ body { color: #000000; } +.bg-danger { + display: none; +} + @font-face { font-family: "Public Sans"; font-weight: 700; @@ -14,10 +18,6 @@ body { src: local("PublicSans"), url("../fonts/PublicSans-Bold.woff") format("woff"); } -.bg-danger { - display: none; -} - body, h1, h2, @@ -109,14 +109,16 @@ footer { } footer.global-footer .footer-links a { - font-size: 16px; + font-size: 18px; color: #73b3e7; + text-decoration: underline; } footer.global-footer .footer-links a:hover, footer.global-footer .footer-links a:focus, footer.global-footer .footer-links a:active { color: #9b74d7; + text-decoration: none; } /* class styles */ @@ -261,7 +263,7 @@ footer.global-footer .footer-links a:active { margin-top: 100px; margin-bottom: 60px; font-size: 24px; - line-height: 30px; + line-height: 36px; } .media-list { @@ -301,12 +303,8 @@ footer.global-footer .footer-links a:active { font-size: 18px; letter-spacing: 0.05em; line-height: 26.1px; -} - -.media-list .media .media-body--heading { font-weight: 700; padding-left: 0; - line-height: 26.1px; margin-bottom: 16px; } @@ -382,9 +380,8 @@ footer.global-footer .footer-links a:active { /* Enrollment Success */ .enrollment-success #login { - padding: 0.1rem 0.5rem 0.3rem 0.6rem; + padding: 0 0.5rem 0.3rem 0.6rem; display: inline-block; - padding-top: 0; margin: 0; width: inherit; border-radius: 0; @@ -488,6 +485,7 @@ footer.global-footer .footer-links a:active { .container.content h1.icon-title { padding-bottom: 1.5rem; + line-height: 36px; } .container.content h1.icon-title span.icon { @@ -628,6 +626,10 @@ footer.global-footer .footer-links a:active { height: 131px; } + .navbar.navbar-expand-sm.navbar-dark.bg-primary { + padding: 14.5px 1rem; + } + .container.content .btn-lg { padding: 1.1875rem 0.813rem; } @@ -646,13 +648,15 @@ footer.global-footer .footer-links a:active { } .signout-row .container .signout-link { - font-size: 16px; + font-size: 18px; color: #2b6597; background: none; - padding: 10px 30px; + padding: 5px 10px; border-radius: 3px; border: 2px solid #2b6597; margin-top: 20px; + letter-spacing: 0.02em; + font-weight: 500; } .signout-row .container .signout-link:hover { @@ -664,7 +668,7 @@ footer.global-footer .footer-links a:active { } .no-image-mobile .signout-row { - top: 68px; + top: 80px; } .no-image-mobile .main-primary .container-fluid { @@ -694,12 +698,13 @@ footer.global-footer .footer-links a:active { } .media-list .media .media-line .icon { - width: 90px; - height: 90px; + width: 92px; + height: 92px; + margin: 0 auto 42px auto; } .media-list .media { - flex-direction: row; + flex-direction: column; } .media-list .media .media-line { @@ -711,18 +716,19 @@ footer.global-footer .footer-links a:active { } .media-list .media .media-body--heading { - height: 90px; + font-size: 20px; + line-height: 30px; } .media-list .media .media-body--details { padding-bottom: 25px; - margin-left: -100px; + margin-left: 0; font-size: 20px; line-height: 30px; } .media-list .media .media-body--links { - margin-left: -100px; + margin-left: 0; float: right; } From 082e147f8bcd9d8f48631f29462cedf9ae3dad5a Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Fri, 17 Jun 2022 04:42:54 +0000 Subject: [PATCH 045/106] fix: addd padding under Continue button --- benefits/static/css/styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 7d031a998b..ef46e50ef1 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -356,6 +356,7 @@ footer.global-footer .footer-links a:active { display: flex; align-items: flex-end; justify-content: right; + margin-bottom: 30px; } .eligibility-start .main-container .buttons .btn { From 5ea0ea21f519281ee63fdfa2567b839407228f0b Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Fri, 17 Jun 2022 05:01:46 +0000 Subject: [PATCH 046/106] fix: use horizontal photo for eligibility-start page:rt --- benefits/static/css/styles.css | 4 +++- .../img/rider-tapping-bank-card-horizontal.png | Bin 0 -> 318237 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 benefits/static/img/rider-tapping-bank-card-horizontal.png diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index ef46e50ef1..5a06407a81 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -97,8 +97,10 @@ main .main-row .col-lg-6.image { } .one-column-image { - background: 48% 75% url("/static/img/ridertappingbankcard.png") no-repeat; + background: url("/static/img/rider-tapping-bank-card-horizontal.png"); background-size: cover; + background-repeat: no-repeat; + background-position: center center; height: 292px; } diff --git a/benefits/static/img/rider-tapping-bank-card-horizontal.png b/benefits/static/img/rider-tapping-bank-card-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..9bec4f8e81b13008e7d5599dd5c8a47bd58c22cd GIT binary patch literal 318237 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

    v)81Q4{hVaTt5ZmLj61wSzGHwHjLsMxGG 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 Date: Fri, 17 Jun 2022 05:03:47 +0000 Subject: [PATCH 047/106] chore: remove debug bar hide --- benefits/static/css/styles.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index 5a06407a81..b4b15490bf 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -7,10 +7,6 @@ body { color: #000000; } -.bg-danger { - display: none; -} - @font-face { font-family: "Public Sans"; font-weight: 700; From cd2de7ffbf21c96b8561663c7792238c4de3ac9d Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 22:31:07 +0000 Subject: [PATCH 048/106] fix(copy): update help copy about partnering to confirm age --- benefits/locale/en/LC_MESSAGES/django.po | 4 ++-- benefits/locale/es/LC_MESSAGES/django.po | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 0cdf9f2497..05f8daa0c0 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -25,8 +25,8 @@ msgstr "" msgid "core.pages.help.about.p[1]" msgstr "" -"We partner with the California DMV to confirm age for age-based discounts and " -"local transporation agencies to confirm specific discount programs. Once verified, " +"We partner with Login.gov to confirm age for age-based discounts, and we " +"partner with local transporation agencies to confirm specific discount programs. Once verified, " "discounts are attached to your bank card through our payment partner, Littlepay, so " "that when you pay for your transit ride, you get your discount automatically." diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index c5e5595667..1749dd414b 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -23,7 +23,7 @@ msgstr "" msgid "core.pages.help.about.p[1]" msgstr "" -"Nos asociamos con el DMV de California para confirmar la edad para los descuentos basados en la edad y las agencias de transporte locales para confirmar los programas de descuento específicos. Una vez verificados, los descuentos se adjuntan a su tarjeta bancaria a través de nuestro socio de pago, Littlepay, para que cuando pague su viaje en transporte, obtenga su descuento automáticamente.?" +"Nos asociamos con Login.gov para confirmar la edad para los descuentos basados en la edad y las agencias de transporte locales para confirmar los programas de descuento específicos. Una vez verificados, los descuentos se adjuntan a su tarjeta bancaria a través de nuestro socio de pago, Littlepay, para que cuando pague su viaje en transporte, obtenga su descuento automáticamente." msgid "core.pages.help.payment_options" msgstr "Opciones de pago" From d4703a006565d3ef64367e1f0147fb868ecff76f Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 22:56:24 +0000 Subject: [PATCH 049/106] fix(copy): update Help page with section on Login.gov verification --- benefits/core/templates/core/help.html | 6 ++++++ benefits/locale/en/LC_MESSAGES/django.po | 15 +++++++++++++++ benefits/locale/es/LC_MESSAGES/django.po | 15 +++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/benefits/core/templates/core/help.html b/benefits/core/templates/core/help.html index 139fc225d8..66a82e05a7 100644 --- a/benefits/core/templates/core/help.html +++ b/benefits/core/templates/core/help.html @@ -35,6 +35,12 @@

    {% translate "core.pages.help.login_gov" %}

    {% blocktranslate with website="https://login.gov/help/"%}core.pages.help.login_gov.p[2][1]{{ website }}{% endblocktranslate %}

    +

    {% translate "core.pages.help.login_gov_verify" %}

    +

    {% translate "core.pages.help.login_gov_verify.p[0]" %}

    +

    {% translate "core.pages.help.login_gov_verify.p[1]" %}

    +

    {% translate "core.pages.help.login_gov_verify.p[2]" %}

    +

    {% blocktranslate with website="https://login.gov/help/" %}core.pages.help.login_gov_verify.p[3]{{ website }}{% endblocktranslate %}

    +

    {% translate "core.pages.help.littlepay" %}

    {% translate "core.pages.help.littlepay.p[0]" %}

    {% blocktranslate with website="https://littlepay.com" %}core.pages.help.littlepay.p[1]{{ website }}{% endblocktranslate %}

    diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 05f8daa0c0..8baac3a102 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -76,6 +76,21 @@ msgstr "To learn more about Login.gov, please visit the " msgid "core.pages.help.login_gov.p[2][1]%(website)s" msgstr "or the
    Login.gov Help Center." +msgid "core.pages.help.login_gov_verify" +msgstr "How do I verify my identity on Login.gov?" + +msgid "core.pages.help.login_gov_verify.p[0]" +msgstr "To verify your identity, Login.gov asks that you upload a photograph of your state-issued ID and share your phone number and other personal information, which is then verified against authoritative sources. These requirements are in addition to meeting the requirements for two-factor authentication." + +msgid "core.pages.help.login_gov_verify.p[1]" +msgstr "You cannot verify your identity on Login.gov without a state-issued ID, which can be either a driver’s license or non-driver’s license state-issued ID card." + +msgid "core.pages.help.login_gov_verify.p[2]" +msgstr "If you do not have a phone plan that is in your name, Login.gov can send you the verification code by mail which takes approximately 3-5 days." + +msgid "core.pages.help.login_gov_verify.p[3]%(website)s" +msgstr "Verifying your identity makes sure the right person has access to the right information. We are using Login.gov’s simple and secure process to keep your sensitive information safe. To learn more about identity verification on Login.gov, please visit their Help Center." + msgid "core.pages.help.littlepay" msgstr "What is Littlepay?" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 1749dd414b..ba94151944 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -63,6 +63,21 @@ msgstr "Para obtener más información sobre Login.gov, visite " msgid "core.pages.help.login_gov.p[2][1]%(website)s" msgstr "o Login.gov: Centro de ayuda." +msgid "core.pages.help.login_gov_verify" +msgstr "TODO" + +msgid "core.pages.help.login_gov_verify.p[0]" +msgstr "TODO" + +msgid "core.pages.help.login_gov_verify.p[1]" +msgstr "TODO" + +msgid "core.pages.help.login_gov_verify.p[2]" +msgstr "TODO" + +msgid "core.pages.help.login_gov_verify.p[3]%(website)s" +msgstr "TODO" + msgid "core.pages.help.littlepay" msgstr "¿Qué es Littlepay?" From d44bf39f77ad3f26951aa290ff3c65cd66666429 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 16 Jun 2022 23:02:59 +0000 Subject: [PATCH 050/106] test(cypress/ui): add assertion for new Login.gov verify section --- tests/cypress/specs/ui/help.cy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cypress/specs/ui/help.cy.js b/tests/cypress/specs/ui/help.cy.js index 19cb4cfac8..2d5e5681e5 100644 --- a/tests/cypress/specs/ui/help.cy.js +++ b/tests/cypress/specs/ui/help.cy.js @@ -62,6 +62,7 @@ describe("Help page spec", () => { cy.get("h2").contains("What is Cal-ITP Benefits?"); cy.get("h2").contains("Payment options"); cy.get("h2").contains("What is Login.gov?"); + cy.get("h2").contains("How do I verify my identity on Login.gov?"); cy.get("h2").contains("What is Littlepay?"); cy.get("h2").contains("Questions?"); }); From 16e60073c4b2a297a6860dd2befa482025c636cf Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Fri, 17 Jun 2022 16:41:58 +0000 Subject: [PATCH 051/106] chore: add TODO and new English copy to the corresponding Spanish entry --- benefits/locale/es/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index c5e5595667..99974054d3 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -364,7 +364,7 @@ msgid "eligibility.pages.start.oauth.heading" msgstr "Inicia sesión en Login.gov" msgid "eligibility.pages.start.oauth.details" -msgstr "Login.gov es una forma segura de iniciar sesión en los servicios gubernamentales." +msgstr "TODO: Login.gov is a safe way to sign in to government services. Benefits uses Login.gov to verify your age. If you do not have an account already, you will be able to create one. You will also need to verify your identity, which will require these items:" msgid "eligibility.pages.start.oauth.link_text" msgstr "Conozca más sobre Login.gov" From 9d12c8235d3cd83eee9604e541c314bba2a4314d Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Thu, 16 Jun 2022 22:23:15 +0000 Subject: [PATCH 052/106] refactor(models): move claim/scope to AuthProvider update impacted views and ensure tests pass --- benefits/core/migrations/0001_initial.py | 6 +++--- benefits/core/models.py | 4 ++-- benefits/eligibility/verify.py | 2 +- benefits/oauth/views.py | 18 +++++++++++------- fixtures/02_authprovider.json | 4 +++- fixtures/03_eligibilityverifier.json | 2 -- tests/pytest/eligibility/test_verify.py | 4 ++-- tests/pytest/oauth/test_views.py | 10 ++++------ 8 files changed, 26 insertions(+), 24 deletions(-) diff --git a/benefits/core/migrations/0001_initial.py b/benefits/core/migrations/0001_initial.py index b5f5eb6eb9..4795647e93 100644 --- a/benefits/core/migrations/0001_initial.py +++ b/benefits/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2022-06-16 22:13 +# Generated by Django 3.2.13 on 2022-06-16 22:20 from django.db import migrations, models import django.db.models.deletion @@ -17,6 +17,8 @@ class Migration(migrations.Migration): ("id", models.AutoField(primary_key=True, serialize=False)), ("sign_in_button_label", models.TextField()), ("sign_out_button_label", models.TextField()), + ("scope", models.TextField(null=True)), + ("claim", models.TextField(null=True)), ], ), migrations.CreateModel( @@ -39,8 +41,6 @@ class Migration(migrations.Migration): ("jwe_cek_enc", models.TextField()), ("jwe_encryption_alg", models.TextField()), ("jws_signing_alg", models.TextField()), - ("auth_scope", models.TextField(null=True)), - ("auth_claim", models.TextField(null=True)), ("selection_label", models.TextField()), ("selection_label_description", models.TextField(null=True)), ("start_content_title", models.TextField()), diff --git a/benefits/core/models.py b/benefits/core/models.py index c012db0f77..b97c4bd398 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -29,6 +29,8 @@ class AuthProvider(models.Model): id = models.AutoField(primary_key=True) sign_in_button_label = models.TextField() sign_out_button_label = models.TextField() + scope = models.TextField(null=True) + claim = models.TextField(null=True) class EligibilityType(models.Model): @@ -73,8 +75,6 @@ class EligibilityVerifier(models.Model): # The JWS-compatible signing algorithm jws_signing_alg = models.TextField() auth_provider = models.ForeignKey(AuthProvider, on_delete=models.PROTECT, null=True) - auth_scope = models.TextField(null=True) - auth_claim = models.TextField(null=True) selection_label = models.TextField() selection_label_description = models.TextField(null=True) start_content_title = models.TextField() diff --git a/benefits/eligibility/verify.py b/benefits/eligibility/verify.py index 5567f130d7..8ecaab5761 100644 --- a/benefits/eligibility/verify.py +++ b/benefits/eligibility/verify.py @@ -33,7 +33,7 @@ def eligibility_from_api(verifier, form, agency): def eligibility_from_oauth(verifier, oauth_claim, agency): - if verifier.requires_authentication and verifier.auth_claim == oauth_claim: + if verifier.requires_authentication and verifier.auth_provider.claim == oauth_claim: return list(map(lambda t: t.name, agency.types_to_verify(verifier))) else: return [] diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index b82b7d6878..faaad03bf1 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -22,7 +22,7 @@ def login(request): """View implementing OIDC authorize_redirect.""" verifier = session.verifier(request) - oauth_client = client.instance(verifier.auth_scope) + oauth_client = client.instance(verifier.auth_provider.scope) route = reverse(ROUTE_AUTH) redirect_uri = redirects.generate_redirect_uri(request, route) @@ -49,16 +49,20 @@ def authorize(request): # We store the id_token in the user's session. This is the minimal amount of information needed later to log the user out. id_token = token["id_token"] + # We store the returned claim in case it can be used later in eligibility verification. verifier = session.verifier(request) - if verifier.auth_claim: + verifier_claim = verifier.auth_provider.claim + stored_claim = None + + if verifier_claim: userinfo = token.get("userinfo") - claim_value = token.get("userinfo").get(verifier.auth_claim) if userinfo else None - claim = verifier.auth_claim if claim_value else None - else: - claim = None + # the claim comes back in userinfo like { claim: True | False } + claim_flag = userinfo.get(verifier_claim) if userinfo else None + # if userinfo contains our claim and the flag is true, store the *claim* + stored_claim = verifier_claim if claim_flag else None - session.update(request, oauth_token=id_token, oauth_claim=claim) + session.update(request, oauth_token=id_token, oauth_claim=stored_claim) analytics.finished_sign_in(request) diff --git a/fixtures/02_authprovider.json b/fixtures/02_authprovider.json index 08c055b506..052abf054d 100644 --- a/fixtures/02_authprovider.json +++ b/fixtures/02_authprovider.json @@ -4,7 +4,9 @@ "pk": 1, "fields": { "sign_in_button_label": "eligibility.buttons.signin", - "sign_out_button_label": "eligibility.buttons.signout" + "sign_out_button_label": "eligibility.buttons.signout", + "scope": "", + "claim": "" } } ] diff --git a/fixtures/03_eligibilityverifier.json b/fixtures/03_eligibilityverifier.json index 00d182fa71..9c724ebc89 100644 --- a/fixtures/03_eligibilityverifier.json +++ b/fixtures/03_eligibilityverifier.json @@ -13,8 +13,6 @@ "jwe_encryption_alg": "RSA-OAEP", "jws_signing_alg": "RS256", "auth_provider": 1, - "auth_scope": "", - "auth_claim": "", "selection_label": "eligibility.pages.index.dmv.label", "selection_label_description": null, "start_content_title": "eligibility.pages.start.dmv.content_title", diff --git a/tests/pytest/eligibility/test_verify.py b/tests/pytest/eligibility/test_verify.py index 9da7f918c7..6486565d64 100644 --- a/tests/pytest/eligibility/test_verify.py +++ b/tests/pytest/eligibility/test_verify.py @@ -77,8 +77,8 @@ def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_auth def test_eligibility_from_oauth_auth_claim_match(mocked_session_verifier_auth_required, first_eligibility, first_agency): # mocked_session_verifier_auth_required 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.auth_claim = "claim" + verifier = mocked_session_verifier_auth_required.return_value + verifier.auth_provider.claim = "claim" verifier.eligibility_type = first_eligibility types = eligibility_from_oauth(verifier, "claim", first_agency) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 135a4af419..3a0196e166 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -36,11 +36,11 @@ def test_login_scope(mocked_oauth_client_instance, mocked_session_verifier_auth_ mocked_oauth_client.authorize_redirect.return_value = HttpResponse("authorize redirect") mocked_verifier = mocked_session_verifier_auth_required.return_value - mocked_verifier.auth_scope = "scope" + mocked_verifier.auth_provider.scope = "scope" login(app_request) - mocked_oauth_client_instance.assert_called_once_with(mocked_verifier.auth_scope) + mocked_oauth_client_instance.assert_called_once_with(mocked_verifier.auth_provider.scope) def test_authorize_fail(mocked_oauth_client_instance, app_request): @@ -75,10 +75,8 @@ def test_authorize_success(mocked_oauth_client_instance, mocked_analytics_module @pytest.mark.django_db def test_authorize_success_with_claim(mocked_session_verifier_auth_required, mocked_oauth_client_instance, app_request): - # mocked_session_verifier_auth_required is a fixture that mocks benefits.core.session.verifier(request) - # call it here, passing a None request, to get the return value from the mock - verifier = mocked_session_verifier_auth_required(None) - verifier.auth_claim = "claim" + verifier = mocked_session_verifier_auth_required.return_value + verifier.auth_provider.claim = "claim" mocked_oauth_client = mocked_oauth_client_instance.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} From b48aec1c4fe71fa0fc850ecf72a5d542898824fc Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 05:04:08 +0000 Subject: [PATCH 053/106] feat(models): add OAuth config fields to AuthProvider --- benefits/core/migrations/0001_initial.py | 5 ++++- benefits/core/models.py | 3 +++ fixtures/02_authprovider.json | 7 +++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/benefits/core/migrations/0001_initial.py b/benefits/core/migrations/0001_initial.py index 4795647e93..290c336b0a 100644 --- a/benefits/core/migrations/0001_initial.py +++ b/benefits/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2022-06-16 22:20 +# Generated by Django 3.2.13 on 2022-06-17 05:03 from django.db import migrations, models import django.db.models.deletion @@ -17,6 +17,9 @@ class Migration(migrations.Migration): ("id", models.AutoField(primary_key=True, serialize=False)), ("sign_in_button_label", models.TextField()), ("sign_out_button_label", models.TextField()), + ("client_name", models.TextField()), + ("client_id", models.TextField()), + ("authority", models.TextField()), ("scope", models.TextField(null=True)), ("claim", models.TextField(null=True)), ], diff --git a/benefits/core/models.py b/benefits/core/models.py index b97c4bd398..c87bc85cf0 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -29,6 +29,9 @@ class AuthProvider(models.Model): id = models.AutoField(primary_key=True) sign_in_button_label = models.TextField() sign_out_button_label = models.TextField() + client_name = models.TextField() + client_id = models.TextField() + authority = models.TextField() scope = models.TextField(null=True) claim = models.TextField(null=True) diff --git a/fixtures/02_authprovider.json b/fixtures/02_authprovider.json index 052abf054d..488a4ba0d4 100644 --- a/fixtures/02_authprovider.json +++ b/fixtures/02_authprovider.json @@ -5,8 +5,11 @@ "fields": { "sign_in_button_label": "eligibility.buttons.signin", "sign_out_button_label": "eligibility.buttons.signout", - "scope": "", - "claim": "" + "client_name": "benefits-oauth-client-name", + "client_id": "benefits-oauth-client-id", + "authority": "https://example.com", + "scope": "verify:type1", + "claim": "type1" } } ] From ce9494d45e8086911d0bc69393a43d78d362b800 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 04:54:05 +0000 Subject: [PATCH 054/106] refactor(oauth): register each AuthProvider use app.ready(), Django's hook to tell the app that the environment is initialized adapted from https://stackoverflow.com/a/64174413/453168 --- benefits/oauth/__init__.py | 5 ++ benefits/oauth/client.py | 54 +++++++++++++------- tests/pytest/oauth/conftest.py | 11 ++++ tests/pytest/oauth/test_app.py | 12 +++++ tests/pytest/oauth/test_client.py | 85 ++++++++++++++++--------------- 5 files changed, 109 insertions(+), 58 deletions(-) create mode 100644 tests/pytest/oauth/test_app.py diff --git a/benefits/oauth/__init__.py b/benefits/oauth/__init__.py index d56d39df5f..b01f0a9c39 100644 --- a/benefits/oauth/__init__.py +++ b/benefits/oauth/__init__.py @@ -8,3 +8,8 @@ class OAuthAppConfig(AppConfig): name = "benefits.oauth" label = "oauth" verbose_name = "Benefits OAuth" + + def ready(self): + from .client import oauth, register_providers + + register_providers(oauth) diff --git a/benefits/oauth/client.py b/benefits/oauth/client.py index f3d4fb3407..7bc19ce3a9 100644 --- a/benefits/oauth/client.py +++ b/benefits/oauth/client.py @@ -1,36 +1,54 @@ -import logging +""" +The oauth application: helpers for working with OAuth clients. +""" -from django.conf import settings +import logging from authlib.integrations.django_client import OAuth +from benefits.core.models import AuthProvider + logger = logging.getLogger(__name__) +oauth = OAuth() + + +def _client_kwargs(scope=None): + """ + Generate the OpenID Connect client_kwargs, with optional extra scope(s). -_OPENID_SCOPE = "openid" + `scope` should be a space-separated list of scopes to add. + """ + scopes = ["openid", scope] if scope else ["openid"] + return {"code_challenge_method": "S256", "scope": " ".join(scopes)} -def instance(scope=None): +def _server_metadata_url(authority): """ - Get an OAuth client instance using the OAUTH_CLIENT_NAME setting. + Generate the OpenID Connect server_metadata_url for an OAuth authority server. - Optionally configure with a (space-separated) scope. + `authority` should be a fully qualified HTTPS domain name, e.g. https://example.com. """ - if settings.OAUTH_CLIENT_NAME: - logger.debug(f"Using OAuth client configuration: {settings.OAUTH_CLIENT_NAME}") + return f"{authority}/.well-known/openid-configuration" - oauth = OAuth() - oauth.register(settings.OAUTH_CLIENT_NAME) - client = oauth.create_client(settings.OAUTH_CLIENT_NAME) - else: - raise Exception("OAUTH_CLIENT_NAME is not configured") - scopes = [_OPENID_SCOPE] +def register_providers(oauth_registry): + """ + Register OAuth clients into the given registry, using configuration from AuthProvider models. + + Adapted from https://stackoverflow.com/a/64174413. + """ + logger.info("Registering OAuth clients") - if scope: - scopes.append(scope) + providers = AuthProvider.objects.all() - client.client_kwargs["scope"] = " ".join(scopes) + for provider in providers: + logger.debug(f"Registering OAuth client: {provider.client_name}") - return 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), + ) diff --git a/tests/pytest/oauth/conftest.py b/tests/pytest/oauth/conftest.py index beefe93fcd..a2a81ce482 100644 --- a/tests/pytest/oauth/conftest.py +++ b/tests/pytest/oauth/conftest.py @@ -1,8 +1,19 @@ +from authlib.integrations.django_client import OAuth from authlib.integrations.django_client.apps import DjangoOAuth2App import pytest +@pytest.fixture +def mocked_oauth_registry(mocker): + return mocker.Mock(spec=OAuth) + + +@pytest.fixture +def mocked_oauth_client_registry(mocker, mocked_oauth_registry): + return mocker.patch("benefits.oauth.client.oauth", mocked_oauth_registry) + + @pytest.fixture def mocked_oauth_client(mocker): return mocker.Mock(spec=DjangoOAuth2App) diff --git a/tests/pytest/oauth/test_app.py b/tests/pytest/oauth/test_app.py new file mode 100644 index 0000000000..30c665d5f9 --- /dev/null +++ b/tests/pytest/oauth/test_app.py @@ -0,0 +1,12 @@ +import benefits +from benefits.oauth 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) diff --git a/tests/pytest/oauth/test_client.py b/tests/pytest/oauth/test_client.py index 03b805e0b1..309a35746e 100644 --- a/tests/pytest/oauth/test_client.py +++ b/tests/pytest/oauth/test_client.py @@ -1,52 +1,57 @@ import pytest -import benefits.oauth.client as client +from benefits.core.models import AuthProvider +from benefits.oauth.client import _client_kwargs, _server_metadata_url, register_providers -@pytest.fixture -def no_oauth_client_name(mocker): - return mocker.patch("benefits.oauth.client.settings.OAUTH_CLIENT_NAME", None) +def test_client_kwargs(): + kwargs = _client_kwargs() + assert kwargs["code_challenge_method"] == "S256" + assert "openid" in kwargs["scope"] -@pytest.mark.django_db -@pytest.mark.usefixtures("no_oauth_client_name") -def test_instance_no_oauth_client_name(): - with pytest.raises(Exception, match=r"OAUTH_CLIENT_NAME"): - client.instance() +def test_client_kwargs_scope(): + kwargs = _client_kwargs("scope1") + + assert kwargs["code_challenge_method"] == "S256" + assert "openid" in kwargs["scope"] + assert "scope1" in kwargs["scope"] -@pytest.mark.django_db -def test_instance(): - oauth_client = client.instance() - oauth_client2 = client.instance() - assert oauth_client - assert oauth_client2 - assert oauth_client is not oauth_client2 +def test_server_metadata_url(): + url = _server_metadata_url("https://example.com") + + assert url.startswith("https://example.com") + assert url.endswith("openid-configuration") @pytest.mark.django_db -def test_instance_scope(): - scope1 = "scope1" - oauth_client = client.instance(scope1) - client_scope = oauth_client.client_kwargs["scope"] - - assert scope1 in client_scope - assert client._OPENID_SCOPE in client_scope - - scope2 = "scope2" - oauth_client2 = client.instance(scope2) - client_scope = oauth_client2.client_kwargs["scope"] - - assert scope1 not in client_scope - assert scope2 in client_scope - assert client._OPENID_SCOPE in client_scope - - scope3 = " ".join((scope1, scope2)) - oauth_client3 = client.instance(scope3) - client_scope = oauth_client3.client_kwargs["scope"] - - assert scope1 in client_scope - assert scope2 in client_scope - assert scope3 in client_scope - assert client._OPENID_SCOPE in client_scope +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 + + mocker.patch("benefits.oauth.client._client_kwargs", return_value={"client": "kwargs"}) + mocker.patch("benefits.oauth.client._server_metadata_url", return_value="https://metadata.url") + + register_providers(mocked_oauth_registry) + + mocked_client_provider.objects.all.assert_called_once() + + for provider in mock_providers: + i = mock_providers.index(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"}, + ) From 4dcdef0105140c70a5a8286ffff2cdb23c1ca165 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 05:43:11 +0000 Subject: [PATCH 055/106] refactor(oauth): pass client to deauthorize_redirect --- benefits/oauth/redirects.py | 6 +----- tests/pytest/oauth/test_redirects.py | 5 ++--- tests/pytest/oauth/test_views.py | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/benefits/oauth/redirects.py b/benefits/oauth/redirects.py index fafa26451f..52753a07b7 100644 --- a/benefits/oauth/redirects.py +++ b/benefits/oauth/redirects.py @@ -1,18 +1,14 @@ from django.shortcuts import redirect from django.utils.http import urlencode -from . import client - -def deauthorize_redirect(token, redirect_uri): +def deauthorize_redirect(oauth_client, token, redirect_uri): """Helper implements OIDC signout via the `end_session_endpoint`.""" # Authlib has not yet implemented `end_session_endpoint` as the OIDC Session Management 1.0 spec is still in draft # See https://github.com/lepture/authlib/issues/331#issuecomment-827295954 for more # # The implementation here was adapted from the same ticket: https://github.com/lepture/authlib/issues/331#issue-838728145 - oauth_client = client.instance() - metadata = oauth_client.load_server_metadata() end_session_endpoint = metadata.get("end_session_endpoint") diff --git a/tests/pytest/oauth/test_redirects.py b/tests/pytest/oauth/test_redirects.py index d35b2737ab..227e13a0c3 100644 --- a/tests/pytest/oauth/test_redirects.py +++ b/tests/pytest/oauth/test_redirects.py @@ -1,11 +1,10 @@ from benefits.oauth.redirects import deauthorize_redirect, generate_redirect_uri -def test_deauthorize_redirect(mocked_oauth_client_instance): - mocked_oauth_client = mocked_oauth_client_instance.return_value +def test_deauthorize_redirect(mocked_oauth_client): mocked_oauth_client.load_server_metadata.return_value = {"end_session_endpoint": "https://server/endsession"} - result = deauthorize_redirect("token", "https://localhost/redirect_uri") + result = deauthorize_redirect(mocked_oauth_client, "token", "https://localhost/redirect_uri") mocked_oauth_client.load_server_metadata.assert_called() assert result.status_code == 302 diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 3a0196e166..1f32c6ad01 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -106,7 +106,7 @@ def test_authorize_success_without_claim(mocked_session_verifier_auth_required, def test_logout(mocker, mocked_analytics_module, app_request): - # logout internally calls _deauthorize_redirect + # logout internally calls deauthorize_redirect # this mocks that function and a success response # and returns a spy object we can use to validate calls message = "logout successful" From d8ec21248bddf4762bf1c09418733a07d5236fe1 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 05:50:28 +0000 Subject: [PATCH 056/106] refactor(oauth): create client in views --- benefits/oauth/views.py | 16 ++++++---- tests/pytest/oauth/conftest.py | 4 +-- tests/pytest/oauth/test_views.py | 51 +++++++++++++------------------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index faaad03bf1..c1c1b4c489 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -6,7 +6,8 @@ from benefits.core import session from benefits.core.middleware import VerifierSessionRequired -from . import analytics, client, redirects +from . import analytics, redirects +from .client import oauth logger = logging.getLogger(__name__) @@ -22,7 +23,7 @@ def login(request): """View implementing OIDC authorize_redirect.""" verifier = session.verifier(request) - oauth_client = client.instance(verifier.auth_provider.scope) + oauth_client = oauth.create_client(verifier.auth_provider.client_name) route = reverse(ROUTE_AUTH) redirect_uri = redirects.generate_redirect_uri(request, route) @@ -34,9 +35,11 @@ def login(request): return oauth_client.authorize_redirect(request, redirect_uri) +@decorator_from_middleware(VerifierSessionRequired) def authorize(request): """View implementing OIDC token authorization.""" - oauth_client = client.instance() + verifier = session.verifier(request) + oauth_client = oauth.create_client(verifier.auth_provider.client_name) logger.debug("Attempting to authorize OAuth access token") token = oauth_client.authorize_access_token(request) @@ -51,7 +54,6 @@ def authorize(request): id_token = token["id_token"] # We store the returned claim in case it can be used later in eligibility verification. - verifier = session.verifier(request) verifier_claim = verifier.auth_provider.claim stored_claim = None @@ -69,8 +71,12 @@ def authorize(request): return redirect(ROUTE_CONFIRM) +@decorator_from_middleware(VerifierSessionRequired) def logout(request): """View implementing OIDC and application sign out.""" + verifier = session.verifier(request) + oauth_client = oauth.create_client(verifier.auth_provider.client_name) + analytics.started_sign_out(request) # overwrite the oauth session token, the user is signed out of the app @@ -84,7 +90,7 @@ def logout(request): # send the user through the end_session_endpoint, redirecting back to # the post_logout route - return redirects.deauthorize_redirect(token, redirect_uri) + return redirects.deauthorize_redirect(oauth_client, token, redirect_uri) def post_logout(request): diff --git a/tests/pytest/oauth/conftest.py b/tests/pytest/oauth/conftest.py index a2a81ce482..ddc6d7be30 100644 --- a/tests/pytest/oauth/conftest.py +++ b/tests/pytest/oauth/conftest.py @@ -20,5 +20,5 @@ def mocked_oauth_client(mocker): @pytest.fixture -def mocked_oauth_client_instance(mocker, mocked_oauth_client): - return mocker.patch("benefits.oauth.client.instance", return_value=mocked_oauth_client) +def mocked_oauth_create_client(mocker, mocked_oauth_client): + return mocker.patch("benefits.oauth.client.oauth.create_client", return_value=mocked_oauth_client) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 1f32c6ad01..894cb65511 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -16,35 +16,25 @@ def mocked_analytics_module(mocked_analytics_module): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") -def test_login(mocked_oauth_client_instance, mocked_analytics_module, app_request): +def test_login(mocked_oauth_create_client, mocked_session_verifier_auth_required, mocked_analytics_module, app_request): assert not session.logged_in(app_request) - mocked_oauth_client = mocked_oauth_client_instance.return_value + mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_redirect.return_value = HttpResponse("authorize redirect") login(app_request) + mocked_verifier = mocked_session_verifier_auth_required.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() assert not session.logged_in(app_request) @pytest.mark.django_db -def test_login_scope(mocked_oauth_client_instance, mocked_session_verifier_auth_required, app_request): - mocked_oauth_client = mocked_oauth_client_instance.return_value - mocked_oauth_client.authorize_redirect.return_value = HttpResponse("authorize redirect") - - mocked_verifier = mocked_session_verifier_auth_required.return_value - mocked_verifier.auth_provider.scope = "scope" - - login(app_request) - - mocked_oauth_client_instance.assert_called_once_with(mocked_verifier.auth_provider.scope) - - -def test_authorize_fail(mocked_oauth_client_instance, app_request): - mocked_oauth_client = mocked_oauth_client_instance.return_value +@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +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 assert not session.logged_in(app_request) @@ -59,8 +49,8 @@ def test_authorize_fail(mocked_oauth_client_instance, app_request): @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_auth_required") -def test_authorize_success(mocked_oauth_client_instance, mocked_analytics_module, app_request): - mocked_oauth_client = mocked_oauth_client_instance.return_value +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"} result = authorize(app_request) @@ -74,10 +64,10 @@ def test_authorize_success(mocked_oauth_client_instance, mocked_analytics_module @pytest.mark.django_db -def test_authorize_success_with_claim(mocked_session_verifier_auth_required, mocked_oauth_client_instance, app_request): +def test_authorize_success_with_claim(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_client_instance.return_value + mocked_oauth_client = mocked_oauth_create_client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} result = authorize(app_request) @@ -89,12 +79,10 @@ def test_authorize_success_with_claim(mocked_session_verifier_auth_required, moc @pytest.mark.django_db -def test_authorize_success_without_claim(mocked_session_verifier_auth_required, mocked_oauth_client_instance, app_request): - # mocked_session_verifier_auth_required is a fixture that mocks benefits.core.session.verifier(request) - # call it here, passing a None request, to get the return value from the mock - verifier = mocked_session_verifier_auth_required(None) - verifier.auth_claim = "" - mocked_oauth_client = mocked_oauth_client_instance.return_value +def test_authorize_success_without_claim(mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request): + verifier = mocked_session_verifier_auth_required.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"}} result = authorize(app_request) @@ -105,12 +93,15 @@ def test_authorize_success_without_claim(mocked_session_verifier_auth_required, assert result.url == reverse(ROUTE_CONFIRM) -def test_logout(mocker, mocked_analytics_module, app_request): +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +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 # and returns a spy object we can use to validate calls message = "logout successful" - spy = mocker.patch("benefits.oauth.views.redirects.deauthorize_redirect", return_value=HttpResponse(message)) + mocked_oauth_client = mocked_oauth_create_client.return_value + mocked_redirect = mocker.patch("benefits.oauth.views.redirects.deauthorize_redirect", return_value=HttpResponse(message)) token = "token" session.update(app_request, oauth_token=token) @@ -118,7 +109,7 @@ def test_logout(mocker, mocked_analytics_module, app_request): result = logout(app_request) - spy.assert_called_with(token, "https://testserver/oauth/post_logout") + mocked_redirect.assert_called_with(mocked_oauth_client, token, "https://testserver/oauth/post_logout") mocked_analytics_module.started_sign_out.assert_called_once() assert result.status_code == 200 assert message in str(result.content) From 4af5f5b4f291cbfd586f1d6a4cf9084b08baa3db Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 06:48:59 +0000 Subject: [PATCH 057/106] docs: update OAuth configuration section --- docs/configuration/oauth.md | 66 +++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/docs/configuration/oauth.md b/docs/configuration/oauth.md index fcdb3d6c9b..1e5596df9b 100644 --- a/docs/configuration/oauth.md +++ b/docs/configuration/oauth.md @@ -14,53 +14,49 @@ This section describes the related settings and how to configure the application Benefits uses the open-source [Authlib](https://authlib.org/) for OAuth and OIDC client implementation. See the Authlib docs for more details about what features are available. Specifically, from Authlib we: -1. Register an OAuth `client` using the configured [Django settings](#django-settings) +1. Create an OAuth client using the [Django configuration](#django-configuration) 1. Call `client.authorize_redirect()` to send the user into the OIDC server's authentication flow, with our authorization callback URL 1. Upon the user returning from the OIDC Server with an access token, call `client.authorize_access_token()` to get a validated id token from the OIDC server -## Environment variables +## Django configuration -!!! warning +OAuth settings are configured as instances of the [`AuthProvider` model](../development/models-migrations.md). - The following environment variables are all required for OAuth configuration +The [sample fixtures](./fixtures.md) contain example `AuthProvider` configurations; create new entries to integrate with +real Open ID Connect providers. -### `DJANGO_OAUTH_AUTHORITY` +Authlib's [Django OpenID Connect Client example][authlib-django-oidc] for Google could be adapted into a Benefits fixture, +(with extraneous fields omitted) like: -Base address of the OAuth/OIDC server to use for authorization. +```json +{ + "model": "core.authprovider", + "pk": 1, + "fields": { + "client_name": "google", + "client_id": "google-client-id", + "authority": "https://accounts.google.com", + "scope": "profile email", + } +} +``` -### `DJANGO_OAUTH_CLIENT_ID` +## Django usage -This application's client ID, as registered with the OAuth/OIDC server. +The [`benefits.oauth.client`][oauth-client] module defines helpers for registering OAuth clients, and creating instances for +use in e.g. views. -### `DJANGO_OAUTH_CLIENT_NAME` +* `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 -The internal label of the OAuth client within this application. +Providers are registered into this instance once in the [`OAuthAppConfig.ready()`][oauth-app-ready] function at application +startup. -See the [`OAUTH_CLIENT_NAME`](#oauth_client_name) setting for more. +Consumers call `oauth.create_client(client_name)` with the name of a previously registered client to obtain an Authlib client +instance. -## Django settings - -There are a few relevant settings defined in [`benefits/settings.py`][benefits-settings] related to OAuth. - -### `OAUTH_CLIENT_NAME` - -A `str` defining the application's internal label for the OAuth client that is used. - -The app uses the value of this variable to further configure the OAuth feature, or skip that configuration for empty or `None`. - -The value is initialized from the [`DJANGO_OAUTH_CLIENT_NAME`](#django_oauth_client_name) environment variable. - -### `AUTHLIB_OAUTH_CLIENTS` - -!!! tldr "Authlib docs" - - Read more about [configuring Authlib for Django](https://docs.authlib.org/en/latest/client/django.html#configuration) - -A `dict` of OAuth client configurations this app may use. - -By default, contains a single entry, keyed by [`OAUTH_CLIENT_NAME`](#oauth_client_name) and using the other -[`DJANGO_OAUTH_*` environment variables](#environment-variables) to populate the client's settings. - -[benefits-settings]: https://github.com/cal-itp/benefits/blob/dev/benefits/settings.py +[authlib-django-oidc]: https://docs.authlib.org/en/latest/client/django.html#django-openid-connect-client +[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 ab798df02b289d06bfe6c95084ff3e7a025f8a34 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 07:04:58 +0000 Subject: [PATCH 058/106] chore(settings): remove OAuth configuration --- benefits/eligibility/views.py | 4 ---- benefits/enrollment/views.py | 4 ---- benefits/oauth/analytics.py | 4 ++-- benefits/settings.py | 15 --------------- benefits/urls.py | 9 ++------- tests/pytest/eligibility/test_views.py | 11 ----------- tests/pytest/enrollment/test_views.py | 11 ----------- 7 files changed, 4 insertions(+), 54 deletions(-) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 1f61c22468..85d757bf63 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -1,7 +1,6 @@ """ The eligibility application: view definitions for the eligibility verification flow. """ -from django.conf import settings from django.contrib import messages from django.shortcuts import redirect from django.template.response import TemplateResponse @@ -98,9 +97,6 @@ def start(request): ] if verifier.requires_authentication: - if settings.OAUTH_CLIENT_NAME is None: - raise Exception("EligibilityVerifier requires authentication, but OAUTH_CLIENT_NAME is None") - oauth_help_link = f"{reverse(ROUTE_HELP)}#login-gov" oauth_help_more_link = f"{reverse(ROUTE_HELP)}#login-gov-verify-items" diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index f4642ca740..4d1ac01b3e 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -3,7 +3,6 @@ """ import logging -from django.conf import settings from django.http import JsonResponse from django.template.response import TemplateResponse from django.urls import reverse @@ -151,9 +150,6 @@ def success(request): ) if verifier.requires_authentication: - if settings.OAUTH_CLIENT_NAME is None: - raise Exception("EligibilityVerifier requires authentication, but OAUTH_CLIENT_NAME is None") - if session.logged_in(request): page.buttons = [viewmodels.Button.logout()] page.classes = ["logged-in"] diff --git a/benefits/oauth/analytics.py b/benefits/oauth/analytics.py index 10dceebff3..74bb8af501 100644 --- a/benefits/oauth/analytics.py +++ b/benefits/oauth/analytics.py @@ -2,7 +2,6 @@ The oauth application: analytics implementation. """ from benefits.core import analytics as core, session -from django.conf import settings class OAuthEvent(core.Event): @@ -10,7 +9,8 @@ class OAuthEvent(core.Event): def __init__(self, request, event_type): super().__init__(request, event_type) - self.update_event_properties(auth_provider=settings.OAUTH_CLIENT_NAME) + verifier = session.verifier(request) + self.update_event_properties(auth_provider=verifier.auth_provider.client_name) class StartedSignInEvent(OAuthEvent): diff --git a/benefits/settings.py b/benefits/settings.py index b11948e282..402c2aa155 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -166,21 +166,6 @@ def _filter_empty(ls): ] ) -# OAuth configuration - -OAUTH_AUTHORITY = os.environ.get("DJANGO_OAUTH_AUTHORITY", "http://example.com") -OAUTH_CLIENT_NAME = os.environ.get("DJANGO_OAUTH_CLIENT_NAME", "benefits-oauth-client-name") -OAUTH_CLIENT_ID = os.environ.get("DJANGO_OAUTH_CLIENT_ID", "benefits-oauth-client-id") - -if OAUTH_CLIENT_NAME: - AUTHLIB_OAUTH_CLIENTS = { - OAUTH_CLIENT_NAME: { - "client_id": OAUTH_CLIENT_ID, - "server_metadata_url": f"{OAUTH_AUTHORITY}/.well-known/openid-configuration", - "client_kwargs": {"code_challenge_method": "S256"}, - } - } - # Internationalization LANGUAGE_CODE = "en" diff --git a/benefits/urls.py b/benefits/urls.py index d4f6ddfec3..a8030ddee7 100644 --- a/benefits/urls.py +++ b/benefits/urls.py @@ -2,7 +2,7 @@ benefits URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/3.1/topics/http/urls/ + https://docs.djangoproject.com/en/3.2/topics/http/urls/ """ import logging @@ -21,6 +21,7 @@ path("eligibility/", include("benefits.eligibility.urls")), path("enrollment/", include("benefits.enrollment.urls")), path("i18n/", include("django.conf.urls.i18n")), + path("oauth/", include("benefits.oauth.urls")), ] if settings.ADMIN: @@ -30,9 +31,3 @@ urlpatterns.append(path("admin/", admin.site.urls)) else: logger.debug("Skip url registrations for admin") - -if settings.OAUTH_CLIENT_NAME: - logger.info("Register oauth urls") - urlpatterns.append(path("oauth/", include("benefits.oauth.urls"))) -else: - logger.debug("Skip url registrations for oauth") diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index eeab3d90e3..da860dabbe 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -120,17 +120,6 @@ def test_index_post_valid_form(client, first_verifier, mocked_session_update): assert mocked_session_update.call_args.kwargs["verifier"] == first_verifier -@pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_agency", "mocked_verifier_form", "mocked_session_verifier_auth_required") -def test_start_verifier_auth_required_no_oauth_client(mocker, client): - mock_settings = mocker.patch("benefits.eligibility.views.settings") - mock_settings.OAUTH_CLIENT_NAME = None - - path = reverse(ROUTE_START) - with pytest.raises(Exception, match=r"OAUTH_CLIENT_NAME"): - client.get(path) - - @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_verifier_form", "mocked_session_verifier_auth_required") def test_start_verifier_auth_required_logged_in(mocker, client): diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 09aa1ce200..582f526b1c 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -169,17 +169,6 @@ def test_success_no_verifier(client): client.get(path) -@pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_auth_required") -def test_success_no_oauth_client(mocker, client): - mock_settings = mocker.patch("benefits.enrollment.views.settings") - mock_settings.OAUTH_CLIENT_NAME = None - - path = reverse(ROUTE_SUCCESS) - with pytest.raises(Exception, match=r"OAUTH_CLIENT_NAME"): - client.get(path) - - @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_auth_required") def test_success_authentication_logged_in(mocker, client): From 9537c593f7e2f4afd23796374bdf1fdf977944ca Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 07:36:05 +0000 Subject: [PATCH 059/106] fix(view): parse claim result to bool --- benefits/oauth/views.py | 4 ++-- tests/pytest/oauth/test_views.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index c1c1b4c489..f1ac401b9d 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -59,8 +59,8 @@ def authorize(request): if verifier_claim: userinfo = token.get("userinfo") - # the claim comes back in userinfo like { claim: True | False } - claim_flag = userinfo.get(verifier_claim) if userinfo else None + # the claim comes back in userinfo like { "claim": "True" | "False" } + claim_flag = (userinfo.get(verifier_claim) if userinfo else "false").lower() == "true" # if userinfo contains our claim and the flag is true, store the *claim* stored_claim = verifier_claim if claim_flag else None diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 894cb65511..551f1b6d6f 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -64,11 +64,15 @@ def test_authorize_success(mocked_oauth_create_client, mocked_analytics_module, @pytest.mark.django_db -def test_authorize_success_with_claim(mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request): +@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 +): 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": "True"}} + mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": flag}} result = authorize(app_request) @@ -79,6 +83,26 @@ def test_authorize_success_with_claim(mocked_session_verifier_auth_required, moc @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 +): + 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}} + + 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) + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_analytics_module") def test_authorize_success_without_claim(mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request): verifier = mocked_session_verifier_auth_required.return_value verifier.auth_provider.claim = "" From 027d3aebcff0dc8c9f84215e0e1b0a855c85981c Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 15:35:20 +0000 Subject: [PATCH 060/106] fix(apps): define in apps.py for proper registration Django's convention is to look in the apps.py files for app configs the OAuthAppConfig.ready() function was not being called from __init__.py --- benefits/core/__init__.py | 10 ---------- benefits/core/apps.py | 10 ++++++++++ benefits/eligibility/__init__.py | 10 ---------- benefits/eligibility/apps.py | 10 ++++++++++ benefits/enrollment/__init__.py | 10 ---------- benefits/enrollment/apps.py | 10 ++++++++++ benefits/oauth/__init__.py | 15 --------------- benefits/oauth/apps.py | 17 +++++++++++++++++ tests/pytest/oauth/test_app.py | 2 +- 9 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 benefits/core/apps.py create mode 100644 benefits/eligibility/apps.py create mode 100644 benefits/enrollment/apps.py create mode 100644 benefits/oauth/apps.py diff --git a/benefits/core/__init__.py b/benefits/core/__init__.py index 4f8b9243db..e69de29bb2 100644 --- a/benefits/core/__init__.py +++ b/benefits/core/__init__.py @@ -1,10 +0,0 @@ -""" -The core application: Houses base templates and reusable models and components. -""" -from django.apps import AppConfig - - -class CoreAppConfig(AppConfig): - name = "benefits.core" - label = "core" - verbose_name = "Core" diff --git a/benefits/core/apps.py b/benefits/core/apps.py new file mode 100644 index 0000000000..4f8b9243db --- /dev/null +++ b/benefits/core/apps.py @@ -0,0 +1,10 @@ +""" +The core application: Houses base templates and reusable models and components. +""" +from django.apps import AppConfig + + +class CoreAppConfig(AppConfig): + name = "benefits.core" + label = "core" + verbose_name = "Core" diff --git a/benefits/eligibility/__init__.py b/benefits/eligibility/__init__.py index e7d07247b4..e69de29bb2 100644 --- a/benefits/eligibility/__init__.py +++ b/benefits/eligibility/__init__.py @@ -1,10 +0,0 @@ -""" -The eligibility application: Verifies eligibility for benefits. -""" -from django.apps import AppConfig - - -class EligibilityAppConfig(AppConfig): - name = "benefits.eligibility" - label = "eligibility" - verbose_name = "Eligibility Verification" diff --git a/benefits/eligibility/apps.py b/benefits/eligibility/apps.py new file mode 100644 index 0000000000..e7d07247b4 --- /dev/null +++ b/benefits/eligibility/apps.py @@ -0,0 +1,10 @@ +""" +The eligibility application: Verifies eligibility for benefits. +""" +from django.apps import AppConfig + + +class EligibilityAppConfig(AppConfig): + name = "benefits.eligibility" + label = "eligibility" + verbose_name = "Eligibility Verification" diff --git a/benefits/enrollment/__init__.py b/benefits/enrollment/__init__.py index 79267ad47c..e69de29bb2 100644 --- a/benefits/enrollment/__init__.py +++ b/benefits/enrollment/__init__.py @@ -1,10 +0,0 @@ -""" -The enrollment application: Allows user to enroll payment device for benefits. -""" -from django.apps import AppConfig - - -class EnrollmentAppConfig(AppConfig): - name = "benefits.enrollment" - label = "enrollment" - verbose_name = "Benefits Enrollment" diff --git a/benefits/enrollment/apps.py b/benefits/enrollment/apps.py new file mode 100644 index 0000000000..79267ad47c --- /dev/null +++ b/benefits/enrollment/apps.py @@ -0,0 +1,10 @@ +""" +The enrollment application: Allows user to enroll payment device for benefits. +""" +from django.apps import AppConfig + + +class EnrollmentAppConfig(AppConfig): + name = "benefits.enrollment" + label = "enrollment" + verbose_name = "Benefits Enrollment" diff --git a/benefits/oauth/__init__.py b/benefits/oauth/__init__.py index b01f0a9c39..e69de29bb2 100644 --- a/benefits/oauth/__init__.py +++ b/benefits/oauth/__init__.py @@ -1,15 +0,0 @@ -""" -The oauth application: Implements OAuth-based authentication -""" -from django.apps import AppConfig - - -class OAuthAppConfig(AppConfig): - name = "benefits.oauth" - label = "oauth" - verbose_name = "Benefits OAuth" - - def ready(self): - from .client import oauth, register_providers - - register_providers(oauth) diff --git a/benefits/oauth/apps.py b/benefits/oauth/apps.py new file mode 100644 index 0000000000..c6e15223a4 --- /dev/null +++ b/benefits/oauth/apps.py @@ -0,0 +1,17 @@ +""" +The oauth application: Implements OAuth-based authentication +""" +from django.apps import AppConfig + + +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 + + register_providers(oauth) diff --git a/tests/pytest/oauth/test_app.py b/tests/pytest/oauth/test_app.py index 30c665d5f9..36ae2dbef7 100644 --- a/tests/pytest/oauth/test_app.py +++ b/tests/pytest/oauth/test_app.py @@ -1,5 +1,5 @@ import benefits -from benefits.oauth import OAuthAppConfig +from benefits.oauth.apps import OAuthAppConfig def test_ready_registers_clients(mocker): From e3cd32a4224800de263e1923d1e77657e2b92d15 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 17:57:45 +0000 Subject: [PATCH 061/106] fix(oauth): wrap registration in try/catch AppConfig.ready() is called multiple times during app startup, in some cases before the database is fully built/populated try/catch around registration ensures things like the bin/init.sh script can still run --- benefits/oauth/apps.py | 7 ++++++- tests/pytest/oauth/test_app.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/benefits/oauth/apps.py b/benefits/oauth/apps.py index c6e15223a4..34936b0584 100644 --- a/benefits/oauth/apps.py +++ b/benefits/oauth/apps.py @@ -14,4 +14,9 @@ def ready(self): # Django has loaded all the apps and models from .client import oauth, register_providers - register_providers(oauth) + # 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/tests/pytest/oauth/test_app.py b/tests/pytest/oauth/test_app.py index 36ae2dbef7..93b1aaeb62 100644 --- a/tests/pytest/oauth/test_app.py +++ b/tests/pytest/oauth/test_app.py @@ -10,3 +10,14 @@ def test_ready_registers_clients(mocker): 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 From f9f635a1c2aa96972a5090aff1ed3cb0e2a8b43d Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 20:16:20 +0000 Subject: [PATCH 062/106] chore(oauth): raise exception for unregistered clients --- benefits/oauth/views.py | 9 +++++++++ tests/pytest/oauth/test_views.py | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index f1ac401b9d..85e3fb353d 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -25,6 +25,9 @@ def login(request): verifier = session.verifier(request) oauth_client = oauth.create_client(verifier.auth_provider.client_name) + if not oauth_client: + raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") + route = reverse(ROUTE_AUTH) redirect_uri = redirects.generate_redirect_uri(request, route) @@ -41,6 +44,9 @@ def authorize(request): verifier = session.verifier(request) oauth_client = oauth.create_client(verifier.auth_provider.client_name) + if not oauth_client: + raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") + logger.debug("Attempting to authorize OAuth access token") token = oauth_client.authorize_access_token(request) @@ -77,6 +83,9 @@ def logout(request): verifier = session.verifier(request) oauth_client = oauth.create_client(verifier.auth_provider.client_name) + if not oauth_client: + raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") + analytics.started_sign_out(request) # overwrite the oauth session token, the user is signed out of the app diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index 551f1b6d6f..f8e09ebd08 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -15,6 +15,15 @@ def mocked_analytics_module(mocked_analytics_module): return mocked_analytics_module(benefits.oauth.views) +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +def test_login_no_oauth_client(mocked_oauth_create_client, app_request): + mocked_oauth_create_client.return_value = None + + with pytest.raises(Exception, match=r"oauth_client"): + login(app_request) + + @pytest.mark.django_db def test_login(mocked_oauth_create_client, mocked_session_verifier_auth_required, mocked_analytics_module, app_request): assert not session.logged_in(app_request) @@ -31,6 +40,15 @@ def test_login(mocked_oauth_create_client, mocked_session_verifier_auth_required assert not session.logged_in(app_request) +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +def test_authorize_no_oauth_client(mocked_oauth_create_client, app_request): + mocked_oauth_create_client.return_value = None + + with pytest.raises(Exception, match=r"oauth_client"): + authorize(app_request) + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_auth_required") def test_authorize_fail(mocked_oauth_create_client, app_request): @@ -117,6 +135,15 @@ def test_authorize_success_without_claim(mocked_session_verifier_auth_required, assert result.url == reverse(ROUTE_CONFIRM) +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_verifier_auth_required") +def test_logout_no_oauth_client(mocked_oauth_create_client, app_request): + mocked_oauth_create_client.return_value = None + + with pytest.raises(Exception, match=r"oauth_client"): + logout(app_request) + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_auth_required") def test_logout(mocker, mocked_oauth_create_client, mocked_analytics_module, app_request): From 46269312b9c75129bfc798f4ddae734b5dd06bb3 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Fri, 17 Jun 2022 22:15:36 +0000 Subject: [PATCH 063/106] fix: combine 2 link buttons into 1 --- benefits/eligibility/views.py | 20 ++------------------ benefits/locale/en/LC_MESSAGES/django.po | 8 ++++---- benefits/locale/es/LC_MESSAGES/django.po | 8 ++++---- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 1f61c22468..345755b524 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -76,7 +76,6 @@ def start(request): button = viewmodels.Button.primary(text=_("eligibility.buttons.continue"), url=reverse(ROUTE_CONFIRM)) - payment_options_link = f"{reverse(ROUTE_HELP)}#payment-options" media = [ dict( icon=viewmodels.Icon("bankcardcheck", pgettext("image alt text", "core.icons.bankcardcheck")), @@ -86,12 +85,7 @@ def start(request): viewmodels.Button.link( classes="btn-text btn-link", text=_("eligibility.pages.start.bankcard.button[0].link"), - url=payment_options_link, - ), - viewmodels.Button.link( - classes="btn-text btn-link", - text=_("eligibility.pages.start.bankcard.button[1].link"), - url=payment_options_link, + url=f"{reverse(ROUTE_HELP)}#payment-options", ), ], ), @@ -101,9 +95,6 @@ def start(request): if settings.OAUTH_CLIENT_NAME is None: raise Exception("EligibilityVerifier requires authentication, but OAUTH_CLIENT_NAME is None") - oauth_help_link = f"{reverse(ROUTE_HELP)}#login-gov" - oauth_help_more_link = f"{reverse(ROUTE_HELP)}#login-gov-verify-items" - media.insert( 0, dict( @@ -114,14 +105,7 @@ def start(request): viewmodels.Button.link( classes="btn-text btn-link", text=_("eligibility.pages.start.oauth.link_text"), - url=oauth_help_link, - rel="noopener noreferrer", - ), - viewmodels.Button.link( - classes="btn-text btn-link", - text=_("eligibility.pages.start.oauth.link_text[2]"), - url=oauth_help_more_link, - rel="noopener noreferrer", + url=f"{reverse(ROUTE_HELP)}#login-gov", ), ], bullets=[ diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 7d2fa259f8..5374032f99 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -190,7 +190,7 @@ msgstr "Note: Must include a Visa or Mastercard logo." #: benefits/core/templates/core/payment-options.html:15 msgid "core.pages.payment_options.p[3]" msgstr "" -"Don't have access to a bank card? You can request a contactless card from " +"Don’t have access to a bank card? You can request a contactless card from " "one of the companies that offer free contactless prepaid debit cards, such " "as the " @@ -366,7 +366,7 @@ msgid "eligibility.pages.start.bankcard.text" msgstr "Your card must be a contactless debit or credit card by Visa or Mastercard." msgid "eligibility.pages.start.bankcard.button[0].link" -msgstr "Don’t have a bank card?" +msgstr "Don’t have a bank card or unsure if your card is contactless?" msgid "eligibility.pages.start.bankcard.button[1].link" msgstr "Unsure if you have a contactless card?" @@ -382,7 +382,7 @@ msgid "eligibility.pages.start.oauth.details" msgstr "Login.gov is a safe way to sign in to government services. If you do not have an account already, you will be able to create one. You will also need to provide proof of your identity. Some of the items you can use to do that include:" msgid "eligibility.pages.start.oauth.link_text" -msgstr "Learn more about Login.gov" +msgstr "Learn more about Login.gov and providing proof of identity" msgid "eligibility.pages.start.oauth.link_text[2]" msgstr "Don’t have access to these items?" @@ -574,7 +574,7 @@ msgstr "Let’s see if we can find you in our system" msgid "eligibility.pages.confirm.mst.p[0]" msgstr "" -"Please input your Courtesy Card number and last name below. If you're a " +"Please input your Courtesy Card number and last name below. If youre a " "current MST Courtesy Cardholder, we can confirm that you are eligible for " "a discount. We do not save your information." diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index ef152d18bc..fcc3c6ea22 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -351,7 +351,7 @@ msgid "eligibility.pages.start.bankcard.text" msgstr "Debe ser una tarjeta de débito o crédito sin contacto de Visa o Mastercard." msgid "eligibility.pages.start.bankcard.button[0].link" -msgstr "¿No tienes tarjeta bancaria?" +msgstr "TODO: ¿No tienes tarjeta bancaria?" msgid "eligibility.pages.start.bankcard.button[1].link" msgstr "¿No está seguro si tiene una tarjeta sin contacto?" @@ -367,10 +367,10 @@ msgid "eligibility.pages.start.oauth.details" msgstr "Login.gov es una forma segura de iniciar sesión en los servicios gubernamentales." msgid "eligibility.pages.start.oauth.link_text" -msgstr "Conozca más sobre Login.gov" +msgstr "TODO: Conozca más sobre Login.gov" msgid "eligibility.pages.start.oauth.link_text[2]" -msgstr "TODO: Don’t have access to these items?" +msgstr "Don’t have access to these items?" msgid "eligibility.pages.start.oauth.required_items[0]" msgstr "TODO: Your state-issued ID card" @@ -383,7 +383,7 @@ msgstr "TODO: A phone number with a phone plan associated with your name" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signin" -msgstr "Crear una cuenta o iniciar sesión con" +msgstr "TODO: Crear una cuenta o iniciar sesión con" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signout" From 6471bd80cda671b5980fe0270427a76225329602 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Fri, 17 Jun 2022 22:22:04 +0000 Subject: [PATCH 064/106] fix(copy): add smart quote --- benefits/locale/en/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index d8a0936951..6ef6d67ed2 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -589,7 +589,7 @@ msgstr "Let’s see if we can find you in our system" msgid "eligibility.pages.confirm.mst.p[0]" msgstr "" -"Please input your Courtesy Card number and last name below. If youre a " +"Please input your Courtesy Card number and last name below. If you’re a " "current MST Courtesy Cardholder, we can confirm that you are eligible for " "a discount. We do not save your information." From ba35ba8e55a19ca8066f8500f69442e8c9151bd8 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Fri, 17 Jun 2022 22:32:28 +0000 Subject: [PATCH 065/106] fix: fix merge conflict issue --- benefits/eligibility/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 345755b524..d9ed58b62a 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -1,7 +1,6 @@ """ The eligibility application: view definitions for the eligibility verification flow. """ -from django.conf import settings from django.contrib import messages from django.shortcuts import redirect from django.template.response import TemplateResponse @@ -92,9 +91,6 @@ def start(request): ] if verifier.requires_authentication: - if settings.OAUTH_CLIENT_NAME is None: - raise Exception("EligibilityVerifier requires authentication, but OAUTH_CLIENT_NAME is None") - media.insert( 0, dict( From dd45b3f84b4bea9b76bfd6e765896a356367a064 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 21 Jun 2022 20:45:06 +0000 Subject: [PATCH 066/106] chore: remove unused language file entries --- benefits/locale/en/LC_MESSAGES/django.po | 3 --- benefits/locale/es/LC_MESSAGES/django.po | 3 --- 2 files changed, 6 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 6ef6d67ed2..149009b8c7 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -383,9 +383,6 @@ msgstr "Your card must be a contactless debit or credit card by Visa or Masterca msgid "eligibility.pages.start.bankcard.button[0].link" msgstr "Don’t have a bank card or unsure if your card is contactless?" -msgid "eligibility.pages.start.bankcard.button[1].link" -msgstr "Unsure if you have a contactless card?" - #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.continue" msgstr "Continue" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 1e5c1de2d0..314274f84e 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -368,9 +368,6 @@ msgstr "Debe ser una tarjeta de débito o crédito sin contacto de Visa o Master msgid "eligibility.pages.start.bankcard.button[0].link" msgstr "TODO: ¿No tienes tarjeta bancaria?" -msgid "eligibility.pages.start.bankcard.button[1].link" -msgstr "¿No está seguro si tiene una tarjeta sin contacto?" - #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.continue" msgstr "Continuar" From 2b2f1966da0b5b135ce2b8974790c0316efd5a60 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 17:59:49 +0000 Subject: [PATCH 067/106] fix(mobile): Enrollment, Eligibility Pages - Fix Sign Out button height so that it works with Debug Bar --- benefits/static/css/styles.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index b4b15490bf..e85e7e5f6a 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -82,6 +82,10 @@ main { flex-shrink: 0 !important; } +main#main-content.main-content { + position: relative; +} + /* All pages but the Start Page */ main .main-row { min-height: calc(100vh - 120px); @@ -663,11 +667,11 @@ footer.global-footer .footer-links a:active { } .signout-row { - top: 308px; + top: 240px; } .no-image-mobile .signout-row { - top: 80px; + top: 0; } .no-image-mobile .main-primary .container-fluid { From a55c5bac2f1e1e49c3f7d2219eecb521779379de Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 20:40:41 +0000 Subject: [PATCH 068/106] fix(mobile): Eligibility Start - link should be regular link style --- benefits/static/css/styles.css | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css index e85e7e5f6a..0682160e78 100644 --- a/benefits/static/css/styles.css +++ b/benefits/static/css/styles.css @@ -333,12 +333,9 @@ footer.global-footer .footer-links a:active { .media-list .media .media-body .media-body--links .btn-lg { font-size: 16px; - line-height: 20px; letter-spacing: 0.05em; padding: 0 0 6px 0; text-align: left; - margin: 0; - display: block; width: fit-content; } @@ -723,11 +720,18 @@ footer.global-footer .footer-links a:active { line-height: 30px; } - .media-list .media .media-body--details { + .media-list .media .media-body--details p { padding-bottom: 25px; margin-left: 0; font-size: 20px; line-height: 30px; + letter-spacing: 0.05em; + } + + .media-list .media .media-body--items li { + font-size: 20px; + line-height: 30px; + letter-spacing: 0.05em; } .media-list .media .media-body--links { @@ -740,20 +744,10 @@ footer.global-footer .footer-links a:active { } .media-list .media .media-body .media-body--links .btn-lg { - letter-spacing: 0.02em; - padding: 6px; - text-align: left; - width: 100%; - margin: 0; font-size: 18px; - text-decoration: none; - letter-spacing: 0.2px; - border: 2px solid #046b99; - margin-bottom: 24px; - font-weight: 500; - width: 212px; - line-height: 22.5px; - border-radius: 3px; + line-height: 27px; + font-weight: 700; + letter-spacing: 0.05em; display: block; } From e92e17f32f4e496677e5d80235fe3daf813a91a7 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 22 Jun 2022 21:01:28 +0000 Subject: [PATCH 069/106] chore(ci): don't run Cypress tests things are changing too much on the front-end with copy, flows, options, etc. our Pytest coverage is over 80% and we need to re-think the role of Cypress tests - for now, don't run them to unblock other changes --- .github/workflows/tests-ui.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.github/workflows/tests-ui.yml b/.github/workflows/tests-ui.yml index f892635f1d..99c27ba1a3 100644 --- a/.github/workflows/tests-ui.yml +++ b/.github/workflows/tests-ui.yml @@ -40,34 +40,6 @@ jobs: -v ${{ github.workspace }}/fixtures:/home/calitp/app/fixtures \ benefits_client:${{ github.sha }} - - name: Start server - run: | - docker run \ - --detach \ - -p 5000:5000 \ - ghcr.io/cal-itp/eligibility-server:main - - - name: Run UI automation tests - uses: cypress-io/github-action@v2 - env: - CYPRESS_baseUrl: http://localhost:8000 - with: - command: npm run cypress:ui - working-directory: tests/cypress - wait-on: http://localhost:8000/healthcheck - - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: cypress-screenshots - path: tests/cypress/screenshots - - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: cypress-videos - path: tests/cypress/videos - - name: Run Lighthouse tests for a11y uses: treosh/lighthouse-ci-action@9.3.0 with: From 2bb4d3e94bc2fe60d37a65158d0c8b5de56329e6 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 21:03:21 +0000 Subject: [PATCH 070/106] fix(debug bar): add text-break Bootstrap utility class to debug bar so that it doesn't overflow-x/yf --- benefits/core/templates/core/includes/debug.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/core/templates/core/includes/debug.html b/benefits/core/templates/core/includes/debug.html index a21aa97a7e..82d839e5a3 100644 --- a/benefits/core/templates/core/includes/debug.html +++ b/benefits/core/templates/core/includes/debug.html @@ -3,7 +3,7 @@ {% block content %} DEBUG MODE {% if debug %} - + { {% for key, value in debug.items %}"{{ key }}": "{{ value }}"{% if not forloop.last %}, {% endif %}{% endfor %} } {% endif %} From c414b1ca6c7b26b875badccd804eee6397ef2db6 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 23:21:54 +0000 Subject: [PATCH 071/106] feat(models): many Verifier fields are optional depending on if Verifier is used for the EV API or OAuth --- benefits/core/migrations/0001_initial.py | 32 +++++++++++++----------- benefits/core/models.py | 28 ++++++++++----------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/benefits/core/migrations/0001_initial.py b/benefits/core/migrations/0001_initial.py index 290c336b0a..f01238a99f 100644 --- a/benefits/core/migrations/0001_initial.py +++ b/benefits/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2022-06-17 05:03 +# Generated by Django 3.2.13 on 2022-06-17 23:20 from django.db import migrations, models import django.db.models.deletion @@ -38,26 +38,26 @@ class Migration(migrations.Migration): fields=[ ("id", models.AutoField(primary_key=True, serialize=False)), ("name", models.TextField()), - ("api_url", models.TextField()), - ("api_auth_header", models.TextField()), - ("api_auth_key", models.TextField()), - ("jwe_cek_enc", models.TextField()), - ("jwe_encryption_alg", models.TextField()), - ("jws_signing_alg", models.TextField()), + ("api_url", models.TextField(null=True)), + ("api_auth_header", models.TextField(null=True)), + ("api_auth_key", models.TextField(null=True)), + ("jwe_cek_enc", models.TextField(null=True)), + ("jwe_encryption_alg", models.TextField(null=True)), + ("jws_signing_alg", models.TextField(null=True)), ("selection_label", models.TextField()), ("selection_label_description", models.TextField(null=True)), ("start_content_title", models.TextField()), ("start_item_name", models.TextField()), ("start_item_description", models.TextField()), ("start_blurb", models.TextField()), - ("form_title", models.TextField()), - ("form_content_title", models.TextField()), - ("form_blurb", models.TextField()), - ("form_sub_label", models.TextField()), - ("form_sub_placeholder", models.TextField()), + ("form_title", models.TextField(null=True)), + ("form_content_title", models.TextField(null=True)), + ("form_blurb", models.TextField(null=True)), + ("form_sub_label", models.TextField(null=True)), + ("form_sub_placeholder", models.TextField(null=True)), ("form_sub_pattern", models.TextField(null=True)), - ("form_name_label", models.TextField()), - ("form_name_placeholder", models.TextField()), + ("form_name_label", models.TextField(null=True)), + ("form_name_placeholder", models.TextField(null=True)), ("form_name_max_length", models.PositiveSmallIntegerField(null=True)), ("unverified_title", models.TextField()), ("unverified_content_title", models.TextField()), @@ -140,6 +140,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name="eligibilityverifier", name="public_key", - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="+", to="core.pemdata"), + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.PROTECT, related_name="+", to="core.pemdata" + ), ), ] diff --git a/benefits/core/models.py b/benefits/core/models.py index c87bc85cf0..62ad9d6715 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -65,18 +65,18 @@ class EligibilityVerifier(models.Model): id = models.AutoField(primary_key=True) name = models.TextField() - api_url = models.TextField() - api_auth_header = models.TextField() - api_auth_key = models.TextField() + api_url = models.TextField(null=True) + api_auth_header = models.TextField(null=True) + api_auth_key = models.TextField(null=True) eligibility_type = models.ForeignKey(EligibilityType, on_delete=models.PROTECT) # public key is used to encrypt requests targeted at this Verifier and to verify signed responses from this verifier - public_key = models.ForeignKey(PemData, related_name="+", on_delete=models.PROTECT) + public_key = models.ForeignKey(PemData, related_name="+", on_delete=models.PROTECT, null=True) # The JWE-compatible Content Encryption Key (CEK) key-length and mode - jwe_cek_enc = models.TextField() + jwe_cek_enc = models.TextField(null=True) # The JWE-compatible encryption algorithm - jwe_encryption_alg = models.TextField() + jwe_encryption_alg = models.TextField(null=True) # The JWS-compatible signing algorithm - jws_signing_alg = models.TextField() + jws_signing_alg = models.TextField(null=True) auth_provider = models.ForeignKey(AuthProvider, on_delete=models.PROTECT, null=True) selection_label = models.TextField() selection_label_description = models.TextField(null=True) @@ -84,15 +84,15 @@ class EligibilityVerifier(models.Model): start_item_name = models.TextField() start_item_description = models.TextField() start_blurb = models.TextField() - form_title = models.TextField() - form_content_title = models.TextField() - form_blurb = models.TextField() - form_sub_label = models.TextField() - form_sub_placeholder = models.TextField() + form_title = models.TextField(null=True) + form_content_title = models.TextField(null=True) + form_blurb = models.TextField(null=True) + form_sub_label = models.TextField(null=True) + form_sub_placeholder = models.TextField(null=True) # A regular expression used to validate the 'sub' API field before sending to this verifier form_sub_pattern = models.TextField(null=True) - form_name_label = models.TextField() - form_name_placeholder = models.TextField() + form_name_label = models.TextField(null=True) + form_name_placeholder = models.TextField(null=True) # The maximum length accepted for the 'name' API field before sending to this verifier form_name_max_length = models.PositiveSmallIntegerField(null=True) unverified_title = models.TextField() From 0a12b8556735c5545f20b74125d2b530cd8a3911 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 17 Jun 2022 23:24:51 +0000 Subject: [PATCH 072/106] refactor(models): clearer helper attribute names rename require_authentication --> is_auth_required add uses_auth_verification to decide if using OAuth verification --- benefits/core/context_processors.py | 4 ++-- benefits/core/middleware.py | 2 +- benefits/core/models.py | 8 +++++++- benefits/eligibility/verify.py | 2 +- benefits/eligibility/views.py | 2 +- benefits/enrollment/views.py | 2 +- tests/pytest/conftest.py | 5 +++-- tests/pytest/core/test_middleware_login_required.py | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/benefits/core/context_processors.py b/benefits/core/context_processors.py index 4e0140bf6c..d9120a861f 100644 --- a/benefits/core/context_processors.py +++ b/benefits/core/context_processors.py @@ -18,12 +18,12 @@ def authentication(request): if verifier: data = { - "required": verifier.requires_authentication, + "required": verifier.is_auth_required, "logged_in": session.logged_in(request), "sign_out_route": reverse("oauth:logout"), } - if verifier.requires_authentication: + if verifier.is_auth_required: auth_provider = verifier.auth_provider data["sign_in_button_label"] = auth_provider.sign_in_button_label data["sign_out_button_label"] = auth_provider.sign_out_button_label diff --git a/benefits/core/middleware.py b/benefits/core/middleware.py index 94fb659fa7..70e761888f 100644 --- a/benefits/core/middleware.py +++ b/benefits/core/middleware.py @@ -142,7 +142,7 @@ class LoginRequired(MiddlewareMixin): def process_view(self, request, view_func, view_args, view_kwargs): # only require login if verifier requires it verifier = session.verifier(request) - if not verifier or not verifier.requires_authentication or session.logged_in(request): + if not verifier or not verifier.is_auth_required or session.logged_in(request): # pass through return None diff --git a/benefits/core/models.py b/benefits/core/models.py index 62ad9d6715..d212486c6c 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -108,9 +108,15 @@ def public_key_data(self): return self.public_key.text @property - def requires_authentication(self): + def is_auth_required(self): + """True if this Verifier requires authentication. False otherwise.""" return self.auth_provider is not None + @property + def uses_auth_verification(self): + """True if this Verifier verifies via the auth provider. False otherwise.""" + return all((self.is_auth_required, self.auth_provider.scope, self.auth_provider.claim)) + @staticmethod def by_id(id): """Get an EligibilityVerifier instance by its ID.""" diff --git a/benefits/eligibility/verify.py b/benefits/eligibility/verify.py index 8ecaab5761..5d9485fa26 100644 --- a/benefits/eligibility/verify.py +++ b/benefits/eligibility/verify.py @@ -33,7 +33,7 @@ def eligibility_from_api(verifier, form, agency): def eligibility_from_oauth(verifier, oauth_claim, agency): - if verifier.requires_authentication and verifier.auth_provider.claim == oauth_claim: + if verifier.uses_auth_verification and verifier.auth_provider.claim == oauth_claim: return list(map(lambda t: t.name, agency.types_to_verify(verifier))) else: return [] diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index d9ed58b62a..f831127c6f 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -90,7 +90,7 @@ def start(request): ), ] - if verifier.requires_authentication: + if verifier.is_auth_required: media.insert( 0, dict( diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 2356fee3fa..65f7695882 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -149,7 +149,7 @@ def success(request): content_title=_("enrollment.pages.success.content_title"), ) - if verifier.requires_authentication: + if verifier.is_auth_required: if session.logged_in(request): page.buttons = [viewmodels.Button.logout()] page.classes = ["no-image-mobile", "logged-in"] diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index 97cd9c93cb..e3bf7a3d50 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -103,7 +103,7 @@ def mocked_session_verifier(mocker, first_verifier): @pytest.fixture def mocked_session_verifier_auth_required(mocker, first_verifier, mocked_session_verifier): mock_verifier = mocker.Mock(spec=first_verifier) - mock_verifier.requires_authentication = True + mock_verifier.is_auth_required = True mocked_session_verifier.return_value = mock_verifier return mocked_session_verifier @@ -111,7 +111,8 @@ def mocked_session_verifier_auth_required(mocker, first_verifier, mocked_session @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=first_verifier) from that fixture - mocked_session_verifier_auth_required.return_value.requires_authentication = False + 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 diff --git a/tests/pytest/core/test_middleware_login_required.py b/tests/pytest/core/test_middleware_login_required.py index d0584ad6d2..8063863f10 100644 --- a/tests/pytest/core/test_middleware_login_required.py +++ b/tests/pytest/core/test_middleware_login_required.py @@ -18,7 +18,7 @@ def decorated_view(mocked_view): @pytest.fixture def require_login(mocker): mock_verifier = mocker.Mock(spec=EligibilityVerifier) - mock_verifier.requires_authentication = True + mock_verifier.is_auth_required = True mocker.patch("benefits.core.session.verifier", return_value=mock_verifier) @@ -37,7 +37,7 @@ def test_login_auth_required(app_request, mocked_view, decorated_view): def test_login_auth_not_required(app_request, mocked_view, decorated_view): verifier = EligibilityVerifier.objects.filter(auth_provider__isnull=True).first() assert verifier - assert not verifier.requires_authentication + assert not verifier.is_auth_required session.update(app_request, verifier=verifier) decorated_view(app_request) From 3e915e0d44ae2758772b60fbd5603e10809bbdd9 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Sat, 18 Jun 2022 01:46:52 +0000 Subject: [PATCH 073/106] refactor(views): handle different verify methods now that certain verifier fields are optional given the type of verification, we can't assume a confirm page w/form will be created make a specific early path for oauth verification --- benefits/eligibility/views.py | 84 ++++++++++++++------------ tests/pytest/eligibility/test_views.py | 54 +++++++++++------ 2 files changed, 79 insertions(+), 59 deletions(-) diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index f831127c6f..605607b117 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -150,55 +150,61 @@ def start(request): def confirm(request): """View handler for the eligibility verification form.""" - verifier = session.verifier(request) + # GET from an already verified user, no need to verify again + if request.method == "GET" and session.eligible(request): + eligibility = session.eligibility(request) + return verified(request, [eligibility.name]) - page = viewmodels.Page( - title=_(verifier.form_title), - content_title=_(verifier.form_content_title), - paragraphs=[_(verifier.form_blurb)], - form=forms.EligibilityVerificationForm(auto_id=True, label_suffix="", verifier=verifier), - classes="text-lg-center", - ) + verifier = session.verifier(request) - # POST form submission, process form data - if request.method == "POST": + # GET for OAuth verification + if request.method == "GET" and verifier.uses_auth_verification: analytics.started_eligibility(request) - form = forms.EligibilityVerificationForm(data=request.POST, verifier=verifier) + verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request), session.agency(request)) + if verified_types: + return verified(request, verified_types) + else: + return unverified(request) - # form was not valid, allow for correction/resubmission - if not form.is_valid(): - if recaptcha.has_error(form): - messages.error(request, "Recaptcha failed. Please try again.") + # GET/POST for Eligibility API verification + else: + page = viewmodels.Page( + title=_(verifier.form_title), + content_title=_(verifier.form_content_title), + paragraphs=[_(verifier.form_blurb)], + form=forms.EligibilityVerificationForm(auto_id=True, label_suffix="", verifier=verifier), + classes="text-lg-center", + ) - page.forms = [form] - return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) + # POST form submission, process form data + if request.method == "POST": + analytics.started_eligibility(request) - # form is valid, make Eligibility Verification request to get the verified types - verified_types = verify.eligibility_from_api(verifier, form, session.agency(request)) + form = forms.EligibilityVerificationForm(data=request.POST, verifier=verifier) + # form was not valid, allow for correction/resubmission + if not form.is_valid(): + if recaptcha.has_error(form): + messages.error(request, "Recaptcha failed. Please try again.") - # form was not valid, allow for correction/resubmission - if verified_types is None: - analytics.returned_error(request, form.errors) - page.forms = [form] - return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) - # no types were verified - elif len(verified_types) == 0: - return unverified(request) - # type(s) were verified - else: - return verified(request, verified_types) + page.forms = [form] + return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) - # GET from an already verified user, no need to verify again - elif session.eligible(request): - eligibility = session.eligibility(request) - return verified(request, [eligibility.name]) + # form is valid, make Eligibility Verification request to get the verified types + verified_types = verify.eligibility_from_api(verifier, form, session.agency(request)) - # GET from an unverified user, see if verifier can get verified types and if not, present the form - else: - verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request), session.agency(request)) - if verified_types: - return verified(request, verified_types) + # form was not valid, allow for correction/resubmission + if verified_types is None: + analytics.returned_error(request, form.errors) + page.forms = [form] + return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) + # no types were verified + elif len(verified_types) == 0: + return unverified(request) + # type(s) were verified + else: + return verified(request, verified_types) + # GET from an unverified user, see if verifier can get verified types and if not, present the form else: return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict()) diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index da860dabbe..2de197e7e2 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -171,8 +171,11 @@ def test_start_without_verifier(client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_eligibility_auth_request") -def test_confirm_get_unverified(client): +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier_auth_not_required") +def test_confirm_get_unverified(mocker, client): + mock_page = mocker.patch("benefits.eligibility.views.viewmodels.Page") + mock_page.return_value.context_dict.return_value = {"page": {"title": "page title", "content_title": "page content title"}} + path = reverse(ROUTE_CONFIRM) response = client.get(path) @@ -181,7 +184,7 @@ def test_confirm_get_unverified(client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_eligibility_auth_request", "mocked_session_eligibility") +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility", "mocked_session_verifier_auth_not_required") def test_confirm_get_verified(client, mocked_session_update): path = reverse(ROUTE_CONFIRM) response = client.get(path) @@ -191,6 +194,33 @@ def test_confirm_get_verified(client, mocked_session_update): mocked_session_update.assert_called_once() +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_eligibility_auth_request") +def test_confirm_get_oauth_verified(mocker, client, first_eligibility, mocked_session_update, mocked_analytics_module): + mocker.patch("benefits.eligibility.verify.eligibility_from_oauth", return_value=[first_eligibility]) + + path = reverse(ROUTE_CONFIRM) + response = client.get(path) + + mocked_session_update.assert_called_once() + mocked_analytics_module.returned_success.assert_called_once() + assert response.status_code == 302 + assert response.url == reverse(ROUTE_ENROLLMENT) + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_eligibility_auth_request", "mocked_session_update") +def test_confirm_get_oauth_unverified(mocker, client, mocked_analytics_module): + mocker.patch("benefits.eligibility.verify.eligibility_from_oauth", return_value=[]) + + path = reverse(ROUTE_CONFIRM) + response = client.get(path) + + mocked_analytics_module.returned_fail.assert_called_once() + assert response.status_code == 200 + assert response.template_name == TEMPLATE_UNVERIFIED + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_eligibility_auth_request") def test_confirm_post_invalid_form(client, invalid_form_data, mocked_analytics_module): @@ -247,9 +277,7 @@ def test_confirm_post_valid_form_eligibility_unverified(mocker, client, form_dat def test_confirm_post_valid_form_eligibility_verified( mocker, client, form_data, mocked_session_eligibility, mocked_session_update, mocked_analytics_module ): - # mocked_session_eligibility is a fixture that mocks benefits.core.session.eligibility(request) - # call it here, passing a None request, to get the return value from the mock - eligibility = mocked_session_eligibility(None) + eligibility = mocked_session_eligibility.return_value mocker.patch("benefits.eligibility.verify.eligibility_from_api", return_value=[eligibility]) path = reverse(ROUTE_CONFIRM) @@ -259,17 +287,3 @@ def test_confirm_post_valid_form_eligibility_verified( mocked_analytics_module.returned_success.assert_called_once() assert response.status_code == 302 assert response.url == reverse(ROUTE_ENROLLMENT) - - -@pytest.mark.django_db -@pytest.mark.usefixtures("mocked_eligibility_auth_request") -def test_confirm_get_oauth_verified(mocker, client, first_eligibility, mocked_session_update, mocked_analytics_module): - mocker.patch("benefits.eligibility.verify.eligibility_from_oauth", return_value=[first_eligibility]) - - path = reverse(ROUTE_CONFIRM) - response = client.get(path) - - mocked_session_update.assert_called_once() - mocked_analytics_module.returned_success.assert_called_once() - assert response.status_code == 302 - assert response.url == reverse(ROUTE_ENROLLMENT) From 267c325be9b72eacaeb0c7af12dfa49d0f22fd4b Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Sat, 18 Jun 2022 02:01:03 +0000 Subject: [PATCH 074/106] fix(tests): query by name rather than number test logic breaks if there are more objects configured than what is in the included sample fixtures --- tests/pytest/core/test_views.py | 40 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/tests/pytest/core/test_views.py b/tests/pytest/core/test_views.py index f1c73a518f..88a772db8d 100644 --- a/tests/pytest/core/test_views.py +++ b/tests/pytest/core/test_views.py @@ -4,7 +4,7 @@ from benefits.core.models import TransitAgency import benefits.core.session -from benefits.core.views import ROUTE_INDEX, ROUTE_HELP, TEMPLATE_AGENCY, bad_request, csrf_failure +from benefits.core.views import ROUTE_ELIGIBILITY, ROUTE_INDEX, ROUTE_HELP, TEMPLATE_AGENCY, bad_request, csrf_failure ROUTE_AGENCY = "core:agency_index" @@ -36,43 +36,45 @@ def test_index_multiple_agencies(client): @pytest.mark.django_db def test_index_single_agency(mocker, client, session_reset_spy): - agencies = TransitAgency.all_active()[:1] - mocker.patch("benefits.core.models.TransitAgency.all_active", return_value=agencies) + # all_active set to ABC + agency = TransitAgency.by_slug("abc") + mocker.patch("benefits.core.models.TransitAgency.all_active", return_value=[agency]) path = reverse(ROUTE_INDEX) response = client.get(path) - assert response.status_code == 302 - assert response.url == agencies[0].index_url session_reset_spy.assert_called_once() + assert response.status_code == 302 + assert response.url == agency.index_url @pytest.mark.django_db -def test_index_single_agency_single_verifier(mocker, client): +def test_agency_index_single_verifier(mocker, client, session_reset_spy, mocked_session_update): # Agency set to DEFTl, which only has 1 verifier - agencies = TransitAgency.all_active()[1:] - mocker.patch("benefits.core.models.TransitAgency.all_active", return_value=agencies) + agency = TransitAgency.by_slug("deftl") + mocker.patch("benefits.core.models.TransitAgency.all_active", return_value=[agency]) - path = reverse(ROUTE_INDEX) - response = client.get(path, follow=True) + response = client.get(agency.index_url) - # Setting follow to True allows the test to go thorugh redirects and returns the redirect_chain attr - # https://docs.djangoproject.com/en/3.2/topics/testing/tools/#making-requests - assert response.redirect_chain[0] == ("/deftl", 302) - assert response.redirect_chain[1] == ("/eligibility/", 302) - assert response.redirect_chain[-1] == ("/eligibility/start", 302) + session_reset_spy.assert_called_once() + mocked_session_update.assert_called_once() + + assert response.status_code == 302 + assert response.url == reverse(ROUTE_ELIGIBILITY) @pytest.mark.django_db -def test_agency_index_multiple_verifier(first_agency, client, session_reset_spy): +def test_agency_index_multiple_verifier(mocker, client, session_reset_spy, mocked_session_update): # Agency set to ABC, which has 2 verifiers - path = reverse(ROUTE_AGENCY, kwargs={"agency": first_agency.slug}) + agency = TransitAgency.by_slug("abc") + mocker.patch("benefits.core.models.TransitAgency.all_active", return_value=[agency]) - response = client.get(path) + response = client.get(agency.index_url) + session_reset_spy.assert_called_once() + mocked_session_update.assert_called_once() assert response.status_code == 200 assert response.template_name == TEMPLATE_AGENCY - session_reset_spy.assert_called_once() @pytest.mark.django_db From 811c625aeced06eea0868255d1276c1dbfa80491 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 21 Jun 2022 09:16:12 -0700 Subject: [PATCH 075/106] feat(fixtures): new verifier configured with oauth provider --- benefits/locale/en/LC_MESSAGES/django.po | 18 ++++++++++++------ benefits/locale/es/LC_MESSAGES/django.po | 17 +++++++++++------ fixtures/03_eligibilityverifier.json | 18 ++++++++++++++++++ fixtures/05_transitagency.json | 6 ++---- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 149009b8c7..2af078e200 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -396,9 +396,6 @@ msgstr "Login.gov is a safe way to sign in to government services. Benefits uses msgid "eligibility.pages.start.oauth.link_text" msgstr "Learn more about Login.gov and providing proof of identity" -msgid "eligibility.pages.start.oauth.link_text[2]" -msgstr "Don’t have access to these items?" - msgid "eligibility.pages.start.oauth.required_items[0]" msgstr "Your state-issued ID card" @@ -417,13 +414,18 @@ msgid "eligibility.buttons.signout" msgstr "Sign out of Login.gov" #: benefits/eligibility/views.py:170 -msgid "eligibility.pages.unverified.dmv.title" +msgid "eligibility.pages.unverified.oauth.title" msgstr "Age not confirmed" #: benefits/eligibility/views.py:171 -msgid "eligibility.pages.unverified.dmv.content_title" +msgid "eligibility.pages.unverified.oauth.content_title" msgstr "We can’t confirm your age" +msgid "eligibility.pages.unverified.oauth.p[0]" +msgstr "" +"You may still be eligible for a discount, but we can’t verify your age " +"with Login.gov." + #: benefits/eligibility/views.py:172 msgctxt "image alt text" msgid "core.icons.idcardquestion" @@ -512,7 +514,7 @@ msgid "enrollment.pages.success.p3%(help_link)s" msgstr "For more information on what to expect when using your connected bank card please reach out to your local transit provider or check out our " "help page." -msgid "eligibility.pages.index.dmv.label" +msgid "eligibility.pages.index.oauth.label" msgstr "Senior Discount Program" msgid "eligibility.pages.index.mst.label" @@ -540,6 +542,9 @@ msgstr "CA driver’s license or ID number" msgid "eligibility.forms.confirm.dmv.fields.name" msgstr "Last name (as it appears on ID)" +msgid "eligibility.pages.start.oauth.content_title" +msgstr "You will need a few items to connect your discount:" + msgid "eligibility.pages.start.dmv.content_title" msgstr "You will need a few items to connect your discount:" @@ -552,6 +557,7 @@ msgstr "" msgid "eligibility.pages.confirm.dmv.title" msgstr "Confirm Eligibility" + msgid "eligibility.pages.unverified.dmv.p[0]" msgstr "" "You may still be eligible for a discount, but we can’t verify your age " diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 314274f84e..b5ed013106 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -381,9 +381,6 @@ msgstr "TODO: Login.gov is a safe way to sign in to government services. Benefit msgid "eligibility.pages.start.oauth.link_text" msgstr "TODO: Conozca más sobre Login.gov" -msgid "eligibility.pages.start.oauth.link_text[2]" -msgstr "Don’t have access to these items?" - msgid "eligibility.pages.start.oauth.required_items[0]" msgstr "TODO: Your state-issued ID card" @@ -393,6 +390,9 @@ msgstr "TODO: Your Social Security number" msgid "eligibility.pages.start.oauth.required_items[2]" msgstr "TODO: A phone number with a phone plan associated with your name" +msgid "eligibility.pages.start.oauth.content_title" +msgstr "TODO: You will need a few items to connect your discount:" + #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signin" msgstr "TODO: Crear una cuenta o iniciar sesión con" @@ -402,13 +402,18 @@ msgid "eligibility.buttons.signout" msgstr "Cierre sesión de Login.gov" #: benefits/eligibility/views.py:170 -msgid "eligibility.pages.unverified.dmv.title" +msgid "eligibility.pages.unverified.oauth.title" msgstr "Edad no confirmada" #: benefits/eligibility/views.py:171 -msgid "eligibility.pages.unverified.dmv.content_title" +msgid "eligibility.pages.unverified.oauth.content_title" msgstr "No podemos confirmar su edad" +msgid "eligibility.pages.unverified.oauth.p[0]" +msgstr "" +"TODO: You may still be eligible for a discount, but we can’t verify your age " +"with Login.gov." + #: benefits/eligibility/views.py:172 msgctxt "image alt text" msgid "core.icons.idcardquestion" @@ -500,7 +505,7 @@ msgstr "Si estás en una computadora pública o compartida, no olvides hacer cli msgid "enrollment.pages.success.p4" msgstr "." -msgid "eligibility.pages.index.dmv.label" +msgid "eligibility.pages.index.oauth.label" msgstr "TODO: Senior Discount Program" msgid "eligibility.pages.index.mst.label" diff --git a/fixtures/03_eligibilityverifier.json b/fixtures/03_eligibilityverifier.json index 9c724ebc89..a959ec9e8d 100644 --- a/fixtures/03_eligibilityverifier.json +++ b/fixtures/03_eligibilityverifier.json @@ -66,5 +66,23 @@ "unverified_content_title": "eligibility.pages.unverified.mst.content_title", "unverified_blurb": "eligibility.pages.unverified.mst.p[0]" } + }, + { + "model": "core.eligibilityverifier", + "pk": 3, + "fields": { + "name": "OAuth claims via Login.gov", + "eligibility_type": 1, + "auth_provider": 1, + "selection_label": "eligibility.pages.index.oauth.label", + "selection_label_description": null, + "start_content_title": "eligibility.pages.start.oauth.content_title", + "start_item_name": "eligibility.pages.start.oauth.items[0].title", + "start_item_description": "eligibility.pages.start.oauth.items[0].text", + "start_blurb": "eligibility.pages.start.oauth.p[0]", + "unverified_title": "eligibility.pages.unverified.oauth.title", + "unverified_content_title": "eligibility.pages.unverified.oauth.content_title", + "unverified_blurb": "eligibility.pages.unverified.oauth.p[0]" + } } ] diff --git a/fixtures/05_transitagency.json b/fixtures/05_transitagency.json index 57f9337b71..d62c2af401 100644 --- a/fixtures/05_transitagency.json +++ b/fixtures/05_transitagency.json @@ -36,10 +36,8 @@ "info_url": "https://www.example.com/help", "phone": "321-555-5555", "active": true, - "eligibility_types": [ - 2 - ], - "eligibility_verifiers": [2], + "eligibility_types": [1], + "eligibility_verifiers": [1], "private_key": 2, "jws_signing_alg": "RS256", "payment_processor": 1 From 64cc162cdf47c2bf13b7e2455c28c7978aef9cab Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 Jun 2022 18:58:22 +0000 Subject: [PATCH 076/106] feat(oauth): support cancelling out of identity proofing the user should be redirected to the "unverified" page in such case. remove LoginRequired middleware from unverified to prevent circular dependency. --- benefits/eligibility/urls.py | 1 + benefits/eligibility/views.py | 1 - benefits/oauth/urls.py | 1 + benefits/oauth/views.py | 6 ++++++ tests/pytest/oauth/test_views.py | 11 ++++++++++- 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/benefits/eligibility/urls.py b/benefits/eligibility/urls.py index d50b00ba48..3ba04b87a5 100644 --- a/benefits/eligibility/urls.py +++ b/benefits/eligibility/urls.py @@ -12,4 +12,5 @@ path("", views.index, name="index"), path("start", views.start, name="start"), path("confirm", views.confirm, name="confirm"), + path("unverified", views.unverified, name="unverified"), ] diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index 605607b117..fbaeec523f 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -222,7 +222,6 @@ def verified(request, verified_types): @decorator_from_middleware(AgencySessionRequired) -@decorator_from_middleware(LoginRequired) @decorator_from_middleware(VerifierSessionRequired) def unverified(request): """View handler for the unverified eligibility page.""" diff --git a/benefits/oauth/urls.py b/benefits/oauth/urls.py index a270bd712c..8395d80032 100644 --- a/benefits/oauth/urls.py +++ b/benefits/oauth/urls.py @@ -8,6 +8,7 @@ # /oauth path("login", views.login, name="login"), path("authorize", views.authorize, name="authorize"), + path("cancel", views.cancel, name="cancel"), path("logout", views.logout, name="logout"), path("post_logout", views.post_logout, name="post_logout"), ] diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index 85e3fb353d..41456302ea 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -16,6 +16,7 @@ ROUTE_AUTH = "oauth:authorize" ROUTE_START = "eligibility:start" ROUTE_CONFIRM = "eligibility:confirm" +ROUTE_UNVERIFIED = "eligibility:unverified" ROUTE_POST_LOGOUT = "oauth:post_logout" @@ -77,6 +78,11 @@ def authorize(request): return redirect(ROUTE_CONFIRM) +def cancel(request): + """View implementing cancellation of OIDC authorization.""" + return redirect(ROUTE_UNVERIFIED) + + @decorator_from_middleware(VerifierSessionRequired) def logout(request): """View implementing OIDC and application sign out.""" diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index f8e09ebd08..e035dcae35 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -6,7 +6,7 @@ from benefits.core import session from benefits.core.views import ROUTE_INDEX -from benefits.oauth.views import ROUTE_START, ROUTE_CONFIRM, login, authorize, logout, post_logout +from benefits.oauth.views import ROUTE_START, ROUTE_CONFIRM, ROUTE_UNVERIFIED, login, authorize, cancel, logout, post_logout import benefits.oauth.views @@ -135,6 +135,15 @@ def test_authorize_success_without_claim(mocked_session_verifier_auth_required, assert result.url == reverse(ROUTE_CONFIRM) +def test_cancel(app_request): + unverified_route = reverse(ROUTE_UNVERIFIED) + + result = cancel(app_request) + + assert result.status_code == 302 + assert result.url == unverified_route + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_auth_required") def test_logout_no_oauth_client(mocked_oauth_create_client, app_request): From 842fd9e86786eb11875af39b215827b3e61f4e62 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 Jun 2022 21:00:35 +0000 Subject: [PATCH 077/106] fix: prevent AttributeError when auth_provider is None --- benefits/core/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/core/models.py b/benefits/core/models.py index d212486c6c..688ae8a3ce 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -115,7 +115,7 @@ def is_auth_required(self): @property def uses_auth_verification(self): """True if this Verifier verifies via the auth provider. False otherwise.""" - return all((self.is_auth_required, self.auth_provider.scope, self.auth_provider.claim)) + return self.is_auth_required and self.auth_provider.scope and self.auth_provider.claim @staticmethod def by_id(id): From 9fc75833a5615bee2a8769cb71b4fc0dc411a10a Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Wed, 22 Jun 2022 21:31:57 +0000 Subject: [PATCH 078/106] test(model): add coverage of EligibilityVerifier.uses_auth_verification --- tests/pytest/core/test_models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/pytest/core/test_models.py diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py new file mode 100644 index 0000000000..d758cbaf39 --- /dev/null +++ b/tests/pytest/core/test_models.py @@ -0,0 +1,13 @@ +import pytest + + +@pytest.mark.django_db +def test_EligibilityVerifier_uses_auth_verification_True(first_verifier): + assert first_verifier.uses_auth_verification + + +@pytest.mark.django_db +def test_EligibilityVerifier_uses_auth_verification_False(first_verifier): + first_verifier.auth_provider = None + + assert not first_verifier.uses_auth_verification From 8b840b3e7afa3d2d3423781b36050d0bc1613841 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 22:09:35 +0000 Subject: [PATCH 079/106] feat(copy): Spanish copy for Enrollment Start and Enrollment Success --- benefits/locale/es/LC_MESSAGES/django.po | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 314274f84e..d385bdfad9 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -278,7 +278,7 @@ msgid "core.pages.agency_index.p[2]" msgstr "El programa de beneficios de Cal-ITP actualmente solo está abierto a personas de 65 años o más." msgid "core.pages.agency_index.p[3]" -msgstr "TODO: Connect your bank card to your public transit discount with Cal-ITP Benefits." +msgstr "Conecta tu tarjeta bancaria para tu descuento en el transporte público con Cal-ITP Benefits." msgid "core.pages.agency_index.button.label" msgstr "¡Conecta tu tarjeta bancaria a tu descuento hoy!" @@ -312,7 +312,7 @@ msgid "core.icons.bankcard" msgstr "Icono de tarjeta bancaria" msgid "core.pages.start.headline" -msgstr "TODO: Connect your bank card to your public transit discount with Cal-ITP Benefits." +msgstr "Conecta tu tarjeta bancaria para tu descuento en el transporte público con Cal-ITP Benefits." #: benefits/eligibility/forms.py:37 msgid "eligibility.forms.confirm.submit" @@ -359,43 +359,43 @@ msgstr "TODO: Computer monitor with checkmark" #: benefits/eligibility/views.py:74 msgid "eligibility.pages.start.bankcard.title" -msgstr "Proporcione los datos de su tarjeta bancaria" +msgstr "Los datos de su tarjeta bancaria" #: benefits/eligibility/views.py:75 msgid "eligibility.pages.start.bankcard.text" -msgstr "Debe ser una tarjeta de débito o crédito sin contacto de Visa o Mastercard." +msgstr "Su tarjeta debe ser una tarjeta de débito o crédito sin contacto de Visa o Mastercard." msgid "eligibility.pages.start.bankcard.button[0].link" -msgstr "TODO: ¿No tienes tarjeta bancaria?" +msgstr "¿No tienes tarjeta bancaria o no está seguro si tiene una tarjeta sin contacto?" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.continue" msgstr "Continuar" msgid "eligibility.pages.start.oauth.heading" -msgstr "Inicia sesión en Login.gov" +msgstr "Una cuenta de Login.gov con verificación de identidad" msgid "eligibility.pages.start.oauth.details" -msgstr "TODO: Login.gov is a safe way to sign in to government services. Benefits uses Login.gov to verify your age. If you do not have an account already, you will be able to create one. You will also need to verify your identity, which will require these items:" +msgstr "Login.gov es una forma segura de iniciar sesión en servicios gubernamentales. Benefits utiliza Login.gov para verificar su edad. Si aún no tiene una cuenta, podrá crear una. También deberá verificar su identidad, lo que requerirá estos artículos:" msgid "eligibility.pages.start.oauth.link_text" -msgstr "TODO: Conozca más sobre Login.gov" +msgstr "Conozca más sobre Login.gov y cómo proporcionar una prueba de identidad." msgid "eligibility.pages.start.oauth.link_text[2]" msgstr "Don’t have access to these items?" msgid "eligibility.pages.start.oauth.required_items[0]" -msgstr "TODO: Your state-issued ID card" +msgstr "Su tarjeta de identificación emitida por el estado" msgid "eligibility.pages.start.oauth.required_items[1]" -msgstr "TODO: Your Social Security number" +msgstr "Su número de Seguro Social" msgid "eligibility.pages.start.oauth.required_items[2]" -msgstr "TODO: A phone number with a phone plan associated with your name" +msgstr "Un número de teléfono con un plan de teléfono asociado con su nombre" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signin" -msgstr "TODO: Crear una cuenta o iniciar sesión con" +msgstr "Comencemos con" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signout" @@ -429,12 +429,12 @@ msgstr "Tarjeta de conexión" #: benefits/enrollment/views.py:30 msgid "enrollment.pages.index.content_title" -msgstr "¡Genial! ¡Eres elegible para un descuento!" +msgstr "Vamos a vincular su tarjeta con su descuento." #: benefits/enrollment/views.py:32 msgid "enrollment.pages.index.p[0]" msgstr "" -"A continuación, tenemos que vincular su descuento a su tarjeta bancaria a través de nuestro socio de pago Littlepay." +"Ahora que ha iniciado sesión, podemos vincular su descuento de tránsito a su tarjeta a través de nuestro socio de pagos, LittlePay." #: benefits/enrollment/views.py:32 msgid "enrollment.pages.index.p[1]" @@ -472,14 +472,14 @@ msgid "core.buttons.retry" msgstr "Inténtalo de nuevo" msgid "enrollment.pages.success.title" -msgstr "Éxito" +msgstr "Cierre sesión" #: benefits/enrollment/views.py:147 benefits/enrollment/views.py:149 msgid "enrollment.pages.success.content_title" msgstr "¡Éxito! Su descuento está ahora vinculado a su tarjeta bancaria." msgid "enrollment.pages.success.logout.title" -msgstr "Ha sido desconectado exitosamente. Gracias por utilizar los beneficios de Cal-ITP!" +msgstr "Ha cerrado la sesión correctamente. ¡Gracias por usar los beneficios de Cal-ITP!" #: benefits/enrollment/views.py:150 msgid "enrollment.pages.success.p1" @@ -491,7 +491,7 @@ msgstr "Al abordar el tránsito, pague con esta misma tarjeta física y su descu msgid "enrollment.pages.success.p3%(help_link)s" msgstr "" -"Para obtener más información sobre lo que puede esperar al utilizar su tarjeta bancaria conectada, comuníquese con su proveedor de tránsito local o consulte " +"Para más información, consulte nuestro " "página de ayuda." msgid "enrollment.pages.success.p3" @@ -523,7 +523,7 @@ msgstr "" "Por favor, ingrese su número de licencia/identificación y su apellido abajo. Si tiene 65 años o más, podemos confirmar que es eligible para un descuento para personas mayores cuando viaja en transporte público. No guardamos la información que ingresa aquí." msgid "eligibility.pages.start.dmv.content_title" -msgstr "Hay tres pasos para vincular su descuento de tránsito a su tarjeta bancaria." +msgstr "Necesitará algunos artículos para conectar su descuento:" msgid "eligibility.forms.confirm.dmv.fields.sub" msgstr "Licencia de conducir de California o número de identificación" From 95544887b9067d7670fe84fdf9733b640880b1d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 22:17:48 +0000 Subject: [PATCH 080/106] chore(deps-dev): bump cypress from 10.1.0 to 10.2.0 in /tests/cypress Bumps [cypress](https://github.com/cypress-io/cypress) from 10.1.0 to 10.2.0. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/.releaserc.base.js) - [Commits](https://github.com/cypress-io/cypress/compare/v10.1.0...v10.2.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 a5f79ca358..28db684109 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": "^10.1.0" + "cypress": "^10.2.0" } }, "node_modules/@colors/colors": { @@ -523,9 +523,9 @@ } }, "node_modules/cypress": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.1.0.tgz", - "integrity": "sha512-aQ4JVZVib4Xd9FZW8IRZfKelUvqF4y5A+oUbNvn8TlsBmEfIg3m5Xd6Mt6PVU/jHiVJ9Psl905B3ZPnrDcmyuQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.2.0.tgz", + "integrity": "sha512-+i9lY5ENlfi2mJwsggzR+XASOIgMd7S/Gd3/13NCpv596n3YSplMAueBTIxNLcxDpTcIksp+9pM3UaDrJDpFqA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2202,9 +2202,9 @@ } }, "cypress": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.1.0.tgz", - "integrity": "sha512-aQ4JVZVib4Xd9FZW8IRZfKelUvqF4y5A+oUbNvn8TlsBmEfIg3m5Xd6Mt6PVU/jHiVJ9Psl905B3ZPnrDcmyuQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.2.0.tgz", + "integrity": "sha512-+i9lY5ENlfi2mJwsggzR+XASOIgMd7S/Gd3/13NCpv596n3YSplMAueBTIxNLcxDpTcIksp+9pM3UaDrJDpFqA==", "dev": true, "requires": { "@cypress/request": "^2.88.10", diff --git a/tests/cypress/package.json b/tests/cypress/package.json index 9e57cdb0c5..ec3dc94066 100644 --- a/tests/cypress/package.json +++ b/tests/cypress/package.json @@ -14,6 +14,6 @@ "license": "AGPL-3.0-or-later", "private": true, "devDependencies": { - "cypress": "^10.1.0" + "cypress": "^10.2.0" } } From 4685422a0144162c50001541e552a7fef05a9393 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 22:29:06 +0000 Subject: [PATCH 081/106] feat(copy): Spanish copy for new Help page questions --- benefits/core/templates/core/help.html | 2 +- benefits/locale/es/LC_MESSAGES/django.po | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/benefits/core/templates/core/help.html b/benefits/core/templates/core/help.html index 66a82e05a7..c5864f2a25 100644 --- a/benefits/core/templates/core/help.html +++ b/benefits/core/templates/core/help.html @@ -39,7 +39,7 @@

    {% translate "core.pages.help.login_gov_verify" %}

    {% translate "core.pages.help.login_gov_verify.p[0]" %}

    {% translate "core.pages.help.login_gov_verify.p[1]" %}

    {% translate "core.pages.help.login_gov_verify.p[2]" %}

    -

    {% blocktranslate with website="https://login.gov/help/" %}core.pages.help.login_gov_verify.p[3]{{ website }}{% endblocktranslate %}

    +

    {% blocktranslate with website="https://login.gov/help/verify-your-identity/how-to-verify-your-identity/" %}core.pages.help.login_gov_verify.p[3]{{ website }}{% endblocktranslate %}

    {% translate "core.pages.help.littlepay" %}

    {% translate "core.pages.help.littlepay.p[0]" %}

    diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index d385bdfad9..53daaf63e9 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -23,7 +23,7 @@ msgstr "" msgid "core.pages.help.about.p[1]" msgstr "" -"Nos asociamos con Login.gov para confirmar la edad para los descuentos basados en la edad y las agencias de transporte locales para confirmar los programas de descuento específicos. Una vez verificados, los descuentos se adjuntan a su tarjeta bancaria a través de nuestro socio de pago, Littlepay, para que cuando pague su viaje en transporte, obtenga su descuento automáticamente." +"Nos asociamos con Login.gov para confirmar la edad para los descuentos basados en la edad y nos asociamos con las agencias de transporte locales para confirmar los programas de descuento específicos. Una vez verificados, los descuentos se adjuntan a su tarjeta bancaria a través de nuestro socio de pago, Littlepay, para que cuando pague su viaje en transporte, obtenga su descuento automáticamente." msgid "core.pages.help.payment_options" msgstr "Opciones de pago" @@ -64,19 +64,19 @@ msgid "core.pages.help.login_gov.p[2][1]%(website)s" msgstr "o Login.gov: Centro de ayuda." msgid "core.pages.help.login_gov_verify" -msgstr "TODO" +msgstr "¿Cómo verifico mi identidad en Login.gov?" msgid "core.pages.help.login_gov_verify.p[0]" -msgstr "TODO" +msgstr "​​Para verificar su identidad, Login.gov le pide que suba una fotografía de su identificación emitida por el estado y comparta su número de teléfono y otra información personal, que luego se verificara con fuentes autorizadas. Estos requisitos son adicionales para cumplir con los requisitos para la autentificación de dos factores." msgid "core.pages.help.login_gov_verify.p[1]" -msgstr "TODO" +msgstr "No puede verificar su identidad en Login.gov sin una identificación emitida por el estado, que puede ser una licencia de conducir o una tarjeta de identificación emitida por el estado que no sea una licencia de conducir." msgid "core.pages.help.login_gov_verify.p[2]" -msgstr "TODO" +msgstr "Si no tiene un plan telefónico que esté a su nombre, Login.gov puede enviarle el código de verificación por correo, lo que toma aproximadamente de 3 a 5 días." msgid "core.pages.help.login_gov_verify.p[3]%(website)s" -msgstr "TODO" +msgstr "Verificar su identidad asegura que la persona adecuada tenga acceso a la información correcta. Estamos utilizando el proceso simple y seguro de Login.gov’s para mantener segura su información confidencial. Para obtener más información sobre la verificación de identidad en Login.gov, visite su Login.gov: Centro de ayuda." msgid "core.pages.help.littlepay" msgstr "¿Qué es Littlepay?" From 897f3c1402bf48c64a1dcc5299731c8c4636e63e Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 23:08:44 +0000 Subject: [PATCH 082/106] feat(copy): Update Spanish copy for Back button, 404 --- benefits/locale/es/LC_MESSAGES/django.po | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 53daaf63e9..a633f1c764 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -298,7 +298,7 @@ msgstr "Comencemos" #: benefits/core/views.py:86 benefits/core/views.py:106 msgid "core.buttons.back" -msgstr "Regresar" +msgstr "Regresar a la página principal" #: benefits/core/views.py:91 msgid "core.pages.help.p[1]" @@ -453,7 +453,7 @@ msgstr "¿Qué pasa si no tengo una tarjeta bancaria?" #: benefits/enrollment/views.py:127 benefits/enrollment/views.py:129 msgid "enrollment.pages.retry.title" -msgstr "No pudimos conectar tu tarjeta bancaria" +msgstr "No pudimos conectar tú tarjeta bancaria" #: benefits/enrollment/views.py:128 msgctxt "image alt text" @@ -464,12 +464,11 @@ msgstr "" #: benefits/enrollment/views.py:130 msgid "enrollment.pages.retry.p[0]" msgstr "" -"Puede intentarlo de nuevo o puede comunicarse con su proveedor de transporte " -"público para obtener ayuda." +"Puede intentarlo de nuevo o comunicarse con su proveedor de transporte local para obtener ayuda." #: benefits/enrollment/views.py:133 msgid "core.buttons.retry" -msgstr "Inténtalo de nuevo" +msgstr "Intentar otra vez" msgid "enrollment.pages.success.title" msgstr "Cierre sesión" From 1f74d1f2eccb754963a2ad805d327be45d8fc667 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 23:44:20 +0000 Subject: [PATCH 083/106] feat(404): Update 404 Copy for Eng and Sp --- benefits/locale/en/LC_MESSAGES/django.po | 4 ++-- benefits/locale/es/LC_MESSAGES/django.po | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 149009b8c7..bf822e7857 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -258,11 +258,11 @@ msgstr "Page not found" #: benefits/core/viewmodels.py:194 msgid "core.pages.not_found.content_title" -msgstr "We can’t find that page" +msgstr "Sorry! We can’t find that page." #: benefits/core/viewmodels.py:195 msgid "core.pages.not_found.p[0]" -msgstr "It looks like that page doesn’t exist or it was moved." +msgstr "The page you are looking for might be somewhere else or may not exist anymore." #: benefits/core/viewmodels.py:218 msgid "core.buttons.wait" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index a633f1c764..ebaa447bb6 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -225,7 +225,7 @@ msgstr "Icono de autobus con llanta ponchada" #: benefits/core/viewmodels.py:177 msgid "core.pages.server_error.content_title" -msgstr "El servicio no está funcionando" +msgstr "¡Lo sentimos! No podemos encontrar esa página" #: benefits/core/viewmodels.py:183 benefits/core/viewmodels.py:184 msgid "core.pages.server_error.title" @@ -245,11 +245,11 @@ msgstr "No podemos encontrar esa página" #: benefits/core/viewmodels.py:194 msgid "core.pages.not_found.content_title" -msgstr "No podemos encontrar esa página" +msgstr "¡Lo sentimos! No podemos encontrar esa página." #: benefits/core/viewmodels.py:195 msgid "core.pages.not_found.p[0]" -msgstr "Parece que esa página no existe o se ha movido." +msgstr "La página que estás buscando puede estar en otro lugar o puede que ya no exista." #: benefits/core/viewmodels.py:218 msgid "core.buttons.wait" From ebc68c87e5ed866ceac4cacc0fcef8b503103687 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 23:46:01 +0000 Subject: [PATCH 084/106] feat(copy): Update copy on 500 page for Eng and Sp --- benefits/locale/en/LC_MESSAGES/django.po | 6 +++--- benefits/locale/es/LC_MESSAGES/django.po | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index bf822e7857..aeb013e461 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -238,7 +238,7 @@ msgstr "Bus icon with flat tire" #: benefits/core/viewmodels.py:177 msgid "core.pages.server_error.content_title" -msgstr "Unfortunately, our system is having a problem right now." +msgstr "Sorry! Service for this site is down." #: benefits/core/viewmodels.py:183 benefits/core/viewmodels.py:184 msgid "core.pages.server_error.title" @@ -246,11 +246,11 @@ msgstr "Service is down" #: benefits/core/viewmodels.py:185 msgid "core.pages.server_error.p[0]" -msgstr "We should be back in operation soon!" +msgstr "We should be back in operation soon." #: benefits/core/viewmodels.py:185 msgid "core.pages.server_error.p[1]" -msgstr "Please check back later." +msgstr "Please refresh the page in a few minutes." #: benefits/core/viewmodels.py:193 msgid "core.pages.not_found.title" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index ebaa447bb6..fab04a0258 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -225,7 +225,7 @@ msgstr "Icono de autobus con llanta ponchada" #: benefits/core/viewmodels.py:177 msgid "core.pages.server_error.content_title" -msgstr "¡Lo sentimos! No podemos encontrar esa página" +msgstr "¡Lo sentimos! El servicio para este sitio está caído." #: benefits/core/viewmodels.py:183 benefits/core/viewmodels.py:184 msgid "core.pages.server_error.title" @@ -233,11 +233,11 @@ msgstr "El servicio no está funcionando" #: benefits/core/viewmodels.py:185 msgid "core.pages.server_error.p[0]" -msgstr "¡Pronto volveremos a estar en funcionamiento!" +msgstr "Deberíamos volver a funcionar pronto." #: benefits/core/viewmodels.py:185 msgid "core.pages.server_error.p[1]" -msgstr "Por favor, vuelva más tarde." +msgstr "Por favor, actualice la página en unos minutos." #: benefits/core/viewmodels.py:193 msgid "core.pages.not_found.title" From 055493e623624c13379d7b8e24c8c6e6785df3ca Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 22 Jun 2022 23:49:12 +0000 Subject: [PATCH 085/106] feat(copy): Update copy for Eligibility Error page on Eng and Sp --- benefits/locale/en/LC_MESSAGES/django.po | 6 +++--- benefits/locale/es/LC_MESSAGES/django.po | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index aeb013e461..a259bcc32e 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -418,11 +418,11 @@ msgstr "Sign out of Login.gov" #: benefits/eligibility/views.py:170 msgid "eligibility.pages.unverified.dmv.title" -msgstr "Age not confirmed" +msgstr "Eligibility Error" #: benefits/eligibility/views.py:171 msgid "eligibility.pages.unverified.dmv.content_title" -msgstr "We can’t confirm your age" +msgstr "Your eligibility could not be verified." #: benefits/eligibility/views.py:172 msgctxt "image alt text" @@ -431,7 +431,7 @@ msgstr "Identification card icon with question mark" #: benefits/eligibility/views.py:173 msgid "eligibility.pages.unverified.p[1]" -msgstr "Reach out to your transit provider for assistance." +msgstr "That’s okay! You may still be eligible for our program. Please reach out to your local transit provider for assistance." #: benefits/enrollment/templates/enrollment/success.html:7 msgctxt "image alt text" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index fab04a0258..d9cd47b765 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -403,11 +403,11 @@ msgstr "Cierre sesión de Login.gov" #: benefits/eligibility/views.py:170 msgid "eligibility.pages.unverified.dmv.title" -msgstr "Edad no confirmada" +msgstr "Error de elegibilidad" #: benefits/eligibility/views.py:171 msgid "eligibility.pages.unverified.dmv.content_title" -msgstr "No podemos confirmar su edad" +msgstr "No se pudo verificar su elegibilidad." #: benefits/eligibility/views.py:172 msgctxt "image alt text" @@ -416,7 +416,7 @@ msgstr "Icono de tarjeta de identificación con signo de interrogación" #: benefits/eligibility/views.py:173 msgid "eligibility.pages.unverified.p[1]" -msgstr "Comuníquese con su proveedor de transporte público para obtener ayuda." +msgstr "¡Esta bien! Aún puede ser elegible para nuestro programa. Comuníquese con su proveedor de tránsito local para obtener asistencia." #: benefits/enrollment/templates/enrollment/success.html:7 msgctxt "image alt text" From 891ac2a15fc18d310ce88a9a3a6c27977ce58b8c Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Thu, 16 Jun 2022 21:16:57 +0000 Subject: [PATCH 086/106] refactor: create dedicated file for generating logging config --- benefits/logging.py | 28 ++++++++++++++++++++++++++++ benefits/settings.py | 22 ++-------------------- 2 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 benefits/logging.py diff --git a/benefits/logging.py b/benefits/logging.py new file mode 100644 index 0000000000..3cc6b94cf8 --- /dev/null +++ b/benefits/logging.py @@ -0,0 +1,28 @@ +def get_config(level): + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "format": "[{asctime}] {levelname} {name}:{lineno} {message}", + "datefmt": "%d/%b/%Y %H:%M:%S", + "style": "{", + }, + }, + "handlers": { + "default": { + "class": "logging.StreamHandler", + "formatter": "default", + }, + }, + "root": { + "handlers": ["default"], + "level": level, + }, + "loggers": { + "django": { + "handlers": ["default"], + "propagate": False, + }, + }, + } diff --git a/benefits/settings.py b/benefits/settings.py index 402c2aa155..5f0b8dff38 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -2,6 +2,7 @@ Django settings for benefits project. """ import os +import benefits.logging def _filter_empty(ls): @@ -192,27 +193,8 @@ def _filter_empty(ls): STATIC_ROOT = os.path.join(BASE_DIR, "static") # Logging configuration - LOG_LEVEL = os.environ.get("DJANGO_LOG_LEVEL", "DEBUG" if DEBUG else "WARNING") -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "default": { - "format": "[{asctime}] {levelname} {name}:{lineno} {message}", - "datefmt": "%d/%b/%Y %H:%M:%S", - "style": "{", - }, - }, - "handlers": { - "default": {"class": "logging.StreamHandler", "formatter": "default"}, - }, - "root": { - "handlers": ["default"], - "level": LOG_LEVEL, - }, - "loggers": {"django": {"handlers": ["default"], "propagate": False}}, -} +LOGGING = benefits.logging.get_config(LOG_LEVEL) # Analytics configuration From 52bc34bd044537fc9e2ced570ec0e390524179d4 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Wed, 22 Jun 2022 05:30:51 +0000 Subject: [PATCH 087/106] feat: enable sending of logs to Azure Monitor --- benefits/logging.py | 30 ++++++++++++++++++--- docs/configuration/environment-variables.md | 10 +++++++ docs/deployment/infrastructure.md | 11 ++++++++ requirements.txt | 1 + 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/benefits/logging.py b/benefits/logging.py index 3cc6b94cf8..4ca910a5b2 100644 --- a/benefits/logging.py +++ b/benefits/logging.py @@ -1,5 +1,8 @@ +import os + + def get_config(level): - return { + config = { "version": 1, "disable_existing_loggers": False, "formatters": { @@ -10,19 +13,38 @@ def get_config(level): }, }, "handlers": { - "default": { + "console": { "class": "logging.StreamHandler", "formatter": "default", }, }, "root": { - "handlers": ["default"], + "handlers": ["console"], "level": level, }, "loggers": { "django": { - "handlers": ["default"], + "handlers": ["console"], "propagate": False, }, }, } + + if "APPLICATIONINSIGHTS_CONNECTION_STRING" in os.environ: + # enable Azure Insights logging + + # https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python#configure-logging-for-django-applications + config["handlers"]["azure"] = { + "class": "opencensus.ext.azure.log_exporter.AzureLogHandler", + # send all logs + "logging_sampling_rate": 1.0, + } + + # create custom logger + # https://github.com/census-instrumentation/opencensus-python/issues/1130#issuecomment-1161898856 + config["loggers"]["azure"] = { + "handlers": ["azure"], + "level": "INFO", + } + + return config diff --git a/docs/configuration/environment-variables.md b/docs/configuration/environment-variables.md index 0017c3a118..7bf9e28198 100644 --- a/docs/configuration/environment-variables.md +++ b/docs/configuration/environment-variables.md @@ -149,3 +149,13 @@ devcontainer, check the [`DJANGO_LOCAL_PORT`](#django_local_port). [deployment]: ../deployment/README.md [getting-started_create-env]: ../getting-started/README.md#create-an-environment-file + +## Azure + +### `APPLICATIONINSIGHTS_CONNECTION_STRING` + +!!! tldr "Azure docs" + + [Azure Monitor connection strings](https://docs.microsoft.com/en-us/azure/azure-monitor/app/sdk-connection-string) + +Enables [log collection](../../deployment/infrastructure/#logs). Set the value in quotes, e.g. `APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=…"`. diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index 16b0d8dcd4..22bcd9f0fb 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -63,6 +63,17 @@ flowchart LR We have [ping tests](https://docs.microsoft.com/en-us/azure/azure-monitor/app/monitor-web-app-availability) set up to notify about availability of the dev, test, and prod deployments. Alerts go to [#benefits-notify](https://cal-itp.slack.com/archives/C022HHSEE3F). +## Logs + +We send application logs to [Azure Monitor Logs](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-platform-logs). To find them: + +1. [Open Application Insights.](https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/microsoft.insights%2Fcomponents) +1. Click the resource corresponding to the environment. +1. In the navigation, under `Monitoring`, click `Logs`. +1. In the Query Editor, type `traces`, and click `Run`. + +You should see recent log output. Note [there is some latency](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-ingestion-time). + ## Making changes 1. Get access to the Azure account through the DevSecOps team. diff --git a/requirements.txt b/requirements.txt index aa5da1421c..0d75452db5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ Django==3.2.13 django-csp==3.7 git+https://github.com/cal-itp/eligibility-api#egg=eligibility_api gunicorn==20.1.0 +opencensus-ext-azure==1.1.4 requests==2.28.0 six==1.16.0 From 4737e56f91754f21f7da2facdb2820905662c59e Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 23 Jun 2022 18:03:17 +0000 Subject: [PATCH 088/106] fix: remove extra space --- benefits/locale/es/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 855730e62d..ca650c2853 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -23,7 +23,7 @@ msgstr "" msgid "core.pages.help.about.p[1]" msgstr "" -"Nos asociamos con Login.gov para confirmar la edad para los descuentos basados en la edad y nos asociamos con las agencias de transporte locales para confirmar los programas de descuento específicos. Una vez verificados, los descuentos se adjuntan a su tarjeta bancaria a través de nuestro socio de pago, Littlepay, para que cuando pague su viaje en transporte, obtenga su descuento automáticamente." +"Nos asociamos con Login.gov para confirmar la edad para los descuentos basados en la edad y nos asociamos con las agencias de transporte locales para confirmar los programas de descuento específicos. Una vez verificados, los descuentos se adjuntan a su tarjeta bancaria a través de nuestro socio de pago, Littlepay, para que cuando pague su viaje en transporte, obtenga su descuento automáticamente." msgid "core.pages.help.payment_options" msgstr "Opciones de pago" From 244c6c2a1ee587e2c7020363a47cf5493beb48e5 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 23 Jun 2022 18:06:57 +0000 Subject: [PATCH 089/106] fix(Spanish): separate out text into 2 paragraphs, use oauth copy --- benefits/locale/es/LC_MESSAGES/django.po | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index ca650c2853..2c13a44a2f 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -391,7 +391,7 @@ msgid "eligibility.pages.start.oauth.required_items[2]" msgstr "Un número de teléfono con un plan de teléfono asociado con su nombre" msgid "eligibility.pages.start.oauth.content_title" -msgstr "TODO: You will need a few items to connect your discount:" +msgstr "Necesitará algunos artículos para conectar su descuento:" #: benefits/eligibility/views.py:80 msgid "eligibility.buttons.signin" @@ -411,8 +411,7 @@ msgstr "No se pudo verificar su elegibilidad." msgid "eligibility.pages.unverified.oauth.p[0]" msgstr "" -"TODO: You may still be eligible for a discount, but we can’t verify your age " -"with Login.gov." +"¡Esta bien! Aún puede ser elegible para nuestro programa. " #: benefits/eligibility/views.py:172 msgctxt "image alt text" @@ -421,7 +420,7 @@ msgstr "Icono de tarjeta de identificación con signo de interrogación" #: benefits/eligibility/views.py:173 msgid "eligibility.pages.unverified.p[1]" -msgstr "¡Esta bien! Aún puede ser elegible para nuestro programa. Comuníquese con su proveedor de tránsito local para obtener asistencia." +msgstr "Comuníquese con su proveedor de tránsito local para obtener asistencia." #: benefits/enrollment/templates/enrollment/success.html:7 msgctxt "image alt text" From 2ad4e8a9faea114064067c84924eb53c8844de9f Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 23 Jun 2022 18:10:28 +0000 Subject: [PATCH 090/106] fix(Connect Card): verify Connect Card copy for Eng and Sp --- benefits/locale/en/LC_MESSAGES/django.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 564d3eb13f..fdc27150c4 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -446,12 +446,12 @@ msgstr "Connect Card" #: benefits/enrollment/views.py:30 msgid "enrollment.pages.index.content_title" -msgstr "Great! You’re eligible for a discount!" +msgstr "Let’s link your card to your discount." #: benefits/enrollment/views.py:32 msgid "enrollment.pages.index.p[0]" msgstr "" -"Next, we need to link your discount to your bank-issued card through our payment partner Littlepay." +"Now that you are logged in we can link your transit discount to your card through our payment partner, Littlepay." #: benefits/enrollment/views.py:32 msgid "enrollment.pages.index.p[1]" From 7e3012e59154e5d52104ec858929b386f9b67e53 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Wed, 22 Jun 2022 06:00:01 +0000 Subject: [PATCH 091/106] feat: send incoming requests to Azure Monitor logs --- benefits/logging.py | 9 +++------ benefits/settings.py | 21 +++++++++++++++++++-- docs/deployment/infrastructure.md | 3 ++- requirements.txt | 1 + tests/pytest/test_logging.py | 13 +++++++++++++ 5 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 tests/pytest/test_logging.py diff --git a/benefits/logging.py b/benefits/logging.py index 4ca910a5b2..a2dbfafaff 100644 --- a/benefits/logging.py +++ b/benefits/logging.py @@ -1,7 +1,4 @@ -import os - - -def get_config(level): +def get_config(level="INFO", enable_azure=False): config = { "version": 1, "disable_existing_loggers": False, @@ -30,7 +27,7 @@ def get_config(level): }, } - if "APPLICATIONINSIGHTS_CONNECTION_STRING" in os.environ: + if enable_azure: # enable Azure Insights logging # https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python#configure-logging-for-django-applications @@ -44,7 +41,7 @@ def get_config(level): # https://github.com/census-instrumentation/opencensus-python/issues/1130#issuecomment-1161898856 config["loggers"]["azure"] = { "handlers": ["azure"], - "level": "INFO", + "level": level, } return config diff --git a/benefits/settings.py b/benefits/settings.py index 5f0b8dff38..82566a2f97 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -65,7 +65,24 @@ def _filter_empty(ls): ) if DEBUG: - MIDDLEWARE.extend(["benefits.core.middleware.DebugSession"]) + MIDDLEWARE.append("benefits.core.middleware.DebugSession") + + +# Azure Insights +# https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python-request#tracking-django-applications + +ENABLE_AZURE_INSIGHTS = "APPLICATIONINSIGHTS_CONNECTION_STRING" in os.environ +if ENABLE_AZURE_INSIGHTS: + MIDDLEWARE.append("opencensus.ext.django.middleware.OpencensusMiddleware") + +# only used if enabled above +OPENCENSUS = { + "TRACE": { + "SAMPLER": "opencensus.trace.samplers.ProbabilitySampler(rate=1)", + "EXPORTER": "opencensus.ext.azure.trace_exporter.AzureExporter()", + } +} + CSRF_COOKIE_AGE = None CSRF_COOKIE_SAMESITE = "Strict" @@ -194,7 +211,7 @@ def _filter_empty(ls): # Logging configuration LOG_LEVEL = os.environ.get("DJANGO_LOG_LEVEL", "DEBUG" if DEBUG else "WARNING") -LOGGING = benefits.logging.get_config(LOG_LEVEL) +LOGGING = benefits.logging.get_config(LOG_LEVEL, enable_azure=ENABLE_AZURE_INSIGHTS) # Analytics configuration diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index 22bcd9f0fb..0e7a5407fb 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -70,7 +70,8 @@ We send application logs to [Azure Monitor Logs](https://docs.microsoft.com/en-u 1. [Open Application Insights.](https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/microsoft.insights%2Fcomponents) 1. Click the resource corresponding to the environment. 1. In the navigation, under `Monitoring`, click `Logs`. -1. In the Query Editor, type `traces`, and click `Run`. +1. In the Query Editor, type `requests` or `traces`, and click `Run`. + - [What each means](https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python#telemetry-type-mappings) You should see recent log output. Note [there is some latency](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-ingestion-time). diff --git a/requirements.txt b/requirements.txt index 0d75452db5..475e09b8fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,6 @@ django-csp==3.7 git+https://github.com/cal-itp/eligibility-api#egg=eligibility_api gunicorn==20.1.0 opencensus-ext-azure==1.1.4 +opencensus-ext-django==0.7.6 requests==2.28.0 six==1.16.0 diff --git a/tests/pytest/test_logging.py b/tests/pytest/test_logging.py new file mode 100644 index 0000000000..2fdcb06d9c --- /dev/null +++ b/tests/pytest/test_logging.py @@ -0,0 +1,13 @@ +from benefits.logging import get_config + + +def test_get_config_no_azure(): + config = get_config() + assert "azure" not in config["handlers"] + assert "azure" not in config["loggers"] + + +def test_get_config_with_azure(): + config = get_config(enable_azure=True) + assert "azure" in config["handlers"] + assert "azure" in config["loggers"] From 4b471640cef5580ce20ff32db3c820302f619bd8 Mon Sep 17 00:00:00 2001 From: machiko Date: Thu, 23 Jun 2022 12:15:39 -0700 Subject: [PATCH 092/106] Update benefits/locale/es/LC_MESSAGES/django.po Co-authored-by: Kegan Maher --- benefits/locale/es/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 2c13a44a2f..22d2b0d188 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -438,7 +438,7 @@ msgstr "Vamos a vincular su tarjeta con su descuento." #: benefits/enrollment/views.py:32 msgid "enrollment.pages.index.p[0]" msgstr "" -"Ahora que ha iniciado sesión, podemos vincular su descuento de tránsito a su tarjeta a través de nuestro socio de pagos, LittlePay." +"Ahora que ha iniciado sesión, podemos vincular su descuento de tránsito a su tarjeta a través de nuestro socio de pagos, Littlepay." #: benefits/enrollment/views.py:32 msgid "enrollment.pages.index.p[1]" From d8a83bd03e634057cf0c29a79173088878172891 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 23 Jun 2022 20:35:03 +0000 Subject: [PATCH 093/106] fix(Help): use English and Spanish-specific Login.gov help links on Help page --- benefits/core/templates/core/help.html | 4 ++-- benefits/locale/en/LC_MESSAGES/django.po | 7 ++----- benefits/locale/es/LC_MESSAGES/django.po | 7 ++----- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/benefits/core/templates/core/help.html b/benefits/core/templates/core/help.html index c5864f2a25..d47aea0928 100644 --- a/benefits/core/templates/core/help.html +++ b/benefits/core/templates/core/help.html @@ -31,8 +31,8 @@

    {% translate "core.pages.help.login_gov" %}

    {% translate "core.pages.help.login_gov.p[0]" %}

    {% translate "core.pages.help.login_gov.p[1]" %}

    - {% blocktranslate with website="https://login.gov"%}core.pages.help.login_gov.p[2][0]{{ website }}{% endblocktranslate %} - {% blocktranslate with website="https://login.gov/help/"%}core.pages.help.login_gov.p[2][1]{{ website }}{% endblocktranslate %} + {% translate "core.pages.help.login_gov.p[2]" %} + {% comment %} {% blocktranslate with website="https://login.gov/help/"%}core.pages.help.login_gov.p[2][1]{{ website }}{% endblocktranslate %} {% endcomment %}

    {% translate "core.pages.help.login_gov_verify" %}

    diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index fdc27150c4..4856fbdab6 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -69,12 +69,9 @@ msgstr "Creating an account is simple. You will need an email address and method "authenticating your account—which will use either a landline, mobile phone, or " "backup codes that must be printed or written down." -msgid "core.pages.help.login_gov.p[2][0]%(website)s" +msgid "core.pages.help.login_gov.p[2]" msgstr "To learn more about Login.gov, please visit the " -"Login.gov website " - -msgid "core.pages.help.login_gov.p[2][1]%(website)s" -msgstr "or the Login.gov Help Center." +"Login.gov website or the Login.gov Help Center." msgid "core.pages.help.login_gov_verify" msgstr "How do I verify my identity on Login.gov?" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 22d2b0d188..37c361a5ef 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -56,12 +56,9 @@ msgstr "Login.gov es una forma segura de iniciar sesión en las agencias guberna msgid "core.pages.help.login_gov.p[1]" msgstr "Crear una cuenta es simple. Necesitará una dirección de correo electrónico y un método para autenticar su cuenta—que utilizará un teléfono fijo o un teléfono móvil o códigos de seguridad que deben imprimirse o anotarse." -msgid "core.pages.help.login_gov.p[2][0]%(website)s" +msgid "core.pages.help.login_gov.p[2]" msgstr "Para obtener más información sobre Login.gov, visite " -"Login.gov " - -msgid "core.pages.help.login_gov.p[2][1]%(website)s" -msgstr "o Login.gov: Centro de ayuda." +"Login.gov o Login.gov: Centro de ayuda." msgid "core.pages.help.login_gov_verify" msgstr "¿Cómo verifico mi identidad en Login.gov?" From 4c78d8c1898a4b80cc77cd994b4793307cde6158 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 23 Jun 2022 20:38:53 +0000 Subject: [PATCH 094/106] chore: remove comment --- benefits/core/templates/core/help.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/benefits/core/templates/core/help.html b/benefits/core/templates/core/help.html index d47aea0928..04ad198d95 100644 --- a/benefits/core/templates/core/help.html +++ b/benefits/core/templates/core/help.html @@ -30,10 +30,7 @@

    {% translate "core.pages.help.payment_options" %}

    {% translate "core.pages.help.login_gov" %}

    {% translate "core.pages.help.login_gov.p[0]" %}

    {% translate "core.pages.help.login_gov.p[1]" %}

    -

    - {% translate "core.pages.help.login_gov.p[2]" %} - {% comment %} {% blocktranslate with website="https://login.gov/help/"%}core.pages.help.login_gov.p[2][1]{{ website }}{% endblocktranslate %} {% endcomment %} -

    +

    {% translate "core.pages.help.login_gov.p[2]" %}

    {% translate "core.pages.help.login_gov_verify" %}

    {% translate "core.pages.help.login_gov_verify.p[0]" %}

    From 3ecaf94a8c27cadbb24ce9a71e1da13f86625aa1 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 23 Jun 2022 20:40:10 +0000 Subject: [PATCH 095/106] fix(Help): use English and Spanish-specific Login.gov Help Center Verify page links --- benefits/core/templates/core/help.html | 2 +- benefits/locale/en/LC_MESSAGES/django.po | 4 ++-- benefits/locale/es/LC_MESSAGES/django.po | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benefits/core/templates/core/help.html b/benefits/core/templates/core/help.html index 04ad198d95..b465058051 100644 --- a/benefits/core/templates/core/help.html +++ b/benefits/core/templates/core/help.html @@ -36,7 +36,7 @@

    {% translate "core.pages.help.login_gov_verify" %}

    {% translate "core.pages.help.login_gov_verify.p[0]" %}

    {% translate "core.pages.help.login_gov_verify.p[1]" %}

    {% translate "core.pages.help.login_gov_verify.p[2]" %}

    -

    {% blocktranslate with website="https://login.gov/help/verify-your-identity/how-to-verify-your-identity/" %}core.pages.help.login_gov_verify.p[3]{{ website }}{% endblocktranslate %}

    +

    {% translate "core.pages.help.login_gov_verify.p[3]" %}

    {% translate "core.pages.help.littlepay" %}

    {% translate "core.pages.help.littlepay.p[0]" %}

    diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index 4856fbdab6..12fa02e6a9 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -85,8 +85,8 @@ msgstr "You cannot verify your identity on Login.gov without a state-issued ID, msgid "core.pages.help.login_gov_verify.p[2]" msgstr "If you do not have a phone plan that is in your name, Login.gov can send you the verification code by mail which takes approximately 3-5 days." -msgid "core.pages.help.login_gov_verify.p[3]%(website)s" -msgstr "Verifying your identity makes sure the right person has access to the right information. We are using Login.gov’s simple and secure process to keep your sensitive information safe. To learn more about identity verification on Login.gov, please visit their Help Center." +msgid "core.pages.help.login_gov_verify.p[3]" +msgstr "Verifying your identity makes sure the right person has access to the right information. We are using Login.gov’s simple and secure process to keep your sensitive information safe. To learn more about identity verification on Login.gov, please visit their Help Center." msgid "core.pages.help.littlepay" msgstr "What is Littlepay?" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 37c361a5ef..2764233a14 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -72,8 +72,8 @@ msgstr "No puede verificar su identidad en Login.gov sin una identificación emi msgid "core.pages.help.login_gov_verify.p[2]" msgstr "Si no tiene un plan telefónico que esté a su nombre, Login.gov puede enviarle el código de verificación por correo, lo que toma aproximadamente de 3 a 5 días." -msgid "core.pages.help.login_gov_verify.p[3]%(website)s" -msgstr "Verificar su identidad asegura que la persona adecuada tenga acceso a la información correcta. Estamos utilizando el proceso simple y seguro de Login.gov’s para mantener segura su información confidencial. Para obtener más información sobre la verificación de identidad en Login.gov, visite su Login.gov: Centro de ayuda." +msgid "core.pages.help.login_gov_verify.p[3]" +msgstr "Verificar su identidad asegura que la persona adecuada tenga acceso a la información correcta. Estamos utilizando el proceso simple y seguro de Login.gov’s para mantener segura su información confidencial. Para obtener más información sobre la verificación de identidad en Login.gov, visite su Login.gov: Centro de ayuda." msgid "core.pages.help.littlepay" msgstr "¿Qué es Littlepay?" From a52a6c70c358b9d9bee72f9ef252f02bcb1552f0 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Thu, 23 Jun 2022 21:26:22 +0000 Subject: [PATCH 096/106] chore: set up test instance of Azure Insights Seems like we want one corresponding to each (persistent) slot. --- terraform/monitor.tf | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/terraform/monitor.tf b/terraform/monitor.tf index 7dd763e456..6147ca0cbf 100644 --- a/terraform/monitor.tf +++ b/terraform/monitor.tf @@ -21,6 +21,19 @@ resource "azurerm_application_insights" "dev" { } } +resource "azurerm_application_insights" "test" { + name = "AI-CDT-PUB-VIP-CALITP-P-001-test" + application_type = "web" + location = data.azurerm_resource_group.prod.location + resource_group_name = data.azurerm_resource_group.prod.name + sampling_percentage = 0 + workspace_id = azurerm_log_analytics_workspace.main.id + + lifecycle { + ignore_changes = [tags] + } +} + resource "azurerm_application_insights" "prod" { name = "AI-CDT-PUB-VIP-CALITP-P-001" application_type = "web" From 0d95d978f4be1945b4bc0faa1e2ba939eeca0f6e Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Thu, 23 Jun 2022 20:40:50 +0000 Subject: [PATCH 097/106] feat: send exceptions to Azure Monitor --- benefits/core/middleware.py | 15 +++++++++++++++ benefits/settings.py | 7 ++++++- docs/deployment/infrastructure.md | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/benefits/core/middleware.py b/benefits/core/middleware.py index 70e761888f..47daf77097 100644 --- a/benefits/core/middleware.py +++ b/benefits/core/middleware.py @@ -147,3 +147,18 @@ def process_view(self, request, view_func, view_args, view_kwargs): return None return redirect("oauth:login") + + +# https://github.com/census-instrumentation/opencensus-python/issues/766 +class LogErrorToAzure(MiddlewareMixin): + def __init__(self, get_response): + self.get_response = get_response + # wait to do this here to be sure the handler is initialized + self.azure_logger = logging.getLogger("azure") + + def process_exception(self, request, exception): + # https://stackoverflow.com/a/45532289 + msg = getattr(exception, "message", repr(exception)) + self.azure_logger.exception(msg, exc_info=exception) + + return None diff --git a/benefits/settings.py b/benefits/settings.py index 82566a2f97..d4e1532bfb 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -73,7 +73,12 @@ def _filter_empty(ls): ENABLE_AZURE_INSIGHTS = "APPLICATIONINSIGHTS_CONNECTION_STRING" in os.environ if ENABLE_AZURE_INSIGHTS: - MIDDLEWARE.append("opencensus.ext.django.middleware.OpencensusMiddleware") + MIDDLEWARE.extend( + [ + "opencensus.ext.django.middleware.OpencensusMiddleware", + "benefits.core.middleware.LogErrorToAzure", + ] + ) # only used if enabled above OPENCENSUS = { diff --git a/docs/deployment/infrastructure.md b/docs/deployment/infrastructure.md index 0e7a5407fb..e1aebc439f 100644 --- a/docs/deployment/infrastructure.md +++ b/docs/deployment/infrastructure.md @@ -75,6 +75,8 @@ We send application logs to [Azure Monitor Logs](https://docs.microsoft.com/en-u You should see recent log output. Note [there is some latency](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-ingestion-time). +See [`Failures`](https://docs.microsoft.com/en-us/azure/azure-monitor/app/asp-net-exceptions#diagnose-failures-using-the-azure-portal) in the sidebar (or `exceptions` under `Logs`) for application errors/exceptions. + ## Making changes 1. Get access to the Azure account through the DevSecOps team. From 6aa01191be5f49d6e611d2d6fce2ca6d0d9b950f Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Thu, 23 Jun 2022 23:48:52 +0000 Subject: [PATCH 098/106] chore: clarify what `sticky_settings` mean --- terraform/app_service.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index bc6089adde..ced7e52365 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -41,6 +41,12 @@ resource "azurerm_linux_web_app" "main" { } sticky_settings { + # Confusingly named argument; these are settings / environment variables that should be unique to each slot. Also known as "deployment slot settings". + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#app_setting_names + # https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots#which-settings-are-swapped + # + # The APPINSIGHTS_* ones get populated through auto-instrumentation. + # https://docs.microsoft.com/en-us/azure/azure-monitor/app/azure-web-apps#enable-application-insights app_setting_names = [ "APPINSIGHTS_INSTRUMENTATIONKEY", "APPINSIGHTS_PROFILERFEATURE_VERSION", From 8ef781b64b4415df1efaf8c7b00e63faad7270ff Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Thu, 23 Jun 2022 23:59:26 +0000 Subject: [PATCH 099/106] chore: update list of sticky settings ANALYTICS_KEY was added manually, the DJANGO_OAUTH_* ones are no longer needed. --- terraform/app_service.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index ced7e52365..aa23492449 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -48,6 +48,7 @@ resource "azurerm_linux_web_app" "main" { # The APPINSIGHTS_* ones get populated through auto-instrumentation. # https://docs.microsoft.com/en-us/azure/azure-monitor/app/azure-web-apps#enable-application-insights app_setting_names = [ + "ANALYTICS_KEY", "APPINSIGHTS_INSTRUMENTATIONKEY", "APPINSIGHTS_PROFILERFEATURE_VERSION", "APPINSIGHTS_SNAPSHOTFEATURE_VERSION", @@ -55,8 +56,6 @@ resource "azurerm_linux_web_app" "main" { "APPLICATIONINSIGHTS_CONNECTION_STRING", "ApplicationInsightsAgent_EXTENSION_VERSION", "DJANGO_INIT_PATH", - "DJANGO_OAUTH_CLIENT_ID", - "DJANGO_OAUTH_CLIENT_NAME", "DiagnosticServices_EXTENSION_VERSION", "InstrumentationEngine_EXTENSION_VERSION", "SnapshotDebugger_EXTENSION_VERSION", From 56165f62cf6e337b51ccebe866448b74bccbac15 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Thu, 23 Jun 2022 22:34:00 -0400 Subject: [PATCH 100/106] chore: update list of deployment slot settings Removes a bunch that were auto-populated but aren't needed, and adds a couple that should be slot-specific. --- terraform/app_service.tf | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index aa23492449..28581ecf70 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -44,26 +44,13 @@ resource "azurerm_linux_web_app" "main" { # Confusingly named argument; these are settings / environment variables that should be unique to each slot. Also known as "deployment slot settings". # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#app_setting_names # https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots#which-settings-are-swapped - # - # The APPINSIGHTS_* ones get populated through auto-instrumentation. - # https://docs.microsoft.com/en-us/azure/azure-monitor/app/azure-web-apps#enable-application-insights app_setting_names = [ "ANALYTICS_KEY", - "APPINSIGHTS_INSTRUMENTATIONKEY", - "APPINSIGHTS_PROFILERFEATURE_VERSION", - "APPINSIGHTS_SNAPSHOTFEATURE_VERSION", - "APPLICATIONINSIGHTS_CONFIGURATION_CONTENT", "APPLICATIONINSIGHTS_CONNECTION_STRING", - "ApplicationInsightsAgent_EXTENSION_VERSION", + "DJANGO_ALLOWED_HOSTS", "DJANGO_INIT_PATH", - "DiagnosticServices_EXTENSION_VERSION", - "InstrumentationEngine_EXTENSION_VERSION", - "SnapshotDebugger_EXTENSION_VERSION", - "XDT_MicrosoftApplicationInsightsJava", - "XDT_MicrosoftApplicationInsights_BaseExtensions", - "XDT_MicrosoftApplicationInsights_Mode", - "XDT_MicrosoftApplicationInsights_NodeJS", - "XDT_MicrosoftApplicationInsights_PreemptSdk", + "DJANGO_LOG_LEVEL", + "DJANGO_TRUSTED_ORIGINS", ] } From d3b917e90d1b55f7c642e483eb54546bb7c6dbef Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 24 Jun 2022 13:28:15 -0400 Subject: [PATCH 101/106] chore: re-add auto-instrumented variables to deployment slot settings These seem to be necessary for connecting the App Service instance to Application Insights. --- terraform/app_service.tf | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 28581ecf70..75ae07d9f5 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -40,17 +40,34 @@ resource "azurerm_linux_web_app" "main" { } } + # Confusingly named argument; these are settings / environment variables that should be unique to each slot. Also known as "deployment slot settings". + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#app_setting_names + # https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots#which-settings-are-swapped sticky_settings { - # Confusingly named argument; these are settings / environment variables that should be unique to each slot. Also known as "deployment slot settings". - # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#app_setting_names - # https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots#which-settings-are-swapped app_setting_names = [ + # custom config "ANALYTICS_KEY", - "APPLICATIONINSIGHTS_CONNECTION_STRING", "DJANGO_ALLOWED_HOSTS", "DJANGO_INIT_PATH", "DJANGO_LOG_LEVEL", "DJANGO_TRUSTED_ORIGINS", + + # populated through auto-instrumentation + # https://docs.microsoft.com/en-us/azure/azure-monitor/app/azure-web-apps#enable-application-insights + "APPINSIGHTS_INSTRUMENTATIONKEY", + "APPINSIGHTS_PROFILERFEATURE_VERSION", + "APPINSIGHTS_SNAPSHOTFEATURE_VERSION", + "APPLICATIONINSIGHTS_CONFIGURATION_CONTENT", + "APPLICATIONINSIGHTS_CONNECTION_STRING", + "ApplicationInsightsAgent_EXTENSION_VERSION", + "DiagnosticServices_EXTENSION_VERSION", + "InstrumentationEngine_EXTENSION_VERSION", + "SnapshotDebugger_EXTENSION_VERSION", + "XDT_MicrosoftApplicationInsights_BaseExtensions", + "XDT_MicrosoftApplicationInsights_Mode", + "XDT_MicrosoftApplicationInsights_NodeJS", + "XDT_MicrosoftApplicationInsights_PreemptSdk", + "XDT_MicrosoftApplicationInsightsJava", ] } From e6dd2dcc2855b2cf3a05dd8e9f6b39b4290d3d69 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 24 Jun 2022 13:31:39 -0400 Subject: [PATCH 102/106] feat: deploy dev on all pushes The path filter was potentially making deploys not happen when we would expect them; this will avoid those cases by always deploying. --- .github/workflows/deploy-dev.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 81946d59d6..e23ac3a571 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -5,14 +5,6 @@ on: push: branches: - dev - paths: - - '.github/workflows/deploy-*.yml' - - 'benefits/**' - - 'bin/**' - - Dockerfile - - gunicorn.conf.py - - nginx.conf - - requirements.txt defaults: run: @@ -43,6 +35,7 @@ jobs: uses: docker/build-push-action@v3 with: builder: ${{ steps.buildx.outputs.name }} + build-args: GIT-SHA=${{ github.sha }} cache-from: type=gha,scope=cal-itp cache-to: type=gha,scope=cal-itp,mode=max context: . From f3f3b68d7e2001de868f8a8b6fe113a6b4858e91 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 24 Jun 2022 14:37:18 -0400 Subject: [PATCH 103/106] chore: add debugging messages around Azure configuration --- benefits/logging.py | 2 ++ benefits/settings.py | 1 + 2 files changed, 3 insertions(+) diff --git a/benefits/logging.py b/benefits/logging.py index a2dbfafaff..8c92e13ce7 100644 --- a/benefits/logging.py +++ b/benefits/logging.py @@ -27,6 +27,8 @@ def get_config(level="INFO", enable_azure=False): }, } + print("ENABLE AZURE: ", enable_azure) + if enable_azure: # enable Azure Insights logging diff --git a/benefits/settings.py b/benefits/settings.py index d4e1532bfb..daa49734c9 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -72,6 +72,7 @@ def _filter_empty(ls): # https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python-request#tracking-django-applications ENABLE_AZURE_INSIGHTS = "APPLICATIONINSIGHTS_CONNECTION_STRING" in os.environ +print("ENABLE_AZURE_INSIGHTS: ", ENABLE_AZURE_INSIGHTS) if ENABLE_AZURE_INSIGHTS: MIDDLEWARE.extend( [ From c7fc73efd37f83dfa94d140742b4efec744db4a6 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 24 Jun 2022 19:12:43 +0000 Subject: [PATCH 104/106] chore: make single workflow for deployment This way we can avoid having to copy changes across them. --- .github/workflows/deploy-prod.yml | 44 ------------------- .github/workflows/deploy-test.yml | 44 ------------------- .../workflows/{deploy-dev.yml => deploy.yml} | 10 +++-- 3 files changed, 6 insertions(+), 92 deletions(-) delete mode 100644 .github/workflows/deploy-prod.yml delete mode 100644 .github/workflows/deploy-test.yml rename .github/workflows/{deploy-dev.yml => deploy.yml} (83%) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml deleted file mode 100644 index f8d17edab4..0000000000 --- a/.github/workflows/deploy-prod.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Deploy (prod) - -on: - workflow_dispatch: - push: - branches: - - prod - -defaults: - run: - shell: bash - -jobs: - deploy: - runs-on: ubuntu-latest - environment: prod - concurrency: prod - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v2 - - - name: Build, tag, and push image to GitHub Container Registry - uses: docker/build-push-action@v3 - with: - builder: ${{ steps.buildx.outputs.name }} - cache-from: type=gha,scope=cal-itp - cache-to: type=gha,scope=cal-itp,mode=max - context: . - push: true - tags: | - ghcr.io/${{ github.repository }}:prod - ghcr.io/${{ github.repository }}:${{ github.sha }} diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml deleted file mode 100644 index ce53585801..0000000000 --- a/.github/workflows/deploy-test.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Deploy (test) - -on: - workflow_dispatch: - push: - branches: - - test - -defaults: - run: - shell: bash - -jobs: - deploy: - runs-on: ubuntu-latest - environment: test - concurrency: test - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v2 - - - name: Build, tag, and push image to GitHub Container Registry - uses: docker/build-push-action@v3 - with: - builder: ${{ steps.buildx.outputs.name }} - cache-from: type=gha,scope=cal-itp - cache-to: type=gha,scope=cal-itp,mode=max - context: . - push: true - tags: | - ghcr.io/${{ github.repository }}:test - ghcr.io/${{ github.repository }}:${{ github.sha }} diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy.yml similarity index 83% rename from .github/workflows/deploy-dev.yml rename to .github/workflows/deploy.yml index e23ac3a571..306d4c39a0 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy.yml @@ -1,10 +1,12 @@ -name: Deploy (dev) +name: Deploy on: workflow_dispatch: push: branches: - dev + - test + - prod defaults: run: @@ -13,8 +15,8 @@ defaults: jobs: deploy: runs-on: ubuntu-latest - environment: dev - concurrency: dev + environment: ${{ github.ref_name }} + concurrency: ${{ github.ref_name }} steps: - name: Checkout @@ -41,5 +43,5 @@ jobs: context: . push: true tags: | - ghcr.io/${{ github.repository }}:dev + ghcr.io/${{ github.repository }}:${{ github.ref_name }} ghcr.io/${{ github.repository }}:${{ github.sha }} From 3fe051a79218dd640f3e4542ce7d9a981f8515cd Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 24 Jun 2022 19:15:25 +0000 Subject: [PATCH 105/106] chore: allow the SHA of the deployed image to be viewed This will help with confirming changes have been deployed. --- .github/workflows/deploy.yml | 3 +++ .gitignore | 1 + docs/deployment/README.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 306d4c39a0..b23a79dfcc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,6 +22,9 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Write commit SHA to file + run: echo "${{ github.sha }}" >> benefits/static/sha.txt + - name: Docker Login to GitHub Container Registry uses: docker/login-action@v2 with: diff --git a/.gitignore b/.gitignore index c2e99a47b4..307ea6946b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ fixtures/*.json !fixtures/??_*.json static/ !benefits/static +benefits/static/sha.txt __pycache__/ .coverage .DS_Store diff --git a/docs/deployment/README.md b/docs/deployment/README.md index 6b9033a538..6db7df75ed 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -17,6 +17,8 @@ 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. +You can view what Git commit is deployed for a given environment by visitng the URL path `/static/sha.txt`. + ## Configuration [Configuration settings](../configuration/README.md) are stored as Application Configuration variables in Azure. From fd77843f7f1510fccce18b17aa456b52084e69f4 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Mon, 27 Jun 2022 17:25:13 +0000 Subject: [PATCH 106/106] chore: enable versioning on the storage account For safety, in case a fixture configuration change breaks something. --- terraform/storage.tf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/terraform/storage.tf b/terraform/storage.tf index c5d0d5cae9..737b29434b 100644 --- a/terraform/storage.tf +++ b/terraform/storage.tf @@ -5,6 +5,11 @@ resource "azurerm_storage_account" "main" { account_tier = "Standard" account_replication_type = "RAGRS" + blob_properties { + last_access_time_enabled = true + versioning_enabled = true + } + lifecycle { ignore_changes = [tags] }