push templink framework

This commit is contained in:
Joel Falknau 2024-12-06 21:07:42 +10:00
parent 3b792117d9
commit 3ef3098ad1
No known key found for this signature in database
12 changed files with 912 additions and 42 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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')),
],
),
]

View File

@ -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)

View File

@ -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()

View File

@ -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 %}
<div class="allianceauth-mumbletemps">
<div class="row">
{% block mumbletemps %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,274 @@
{% extends "mumbletemps/base.html" %}
{% load i18n %}
{% load humanize %}
{% load timetags %}
{% block mumbletemps %}
<div class="col-md-6 mb-2">
<div class="card h-100">
<div class="card-header bg-warning">
<div class="card-title mb-0">{% translate "Mumble Temporary Links" %}</div>
</div>
<div class="card-body text-center">
<p>
{% translate 'Temp Links Give Access to mumble with the <b class="text-success">Guest</b> Group for the preset time.' %}
</p>
<p>
{% translate 'Connected users <b class="text-warning">will not</b> be kicked at the end of this period.' %}
</p>
<p>
{% translate 'Templink users can be kicked in mumble by typing <span class="badge bg-danger">!kicktemps</span> in mumble chat.' %}
</p>
<p>
{% translate 'There are <b class="text-warning">no restrictions</b> on who or how many can use a templink, share wisely.' %}
</p>
</div>
</div>
</div>
<div class="col-md-6 mt-sm-2 mt-md-0 mb-2">
<div class="card h-100">
<div class="card-header">
<div class="card-title mb-0">{% translate "Create New Link" %}</div>
</div>
<div class="card-body text-center">
<p>{% translate "Your link will be displayed on the next page for an easy copy and paste." %}</p>
<form action="{% url 'mumbletemps:index' %}" method="post">
{% csrf_token %}
<div class="mb-3">
<label for="time">{% translate "Duration (hours)" %}</label>
<select class="form-select" name="time" id="time">
<option value="3">3</option>
<option value="6">6</option>
<option value="12">12</option>
<option value="24">24</option>
</select>
</div>
<input class="btn btn-primary" type="submit" value="{% translate 'OK' %}" />
</form>
</div>
</div>
</div>
{% if tl %}
<div class="my-2">
<div class="card">
<div class="card-header bg-success">
<div class="card-title mb-0">{% translate "New Link" %}</div>
</div>
<div class="card-body text-center" style="min-height: 100px;">
<p>
{% translate "Expires in" %}: <span id="countdown{{ tl.link_ref }}hot"></span>
</p>
<pre>{{ SITE_URL }}{% url 'mumbletemps:join' tl.link_ref %}</pre>
<button class="btn btn-info" id="clipboard-new" data-clipboard-text="{{ SITE_URL }}{% url 'mumbletemps:join' tl.link_ref %}">
{% translate "Copy to Clipboard!" %}
</button>
</div>
</div>
</div>
{% endif %}
<div class="my-2">
<div class="card pb-2">
<div class="card-header">
<div class="card-title mb-0">{% translate "Active Links" %}</div>
</div>
<div class="card-body" style="min-height: 100px;">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Creator" %}</th>
<th class="text-center">{% translate "Key" %}</th>
<th class="text-center">{% translate "Time Left" %}</th>
<th class="text-center">{% translate "Copy" %}</th>
<th class="text-center">{% translate "Nuke" %}</th>
</tr>
</thead>
<tbody>
{% for lnk in tl_list %}
<tr>
<td class="text-center">
<img class="ra-avatar img-circle" src="{{ lnk.creator.portrait_url_32 }}" alt="{{ lnk.creator.character_name }}" />
</td>
<td class="text-center">{{ lnk.creator.character_name }}</td>
<td class="text-center">{{ lnk.link_ref }}</td>
<td class="text-center" id="countdown{{ lnk.link_ref }}"></td>
<td class="text-center">
<button class="btn btn-info"
id="clipboard-{{ lnk.link_ref }}"
data-clipboard-text="{{ SITE_URL }}{% url 'mumbletemps:join' lnk.link_ref %}">
{% translate "Copy to Clipboard!" %}
</button>
</td>
<td class="text-center">
<a class="btn btn-danger" href="{% url 'mumbletemps:nuke' lnk.link_ref %}">{% translate "Nuke Link!" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% if ex_tl_list.count > 0 %}
<div class="my-2">
<div class="card">
<div class="card-header">
<div class="card-title mb-0">{% translate "Expired Links" %}</div>
</div>
<div class="card-body" style="min-height: 100px;">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Creator" %}</th>
<th class="text-center">{% translate "Key" %}</th>
<th class="text-center">{% translate "Nuke" %}</th>
</tr>
</thead>
<tbody>
{% for lnk in ex_tl_list %}
<tr>
<td class="text-center">
<img class="ra-avatar img-circle" src="{{ lnk.creator.portrait_url_32 }}" alt="{{ lnk.creator.character_name }}" />
</td>
<td class="text-center">{{ lnk.creator.character_name }}</td>
<td class="text-center">{{ lnk.link_ref }}</td>
<td class="text-center">
<a class="btn btn-danger" href="{% url 'mumbletemps:nuke' lnk.link_ref %}">{% translate "Nuke Link!" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
{% endblock mumbletemps %}
{% block extra_javascript %}
{% include 'bundles/clipboard-js.html' %}
<script>
/* global ClipboardJS, moment */
const clipboard = new ClipboardJS('#clipboard-new');
{
%
for lnk in tl_list %
}
const lnk {
{
lnk.link_ref
}
} = new ClipboardJS('#clipboard-{{ lnk.link_ref }}'); {
% endfor %
}
</script>
{% include "bundles/moment-js.html" with locale=True %}
{% include "bundles/timers-js.html" %}
<script>
const locale = '{{ LANGUAGE_CODE }}';
const timers = [{
%
for lnk in tl_list %
} {
'id': "{{ lnk.link_ref }}",
'targetDate': moment("{{ lnk.expires|print_timestamp| date:"
c " }}"),
'expired': false
}, {
% endfor %
}
{
%
if tl %
} {
'id': "{{ tl.link_ref }}hot",
'targetDate': moment('{{ tl.expires|print_timestamp| date:"c" }}'),
'expired': false
}, {
% endif %
}
];
moment.locale(locale);
/**
* Update a timer
*
* @param timer
*/
const updateTimer = (timer) => {
if (timer.targetDate.isAfter(Date.now())) {
const duration = moment.duration(timer.targetDate - moment(), 'milliseconds');
document.getElementById('countdown' + timer.id).innerHTML = getDurationString(duration);
} else {
timer.expired = true;
document.getElementById('countdown' + timer.id).innerHTML = '';
}
};
/**
* Update all timers
*/
const updateAllTimers = () => {
const l = timers.length;
for (let i = 0; i < l; ++i) {
if (timers[i].expired) {
continue;
}
updateTimer(timers[i]);
}
};
/**
* Timed update function
*/
const timedUpdate = () => {
updateAllTimers();
};
// Set initial values
timedUpdate();
// Start timed updates
setInterval(timedUpdate, 1000);
</script>
{% endblock extra_javascript %}

View File

@ -0,0 +1,121 @@
{% extends "mumbletemps/base.html" %}
{% load static %}
{% load i18n %}
{% load timetags %}
{% block mumbletemps %}
<div class="text-center">
<p>
<img class="ra-avatar img-circle" src="{{ link.creator.portrait_url_128 }}" alt="{{ link.creator.character_name }}" />
</p>
<h4>
{% blocktranslate with character=link.creator.character_name %}
{{ character }} has invited you to join Mumble!
{% endblocktranslate %}
</h4>
<p>
{% translate "Time Remaining" %}: <span id="countdown{{ link.link_ref }}"></span>
</p>
<p>
<span class="label label-info">{% translate "Link Ref" %}: {{ link.link_ref }}</span>
</p>
<p>
<a class="btn btn-primary" href="mumble://{{ connect_url }}">
<img src="{% static 'mumbletemps/images/mumble-icon.png' %}" alt="{% translate 'Mumble' %}" height="64" width="64" style="margin: 15px" />
<span style="margin: 15px;">
{% translate "Click to Join Mumble as" %}: <b>{{ temp_user.name }}</b>
</span>
</a>
</p>
<div class="row justify-content-center">
<div class="col-md-10 col-lg-8">
<div class="card">
<div class="card-header text-center">
<div class="card-title mb-0">{% translate "Manually Connect" %}</div>
</div>
<div class="card-body">
<p>
{% translate "If you have problems with the application link above, please use the following in Mumble's connection dialog." %}
</p>
<p>
{% translate "Mumble URL" %}: <span class="badge bg-dark">{{ mumble }}</span>
</p>
<p>
{% translate "Username" %}: <span class="badge bg-dark">{{ temp_user.username }}</span>
</p>
<p>
{% translate "Password" %}: <span class="badge bg-dark">{{ temp_user.password }}</span>
</p>
</div>
</div>
</div>
</div>
</div>
{% include "bundles/moment-js.html" with locale=True %}
{% include "bundles/timers-js.html" %}
<script>
/* global moment */
const locale = '{{ LANGUAGE_CODE }}';
const timers = [{
'id': '{{ link.link_ref }}',
'targetDate': moment('{{ link.expires|print_timestamp| date:"c" }}'),
'expired': false
}];
moment.locale(locale);
/**
* Update a timer
*
* @param timer
*/
const updateTimer = (timer) => {
if (timer.targetDate.isAfter(Date.now())) {
const duration = moment.duration(timer.targetDate - moment(), 'milliseconds');
document.getElementById("countdown" + timer.id).innerHTML = getDurationString(duration);
} else {
timer.expired = true;
document.getElementById("countdown" + timer.id).innerHTML = "";
}
};
/**
* Update all timers
*/
const updateAllTimers = () => {
const l = timers.length;
for (let i = 0; i < l; ++i) {
if (timers[i].expired) {
continue;
}
updateTimer(timers[i]);
}
};
/**
* Timed update
*/
const timedUpdate = () => {
updateAllTimers();
};
// Set initial values
timedUpdate();
// Start timed updates
setInterval(timedUpdate, 1000);
</script>
{% endblock mumbletemps %}

View File

@ -0,0 +1,140 @@
{% extends "mumbletemps/base.html" %}
{% load static %}
{% load i18n %}
{% load timetags %}
{% block mumbletemps %}
<div class="col-md-12 text-center">
<div class="mumbletemps-sso-login mb-3">
<p>
<img class="ra-avatar img-circle" src="{{ link.creator.portrait_url_128 }}" alt="{{ link.creator.character_name }}" />
</p>
<h4>
{% blocktranslate with character=link.creator.character_name %}
{{ character }} has invited you to join Mumble!
{% endblocktranslate %}
</h4>
<p>
{% translate "Time Remaining" %}: <span id="countdown{{ link.link_ref }}"></span>
</p>
<p>
<span class="label label-info">{% translate "Link Ref" %}: {{ link.link_ref }}</span>
</p>
<form action="{% url 'mumbletemps:join' link.link_ref %}" method="post">
{% csrf_token %}
<input type="hidden" name="sso" value="True" />
<input
type="image"
class="img-responsive center-block"
src="{% static 'allianceauth/authentication/img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}"
alt="{% translate 'Login with SSO"' %}"
>
</form>
</div>
<div class="mumbletemps-non-sso-login">
<div class="row justify-content-center">
<div class="col-md-10 col-lg-8">
<div class="card">
<div class="card-header text-center">
<div class="card-title mb-0">{% translate "Non SSO Login" %}</div>
</div>
<div class="card-body text-start">
<div class="form-group">
<form action="{% url 'mumbletemps:join' link.link_ref %}" method="post">
{% csrf_token %}
<input type="hidden" name="sso" value="False" />
<p class="text-center">
{% translate "If you cannot SSO with your EVE character, You can enter your own details below." %}
</p>
<p>
<label for="name">{% translate "Name" %}</label>
<input type="text" class="form-control" name="name" id="name" placeholder="{% translate 'Who are you?' %}" />
</p>
<p>
<label for="association">{% translate "Association" %}</label>
<input type="text" class="form-control" name="association" id="association" placeholder="{% translate 'Who are you with?' %}" />
</p>
<button type="submit" value="{% translate 'Submit' %}" class="btn btn-primary">
{% translate "Login Without SSO" %}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include "bundles/moment-js.html" with locale=True %}
{% include "bundles/timers-js.html" %}
<script>
/* global moment */
const locale = '{{ LANGUAGE_CODE }}';
const timers = [{
'id': '{{ link.link_ref }}',
'targetDate': moment('{{ link.expires|print_timestamp| date:"c" }}'),
'expired': false
}];
moment.locale(locale);
/**
* Update a timer
*
* @param timer
*/
const updateTimer = (timer) => {
if (timer.targetDate.isAfter(Date.now())) {
const duration = moment.duration(timer.targetDate - moment(), 'milliseconds');
document.getElementById("countdown" + timer.id).innerHTML = getDurationString(duration);
} else {
timer.expired = true;
document.getElementById("countdown" + timer.id).innerHTML = "";
}
};
/**
* Update all timers
*/
const updateAllTimers = () => {
const l = timers.length;
for (let i = 0; i < l; ++i) {
if (timers[i].expired) {
continue;
}
updateTimer(timers[i]);
}
};
/**
* Timed update
*/
const timedUpdate = () => {
updateAllTimers();
};
// Set initial values
timedUpdate();
// Start timed updates
setInterval(timedUpdate, 1000);
</script>
{% endblock mumbletemps %}

View File

@ -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<link_ref>[\w\-]+)/$", views.link, name="join"),
re_path(r"^nuke/(?P<link_ref>[\w\-]+)/$", views.nuke, name="nuke"),
]
urlpatterns = [

View File

@ -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")