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 %}
+ {% if administrator.email %}
+
{{ administrator.email }}
+ {% else %}
+
{{ administrator }}
+ {% endif %}
+ {% endfor %}
+
+ {% else %}
+
No administrators were found on your organization.
+
If you believe you should have access to a domain, email help@get.gov.
+ {% endif %}
+
+
+{% endblock %}
diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py
index 60764cf1c..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):
@@ -221,8 +222,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 +250,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 +296,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."""
@@ -355,3 +382,51 @@ class TestPortfolio(WebTest):
self.assertContains(success_result_page, "6 Downing st")
self.assertContains(success_result_page, "London")
+
+ @less_console_noise_decorator
+ @override_flag("organization_feature", active=True)
+ def test_org_member_can_only_see_domains_with_appropriate_permissions(self):
+ """A user with the role organization_member should not have access to the domains page
+ if they do not have the right permissions.
+ """
+
+ # A default organization member should not be able to see any domains
+ self.app.set_user(self.user.username)
+ self.user.portfolio = self.portfolio
+ self.user.portfolio_roles = [UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
+ self.user.save()
+ self.user.refresh_from_db()
+
+ self.assertFalse(self.user.has_domains_portfolio_permission())
+
+ response = self.app.get(reverse("no-portfolio-domains"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "You aren’t managing any domains.")
+
+ # Test the domains page - this user should not have access
+ response = self.app.get(reverse("domains"), expect_errors=True)
+ self.assertEqual(response.status_code, 403)
+
+ # Ensure that this user can see domains with the right permissions
+ self.user.portfolio_additional_permissions = [UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS]
+ self.user.save()
+ self.user.refresh_from_db()
+
+ self.assertTrue(self.user.has_domains_portfolio_permission())
+
+ # Test the domains page - this user should have access
+ response = self.app.get(reverse("domains"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Domain name")
+
+ # Test the managed domains permission
+ self.user.portfolio_additional_permissions = [UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS]
+ self.user.save()
+ self.user.refresh_from_db()
+
+ self.assertTrue(self.user.has_domains_portfolio_permission())
+
+ # Test the domains page - this user should have access
+ response = self.app.get(reverse("domains"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Domain name")
diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py
index 8a5321cc9..5869a19e2 100644
--- a/src/registrar/views/portfolios.py
+++ b/src/registrar/views/portfolios.py
@@ -4,11 +4,13 @@ from django.shortcuts import render
from django.urls import reverse
from django.contrib import messages
from registrar.forms.portfolio import PortfolioOrgAddressForm, PortfolioSeniorOfficialForm
-from registrar.models.portfolio import Portfolio
+from registrar.models import Portfolio, User
+from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
from registrar.views.utility.permission_views import (
PortfolioDomainRequestsPermissionView,
PortfolioDomainsPermissionView,
PortfolioBasePermissionView,
+ NoPortfolioDomainsPermissionView,
)
from django.views.generic import View
from django.views.generic.edit import FormMixin
@@ -38,6 +40,32 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
return render(request, "portfolio_requests.html")
+class PortfolioNoDomainsView(NoPortfolioDomainsPermissionView, View):
+ """Some users have access to the underlying portfolio, but not any domains.
+ This is a custom view which explains that to the user - and denotes who to contact.
+ """
+
+ model = Portfolio
+ template_name = "no_portfolio_domains.html"
+
+ def get(self, request):
+ return render(request, self.template_name, context=self.get_context_data())
+
+ def get_context_data(self, **kwargs):
+ """Add additional context data to the template."""
+ # We can override the base class. This view only needs this item.
+ context = {}
+ portfolio = self.request.user.portfolio if self.request and self.request.user else None
+ if portfolio:
+ context["portfolio_administrators"] = User.objects.filter(
+ portfolio=portfolio,
+ portfolio_roles__overlap=[
+ UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
+ ],
+ )
+ return context
+
+
class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
"""
View to handle displaying and updating the portfolio's organization details.
diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py
index 501658e9f..0ff7d1676 100644
--- a/src/registrar/views/utility/permission_views.py
+++ b/src/registrar/views/utility/permission_views.py
@@ -214,6 +214,15 @@ class PortfolioDomainsPermissionView(PortfolioDomainsPermission, PortfolioBasePe
"""
+class NoPortfolioDomainsPermissionView(PortfolioBasePermissionView, abc.ABC):
+ """Abstract base view for a user without access to the
+ portfolio domains views that enforces permissions.
+
+ This abstract view cannot be instantiated. Actual views must specify
+ `template_name`.
+ """
+
+
class PortfolioDomainRequestsPermissionView(PortfolioDomainRequestsPermission, PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for portfolio domain request views that enforces permissions.