mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-17 01:57:03 +02:00
unit tests, add cisa_analyst permission in the cisa_analysts_group for better grannular hasPerm testing in admin.py
This commit is contained in:
parent
cd14eb2584
commit
155baa0200
11 changed files with 142 additions and 64 deletions
|
@ -9,13 +9,8 @@ our `user_group` model and run in a migration.
|
||||||
|
|
||||||
## Editing group permissions through code
|
## Editing group permissions through code
|
||||||
|
|
||||||
We can edit and deploy new group permissions by
|
We can edit and deploy new group permissions by:
|
||||||
editing `user_group` then:
|
|
||||||
|
|
||||||
- Duplicating migration `0036_create_groups`
|
1. editing `user_group` then:
|
||||||
and running migrations (RECOMMENDED METHOD), or
|
2. Duplicating migration `0036_create_groups`
|
||||||
|
and running migrations
|
||||||
- Fake the previous migration to run an existing create groups migration:
|
|
||||||
- step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
|
||||||
- step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
|
|
||||||
- step 3: fake run the latest migration in the migrations list
|
|
|
@ -205,7 +205,7 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
# which is equivalent to superuser. The other group we use to manage
|
# which is equivalent to superuser. The other group we use to manage
|
||||||
# perms is cisa_analysts_group. cisa_analysts_group will never contain
|
# perms is cisa_analysts_group. cisa_analysts_group will never contain
|
||||||
# full_access_permission
|
# full_access_permission
|
||||||
if request.user.has_perm('registrar.full_access_permission'):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
# Use the default list display for all access users
|
# Use the default list display for all access users
|
||||||
return super().get_list_display(request)
|
return super().get_list_display(request)
|
||||||
|
|
||||||
|
@ -220,17 +220,23 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
if request.user.has_perm('registrar.full_access_permission'):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
# Show all fields for all access users
|
# Show all fields for all access users
|
||||||
return super().get_fieldsets(request, obj)
|
return super().get_fieldsets(request, obj)
|
||||||
|
elif request.user.has_perm("registrar.analyst_access_permission"):
|
||||||
# show analyst_fieldsets for analysts
|
# show analyst_fieldsets for analysts
|
||||||
return self.analyst_fieldsets
|
return self.analyst_fieldsets
|
||||||
|
else:
|
||||||
|
# any admin user should belong to either full_access_group
|
||||||
|
# or cisa_analyst_group
|
||||||
|
return []
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
if request.user.has_perm('registrar.full_access_permission'):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return () # No read-only fields for all access users
|
return () # No read-only fields for all access users
|
||||||
return self.analyst_readonly_fields # Read-only fields for analysts
|
# Return restrictive Read-only fields for analysts and
|
||||||
|
# users who might not belong to groups
|
||||||
|
return self.analyst_readonly_fields
|
||||||
|
|
||||||
|
|
||||||
class HostIPInline(admin.StackedInline):
|
class HostIPInline(admin.StackedInline):
|
||||||
|
@ -409,11 +415,12 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
readonly_fields = list(self.readonly_fields)
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
if request.user.has_perm('registrar.full_access_permission'):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
else:
|
# Return restrictive Read-only fields for analysts and
|
||||||
|
# users who might not belong to groups
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
return readonly_fields
|
return readonly_fields # Read-only fields for analysts
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdminForm(forms.ModelForm):
|
class DomainApplicationAdminForm(forms.ModelForm):
|
||||||
|
@ -627,9 +634,10 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
["current_websites", "other_contacts", "alternative_domains"]
|
["current_websites", "other_contacts", "alternative_domains"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.user.has_perm('registrar.full_access_permission'):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
else:
|
# Return restrictive Read-only fields for analysts and
|
||||||
|
# users who might not belong to groups
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
|
||||||
|
@ -702,7 +710,7 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
search_help_text = "Search by domain name."
|
search_help_text = "Search by domain name."
|
||||||
change_form_template = "django/admin/domain_change_form.html"
|
change_form_template = "django/admin/domain_change_form.html"
|
||||||
readonly_fields = ["state"]
|
# readonly_fields = ["state"]
|
||||||
|
|
||||||
def response_change(self, request, obj):
|
def response_change(self, request, obj):
|
||||||
# Create dictionary of action functions
|
# Create dictionary of action functions
|
||||||
|
|
|
@ -10,9 +10,6 @@ from registrar.models import (
|
||||||
Website,
|
Website,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
fake = Faker()
|
fake = Faker()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -153,4 +153,3 @@ class UserFixture:
|
||||||
def load(cls):
|
def load(cls):
|
||||||
cls.load_users(cls, cls.ADMINS, "full_access_group")
|
cls.load_users(cls, cls.ADMINS, "full_access_group")
|
||||||
cls.load_users(cls, cls.STAFF, "cisa_analysts_group")
|
cls.load_users(cls, cls.STAFF, "cisa_analysts_group")
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Migration(migrations.Migration):
|
||||||
name="user",
|
name="user",
|
||||||
options={
|
options={
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
("analyst_access_permission", "Analyst Access Permission"),
|
||||||
("full_access_permission", "Full Access Permission"),
|
("full_access_permission", "Full Access Permission"),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
# From mithuntnt's answer on:
|
# From mithuntnt's answer on:
|
||||||
# https://stackoverflow.com/questions/26464838/getting-model-contenttype-in-migration-django-1-7
|
# https://stackoverflow.com/questions/26464838/getting-model-contenttype-in-migration-django-1-7
|
||||||
# The problem is that ContentType and Permission objects are not already created
|
# The problem is that ContentType and Permission objects are not already created
|
||||||
# while we're still running migrations, so we'll go ahead and speen up that process
|
# while we're still running migrations, so we'll go ahead and speed up that process
|
||||||
# a bit before we attempt to create groups which require Permissions and ContentType.
|
# a bit before we attempt to create groups which require Permissions and ContentType.
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
def create_all_contenttypes(**kwargs):
|
def create_all_contenttypes(**kwargs):
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.contenttypes.management import create_contenttypes
|
from django.contrib.contenttypes.management import create_contenttypes
|
||||||
|
@ -14,6 +15,7 @@ def create_all_contenttypes(**kwargs):
|
||||||
for app_config in apps.get_app_configs():
|
for app_config in apps.get_app_configs():
|
||||||
create_contenttypes(app_config, **kwargs)
|
create_contenttypes(app_config, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def create_all_permissions(**kwargs):
|
def create_all_permissions(**kwargs):
|
||||||
from django.contrib.auth.management import create_permissions
|
from django.contrib.auth.management import create_permissions
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
@ -21,20 +23,21 @@ def create_all_permissions(**kwargs):
|
||||||
for app_config in apps.get_app_configs():
|
for app_config in apps.get_app_configs():
|
||||||
create_permissions(app_config, **kwargs)
|
create_permissions(app_config, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def forward(apps, schema_editor):
|
def forward(apps, schema_editor):
|
||||||
create_all_contenttypes()
|
create_all_contenttypes()
|
||||||
create_all_permissions()
|
create_all_permissions()
|
||||||
|
|
||||||
|
|
||||||
def backward(apps, schema_editor):
|
def backward(apps, schema_editor):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
("registrar", "0034_alter_user_options"),
|
("registrar", "0034_alter_user_options"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [migrations.RunPython(forward, backward)]
|
||||||
migrations.RunPython(forward, backward)
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
||||||
|
# It is dependent on 0035 (which populates ContentType and Permissions)
|
||||||
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
||||||
# in the user_group model then:
|
# in the user_group model then:
|
||||||
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
||||||
|
@ -10,13 +11,20 @@
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
from registrar.models import UserGroup
|
from registrar.models import UserGroup
|
||||||
|
|
||||||
|
def create_groups():
|
||||||
|
UserGroup.create_cisa_analyst_group()
|
||||||
|
UserGroup.create_full_access_group()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("registrar", "0035_contenttypes_permissions"),
|
("registrar", "0035_contenttypes_permissions"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(UserGroup.create_cisa_analyst_group, reverse_code=migrations.RunPython.noop, atomic=True),
|
migrations.RunPython(
|
||||||
migrations.RunPython(UserGroup.create_full_access_group, reverse_code=migrations.RunPython.noop, atomic=True),
|
create_groups, # noqa
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
atomic=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -675,7 +675,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
max_length=21,
|
max_length=21,
|
||||||
choices=State.choices,
|
choices=State.choices,
|
||||||
default=State.UNKNOWN,
|
default=State.UNKNOWN,
|
||||||
protected=True, # cannot change state directly, particularly in Django admin
|
protected=False, # cannot change state directly, particularly in Django admin
|
||||||
help_text="Very basic info about the lifecycle of this domain object",
|
help_text="Very basic info about the lifecycle of this domain object",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -84,5 +84,6 @@ class User(AbstractUser):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = [
|
permissions = [
|
||||||
|
("analyst_access_permission", "Analyst Access Permission"),
|
||||||
("full_access_permission", "Full Access Permission"),
|
("full_access_permission", "Full Access Permission"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,13 +3,14 @@ import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UserGroup(Group):
|
|
||||||
|
|
||||||
|
class UserGroup(Group):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "User group"
|
verbose_name = "User group"
|
||||||
verbose_name_plural = "User groups"
|
verbose_name_plural = "User groups"
|
||||||
|
|
||||||
def create_cisa_analyst_group(apps, schema_editor):
|
def create_cisa_analyst_group(apps, schema_editor):
|
||||||
|
"""This method gets run from a data migration."""
|
||||||
|
|
||||||
# Hard to pass self to these methods as the calls from migrations
|
# Hard to pass self to these methods as the calls from migrations
|
||||||
# are only expecting apps and schema_editor, so we'll just define
|
# are only expecting apps and schema_editor, so we'll just define
|
||||||
|
@ -20,7 +21,11 @@ class UserGroup(Group):
|
||||||
"model": "logentry",
|
"model": "logentry",
|
||||||
"permissions": ["view_logentry"],
|
"permissions": ["view_logentry"],
|
||||||
},
|
},
|
||||||
{"app_label": "registrar", "model": "contact", "permissions": ["view_contact"]},
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "contact",
|
||||||
|
"permissions": ["view_contact"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"app_label": "registrar",
|
"app_label": "registrar",
|
||||||
"model": "domaininformation",
|
"model": "domaininformation",
|
||||||
|
@ -31,13 +36,21 @@ class UserGroup(Group):
|
||||||
"model": "domainapplication",
|
"model": "domainapplication",
|
||||||
"permissions": ["change_domainapplication"],
|
"permissions": ["change_domainapplication"],
|
||||||
},
|
},
|
||||||
{"app_label": "registrar", "model": "domain", "permissions": ["view_domain"]},
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "domain",
|
||||||
|
"permissions": ["view_domain"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"app_label": "registrar",
|
"app_label": "registrar",
|
||||||
"model": "draftdomain",
|
"model": "draftdomain",
|
||||||
"permissions": ["change_draftdomain"],
|
"permissions": ["change_draftdomain"],
|
||||||
},
|
},
|
||||||
{"app_label": "registrar", "model": "user", "permissions": ["change_user"]},
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "user",
|
||||||
|
"permissions": ["analyst_access_permission", "change_user"],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Avoid error: You can't execute queries until the end
|
# Avoid error: You can't execute queries until the end
|
||||||
|
@ -77,9 +90,7 @@ class UserGroup(Group):
|
||||||
cisa_analysts_group.permissions.add(*permissions)
|
cisa_analysts_group.permissions.add(*permissions)
|
||||||
|
|
||||||
# Convert the permissions QuerySet to a list of codenames
|
# Convert the permissions QuerySet to a list of codenames
|
||||||
permission_list = list(
|
permission_list = list(permissions.values_list("codename", flat=True))
|
||||||
permissions.values_list("codename", flat=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
app_label
|
app_label
|
||||||
|
@ -92,11 +103,15 @@ class UserGroup(Group):
|
||||||
)
|
)
|
||||||
|
|
||||||
cisa_analysts_group.save()
|
cisa_analysts_group.save()
|
||||||
logger.debug("CISA Analyt permissions added to group " + cisa_analysts_group.name)
|
logger.debug(
|
||||||
|
"CISA Analyt permissions added to group " + cisa_analysts_group.name
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating analyst permissions group: {e}")
|
logger.error(f"Error creating analyst permissions group: {e}")
|
||||||
|
|
||||||
def create_full_access_group(apps, schema_editor):
|
def create_full_access_group(apps, schema_editor):
|
||||||
|
"""This method gets run from a data migration."""
|
||||||
|
|
||||||
Permission = apps.get_model("auth", "Permission")
|
Permission = apps.get_model("auth", "Permission")
|
||||||
UserGroup = apps.get_model("registrar", "UserGroup")
|
UserGroup = apps.get_model("registrar", "UserGroup")
|
||||||
|
|
||||||
|
|
51
src/registrar/tests/test_migrations.py
Normal file
51
src/registrar/tests/test_migrations.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from registrar.models import (
|
||||||
|
UserGroup,
|
||||||
|
)
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGroups(TestCase):
|
||||||
|
def test_groups_created(self):
|
||||||
|
"""The test enviroment contains data that was created in migration,
|
||||||
|
so we are able to test groups and permissions.
|
||||||
|
|
||||||
|
- Test cisa_analysts_group and full_access_group created
|
||||||
|
- Test permissions on full_access_group
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get the UserGroup objects
|
||||||
|
cisa_analysts_group = UserGroup.objects.get(name="cisa_analysts_group")
|
||||||
|
full_access_group = UserGroup.objects.get(name="full_access_group")
|
||||||
|
|
||||||
|
# Assert that the cisa_analysts_group exists in the database
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
UserGroup.objects.filter(name="cisa_analysts_group"), [cisa_analysts_group]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert that the full_access_group exists in the database
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
UserGroup.objects.filter(name="full_access_group"), [full_access_group]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test permissions for cisa_analysts)group
|
||||||
|
# Define the expected permission codenames
|
||||||
|
expected_permissions = [
|
||||||
|
"view_logentry",
|
||||||
|
"view_contact",
|
||||||
|
"view_domain",
|
||||||
|
"change_domainapplication",
|
||||||
|
"change_domaininformation",
|
||||||
|
"change_draftdomain",
|
||||||
|
"analyst_access_permission",
|
||||||
|
"change_user",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Get the codenames of actual permissions associated with the group
|
||||||
|
actual_permissions = [p.codename for p in cisa_analysts_group.permissions.all()]
|
||||||
|
|
||||||
|
# Assert that the actual permissions match the expected permissions
|
||||||
|
self.assertListEqual(actual_permissions, expected_permissions)
|
Loading…
Add table
Add a link
Reference in a new issue