authentic/src/authentic2/forms/passwords.py

134 lines
5.3 KiB
Python

# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from collections import OrderedDict
from django import forms
from django.contrib.auth import forms as auth_forms
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.forms import Form
from django.utils.translation import ugettext_lazy as _
from .. import app_settings, hooks, models, utils
from ..backends import get_user_queryset
from .fields import CheckPasswordField, NewPasswordField, PasswordField
from .utils import NextUrlFormMixin
logger = logging.getLogger(__name__)
class PasswordResetForm(forms.Form):
next_url = forms.CharField(widget=forms.HiddenInput, required=False)
email = forms.CharField(label=_("Email"), max_length=254)
def save(self):
"""
Generates a one-use only link for resetting password and sends to the
user.
"""
email = self.cleaned_data["email"].strip()
users = get_user_queryset().filter(Q(email__iexact=email) | Q(username__iexact=email))
active_users = users.filter(is_active=True)
for user in active_users:
# we don't set the password to a random string, as some users should not have
# a password
set_random_password = user.has_usable_password() and app_settings.A2_SET_RANDOM_PASSWORD_ON_RESET
utils.send_password_reset_mail(
user, set_random_password=set_random_password, next_url=self.cleaned_data.get('next_url')
)
for user in users.filter(is_active=False):
logger.info('password reset failed for user "%r": account is disabled', user)
utils.send_templated_mail(user, ['authentic2/password_reset_refused'])
if not users.exists():
logger.info(u'password reset request for "%s", no user found', email)
ctx = {'registration_url': utils.make_url('registration_register', absolute=True)}
utils.send_templated_mail(email, ['authentic2/password_reset_no_account'], context=ctx)
hooks.call_hooks('event', name='password-reset', email=email, users=active_users)
class PasswordResetMixin(Form):
"""Remove all password reset object for the current user when password is
successfully changed."""
def save(self, commit=True):
ret = super(PasswordResetMixin, self).save(commit=commit)
if commit:
models.PasswordReset.objects.filter(user=self.user).delete()
else:
old_save = self.user.save
def save(*args, **kwargs):
ret = old_save(*args, **kwargs)
models.PasswordReset.objects.filter(user=self.user).delete()
return ret
self.user.save = save
return ret
class NotifyOfPasswordChange(object):
def save(self, commit=True):
user = super(NotifyOfPasswordChange, self).save(commit=commit)
if user.email:
ctx = {
'user': user,
'password': self.cleaned_data['new_password1'],
}
utils.send_templated_mail(user, "authentic2/password_change", ctx)
return user
class SetPasswordForm(NotifyOfPasswordChange, PasswordResetMixin, auth_forms.SetPasswordForm):
new_password1 = NewPasswordField(label=_("New password"))
new_password2 = CheckPasswordField(label=_("New password confirmation"))
def clean_new_password1(self):
new_password1 = self.cleaned_data.get('new_password1')
if new_password1 and self.user.check_password(new_password1):
raise ValidationError(_('New password must differ from old password'))
return new_password1
class PasswordChangeForm(
NotifyOfPasswordChange, NextUrlFormMixin, PasswordResetMixin, auth_forms.PasswordChangeForm
):
old_password = PasswordField(label=_('Old password'))
new_password1 = NewPasswordField(label=_('New password'))
new_password2 = CheckPasswordField(label=_("New password confirmation"))
def clean_new_password1(self):
new_password1 = self.cleaned_data.get('new_password1')
old_password = self.cleaned_data.get('old_password')
if new_password1 and new_password1 == old_password:
raise ValidationError(_('New password must differ from old password'))
return new_password1
# make old_password the first field
new_base_fields = OrderedDict()
for k in ['old_password', 'new_password1', 'new_password2']:
new_base_fields[k] = PasswordChangeForm.base_fields[k]
for k in PasswordChangeForm.base_fields:
if k not in ['old_password', 'new_password1', 'new_password2']:
new_base_fields[k] = PasswordChangeForm.base_fields[k]
PasswordChangeForm.base_fields = new_base_fields