From d11832913d1563c62cc285e4ac1ad4d105265963 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 30 Nov 2021 23:41:26 -0500 Subject: [PATCH 1/4] Implement reserved group names in Teamspeak3 service module. Closes #1302 --- .../services/modules/teamspeak3/manager.py | 4 +- .../services/modules/teamspeak3/tests.py | 49 +++++++++++++++++++ docs/features/core/groups.md | 2 +- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/allianceauth/services/modules/teamspeak3/manager.py b/allianceauth/services/modules/teamspeak3/manager.py index d05f910d..9f7eea6c 100755 --- a/allianceauth/services/modules/teamspeak3/manager.py +++ b/allianceauth/services/modules/teamspeak3/manager.py @@ -4,6 +4,7 @@ from django.conf import settings from .util.ts3 import TS3Server, TeamspeakError from .models import TSgroup +from allianceauth.groupmanagement.models import ReservedGroupName logger = logging.getLogger(__name__) @@ -270,7 +271,8 @@ class Teamspeak3Manager: addgroups.append(ts_groups[ts_group_key]) for user_ts_group_key in user_ts_groups: if user_ts_groups[user_ts_group_key] not in ts_groups.values(): - remgroups.append(user_ts_groups[user_ts_group_key]) + if not ReservedGroupName.objects.filter(name=user_ts_group_key).exists(): + remgroups.append(user_ts_groups[user_ts_group_key]) for g in addgroups: logger.info(f"Adding Teamspeak user {userid} into group {g}") diff --git a/allianceauth/services/modules/teamspeak3/tests.py b/allianceauth/services/modules/teamspeak3/tests.py index 1cb923ed..4abc68e4 100644 --- a/allianceauth/services/modules/teamspeak3/tests.py +++ b/allianceauth/services/modules/teamspeak3/tests.py @@ -15,6 +15,7 @@ from .signals import m2m_changed_authts_group, post_save_authts, post_delete_aut from .manager import Teamspeak3Manager from .util.ts3 import TeamspeakError from allianceauth.authentication.models import State +from allianceauth.groupmanagement.models import ReservedGroupName MODULE_PATH = 'allianceauth.services.modules.teamspeak3' DEFAULT_AUTH_GROUP = 'Member' @@ -316,6 +317,9 @@ class Teamspeak3SignalsTestCase(TestCase): class Teamspeak3ManagerTestCase(TestCase): + def setUp(self): + self.reserved = ReservedGroupName.objects.create(name='reserved', reason='tests', created_by='Bob, praise be!') + @staticmethod def my_side_effect(*args, **kwargs): raise TeamspeakError(1) @@ -339,3 +343,48 @@ class Teamspeak3ManagerTestCase(TestCase): # perform test manager.add_user(user, "Dummy User") + + @mock.patch.object(Teamspeak3Manager, '_get_userid') + @mock.patch.object(Teamspeak3Manager, '_user_group_list') + @mock.patch.object(Teamspeak3Manager, '_add_user_to_group') + @mock.patch.object(Teamspeak3Manager, '_remove_user_from_group') + @mock.patch.object(Teamspeak3Manager, 'server') + def test_update_groups_add(self, server, remove, add, groups, userid): + """Add to one group""" + userid.return_value = 1 + groups.return_value = {'test': 1} + + Teamspeak3Manager().update_groups(1, {'test': 1, 'dummy': 2}) + self.assertEqual(add.call_count, 1) + self.assertEqual(remove.call_count, 0) + self.assertEqual(add.call_args[0][1], 2) + + @mock.patch.object(Teamspeak3Manager, '_get_userid') + @mock.patch.object(Teamspeak3Manager, '_user_group_list') + @mock.patch.object(Teamspeak3Manager, '_add_user_to_group') + @mock.patch.object(Teamspeak3Manager, '_remove_user_from_group') + @mock.patch.object(Teamspeak3Manager, 'server') + def test_update_groups_remove(self, server, remove, add, groups, userid): + """Remove from one group""" + userid.return_value = 1 + groups.return_value = {'test': 1, 'dummy': 2} + + Teamspeak3Manager().update_groups(1, {'test': 1}) + self.assertEqual(add.call_count, 0) + self.assertEqual(remove.call_count, 1) + self.assertEqual(remove.call_args[0][1], 2) + + @mock.patch.object(Teamspeak3Manager, '_get_userid') + @mock.patch.object(Teamspeak3Manager, '_user_group_list') + @mock.patch.object(Teamspeak3Manager, '_add_user_to_group') + @mock.patch.object(Teamspeak3Manager, '_remove_user_from_group') + @mock.patch.object(Teamspeak3Manager, 'server') + def test_update_groups_remove_reserved(self, server, remove, add, groups, userid): + """Remove from one group, but do not touch reserved group""" + userid.return_value = 1 + groups.return_value = {'test': 1, 'dummy': 2, self.reserved.name: 3} + + Teamspeak3Manager().update_groups(1, {'test': 1}) + self.assertEqual(add.call_count, 0) + self.assertEqual(remove.call_count, 1) + self.assertEqual(remove.call_args[0][1], 2) diff --git a/docs/features/core/groups.md b/docs/features/core/groups.md index 25fd16c2..8530a62a 100644 --- a/docs/features/core/groups.md +++ b/docs/features/core/groups.md @@ -48,7 +48,7 @@ When using Alliance Auth to manage external services like Discord, Auth will aut ```eval_rst .. note:: - While this feature can help to avoid naming conflicts with groups on external services, the respective service component in Alliance Auth also needs to be build in such a way that it knows how to prevent these conflicts. Currently only the Discord service has this ability. + While this feature can help to avoid naming conflicts with groups on external services, the respective service component in Alliance Auth also needs to be build in such a way that it knows how to prevent these conflicts. Currently only the Discord and Teamspeak3 services have this ability. ``` ## Managing groups From 72740b9e4da7b736a2bd64e33864c90f3627110a Mon Sep 17 00:00:00 2001 From: Adarnof Date: Wed, 8 Dec 2021 23:41:10 -0500 Subject: [PATCH 2/4] Prevent assignment of reserved groups to AuthTSgroup mappings. Implemented in TS group updates to prevent their creation / delete once reserved, and the admin site for when a reserved group name is created but before the TS group sync occurs. --- .../services/modules/teamspeak3/admin.py | 15 +++- .../services/modules/teamspeak3/manager.py | 35 +++----- .../services/modules/teamspeak3/tests.py | 90 ++++++++++++++++--- 3 files changed, 105 insertions(+), 35 deletions(-) diff --git a/allianceauth/services/modules/teamspeak3/admin.py b/allianceauth/services/modules/teamspeak3/admin.py index a8b614d8..dbba1cb1 100644 --- a/allianceauth/services/modules/teamspeak3/admin.py +++ b/allianceauth/services/modules/teamspeak3/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin - -from .models import AuthTS, Teamspeak3User, StateGroup +from django.contrib.auth.models import Group +from .models import AuthTS, Teamspeak3User, StateGroup, TSgroup from ...admin import ServicesUserAdmin +from allianceauth.groupmanagement.models import ReservedGroupName @admin.register(Teamspeak3User) @@ -25,6 +26,16 @@ class AuthTSgroupAdmin(admin.ModelAdmin): fields = ('auth_group', 'ts_group') filter_horizontal = ('ts_group',) + def formfield_for_foreignkey(self, db_field, request, **kwargs): + if db_field.name == 'auth_group': + kwargs['queryset'] = Group.objects.exclude(name__in=ReservedGroupName.objects.values_list('name', flat=True)) + return super().formfield_for_foreignkey(db_field, request, **kwargs) + + def formfield_for_manytomany(self, db_field, request, **kwargs): + if db_field.name == 'ts_group': + kwargs['queryset'] = TSgroup.objects.exclude(ts_group_name__in=ReservedGroupName.objects.values_list('name', flat=True)) + return super().formfield_for_manytomany(db_field, request, **kwargs) + def _ts_group(self, obj): return [x for x in obj.ts_group.all().order_by('ts_group_id')] diff --git a/allianceauth/services/modules/teamspeak3/manager.py b/allianceauth/services/modules/teamspeak3/manager.py index 9f7eea6c..9cad7cae 100755 --- a/allianceauth/services/modules/teamspeak3/manager.py +++ b/allianceauth/services/modules/teamspeak3/manager.py @@ -157,32 +157,25 @@ class Teamspeak3Manager: logger.info(f"Removed user id {uid} from group id {groupid} on TS3 server.") def _sync_ts_group_db(self): - logger.debug("_sync_ts_group_db function called.") try: remote_groups = self._group_list() - local_groups = TSgroup.objects.all() - logger.debug("Comparing remote groups to TSgroup objects: %s" % local_groups) - for key in remote_groups: - logger.debug(f"Typecasting remote_group value at position {key} to int: {remote_groups[key]}") - remote_groups[key] = int(remote_groups[key]) + managed_groups = {g:remote_groups[g] for g in remote_groups if g in set(remote_groups.keys()) - set(ReservedGroupName.objects.values_list('name', flat=True))} + remove = TSgroup.objects.exclude(ts_group_id__in=managed_groups.values()) + + if remove: + logger.debug(f"Deleting {remove.count()} TSgroup models: not found on server, or reserved name.") + remove.delete() + + add = {g:managed_groups[g] for g in managed_groups if managed_groups[g] in set(managed_groups.values()) - set(TSgroup.objects.values_list("ts_group_id", flat=True))} + if add: + logger.debug(f"Adding {len(add)} new TSgroup models.") + models = [TSgroup(ts_group_name=name, ts_group_id=add[name]) for name in add] + TSgroup.objects.bulk_create(models) - for group in local_groups: - logger.debug("Checking local group %s" % group) - if group.ts_group_id not in remote_groups.values(): - logger.debug( - f"Local group id {group.ts_group_id} not found on server. Deleting model {group}") - TSgroup.objects.filter(ts_group_id=group.ts_group_id).delete() - for key in remote_groups: - g = TSgroup(ts_group_id=remote_groups[key], ts_group_name=key) - q = TSgroup.objects.filter(ts_group_id=g.ts_group_id) - if not q: - logger.debug("Local group does not exist for TS group {}. Creating TSgroup model {}".format( - remote_groups[key], g)) - g.save() except TeamspeakError as e: - logger.error("Error occured while syncing TS group db: %s" % str(e)) + logger.error(f"Error occurred while syncing TS group db: {str(e)}") except: - logger.exception("An unhandled exception has occured while syncing TS groups.") + logger.exception("An unhandled exception has occurred while syncing TS groups.") def add_user(self, user, fmt_name): username_clean = self.__santatize_username(fmt_name[:30]) diff --git a/allianceauth/services/modules/teamspeak3/tests.py b/allianceauth/services/modules/teamspeak3/tests.py index 4abc68e4..bfed1906 100644 --- a/allianceauth/services/modules/teamspeak3/tests.py +++ b/allianceauth/services/modules/teamspeak3/tests.py @@ -5,16 +5,17 @@ from django import urls from django.contrib.auth.models import User, Group, Permission from django.core.exceptions import ObjectDoesNotExist from django.db.models import signals +from django.contrib.admin import AdminSite from allianceauth.tests.auth_utils import AuthUtils from .auth_hooks import Teamspeak3Service from .models import Teamspeak3User, AuthTS, TSgroup, StateGroup from .tasks import Teamspeak3Tasks from .signals import m2m_changed_authts_group, post_save_authts, post_delete_authts +from .admin import AuthTSgroupAdmin from .manager import Teamspeak3Manager from .util.ts3 import TeamspeakError -from allianceauth.authentication.models import State from allianceauth.groupmanagement.models import ReservedGroupName MODULE_PATH = 'allianceauth.services.modules.teamspeak3' @@ -316,9 +317,9 @@ class Teamspeak3SignalsTestCase(TestCase): class Teamspeak3ManagerTestCase(TestCase): - - def setUp(self): - self.reserved = ReservedGroupName.objects.create(name='reserved', reason='tests', created_by='Bob, praise be!') + @classmethod + def setUpTestData(cls): + cls.reserved = ReservedGroupName.objects.create(name='reserved', reason='tests', created_by='Bob, praise be!') @staticmethod def my_side_effect(*args, **kwargs): @@ -338,8 +339,8 @@ class Teamspeak3ManagerTestCase(TestCase): manager._server = server # create test data - user = User.objects.create_user("dummy") - user.profile.state = State.objects.filter(name="Member").first() + user = AuthUtils.create_user("dummy") + AuthUtils.assign_state(user, AuthUtils.get_member_state()) # perform test manager.add_user(user, "Dummy User") @@ -348,8 +349,7 @@ class Teamspeak3ManagerTestCase(TestCase): @mock.patch.object(Teamspeak3Manager, '_user_group_list') @mock.patch.object(Teamspeak3Manager, '_add_user_to_group') @mock.patch.object(Teamspeak3Manager, '_remove_user_from_group') - @mock.patch.object(Teamspeak3Manager, 'server') - def test_update_groups_add(self, server, remove, add, groups, userid): + def test_update_groups_add(self, remove, add, groups, userid): """Add to one group""" userid.return_value = 1 groups.return_value = {'test': 1} @@ -363,8 +363,7 @@ class Teamspeak3ManagerTestCase(TestCase): @mock.patch.object(Teamspeak3Manager, '_user_group_list') @mock.patch.object(Teamspeak3Manager, '_add_user_to_group') @mock.patch.object(Teamspeak3Manager, '_remove_user_from_group') - @mock.patch.object(Teamspeak3Manager, 'server') - def test_update_groups_remove(self, server, remove, add, groups, userid): + def test_update_groups_remove(self, remove, add, groups, userid): """Remove from one group""" userid.return_value = 1 groups.return_value = {'test': 1, 'dummy': 2} @@ -378,8 +377,7 @@ class Teamspeak3ManagerTestCase(TestCase): @mock.patch.object(Teamspeak3Manager, '_user_group_list') @mock.patch.object(Teamspeak3Manager, '_add_user_to_group') @mock.patch.object(Teamspeak3Manager, '_remove_user_from_group') - @mock.patch.object(Teamspeak3Manager, 'server') - def test_update_groups_remove_reserved(self, server, remove, add, groups, userid): + def test_update_groups_remove_reserved(self, remove, add, groups, userid): """Remove from one group, but do not touch reserved group""" userid.return_value = 1 groups.return_value = {'test': 1, 'dummy': 2, self.reserved.name: 3} @@ -388,3 +386,71 @@ class Teamspeak3ManagerTestCase(TestCase): self.assertEqual(add.call_count, 0) self.assertEqual(remove.call_count, 1) self.assertEqual(remove.call_args[0][1], 2) + + @mock.patch.object(Teamspeak3Manager, '_group_list') + def test_sync_group_db_create(self, group_list): + """Populate the list of all TSgroups""" + group_list.return_value = {'allowed':1, 'also allowed': 2} + Teamspeak3Manager()._sync_ts_group_db() + self.assertEqual(TSgroup.objects.all().count(), 2) + + @mock.patch.object(Teamspeak3Manager, '_group_list') + def test_sync_group_db_delete(self, group_list): + """Populate the list of all TSgroups, and delete one which no longer exists""" + TSgroup.objects.create(ts_group_name='deleted', ts_group_id=3) + group_list.return_value = {'allowed': 1, 'also allowed': 2} + Teamspeak3Manager()._sync_ts_group_db() + self.assertEqual(TSgroup.objects.all().count(), 2) + self.assertFalse(TSgroup.objects.filter(ts_group_name='deleted').exists()) + + @mock.patch.object(Teamspeak3Manager, '_group_list') + def test_sync_group_db_dont_create_reserved(self, group_list): + """Populate the list of all TSgroups, ignoring a reserved group name""" + group_list.return_value = {'allowed': 1, 'reserved': 4} + Teamspeak3Manager()._sync_ts_group_db() + self.assertEqual(TSgroup.objects.all().count(), 1) + self.assertFalse(TSgroup.objects.filter(ts_group_name='reserved').exists()) + + @mock.patch.object(Teamspeak3Manager, '_group_list') + def test_sync_group_db_delete_reserved(self, group_list): + """Populate the list of all TSgroups, deleting the TSgroup model for one which has become reserved""" + TSgroup.objects.create(ts_group_name='reserved', ts_group_id=4) + group_list.return_value = {'allowed': 1, 'reserved': 4} + Teamspeak3Manager()._sync_ts_group_db() + self.assertEqual(TSgroup.objects.all().count(), 1) + self.assertFalse(TSgroup.objects.filter(ts_group_name='reserved').exists()) + + +class MockRequest: + pass + + +class MockSuperUser: + def has_perm(self, perm, obj=None): + return True + + +request = MockRequest() +request.user = MockSuperUser() + + +class Teamspeak3AdminTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.site = AdminSite() + cls.admin = AuthTSgroupAdmin(AuthTS, cls.site) + cls.group = Group.objects.create(name='test') + cls.ts_group = TSgroup.objects.create(ts_group_name='test') + + def test_field_queryset_no_reserved_names(self): + """Ensure all groups are listed when no reserved names""" + form = self.admin.get_form(request) + self.assertEqual(form.base_fields['auth_group']._get_queryset().count(), 1) + self.assertEqual(form.base_fields['ts_group']._get_queryset().count(), 1) + + def test_field_queryset_reserved_names(self): + """Ensure reserved group names are filtered out""" + ReservedGroupName.objects.bulk_create([ReservedGroupName(name='test', reason='tests', created_by='Bob')]) + form = self.admin.get_form(request) + self.assertEqual(form.base_fields['auth_group']._get_queryset().count(), 0) + self.assertEqual(form.base_fields['ts_group']._get_queryset().count(), 0) From 6688f735653763bd0bfa4fd539502091c76c6a4b Mon Sep 17 00:00:00 2001 From: Adarnof Date: Wed, 15 Dec 2021 23:54:53 -0500 Subject: [PATCH 3/4] Use integer teamspeak group IDs when filtering. --- .../services/modules/teamspeak3/manager.py | 8 ++-- .../services/modules/teamspeak3/tests.py | 37 ++++++++++++++----- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/allianceauth/services/modules/teamspeak3/manager.py b/allianceauth/services/modules/teamspeak3/manager.py index 9cad7cae..519a8a47 100755 --- a/allianceauth/services/modules/teamspeak3/manager.py +++ b/allianceauth/services/modules/teamspeak3/manager.py @@ -159,7 +159,7 @@ class Teamspeak3Manager: def _sync_ts_group_db(self): try: remote_groups = self._group_list() - managed_groups = {g:remote_groups[g] for g in remote_groups if g in set(remote_groups.keys()) - set(ReservedGroupName.objects.values_list('name', flat=True))} + managed_groups = {g:int(remote_groups[g]) for g in remote_groups if g in set(remote_groups.keys()) - set(ReservedGroupName.objects.values_list('name', flat=True))} remove = TSgroup.objects.exclude(ts_group_id__in=managed_groups.values()) if remove: @@ -174,8 +174,8 @@ class Teamspeak3Manager: except TeamspeakError as e: logger.error(f"Error occurred while syncing TS group db: {str(e)}") - except: - logger.exception("An unhandled exception has occurred while syncing TS groups.") + except Exception: + logger.exception(f"An unhandled exception has occurred while syncing TS groups.") def add_user(self, user, fmt_name): username_clean = self.__santatize_username(fmt_name[:30]) @@ -234,7 +234,7 @@ class Teamspeak3Manager: logger.exception(f"Failed to delete user id {uid} from TS3 - received response {ret}") return False else: - logger.warn("User with id %s not found on TS3 server. Assuming succesful deletion." % uid) + logger.warning("User with id %s not found on TS3 server. Assuming succesful deletion." % uid) return True def check_user_exists(self, uid): diff --git a/allianceauth/services/modules/teamspeak3/tests.py b/allianceauth/services/modules/teamspeak3/tests.py index bfed1906..2354bf98 100644 --- a/allianceauth/services/modules/teamspeak3/tests.py +++ b/allianceauth/services/modules/teamspeak3/tests.py @@ -366,7 +366,7 @@ class Teamspeak3ManagerTestCase(TestCase): def test_update_groups_remove(self, remove, add, groups, userid): """Remove from one group""" userid.return_value = 1 - groups.return_value = {'test': 1, 'dummy': 2} + groups.return_value = {'test': '1', 'dummy': '2'} Teamspeak3Manager().update_groups(1, {'test': 1}) self.assertEqual(add.call_count, 0) @@ -390,7 +390,7 @@ class Teamspeak3ManagerTestCase(TestCase): @mock.patch.object(Teamspeak3Manager, '_group_list') def test_sync_group_db_create(self, group_list): """Populate the list of all TSgroups""" - group_list.return_value = {'allowed':1, 'also allowed': 2} + group_list.return_value = {'allowed':'1', 'also allowed':'2'} Teamspeak3Manager()._sync_ts_group_db() self.assertEqual(TSgroup.objects.all().count(), 2) @@ -398,15 +398,15 @@ class Teamspeak3ManagerTestCase(TestCase): def test_sync_group_db_delete(self, group_list): """Populate the list of all TSgroups, and delete one which no longer exists""" TSgroup.objects.create(ts_group_name='deleted', ts_group_id=3) - group_list.return_value = {'allowed': 1, 'also allowed': 2} + group_list.return_value = {'allowed': '1'} Teamspeak3Manager()._sync_ts_group_db() - self.assertEqual(TSgroup.objects.all().count(), 2) + self.assertEqual(TSgroup.objects.all().count(), 1) self.assertFalse(TSgroup.objects.filter(ts_group_name='deleted').exists()) @mock.patch.object(Teamspeak3Manager, '_group_list') def test_sync_group_db_dont_create_reserved(self, group_list): """Populate the list of all TSgroups, ignoring a reserved group name""" - group_list.return_value = {'allowed': 1, 'reserved': 4} + group_list.return_value = {'allowed': '1', 'reserved': '4'} Teamspeak3Manager()._sync_ts_group_db() self.assertEqual(TSgroup.objects.all().count(), 1) self.assertFalse(TSgroup.objects.filter(ts_group_name='reserved').exists()) @@ -415,11 +415,28 @@ class Teamspeak3ManagerTestCase(TestCase): def test_sync_group_db_delete_reserved(self, group_list): """Populate the list of all TSgroups, deleting the TSgroup model for one which has become reserved""" TSgroup.objects.create(ts_group_name='reserved', ts_group_id=4) - group_list.return_value = {'allowed': 1, 'reserved': 4} + group_list.return_value = {'allowed': '1', 'reserved': '4'} Teamspeak3Manager()._sync_ts_group_db() self.assertEqual(TSgroup.objects.all().count(), 1) self.assertFalse(TSgroup.objects.filter(ts_group_name='reserved').exists()) + @mock.patch.object(Teamspeak3Manager, '_group_list') + def test_sync_group_db_partial_addition(self, group_list): + """Some TSgroups already exist in database, add new ones""" + TSgroup.objects.create(ts_group_name='allowed', ts_group_id=1) + group_list.return_value = {'allowed': '1', 'also allowed': '2'} + Teamspeak3Manager()._sync_ts_group_db() + self.assertEqual(TSgroup.objects.all().count(), 2) + + @mock.patch.object(Teamspeak3Manager, '_group_list') + def test_sync_group_db_partial_removal(self, group_list): + """One TSgroup has been deleted on server, so remove its model""" + TSgroup.objects.create(ts_group_name='allowed', ts_group_id=1) + TSgroup.objects.create(ts_group_name='also allowed', ts_group_id=2) + group_list.return_value = {'allowed': '1'} + Teamspeak3Manager()._sync_ts_group_db() + self.assertEqual(TSgroup.objects.all().count(), 1) + class MockRequest: pass @@ -445,12 +462,12 @@ class Teamspeak3AdminTestCase(TestCase): def test_field_queryset_no_reserved_names(self): """Ensure all groups are listed when no reserved names""" form = self.admin.get_form(request) - self.assertEqual(form.base_fields['auth_group']._get_queryset().count(), 1) - self.assertEqual(form.base_fields['ts_group']._get_queryset().count(), 1) + self.assertQuerysetEqual(form.base_fields['auth_group']._get_queryset(), Group.objects.all()) + self.assertQuerysetEqual(form.base_fields['ts_group']._get_queryset(), TSgroup.objects.all()) def test_field_queryset_reserved_names(self): """Ensure reserved group names are filtered out""" ReservedGroupName.objects.bulk_create([ReservedGroupName(name='test', reason='tests', created_by='Bob')]) form = self.admin.get_form(request) - self.assertEqual(form.base_fields['auth_group']._get_queryset().count(), 0) - self.assertEqual(form.base_fields['ts_group']._get_queryset().count(), 0) + self.assertQuerysetEqual(form.base_fields['auth_group']._get_queryset(), Group.objects.none()) + self.assertQuerysetEqual(form.base_fields['ts_group']._get_queryset(), TSgroup.objects.none()) From 8de2c3bfcb0c6240f3c19f6b43f0b86c27b6e70e Mon Sep 17 00:00:00 2001 From: Adarnof Date: Thu, 16 Dec 2021 22:23:15 -0500 Subject: [PATCH 4/4] Update name of serverquery IP file changed in TS3 v3.13.0 Changelog indicates old filenames are still accepted, but newly installed servers come with the new file names. Closes #1298 --- docs/features/services/teamspeak3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/services/teamspeak3.md b/docs/features/services/teamspeak3.md index 80c7dad3..507ab1bc 100644 --- a/docs/features/services/teamspeak3.md +++ b/docs/features/services/teamspeak3.md @@ -160,7 +160,7 @@ This error generally means teamspeak returned an error message that went unhandl This most commonly happens when your teamspeak server is externally hosted. You need to add the auth server IP to the teamspeak serverquery whitelist. This varies by provider. -If you have SSH access to the server hosting it, you need to locate the teamspeak server folder and add the auth server IP on a new line in `server_query_whitelist.txt` +If you have SSH access to the server hosting it, you need to locate the teamspeak server folder and add the auth server IP on a new line in `query_ip_allowlist.txt` (named `query_ip_whitelist.txt` on older teamspeak versions). ### `520 invalid loginname or password`