From 0bae79cfedb8353b83c3d1c66db0642f724143bd Mon Sep 17 00:00:00 2001
From: David Schmitz <schmitz@lrz.de>
Date: Wed, 12 Jul 2023 10:08:25 +0000
Subject: [PATCH] feature/admin_user_delete_with_owned_rule_reassigning: allow
admin deletion of users which are assigned as applier to some rules by
re-assigning the rules to another remaining user of the same peer the user to
be deleted pertains (only if the user is assigned to exactly 1 peer)
---
accounts/models.py | 55 +++++++++++++++++++++++++++++++++++++++
flowspec/admin.py | 23 ++++++++++++++++
flowspec/iprange_match.py | 9 +++++--
flowspec/models.py | 3 ++-
4 files changed, 87 insertions(+), 3 deletions(-)
diff --git a/accounts/models.py b/accounts/models.py
index d72f2f94..6d67dc4e 100644
--- a/accounts/models.py
+++ b/accounts/models.py
@@ -21,7 +21,16 @@ from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, User, BaseUserManager
from peers.models import Peer
+from flowspec.models import Route
+# TODO: dependency issue: move logging_utils to general package
+import flowspec.logging_utils
+logger = flowspec.logging_utils.logger_init_default(__name__, "accounts_model.log", False)
+
+#
+
+
+#
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
@@ -46,8 +55,54 @@ class UserProfile(models.Model):
return False
return networks
+ # deleting of rules by this account is allowed
def is_delete_allowed(self):
user_is_admin = self.user.is_superuser
username = self.username
return (user_is_admin and settings.ALLOW_DELETE_FULL_FOR_ADMIN) or settings.ALLOW_DELETE_FULL_FOR_USER_ALL or (username in settings.ALLOW_DELETE_FULL_FOR_USER_LIST)
+#
+
+from django.dispatch import receiver
+from django.db.models.signals import pre_delete
+
+#@receiver(pre_delete, sender=UserProfile)
+@receiver(pre_delete, sender=User)
+def user_pre_delete_handler(sender, instance, **kwargs):
+ logger.info("user_pre_delete_handler(): pre_delete instance="+str(instance))
+ user_owned_rules_adopt_to_related_user(instance)
+
+def user_owned_rules_adopt_to_related_user(user):
+ routes_owned = Route.objects.filter(applier=user)
+ logger.info("user_owned_rules_adopt_to_related_user(): => routes_owned="+str(routes_owned))
+
+ users_peers = user.userprofile.peers.all()
+ users_peers1 = None
+ logger.info("user_owned_rules_adopt_to_related_user(): => users_peers="+str(users_peers))
+ if len(users_peers)==1:
+ users_peers1 = users_peers[0]
+ logger.info("user_owned_rules_adopt_to_related_user(): => users_peers[0]="+str(users_peers1))
+ #peers1_userprofiles = users_peers[0].user_profile
+ #logger.info("user_owned_rules_adopt_to_related_user(): => peers1_userprofiles="+str(peers1_userprofiles))
+
+ users_related = User.objects.filter(userprofile__peers__in=users_peers)
+ logger.info("user_owned_rules_adopt_to_related_user(): => users_related="+str(users_related))
+ user_related1 = None
+ for user2 in users_related:
+ if user2 != user:
+ user_related1=user2
+ break
+
+ logger.info("user_owned_rules_adopt_to_related_user(): => user_related1="+str(user_related1))
+
+ if user_related1!=None:
+ if len(routes_owned)>0:
+ logger.info("user_owned_rules_adopt_to_related_user(): len="+str(len(routes_owned)))
+ for route in routes_owned:
+ logger.info("user_owned_rules_adopt_to_related_user(): owned route="+str(route))
+ route.applier = user_related1
+ logger.info("user_owned_rules_adopt_to_related_user(): reassigning owned route="+str(route)+" by user to be deleted ("+str(user)+") to new owner "+str(user_related1))
+ route.save()
+
+ return (routes_owned, user_related1, users_peers1)
+
diff --git a/flowspec/admin.py b/flowspec/admin.py
index c7cd52c5..81497b99 100644
--- a/flowspec/admin.py
+++ b/flowspec/admin.py
@@ -27,6 +27,14 @@ from django.contrib.auth.admin import UserAdmin
from peers.models import *
from longerusername.forms import UserCreationForm, UserChangeForm
+from django.contrib import messages
+from accounts.models import user_owned_rules_adopt_to_related_user
+
+# TODO: dependency issue: move logging_utils to general package
+import flowspec.logging_utils
+logger = flowspec.logging_utils.logger_init_default(__name__, "flowspec_admin.log", False)
+
+#
class RouteAdmin(admin.ModelAdmin):
form = RouteForm
@@ -83,6 +91,21 @@ class UserProfileAdmin(UserAdmin):
queryset = queryset.update(is_active=True)
activate.short_description = "Activate Selected Users"
+ def delete_model(self, request, client):
+ if False:
+ messages.set_level(request, messages.ERROR)
+ messages.error(request, 'Blocking deletion')
+ else:
+ (adopted_rules, adoting_user, users_peer1) = user_owned_rules_adopt_to_related_user(client) # before actually calling the super.delete_model clean-up owned rules in order to get info about the cleanup which can be used for extra message to the admin UI
+ logger.info("delete_model() => adoting_user="+str(adoting_user))
+ logger.info("delete_model() => adopted_rules="+str(adopted_rules))
+
+ if len(adopted_rules)>0:
+ messages.set_level(request, messages.INFO)
+ messages.error(request, 'additional info: the rules '+str(adopted_rules)+' were re-assigned to remaining user '+str(adoting_user)+' of peer '+str(users_peer1))
+
+ super().delete_model(request, client)
+
def get_userprofile_peers(self, instance):
# instance is User instance
peers = instance.userprofile.peers.all()
diff --git a/flowspec/iprange_match.py b/flowspec/iprange_match.py
index a89e79ad..1d84d9ff 100644
--- a/flowspec/iprange_match.py
+++ b/flowspec/iprange_match.py
@@ -45,8 +45,13 @@ def get_matching_related_peer_for_rule_destination(ivaltrees_per_version, route)
peer_name_tmp = None
peer_name_tmp_not_applier_related = None
try:
- if route.applier!=None:
- route_applier__peers_related = set(route.applier.userprofile.peers.select_related())
+ applier = route.applier
+ except:
+ applier = None
+
+ try:
+ if applier!=None:
+ route_applier__peers_related = set(applier.userprofile.peers.select_related())
else:
route_applier__peers_related = None
diff --git a/flowspec/models.py b/flowspec/models.py
index a81a9655..662436ff 100644
--- a/flowspec/models.py
+++ b/flowspec/models.py
@@ -157,7 +157,8 @@ class ThenAction(models.Model):
class Route(models.Model):
name = models.SlugField(max_length=128, verbose_name=_("Name"))
- applier = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
+ #applier = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
+ applier = models.ForeignKey(User, blank=True, null=True, on_delete=models.DO_NOTHING)
source = models.CharField(max_length=45+4, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Source Address"))
sourceport = models.TextField(blank=True, null=True, verbose_name=_("Source Port"))
destination = models.CharField(max_length=45+4, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Destination Address"))
--
GitLab