You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

187 lines
7.1 KiB

from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group
from apps.account.models import User
class Command(BaseCommand):
help = (
"Audit admin panel access consistency for users. "
"Shows users with panel access and highlights unsafe role/flag/group combinations. "
"Use --fix to automatically normalize unsafe records."
)
PANEL_GROUPS = {"Professor Group", "Admin Group", "Super Admin Group", "Super admin Group"}
def add_arguments(self, parser):
parser.add_argument(
"--fix",
action="store_true",
help="Automatically fix unsafe user records.",
)
parser.add_argument(
"--email",
action="append",
dest="emails",
help="Limit audit to one or more email addresses. Can be passed multiple times.",
)
def handle(self, *args, **options):
should_fix = options["fix"]
emails = options.get("emails") or []
queryset = User.objects.filter(email__isnull=False).exclude(email="").order_by("id")
if emails:
queryset = queryset.filter(email__in=emails)
panel_users = []
anomalies = []
fixed_count = 0
for user in queryset:
groups = list(user.groups.values_list("name", flat=True))
can_panel = user.can_access_admin_panel()
if can_panel or user.is_staff or user.is_superuser or any(g in self.PANEL_GROUPS for g in groups):
panel_users.append(
{
"id": user.id,
"email": user.email,
"user_type": user.user_type,
"is_staff": user.is_staff,
"is_superuser": user.is_superuser,
"groups": groups,
"can_panel": can_panel,
}
)
user_anomalies = self.get_anomalies(user, groups)
if user_anomalies:
anomalies.append(
{
"user": user,
"groups": groups,
"anomalies": user_anomalies,
}
)
self.stdout.write(self.style.MIGRATE_HEADING("Panel Access Users"))
if panel_users:
for item in panel_users:
self.stdout.write(str(item))
else:
self.stdout.write(self.style.SUCCESS("No panel-related users found."))
self.stdout.write("")
self.stdout.write(self.style.MIGRATE_HEADING("Anomalies"))
if anomalies:
for item in anomalies:
user = item["user"]
self.stdout.write(
self.style.WARNING(
str(
{
"id": user.id,
"email": user.email,
"user_type": user.user_type,
"is_staff": user.is_staff,
"is_superuser": user.is_superuser,
"groups": item["groups"],
"anomalies": item["anomalies"],
}
)
)
)
if should_fix:
if self.fix_user(user, item["anomalies"]):
fixed_count += 1
else:
self.stdout.write(self.style.SUCCESS("No anomalies found."))
if should_fix:
self.stdout.write("")
self.stdout.write(self.style.SUCCESS(f"Fixed {fixed_count} user(s)."))
def get_anomalies(self, user, groups):
anomalies = []
if user.user_type in [User.UserType.STUDENT, User.UserType.CLIENT, User.UserType.CONSULTANT]:
if user.is_staff or user.is_superuser:
anomalies.append("low_role_with_staff_flags")
if any(group in self.PANEL_GROUPS for group in groups):
anomalies.append("low_role_with_panel_group")
if user.can_access_admin_panel():
anomalies.append("low_role_can_access_panel")
if user.user_type == User.UserType.PROFESSOR and "Professor Group" not in groups:
anomalies.append("professor_missing_group")
if user.user_type == User.UserType.ADMIN and "Admin Group" not in groups:
anomalies.append("admin_missing_group")
if user.user_type == User.UserType.SUPER_ADMIN and not any(
group in groups for group in ["Super Admin Group", "Super admin Group"]
):
anomalies.append("super_admin_missing_group")
if user.user_type == User.UserType.SUPER_ADMIN and not user.is_superuser:
anomalies.append("super_admin_missing_superuser_flag")
return anomalies
def fix_user(self, user, anomalies):
changed = False
if user.user_type == User.UserType.SUPER_ADMIN:
if not user.is_staff:
user.is_staff = True
changed = True
if not user.is_superuser:
user.is_superuser = True
changed = True
changed = self.ensure_group(user, "Super Admin Group") or changed
changed = self.remove_groups(user, {"Professor Group", "Admin Group", "Super admin Group"}) or changed
elif user.user_type == User.UserType.ADMIN:
if user.is_superuser:
user.is_superuser = False
changed = True
changed = self.ensure_group(user, "Admin Group") or changed
changed = self.remove_groups(user, {"Professor Group", "Super Admin Group", "Super admin Group"}) or changed
elif user.user_type == User.UserType.PROFESSOR:
if user.is_superuser:
user.is_superuser = False
changed = True
changed = self.ensure_group(user, "Professor Group") or changed
changed = self.remove_groups(user, {"Admin Group", "Super Admin Group", "Super admin Group"}) or changed
elif user.user_type in [User.UserType.STUDENT, User.UserType.CLIENT, User.UserType.CONSULTANT]:
if user.is_staff:
user.is_staff = False
changed = True
if user.is_superuser:
user.is_superuser = False
changed = True
changed = self.remove_groups(user, self.PANEL_GROUPS) or changed
if changed:
user.save()
self.stdout.write(self.style.SUCCESS(f"Fixed user {user.id} <{user.email}>"))
return changed
def ensure_group(self, user, group_name):
group, _ = Group.objects.get_or_create(name=group_name)
if not user.groups.filter(id=group.id).exists():
user.groups.add(group)
return True
return False
def remove_groups(self, user, group_names):
groups = Group.objects.filter(name__in=group_names)
existing_ids = set(user.groups.filter(id__in=groups.values("id")).values_list("id", flat=True))
if existing_ids:
user.groups.remove(*groups)
return True
return False