- {% if original.state != original.State.DELETED %}
+ {% if original.state != original.State.DELETED and not adminform.form.is_omb_analyst %}
{% endif %}
{% if original.state == original.State.READY or original.state == original.State.ON_HOLD %}
+ {% if not adminform.form.is_omb_analyst %}
{% endif %}
- {% if original.state != original.State.DELETED %}
+ {% endif %}
+ {% if original.state != original.State.DELETED and not adminform.form.is_omb_analyst %}
diff --git a/src/registrar/templates/django/admin/includes/contact_detail_list.html b/src/registrar/templates/django/admin/includes/contact_detail_list.html
index 0baabac17..b3cdeb875 100644
--- a/src/registrar/templates/django/admin/includes/contact_detail_list.html
+++ b/src/registrar/templates/django/admin/includes/contact_detail_list.html
@@ -6,7 +6,11 @@
{% if show_formatted_name %}
{% if user.get_formatted_name %}
-
+ {% if adminform.form.show_contact_as_plain_text %}
+ {{ user.get_formatted_name }}
+ {% else %}
+
+ {% endif %}
{% else %}
None
{% endif %}
diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
index f12bd67f9..dcf393d82 100644
--- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
+++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
@@ -69,7 +69,11 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{% elif field.field.name == "portfolio_senior_official" %}
+ {% elif field.field.name == "creator" and adminform.form.show_contact_as_plain_text %}
+
+ {% elif field.field.name == "senior_official" and adminform.form.show_contact_as_plain_text %}
+
{% endif %}
diff --git a/src/registrar/templates/django/admin/includes/portfolio/portfolio_admins_table.html b/src/registrar/templates/django/admin/includes/portfolio/portfolio_admins_table.html
index 7add74323..574c05738 100644
--- a/src/registrar/templates/django/admin/includes/portfolio/portfolio_admins_table.html
+++ b/src/registrar/templates/django/admin/includes/portfolio/portfolio_admins_table.html
@@ -16,7 +16,11 @@
{% for admin in admins %}
{% url 'admin:registrar_userportfoliopermission_change' admin.pk as url %}
- {{ admin.user.get_formatted_name}} |
+ {% if adminform.form.is_omb_analyst %}
+ {{ admin.user.get_formatted_name }} |
+ {% else %}
+ {{ admin.user.get_formatted_name}} |
+ {% endif %}
{{ admin.user.title }} |
{% if admin.user.email %}
diff --git a/src/registrar/templates/django/admin/includes/portfolio/portfolio_fieldset.html b/src/registrar/templates/django/admin/includes/portfolio/portfolio_fieldset.html
index 87b56cb60..54ac502d1 100644
--- a/src/registrar/templates/django/admin/includes/portfolio/portfolio_fieldset.html
+++ b/src/registrar/templates/django/admin/includes/portfolio/portfolio_fieldset.html
@@ -30,6 +30,9 @@
No senior official found. Create one now.
{% endif %}
+
+ {% elif field.field.name == "creator" and adminform.form.show_contact_as_plain_text %}
+ {{ field.contents|striptags }}
{% else %}
{{ field.contents }}
{% endif %}
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index 8fbf052a4..a3e2114f7 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -1010,6 +1010,27 @@ def create_user(**kwargs):
return user
+def create_omb_analyst_user(**kwargs):
+ """Creates a analyst user with is_staff=True and the group cisa_analysts_group"""
+ User = get_user_model()
+ p = "userpass"
+ user = User.objects.create_user(
+ username=kwargs.get("username", "ombanalystuser"),
+ email=kwargs.get("email", "ombanalyst@example.com"),
+ first_name=kwargs.get("first_name", "first"),
+ last_name=kwargs.get("last_name", "last"),
+ is_staff=kwargs.get("is_staff", True),
+ title=kwargs.get("title", "title"),
+ password=kwargs.get("password", p),
+ phone=kwargs.get("phone", "8003111234"),
+ )
+ # Retrieve the group or create it if it doesn't exist
+ group, _ = UserGroup.objects.get_or_create(name="omb_analysts_group")
+ # Add the user to the group
+ user.groups.set([group])
+ return user
+
+
def create_test_user():
username = "test_user"
first_name = "First"
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
index 1de6b1be3..8fd2744ec 100644
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -3,6 +3,7 @@ from django.utils import timezone
from django.test import TestCase, RequestFactory, Client
from django.contrib.admin.sites import AdminSite
from registrar import models
+from registrar.utility.constants import BranchChoices
from registrar.utility.email import EmailSendingError
from registrar.utility.errors import MissingEmailError
from waffle.testutils import override_flag
@@ -57,6 +58,7 @@ from .common import (
MockDbForSharedTests,
AuditedAdminMockData,
completed_domain_request,
+ create_omb_analyst_user,
create_test_user,
generic_domain_object,
less_console_noise,
@@ -136,18 +138,25 @@ class TestDomainInvitationAdmin(WebTest):
csrf_checks = False
@classmethod
- def setUpClass(self):
+ def setUpClass(cls):
super().setUpClass()
- self.site = AdminSite()
- self.factory = RequestFactory()
- self.superuser = create_superuser()
+ cls.site = AdminSite()
+ cls.factory = RequestFactory()
def setUp(self):
super().setUp()
+ self.superuser = create_superuser()
+ self.cisa_analyst = create_user()
+ self.omb_analyst = create_omb_analyst_user()
self.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite())
self.domain = Domain.objects.create(name="example.com")
+ self.fed_agency = FederalAgency.objects.create(
+ agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
+ )
self.portfolio = Portfolio.objects.create(organization_name="new portfolio", creator=self.superuser)
- DomainInformation.objects.create(domain=self.domain, portfolio=self.portfolio, creator=self.superuser)
+ self.domain_info = DomainInformation.objects.create(
+ domain=self.domain, portfolio=self.portfolio, creator=self.superuser
+ )
"""Create a client object"""
self.client = Client(HTTP_HOST="localhost:8080")
self.client.force_login(self.superuser)
@@ -159,10 +168,124 @@ class TestDomainInvitationAdmin(WebTest):
DomainInvitation.objects.all().delete()
DomainInformation.objects.all().delete()
Portfolio.objects.all().delete()
+ self.fed_agency.delete()
Domain.objects.all().delete()
Contact.objects.all().delete()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_analyst_view(self):
+ """Ensure regular analysts can view domain invitations."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ self.client.force_login(self.cisa_analyst)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, invitation.email)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_view_non_feb_domain(self):
+ """Ensure OMB analysts cannot view non-federal domains."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
+ self.assertNotContains(response, invitation.email)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_view_feb_domain(self):
+ """Ensure OMB analysts can view federal executive branch domains."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ self.portfolio.organization_type = DomainRequest.OrganizationChoices.FEDERAL
+ self.portfolio.federal_agency = self.fed_agency
+ self.portfolio.save()
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
+ self.assertContains(response, invitation.email)
+
+ @less_console_noise_decorator
+ def test_superuser_view(self):
+ """Ensure superusers can view domain invitations."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, invitation.email)
+
+ @less_console_noise_decorator
+ def test_analyst_change(self):
+ """Ensure regular analysts can view domain invitations but not update."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ self.client.force_login(self.cisa_analyst)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, invitation.email)
+ # test whether fields are readonly or editable
+ self.assertNotContains(response, "id_domain")
+ self.assertNotContains(response, "id_email")
+ self.assertContains(response, "closelink")
+ self.assertNotContains(response, "Save")
+ self.assertNotContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change_non_feb_domain(self):
+ """Ensure OMB analysts cannot change non-federal domains."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
+ self.assertEqual(response.status_code, 302)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change_feb_domain(self):
+ """Ensure OMB analysts can view federal executive branch domains."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ # update domain
+ self.portfolio.organization_type = DomainRequest.OrganizationChoices.FEDERAL
+ self.portfolio.federal_agency = self.fed_agency
+ self.portfolio.save()
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, invitation.email)
+ # test whether fields are readonly or editable
+ self.assertNotContains(response, "id_domain")
+ self.assertNotContains(response, "id_email")
+ self.assertContains(response, "closelink")
+ self.assertNotContains(response, "Save")
+ self.assertNotContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_superuser_change(self):
+ """Ensure superusers can change domain invitations."""
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, invitation.email)
+ # test whether fields are readonly or editable
+ self.assertContains(response, "id_domain")
+ self.assertContains(response, "id_email")
+ self.assertNotContains(response, "closelink")
+ self.assertContains(response, "Save")
+ self.assertContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_filter_feb_domain(self):
+ """Ensure OMB analysts can apply filters and only federal executive branch domains show."""
+ # create invitation on domain that is not FEB
+ invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(
+ reverse("admin:registrar_domaininvitation_changelist"),
+ {"status": DomainInvitation.DomainInvitationStatus.INVITED},
+ )
+ self.assertNotContains(response, invitation.email)
+ # update domain
+ self.portfolio.organization_type = DomainRequest.OrganizationChoices.FEDERAL
+ self.portfolio.federal_agency = self.fed_agency
+ self.portfolio.save()
+ response = self.client.get(
+ reverse("admin:registrar_domaininvitation_changelist"),
+ {"status": DomainInvitation.DomainInvitationStatus.INVITED},
+ )
+ self.assertContains(response, invitation.email)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
@@ -1139,6 +1262,7 @@ class TestUserPortfolioPermissionAdmin(TestCase):
self.client = Client(HTTP_HOST="localhost:8080")
self.superuser = create_superuser()
self.testuser = create_test_user()
+ self.omb_analyst = create_omb_analyst_user()
self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
def tearDown(self):
@@ -1148,6 +1272,26 @@ class TestUserPortfolioPermissionAdmin(TestCase):
User.objects.all().delete()
UserPortfolioPermission.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view user portfolio permissions list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_userportfoliopermission_changelist"))
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts cannot change user portfolio permission."""
+ self.client.force_login(self.omb_analyst)
+ user_portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
+ user=self.superuser, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
+ )
+ response = self.client.get(
+ "/admin/registrar/userportfoliopermission/{}/change/".format(user_portfolio_permission.pk),
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_change_form_description(self):
"""Tests if this model has a model description on the change form view"""
@@ -1204,6 +1348,7 @@ class TestPortfolioInvitationAdmin(TestCase):
def setUp(self):
"""Create a client object"""
self.client = Client(HTTP_HOST="localhost:8080")
+ self.omb_analyst = create_omb_analyst_user()
self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
def tearDown(self):
@@ -1217,6 +1362,26 @@ class TestPortfolioInvitationAdmin(TestCase):
def tearDownClass(self):
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view portfolio invitations list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_portfolioinvitation_changelist"))
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts cannot change portfolio invitation."""
+ self.client.force_login(self.omb_analyst)
+ invitation, _ = PortfolioInvitation.objects.get_or_create(
+ email=self.superuser.email, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
+ )
+ response = self.client.get(
+ "/admin/registrar/portfolioinvitation/{}/change/".format(invitation.pk),
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
@@ -1791,6 +1956,8 @@ class TestHostAdmin(TestCase):
cls.factory = RequestFactory()
cls.admin = MyHostAdmin(model=Host, admin_site=cls.site)
cls.superuser = create_superuser()
+ cls.staffuser = create_user()
+ cls.omb_analyst = create_omb_analyst_user()
def setUp(self):
"""Setup environment for a mock admin user"""
@@ -1806,6 +1973,20 @@ class TestHostAdmin(TestCase):
def tearDownClass(cls):
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_analyst_view(self):
+ """Ensure analysts cannot view hosts list."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(reverse("admin:registrar_host_changelist"))
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view hosts list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_host_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
@@ -1870,6 +2051,7 @@ class TestDomainInformationAdmin(TestCase):
cls.admin = DomainInformationAdmin(model=DomainInformation, admin_site=cls.site)
cls.superuser = create_superuser()
cls.staffuser = create_user()
+ cls.omb_analyst = create_omb_analyst_user()
cls.mock_data_generator = AuditedAdminMockData()
cls.test_helper = GenericTestHelper(
factory=cls.factory,
@@ -1881,12 +2063,24 @@ class TestDomainInformationAdmin(TestCase):
def setUp(self):
self.client = Client(HTTP_HOST="localhost:8080")
+ self.nonfeddomain = Domain.objects.create(name="nonfeddomain.com")
+ self.feddomain = Domain.objects.create(name="feddomain.com")
+ self.fed_agency = FederalAgency.objects.create(
+ agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
+ )
+ self.portfolio = Portfolio.objects.create(organization_name="new portfolio", creator=self.superuser)
+ self.domain_info = DomainInformation.objects.create(
+ domain=self.feddomain, portfolio=self.portfolio, creator=self.superuser
+ )
def tearDown(self):
"""Delete all Users, Domains, and UserDomainRoles"""
DomainInformation.objects.all().delete()
DomainRequest.objects.all().delete()
Domain.objects.all().delete()
+ DomainInformation.objects.all().delete()
+ Portfolio.objects.all().delete()
+ self.fed_agency.delete()
Contact.objects.all().delete()
@classmethod
@@ -1894,6 +2088,56 @@ class TestDomainInformationAdmin(TestCase):
User.objects.all().delete()
SeniorOfficial.objects.all().delete()
+ @less_console_noise_decorator
+ def test_analyst_view(self):
+ """Ensure regular analysts cannot view domain information list."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(reverse("admin:registrar_domaininformation_changelist"))
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view domain information list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_domaininformation_changelist"))
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_superuser_view(self):
+ """Ensure superusers can view domain information list."""
+ self.client.force_login(self.superuser)
+ response = self.client.get(reverse("admin:registrar_domaininformation_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feddomain.name)
+
+ @less_console_noise_decorator
+ def test_analyst_change(self):
+ """Ensure regular analysts cannot view/edit domain information directly."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(
+ reverse("admin:registrar_domaininformation_change", args=[self.feddomain.domain_info.id])
+ )
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts cannot view/edit domain information directly."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(
+ reverse("admin:registrar_domaininformation_change", args=[self.feddomain.domain_info.id])
+ )
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_superuser_change(self):
+ """Ensure superusers can view/change domain information directly."""
+ self.client.force_login(self.superuser)
+ response = self.client.get(
+ reverse("admin:registrar_domaininformation_change", args=[self.feddomain.domain_info.id])
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feddomain.name)
+
@less_console_noise_decorator
def test_domain_information_senior_official_is_alphabetically_sorted(self):
"""Tests if the senior offical dropdown is alphanetically sorted in the django admin display"""
@@ -2258,6 +2502,8 @@ class TestUserDomainRoleAdmin(WebTest):
cls.factory = RequestFactory()
cls.admin = UserDomainRoleAdmin(model=UserDomainRole, admin_site=cls.site)
cls.superuser = create_superuser()
+ cls.staffuser = create_user()
+ cls.omb_analyst = create_omb_analyst_user()
cls.test_helper = GenericTestHelper(
factory=cls.factory,
user=cls.superuser,
@@ -2285,6 +2531,31 @@ class TestUserDomainRoleAdmin(WebTest):
super().tearDownClass()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_analyst_view(self):
+ """Ensure analysts cannot view user domain roles list."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(reverse("admin:registrar_userdomainrole_changelist"))
+ self.assertEqual(response.status_code, 200)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view user domain roles list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_userdomainrole_changelist"))
+ self.assertEqual(response.status_code, 403)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts cannot view/edit user domain roles list."""
+ domain, _ = Domain.objects.get_or_create(name="anyrandomdomain.com")
+ user_domain_role, _ = UserDomainRole.objects.get_or_create(
+ user=self.superuser, domain=domain, role=[UserDomainRole.Roles.MANAGER]
+ )
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_userdomainrole_change", args=[user_domain_role.id]))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
@@ -2580,6 +2851,7 @@ class TestMyUserAdmin(MockDbForSharedTests, WebTest):
cls.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site)
cls.superuser = create_superuser()
cls.staffuser = create_user()
+ cls.omb_analyst = create_omb_analyst_user()
cls.test_helper = GenericTestHelper(admin=cls.admin)
def setUp(self):
@@ -2596,6 +2868,13 @@ class TestMyUserAdmin(MockDbForSharedTests, WebTest):
super().tearDownClass()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view users list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_user_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
@@ -3221,6 +3500,7 @@ class TestContactAdmin(TestCase):
cls.admin = ContactAdmin(model=Contact, admin_site=None)
cls.superuser = create_superuser()
cls.staffuser = create_user()
+ cls.omb_analyst = create_omb_analyst_user()
def setUp(self):
super().setUp()
@@ -3236,6 +3516,13 @@ class TestContactAdmin(TestCase):
super().tearDownClass()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view contact list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_contact_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
@@ -3282,6 +3569,7 @@ class TestVerifiedByStaffAdmin(TestCase):
super().setUpClass()
cls.site = AdminSite()
cls.superuser = create_superuser()
+ cls.omb_analyst = create_omb_analyst_user()
cls.admin = VerifiedByStaffAdmin(model=VerifiedByStaff, admin_site=cls.site)
cls.factory = RequestFactory()
cls.test_helper = GenericTestHelper(admin=cls.admin)
@@ -3299,18 +3587,20 @@ class TestVerifiedByStaffAdmin(TestCase):
super().tearDownClass()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view verified by staff list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_verifiedbystaff_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
self.client.force_login(self.superuser)
- response = self.client.get(
- "/admin/registrar/verifiedbystaff/",
- follow=True,
- )
-
+ response = self.client.get(reverse("admin:registrar_verifiedbystaff_changelist"))
# Make sure that the page is loaded correctly
self.assertEqual(response.status_code, 200)
-
# Test for a description snippet
self.assertContains(
response, "This table contains users who have been allowed to bypass " "identity proofing through Login.gov"
@@ -3365,6 +3655,7 @@ class TestWebsiteAdmin(TestCase):
super().setUp()
self.site = AdminSite()
self.superuser = create_superuser()
+ self.omb_analyst = create_omb_analyst_user()
self.admin = WebsiteAdmin(model=Website, admin_site=self.site)
self.factory = RequestFactory()
self.client = Client(HTTP_HOST="localhost:8080")
@@ -3375,15 +3666,18 @@ class TestWebsiteAdmin(TestCase):
Website.objects.all().delete()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view website list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_website_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
self.client.force_login(self.superuser)
- response = self.client.get(
- "/admin/registrar/website/",
- follow=True,
- )
-
+ response = self.client.get(reverse("admin:registrar_website_changelist"))
# Make sure that the page is loaded correctly
self.assertEqual(response.status_code, 200)
@@ -3392,13 +3686,14 @@ class TestWebsiteAdmin(TestCase):
self.assertContains(response, "Show more")
-class TestDraftDomain(TestCase):
+class TestDraftDomainAdmin(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.site = AdminSite()
cls.superuser = create_superuser()
+ cls.omb_analyst = create_omb_analyst_user()
cls.admin = DraftDomainAdmin(model=DraftDomain, admin_site=cls.site)
cls.factory = RequestFactory()
cls.test_helper = GenericTestHelper(admin=cls.admin)
@@ -3416,15 +3711,18 @@ class TestDraftDomain(TestCase):
super().tearDownClass()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view draft domain list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_draftdomain_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
self.client.force_login(self.superuser)
- response = self.client.get(
- "/admin/registrar/draftdomain/",
- follow=True,
- )
-
+ response = self.client.get(reverse("admin:registrar_draftdomain_changelist"))
# Make sure that the page is loaded correctly
self.assertEqual(response.status_code, 200)
@@ -3435,13 +3733,21 @@ class TestDraftDomain(TestCase):
self.assertContains(response, "Show more")
-class TestFederalAgency(TestCase):
+class TestFederalAgencyAdmin(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.site = AdminSite()
cls.superuser = create_superuser()
+ cls.staffuser = create_user()
+ cls.omb_analyst = create_omb_analyst_user()
+ cls.non_feb_agency = FederalAgency.objects.create(
+ agency="Fake judicial agency", federal_type=BranchChoices.JUDICIAL
+ )
+ cls.feb_agency = FederalAgency.objects.create(
+ agency="Fake executive agency", federal_type=BranchChoices.EXECUTIVE
+ )
cls.admin = FederalAgencyAdmin(model=FederalAgency, admin_site=cls.site)
cls.factory = RequestFactory()
cls.test_helper = GenericTestHelper(admin=cls.admin)
@@ -3454,6 +3760,100 @@ class TestFederalAgency(TestCase):
super().tearDownClass()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_analyst_view(self):
+ """Ensure regular analysts can view federal agencies."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(reverse("admin:registrar_federalagency_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.non_feb_agency.agency)
+ self.assertContains(response, self.feb_agency.agency)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts can view FEB agencies but not other branches."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_federalagency_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, self.non_feb_agency.agency)
+ self.assertContains(response, self.feb_agency.agency)
+
+ @less_console_noise_decorator
+ def test_superuser_view(self):
+ """Ensure superusers can view domain invitations."""
+ self.client.force_login(self.superuser)
+ response = self.client.get(reverse("admin:registrar_federalagency_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.non_feb_agency.agency)
+ self.assertContains(response, self.feb_agency.agency)
+
+ @less_console_noise_decorator
+ def test_analyst_change(self):
+ """Ensure regular analysts can view/edit federal agencies list."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.non_feb_agency.id]))
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.feb_agency.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feb_agency.agency)
+ # test whether fields are readonly or editable
+ self.assertContains(response, "id_agency")
+ self.assertContains(response, "id_federal_type")
+ self.assertContains(response, "id_acronym")
+ self.assertContains(response, "id_is_fceb")
+ self.assertNotContains(response, "closelink")
+ self.assertContains(response, "Save")
+ self.assertContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts can change FEB agencies but not others."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.non_feb_agency.id]))
+ self.assertEqual(response.status_code, 302)
+ response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.feb_agency.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feb_agency.agency)
+ # test whether fields are readonly or editable
+ self.assertNotContains(response, "id_agency")
+ self.assertNotContains(response, "id_federal_type")
+ self.assertNotContains(response, "id_acronym")
+ self.assertNotContains(response, "id_is_fceb")
+ self.assertContains(response, "closelink")
+ self.assertNotContains(response, "Save")
+ self.assertNotContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_superuser_change(self):
+ """Ensure superusers can change all federal agencies."""
+ self.client.force_login(self.superuser)
+ response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.non_feb_agency.id]))
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.feb_agency.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feb_agency.agency)
+ # test whether fields are readonly or editable
+ self.assertContains(response, "id_agency")
+ self.assertContains(response, "id_federal_type")
+ self.assertContains(response, "id_acronym")
+ self.assertContains(response, "id_is_fceb")
+ self.assertNotContains(response, "closelink")
+ self.assertContains(response, "Save")
+ self.assertContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_filter_feb_agencies(self):
+ """Ensure OMB analysts can apply filters and only federal agencies show."""
+ self.client.force_login(self.omb_analyst)
+ # in setup, created two agencies: Fake judicial agency and Fake executive agency
+ # only executive agency should show up with the search for 'fake'
+ response = self.client.get(
+ reverse("admin:registrar_federalagency_changelist"),
+ data={"q": "fake"},
+ )
+ self.assertNotContains(response, self.non_feb_agency.agency)
+ self.assertContains(response, self.feb_agency.agency)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
@@ -3471,11 +3871,12 @@ class TestFederalAgency(TestCase):
self.assertContains(response, "Show more")
-class TestPublicContact(TestCase):
+class TestPublicContactAdmin(TestCase):
def setUp(self):
super().setUp()
self.site = AdminSite()
self.superuser = create_superuser()
+ self.omb_analyst = create_omb_analyst_user()
self.admin = PublicContactAdmin(model=PublicContact, admin_site=self.site)
self.factory = RequestFactory()
self.client = Client(HTTP_HOST="localhost:8080")
@@ -3486,16 +3887,19 @@ class TestPublicContact(TestCase):
PublicContact.objects.all().delete()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view public contact list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_publiccontact_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
p = "adminpass"
self.client.login(username="superuser", password=p)
- response = self.client.get(
- "/admin/registrar/publiccontact/",
- follow=True,
- )
-
+ response = self.client.get(reverse("admin:registrar_publiccontact_changelist"))
# Make sure that the page is loaded correctly
self.assertEqual(response.status_code, 200)
@@ -3504,11 +3908,12 @@ class TestPublicContact(TestCase):
self.assertContains(response, "Show more")
-class TestTransitionDomain(TestCase):
+class TestTransitionDomainAdmin(TestCase):
def setUp(self):
super().setUp()
self.site = AdminSite()
self.superuser = create_superuser()
+ self.omb_analyst = create_omb_analyst_user()
self.admin = TransitionDomainAdmin(model=TransitionDomain, admin_site=self.site)
self.factory = RequestFactory()
self.client = Client(HTTP_HOST="localhost:8080")
@@ -3519,15 +3924,18 @@ class TestTransitionDomain(TestCase):
PublicContact.objects.all().delete()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view transition domain list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_transitiondomain_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
self.client.force_login(self.superuser)
- response = self.client.get(
- "/admin/registrar/transitiondomain/",
- follow=True,
- )
-
+ response = self.client.get(reverse("admin:registrar_transitiondomain_changelist"))
# Make sure that the page is loaded correctly
self.assertEqual(response.status_code, 200)
@@ -3536,11 +3944,12 @@ class TestTransitionDomain(TestCase):
self.assertContains(response, "Show more")
-class TestUserGroup(TestCase):
+class TestUserGroupAdmin(TestCase):
def setUp(self):
super().setUp()
self.site = AdminSite()
self.superuser = create_superuser()
+ self.omb_analyst = create_omb_analyst_user()
self.admin = UserGroupAdmin(model=UserGroup, admin_site=self.site)
self.factory = RequestFactory()
self.client = Client(HTTP_HOST="localhost:8080")
@@ -3550,15 +3959,18 @@ class TestUserGroup(TestCase):
super().tearDown()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts cannot view user group list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_usergroup_changelist"))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
self.client.force_login(self.superuser)
- response = self.client.get(
- "/admin/registrar/usergroup/",
- follow=True,
- )
-
+ response = self.client.get(reverse("admin:registrar_usergroup_changelist"))
# Make sure that the page is loaded correctly
self.assertEqual(response.status_code, 200)
@@ -3575,12 +3987,23 @@ class TestPortfolioAdmin(TestCase):
super().setUpClass()
cls.site = AdminSite()
cls.superuser = create_superuser()
+ cls.staffuser = create_user()
+ cls.omb_analyst = create_omb_analyst_user()
cls.admin = PortfolioAdmin(model=Portfolio, admin_site=cls.site)
cls.factory = RequestFactory()
def setUp(self):
self.client = Client(HTTP_HOST="localhost:8080")
- self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
+ self.portfolio = Portfolio.objects.create(organization_name="Test portfolio", creator=self.superuser)
+ self.feb_agency = FederalAgency.objects.create(
+ agency="Test FedExec Agency", federal_type=BranchChoices.EXECUTIVE
+ )
+ self.feb_portfolio = Portfolio.objects.create(
+ organization_name="Test FEB portfolio",
+ creator=self.superuser,
+ federal_agency=self.feb_agency,
+ organization_type=DomainRequest.OrganizationChoices.FEDERAL,
+ )
def tearDown(self):
Suborganization.objects.all().delete()
@@ -3588,8 +4011,118 @@ class TestPortfolioAdmin(TestCase):
DomainRequest.objects.all().delete()
Domain.objects.all().delete()
Portfolio.objects.all().delete()
+ self.feb_agency.delete()
User.objects.all().delete()
+ @less_console_noise_decorator
+ def test_analyst_view(self):
+ """Ensure regular analysts can view portfolios."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(reverse("admin:registrar_portfolio_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.portfolio.organization_name)
+ self.assertContains(response, self.feb_portfolio.organization_name)
+
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts can view FEB portfolios but not others."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_portfolio_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, self.portfolio.organization_name)
+ self.assertContains(response, self.feb_portfolio.organization_name)
+
+ @less_console_noise_decorator
+ def test_superuser_view(self):
+ """Ensure superusers can view portfolios."""
+ self.client.force_login(self.superuser)
+ response = self.client.get(reverse("admin:registrar_portfolio_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.portfolio.organization_name)
+ self.assertContains(response, self.feb_portfolio.organization_name)
+
+ @less_console_noise_decorator
+ def test_analyst_change(self):
+ """Ensure regular analysts can view/edit portfolios."""
+ self.client.force_login(self.staffuser)
+ response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.portfolio.id]))
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.feb_portfolio.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feb_portfolio.organization_name)
+ # test whether fields are readonly or editable
+ self.assertContains(response, "id_organization_name")
+ self.assertContains(response, "id_notes")
+ self.assertContains(response, "id_organization_type")
+ self.assertContains(response, "id_state_territory")
+ self.assertContains(response, "id_address_line1")
+ self.assertContains(response, "id_address_line2")
+ self.assertContains(response, "id_city")
+ self.assertContains(response, "id_zipcode")
+ self.assertContains(response, "id_urbanization")
+ self.assertNotContains(response, "closelink")
+ self.assertContains(response, "Save")
+ self.assertContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts can change FEB portfolios but not others."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.portfolio.id]))
+ self.assertEqual(response.status_code, 302)
+ response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.feb_portfolio.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feb_portfolio.organization_name)
+ # test whether fields are readonly or editable
+ self.assertNotContains(response, "id_organization_name")
+ self.assertNotContains(response, "id_notes")
+ self.assertNotContains(response, "id_organization_type")
+ self.assertNotContains(response, "id_state_territory")
+ self.assertNotContains(response, "id_address_line1")
+ self.assertNotContains(response, "id_address_line2")
+ self.assertNotContains(response, "id_city")
+ self.assertNotContains(response, "id_zipcode")
+ self.assertNotContains(response, "id_urbanization")
+ self.assertContains(response, "closelink")
+ self.assertNotContains(response, "Save")
+ self.assertNotContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_superuser_change(self):
+ """Ensure superusers can change all portfolios."""
+ self.client.force_login(self.superuser)
+ response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.portfolio.id]))
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.feb_portfolio.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.feb_portfolio.organization_name)
+ # test whether fields are readonly or editable
+ self.assertContains(response, "id_organization_name")
+ self.assertContains(response, "id_notes")
+ self.assertContains(response, "id_organization_type")
+ self.assertContains(response, "id_state_territory")
+ self.assertContains(response, "id_address_line1")
+ self.assertContains(response, "id_address_line2")
+ self.assertContains(response, "id_city")
+ self.assertContains(response, "id_zipcode")
+ self.assertContains(response, "id_urbanization")
+ self.assertNotContains(response, "closelink")
+ self.assertContains(response, "Save")
+ self.assertContains(response, "Delete")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_filter_feb_portfolios(self):
+ """Ensure OMB analysts can apply filters and only feb portfolios show."""
+ self.client.force_login(self.omb_analyst)
+ # in setup, created two portfolios: Test portfolio and Test FEB portfolio
+ # only executive portfolio should show up with the search for 'portfolio'
+ response = self.client.get(
+ reverse("admin:registrar_portfolio_changelist"),
+ data={"q": "test"},
+ )
+ self.assertNotContains(response, self.portfolio.organization_name)
+ self.assertContains(response, self.feb_portfolio.organization_name)
+
@less_console_noise_decorator
def test_created_on_display(self):
"""Tests the custom created on which is a reskin of the created_at field"""
@@ -3777,6 +4310,7 @@ class TestTransferUser(WebTest):
super().setUpClass()
cls.site = AdminSite()
cls.superuser = create_superuser()
+ cls.omb_analyst = create_omb_analyst_user()
cls.admin = PortfolioAdmin(model=Portfolio, admin_site=cls.site)
cls.factory = RequestFactory()
@@ -3797,6 +4331,13 @@ class TestTransferUser(WebTest):
Portfolio.objects.all().delete()
UserDomainRole.objects.all().delete()
+ @less_console_noise_decorator
+ def test_omb_analyst(self):
+ """Ensure OMB analysts cannot view transfer_user."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("transfer_user", args=[self.user1.pk]))
+ self.assertEqual(response.status_code, 403)
+
@less_console_noise_decorator
def test_transfer_user_shows_current_and_selected_user_information(self):
"""Assert we pull the current user info and display it on the transfer page"""
diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py
index 969d043d7..aa6e799bd 100644
--- a/src/registrar/tests/test_admin_domain.py
+++ b/src/registrar/tests/test_admin_domain.py
@@ -17,14 +17,17 @@ from registrar.models import (
Host,
Portfolio,
)
+from registrar.models.federal_agency import FederalAgency
from registrar.models.public_contact import PublicContact
from registrar.models.user_domain_role import UserDomainRole
+from registrar.utility.constants import BranchChoices
from .common import (
MockSESClient,
completed_domain_request,
less_console_noise,
create_superuser,
create_user,
+ create_omb_analyst_user,
create_ready_domain,
MockEppLib,
GenericTestHelper,
@@ -48,7 +51,9 @@ class TestDomainAdminAsStaff(MockEppLib):
@classmethod
def setUpClass(self):
super().setUpClass()
+ self.superuser = create_superuser()
self.staffuser = create_user()
+ self.omb_analyst = create_omb_analyst_user()
self.site = AdminSite()
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
self.factory = RequestFactory()
@@ -56,6 +61,24 @@ class TestDomainAdminAsStaff(MockEppLib):
def setUp(self):
self.client = Client(HTTP_HOST="localhost:8080")
self.client.force_login(self.staffuser)
+ self.nonfebdomain = Domain.objects.create(name="nonfebexample.com")
+ self.febdomain = Domain.objects.create(name="febexample.com", state=Domain.State.READY)
+ self.fed_agency = FederalAgency.objects.create(
+ agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
+ )
+ self.portfolio = Portfolio.objects.create(
+ organization_name="new portfolio",
+ organization_type=DomainRequest.OrganizationChoices.FEDERAL,
+ federal_agency=self.fed_agency,
+ creator=self.staffuser,
+ )
+ self.domain_info = DomainInformation.objects.create(
+ domain=self.febdomain, portfolio=self.portfolio, creator=self.staffuser
+ )
+ self.nonfebportfolio = Portfolio.objects.create(
+ organization_name="non feb portfolio",
+ creator=self.staffuser,
+ )
super().setUp()
def tearDown(self):
@@ -65,12 +88,134 @@ class TestDomainAdminAsStaff(MockEppLib):
Domain.objects.all().delete()
DomainInformation.objects.all().delete()
DomainRequest.objects.all().delete()
+ Portfolio.objects.all().delete()
+ self.fed_agency.delete()
@classmethod
def tearDownClass(self):
User.objects.all().delete()
super().tearDownClass()
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts can view domain list."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_domain_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.febdomain.name)
+ self.assertNotContains(response, self.nonfebdomain.name)
+ self.assertNotContains(response, ">Import<")
+ self.assertNotContains(response, ">Export<")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts can view/edit federal executive branch domains."""
+ self.client.force_login(self.omb_analyst)
+ response = self.client.get(reverse("admin:registrar_domain_change", args=[self.nonfebdomain.id]))
+ self.assertEqual(response.status_code, 302)
+ response = self.client.get(reverse("admin:registrar_domain_change", args=[self.febdomain.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.febdomain.name)
+ # test portfolio dropdown
+ self.assertContains(response, self.portfolio.organization_name)
+ self.assertNotContains(response, self.nonfebportfolio.organization_name)
+ # test buttons
+ self.assertNotContains(response, "Manage domain")
+ self.assertNotContains(response, "Get registry status")
+ self.assertNotContains(response, "Extend expiration date")
+ self.assertNotContains(response, "Remove from registry")
+ self.assertContains(response, "Place hold")
+ self.assertContains(response, "Save")
+ self.assertNotContains(response, ">Delete<")
+ # test whether fields are readonly or editable
+ self.assertNotContains(response, "id_domain_info-0-portfolio")
+ self.assertNotContains(response, "id_domain_info-0-sub_organization")
+ self.assertNotContains(response, "id_domain_info-0-creator")
+ self.assertNotContains(response, "id_domain_info-0-federal_agency")
+ self.assertNotContains(response, "id_domain_info-0-about_your_organization")
+ self.assertNotContains(response, "id_domain_info-0-anything_else")
+ self.assertNotContains(response, "id_domain_info-0-cisa_representative_first_name")
+ self.assertNotContains(response, "id_domain_info-0-cisa_representative_last_name")
+ self.assertNotContains(response, "id_domain_info-0-cisa_representative_email")
+ self.assertNotContains(response, "id_domain_info-0-domain_request")
+ self.assertNotContains(response, "id_domain_info-0-notes")
+ self.assertNotContains(response, "id_domain_info-0-senior_official")
+ self.assertNotContains(response, "id_domain_info-0-organization_type")
+ self.assertNotContains(response, "id_domain_info-0-state_territory")
+ self.assertNotContains(response, "id_domain_info-0-address_line1")
+ self.assertNotContains(response, "id_domain_info-0-address_line2")
+ self.assertNotContains(response, "id_domain_info-0-city")
+ self.assertNotContains(response, "id_domain_info-0-zipcode")
+ self.assertNotContains(response, "id_domain_info-0-urbanization")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_organization_type")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_federal_type")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_organization_name")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_federal_agency")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_state_territory")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_address_line1")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_address_line2")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_city")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_zipcode")
+ self.assertNotContains(response, "id_domain_info-0-portfolio_urbanization")
+ self.assertNotContains(response, "id_domain_info-0-organization_type")
+ self.assertNotContains(response, "id_domain_info-0-federal_type")
+ self.assertNotContains(response, "id_domain_info-0-federal_agency")
+ self.assertNotContains(response, "id_domain_info-0-tribe_name")
+ self.assertNotContains(response, "id_domain_info-0-federally_recognized_tribe")
+ self.assertNotContains(response, "id_domain_info-0-state_recognized_tribe")
+ self.assertNotContains(response, "id_domain_info-0-about_your_organization")
+ self.assertNotContains(response, "id_domain_info-0-portfolio")
+ self.assertNotContains(response, "id_domain_info-0-sub_organization")
+
+ @less_console_noise_decorator
+ def test_superuser_change(self):
+ """Ensure super user can view/edit all domains."""
+ self.client.force_login(self.superuser)
+ response = self.client.get(reverse("admin:registrar_domain_change", args=[self.nonfebdomain.id]))
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get(reverse("admin:registrar_domain_change", args=[self.febdomain.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, self.febdomain.name)
+ # test portfolio dropdown
+ self.assertContains(response, self.portfolio.organization_name)
+ # test buttons
+ self.assertContains(response, "Manage domain")
+ self.assertContains(response, "Get registry status")
+ self.assertContains(response, "Extend expiration date")
+ self.assertContains(response, "Remove from registry")
+ self.assertContains(response, "Place hold")
+ self.assertContains(response, "Save")
+ self.assertContains(response, ">Delete<")
+ # test whether fields are readonly or editable
+ self.assertContains(response, "id_domain_info-0-portfolio")
+ self.assertContains(response, "id_domain_info-0-sub_organization")
+ self.assertContains(response, "id_domain_info-0-creator")
+ self.assertContains(response, "id_domain_info-0-federal_agency")
+ self.assertContains(response, "id_domain_info-0-about_your_organization")
+ self.assertContains(response, "id_domain_info-0-anything_else")
+ self.assertContains(response, "id_domain_info-0-cisa_representative_first_name")
+ self.assertContains(response, "id_domain_info-0-cisa_representative_last_name")
+ self.assertContains(response, "id_domain_info-0-cisa_representative_email")
+ self.assertContains(response, "id_domain_info-0-domain_request")
+ self.assertContains(response, "id_domain_info-0-notes")
+ self.assertContains(response, "id_domain_info-0-senior_official")
+ self.assertContains(response, "id_domain_info-0-organization_type")
+ self.assertContains(response, "id_domain_info-0-state_territory")
+ self.assertContains(response, "id_domain_info-0-address_line1")
+ self.assertContains(response, "id_domain_info-0-address_line2")
+ self.assertContains(response, "id_domain_info-0-city")
+ self.assertContains(response, "id_domain_info-0-zipcode")
+ self.assertContains(response, "id_domain_info-0-urbanization")
+ self.assertContains(response, "id_domain_info-0-organization_type")
+ self.assertContains(response, "id_domain_info-0-federal_type")
+ self.assertContains(response, "id_domain_info-0-federal_agency")
+ self.assertContains(response, "id_domain_info-0-tribe_name")
+ self.assertContains(response, "id_domain_info-0-federally_recognized_tribe")
+ self.assertContains(response, "id_domain_info-0-state_recognized_tribe")
+ self.assertContains(response, "id_domain_info-0-about_your_organization")
+ self.assertContains(response, "id_domain_info-0-portfolio")
+ self.assertContains(response, "id_domain_info-0-sub_organization")
+
@less_console_noise_decorator
def test_staff_can_see_cisa_region_federal(self):
"""Tests if staff can see CISA Region: N/A"""
diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py
index 9320dd3d3..8256cb188 100644
--- a/src/registrar/tests/test_admin_request.py
+++ b/src/registrar/tests/test_admin_request.py
@@ -1,6 +1,8 @@
from datetime import datetime
from django.forms import ValidationError
from django.utils import timezone
+from registrar.models.federal_agency import FederalAgency
+from registrar.utility.constants import BranchChoices
from waffle.testutils import override_flag
import re
from django.test import RequestFactory, Client, TestCase, override_settings
@@ -37,6 +39,7 @@ from .common import (
less_console_noise,
create_superuser,
create_user,
+ create_omb_analyst_user,
multiple_unalphabetical_domain_objects,
MockEppLib,
GenericTestHelper,
@@ -68,6 +71,7 @@ class TestDomainRequestAdmin(MockEppLib):
self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
self.superuser = create_superuser()
self.staffuser = create_user()
+ self.ombanalyst = create_omb_analyst_user()
self.client = Client(HTTP_HOST="localhost:8080")
self.test_helper = GenericTestHelper(
factory=self.factory,
@@ -80,6 +84,12 @@ class TestDomainRequestAdmin(MockEppLib):
allowed_emails = [AllowedEmail(email="mayor@igorville.gov"), AllowedEmail(email="help@get.gov")]
AllowedEmail.objects.bulk_create(allowed_emails)
+ def setUp(self):
+ super().setUp()
+ self.fed_agency = FederalAgency.objects.create(
+ agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
+ )
+
def tearDown(self):
super().tearDown()
Host.objects.all().delete()
@@ -92,6 +102,7 @@ class TestDomainRequestAdmin(MockEppLib):
SeniorOfficial.objects.all().delete()
Suborganization.objects.all().delete()
Portfolio.objects.all().delete()
+ self.fed_agency.delete()
self.mock_client.EMAILS_SENT.clear()
@classmethod
@@ -100,6 +111,71 @@ class TestDomainRequestAdmin(MockEppLib):
User.objects.all().delete()
AllowedEmail.objects.all().delete()
+ @override_flag("organization_feature", active=True)
+ @less_console_noise_decorator
+ def test_omb_analyst_view(self):
+ """Ensure OMB analysts can view domain request list."""
+ febportfolio = Portfolio.objects.create(
+ organization_name="new portfolio",
+ organization_type=DomainRequest.OrganizationChoices.FEDERAL,
+ federal_agency=self.fed_agency,
+ creator=self.ombanalyst,
+ )
+ nonfebportfolio = Portfolio.objects.create(
+ organization_name="non feb portfolio",
+ creator=self.ombanalyst,
+ )
+ nonfebdomainrequest = completed_domain_request(
+ name="test1234nonfeb.gov",
+ portfolio=nonfebportfolio,
+ status=DomainRequest.DomainRequestStatus.SUBMITTED,
+ )
+ febdomainrequest = completed_domain_request(
+ name="test1234feb.gov",
+ portfolio=febportfolio,
+ status=DomainRequest.DomainRequestStatus.SUBMITTED,
+ )
+ self.client.force_login(self.ombanalyst)
+ response = self.client.get(reverse("admin:registrar_domainrequest_changelist"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, febdomainrequest.requested_domain.name)
+ self.assertNotContains(response, nonfebdomainrequest.requested_domain.name)
+ self.assertNotContains(response, ">Import<")
+ self.assertNotContains(response, ">Export<")
+
+ @less_console_noise_decorator
+ def test_omb_analyst_change(self):
+ """Ensure OMB analysts can view/edit federal executive branch domain requests."""
+ self.client.force_login(self.ombanalyst)
+ febportfolio = Portfolio.objects.create(
+ organization_name="new portfolio",
+ organization_type=DomainRequest.OrganizationChoices.FEDERAL,
+ federal_agency=self.fed_agency,
+ creator=self.ombanalyst,
+ )
+ nonfebportfolio = Portfolio.objects.create(
+ organization_name="non feb portfolio",
+ creator=self.ombanalyst,
+ )
+ nonfebdomainrequest = completed_domain_request(
+ name="test1234nonfeb.gov",
+ portfolio=nonfebportfolio,
+ status=DomainRequest.DomainRequestStatus.SUBMITTED,
+ )
+ febdomainrequest = completed_domain_request(
+ name="test1234feb.gov",
+ portfolio=febportfolio,
+ status=DomainRequest.DomainRequestStatus.SUBMITTED,
+ )
+ response = self.client.get(reverse("admin:registrar_domainrequest_change", args=[nonfebdomainrequest.id]))
+ self.assertEqual(response.status_code, 302)
+ response = self.client.get(reverse("admin:registrar_domainrequest_change", args=[febdomainrequest.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, febdomainrequest.requested_domain.name)
+ # test buttons
+ self.assertContains(response, "Save")
+ self.assertNotContains(response, ">Delete<")
+
@override_flag("organization_feature", active=True)
@less_console_noise_decorator
def test_clean_validates_duplicate_suborganization(self):
@@ -2072,6 +2148,86 @@ class TestDomainRequestAdmin(MockEppLib):
self.assertEqual(readonly_fields, expected_fields)
+ def test_readonly_fields_for_omb_analyst(self):
+ with less_console_noise():
+ request = self.factory.get("/") # Use the correct method and path
+ request.user = self.ombanalyst
+
+ readonly_fields = self.admin.get_readonly_fields(request)
+
+ expected_fields = [
+ "portfolio_senior_official",
+ "portfolio_organization_type",
+ "portfolio_federal_type",
+ "portfolio_organization_name",
+ "portfolio_federal_agency",
+ "portfolio_state_territory",
+ "portfolio_address_line1",
+ "portfolio_address_line2",
+ "portfolio_city",
+ "portfolio_zipcode",
+ "portfolio_urbanization",
+ "other_contacts",
+ "current_websites",
+ "alternative_domains",
+ "is_election_board",
+ "status_history",
+ "federal_agency",
+ "creator",
+ "about_your_organization",
+ "requested_domain",
+ "approved_domain",
+ "alternative_domains",
+ "purpose",
+ "no_other_contacts_rationale",
+ "anything_else",
+ "is_policy_acknowledged",
+ "cisa_representative_first_name",
+ "cisa_representative_last_name",
+ "cisa_representative_email",
+ "status",
+ "investigator",
+ "notes",
+ "senior_official",
+ "organization_type",
+ "organization_name",
+ "state_territory",
+ "address_line1",
+ "address_line2",
+ "city",
+ "zipcode",
+ "urbanization",
+ "portfolio_organization_type",
+ "portfolio_federal_type",
+ "portfolio_organization_name",
+ "portfolio_federal_agency",
+ "portfolio_state_territory",
+ "portfolio_address_line1",
+ "portfolio_address_line2",
+ "portfolio_city",
+ "portfolio_zipcode",
+ "portfolio_urbanization",
+ "is_election_board",
+ "organization_type",
+ "federal_type",
+ "federal_agency",
+ "tribe_name",
+ "federally_recognized_tribe",
+ "state_recognized_tribe",
+ "about_your_organization",
+ "rejection_reason",
+ "rejection_reason_email",
+ "action_needed_reason",
+ "action_needed_reason_email",
+ "portfolio",
+ "sub_organization",
+ "requested_suborganization",
+ "suborganization_city",
+ "suborganization_state_territory",
+ ]
+
+ self.assertEqual(readonly_fields, expected_fields)
+
def test_saving_when_restricted_creator(self):
with less_console_noise():
# Create an instance of the model
diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py
index 9ec3bd0d3..236e810cf 100644
--- a/src/registrar/tests/test_reports.py
+++ b/src/registrar/tests/test_reports.py
@@ -72,7 +72,7 @@ class CsvReportsTest(MockDbForSharedTests):
fake_open = mock_open()
expected_file_content = [
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
- call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
+ call("cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\r\n"),
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
@@ -94,7 +94,7 @@ class CsvReportsTest(MockDbForSharedTests):
fake_open = mock_open()
expected_file_content = [
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
- call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
+ call("cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\r\n"),
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
@@ -261,9 +261,6 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,"
"Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,,, ,,(blank),"
'"big_lebowski@dude.co, info@example.com, meoward@rocks.com",woofwardthethird@rocks.com\n'
- "cdomain11.gov,Ready,2024-04-02,(blank),Federal - Executive,"
- "World War I Centennial Commission,,,, ,,(blank),"
- "meoward@rocks.com,\n"
"adomain10.gov,Ready,2024-04-03,(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,"
"squeaker@rocks.com\n"
"bdomain4.gov,Unknown,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
@@ -274,6 +271,9 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
"sdomain8.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
"xdomain7.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
"zdomain9.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
+ "cdomain11.gov,Ready,2024-04-02,(blank),Federal,"
+ "World War I Centennial Commission,,,, ,,(blank),"
+ "meoward@rocks.com,\n"
"zdomain12.gov,Ready,2024-04-02,(blank),Interstate,,,,, ,,(blank),meoward@rocks.com,\n"
)
@@ -498,7 +498,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
# sorted alphabetially by domain name
expected_content = (
"Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n"
- "cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
+ "cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\n"
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\n"
"ddomain3.gov,Federal,Armed Forces Retirement Home,,,,security@mail.gov\n"
@@ -538,7 +538,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
# sorted alphabetially by domain name
expected_content = (
"Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n"
- "cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
+ "cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\n"
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\n"
"ddomain3.gov,Federal,Armed Forces Retirement Home,,,,security@mail.gov\n"
@@ -594,7 +594,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
"State,Status,Expiration date, Deleted\n"
"cdomain1.gov,Federal-Executive,Portfolio1FederalAgency,Portfolio1FederalAgency,Ready,(blank)\n"
"adomain10.gov,Federal,ArmedForcesRetirementHome,Ready,(blank)\n"
- "cdomain11.gov,Federal-Executive,WorldWarICentennialCommission,Ready,(blank)\n"
+ "cdomain11.gov,Federal,WorldWarICentennialCommission,Ready,(blank)\n"
"zdomain12.gov,Interstate,Ready,(blank)\n"
"zdomain9.gov,Federal,ArmedForcesRetirementHome,Deleted,(blank),2024-04-01\n"
"sdomain8.gov,Federal,ArmedForcesRetirementHome,Deleted,(blank),2024-04-02\n"
@@ -642,7 +642,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
"3,2,1,0,0,0,0,0,0,0\n"
"\n"
"Domain name,Domain type,Domain managers,Invited domain managers\n"
- "cdomain11.gov,Federal - Executive,meoward@rocks.com,\n"
+ "cdomain11.gov,Federal,meoward@rocks.com,\n"
'cdomain1.gov,Federal - Executive,"big_lebowski@dude.co, info@example.com, meoward@rocks.com",'
"woofwardthethird@rocks.com\n"
"zdomain12.gov,Interstate,meoward@rocks.com,\n"
@@ -716,7 +716,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
expected_content = (
"Domain request,Domain type,Federal type\n"
"city3.gov,Federal,Executive\n"
- "city4.gov,City,Executive\n"
+ "city4.gov,City,\n"
"city6.gov,Federal,Executive\n"
)
@@ -783,7 +783,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
"SO last name,SO email,SO title/role,Request purpose,Request additional details,Other contacts,"
"CISA regional representative,Current websites,Investigator\n"
# Content
- "city5.gov,Approved,Federal,No,Executive,,Testorg,N/A,,NY,2,requested_suborg,SanFran,CA,,,,,1,0,"
+ "city5.gov,Approved,Federal,No,,,Testorg,N/A,,NY,2,requested_suborg,SanFran,CA,,,,,1,0,"
"city1.gov,Testy,Tester,testy@town.com,Chief Tester,Purpose of the site,There is more,"
"Testy Tester testy2@town.com,,city.com,\n"
"city2.gov,In review,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,"
@@ -795,7 +795,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
'There is more,"Meow Tester24 te2@town.com, Testy1232 Tester24 te2@town.com, '
'Testy Tester testy2@town.com",'
'test@igorville.com,"city.com, https://www.example2.com, https://www.example.com",\n'
- "city4.gov,Submitted,City,No,Executive,,Testorg,Yes,,NY,2,,,,,,,,0,1,city1.gov,Testy,"
+ "city4.gov,Submitted,City,No,,,Testorg,Yes,,NY,2,,,,,,,,0,1,city1.gov,Testy,"
"Tester,testy@town.com,"
"Chief Tester,Purpose of the site,CISA-first-name CISA-last-name | There is more,"
"Testy Tester testy2@town.com,"
diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py
index fad58b2e2..cde91baca 100644
--- a/src/registrar/utility/csv_export.py
+++ b/src/registrar/utility/csv_export.py
@@ -579,8 +579,8 @@ class DomainExport(BaseExport):
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
then=F("portfolio__federal_agency__federal_type"),
),
- # Otherwise, return the natively assigned value
- default=F("federal_type"),
+ # Otherwise, return the federal type from federal agency
+ default=F("federal_agency__federal_type"),
output_field=CharField(),
),
"converted_organization_name": Case(
@@ -1654,8 +1654,8 @@ class DomainRequestExport(BaseExport):
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
then=F("portfolio__federal_agency__federal_type"),
),
- # Otherwise, return the natively assigned value
- default=F("federal_type"),
+ # Otherwise, return the federal type from federal agency
+ default=F("federal_agency__federal_type"),
output_field=CharField(),
),
"converted_organization_name": Case(
diff --git a/src/registrar/views/report_views.py b/src/registrar/views/report_views.py
index c07dcfc1b..7f1e63e32 100644
--- a/src/registrar/views/report_views.py
+++ b/src/registrar/views/report_views.py
@@ -6,7 +6,7 @@ from django.shortcuts import render
from django.contrib import admin
from django.db.models import Avg, F
-from registrar.decorators import ALL, HAS_PORTFOLIO_MEMBERS_VIEW, IS_STAFF, grant_access
+from registrar.decorators import ALL, HAS_PORTFOLIO_MEMBERS_VIEW, IS_CISA_ANALYST, IS_FULL_ACCESS, grant_access
from .. import models
import datetime
from django.utils import timezone
@@ -16,7 +16,7 @@ import logging
logger = logging.getLogger(__name__)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class AnalyticsView(View):
def get(self, request):
thirty_days_ago = datetime.datetime.today() - datetime.timedelta(days=30)
@@ -176,7 +176,7 @@ class AnalyticsView(View):
return render(request, "admin/analytics.html", context)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDataType(View):
def get(self, request, *args, **kwargs):
# match the CSV example with all the fields
@@ -227,7 +227,7 @@ class ExportMembersPortfolio(View):
return response
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDataFull(View):
def get(self, request, *args, **kwargs):
# Smaller export based on 1
@@ -237,7 +237,7 @@ class ExportDataFull(View):
return response
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDataFederal(View):
def get(self, request, *args, **kwargs):
# Federal only
@@ -247,7 +247,7 @@ class ExportDataFederal(View):
return response
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDomainRequestDataFull(View):
"""Generates a downloaded report containing all Domain Requests (except started)"""
@@ -259,7 +259,7 @@ class ExportDomainRequestDataFull(View):
return response
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDataDomainsGrowth(View):
def get(self, request, *args, **kwargs):
start_date = request.GET.get("start_date", "")
@@ -272,7 +272,7 @@ class ExportDataDomainsGrowth(View):
return response
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDataRequestsGrowth(View):
def get(self, request, *args, **kwargs):
start_date = request.GET.get("start_date", "")
@@ -285,7 +285,7 @@ class ExportDataRequestsGrowth(View):
return response
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDataManagedDomains(View):
def get(self, request, *args, **kwargs):
start_date = request.GET.get("start_date", "")
@@ -297,7 +297,7 @@ class ExportDataManagedDomains(View):
return response
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class ExportDataUnmanagedDomains(View):
def get(self, request, *args, **kwargs):
start_date = request.GET.get("start_date", "")
diff --git a/src/registrar/views/transfer_user.py b/src/registrar/views/transfer_user.py
index 62cd0a9d2..ee8ebad35 100644
--- a/src/registrar/views/transfer_user.py
+++ b/src/registrar/views/transfer_user.py
@@ -4,7 +4,7 @@ from django.db.models import ForeignKey, OneToOneField, ManyToManyField, ManyToO
from django.shortcuts import render, get_object_or_404, redirect
from django.views import View
-from registrar.decorators import IS_STAFF, grant_access
+from registrar.decorators import IS_CISA_ANALYST, IS_FULL_ACCESS, grant_access
from registrar.models.domain import Domain
from registrar.models.domain_request import DomainRequest
from registrar.models.user import User
@@ -19,7 +19,7 @@ from registrar.utility.db_helpers import ignore_unique_violation
logger = logging.getLogger(__name__)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
class TransferUserView(View):
"""Transfer user methods that set up the transfer_user template and handle the forms on it."""
diff --git a/src/registrar/views/utility/api_views.py b/src/registrar/views/utility/api_views.py
index 6d0a2b5ec..ea794e185 100644
--- a/src/registrar/views/utility/api_views.py
+++ b/src/registrar/views/utility/api_views.py
@@ -1,7 +1,7 @@
import logging
from django.http import JsonResponse
from django.forms.models import model_to_dict
-from registrar.decorators import IS_STAFF, grant_access
+from registrar.decorators import IS_CISA_ANALYST, IS_FULL_ACCESS, IS_OMB_ANALYST, grant_access
from registrar.models import FederalAgency, SeniorOfficial, DomainRequest
from registrar.utility.admin_helpers import get_action_needed_reason_default_email, get_rejection_reason_default_email
from registrar.models.portfolio import Portfolio
@@ -10,16 +10,10 @@ from registrar.utility.constants import BranchChoices
logger = logging.getLogger(__name__)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
def get_senior_official_from_federal_agency_json(request):
"""Returns federal_agency information as a JSON"""
- # This API is only accessible to admins and analysts
- superuser_perm = request.user.has_perm("registrar.full_access_permission")
- analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
- if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
- return JsonResponse({"error": "You do not have access to this resource"}, status=403)
-
agency_name = request.GET.get("agency_name")
agency = FederalAgency.objects.filter(agency=agency_name).first()
senior_official = SeniorOfficial.objects.filter(federal_agency=agency).first()
@@ -37,16 +31,10 @@ def get_senior_official_from_federal_agency_json(request):
return JsonResponse({"error": "Senior Official not found"}, status=404)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
def get_portfolio_json(request):
"""Returns portfolio information as a JSON"""
- # This API is only accessible to admins and analysts
- superuser_perm = request.user.has_perm("registrar.full_access_permission")
- analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
- if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
- return JsonResponse({"error": "You do not have access to this resource"}, status=403)
-
portfolio_id = request.GET.get("id")
try:
portfolio = Portfolio.objects.get(id=portfolio_id)
@@ -93,16 +81,10 @@ def get_portfolio_json(request):
return JsonResponse(portfolio_dict)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
def get_suborganization_list_json(request):
"""Returns suborganization list information for a portfolio as a JSON"""
- # This API is only accessible to admins and analysts
- superuser_perm = request.user.has_perm("registrar.full_access_permission")
- analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
- if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
- return JsonResponse({"error": "You do not have access to this resource"}, status=403)
-
portfolio_id = request.GET.get("portfolio_id")
try:
portfolio = Portfolio.objects.get(id=portfolio_id)
@@ -115,17 +97,11 @@ def get_suborganization_list_json(request):
return JsonResponse({"results": results, "pagination": {"more": False}})
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
def get_federal_and_portfolio_types_from_federal_agency_json(request):
"""Returns specific portfolio information as a JSON. Request must have
both agency_name and organization_type."""
- # This API is only accessible to admins and analysts
- superuser_perm = request.user.has_perm("registrar.full_access_permission")
- analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
- if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
- return JsonResponse({"error": "You do not have access to this resource"}, status=403)
-
federal_type = None
portfolio_type = None
@@ -143,16 +119,10 @@ def get_federal_and_portfolio_types_from_federal_agency_json(request):
return JsonResponse(response_data)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
def get_action_needed_email_for_user_json(request):
"""Returns a default action needed email for a given user"""
- # This API is only accessible to admins and analysts
- superuser_perm = request.user.has_perm("registrar.full_access_permission")
- analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
- if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
- return JsonResponse({"error": "You do not have access to this resource"}, status=403)
-
reason = request.GET.get("reason")
domain_request_id = request.GET.get("domain_request_id")
if not reason:
@@ -167,16 +137,10 @@ def get_action_needed_email_for_user_json(request):
return JsonResponse({"email": email}, status=200)
-@grant_access(IS_STAFF)
+@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
def get_rejection_email_for_user_json(request):
"""Returns a default rejection email for a given user"""
- # This API is only accessible to admins and analysts
- superuser_perm = request.user.has_perm("registrar.full_access_permission")
- analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
- if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
- return JsonResponse({"error": "You do not have access to this resource"}, status=403)
-
reason = request.GET.get("reason")
domain_request_id = request.GET.get("domain_request_id")
if not reason:
|