diff --git a/allianceauth/services/modules/mumble/auth_hooks.py b/allianceauth/services/modules/mumble/auth_hooks.py index efc8df33..f1522e0a 100644 --- a/allianceauth/services/modules/mumble/auth_hooks.py +++ b/allianceauth/services/modules/mumble/auth_hooks.py @@ -1,15 +1,18 @@ import logging import urllib +from allianceauth.menu.hooks import MenuItemHook from django.conf import settings from django.template.loader import render_to_string from allianceauth.notifications import notify from allianceauth import hooks -from allianceauth.services.hooks import ServicesHook +from allianceauth.services.hooks import ServicesHook, UrlHook from .tasks import MumbleTasks from .models import MumbleUser from .urls import urlpatterns +from allianceauth.services.modules.mumble import urls +from django.utils.translation import gettext_lazy as _ logger = logging.getLogger(__name__) @@ -71,7 +74,28 @@ class MumbleService(ServicesHook): 'username': request.user.mumble.username if MumbleTasks.has_account(request.user) else '', }, request=request) - @hooks.register('services_hook') -def register_mumble_service(): +def register_mumble_service() -> ServicesHook: return MumbleService() + + +class MumbleMenuItem(MenuItemHook): + def __init__(self): + # setup menu entry for sidebar + MenuItemHook.__init__( + self=self, + text=_("Mumble Temp Links"), + classes="fa-solid fa-microphone", + url_name="mumble:index", + navactive=["mumble:index"], + ) + + def render(self, request): + if request.user.has_perm("mumble.create_new_templinks"): + return MenuItemHook.render(self, request) + return "" + + +@hooks.register("menu_item_hook") +def register_menu(): + return MumbleMenuItem() diff --git a/allianceauth/services/modules/mumble/authenticator.py b/allianceauth/services/modules/mumble/authenticator.py index ad8b2af8..579c0c03 100644 --- a/allianceauth/services/modules/mumble/authenticator.py +++ b/allianceauth/services/modules/mumble/authenticator.py @@ -430,8 +430,7 @@ def main() -> None: except (OSError, Exception) as e: logger.exception(e) - logger.debug( - f'idToTexture {id} -> image download for {avatar_url} failed, fall through') + logger.debug(f'idToTexture {id} -> image download for {avatar_url} failed, fall through') return "" # FALL_THROUGH else: file = handle.read() diff --git a/allianceauth/services/modules/mumble/managers.py b/allianceauth/services/modules/mumble/managers.py index 81185a1e..5ee02357 100644 --- a/allianceauth/services/modules/mumble/managers.py +++ b/allianceauth/services/modules/mumble/managers.py @@ -6,7 +6,7 @@ from django.db import models from allianceauth.services.hooks import NameFormatter import logging -logging.getLogger(__name__) +logger = logging.getLogger(__name__) class MumbleManager(models.Manager): @@ -48,7 +48,6 @@ class MumbleManager(models.Manager): user=user, username=username_clean, pwhash=pwhash, - hashfn=self.HashFunction.SHA256, display_name=display_name) result.update_groups() result.credentials.update({'username': result.username, 'password': password}) @@ -57,5 +56,5 @@ class MumbleManager(models.Manager): return False return False - def user_exists(self, username): + def user_exists(self, username) -> bool: return self.filter(username=username).exists() diff --git a/allianceauth/services/modules/mumble/migrations/0016_templink_alter_mumbleuser_last_connect_and_more.py b/allianceauth/services/modules/mumble/migrations/0016_templink_alter_mumbleuser_last_connect_and_more.py new file mode 100644 index 00000000..c9f3f0b7 --- /dev/null +++ b/allianceauth/services/modules/mumble/migrations/0016_templink_alter_mumbleuser_last_connect_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2.16 on 2024-12-06 10:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eveonline', '0017_alliance_and_corp_names_are_not_unique'), + ('mumble', '0015_alter_idlerhandler_options_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='TempLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('expires', models.DateTimeField(verbose_name='Expiry')), + ('link_ref', models.CharField(max_length=20)), + ('creator', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eveonline.evecharacter')), + ], + options={ + 'permissions': (('create_new_links', 'Can Create Temp Links'),), + }, + ), + migrations.AlterField( + model_name='mumbleuser', + name='last_connect', + field=models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Connection to Mumble', null=True, verbose_name='Last Connection'), + ), + migrations.AlterField( + model_name='mumbleuser', + name='last_disconnect', + field=models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Disconnection from Mumble', null=True, verbose_name='Last Disconnection'), + ), + migrations.AlterField( + model_name='mumbleuser', + name='release', + field=models.TextField(blank=True, editable=False, help_text='Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else.', null=True, verbose_name='Mumble Release'), + ), + migrations.CreateModel( + name='TempUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('username', models.CharField(max_length=254, unique=True)), + ('pwhash', models.CharField(max_length=90)), + ('hashfn', models.CharField(choices=[('bcrypt-sha256', 'SHA256'), ('sha1', 'SHA1')], default='bcrypt-sha256', max_length=15)), + ('certhash', models.CharField(blank=True, editable=False, help_text='Hash of Mumble client certificate as presented when user authenticates', max_length=254, null=True, verbose_name='Certificate Hash')), + ('release', models.TextField(blank=True, editable=False, help_text='Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else.', null=True, verbose_name='Mumble Release')), + ('version', models.IntegerField(blank=True, editable=False, help_text='Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203.', null=True, verbose_name='Mumble Version')), + ('last_connect', models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Connection to Mumble', null=True, verbose_name='Last Connection')), + ('last_disconnect', models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Disconnection from Mumble', null=True, verbose_name='Last Disconnection')), + ('expires', models.DateTimeField(verbose_name='Expiry')), + ('character_id', models.IntegerField(blank=True, default=None, null=True)), + ('templink', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='mumble.templink')), + ], + ), + ] diff --git a/allianceauth/services/modules/mumble/models.py b/allianceauth/services/modules/mumble/models.py index eeddf7f2..8e04f5b2 100644 --- a/allianceauth/services/modules/mumble/models.py +++ b/allianceauth/services/modules/mumble/models.py @@ -1,4 +1,5 @@ from typing import LiteralString +from allianceauth.eveonline.models import EveCharacter from allianceauth.services.hooks import NameFormatter from allianceauth.services.modules.mumble.managers import MumbleManager from django.db import models @@ -28,43 +29,24 @@ class MumbleUser(AbstractServiceModel): default=HashFunction.SHA256) certhash = models.CharField( verbose_name="Certificate Hash", - max_length=254, - blank=True, - null=True, - editable=False, - help_text="Hash of Mumble client certificate as presented when user authenticates" - ) + max_length=254,blank=True, null=True, editable=False, + help_text="Hash of Mumble client certificate as presented when user authenticates") release = models.TextField( verbose_name="Mumble Release", - max_length=254, - blank=True, - null=True, - editable=False, - help_text="Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else." - ) + blank=True, null=True, editable=False, + help_text="Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else.") version = models.IntegerField( verbose_name="Mumble Version", - blank=True, - null=True, - editable=False, - help_text="Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203." - ) + blank=True, null=True, editable=False, + help_text="Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203.") last_connect = models.DateTimeField( verbose_name="Last Connection", - max_length=254, - blank=True, - null=True, - editable=False, - help_text="Timestamp of the users Last Connection to Mumble" - ) + blank=True, null=True, editable=False, + help_text="Timestamp of the users Last Connection to Mumble") last_disconnect = models.DateTimeField( verbose_name="Last Disconnection", - max_length=254, - blank=True, - null=True, - editable=False, - help_text="Timestamp of the users Last Disconnection from Mumble" - ) + blank=True, null=True, editable=False, + help_text="Timestamp of the users Last Disconnection from Mumble") objects = MumbleManager() @@ -112,6 +94,55 @@ class MumbleUser(AbstractServiceModel): ) +class TempLink(models.Model): + expires = models.DateTimeField(_("Expiry"), auto_now=False, auto_now_add=False) + link_ref = models.CharField(max_length=20) + creator = models.ForeignKey(EveCharacter, on_delete=models.SET_NULL, null=True, default=None) + + class Meta: + permissions = (("create_new_links", "Can Create Temp Links"),) + + def __str__(self): + return f"Link {self.link_ref} - {self.expires}" + + +class TempUser(models.Model): + name = models.CharField(max_length=200) # Display name to show + username = models.CharField(max_length=254, unique=True) + pwhash = models.CharField(max_length=90) + hashfn = models.CharField( + max_length=15, + choices=MumbleUser.HashFunction.choices, + default=MumbleUser.HashFunction.SHA256) + certhash = models.CharField( + verbose_name="Certificate Hash", + max_length=254,blank=True, null=True, editable=False, + help_text="Hash of Mumble client certificate as presented when user authenticates") + release = models.TextField( + verbose_name="Mumble Release", + blank=True, null=True, editable=False, + help_text="Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else.") + version = models.IntegerField( + verbose_name="Mumble Version", + blank=True, null=True, editable=False, + help_text="Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203.") + last_connect = models.DateTimeField( + verbose_name="Last Connection", + blank=True, null=True, editable=False, + help_text="Timestamp of the users Last Connection to Mumble") + last_disconnect = models.DateTimeField( + verbose_name="Last Disconnection", + blank=True, null=True, editable=False, + help_text="Timestamp of the users Last Disconnection from Mumble") + expires = models.DateTimeField(_("Expiry"), auto_now=False, auto_now_add=False) + + templink = models.ForeignKey(TempLink, on_delete=models.CASCADE, null=True, default=None) + character_id = models.IntegerField(default=None, blank=True, null=True) + + def __str__(self) -> str: + return f"Temp User: {self.username} - {self.name}" + + class IdlerHandler(models.Model): name = models.CharField(_("Name"), max_length=50) enabled = models.BooleanField(_("Enabled"), default=False) diff --git a/allianceauth/services/modules/mumble/tasks.py b/allianceauth/services/modules/mumble/tasks.py index 5ce7c9c4..e02a1c86 100644 --- a/allianceauth/services/modules/mumble/tasks.py +++ b/allianceauth/services/modules/mumble/tasks.py @@ -1,10 +1,11 @@ +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 +from .models import MumbleUser, TempLink, TempUser logger = logging.getLogger(__name__) @@ -78,3 +79,10 @@ class MumbleTasks: logger.debug("Updating ALL mumble display names") for mumble_user in MumbleUser.objects.exclude(username__exact=''): MumbleTasks.update_display_name.delay(mumble_user.user.pk) + + +@shared_task +def tidy_up_temp_links(): + TempLink.objects.filter(expires__lt=datetime.now(timezone.utc).replace(tzinfo=timezone.utc).timestamp()).delete() + TempUser.objects.filter(templink__isnull=True).delete() + TempUser.objects.filter(expires__lt=datetime.now(timezone.utc).replace(tzinfo=timezone.utc).timestamp()).delete() diff --git a/allianceauth/services/modules/mumble/templates/services/mumble/base.html b/allianceauth/services/modules/mumble/templates/services/mumble/base.html new file mode 100644 index 00000000..c08c78a7 --- /dev/null +++ b/allianceauth/services/modules/mumble/templates/services/mumble/base.html @@ -0,0 +1,19 @@ +{% extends "allianceauth/base-bs5.html" %} + +{% load i18n %} + +{% block page_title %} + {% translate "Mumble Temporary Links" %} +{% endblock page_title %} + +{% block header_nav_brand %} + {% translate "Mumble Temporary Links" %} +{% endblock header_nav_brand %} + +{% block content %} +
+
+ {% block mumbletemps %}{% endblock %} +
+
+{% endblock %} diff --git a/allianceauth/services/modules/mumble/templates/services/mumble/index.html b/allianceauth/services/modules/mumble/templates/services/mumble/index.html new file mode 100644 index 00000000..c7ab0444 --- /dev/null +++ b/allianceauth/services/modules/mumble/templates/services/mumble/index.html @@ -0,0 +1,274 @@ +{% extends "mumbletemps/base.html" %} + +{% load i18n %} +{% load humanize %} +{% load timetags %} + +{% block mumbletemps %} +
+
+
+
{% translate "Mumble Temporary Links" %}
+
+ +
+

+ {% translate 'Temp Links Give Access to mumble with the Guest Group for the preset time.' %} +

+ +

+ {% translate 'Connected users will not be kicked at the end of this period.' %} +

+ +

+ {% translate 'Templink users can be kicked in mumble by typing !kicktemps in mumble chat.' %} +

+ +

+ {% translate 'There are no restrictions on who or how many can use a templink, share wisely.' %} +

+
+
+
+ +
+
+
+
{% translate "Create New Link" %}
+
+ +
+

{% translate "Your link will be displayed on the next page for an easy copy and paste." %}

+ +
+ {% csrf_token %} + +
+ + +
+ + +
+
+
+
+ + {% if tl %} +
+
+
+
{% translate "New Link" %}
+
+ +
+

+ {% translate "Expires in" %}: +

+ +
{{ SITE_URL }}{% url 'mumbletemps:join' tl.link_ref %}
+ + +
+
+
+ {% endif %} + +
+
+
+
{% translate "Active Links" %}
+
+ +
+ + + + + + + + + + + + + + {% for lnk in tl_list %} + + + + + + + + + + + + + + {% endfor %} + +
{% translate "Creator" %}{% translate "Key" %}{% translate "Time Left" %}{% translate "Copy" %}{% translate "Nuke" %}
+ {{ lnk.creator.character_name }} + {{ lnk.creator.character_name }}{{ lnk.link_ref }} + + + {% translate "Nuke Link!" %} +
+
+
+
+ + {% if ex_tl_list.count > 0 %} +
+
+
+
{% translate "Expired Links" %}
+
+ +
+ + + + + + + + + + + + {% for lnk in ex_tl_list %} + + + + + + + + + + {% endfor %} + +
{% translate "Creator" %}{% translate "Key" %}{% translate "Nuke" %}
+ {{ lnk.creator.character_name }} + {{ lnk.creator.character_name }}{{ lnk.link_ref }} + {% translate "Nuke Link!" %} +
+
+
+
+ {% endif %} +{% endblock mumbletemps %} + +{% block extra_javascript %} + {% include 'bundles/clipboard-js.html' %} + + + + {% include "bundles/moment-js.html" with locale=True %} + {% include "bundles/timers-js.html" %} + + +{% endblock extra_javascript %} diff --git a/allianceauth/services/modules/mumble/templates/services/mumble/link.html b/allianceauth/services/modules/mumble/templates/services/mumble/link.html new file mode 100644 index 00000000..34f41aa2 --- /dev/null +++ b/allianceauth/services/modules/mumble/templates/services/mumble/link.html @@ -0,0 +1,121 @@ +{% extends "mumbletemps/base.html" %} + +{% load static %} +{% load i18n %} +{% load timetags %} + +{% block mumbletemps %} +
+

+ {{ link.creator.character_name }} +

+ +

+ {% blocktranslate with character=link.creator.character_name %} + {{ character }} has invited you to join Mumble! + {% endblocktranslate %} +

+ +

+ {% translate "Time Remaining" %}: +

+

+ {% translate "Link Ref" %}: {{ link.link_ref }} +

+ +

+ + {% translate 'Mumble' %} + + + {% translate "Click to Join Mumble as" %}: {{ temp_user.name }} + + +

+ +
+
+
+
+
{% translate "Manually Connect" %}
+
+ +
+

+ {% translate "If you have problems with the application link above, please use the following in Mumble's connection dialog." %} +

+

+ {% translate "Mumble URL" %}: {{ mumble }} +

+

+ {% translate "Username" %}: {{ temp_user.username }} +

+

+ {% translate "Password" %}: {{ temp_user.password }} +

+
+
+
+
+
+ + {% include "bundles/moment-js.html" with locale=True %} + {% include "bundles/timers-js.html" %} + + +{% endblock mumbletemps %} diff --git a/allianceauth/services/modules/mumble/templates/services/mumble/login.html b/allianceauth/services/modules/mumble/templates/services/mumble/login.html new file mode 100644 index 00000000..f7bea38a --- /dev/null +++ b/allianceauth/services/modules/mumble/templates/services/mumble/login.html @@ -0,0 +1,140 @@ +{% extends "mumbletemps/base.html" %} + +{% load static %} +{% load i18n %} +{% load timetags %} + +{% block mumbletemps %} +
+
+

+ {{ link.creator.character_name }} +

+ +

+ {% blocktranslate with character=link.creator.character_name %} + {{ character }} has invited you to join Mumble! + {% endblocktranslate %} +

+ +

+ {% translate "Time Remaining" %}: +

+

+ {% translate "Link Ref" %}: {{ link.link_ref }} +

+ +
+ {% csrf_token %} + + + +
+
+ +
+
+
+
+
+
{% translate "Non SSO Login" %}
+
+ +
+
+
+ {% csrf_token %} + + + +

+ {% translate "If you cannot SSO with your EVE character, You can enter your own details below." %} +

+ +

+ + +

+ +

+ + +

+ + +
+
+
+
+
+
+
+
+ + {% include "bundles/moment-js.html" with locale=True %} + {% include "bundles/timers-js.html" %} + + + {% endblock mumbletemps %} diff --git a/allianceauth/services/modules/mumble/urls.py b/allianceauth/services/modules/mumble/urls.py index d3276e0b..4d9a5340 100644 --- a/allianceauth/services/modules/mumble/urls.py +++ b/allianceauth/services/modules/mumble/urls.py @@ -1,4 +1,4 @@ -from django.urls import include, path +from django.urls import include, path, re_path from . import views @@ -14,6 +14,10 @@ module_urls = [ path('ajax/connection_history_data', views.connection_history_data, name="connection_history_data"), 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"), + re_path(r"^join/(?P[\w\-]+)/$", views.link, name="join"), + re_path(r"^nuke/(?P[\w\-]+)/$", views.nuke, name="nuke"), ] urlpatterns = [ diff --git a/allianceauth/services/modules/mumble/views.py b/allianceauth/services/modules/mumble/views.py index 7dd07c82..f975ff86 100644 --- a/allianceauth/services/modules/mumble/views.py +++ b/allianceauth/services/modules/mumble/views.py @@ -1,15 +1,23 @@ +from datetime import datetime, timedelta, timezone import logging +from allianceauth.authentication.models import get_guest_state +from allianceauth.eveonline.models import EveCharacter from allianceauth.services.forms import ServicePasswordModelForm from allianceauth.services.abstract import BaseCreatePasswordServiceAccountView, BaseDeactivateServiceAccountView, \ BaseResetPasswordServiceAccountView, BaseSetPasswordServiceAccountView +from allianceauth.services.hooks import NameFormatter +from allianceauth.services.modules.mumble.auth_hooks import MumbleService from django.conf import settings +from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required +from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.db.models import Count -from django.http import HttpResponse, JsonResponse -from django.shortcuts import render +from django.http import Http404, HttpResponse, JsonResponse +from django.shortcuts import redirect, render +from django.utils.crypto import get_random_string -from .models import MumbleUser +from .models import MumbleUser, TempLink, TempUser logger = logging.getLogger(__name__) @@ -88,3 +96,186 @@ def release_pie_chart_data(request) -> JsonResponse: "labels": list(release_counts.values_list("release", flat=True)), "values": list(release_counts.values_list("user_count", flat=True)), }) + +class PseudoProfile: + def __init__(self, main): + self.main_character = main + self.state = get_guest_state() + + +class PseudoUser: + def __init__(self, main, username): + self.username = username + self.profile = PseudoProfile(main) + + +@login_required +@permission_required("mumbletemps.create_new_links") +def index(request): + tl = None + + if request.method == "POST": + duration = request.POST.get("time") + + if duration in ["3", "6", "12", "24"]: + expiry = datetime.now().replace(tzinfo=timezone.utc) + timedelta(hours=int(duration)) + tl = TempLink.objects.create( + creator=request.user.profile.main_character, + link_ref=get_random_string(15), + expires=expiry.timestamp(), + ) + tl.save() + + tl_list = TempLink.objects.prefetch_related("creator").filter( + expires__gte=datetime.datetime.utcnow() + .replace(tzinfo=datetime.timezone.utc) + .timestamp() + ) + ex_tl_list = TempLink.objects.prefetch_related("creator").filter( + expires__lt=datetime.datetime.utcnow() + .replace(tzinfo=datetime.timezone.utc) + .timestamp() + ) + + context = { + "tl": tl, + "text": "Make Links", + "tl_list": tl_list, + "ex_tl_list": ex_tl_list, + } + + return render( + request=request, template_name="mumbletemps/index.html", context=context + ) + + +def link(request, link_ref): + try: + templink = TempLink.objects.get(link_ref=link_ref) + except ObjectDoesNotExist: + raise Http404("Temp Link Does not Exist") + + token = _check_callback(request=request) + if token: + return link_sso(request=request, token=token, link=templink) + + if app_settings.MUMBLE_TEMPS_FORCE_SSO: # default always SSO + # prompt the user to log in for a new token + return sso_redirect(request=request, scopes=["publicData"]) + + if request.method == "POST": # ok so maybe we want to let some other people in too. + if request.POST.get("sso", False) == "False": # they picked user + name = request.POST.get("name", False) + association = request.POST.get("association", False) + + return link_username( + request=request, name=name, association=association, link=templink + ) + elif request.POST.get("sso", False) == "True": # they picked SSO + # prompt the user to log in for a new token + return sso_redirect(request=request, scopes=["publicData"]) + + context = {"link": templink} + + return render( + request=request, template_name="mumbletemps/login.html", context=context + ) + + +def link_username(request, name, association, link): + username = get_random_string(length=10) + + while TempUser.objects.filter(username=username).exists(): # force unique + username = get_random_string(length=10) + + password = get_random_string(length=15) + + display_name = "{}[{}] {}".format( + app_settings.MUMBLE_TEMPS_LOGIN_PREFIX, association, name + ) + + temp_user = TempUser.objects.create( + username=username, + password=password, + name=display_name, + expires=link.expires, + templink=link, + ) + + connect_url = f"{username}:{password}@{settings.MUMBLE_URL}" + + context = { + "temp_user": temp_user, + "link": link, + "connect_url": connect_url, + "mumble": settings.MUMBLE_URL, + } + + return render( + request=request, template_name="mumbletemps/link.html", context=context + ) + + +def link_sso(request, token, link): + try: + char = EveCharacter.objects.get(character_id=token.character_id) + except ObjectDoesNotExist: + try: # create a new character, we should not get here. + char = EveCharacter.objects.update_character( + character_id=token.character_id + ) + except: # noqa: E722 + pass # Yeah… ain't gonna happen + except MultipleObjectsReturned: + pass # authenticator won't care…, but the DB will be unhappy. + + username = get_random_string(length=10) + + while TempUser.objects.filter(username=username).exists(): # force unique + username = get_random_string(length=10) + + password = get_random_string(length=15) + + display_name = "{}{}".format( + app_settings.MUMBLE_TEMPS_SSO_PREFIX, + NameFormatter( + service=MumbleService(), user=PseudoUser(main=char, username=username) + ).format_name(), + ) + + temp_user = TempUser.objects.create( + username=username, + password=password, + name=display_name, + expires=link.expires, + templink=link, + character_id=char.character_id, + ) + + connect_url = f"{username}:{password}@{settings.MUMBLE_URL}" + + context = { + "temp_user": temp_user, + "link": link, + "connect_url": connect_url, + "mumble": settings.MUMBLE_URL, + } + + return render( + request=request, template_name="mumbletemps/link.html", context=context + ) + + +@login_required +@permission_required("mumbletemps.create_new_links") +def nuke(request, link_ref): + try: + TempLink.objects.get(link_ref=link_ref).delete() + TempUser.objects.filter(templink__isnull=True).delete() + + messages.success(request=request, message=f"Deleted Token {link_ref}") + except: # noqa: E722 + messages.error(request=request, message=f"Deleted Token {link_ref}") + pass # Crappy link + + return redirect(to="mumbletemps:index")