Joel Falknau 8f33c649b3
cleanup
2024-12-30 20:28:54 +10:00

285 lines
11 KiB
Python

import random
import string
from allianceauth.eveonline.models import EveCharacter
from allianceauth.services.hooks import NameFormatter
from passlib.hash import bcrypt_sha256
from django.db import models
from allianceauth.services.abstract import AbstractServiceModel
import logging
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
class MumbleUser(AbstractServiceModel):
class HashFunction(models.TextChoices):
SHA256 = 'bcrypt-sha256', _('SHA256')
SHA1 = 'sha1', _('SHA1')
user = models.OneToOneField(
User,
primary_key=True,
on_delete=models.CASCADE,
related_name='mumble'
)
username = models.CharField(max_length=254, unique=True)
pwhash = models.CharField(max_length=90)
hashfn = models.CharField(
max_length=15,
choices=HashFunction.choices,
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")
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")
@property
def display_name(self) -> str:
from .auth_hooks import MumbleService
return NameFormatter(MumbleService(), self.user).format_name()
@staticmethod
def get_username(user) -> str:
return user.profile.main_character.character_name # main character as the user.username may be incorrect
@staticmethod
def sanitise_username(username) -> str:
return username.replace(" ", "_")
@staticmethod
def generate_random_pass() -> str:
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
@staticmethod
def gen_pwhash(password) -> str:
return bcrypt_sha256.encrypt(password.encode('utf-8'))
def __str__(self) -> str:
return f"{self.username}"
def update_password(self, password=None) -> None:
init_password = password
logger.debug(f"Updating mumble user {self.user} password.")
if not password:
password = self.generate_random_pass()
pwhash = self.gen_pwhash(password)
logger.debug(f"Proceeding with mumble user {self.user} password update - pwhash starts with {pwhash[0:5]}")
self.pwhash = pwhash
self.hashfn = self.HashFunction.SHA256
self.save()
if init_password is None:
self.credentials.update({'username': self.username, 'password': password})
def reset_password(self) -> None:
self.update_password()
def group_string(self) -> str:
"""Return a Mumble Safe Formatted List of Groups
This used to be a ModelField, generated on the fly now with DjangoAuthenticatorTM
Returns:
LiteralString: Mumble Safe Formatted List of Groups
"""
groups_str = [self.user.profile.state.name]
for group in self.user.groups.all():
groups_str.append(str(group.name))
return ','.join({g.replace(' ', '-') for g in groups_str})
def create(self, user):
try:
username = self.get_username(user)
logger.debug(f"Creating mumble user with username {username}")
username_clean = self.sanitise_username(username)
password = self.generate_random_pass()
pwhash = self.gen_pwhash(password)
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format(
username_clean, pwhash[0:5]))
logger.info(f"Creating mumble user {username_clean}")
result = super().create(user=user, username=username_clean, pwhash=pwhash)
result.credentials.update({'username': result.username, 'password': password})
return result
except AttributeError: # No Main or similar errors
return False
return False
class Meta:
verbose_name = _("User")
verbose_name_plural = _("Users")
permissions = (
("access_mumble", "Can access the Mumble service"),
("view_connection_history", "Can access the connection history of the Mumble service"),
)
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):
class HashFunction(models.TextChoices):
SHA256 = 'bcrypt-sha256', _('SHA256')
SHA1 = 'sha1', _('SHA1')
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=HashFunction.choices,
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")
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)
@property
def display_name(self) -> str:
from .auth_hooks import MumbleService
return NameFormatter(MumbleService(), self.user).format_name()
@staticmethod
def get_username(user) -> str:
return user.profile.main_character.character_name # main character as the user.username may be incorrect
@staticmethod
def sanitise_username(username) -> str:
return username.replace(" ", "_")
@staticmethod
def generate_random_pass() -> str:
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
@staticmethod
def gen_pwhash(password) -> str:
return bcrypt_sha256.encrypt(password.encode('utf-8'))
def __str__(self) -> str:
return f"Temp User: {self.username} - {self.name}"
def group_string(self) -> str:
"""Overwritten from MumbleUser, we could add features to this in the future
"""
return str(["Guest"])
class Meta:
verbose_name = _("Temp User")
verbose_name_plural = _("Temp Users")
permissions = ()
class IdlerHandler(models.Model):
name = models.CharField(_("Name"), max_length=50)
enabled = models.BooleanField(_("Enabled"), default=False)
seconds = models.SmallIntegerField(_("Idle Seconds"), default=3600)
interval = models.SmallIntegerField(_("Run Interval"), default=60)
channel = models.SmallIntegerField(_("AFK Channel"))
denylist = models.BooleanField(_("DenyList"), default=True, help_text="True for DenyList, False for Allowlist")
list = models.CharField(_("Allow/Deny list"), max_length=50, default="")
class Meta:
verbose_name = _("Idler Handler")
verbose_name_plural = _("Idler Handlers")
default_permissions = ()
def __str__(self) -> str:
return f"{self.name}"
class MumbleServerServer(models.Model): # This will clash with ICE MumbleServer
class ServerVersion(models.TextChoices):
MUMBLESERVER_ICE = 'MumbleServer.ice', _('MumbleServer.ice (Mumble >=1.5.17)')
MURMUR_ICE = 'Murmur.Ice', _('Murmur.Ice (Mumble <=1.5.17)')
name = models.CharField(max_length=50)
ip = models.GenericIPAddressField(
verbose_name=_("Host IP Address"),
protocol="both",
unpack_ipv4=False,
default="127.0.0.1")
endpoint = models.GenericIPAddressField(
verbose_name=_("Endpoint IP Address"),
protocol="both",
unpack_ipv4=False,
default="127.0.0.1")
port = models.PositiveSmallIntegerField(verbose_name=_("Port"), default=6502)
secret = models.CharField(verbose_name=_("ICE Secret"), max_length=50)
watchdog = models.SmallIntegerField(verbose_name=_("Watchdog Interval"), default=30)
slice = models.CharField(
max_length=16,
choices=ServerVersion.choices,
default=ServerVersion.MUMBLESERVER_ICE)
virtual_servers = models.CharField(
verbose_name=_("Virtual Servers"),
max_length=50,
default="1")
avatar_enable = models.BooleanField(
verbose_name=_("Enable EVE Avatars"),
default=True)
reject_on_error = models.BooleanField(
verbose_name=_("Reject Unauthenticated"),
default=True)
offset = models.IntegerField(
verbose_name=_("ID Offset"),
default=1000000000)
idler_handler = models.ForeignKey(
IdlerHandler,
verbose_name=_("Idler Handler"),
on_delete=models.SET_NULL,
null=True,
blank=True)
class Meta:
verbose_name = _("Mumble Server")
verbose_name_plural = _("Mumble Servers")
default_permissions = ()
def __str__(self) -> str:
return f"{self.name}"
def virtual_servers_list(self) -> list:
return [int(num) for num in self.virtual_servers.replace(",", " ").split() if num]