diff --git a/src/pretix/base/forms/user.py b/src/pretix/base/forms/user.py
index ee93e8a523..9cecbe05e9 100644
--- a/src/pretix/base/forms/user.py
+++ b/src/pretix/base/forms/user.py
@@ -55,6 +55,7 @@ class UserSettingsForm(forms.ModelForm):
'pw_current_wrong': _("The current password you entered was not correct."),
'pw_mismatch': _("Please enter the same password twice"),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
+ 'pw_equal': _("Please choose a password different to your current one.")
}
old_pw = forms.CharField(max_length=255,
@@ -158,6 +159,12 @@ class UserSettingsForm(forms.ModelForm):
code='pw_current'
)
+ if password1 and password1 == old_pw:
+ raise forms.ValidationError(
+ self.error_messages['pw_equal'],
+ code='pw_equal'
+ )
+
if password1:
self.instance.set_password(password1)
diff --git a/src/pretix/base/migrations/0202_user_needs_password_change.py b/src/pretix/base/migrations/0202_user_needs_password_change.py
new file mode 100644
index 0000000000..164ea0f7f2
--- /dev/null
+++ b/src/pretix/base/migrations/0202_user_needs_password_change.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.9 on 2021-11-04 13:05
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixbase', '0201_invoiceline_event_location'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='user',
+ name='needs_password_change',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/src/pretix/base/models/auth.py b/src/pretix/base/models/auth.py
index 1c126df228..10f7a84d34 100644
--- a/src/pretix/base/models/auth.py
+++ b/src/pretix/base/models/auth.py
@@ -113,6 +113,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:type date_joined: datetime
:param locale: The user's preferred locale code.
:type locale: str
+ :param needs_password_change: Whether this user's password needs to be changed.
+ :type needs_password_change: bool
:param timezone: The user's preferred timezone.
:type timezone: str
"""
@@ -130,6 +132,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
verbose_name=_('Is site admin'))
date_joined = models.DateTimeField(auto_now_add=True,
verbose_name=_('Date joined'))
+ needs_password_change = models.BooleanField(default=False,
+ verbose_name=_('Force user to select a new password'))
locale = models.CharField(max_length=50,
choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE,
diff --git a/src/pretix/control/forms/users.py b/src/pretix/control/forms/users.py
index 6e133d3327..f1cf45e3b9 100644
--- a/src/pretix/control/forms/users.py
+++ b/src/pretix/control/forms/users.py
@@ -70,6 +70,7 @@ class UserEditForm(forms.ModelForm):
'require_2fa',
'is_active',
'is_staff',
+ 'needs_password_change',
'last_login'
]
diff --git a/src/pretix/control/middleware.py b/src/pretix/control/middleware.py
index cc30e3faea..7cdcda66cd 100644
--- a/src/pretix/control/middleware.py
+++ b/src/pretix/control/middleware.py
@@ -69,6 +69,11 @@ class PermissionMiddleware:
"user.settings.notifications.off",
)
+ EXCEPTIONS_FORCED_PW_CHANGE = (
+ "user.settings",
+ "auth.logout"
+ )
+
EXCEPTIONS_2FA = (
"user.settings.2fa",
"user.settings.2fa.add",
@@ -130,6 +135,9 @@ class PermissionMiddleware:
if url_name not in ('user.reauth', 'auth.logout'):
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
+ if request.user.needs_password_change and url_name not in self.EXCEPTIONS_FORCED_PW_CHANGE:
+ return redirect(reverse('control:user.settings') + '?next=' + quote(request.get_full_path()))
+
if not request.user.require_2fa and settings.PRETIX_OBLIGATORY_2FA \
and url_name not in self.EXCEPTIONS_2FA:
return redirect(reverse('control:user.settings.2fa'))
diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html
index 36413577b3..d0b1f621e2 100644
--- a/src/pretix/control/templates/pretixcontrol/base.html
+++ b/src/pretix/control/templates/pretixcontrol/base.html
@@ -429,6 +429,15 @@
{% endif %}
+ {% if request.user.needs_password_change %}
+
+ {% blocktrans trimmed %}
+ For security reasons, please change your password before you continue. Afterwards you
+ will be redirected to your original destination.
+ {% endblocktrans %}
+