mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-14 06:50:15 +02:00
Refactor mumble service (#914)
* Added in_organisation check to EveCharacter model * Basic name formatter * Switch mumble service to use name formatter * Squash services migrations * Add name to example service to allow it to be used in tests * Add name formatter to services * Add abstract views, model, form for services modules * Refactor mumble service to new style * Don't set credentials if setting a provided password * Add success message to set password view
This commit is contained in:
parent
c4979a22dd
commit
86362bb0dd
115
allianceauth/services/abstract.py
Normal file
115
allianceauth/services/abstract.py
Normal file
@ -0,0 +1,115 @@
|
||||
"""
|
||||
Abstract view classes for building services.
|
||||
|
||||
These view classes are provided as convenience only. If they
|
||||
don't make sense to use in your service, there is no obligation
|
||||
to use these views. You are free to build the internal structure
|
||||
of the service as you like.
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from django.views import View
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import UpdateView, DeleteView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
from django.db import models, IntegrityError
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import render, Http404, redirect
|
||||
|
||||
from .forms import ServicePasswordModelForm
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AbstractServiceModel(models.Model):
|
||||
user = models.OneToOneField('auth.User',
|
||||
primary_key=True,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='%(app_label)s'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AbstractServiceModel, self).__init__(*args, **kwargs)
|
||||
self.credentials = OrderedDict()
|
||||
# Should be set with a dict of service credentials (username, password etc) when changed
|
||||
|
||||
def update_password(self, password=None):
|
||||
pass
|
||||
|
||||
def reset_password(self):
|
||||
pass
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class BaseServiceView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
"""
|
||||
Define:
|
||||
permission_required
|
||||
"""
|
||||
index_redirect = 'services:services'
|
||||
success_url = reverse_lazy(index_redirect)
|
||||
model = AbstractServiceModel # Overload
|
||||
service_name = 'base' # Overload
|
||||
|
||||
|
||||
class ServiceCredentialsViewMixin:
|
||||
template_name = 'services/service_credentials.html'
|
||||
|
||||
|
||||
class BaseCreatePasswordServiceAccountView(BaseServiceView, ServiceCredentialsViewMixin):
|
||||
def get(self, request):
|
||||
logger.debug("{} called by user {}".format(self.__class__.__name__, request.user))
|
||||
try:
|
||||
svc_obj = self.model.objects.create(user=request.user)
|
||||
except IntegrityError:
|
||||
messages.error(request, "That service account already exists")
|
||||
return redirect(self.index_redirect)
|
||||
|
||||
return render(request, self.template_name,
|
||||
context={'credentials': svc_obj.credentials, 'service': self.service_name, 'view': self})
|
||||
|
||||
|
||||
class ServicesCRUDMixin(SingleObjectMixin):
|
||||
def get_object(self, queryset=None):
|
||||
"""
|
||||
Returns the object the view is displaying.
|
||||
"""
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
|
||||
try:
|
||||
return queryset.get(user__pk=self.request.user.pk)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404
|
||||
|
||||
|
||||
class BaseDeactivateServiceAccountView(ServicesCRUDMixin, BaseServiceView, DeleteView):
|
||||
template_name = 'services/service_confirm_delete.html'
|
||||
|
||||
|
||||
class BaseSetPasswordServiceAccountView(ServicesCRUDMixin, BaseServiceView, UpdateView):
|
||||
template_name = 'services/service_password.html'
|
||||
form_class = ServicePasswordModelForm # You should overload this with a subclass
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
result = super(BaseSetPasswordServiceAccountView, self).post(request, *args, **kwargs)
|
||||
if self.get_form().is_valid():
|
||||
messages.success(request, "Successfully set your {} password".format(self.service_name))
|
||||
return result
|
||||
|
||||
|
||||
class BaseResetPasswordServiceAccountView(ServicesCRUDMixin, BaseServiceView, ServiceCredentialsViewMixin):
|
||||
"""
|
||||
Set a random password
|
||||
"""
|
||||
def get(self, request):
|
||||
svc_obj = self.get_object()
|
||||
svc_obj.reset_password()
|
||||
return render(request, self.template_name,
|
||||
context={'credentials': svc_obj.credentials, 'service': self.service_name, 'view': self})
|
@ -27,3 +27,20 @@ class ServicePasswordForm(forms.Form):
|
||||
if not len(password) >= 8:
|
||||
raise forms.ValidationError(_("Password must be at least 8 characters long."))
|
||||
return password
|
||||
|
||||
|
||||
class ServicePasswordModelForm(forms.ModelForm):
|
||||
password = forms.CharField(label=_("Password"), required=True, widget=forms.PasswordInput())
|
||||
|
||||
def clean_password(self):
|
||||
password = self.cleaned_data['password']
|
||||
if not len(password) >= 8:
|
||||
raise forms.ValidationError(_("Password must be at least 8 characters long."))
|
||||
return password
|
||||
|
||||
def save(self, commit=True):
|
||||
svc_obj = super(ServicePasswordModelForm, self).save(commit=False)
|
||||
svc_obj.update_password(self.cleaned_data['password'])
|
||||
if commit:
|
||||
svc_obj.save()
|
||||
return svc_obj
|
||||
|
@ -6,8 +6,8 @@ from allianceauth.notifications import notify
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import ServicesHook
|
||||
from .manager import MumbleManager
|
||||
from .tasks import MumbleTasks
|
||||
from .models import MumbleUser
|
||||
from .urls import urlpatterns
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -25,11 +25,14 @@ class MumbleService(ServicesHook):
|
||||
|
||||
def delete_user(self, user, notify_user=False):
|
||||
logging.debug("Deleting user %s %s account" % (user, self.name))
|
||||
if MumbleManager.delete_user(user):
|
||||
try:
|
||||
if user.mumble.delete():
|
||||
if notify_user:
|
||||
notify(user, 'Mumble Account Disabled', level='danger')
|
||||
return True
|
||||
return False
|
||||
except MumbleUser.DoesNotExist:
|
||||
logging.debug("User does not have a mumble account")
|
||||
|
||||
def update_groups(self, user):
|
||||
logger.debug("Updating %s groups for %s" % (self.name, user))
|
||||
|
@ -1,96 +0,0 @@
|
||||
import random
|
||||
import string
|
||||
from passlib.hash import bcrypt_sha256
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from .models import MumbleUser
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MumbleManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
HASH_FN = 'bcrypt-sha256'
|
||||
|
||||
@staticmethod
|
||||
def __santatize_username(username):
|
||||
sanatized = username.replace(" ", "_")
|
||||
return sanatized
|
||||
|
||||
@staticmethod
|
||||
def __generate_random_pass():
|
||||
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
|
||||
|
||||
@classmethod
|
||||
def _gen_pwhash(cls, password):
|
||||
return bcrypt_sha256.encrypt(password.encode('utf-8'))
|
||||
|
||||
@classmethod
|
||||
def create_user(cls, user, username):
|
||||
logger.debug("Creating mumble user with username %s" % (username))
|
||||
username_clean = cls.__santatize_username(username)
|
||||
password = cls.__generate_random_pass()
|
||||
pwhash = cls._gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user creation: clean username %s, pwhash starts with %s" % (
|
||||
username_clean, pwhash[0:5]))
|
||||
if not MumbleUser.objects.filter(username=username_clean).exists():
|
||||
logger.info("Creating mumble user %s" % username_clean)
|
||||
MumbleUser.objects.create(user=user, username=username_clean, pwhash=pwhash, hashfn=cls.HASH_FN)
|
||||
return username_clean, password
|
||||
else:
|
||||
logger.warn("Mumble user %s already exists.")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def delete_user(user):
|
||||
logger.debug("Deleting user %s from mumble." % user)
|
||||
if MumbleUser.objects.filter(user=user).exists():
|
||||
MumbleUser.objects.filter(user=user).delete()
|
||||
logger.info("Deleted user %s from mumble" % user)
|
||||
return True
|
||||
logger.error("Unable to delete user %s from mumble: MumbleUser model not found" % user)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def update_user_password(cls, user, password=None):
|
||||
logger.debug("Updating mumble user %s password." % user)
|
||||
if not password:
|
||||
password = cls.__generate_random_pass()
|
||||
pwhash = cls._gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user %s password update - pwhash starts with %s" % (user, pwhash[0:5]))
|
||||
try:
|
||||
model = MumbleUser.objects.get(user=user)
|
||||
model.pwhash = pwhash
|
||||
model.hashfn = cls.HASH_FN
|
||||
model.save()
|
||||
return password
|
||||
except ObjectDoesNotExist:
|
||||
logger.error("User %s not found on mumble. Unable to update password." % user)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def update_groups(user, groups):
|
||||
logger.debug("Updating mumble user %s groups %s" % (user, groups))
|
||||
safe_groups = list(set([g.replace(' ', '-') for g in groups]))
|
||||
groups = ''
|
||||
for g in safe_groups:
|
||||
groups = groups + g + ','
|
||||
groups = groups.strip(',')
|
||||
if MumbleUser.objects.filter(user=user).exists():
|
||||
logger.info("Updating mumble user %s groups to %s" % (user, safe_groups))
|
||||
model = MumbleUser.objects.get(user=user)
|
||||
model.groups = groups
|
||||
model.save()
|
||||
return True
|
||||
else:
|
||||
logger.error("User %s not found on mumble. Unable to update groups." % user)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def user_exists(username):
|
||||
return MumbleUser.objects.filter(username=username).exists()
|
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.6 on 2017-10-09 09:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mumble', '0006_service_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='mumbleuser',
|
||||
name='id',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mumbleuser',
|
||||
name='user',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='mumble', serialize=False, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
@ -1,16 +1,98 @@
|
||||
import random
|
||||
import string
|
||||
from passlib.hash import bcrypt_sha256
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import Group
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
from allianceauth.services.abstract import AbstractServiceModel
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MumbleUser(models.Model):
|
||||
user = models.OneToOneField('auth.User', related_name='mumble', null=True, on_delete=models.CASCADE)
|
||||
class MumbleManager(models.Manager):
|
||||
HASH_FN = 'bcrypt-sha256'
|
||||
|
||||
@staticmethod
|
||||
def get_username(user):
|
||||
from .auth_hooks import MumbleService
|
||||
return NameFormatter(MumbleService(), user).format_name()
|
||||
|
||||
@staticmethod
|
||||
def sanitise_username(username):
|
||||
return username.replace(" ", "_")
|
||||
|
||||
@staticmethod
|
||||
def generate_random_pass():
|
||||
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
|
||||
|
||||
@staticmethod
|
||||
def gen_pwhash(password):
|
||||
return bcrypt_sha256.encrypt(password.encode('utf-8'))
|
||||
|
||||
def create(self, user):
|
||||
username = self.get_username(user)
|
||||
logger.debug("Creating mumble user with username {}".format(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("Creating mumble user {}".format(username_clean))
|
||||
|
||||
result = super(MumbleManager, self).create(user=user, username=username_clean,
|
||||
pwhash=pwhash, hashfn=self.HASH_FN)
|
||||
result.update_groups()
|
||||
result.credentials.update({'username': result.username, 'password': password})
|
||||
return result
|
||||
|
||||
def user_exists(self, username):
|
||||
return self.filter(username=username).exists()
|
||||
|
||||
|
||||
class MumbleUser(AbstractServiceModel):
|
||||
username = models.CharField(max_length=254, unique=True)
|
||||
pwhash = models.CharField(max_length=80)
|
||||
hashfn = models.CharField(max_length=20, default='sha1')
|
||||
groups = models.TextField(blank=True, null=True)
|
||||
|
||||
objects = MumbleManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
def update_password(self, password=None):
|
||||
init_password = password
|
||||
logger.debug("Updating mumble user %s password.".format(self.user))
|
||||
if not password:
|
||||
password = MumbleManager.generate_random_pass()
|
||||
pwhash = MumbleManager.gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user {} password update - pwhash starts with {}".format(
|
||||
self.user, pwhash[0:5]))
|
||||
self.pwhash = pwhash
|
||||
self.hashfn = MumbleManager.HASH_FN
|
||||
self.save()
|
||||
if init_password is None:
|
||||
self.credentials.update({'username': self.username, 'password': password})
|
||||
|
||||
def reset_password(self):
|
||||
self.update_password()
|
||||
|
||||
def update_groups(self, groups: Group=None):
|
||||
if groups is None:
|
||||
groups = self.user.groups.all()
|
||||
groups_str = []
|
||||
for group in groups:
|
||||
groups_str.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups_str.append('empty')
|
||||
safe_groups = ','.join(set([g.replace(' ', '-') for g in groups_str]))
|
||||
logger.info("Updating mumble user {} groups to {}".format(self.user, safe_groups))
|
||||
self.groups = safe_groups
|
||||
self.save()
|
||||
return True
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("access_mumble", u"Can access the Mumble service"),
|
||||
|
@ -2,10 +2,8 @@ import logging
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
|
||||
from allianceauth.celery import app
|
||||
from .manager import MumbleManager
|
||||
from .models import MumbleUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -27,32 +25,25 @@ class MumbleTasks:
|
||||
logger.info("Deleting all MumbleUser models")
|
||||
MumbleUser.objects.all().delete()
|
||||
|
||||
@staticmethod
|
||||
def get_username(user):
|
||||
from .auth_hooks import MumbleService
|
||||
return NameFormatter(MumbleService(), user).format_name()
|
||||
|
||||
@staticmethod
|
||||
@app.task(bind=True, name="mumble.update_groups")
|
||||
def update_groups(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating mumble groups for user %s" % user)
|
||||
if MumbleTasks.has_account(user):
|
||||
groups = []
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s mumble groups to %s" % (user, groups))
|
||||
try:
|
||||
if not MumbleManager.update_groups(user, groups):
|
||||
if not user.mumble.update_groups():
|
||||
raise Exception("Group sync failed")
|
||||
logger.debug("Updated user %s mumble groups." % user)
|
||||
return True
|
||||
except MumbleUser.DoesNotExist:
|
||||
logger.info("Mumble group sync failed for {}, user does not have a mumble account".format(user))
|
||||
except:
|
||||
logger.exception("Mumble group sync failed for %s, retrying in 10 mins" % user)
|
||||
raise self.retry(countdown=60 * 10)
|
||||
logger.debug("Updated user %s mumble groups." % user)
|
||||
else:
|
||||
logger.debug("User %s does not have a mumble account, skipping" % user)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@app.task(name="mumble.update_all_groups")
|
||||
|
@ -25,7 +25,7 @@ class MumbleHooksTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.member = 'member_user'
|
||||
member = AuthUtils.create_member(self.member)
|
||||
MumbleUser.objects.create(user=member, username=self.member, pwhash='password', groups='Member')
|
||||
MumbleUser.objects.create(user=member)
|
||||
self.none_user = 'none_user'
|
||||
none_user = AuthUtils.create_user(self.none_user)
|
||||
self.service = MumbleService
|
||||
@ -45,13 +45,13 @@ class MumbleHooksTestCase(TestCase):
|
||||
self.assertTrue(service.service_active_for_user(member))
|
||||
self.assertFalse(service.service_active_for_user(none_user))
|
||||
|
||||
@mock.patch(MODULE_PATH + '.tasks.MumbleManager')
|
||||
def test_update_all_groups(self, manager):
|
||||
@mock.patch(MODULE_PATH + '.tasks.User.mumble')
|
||||
def test_update_all_groups(self, mumble):
|
||||
service = self.service()
|
||||
service.update_all_groups()
|
||||
# Check member and blue user have groups updated
|
||||
self.assertTrue(manager.update_groups.called)
|
||||
self.assertEqual(manager.update_groups.call_count, 1)
|
||||
self.assertTrue(mumble.update_groups.called)
|
||||
self.assertEqual(mumble.update_groups.call_count, 1)
|
||||
|
||||
def test_update_groups(self):
|
||||
# Check member has Member group updated
|
||||
@ -66,11 +66,10 @@ class MumbleHooksTestCase(TestCase):
|
||||
self.assertIn(DEFAULT_AUTH_GROUP, mumble_user.groups)
|
||||
|
||||
# Check none user does not have groups updated
|
||||
with mock.patch(MODULE_PATH + '.tasks.MumbleManager') as manager:
|
||||
service = self.service()
|
||||
none_user = User.objects.get(username=self.none_user)
|
||||
service.update_groups(none_user)
|
||||
self.assertFalse(manager.update_groups.called)
|
||||
result = service.update_groups(none_user)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_validate_user(self):
|
||||
service = self.service()
|
||||
@ -81,7 +80,7 @@ class MumbleHooksTestCase(TestCase):
|
||||
|
||||
# Test none user is deleted
|
||||
none_user = User.objects.get(username=self.none_user)
|
||||
MumbleUser.objects.create(user=none_user, username='mr no-name', pwhash='password', groups='Blue,Orange')
|
||||
MumbleUser.objects.create(user=none_user)
|
||||
service.validate_user(none_user)
|
||||
with self.assertRaises(ObjectDoesNotExist):
|
||||
none_mumble = User.objects.get(username=self.none_user).mumble
|
||||
@ -139,11 +138,11 @@ class MumbleViewsTestCase(TestCase):
|
||||
self.assertTrue(mumble_user.pwhash)
|
||||
self.assertEqual('Member', mumble_user.groups)
|
||||
|
||||
def test_deactivate(self):
|
||||
def test_deactivate_post(self):
|
||||
self.login()
|
||||
MumbleUser.objects.create(user=self.member, username='some member')
|
||||
MumbleUser.objects.create(user=self.member)
|
||||
|
||||
response = self.client.get(urls.reverse('mumble:deactivate'))
|
||||
response = self.client.post(urls.reverse('mumble:deactivate'))
|
||||
|
||||
self.assertRedirects(response, expected_url=urls.reverse('services:services'), target_status_code=200)
|
||||
with self.assertRaises(ObjectDoesNotExist):
|
||||
@ -151,37 +150,39 @@ class MumbleViewsTestCase(TestCase):
|
||||
|
||||
def test_set_password(self):
|
||||
self.login()
|
||||
MumbleUser.objects.create(user=self.member, username='some member', pwhash='old')
|
||||
created = MumbleUser.objects.create(user=self.member)
|
||||
old_pwd = created.credentials.get('password')
|
||||
|
||||
response = self.client.post(urls.reverse('mumble:set_password'), data={'password': '1234asdf'})
|
||||
|
||||
self.assertNotEqual(MumbleUser.objects.get(user=self.member).pwhash, 'old')
|
||||
self.assertNotEqual(MumbleUser.objects.get(user=self.member).pwhash, old_pwd)
|
||||
self.assertRedirects(response, expected_url=urls.reverse('services:services'), target_status_code=200)
|
||||
|
||||
def test_reset_password(self):
|
||||
self.login()
|
||||
MumbleUser.objects.create(user=self.member, username='some member', pwhash='old')
|
||||
created = MumbleUser.objects.create(user=self.member)
|
||||
old_pwd = created.credentials.get('password')
|
||||
|
||||
response = self.client.get(urls.reverse('mumble:reset_password'))
|
||||
|
||||
self.assertNotEqual(MumbleUser.objects.get(user=self.member).pwhash, 'old')
|
||||
self.assertNotEqual(MumbleUser.objects.get(user=self.member).pwhash, old_pwd)
|
||||
self.assertTemplateUsed(response, 'services/service_credentials.html')
|
||||
self.assertContains(response, 'some member')
|
||||
self.assertContains(response, 'auth_member')
|
||||
|
||||
|
||||
class MumbleManagerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
from .manager import MumbleManager
|
||||
from .models import MumbleManager
|
||||
self.manager = MumbleManager
|
||||
|
||||
def test_generate_random_password(self):
|
||||
password = self.manager._MumbleManager__generate_random_pass()
|
||||
password = self.manager.generate_random_pass()
|
||||
|
||||
self.assertEqual(len(password), 16)
|
||||
self.assertIsInstance(password, type(''))
|
||||
|
||||
def test_gen_pwhash(self):
|
||||
pwhash = self.manager._gen_pwhash('test')
|
||||
pwhash = self.manager.gen_pwhash('test')
|
||||
|
||||
self.assertEqual(pwhash[:15], '$bcrypt-sha256$')
|
||||
self.assertEqual(len(pwhash), 75)
|
||||
|
@ -6,11 +6,10 @@ app_name = 'mumble'
|
||||
|
||||
module_urls = [
|
||||
# Mumble service control
|
||||
url(r'^activate/$', views.activate_mumble, name='activate'),
|
||||
url(r'^deactivate/$', views.deactivate_mumble, name='deactivate'),
|
||||
url(r'^reset_password/$', views.reset_mumble_password,
|
||||
name='reset_password'),
|
||||
url(r'^set_password/$', views.set_mumble_password, name='set_password'),
|
||||
url(r'^activate/$', views.CreateAccountMumbleView.as_view(), name='activate'),
|
||||
url(r'^deactivate/$', views.DeleteMumbleView.as_view(), name='deactivate'),
|
||||
url(r'^reset_password/$', views.ResetPasswordMumbleView.as_view(), name='reset_password'),
|
||||
url(r'^set_password/$', views.SetPasswordMumbleView.as_view(), name='set_password'),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -1,103 +1,37 @@
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
from allianceauth.services.forms import ServicePasswordModelForm
|
||||
from allianceauth.services.abstract import BaseCreatePasswordServiceAccountView, BaseDeactivateServiceAccountView, \
|
||||
BaseResetPasswordServiceAccountView, BaseSetPasswordServiceAccountView
|
||||
|
||||
from .manager import MumbleManager
|
||||
from .tasks import MumbleTasks
|
||||
from .models import MumbleUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ACCESS_PERM = 'mumble.access_mumble'
|
||||
|
||||
class MumblePasswordForm(ServicePasswordModelForm):
|
||||
class Meta:
|
||||
model = MumbleUser
|
||||
fields = ('password',)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required(ACCESS_PERM)
|
||||
def activate_mumble(request):
|
||||
logger.debug("activate_mumble called by user %s" % request.user)
|
||||
character = request.user.profile.main_character
|
||||
|
||||
logger.debug("Adding mumble user for %s with main character %s" % (request.user, character))
|
||||
result = MumbleManager.create_user(request.user, MumbleTasks.get_username(request.user))
|
||||
|
||||
if result:
|
||||
logger.debug("Updated authserviceinfo for user %s with mumble credentials. Updating groups." % request.user)
|
||||
MumbleTasks.update_groups.apply(args=(request.user.pk,)) # Run synchronously to prevent timing issues
|
||||
logger.info("Successfully activated mumble for user %s" % request.user)
|
||||
messages.success(request, 'Activated Mumble account.')
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
}
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'Mumble'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate mumble for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Mumble account.')
|
||||
return redirect("services:services")
|
||||
class MumbleViewMixin:
|
||||
service_name = 'mumble'
|
||||
model = MumbleUser
|
||||
permission_required = 'mumble.access_mumble'
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required(ACCESS_PERM)
|
||||
def deactivate_mumble(request):
|
||||
logger.debug("deactivate_mumble called by user %s" % request.user)
|
||||
# if we successfully remove the user or the user is already removed
|
||||
if MumbleManager.delete_user(request.user):
|
||||
logger.info("Successfully deactivated mumble for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated Mumble account.')
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate mumble for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Mumble account.')
|
||||
return redirect("services:services")
|
||||
class CreateAccountMumbleView(MumbleViewMixin, BaseCreatePasswordServiceAccountView):
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required(ACCESS_PERM)
|
||||
def reset_mumble_password(request):
|
||||
logger.debug("reset_mumble_password called by user %s" % request.user)
|
||||
result = MumbleManager.update_user_password(request.user)
|
||||
|
||||
# if blank we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset mumble password for user %s" % request.user)
|
||||
messages.success(request, 'Reset Mumble password.')
|
||||
credentials = {
|
||||
'username': request.user.mumble.username,
|
||||
'password': result,
|
||||
}
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'Mumble'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to reset mumble password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Mumble account.')
|
||||
return redirect("services:services")
|
||||
class DeleteMumbleView(MumbleViewMixin, BaseDeactivateServiceAccountView):
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required(ACCESS_PERM)
|
||||
def set_mumble_password(request):
|
||||
logger.debug("set_mumble_password called by user %s" % request.user)
|
||||
if request.method == 'POST':
|
||||
logger.debug("Received POST request with form.")
|
||||
form = ServicePasswordForm(request.POST)
|
||||
logger.debug("Form is valid: %s" % form.is_valid())
|
||||
if form.is_valid() and MumbleTasks.has_account(request.user):
|
||||
password = form.cleaned_data['password']
|
||||
logger.debug("Form contains password of length %s" % len(password))
|
||||
result = MumbleManager.update_user_password(request.user, password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully reset mumble password for user %s" % request.user)
|
||||
messages.success(request, 'Set Mumble password.')
|
||||
else:
|
||||
logger.error("Failed to install custom mumble password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Mumble account.')
|
||||
return redirect("services:services")
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
form = ServicePasswordForm()
|
||||
class ResetPasswordMumbleView(MumbleViewMixin, BaseResetPasswordServiceAccountView):
|
||||
pass
|
||||
|
||||
logger.debug("Rendering form for user %s" % request.user)
|
||||
context = {'form': form, 'service': 'Mumble'}
|
||||
return render(request, 'services/service_password.html', context=context)
|
||||
|
||||
class SetPasswordMumbleView(MumbleViewMixin, BaseSetPasswordServiceAccountView):
|
||||
form_class = MumblePasswordForm
|
||||
|
@ -0,0 +1,31 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
|
||||
{% block page_title %}
|
||||
{% blocktrans with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktrans %}
|
||||
{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">
|
||||
{% blocktrans with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktrans %}
|
||||
</h1>
|
||||
<div class="container-fluid">
|
||||
<div class="col-md-2 col-md-offset-5">
|
||||
<div class="row">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Are you sure you want to delete your {{ view.service_name }} account {{ object }}?
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<input class="btn btn-danger btn-block" type="submit" value="Confirm" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
@ -2,10 +2,11 @@
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}Credentials{% endblock page_title %}
|
||||
{% block page_title %}{% blocktrans with service_name=view.service_name|title %}{{ service_name }} Credentials{% endblocktrans %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}{{ service }} Credentials{% endblocktrans %}</h1>
|
||||
<h1 class="page-header text-center">{% blocktrans with service_name=view.service_name|title %}{{ service_name }} Credentials{% endblocktrans %}</h1>
|
||||
<div class="container-fluid">
|
||||
<div class="col-lg-4 col-lg-offset-4">
|
||||
<form class="form-signin">
|
||||
|
@ -3,12 +3,11 @@
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% trans "Service Password Change" %}{% endblock page_title %}
|
||||
{% block extra_css %}{% endblock extra_css %}
|
||||
{% block page_title %}{% blocktrans with service_name=view.service_name|title %}{{ service_name }} Password Change{% endblocktrans %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Set {{service}} Password{% endblocktrans %}</h1>
|
||||
<h1 class="page-header text-center">{% blocktrans with service_name=view.service_name|title %}Set {{service_name}} Password{% endblocktrans %}</h1>
|
||||
<div class="container-fluid">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="row">
|
||||
|
Loading…
x
Reference in New Issue
Block a user