Compare commits

...

4 Commits

Author SHA1 Message Date
Joel Falknau
a5f2e29a46
fixes 2024-12-30 22:26:35 +10:00
Joel Falknau
8f33c649b3
cleanup 2024-12-30 20:28:54 +10:00
Joel Falknau
f6cb28d64b
clarify templinks page 2024-12-30 20:28:45 +10:00
Joel Falknau
96b54d15ac
shift tests 2024-12-30 20:27:47 +10:00
12 changed files with 279 additions and 152 deletions

View File

@ -37,19 +37,10 @@ class MumbleService(ServicesHook):
except MumbleUser.DoesNotExist:
logging.debug("User does not have a mumble account")
def update_groups(self, user):
logger.debug(f"Updating {self.name} groups for {user}")
if MumbleTasks.has_account(user):
MumbleTasks.update_groups.delay(user.pk)
def validate_user(self, user):
if MumbleTasks.has_account(user) and not self.service_active_for_user(user):
self.delete_user(user, notify_user=True)
def update_all_groups(self):
logger.debug("Updating all %s groups" % self.name)
MumbleTasks.update_all_groups.delay()
def service_active_for_user(self, user):
return user.has_perm(self.access_perm)
@ -80,8 +71,8 @@ class MumbleMenuItem(MenuItemHook):
self=self,
text=_("Mumble Temp Links"),
classes="fa-solid fa-microphone",
url_name="mumble:index",
navactive=["mumble:index"],
url_name="mumble:templinks",
navactive=["mumble:templinks"],
)
def render(self, request) -> str:

View File

@ -0,0 +1,48 @@
import random
import string
from allianceauth.eveonline.models import EveCharacter
from allianceauth.services.hooks import NameFormatter
from passlib.hash import bcrypt_sha256
from django.db import models
from allianceauth.services.abstract import AbstractServiceModel
import logging
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
class MumbleManager(models.Manager):
@staticmethod
def get_username(user) -> str:
return user.profile.main_character.character_name # main character as the user.username may be incorrect
@staticmethod
def sanitise_username(username) -> str:
return username.replace(" ", "_")
@staticmethod
def generate_random_pass() -> str:
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
@staticmethod
def gen_pwhash(password) -> str:
return bcrypt_sha256.encrypt(password.encode('utf-8'))
def create(self, user):
try:
username = self.get_username(user)
logger.debug(f"Creating mumble user with username {username}")
username_clean = self.sanitise_username(username)
password = self.generate_random_pass()
pwhash = self.gen_pwhash(password)
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format(
username_clean, pwhash[0:5]))
logger.info(f"Creating mumble user {username_clean}")
result = super().create(user=user, username=username_clean, pwhash=pwhash)
result.credentials.update({'username': result.username, 'password': password})
return result
except AttributeError: # No Main or similar errors
return False
return False

View File

@ -2,6 +2,7 @@ import random
import string
from allianceauth.eveonline.models import EveCharacter
from allianceauth.services.hooks import NameFormatter
from allianceauth.services.modules.mumble.managers import MumbleManager
from passlib.hash import bcrypt_sha256
from django.db import models
from allianceauth.services.abstract import AbstractServiceModel
@ -49,26 +50,17 @@ class MumbleUser(AbstractServiceModel):
blank=True, null=True, editable=False,
help_text="Timestamp of the users Last Disconnection from Mumble")
objects = MumbleManager()
@property
def display_name(self) -> str:
from .auth_hooks import MumbleService
return NameFormatter(MumbleService(), self.user).format_name()
@staticmethod
def get_username(user) -> str:
return user.profile.main_character.character_name # main character as the user.username may be incorrect
@staticmethod
def sanitise_username(username) -> str:
return username.replace(" ", "_")
@staticmethod
def generate_random_pass() -> str:
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
@staticmethod
def gen_pwhash(password) -> str:
return bcrypt_sha256.encrypt(password.encode('utf-8'))
@property
def groups(self) -> str:
# Not sure where this is used, there was a test for it
return self.group_string()
def __str__(self) -> str:
return f"{self.username}"
@ -77,8 +69,8 @@ class MumbleUser(AbstractServiceModel):
init_password = password
logger.debug(f"Updating mumble user {self.user} password.")
if not password:
password = self.generate_random_pass()
pwhash = self.gen_pwhash(password)
password = MumbleManager.generate_random_pass()
pwhash = MumbleManager.gen_pwhash(password)
logger.debug(f"Proceeding with mumble user {self.user} password update - pwhash starts with {pwhash[0:5]}")
self.pwhash = pwhash
self.hashfn = self.HashFunction.SHA256
@ -101,26 +93,6 @@ class MumbleUser(AbstractServiceModel):
groups_str.append(str(group.name))
return ','.join({g.replace(' ', '-') for g in groups_str})
def create(self, user):
try:
username = self.get_username(user)
logger.debug(f"Creating mumble user with username {username}")
username_clean = self.sanitise_username(username)
password = self.generate_random_pass()
pwhash = self.gen_pwhash(password)
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format(
username_clean, pwhash[0:5]))
logger.info(f"Creating mumble user {username_clean}")
result = self.objects.create(user=user, username=username_clean, pwhash=pwhash)
result.credentials.update({'username': result.username, 'password': password})
return result
except AttributeError: # No Main or similar errors
return False
def user_exists(self, username) -> bool:
return self.objects.filter(username=username).exists()
class Meta:
verbose_name = _("User")
verbose_name_plural = _("Users")
@ -183,6 +155,11 @@ class TempUser(models.Model):
from .auth_hooks import MumbleService
return NameFormatter(MumbleService(), self.user).format_name()
@property
def groups(self) -> str:
# Not sure where this is used, there was a test for it
return self.group_string()
@staticmethod
def get_username(user) -> str:
return user.profile.main_character.character_name # main character as the user.username may be incorrect

View File

@ -1,10 +1,8 @@
from datetime import datetime, timezone
import logging
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from celery import shared_task
from allianceauth.services.tasks import QueueOnce
from .models import MumbleUser, TempLink, TempUser
logger = logging.getLogger(__name__)

View File

@ -4,17 +4,17 @@
{% block title %}
{{ service_name }}
{% endblock %}
{% endblock title %}
{% block url %}
{% if username != '' %}
<a href="mumble://{{ connect_url }}">{{ service_url }}</a>
{% endif %}
{% endblock %}
{% endblock url %}
{% block user %}
{% include "services/service_username.html" with username=username %}
{% endblock %}
{% endblock user %}
{% block controls %}
{% if username == "" %}
@ -53,4 +53,4 @@
<i class="fa-solid fa-clock-rotate-left"></i> History
</a>
{% endif %}
{% endblock %}
{% endblock controls %}

View File

@ -0,0 +1,88 @@
from allianceauth.services.modules.mumble.auth_hooks import MumbleService
from allianceauth.services.modules.mumble.models import MumbleUser
from allianceauth.services.modules.mumble.tasks import MumbleTasks
from django.test import TestCase, RequestFactory
from django import urls
from django.contrib.auth.models import User, Group, Permission
from django.core.exceptions import ObjectDoesNotExist
from allianceauth.tests.auth_utils import AuthUtils
MODULE_PATH = 'allianceauth.services.modules.mumble'
DEFAULT_AUTH_GROUP = 'Member'
def add_permissions():
permission = Permission.objects.get(codename='access_mumble')
members = Group.objects.get_or_create(name=DEFAULT_AUTH_GROUP)[0]
AuthUtils.add_permissions_to_groups([permission], [members])
class MumbleHooksTestCase(TestCase):
def setUp(self):
self.member = 'member_user'
member = AuthUtils.create_member(self.member)
AuthUtils.add_main_character(member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation', corp_ticker='TESTR')
member = User.objects.get(pk=member.pk)
MumbleUser.objects.create(user=member)
self.none_user = 'none_user'
none_user = AuthUtils.create_user(self.none_user)
self.service = MumbleService
add_permissions()
def test_has_account(self):
member = User.objects.get(username=self.member)
none_user = User.objects.get(username=self.none_user)
self.assertTrue(MumbleTasks.has_account(member))
self.assertFalse(MumbleTasks.has_account(none_user))
def test_service_enabled(self):
service = self.service()
member = User.objects.get(username=self.member)
none_user = User.objects.get(username=self.none_user)
self.assertTrue(service.service_active_for_user(member))
self.assertFalse(service.service_active_for_user(none_user))
def test_validate_user(self):
service = self.service()
# Test member is not deleted
member = User.objects.get(username=self.member)
service.validate_user(member)
self.assertTrue(member.mumble)
# Test none user is deleted
none_user = User.objects.get(username=self.none_user)
MumbleUser.objects.create(user=none_user)
service.validate_user(none_user)
with self.assertRaises(ObjectDoesNotExist):
none_mumble = User.objects.get(username=self.none_user).mumble
def test_delete_user(self):
member = User.objects.get(username=self.member)
service = self.service()
result = service.delete_user(member)
self.assertTrue(result)
with self.assertRaises(ObjectDoesNotExist):
mumble_user = User.objects.get(username=self.member).mumble
def test_render_services_ctrl(self):
service = self.service()
member = User.objects.get(username=self.member)
request = RequestFactory().get('/services/')
request.user = member
response = service.render_services_ctrl(request)
self.assertTemplateUsed(service.service_ctrl_template)
self.assertIn(urls.reverse('mumble:deactivate'), response)
self.assertIn(urls.reverse('mumble:reset_password'), response)
self.assertIn(urls.reverse('mumble:set_password'), response)
# Test register becomes available
member.mumble.delete()
member = User.objects.get(username=self.member)
request.user = member
response = service.render_services_ctrl(request)
self.assertIn(urls.reverse('mumble:activate'), response)

View File

@ -0,0 +1,117 @@
import unittest
from allianceauth.authentication.admin import Permission
from allianceauth.tests.auth_utils import AuthUtils
from django.test import TestCase
from django.utils import timezone
from django.contrib.auth.models import User, Group
from unittest.mock import patch
from datetime import timedelta
from ..models import (
MumbleUser,
TempLink,
TempUser,
IdlerHandler,
MumbleServerServer
)
MODULE_PATH = 'allianceauth.services.modules.mumble'
DEFAULT_AUTH_GROUP = 'Member'
def add_permissions():
permission = Permission.objects.get(codename='access_mumble')
members = Group.objects.get_or_create(name=DEFAULT_AUTH_GROUP)[0]
AuthUtils.add_permissions_to_groups([permission], [members])
class MumbleUserTests(TestCase):
def setUp(self):
self.member = AuthUtils.create_member('auth_member')
self.member.email = 'auth_member@example.com'
self.member.save()
AuthUtils.add_main_character(self.member, 'john_mumble', '12345', corp_id='111', corp_name='Test Corporation', corp_ticker='TESTR')
self.member = User.objects.get(pk=self.member.pk)
add_permissions()
self.mumble_user = MumbleUser.objects.create(user=self.member)
def test_mumble_user_str(self):
"""
Test that __str__ returns the username.
"""
self.assertEqual(str(self.mumble_user), 'john_mumble')
def test_update_password_no_arg(self):
"""
Test update_password when no password is provided
(it should generate a random one).
"""
old_pwhash = self.mumble_user.pwhash
self.mumble_user.update_password() # No password argument
# pwhash should have changed (random pass)
self.assertNotEqual(old_pwhash, self.mumble_user.pwhash)
self.assertTrue(self.mumble_user.credentials) # Should have 'username' & 'password'
def test_reset_password(self):
"""
reset_password is basically an alias to update_password with no password argument.
"""
old_pwhash = self.mumble_user.pwhash
self.mumble_user.reset_password()
self.assertNotEqual(old_pwhash, self.mumble_user.pwhash)
self.assertTrue(self.mumble_user.credentials)
class IdlerHandlerTests(TestCase):
def setUp(self):
self.idler = IdlerHandler.objects.create(
name="MyAFKIdler",
enabled=True,
seconds=7200,
interval=120,
channel=999,
denylist=False,
list="some_list"
)
def test_idler_handler_str(self):
self.assertEqual(str(self.idler), "MyAFKIdler")
class MumbleServerServerTests(TestCase):
def setUp(self):
self.idler = IdlerHandler.objects.create(
name="MyAFKIdler",
enabled=True,
seconds=3600,
interval=60,
channel=999,
denylist=True,
list=""
)
self.server = MumbleServerServer.objects.create(
name="MyMumbleServer",
ip="127.0.0.1",
endpoint="127.0.0.1",
port=6502,
secret="supersecret",
watchdog=30,
slice="MumbleServer.ice",
virtual_servers="1,2",
avatar_enable=True,
reject_on_error=True,
offset=1000000000,
idler_handler=self.idler
)
def test_mumble_server_str(self):
"""
Test string representation of MumbleServerServer.
"""
self.assertEqual(str(self.server), "MyMumbleServer")
def test_virtual_servers_list(self):
"""
The virtual_servers_list should parse '1,2' into [1, 2].
"""
self.assertEqual(self.server.virtual_servers_list(), [1, 2])

View File

@ -1,5 +1,8 @@
from unittest import mock
from allianceauth.services.modules.mumble.auth_hooks import MumbleService
from allianceauth.services.modules.mumble.models import MumbleUser
from allianceauth.services.modules.mumble.tasks import MumbleTasks
from django.test import TestCase, RequestFactory
from django import urls
from django.contrib.auth.models import User, Group, Permission
@ -7,90 +10,14 @@ from django.core.exceptions import ObjectDoesNotExist
from allianceauth.tests.auth_utils import AuthUtils
from .auth_hooks import MumbleService
from .models import MumbleUser
from .tasks import MumbleTasks
MODULE_PATH = 'allianceauth.services.modules.mumble'
DEFAULT_AUTH_GROUP = 'Member'
def add_permissions():
permission = Permission.objects.get(codename='access_mumble')
members = Group.objects.get_or_create(name=DEFAULT_AUTH_GROUP)[0]
AuthUtils.add_permissions_to_groups([permission], [members])
class MumbleHooksTestCase(TestCase):
def setUp(self):
self.member = 'member_user'
member = AuthUtils.create_member(self.member)
AuthUtils.add_main_character(member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation', corp_ticker='TESTR')
member = User.objects.get(pk=member.pk)
MumbleUser.objects.create(user=member)
self.none_user = 'none_user'
none_user = AuthUtils.create_user(self.none_user)
self.service = MumbleService
add_permissions()
def test_has_account(self):
member = User.objects.get(username=self.member)
none_user = User.objects.get(username=self.none_user)
self.assertTrue(MumbleTasks.has_account(member))
self.assertFalse(MumbleTasks.has_account(none_user))
def test_service_enabled(self):
service = self.service()
member = User.objects.get(username=self.member)
none_user = User.objects.get(username=self.none_user)
self.assertTrue(service.service_active_for_user(member))
self.assertFalse(service.service_active_for_user(none_user))
def test_validate_user(self):
service = self.service()
# Test member is not deleted
member = User.objects.get(username=self.member)
service.validate_user(member)
self.assertTrue(member.mumble)
# Test none user is deleted
none_user = User.objects.get(username=self.none_user)
MumbleUser.objects.create(user=none_user)
service.validate_user(none_user)
with self.assertRaises(ObjectDoesNotExist):
none_mumble = User.objects.get(username=self.none_user).mumble
def test_delete_user(self):
member = User.objects.get(username=self.member)
service = self.service()
result = service.delete_user(member)
self.assertTrue(result)
with self.assertRaises(ObjectDoesNotExist):
mumble_user = User.objects.get(username=self.member).mumble
def test_render_services_ctrl(self):
service = self.service()
member = User.objects.get(username=self.member)
request = RequestFactory().get('/services/')
request.user = member
response = service.render_services_ctrl(request)
self.assertTemplateUsed(service.service_ctrl_template)
self.assertIn(urls.reverse('mumble:deactivate'), response)
self.assertIn(urls.reverse('mumble:reset_password'), response)
self.assertIn(urls.reverse('mumble:set_password'), response)
# Test register becomes available
member.mumble.delete()
member = User.objects.get(username=self.member)
request.user = member
response = service.render_services_ctrl(request)
self.assertIn(urls.reverse('mumble:activate'), response)
class MumbleViewsTestCase(TestCase):
def setUp(self):
self.member = AuthUtils.create_member('auth_member')
@ -113,7 +40,6 @@ class MumbleViewsTestCase(TestCase):
# create
mumble_user = MumbleUser.objects.get(user=self.member)
self.assertEqual(mumble_user.username, expected_username)
self.assertTrue(MumbleUser.objects.user_exists(expected_username))
self.assertEqual(str(mumble_user), expected_username)
self.assertEqual(mumble_user.display_name, expected_displayname)
self.assertTrue(mumble_user.pwhash)
@ -124,11 +50,9 @@ class MumbleViewsTestCase(TestCase):
self.member.profile.main_character.character_name = "auth_member_updated"
self.member.profile.main_character.corporation_ticker = "TESTU"
self.member.profile.main_character.save()
mumble_user.update_display_name()
mumble_user = MumbleUser.objects.get(user=self.member)
expected_displayname = '[TESTU]auth_member_updated'
self.assertEqual(mumble_user.username, expected_username)
self.assertTrue(MumbleUser.objects.user_exists(expected_username))
self.assertEqual(str(mumble_user), expected_username)
self.assertEqual(mumble_user.display_name, expected_displayname)
self.assertTrue(mumble_user.pwhash)
@ -167,20 +91,3 @@ class MumbleViewsTestCase(TestCase):
self.assertNotEqual(MumbleUser.objects.get(user=self.member).pwhash, old_pwd)
self.assertTemplateUsed(response, 'services/service_credentials.html')
self.assertContains(response, 'auth_member')
class MumbleManagerTestCase(TestCase):
def setUp(self):
from .models import MumbleManager
self.manager = MumbleManager
def test_generate_random_password(self):
password = self.manager.generate_random_pass()
self.assertEqual(len(password), 16)
self.assertIsInstance(password, str)
def test_gen_pwhash(self):
pwhash = self.manager.gen_pwhash('test')
self.assertEqual(pwhash[:15], '$bcrypt-sha256$')
self.assertEqual(len(pwhash), 83)

View File

@ -15,7 +15,7 @@ module_urls = [
path('ajax/release_counts_data', views.release_counts_data, name="release_counts_data"),
path('ajax/release_pie_chart_data', views.release_pie_chart_data, name="release_pie_chart_data"),
# Temp Links
path("", views.index, name="index"),
path("templinks/", views.templinks, name="templinks"),
re_path(r"^join/(?P<link_ref>[\w\-]+)/$", views.link, name="join"),
re_path(r"^nuke/(?P<link_ref>[\w\-]+)/$", views.nuke, name="nuke"),
]

View File

@ -99,6 +99,7 @@ def release_pie_chart_data(request) -> JsonResponse:
"values": list(release_counts.values_list("user_count", flat=True)),
})
class PseudoProfile:
def __init__(self, main):
self.main_character = main
@ -113,7 +114,7 @@ class PseudoUser:
@login_required
@permission_required("mumble.create_new_links")
def index(request):
def templinks(request) -> HttpResponse:
tl = None
if request.method == "POST":