diff --git a/src/pretix/base/migrations/0147_user_session_token.py b/src/pretix/base/migrations/0147_user_session_token.py new file mode 100644 index 000000000..c0125b3c7 --- /dev/null +++ b/src/pretix/base/migrations/0147_user_session_token.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.9 on 2020-03-21 15:12 + +from django.db import migrations, models + +import pretix.base.models.auth + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0146_giftcardtransaction_text'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='session_token', + field=models.CharField(default=pretix.base.models.auth.generate_session_token, max_length=32), + ), + ] diff --git a/src/pretix/base/models/auth.py b/src/pretix/base/models/auth.py index fd0faf17e..c51983b52 100644 --- a/src/pretix/base/models/auth.py +++ b/src/pretix/base/models/auth.py @@ -12,7 +12,7 @@ from django.contrib.auth.tokens import default_token_generator from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Q -from django.utils.crypto import get_random_string +from django.utils.crypto import get_random_string, salted_hmac from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from django_otp.models import Device @@ -54,6 +54,10 @@ def generate_notifications_token(): return get_random_string(length=32) +def generate_session_token(): + return get_random_string(length=32) + + class SuperuserPermissionSet: def __contains__(self, item): return True @@ -110,6 +114,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin): ) notifications_token = models.CharField(max_length=255, default=generate_notifications_token) auth_backend = models.CharField(max_length=255, default='native') + session_token = models.CharField(max_length=32, default=generate_session_token) objects = UserManager() @@ -382,6 +387,20 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin): self._staff_session_cache[session_key] = sess return self._staff_session_cache[session_key] + def get_session_auth_hash(self): + """ + Return an HMAC that needs to + """ + key_salt = "pretix.base.models.User.get_session_auth_hash" + payload = self.password + payload += self.email + payload += self.session_token + return salted_hmac(key_salt, payload).hexdigest() + + def update_session_token(self): + self.session_token = generate_session_token() + self.save(update_fields=['session_token']) + class StaffSession(models.Model): user = models.ForeignKey('User', on_delete=models.PROTECT) diff --git a/src/pretix/control/views/user.py b/src/pretix/control/views/user.py index 598e83edc..9fff7b31d 100644 --- a/src/pretix/control/views/user.py +++ b/src/pretix/control/views/user.py @@ -323,6 +323,8 @@ class User2FADeviceDeleteView(RecentAuthenticationRequiredMixin, TemplateView): msgs.append(_('Two-factor authentication has been disabled.')) self.request.user.send_security_notice(msgs) + self.request.user.update_session_token() + update_session_auth_hash(self.request, self.request.user) messages.success(request, _('The device has been removed.')) return redirect(reverse('control:user.settings.2fa')) @@ -434,6 +436,8 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa _('Two-factor authentication has been enabled.') ) self.request.user.send_security_notice(notices) + self.request.user.update_session_token() + update_session_auth_hash(self.request, self.request.user) note = '' if not self.request.user.require_2fa: @@ -492,6 +496,8 @@ class User2FADeviceConfirmTOTPView(RecentAuthenticationRequiredMixin, TemplateVi _('Two-factor authentication has been enabled.') ) self.request.user.send_security_notice(notices) + self.request.user.update_session_token() + update_session_auth_hash(self.request, self.request.user) note = '' if not self.request.user.require_2fa: @@ -526,6 +532,8 @@ class User2FAEnableView(RecentAuthenticationRequiredMixin, TemplateView): self.request.user.send_security_notice([ _('Two-factor authentication has been enabled.') ]) + self.request.user.update_session_token() + update_session_auth_hash(self.request, self.request.user) return redirect(reverse('control:user.settings.2fa')) @@ -540,6 +548,8 @@ class User2FADisableView(RecentAuthenticationRequiredMixin, TemplateView): self.request.user.send_security_notice([ _('Two-factor authentication has been disabled.') ]) + self.request.user.update_session_token() + update_session_auth_hash(self.request, self.request.user) return redirect(reverse('control:user.settings.2fa')) @@ -555,6 +565,8 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template self.request.user.send_security_notice([ _('Your two-factor emergency codes have been regenerated.') ]) + self.request.user.update_session_token() + update_session_auth_hash(self.request, self.request.user) messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe ' 'place in case you lose access to your devices.')) return redirect(reverse('control:user.settings.2fa'))