From 3513ce1faf7e01c4deb637e5bb755f61f204ce36 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:55:32 -0600 Subject: [PATCH 01/20] Add no domains view --- src/registrar/config/urls.py | 5 ++++ .../templates/includes/domains_table.html | 14 ++++----- .../templates/includes/header_extended.html | 17 ++++++----- .../templates/no_portfolio_domains.html | 26 ++++++++++++++++ src/registrar/views/portfolios.py | 30 ++++++++++++++++++- .../views/utility/permission_views.py | 7 +++++ 6 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 src/registrar/templates/no_portfolio_domains.html diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 90137c4af..5e5762f70 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -64,6 +64,11 @@ urlpatterns = [ views.PortfolioDomainsView.as_view(), name="domains", ), + path( + "no-organization-domains/", + views.PortfolioNoDomainsView.as_view(), + name="no-portfolio-domains", + ), path( "requests/", views.PortfolioDomainRequestsView.as_view(), diff --git a/src/registrar/templates/includes/domains_table.html b/src/registrar/templates/includes/domains_table.html index 64eddec41..ccf9707e6 100644 --- a/src/registrar/templates/includes/domains_table.html +++ b/src/registrar/templates/includes/domains_table.html @@ -1,15 +1,15 @@ {% load static %} -
-
- {% if not has_domains_portfolio_permission %} +
+
+ {% if not portfolio %}

Domains

{% else %} {% endif %} -
{% endblock %} From 34c9cd27617c6c3df8306c1b152295db24318f51 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:25:22 -0700 Subject: [PATCH 08/20] Convert CSP default src to tuple --- src/registrar/config/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 861ce3a94..e0533943e 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -357,9 +357,9 @@ CSP_FORM_ACTION = allowed_sources # strict CSP by allowing scripts to run from their domain # and inline with a nonce, as well as allowing connections back to their domain. # Note: If needed, we can embed chart.js instead of using the CDN -CSP_DEFAULT_SRC = [ +CSP_DEFAULT_SRC = ( "'self'", -] +) CSP_STYLE_SRC = ["'self'", "https://www.ssa.gov", "'unsafe-inline'"] CSP_SCRIPT_SRC_ELEM = [ "'self'", From 965724ec1d77b554a6d17b7d7fa1b96f3187de1a Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:02:57 -0700 Subject: [PATCH 09/20] Remove reference to ANDI hardware --- src/registrar/config/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index e0533943e..bc9e09103 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -190,7 +190,7 @@ MIDDLEWARE = [ "waffle.middleware.WaffleMiddleware", "registrar.registrar_middleware.CheckUserProfileMiddleware", "registrar.registrar_middleware.CheckPortfolioMiddleware", - "registrar.registrar_middleware.ANDIMiddleware", + # "registrar.registrar_middleware.ANDIMiddleware", ] # application object used by Django’s built-in servers (e.g. `runserver`) @@ -360,7 +360,7 @@ CSP_FORM_ACTION = allowed_sources CSP_DEFAULT_SRC = ( "'self'", ) -CSP_STYLE_SRC = ["'self'", "https://www.ssa.gov", "'unsafe-inline'"] +CSP_STYLE_SRC = ["'self'", "https://www.ssa.gov"] CSP_SCRIPT_SRC_ELEM = [ "'self'", "https://www.googletagmanager.com/", From cb9ce3792016a5836897d2d7f2a899703748c194 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:03:33 -0700 Subject: [PATCH 10/20] Fix lint errors --- src/registrar/config/settings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index bc9e09103..4ae0c9fe5 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -357,9 +357,7 @@ CSP_FORM_ACTION = allowed_sources # strict CSP by allowing scripts to run from their domain # and inline with a nonce, as well as allowing connections back to their domain. # Note: If needed, we can embed chart.js instead of using the CDN -CSP_DEFAULT_SRC = ( - "'self'", -) +CSP_DEFAULT_SRC = ("'self'",) CSP_STYLE_SRC = ["'self'", "https://www.ssa.gov"] CSP_SCRIPT_SRC_ELEM = [ "'self'", From f68ef23dad07517c4816ec1f8354ad680271c5b8 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:19:54 -0700 Subject: [PATCH 11/20] Remove unused middleware class --- src/registrar/config/settings.py | 1 - src/registrar/registrar_middleware.py | 21 --------------------- 2 files changed, 22 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 4ae0c9fe5..1b5b2bee5 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -190,7 +190,6 @@ MIDDLEWARE = [ "waffle.middleware.WaffleMiddleware", "registrar.registrar_middleware.CheckUserProfileMiddleware", "registrar.registrar_middleware.CheckPortfolioMiddleware", - # "registrar.registrar_middleware.ANDIMiddleware", ] # application object used by Django’s built-in servers (e.g. `runserver`) diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index a772fbe0a..98685061b 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -158,24 +158,3 @@ class CheckPortfolioMiddleware: return HttpResponseRedirect(portfolio_redirect) return None - - -class ANDIMiddleware(MiddlewareMixin): - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - response = self.get_response(request) - return response - - def process_template_view(self, request, view_func, view_args, view_kwargs): - response = self.get_response(request) - if "text/html" in response.get("Content-Type", ""): - andi_script = """ - - """ - # Inject the ANDI script before the closing tag - content = response.content.decode("utf-8") - content = content.replace("", f"{andi_script}") - response.content = content.encode("utf-8") - return None From f0219639644f6e9aa7f118a83ee318cf4d12b257 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Thu, 15 Aug 2024 13:29:02 -0700 Subject: [PATCH 12/20] Remove unused import --- src/registrar/registrar_middleware.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index 98685061b..2af331bc9 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -8,7 +8,6 @@ from django.urls import reverse from django.http import HttpResponseRedirect from registrar.models.user import User from waffle.decorators import flag_is_active -from django.utils.deprecation import MiddlewareMixin from registrar.models.utility.generic_helper import replace_url_queryparams From 047b41e25e488e1f74c3966d9880dd943c13cb71 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:22:07 -0700 Subject: [PATCH 13/20] Further define ANDI source --- src/registrar/config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 1b5b2bee5..72bffdbb4 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -365,7 +365,7 @@ CSP_SCRIPT_SRC_ELEM = [ "https://www.ssa.gov", "https://ajax.googleapis.com", ] -CSP_CONNECT_SRC = ["'self'", "https://www.google-analytics.com/", "https://www.ssa.gov"] +CSP_CONNECT_SRC = ["'self'", "https://www.google-analytics.com/", "https://www.ssa.gov/accessibility/andi/andi.js"] CSP_INCLUDE_NONCE_IN = ["script-src-elem", "style-src"] CSP_IMG_SRC = ["'self'", "https://www.ssa.gov"] From 4021bb24d29171d4c27ce1f5f78772d7fa81258e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:03:48 -0600 Subject: [PATCH 14/20] Update no_portfolio_domains.html --- src/registrar/templates/no_portfolio_domains.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/no_portfolio_domains.html b/src/registrar/templates/no_portfolio_domains.html index d0ea1317e..d9a20b7dd 100644 --- a/src/registrar/templates/no_portfolio_domains.html +++ b/src/registrar/templates/no_portfolio_domains.html @@ -9,9 +9,8 @@

You aren’t managing any domains.

-

If you believe you should have access to a domain, reach out to your organization’s administrators.

- {% if portfolio_administrators %} +

If you believe you should have access to a domain, reach out to your organization’s administrators.

Your organizations administrators:

    {% for administrator in portfolio_administrators %} @@ -24,7 +23,7 @@
{% else %}

No administrators were found on your organization.

-

To suggest an update, email help@get.gov.

+

If you believe you should have access to a domain, email help@get.gov.

{% endif %}
From 5e16da67346daa9bd467534faa18d8c581692ddc Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:16:33 -0600 Subject: [PATCH 15/20] Logic tweak --- src/registrar/registrar_middleware.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index 2af331bc9..3bcb1dc23 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -151,8 +151,7 @@ class CheckPortfolioMiddleware: if request.user.has_domains_portfolio_permission(): portfolio_redirect = reverse("domains") else: - # View organization is the lowest access - portfolio_redirect = reverse("organization") + portfolio_redirect = reverse("no-portfolio-domains") return HttpResponseRedirect(portfolio_redirect) From 47765927cdbd9037514b470e2fb6121351c098be Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Fri, 16 Aug 2024 10:29:36 -0700 Subject: [PATCH 16/20] Update to more specific templates from ssagov --- src/registrar/config/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 72bffdbb4..73aecad7a 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -357,7 +357,7 @@ CSP_FORM_ACTION = allowed_sources # and inline with a nonce, as well as allowing connections back to their domain. # Note: If needed, we can embed chart.js instead of using the CDN CSP_DEFAULT_SRC = ("'self'",) -CSP_STYLE_SRC = ["'self'", "https://www.ssa.gov"] +CSP_STYLE_SRC = ["'self'", "https://www.ssa.gov/accessibility/andi/andi.css"] CSP_SCRIPT_SRC_ELEM = [ "'self'", "https://www.googletagmanager.com/", @@ -367,7 +367,7 @@ CSP_SCRIPT_SRC_ELEM = [ ] CSP_CONNECT_SRC = ["'self'", "https://www.google-analytics.com/", "https://www.ssa.gov/accessibility/andi/andi.js"] CSP_INCLUDE_NONCE_IN = ["script-src-elem", "style-src"] -CSP_IMG_SRC = ["'self'", "https://www.ssa.gov"] +CSP_IMG_SRC = ["'self'", "https://www.ssa.gov/accessibility/andi/icons/"] # Cross-Origin Resource Sharing (CORS) configuration # Sets clients that allow access control to manage.get.gov From 0c747bed94c7f42b979f9df9295ae3b854321430 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:04:39 -0600 Subject: [PATCH 17/20] Fix unit tests --- src/registrar/tests/test_views_portfolio.py | 40 +++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py index 93ece291a..3097c8e5c 100644 --- a/src/registrar/tests/test_views_portfolio.py +++ b/src/registrar/tests/test_views_portfolio.py @@ -221,8 +221,8 @@ class TestPortfolio(WebTest): self.assertContains(response, 'for="id_city"') @less_console_noise_decorator - def test_navigation_links_hidden_when_user_not_have_permission(self): - """Test that navigation links are hidden when user does not have portfolio permissions""" + def test_accessible_pages_when_user_does_not_have_permission(self): + """Tests which pages are accessible when user does not have portfolio permissions""" self.app.set_user(self.user.username) self.user.portfolio = self.portfolio self.user.portfolio_additional_permissions = [ @@ -249,16 +249,29 @@ class TestPortfolio(WebTest): self.user.save() self.user.refresh_from_db() + # Members should be redirected to the readonly domains page portfolio_page = self.app.get(reverse("home")).follow() self.assertContains(portfolio_page, self.portfolio.organization_name) - self.assertContains(portfolio_page, "

Organization

") - self.assertNotContains(portfolio_page, '

Domains

') + self.assertNotContains(portfolio_page, "

Organization

") + self.assertContains(portfolio_page, '

Domains

') + self.assertContains(portfolio_page, "You aren’t managing any domains") self.assertNotContains(portfolio_page, reverse("domains")) self.assertNotContains(portfolio_page, reverse("domain-requests")) + # The organization page should still be accessible + org_page = self.app.get(reverse("organization")) + self.assertContains(org_page, self.portfolio.organization_name) + self.assertContains(org_page, "

Organization

") + + # Both domain pages should not be accessible + domain_page = self.app.get(reverse("domains"), expect_errors=True) + self.assertEquals(domain_page.status_code, 403) + domain_request_page = self.app.get(reverse("domain-requests"), expect_errors=True) + self.assertEquals(domain_request_page.status_code, 403) + @less_console_noise_decorator - def test_navigation_links_hidden_when_user_not_have_role(self): + def test_accessible_pages_when_user_does_not_have_role(self): """Test that admin / memmber roles are associated with the right access""" self.app.set_user(self.user.username) self.user.portfolio = self.portfolio @@ -282,14 +295,27 @@ class TestPortfolio(WebTest): self.user.save() self.user.refresh_from_db() + # Members should be redirected to the readonly domains page portfolio_page = self.app.get(reverse("home")).follow() self.assertContains(portfolio_page, self.portfolio.organization_name) - self.assertContains(portfolio_page, "

Organization

") - self.assertNotContains(portfolio_page, '

Domains

') + self.assertNotContains(portfolio_page, "

Organization

") + self.assertContains(portfolio_page, '

Domains

') + self.assertContains(portfolio_page, "You aren’t managing any domains") self.assertNotContains(portfolio_page, reverse("domains")) self.assertNotContains(portfolio_page, reverse("domain-requests")) + # The organization page should still be accessible + org_page = self.app.get(reverse("organization")) + self.assertContains(org_page, self.portfolio.organization_name) + self.assertContains(org_page, "

Organization

") + + # Both domain pages should not be accessible + domain_page = self.app.get(reverse("domains"), expect_errors=True) + self.assertEquals(domain_page.status_code, 403) + domain_request_page = self.app.get(reverse("domain-requests"), expect_errors=True) + self.assertEquals(domain_request_page.status_code, 403) + @less_console_noise_decorator def test_portfolio_org_name(self): """Can load portfolio's org name page.""" From cedd7f254a7e349b06c86db96d57e53888240f16 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:25:42 -0600 Subject: [PATCH 18/20] Fix middleware test --- src/registrar/tests/test_views_portfolio.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py index 3097c8e5c..38f6bf4fa 100644 --- a/src/registrar/tests/test_views_portfolio.py +++ b/src/registrar/tests/test_views_portfolio.py @@ -97,8 +97,8 @@ class TestPortfolio(WebTest): self.assertNotContains(portfolio_page, self.portfolio.organization_name) @less_console_noise_decorator - def test_middleware_redirects_to_portfolio_organization_page(self): - """Test that user with a portfolio and VIEW_PORTFOLIO is redirected to portfolio organization page""" + def test_middleware_redirects_to_portfolio_no_domains_page(self): + """Test that user with a portfolio and VIEW_PORTFOLIO is redirected to the no domains page""" self.app.set_user(self.user.username) self.user.portfolio = self.portfolio self.user.portfolio_additional_permissions = [UserPortfolioPermissionChoices.VIEW_PORTFOLIO] @@ -110,7 +110,8 @@ class TestPortfolio(WebTest): portfolio_page = self.app.get(reverse("home")).follow() # Assert that we're on the right page self.assertContains(portfolio_page, self.portfolio.organization_name) - self.assertContains(portfolio_page, "

Organization

") + self.assertContains(portfolio_page, '

Domains

') + self.assertContains(portfolio_page, "You aren’t managing any domains") @less_console_noise_decorator def test_middleware_redirects_to_portfolio_domains_page(self): From 2fc8acb614f94e13c227a85949566b9207a6b520 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:02:03 -0600 Subject: [PATCH 19/20] Update urls.py --- src/registrar/config/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 413449896..882a71a6c 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -66,7 +66,7 @@ urlpatterns = [ name="domains", ), path( - "no-organization-domains/", + "domains/no-organization/", views.PortfolioNoDomainsView.as_view(), name="no-portfolio-domains", ), From ea5207f01741c4b94ebd4a597a4a83a32de2b50b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:07:13 -0600 Subject: [PATCH 20/20] Revert url (breaking tests) --- src/registrar/config/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 882a71a6c..413449896 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -66,7 +66,7 @@ urlpatterns = [ name="domains", ), path( - "domains/no-organization/", + "no-organization-domains/", views.PortfolioNoDomainsView.as_view(), name="no-portfolio-domains", ),