Commit dbff8cdd authored by Antoine Bernard's avatar Antoine Bernard

[pw_reset] Ajout formulaire de demande et confirmation

parent 30e1326e
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 Antoine BERNARD
# Authors: Antoine BERNARD <abernard@crans.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Formulaire de l'application password_reset
"""
#: Import des formulaires
from django import forms
#: i18n de l'intranet
from django.utils.translation import ugettext_lazy as _
#: Fonctions de communication avec la base LDAP
from lc_ldap import shortcuts
class EmailForm(forms.Form):
"""
Formulaire de demande d'une adresse e-mail.
"""
email = forms.EmailField(
label=_(u'Adresse e-mail'),
max_length=254,
required=True
)
def get_user(self):
"""
Renvoie l'objet LDAP à partir de l'e-mail
"""
try:
email = self.cleaned_data['email']
conn = shortcuts.lc_ldap_readonly()
# On cherches les objets LDAP tels que :
# * le mail est dans le champ `mail` ou `mailExt`
# * et il a droit de se connecter
# * et c'est un adhérent
# * et il a un compte crans
res = conn.search(
u"(&(|(mail=%s)(mailExt=%s))\
(!(shadowExpire=0))(aid=*)(uid=*))"
% (email, email)
)
return res[0]
except IndexError:
return []
class UsernameForm(forms.Form):
"""
Formulaire de demande d'un login Cr@ns.
"""
username = forms.CharField(
label=_(u"Nom d'utilisateur Cr@ns"),
max_length=254,
required=True
)
def get_user(self):
"""
Renvoie l'objet LDAP à partir du login
"""
try:
login = self.cleaned_data['username']
conn = shortcuts.lc_ldap_readonly()
# On cherches les objets LDAP tels que :
# * le login est dans le champ `uid`
# * et il a droit de se connecter
# * et c'est un adhérent
res = conn.search(u"(&(uid=%s)(!(shadowExpire=0))(aid=*))" % login)
return res[0]
except IndexError:
return []
{% extends "template.html" %}
{% load i18n %}
{% block head %}
{% endblock head %}
{% block title %}{{ block.super }} : {% trans "Réinitialisation du mot de passe" %}{% endblock %}
{% block h1 %}{% trans "Mail envoyé" %}{% endblock %}
{% load staticfiles %}
{% block content %}
<p>{% trans "Un mail a été envoyé à l'adresse de contact enregistré dans notre base de donnée si elle existe." %}</p>
<p>{% trans "Pensez à vérifier votre dossier spam." %}</p>
{% endblock %}
{% extends "template.html" %}
{% load i18n %}
{% block head %}
{% endblock head %}
{% block title %}{{ block.super }} : {% trans "Réinitialisation du mot de passe" %}{% endblock %}
{% block h1 %}{% trans "Réinitialisation du mot de passe" %}{% endblock %}
{% load staticfiles %}
{% block content %}
<p>{% trans "Pour réinitialiser votre mot de passe, entrez au choix, votre nom d'utilisateur ou votre mèl de secours." %}</p>
{% if lform %}
<form action="" method="post" class="reset_pw">{% csrf_token %}
<h2>{% trans "Par login" %}</h2>
<div class="row">
{{ lform.as_table }}
<input type="submit" value={% trans "Envoyer"%} name="ResetByLogin">
</div>
{% endif %}
{% if eform %}
</form>
<form action="" method="post" class="reset_pw">{% csrf_token %}
<h2>{% trans "Par adresse e-mail" %}</h2>
<div class="row">
{{ eform.as_table }}
<input type="submit" value={% trans "Envoyer"%} name="ResetByMail">
</div>
</form>
{% endif %}
{% endblock %}
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 Antoine BERNARD
# Authors: Antoine BERNARD <abernard@crans.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Urls de l'application password_reset
"""
from django.conf.urls import patterns, url
import views
urlpatterns = [
url(r'^$',
views.password_reset,
name="password_reset",),
url(r'^done/$',
views.password_reset_done,
name="password_reset_done"),
]
# -*- encoding: utf-8 -*-
"""
Views de l'application password_reset
"""
#: Import de settings de l'intranet
from intranet import settings
#: Import de fonctions utiles
from django.shortcuts import render, redirect
from django.core.urlresolvers import reverse_lazy
#: Import des views Django
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormView
#: i18n
from django.utils.translation import ugettext_lazy as _
#: Pour stocker des messages dans la session courante
from django.contrib import messages
#: Import des formulaires
from password_reset.forms import EmailForm, UsernameForm
#: Tokenization
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, int_to_base36
from django.utils.crypto import salted_hmac
#Copié depuis Django1.11
class MyResetTokenGenerator(PasswordResetTokenGenerator):
"""
Classe pour la génération d'un token à partir de la
base LDAP.
"""
key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
secret = settings.SECRET_KEY
def _make_token_with_timestamp(self, user, timestamp):
ts_b36 = int_to_base36(timestamp)
hash = salted_hmac(
self.key_salt,
self._make_hash_value(user, timestamp),
secret=self.secret,
).hexdigest()[::2]
return "%s-%s" % (ts_b36, hash)
def _make_hash_value(self, user, timestamp):
# On surchage la méthode pour rechercher les informations
# dans la base LDAP
login_timestamp = user['derniereConnexion'][0] or ''
return str(user["uidNumber"][0]) + str(user['userPassword'][0])\
+ str(login_timestamp) + str(timestamp)
my_token_generator = MyResetTokenGenerator()
class PasswordResetFormView(FormView):
template_name = "password_reset/password_reset_form.html"
success_url = reverse_lazy("password_reset:password_reset_done")
lform = UsernameForm
eform = EmailForm
token_generator = my_token_generator
def form_valid(self, request, form, *args, **kwargs):
user = form.get_user()
if user:
from gestion import mail
c = {
'from' : 'cableurs@crans.org',
'to': str((user["mailExt"] or user["mail"])[0]),
'name': str(user["cn"][0]),
'protocol': 'https',
'domain': request.META['HTTP_HOST'],
'uid': urlsafe_base64_encode(force_bytes(user["aid"][0])),
'token': self.token_generator.make_token(user),
'username' : str(user["uid"][0]),
'mailer' : u"reset_pw",
}
# On utilise la class ServerConnection() dans /usr/scripts
with mail.ServerConnection() as conn_smtp:
conn_smtp.send_template('password_reset', c)
return super(PasswordResetFormView, self).form_valid(form, *args, **kwargs)
def post(self, request, *args, **kwargs):
req = request.POST
# On récupere et on rempli le formulaire approrié
form = self.lform(req) if 'username' in req else self.eform(req)
if form.is_valid():
return self.form_valid(request, form, **kwargs)
else:
return redirect(self.success_url)
def get(self, request, next=None, *args, **kwargs):
# On vérifie que l'utilisateur n'est pas connecté.
# Sinon, il ne chercherait pas à réinitialiser son mot de passe.
if not request.user.is_anonymous():
messages.error(request, _(u"Vous devez vous déconnecter pour accédez à cette page"))
return redirect(reverse_lazy('index'))
return render(
request,
self.template_name,
{'lform': self.lform,
'eform': self.eform,
},
)
password_reset = PasswordResetFormView.as_view()
class PasswordResetDoneView(DetailView):
"""
Vue pour la confirmation d'instructions de réinitialisation
de mot de passe.
On confirme toujours sinon quoi on donnerait des informations sur l'existence
de login ou d'adresses e-mails.
"""
def get(self, request, next=None, *args, **kwargs):
if not request.user.is_anonymous():
messages.error(request, _(u"Vous devez vous déconnecter pour accédez à cette page"))
return redirect(reverse_lazy('index'))
return render(
request,
"password_reset/password_reset_done.html",
)
password_reset_done = PasswordResetDoneView.as_view()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment