mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-13 22:40:16 +02:00
Upgrade Mumble password hashing to bcrypt (#671)
Added transition to bcrypt-sha256 hashing for mumble passwords. All new passwords will be hashed by bcrypt-sha256. The existing SHA-1 hashes will continue to work as a fallback for legacy password hashes.
This commit is contained in:
parent
11d52d476c
commit
2c68f485e2
@ -1,7 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import hashlib
|
from passlib.hash import bcrypt_sha256
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
@ -16,6 +16,8 @@ class MumbleManager:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
HASH_FN = 'bcrypt-sha256'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __santatize_username(username):
|
def __santatize_username(username):
|
||||||
sanatized = username.replace(" ", "_")
|
sanatized = username.replace(" ", "_")
|
||||||
@ -33,24 +35,24 @@ class MumbleManager:
|
|||||||
def __generate_username_blue(username, corp_ticker):
|
def __generate_username_blue(username, corp_ticker):
|
||||||
return "[BLUE][" + corp_ticker + "]" + username
|
return "[BLUE][" + corp_ticker + "]" + username
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def _gen_pwhash(password):
|
def _gen_pwhash(cls, password):
|
||||||
return hashlib.sha1(password.encode('utf-8')).hexdigest()
|
return bcrypt_sha256.encrypt(password.encode('utf-8'))
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def create_user(user, corp_ticker, username, blue=False):
|
def create_user(cls, user, corp_ticker, username, blue=False):
|
||||||
logger.debug("Creating%s mumble user with username %s and ticker %s" % (' blue' if blue else '',
|
logger.debug("Creating%s mumble user with username %s and ticker %s" % (' blue' if blue else '',
|
||||||
username, corp_ticker))
|
username, corp_ticker))
|
||||||
username_clean = MumbleManager.__santatize_username(
|
username_clean = cls.__santatize_username(
|
||||||
MumbleManager.__generate_username_blue(username, corp_ticker) if blue else
|
cls.__generate_username_blue(username, corp_ticker) if blue else
|
||||||
MumbleManager.__generate_username(username, corp_ticker))
|
cls.__generate_username(username, corp_ticker))
|
||||||
password = MumbleManager.__generate_random_pass()
|
password = cls.__generate_random_pass()
|
||||||
pwhash = MumbleManager._gen_pwhash(password)
|
pwhash = cls._gen_pwhash(password)
|
||||||
logger.debug("Proceeding with mumble user creation: clean username %s, pwhash starts with %s" % (
|
logger.debug("Proceeding with mumble user creation: clean username %s, pwhash starts with %s" % (
|
||||||
username_clean, pwhash[0:5]))
|
username_clean, pwhash[0:5]))
|
||||||
if not MumbleUser.objects.filter(username=username_clean).exists():
|
if not MumbleUser.objects.filter(username=username_clean).exists():
|
||||||
logger.info("Creating mumble user %s" % username_clean)
|
logger.info("Creating mumble user %s" % username_clean)
|
||||||
MumbleUser.objects.create(user=user, username=username_clean, pwhash=pwhash)
|
MumbleUser.objects.create(user=user, username=username_clean, pwhash=pwhash, hashfn=cls.HASH_FN)
|
||||||
return username_clean, password
|
return username_clean, password
|
||||||
else:
|
else:
|
||||||
logger.warn("Mumble user %s already exists.")
|
logger.warn("Mumble user %s already exists.")
|
||||||
@ -66,16 +68,17 @@ class MumbleManager:
|
|||||||
logger.error("Unable to delete user %s from mumble: MumbleUser model not found" % user)
|
logger.error("Unable to delete user %s from mumble: MumbleUser model not found" % user)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def update_user_password(user, password=None):
|
def update_user_password(cls, user, password=None):
|
||||||
logger.debug("Updating mumble user %s password." % user)
|
logger.debug("Updating mumble user %s password." % user)
|
||||||
if not password:
|
if not password:
|
||||||
password = MumbleManager.__generate_random_pass()
|
password = cls.__generate_random_pass()
|
||||||
pwhash = MumbleManager._gen_pwhash(password)
|
pwhash = cls._gen_pwhash(password)
|
||||||
logger.debug("Proceeding with mumble user %s password update - pwhash starts with %s" % (user, pwhash[0:5]))
|
logger.debug("Proceeding with mumble user %s password update - pwhash starts with %s" % (user, pwhash[0:5]))
|
||||||
try:
|
try:
|
||||||
model = MumbleUser.objects.get(user=user)
|
model = MumbleUser.objects.get(user=user)
|
||||||
model.pwhash = pwhash
|
model.pwhash = pwhash
|
||||||
|
model.hashfn = cls.HASH_FN
|
||||||
model.save()
|
model.save()
|
||||||
return password
|
return password
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
|
25
services/modules/mumble/migrations/0005_mumbleuser_hashfn.py
Normal file
25
services/modules/mumble/migrations/0005_mumbleuser_hashfn.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.4 on 2017-01-23 10:28
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mumble', '0004_auto_20161214_1024'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mumbleuser',
|
||||||
|
name='hashfn',
|
||||||
|
field=models.CharField(default='sha1', max_length=20),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mumbleuser',
|
||||||
|
name='pwhash',
|
||||||
|
field=models.CharField(max_length=80),
|
||||||
|
),
|
||||||
|
]
|
@ -7,7 +7,8 @@ from django.db import models
|
|||||||
class MumbleUser(models.Model):
|
class MumbleUser(models.Model):
|
||||||
user = models.OneToOneField('auth.User', related_name='mumble', null=True)
|
user = models.OneToOneField('auth.User', related_name='mumble', null=True)
|
||||||
username = models.CharField(max_length=254, unique=True)
|
username = models.CharField(max_length=254, unique=True)
|
||||||
pwhash = models.CharField(max_length=40)
|
pwhash = models.CharField(max_length=80)
|
||||||
|
hashfn = models.CharField(max_length=20, default='sha1')
|
||||||
groups = models.TextField(blank=True, null=True)
|
groups = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -19,15 +19,9 @@ from .auth_hooks import MumbleService
|
|||||||
from .models import MumbleUser
|
from .models import MumbleUser
|
||||||
from .tasks import MumbleTasks
|
from .tasks import MumbleTasks
|
||||||
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
MODULE_PATH = 'services.modules.mumble'
|
MODULE_PATH = 'services.modules.mumble'
|
||||||
|
|
||||||
|
|
||||||
def gen_pwhash(password):
|
|
||||||
return hashlib.sha1(password).hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
class MumbleHooksTestCase(TestCase):
|
class MumbleHooksTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.member = 'member_user'
|
self.member = 'member_user'
|
||||||
@ -198,4 +192,5 @@ class MumbleManagerTestCase(TestCase):
|
|||||||
def test_gen_pwhash(self):
|
def test_gen_pwhash(self):
|
||||||
pwhash = self.manager._gen_pwhash('test')
|
pwhash = self.manager._gen_pwhash('test')
|
||||||
|
|
||||||
self.assertEqual(pwhash, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3')
|
self.assertEqual(pwhash[:15], '$bcrypt-sha256$')
|
||||||
|
self.assertEqual(len(pwhash), 75)
|
||||||
|
31
thirdparty/Mumble/authenticator.py
vendored
31
thirdparty/Mumble/authenticator.py
vendored
@ -70,6 +70,8 @@ try:
|
|||||||
except ImportError: # python 2.4 compat
|
except ImportError: # python 2.4 compat
|
||||||
from sha import sha as sha1
|
from sha import sha as sha1
|
||||||
|
|
||||||
|
from passlib.hash import bcrypt_sha256
|
||||||
|
|
||||||
|
|
||||||
def eprint(*args, **kwargs):
|
def eprint(*args, **kwargs):
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
print(*args, file=sys.stderr, **kwargs)
|
||||||
@ -521,7 +523,10 @@ def do_main_program():
|
|||||||
return (FALL_THROUGH, None, None)
|
return (FALL_THROUGH, None, None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sql = 'SELECT id, pwhash, groups FROM %smumble_mumbleuser WHERE username = %%s' % cfg.database.prefix
|
sql = 'SELECT id, pwhash, groups, hashfn ' \
|
||||||
|
'FROM %smumble_mumbleuser ' \
|
||||||
|
'WHERE username = %%s' % cfg.database.prefix
|
||||||
|
|
||||||
cur = threadDB.execute(sql, [name])
|
cur = threadDB.execute(sql, [name])
|
||||||
except threadDbException:
|
except threadDbException:
|
||||||
return (FALL_THROUGH, None, None)
|
return (FALL_THROUGH, None, None)
|
||||||
@ -532,14 +537,16 @@ def do_main_program():
|
|||||||
info('Fall through for unknown user "%s"', name)
|
info('Fall through for unknown user "%s"', name)
|
||||||
return (FALL_THROUGH, None, None)
|
return (FALL_THROUGH, None, None)
|
||||||
|
|
||||||
uid, upwhash, ugroups = res
|
uid, upwhash, ugroups, uhashfn = res
|
||||||
|
|
||||||
if ugroups:
|
if ugroups:
|
||||||
groups = ugroups.split(',')
|
groups = ugroups.split(',')
|
||||||
else:
|
else:
|
||||||
groups = []
|
groups = []
|
||||||
|
|
||||||
if allianceauth_check_hash(pw, upwhash):
|
debug('checking password with hash function: %s' % uhashfn)
|
||||||
|
|
||||||
|
if allianceauth_check_hash(pw, upwhash, uhashfn):
|
||||||
info('User authenticated: "%s" (%d)', name, uid + cfg.user.id_offset)
|
info('User authenticated: "%s" (%d)', name, uid + cfg.user.id_offset)
|
||||||
debug('Group memberships: %s', str(groups))
|
debug('Group memberships: %s', str(groups))
|
||||||
return (uid + cfg.user.id_offset, entity_decode(name), groups)
|
return (uid + cfg.user.id_offset, entity_decode(name), groups)
|
||||||
@ -745,14 +752,20 @@ def do_main_program():
|
|||||||
info('Shutdown complete')
|
info('Shutdown complete')
|
||||||
|
|
||||||
|
|
||||||
#
|
def allianceauth_check_hash(password, hash, hash_type):
|
||||||
# --- Python implementation of the AllianceAuth MumbleUser hash function
|
|
||||||
#
|
|
||||||
def allianceauth_check_hash(password, hash):
|
|
||||||
"""
|
"""
|
||||||
Python implementation of the smf check hash function
|
Python implementation of the AllianceAuth MumbleUser hash function
|
||||||
|
:param password: Password to be verified
|
||||||
|
:param hash: Hash for the password to be checked against
|
||||||
|
:param hash_type: Hashing function originally used to generate the hash
|
||||||
"""
|
"""
|
||||||
return sha1(password).hexdigest() == hash
|
if hash_type == 'sha1':
|
||||||
|
return sha1(password).hexdigest() == hash
|
||||||
|
elif hash_type == 'bcrypt-sha256':
|
||||||
|
return bcrypt_sha256.verify(password, hash)
|
||||||
|
else:
|
||||||
|
warning("No valid hash function found for %s" % hash_type)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
Loading…
x
Reference in New Issue
Block a user