mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-08 20:10:17 +02:00
push templink framework
This commit is contained in:
parent
3b792117d9
commit
3ef3098ad1
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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')),
|
||||
],
|
||||
),
|
||||
]
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 = [
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user