From: Yoan Le Clanche Date: Tue, 6 Jun 2023 08:36:24 +0000 (+0200) Subject: WIP password X-Git-Tag: 2.9.0~63^2~2^2^2 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=53e11b51b71a74eeea7eeaebc439f0a895542707;p=teleforma.git WIP password --- diff --git a/teleforma/templates/registration/password_reset_email.html b/teleforma/templates/registration/password_reset_email.html index 715e4fa9..5c83e147 100644 --- a/teleforma/templates/registration/password_reset_email.html +++ b/teleforma/templates/registration/password_reset_email.html @@ -1,13 +1,13 @@ {% load teleforma_tags %}{% load i18n %}{% autoescape off %}{% trans "Hello" %}, {% trans "You're receiving this e-mail because you requested a password reset" %} -{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}. +{% if logins|length > 1 %}pour vos comptes {% for login in logins %}{{login}}{% if not forloop.last %}, {% endif %}{% endfor %}.{% else %}pour votre compte {{logins.0}}. +{% endif %} {% trans "Please go to the following page and choose a new password:" %} {% block reset_link %} {{ protocol }}://{{ domain }}{% url 'teleforma-password-reset-confirm' uidb64=uid token=token %} {% endblock %} -{% trans "Your username, in case you've forgotten:" %} {{ user.username }} {% trans "Best regards" %}, {% trans "The site administrator" %} {% trans "of the" %} {% organization %} diff --git a/teleforma/templates/registration/password_reset_form.html b/teleforma/templates/registration/password_reset_form.html index a297f988..22380f9f 100644 --- a/teleforma/templates/registration/password_reset_form.html +++ b/teleforma/templates/registration/password_reset_form.html @@ -4,10 +4,11 @@ {% block title %}
{% trans "Password reset" %}{% endblock %} {% block content %} -

{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}

+

Vous avez oubliez votre mot de passe ? Veuillez saisir votre adresse e-mail ou votre identifiant de connexion et nous vous enverrons un email avec les instructions pour définir un nouveau mot de passe. +

{% csrf_token %} {{ form.email.errors }} -

{{ form.email }} {% trans 'Reset my password' %}

+

{{ form.email }} {% trans 'Reset my password' %}

{% endblock %} diff --git a/teleforma/urls.py b/teleforma/urls.py index cbb484aa..3f11100f 100644 --- a/teleforma/urls.py +++ b/teleforma/urls.py @@ -43,13 +43,13 @@ from django.contrib.auth.views import (LoginView, LogoutView, PasswordContextMixin, PasswordResetCompleteView, PasswordResetConfirmView, - PasswordResetDoneView, - PasswordResetView) + PasswordResetDoneView) from django.views.generic.base import TemplateView from django.views.decorators.cache import cache_page from jsonrpc import jsonrpc_site from teleforma.views.home import HomeView +from teleforma.views.password import TFPasswordResetConfirmView, TFPasswordResetView from .views.appointment import Appointments, cancel_appointment from .views.core import (ChatMessageView, ConferenceListView, ConferenceView, CourseListView, @@ -121,11 +121,11 @@ urlpatterns = [ url(r'^accounts/password_change_done/$', PasswordChangeDoneView.as_view( template_name='registration/password_change_done.html'), name="password_change_done"), - url(r'^accounts/password_reset/$', PasswordResetView.as_view(template_name='registration/password_reset_form.html', + url(r'^accounts/password_reset/$', TFPasswordResetView.as_view(template_name='registration/password_reset_form.html', email_template_name='registration/password_reset_email.html'), name="teleforma-password-reset"), url(r'^accounts/password_reset_done/$', PasswordResetDoneView.as_view( template_name='registration/password_reset_done.html'), name="password_reset_done"), - path('accounts/password_reset_confirm///', PasswordResetConfirmView.as_view( + path('accounts/password_reset_confirm///', TFPasswordResetConfirmView.as_view( template_name='registration/password_reset_confirm.html'), name="teleforma-password-reset-confirm"), url(r'^accounts/password_reset_complete/$', PasswordResetCompleteView.as_view(template_name='registration/password_reset_complete.html'), name="password_reset_complete"), diff --git a/teleforma/views/password.py b/teleforma/views/password.py new file mode 100644 index 00000000..af7cdaf5 --- /dev/null +++ b/teleforma/views/password.py @@ -0,0 +1,170 @@ +from django import forms +from django.contrib.auth import get_user_model +from django.contrib.auth import login as auth_login +from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm +from django.contrib.auth.models import User +from django.contrib.auth.tokens import default_token_generator +from django.contrib.auth.views import (INTERNAL_RESET_SESSION_TOKEN, + PasswordResetConfirmView, + PasswordResetView) +from django.contrib.sites.shortcuts import get_current_site +from django.db.models import Q +from django.http import HttpResponseRedirect +from django.urls import reverse_lazy +from django.utils.decorators import method_decorator +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode +from django.utils.translation import gettext_lazy as _ +from django.views.decorators.cache import never_cache +from django.views.decorators.debug import sensitive_post_parameters +from django.views.generic.edit import FormView + +UserModel = get_user_model() + +class TFPasswordResetForm(PasswordResetForm): + email = forms.CharField( + label="Email ou login", + max_length=254, + ) + + def get_users(self, email): + """Given an email, return matching user(s) who should receive a reset. + + This allows subclasses to more easily customize the default policies + that prevent inactive users and users with unusable passwords from + resetting their password. + """ + return User.objects.filter(Q(is_active=True) & (Q(email=email) | Q(username=email))) + + def save(self, domain_override=None, + subject_template_name='registration/password_reset_subject.txt', + email_template_name='registration/password_reset_email.html', + use_https=False, token_generator=default_token_generator, + from_email=None, request=None, html_email_template_name=None, + extra_email_context=None): + """ + Generate a one-use only link for resetting password and send it to the + user. + """ + email = self.cleaned_data["email"] + if not domain_override: + current_site = get_current_site(request) + site_name = current_site.name + domain = current_site.domain + else: + site_name = domain = domain_override + email_field_name = UserModel.get_email_field_name() + logins = [username for username in self.get_users(email)] + users = self.get_users(email) + if users: + user = self.get_users(email)[0] + user_email = getattr(user, email_field_name) + context = { + 'email': user_email, + 'domain': domain, + 'site_name': site_name, + 'uid': urlsafe_base64_encode(force_bytes(user.email)), + 'user': user, + 'logins': logins, + 'token': token_generator.make_token(user), + 'protocol': 'https' if use_https else 'http', + **(extra_email_context or {}), + } + self.send_mail( + subject_template_name, email_template_name, context, from_email, + user_email, html_email_template_name=html_email_template_name, + ) + +class TFPasswordResetView(PasswordResetView): + form_class = TFPasswordResetForm + + + +class TFSetPasswordForm(SetPasswordForm): + def __init__(self, users, *args, **kwargs): + self.users = users + super().__init__(*args, **kwargs) + + def save(self, commit=True): + password = self.cleaned_data["new_password1"] + for user in self.users: + user.set_password(password) + if commit: + user.save() + return self.users + + +class TFPasswordResetConfirmView(PasswordResetConfirmView): + form_class = TFSetPasswordForm + post_reset_login = False + post_reset_login_backend = None + reset_url_token = 'set-password' + success_url = reverse_lazy('password_reset_complete') + template_name = 'registration/password_reset_confirm.html' + title = _('Enter new password') + token_generator = default_token_generator + + @method_decorator(sensitive_post_parameters()) + @method_decorator(never_cache) + def dispatch(self, *args, **kwargs): + assert 'uidb64' in kwargs and 'token' in kwargs + + self.validlink = False + self.user = None + self.users = self.get_users(kwargs['uidb64']) + token = kwargs['token'] + + if self.users: + if token == self.reset_url_token: + session_token = self.request.session.get(INTERNAL_RESET_SESSION_TOKEN) + + check = False + for user in self.users: + check = self.token_generator.check_token(user, session_token) + if check: + break + + if check: + # If the token is valid, display the password reset form. + self.validlink = True + return super(FormView, self).dispatch(*args, **kwargs) + else: + check = False + for user in self.users: + check = self.token_generator.check_token(user, token) + if check: + break + if check: + # Store the token in the session and redirect to the + # password reset form at a URL without the token. That + # avoids the possibility of leaking the token in the + # HTTP Referer header. + self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token + redirect_url = self.request.path.replace(token, self.reset_url_token) + return HttpResponseRedirect(redirect_url) + + # Display the "Password reset unsuccessful" page. + return self.render_to_response(self.get_context_data()) + + def get_users(self, uidb64): + try: + # urlsafe_base64_decode() decodes to bytestring + email = urlsafe_base64_decode(uidb64).decode() + users = User.objects.filter(email=email) + except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist, forms.ValidationError): + users = [] + return users + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['users'] = self.users + return kwargs + + def form_valid(self, form): + users = form.save() + # get last user + user = sorted(users, key=lambda u:u.date_joined)[0] + del self.request.session[INTERNAL_RESET_SESSION_TOKEN] + if self.post_reset_login: + auth_login(self.request, user, self.post_reset_login_backend) + return super(FormView, self).form_valid(form) \ No newline at end of file