Add blacklist for groups and ignore blacklisted roles in Discord service

This commit is contained in:
Erik Kalkoken
2021-11-28 14:48:49 +00:00
committed by Ariel Rin
parent 025c824fbb
commit 4c0683c484
19 changed files with 483 additions and 140 deletions

View File

@@ -1,14 +1,18 @@
from django import forms
from django.apps import apps
from django.contrib.auth.models import Permission
from django.contrib import admin
from django.contrib.auth.models import Group as BaseGroup, User
from django.core.exceptions import ValidationError
from django.db.models import Count
from django.db.models.functions import Lower
from django.db.models.signals import pre_save, post_save, pre_delete, \
post_delete, m2m_changed
from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from .models import AuthGroup
from .models import AuthGroup, ReservedGroupName
from .models import GroupRequest
if 'eve_autogroups' in apps.app_configs:
@@ -70,8 +74,7 @@ if _has_auto_groups:
managedalliancegroup__isnull=True,
managedcorpgroup__isnull=True
)
else:
return queryset
return queryset
class HasLeaderFilter(admin.SimpleListFilter):
@@ -90,11 +93,22 @@ class HasLeaderFilter(admin.SimpleListFilter):
return queryset.filter(authgroup__group_leaders__isnull=False)
elif value == 'no':
return queryset.filter(authgroup__group_leaders__isnull=True)
else:
return queryset
return queryset
class GroupAdminForm(forms.ModelForm):
def clean_name(self):
my_name = self.cleaned_data['name']
if ReservedGroupName.objects.filter(name__iexact=my_name).exists():
raise ValidationError(
_("This name has been reserved and can not be used for groups."),
code='reserved_name'
)
return my_name
class GroupAdmin(admin.ModelAdmin):
form = GroupAdminForm
list_select_related = ('authgroup',)
ordering = ('name',)
list_display = (
@@ -209,6 +223,41 @@ class GroupRequestAdmin(admin.ModelAdmin):
_leave_request.boolean = True
class ReservedGroupNameAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['created_by'].initial = self.current_user.username
self.fields['created_at'].initial = _("(auto)")
created_by = forms.CharField(disabled=True)
created_at = forms.CharField(disabled=True)
def clean_name(self):
my_name = self.cleaned_data['name'].lower()
if Group.objects.filter(name__iexact=my_name).exists():
raise ValidationError(
_("There already exists a group with that name."), code='already_exists'
)
return my_name
def clean_created_at(self):
return now()
@admin.register(ReservedGroupName)
class ReservedGroupNameAdmin(admin.ModelAdmin):
form = ReservedGroupNameAdminForm
list_display = ("name", "created_by", "created_at")
def get_form(self, request, *args, **kwargs):
form = super().get_form(request, *args, **kwargs)
form.current_user = request.user
return form
def has_change_permission(self, *args, **kwargs) -> bool:
return False
@receiver(pre_save, sender=Group)
def redirect_pre_save(sender, signal=None, *args, **kwargs):
pre_save.send(BaseGroup, *args, **kwargs)

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.2.9 on 2021-11-25 18:38
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('groupmanagement', '0017_improve_groups_documentation'),
]
operations = [
migrations.CreateModel(
name='ReservedGroupName',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Name that can not be used for groups.', max_length=150, unique=True, verbose_name='name')),
('reason', models.TextField(help_text='Reason why this name is reserved.', verbose_name='reason')),
('created_by', models.CharField(help_text='Name of the user who created this entry.', max_length=255, verbose_name='created by')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Date when this entry was created', verbose_name='created at')),
],
),
]

View File

@@ -4,8 +4,7 @@ from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from allianceauth.authentication.models import State
@@ -181,18 +180,35 @@ class AuthGroup(models.Model):
)
@receiver(post_save, sender=Group)
def create_auth_group(sender, instance, created, **kwargs):
"""
Creates the AuthGroup model when a group is created
"""
if created:
AuthGroup.objects.create(group=instance)
class ReservedGroupName(models.Model):
"""Name that can not be used for groups.
This enables AA to ignore groups on other services (e.g. Discord) with that name.
"""
name = models.CharField(
_('name'),
max_length=150,
unique=True,
help_text=_("Name that can not be used for groups.")
)
reason = models.TextField(
_('reason'), help_text=_("Reason why this name is reserved.")
)
created_by = models.CharField(
_('created by'),
max_length=255,
help_text="Name of the user who created this entry."
)
created_at = models.DateTimeField(
_('created at'), default=now, help_text=_("Date when this entry was created")
)
@receiver(post_save, sender=Group)
def save_auth_group(sender, instance, **kwargs):
"""
Ensures AuthGroup model is saved automatically
"""
instance.authgroup.save()
def __str__(self) -> str:
return self.name
def save(self, *args, **kwargs) -> None:
if Group.objects.filter(name__iexact=self.name).exists():
raise RuntimeError(
f"Save failed. There already exists a group with the name: {self.name}."
)
super().save(*args, **kwargs)

View File

@@ -1,11 +1,33 @@
import logging
from django.contrib.auth.models import Group
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from allianceauth.authentication.signals import state_changed
from .models import AuthGroup, ReservedGroupName
logger = logging.getLogger(__name__)
@receiver(pre_save, sender=Group)
def find_new_name_for_conflicting_groups(sender, instance, **kwargs):
"""Find new name for a group which name is already reserved."""
new_name = instance.name
num = 0
while ReservedGroupName.objects.filter(name__iexact=new_name).exists():
num += 1
new_name = f"{instance.name}_{num}"
instance.name = new_name
@receiver(post_save, sender=Group)
def create_auth_group(sender, instance, created, **kwargs):
"""Create the AuthGroup model when a group is created."""
if created:
AuthGroup.objects.create(group=instance)
@receiver(state_changed)
def check_groups_on_state_change(sender, user, state, **kwargs):
logger.debug(

View File

@@ -10,9 +10,10 @@ from allianceauth.authentication.models import CharacterOwnership, State
from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo
)
from ..admin import HasLeaderFilter, GroupAdmin, Group
from . import get_admin_change_view_url
from ..models import ReservedGroupName
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
@@ -396,3 +397,108 @@ class TestGroupAdmin(TestCase):
c.login(username='superuser', password='secret')
response = c.get(get_admin_change_view_url(self.group_1))
self.assertEqual(response.status_code, 200)
def test_should_create_new_group(self):
# given
user = User.objects.create_superuser("bruce")
self.client.force_login(user)
# when
response = self.client.post(
"/admin/groupmanagement/group/add/",
data={
"name": "new group",
"authgroup-TOTAL_FORMS": 1,
"authgroup-INITIAL_FORMS": 0,
"authgroup-MIN_NUM_FORMS": 0,
"authgroup-MAX_NUM_FORMS": 1,
}
)
# then
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/groupmanagement/group/")
self.assertTrue(Group.objects.filter(name="new group").exists())
def test_should_not_allow_creating_new_group_with_reserved_name(self):
# given
ReservedGroupName.objects.create(
name="new group", reason="dummy", created_by="bruce"
)
user = User.objects.create_superuser("bruce")
self.client.force_login(user)
# when
response = self.client.post(
"/admin/groupmanagement/group/add/",
data={
"name": "New group",
"authgroup-TOTAL_FORMS": 1,
"authgroup-INITIAL_FORMS": 0,
"authgroup-MIN_NUM_FORMS": 0,
"authgroup-MAX_NUM_FORMS": 1,
}
)
# then
self.assertContains(
response, "This name has been reserved and can not be used for groups"
)
self.assertFalse(Group.objects.filter(name="new group").exists())
def test_should_not_allow_changing_name_of_existing_group_to_reserved_name(self):
# given
ReservedGroupName.objects.create(
name="new group", reason="dummy", created_by="bruce"
)
group = Group.objects.create(name="dummy")
user = User.objects.create_superuser("bruce")
self.client.force_login(user)
# when
response = self.client.post(
f"/admin/groupmanagement/group/{group.pk}/change/",
data={
"name": "new group",
"authgroup-TOTAL_FORMS": 1,
"authgroup-INITIAL_FORMS": 0,
"authgroup-MIN_NUM_FORMS": 0,
"authgroup-MAX_NUM_FORMS": 1,
}
)
# then
self.assertContains(
response, "This name has been reserved and can not be used for groups"
)
self.assertFalse(Group.objects.filter(name="new group").exists())
class TestReservedGroupNameAdmin(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user = User.objects.create_superuser("bruce")
def test_should_create_new_entry(self):
# given
self.client.force_login(self.user)
# when
response = self.client.post(
"/admin/groupmanagement/reservedgroupname/add/",
data={"name": "Test", "reason": "dummy"}
)
# then
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/groupmanagement/reservedgroupname/")
obj = ReservedGroupName.objects.get(name="test")
self.assertEqual(obj.name, "test")
self.assertEqual(obj.created_by, self.user.username)
self.assertTrue(obj.created_at)
def test_should_not_allow_names_of_existing_groups(self):
# given
Group.objects.create(name="Already taken")
self.client.force_login(self.user)
# when
response = self.client.post(
"/admin/groupmanagement/reservedgroupname/add/",
data={"name": "already taken", "reason": "dummy"}
)
# then
self.assertContains(response, "There already exists a group with that name")
self.assertFalse(ReservedGroupName.objects.filter(name="already taken").exists())

View File

@@ -5,7 +5,7 @@ from django.test import TestCase, override_settings
from allianceauth.tests.auth_utils import AuthUtils
from ..models import GroupRequest, RequestLog
from ..models import GroupRequest, RequestLog, ReservedGroupName
MODULE_PATH = "allianceauth.groupmanagement.models"
@@ -284,3 +284,22 @@ class TestAuthGroupRequestApprovers(TestCase):
leaders = child_group.authgroup.group_request_approvers()
# then
self.assertSetEqual(leaders, set())
class TestReservedGroupName(TestCase):
def test_should_return_name(self):
# given
obj = ReservedGroupName(name="test", reason="abc", created_by="xxx")
# when
result = str(obj)
# then
self.assertEqual(result, "test")
def test_should_not_allow_creating_reserved_name_for_existing_group(self):
# given
Group.objects.create(name="Dummy")
# when
with self.assertRaises(RuntimeError):
ReservedGroupName.objects.create(
name="dummy", reason="abc", created_by="xxx"
)

View File

@@ -6,6 +6,27 @@ from allianceauth.eveonline.autogroups.models import AutogroupsConfig
from allianceauth.tests.auth_utils import AuthUtils
from ..models import ReservedGroupName
class TestGroupSignals(TestCase):
def test_should_create_authgroup_when_group_is_created(self):
# when
group = Group.objects.create(name="test")
# then
self.assertEqual(group.authgroup.group, group)
def test_should_rename_group_that_conflicts_with_reserved_name(self):
# given
ReservedGroupName.objects.create(name="test", reason="dummy", created_by="xyz")
ReservedGroupName.objects.create(name="test_1", reason="dummy", created_by="xyz")
# when
group = Group.objects.create(name="Test")
# then
self.assertNotEqual(group.name, "test")
self.assertNotEqual(group.name, "test_1")
class TestCheckGroupsOnStateChange(TestCase):
@classmethod

View File

@@ -1,10 +1,7 @@
from unittest.mock import Mock, patch
from django.test import RequestFactory, TestCase
from django.urls import reverse
from allianceauth.tests.auth_utils import AuthUtils
from esi.models import Token
from .. import views