From: Loic Baron Date: Fri, 10 Jan 2014 17:44:33 +0000 (+0100) Subject: Merge branch 'master' of ssh://git.onelab.eu/git/myslice X-Git-Tag: myslice-0.3-0~38 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=556bd356832001a26516da82eda588a023febe1e;hp=c3272ce22d726e69598349413eb7a7e982b027d8;p=unfold.git Merge branch 'master' of ssh://git.onelab.eu/git/myslice --- diff --git a/README b/README index 2a183a89..58078467 100644 --- a/README +++ b/README @@ -20,6 +20,8 @@ $ apt-get install python-django-south $ apt-get install python-django-south * init django +** when django prompts for creating an admin account, create it and +** keep the username and password safe $ ./manage.py syncdb $ ./manage.py migrate diff --git a/myslice/settings.py b/myslice/settings.py index 70b4a87a..f3bdb7b4 100644 --- a/myslice/settings.py +++ b/myslice/settings.py @@ -57,6 +57,18 @@ EMAIL_HOST = "localhost" EMAIL_PORT = 25 EMAIL_USE_TLS = False +# use the email for debugging purpose +# turn on debugging: +# python -m smtpd -n -c DebuggingServer localhost:1025 + +if DEBUG: + EMAIL_HOST = 'localhost' + EMAIL_PORT = 1025 + EMAIL_HOST_USER = '' + EMAIL_HOST_PASSWORD = '' + EMAIL_USE_TLS = False + DEFAULT_FROM_EMAIL = 'testing@example.com' + DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. @@ -198,7 +210,7 @@ INSTALLED_APPS = [ # managing database migrations 'south', # Uncomment the next line to enable the admin: - # 'django.contrib.admin', + 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'portal', @@ -239,7 +251,7 @@ LOGGING = { } } -AUTHENTICATION_BACKENDS = ( 'auth.manifoldbackend.ManifoldBackend', ) +AUTHENTICATION_BACKENDS = ( 'auth.manifoldbackend.ManifoldBackend','django.contrib.auth.backends.ModelBackend' ) ### the view to redirect malformed (i.e. with a wrong CSRF) incoming requests # without this setting django will return a 403 forbidden error, which is fine diff --git a/myslice/urls.py b/myslice/urls.py index 64197267..5e1547bd 100644 --- a/myslice/urls.py +++ b/myslice/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import patterns, include, url from django.conf import settings +from django.contrib import admin # Uncomment the next two lines to enable the admin: # from django.contrib import admin @@ -29,7 +30,7 @@ the_after_login_view=dashboard_view # where to redirect when login is required # might need another one ? the_login_view=home_view - +admin.autodiscover() urls = [ '', # Examples: @@ -38,7 +39,7 @@ urls = [ # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: - # url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', include(admin.site.urls)), # # default / view (r'^/?$', the_default_view), diff --git a/portal/accountview.py b/portal/accountview.py index 6f1fba0b..2057f241 100644 --- a/portal/accountview.py +++ b/portal/accountview.py @@ -74,7 +74,7 @@ class AccountView(LoginRequiredAutoLogoutView): pub_key_list = [] for platform_detail in platform_details: - if 'sfa' in platform_detail['gateway_type'] and platform_detail['disabled']==0: + if 'sfa' in platform_detail['gateway_type']: total_platform = platform_detail['platform'] total_platform_list.append(total_platform) @@ -285,7 +285,7 @@ def account_process(request): user_config['config']= '{"firstname":"' + edited_first_name + '", "lastname":"'+ edited_last_name + '", "authority": "Unknown Authority"}' user_params = {'config': user_config['config']} # updating config local:user in manifold - manifold_update_user(request,user_params) + manifold_update_user(request, request.user.email,user_params) # this will be depricated, we will show the success msg in same page # Redirect to same page with success message messages.success(request, 'Sucess: First Name and Last Name Updated.') @@ -298,7 +298,7 @@ def account_process(request): user_pass['password'] = edited_password #updating password in local:user user_params = { 'password': user_pass['password']} - manifold_update_user(request,user_params) + manifold_update_user(request,request.user.email,user_params) # return HttpResponse('Success: Password Changed!!') messages.success(request, 'Sucess: Password Updated.') return HttpResponseRedirect("/portal/account/") diff --git a/portal/actions.py b/portal/actions.py index 97b8ac81..dbdfdfa3 100644 --- a/portal/actions.py +++ b/portal/actions.py @@ -60,10 +60,10 @@ def manifold_add_user(request, user_params): result, = results return result['email'] -def manifold_update_user(request, user_params): +def manifold_update_user(request, email, user_params): # user_params: password, config e.g., - query = Query.update('local:user').filter_by('email', '==', request.user.email).set(user_params).select('email') - results = execute_query(request,query) + query = Query.update('local:user').filter_by('email', '==', email).set(user_params).select('email') + results = execute_admin_query(request,query) # NOTE: results remains empty and goes to Exception. However, it updates the manifold DB. # That's why I commented the exception part. -- Yasin #if not results: diff --git a/portal/django_passresetview.py b/portal/django_passresetview.py new file mode 100644 index 00000000..56f03ba8 --- /dev/null +++ b/portal/django_passresetview.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- +# +# portal/views.py: views for the portal application +# This file is part of the Manifold project. +# +# Author: +# Mohammed Yasin Rahman +# Copyright 2014, UPMC Sorbonne Universités / LIP6 +# +# 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, 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; see the file COPYING. If not, write to the Free Software +# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + + + + +""" +View Description: + +Allows a user to reset their password by generating a one-time use link that can be used to reset the password, and sending that link to the user's +registered email address. + +If the email address provided does not exist in the system, this view won't send an email, but the user won't receive any error message either. +This prevents information leaking to potential attackers. If you want to provide an error message in this case, you can subclass PasswordResetForm +and use the password_reset_form argument. + +Users flagged with an unusable password (see set_unusable_password() aren't allowed to request a password reset to prevent misuse when using an external +authentication source like LDAP. Note that they won't receive any error message since this would expose their account's existence but no mail will be sent either. + +More Detail: https://docs.djangoproject.com/en/dev/topics/auth/default/#topics-auth-creating-users +""" + + + +try: + from urllib.parse import urlparse, urlunparse +except ImportError: # Python 2 + from urlparse import urlparse, urlunparse + +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect, QueryDict +from django.template.response import TemplateResponse +from django.utils.http import base36_to_int, is_safe_url +from django.utils.translation import ugettext as _ +from django.shortcuts import resolve_url +from django.views.decorators.debug import sensitive_post_parameters +from django.views.decorators.cache import never_cache +from django.views.decorators.csrf import csrf_protect + +# Avoid shadowing the login() and logout() views below. +from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout, get_user_model +from django.contrib.auth.decorators import login_required +from portal.forms import PasswordResetForm, SetPasswordForm +from django.contrib.auth.tokens import default_token_generator +from django.contrib.sites.models import get_current_site +from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher + +## +import os.path, re +import json + +from random import choice + +from django.core.mail import send_mail +from django.contrib import messages +from django.views.generic import View +from django.shortcuts import render +from django.http import HttpResponse, HttpResponseRedirect + +from unfold.loginrequired import FreeAccessView +from ui.topmenu import topmenu_items_live + +from manifold.manifoldapi import execute_admin_query +from manifold.core.query import Query +from portal.actions import manifold_update_user + +from portal.forms import PassResetForm +from portal.actions import manifold_update_user + + + +# 4 views for password reset: +# - password_reset sends the mail +# - password_reset_done shows a success message for the above +# - password_reset_confirm checks the link the user clicked and +# prompts for a new password +# - password_reset_complete shows a success message for the above + +@csrf_protect +def password_reset(request, is_admin_site=False, + template_name='registration/password_reset_form.html', + email_template_name='registration/password_reset_email.html', + subject_template_name='registration/password_reset_subject.txt', + password_reset_form=PasswordResetForm, + token_generator=default_token_generator, + post_reset_redirect=None, + from_email=None, + current_app=None, + extra_context=None): + if post_reset_redirect is None: + post_reset_redirect = reverse('portal.django_passresetview.password_reset_done') + if request.method == "POST": + form = password_reset_form(request.POST) + if form.is_valid(): + + ### email check in manifold DB ### + email = form.cleaned_data['email'] # email inserted on the form + user_query = Query().get('local:user').select('user_id','email') + user_details = execute_admin_query(request, user_query) + flag = 0 + for user_detail in user_details: + if user_detail['email']==email: + flag = 1 + break + + if flag == 0: + messages.error(request, 'Sorry, this email is not registered.') + return render(request, 'registration/password_reset_form.html', { + 'form': form, + }) + ### end of email check in manifold ### + + opts = { + 'use_https': request.is_secure(), + 'token_generator': token_generator, + 'from_email': from_email, + 'email_template_name': email_template_name, + 'subject_template_name': subject_template_name, + 'request': request, + } + if is_admin_site: + opts = dict(opts, domain_override=request.get_host()) + form.save(**opts) + return HttpResponseRedirect(post_reset_redirect) + else: + form = password_reset_form() + context = { + 'form': form, + } + if extra_context is not None: + context.update(extra_context) + return TemplateResponse(request, template_name, context, + current_app=current_app) + + +def password_reset_done(request, + template_name='registration/password_reset_done.html', + current_app=None, extra_context=None): + context = {} + if extra_context is not None: + context.update(extra_context) + return TemplateResponse(request, template_name, context, + current_app=current_app) + + +# Doesn't need csrf_protect since no-one can guess the URL +@sensitive_post_parameters() +@never_cache +def password_reset_confirm(request, uidb36=None, token=None, + template_name='registration/password_reset_confirm.html', + token_generator=default_token_generator, + set_password_form=SetPasswordForm, + post_reset_redirect=None, + current_app=None, extra_context=None): + """ + View that checks the hash in a password reset link and presents a + form for entering a new password. + """ + UserModel = get_user_model() + assert uidb36 is not None and token is not None # checked by URLconf + if post_reset_redirect is None: + post_reset_redirect = reverse('portal.django_passresetview.password_reset_complete') + try: + uid_int = base36_to_int(uidb36) + user = UserModel._default_manager.get(pk=uid_int) + except (ValueError, OverflowError, UserModel.DoesNotExist): + user = None + + if user is not None and token_generator.check_token(user, token): + validlink = True + if request.method == 'POST': + form = set_password_form(user, request.POST) + if form.is_valid(): + + ### manifold pass update ### + #password = form.cleaned_data('password1') + password=request.POST['new_password1'] + user_query = Query().get('local:user').select('user_id','email','password') + user_details = execute_admin_query(request, user_query) + for user_detail in user_details: + if user_detail['email'] == user.email: + user_detail['password'] = password + #updating password in local:user + user_params = { 'password': user_detail['password']} + manifold_update_user(request,user.email,user_params) + ### end of manifold pass update ### + + + form.save() + return HttpResponseRedirect(post_reset_redirect) + else: + form = set_password_form(None) + else: + validlink = False + form = None + context = { + 'form': form, + 'validlink': validlink, + } + if extra_context is not None: + context.update(extra_context) + return TemplateResponse(request, template_name, context, + current_app=current_app) + + +def password_reset_complete(request, + template_name='registration/password_reset_complete.html', + current_app=None, extra_context=None): + context = { + 'login_url': resolve_url(settings.LOGIN_URL) + } + if extra_context is not None: + context.update(extra_context) + return TemplateResponse(request, template_name, context, + current_app=current_app) + + diff --git a/portal/forms.py b/portal/forms.py index 848f1c3e..df5c1a67 100644 --- a/portal/forms.py +++ b/portal/forms.py @@ -26,6 +26,15 @@ from portal.models import PendingUser, PendingSlice #from crispy_forms.helper import FormHelper #from crispy_forms.layout import Submit from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.tokens import default_token_generator +from django.contrib.auth import authenticate, get_user_model +from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher +from django.contrib.sites.models import get_current_site +from django.utils.http import int_to_base36 +from django.template import loader + + + # xxx painful, but... # bootstrap3 requires the fields to be tagged class='form-control' @@ -110,4 +119,96 @@ class SliceRequestForm(forms.Form): widget = forms.Select(attrs={'class':'form-control'}), choices = authority_hrn, help_text = "An authority responsible for vetting your slice") + + +class PasswordResetForm(forms.Form): + error_messages = { + 'unknown': _("That email address doesn't have an associated " + "user account. Are you sure you've registered?"), + 'unusable': _("The user account associated with this email " + "address cannot reset the password."), + } + email = forms.EmailField(label=_("Email"), max_length=254) + + def clean_email(self): + """ + Validates that an active user exists with the given email address. + """ + UserModel = get_user_model() + email = self.cleaned_data["email"] + self.users_cache = UserModel._default_manager.filter(email__iexact=email) + if not len(self.users_cache): + raise forms.ValidationError(self.error_messages['unknown']) + if not any(user.is_active for user in self.users_cache): + # none of the filtered users are active + raise forms.ValidationError(self.error_messages['unknown']) + if any((user.password == UNUSABLE_PASSWORD) + for user in self.users_cache): + raise forms.ValidationError(self.error_messages['unusable']) + return 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): + """ + Generates a one-use only link for resetting password and sends to the + user. + """ + from django.core.mail import send_mail + for user in self.users_cache: + 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 + c = { + 'email': user.email, + 'domain': domain, + 'site_name': site_name, + 'uid': int_to_base36(user.pk), + 'user': user, + 'token': token_generator.make_token(user), + 'protocol': use_https and 'https' or 'http', + } + subject = loader.render_to_string(subject_template_name, c) + # Email subject *must not* contain newlines + subject = ''.join(subject.splitlines()) + email = loader.render_to_string(email_template_name, c) + send_mail(subject, email, from_email, [user.email]) + + +class SetPasswordForm(forms.Form): + """ + A form that lets a user change set his/her password without entering the + old password + """ + error_messages = { + 'password_mismatch': _("The two password fields didn't match."), + } + new_password1 = forms.CharField(label=_("New password"), + widget=forms.PasswordInput) + new_password2 = forms.CharField(label=_("New password confirmation"), + widget=forms.PasswordInput) + + def __init__(self, user, *args, **kwargs): + self.user = user + super(SetPasswordForm, self).__init__(*args, **kwargs) + + def clean_new_password2(self): + password1 = self.cleaned_data.get('new_password1') + password2 = self.cleaned_data.get('new_password2') + if password1 and password2: + if password1 != password2: + raise forms.ValidationError( + self.error_messages['password_mismatch']) + return password2 + + def save(self, commit=True): + self.user.set_password(self.cleaned_data['new_password1']) + if commit: + self.user.save() + return self.user diff --git a/portal/passresetview.py b/portal/passresetview.py deleted file mode 100644 index c9d50dcb..00000000 --- a/portal/passresetview.py +++ /dev/null @@ -1,47 +0,0 @@ -import os.path, re -import json - -from random import choice - -from django.core.mail import send_mail - -from django.views.generic import View -from django.shortcuts import render - -from unfold.loginrequired import FreeAccessView -from ui.topmenu import topmenu_items_live - -from manifold.manifoldapi import execute_admin_query -from manifold.core.query import Query - -from portal.forms import PassResetForm -from portal.actions import manifold_update_user - - -class PassResetView (FreeAccessView): - def post (self, request): - form = PassResetForm(request.POST) # A form bound to the POST data - if form.is_valid(): # All validation rules pass - # Process the data in form.cleaned_data - email = form.cleaned_data['email'] # email of the user - - sender = 'support@myslice.info' - #recipients = authority_get_pi_emails(authority_hrn) - recipients = [email ] - pass_list = ['demo', 'test', 'abcdef'] - reset_pass = choice (pass_list) - msg = "Your password has been reset to: %s"% reset_pass + "\n\n Please use this temporary password to login and reset your password from MyAccount page." - print "test ", msg - send_mail("Onelab Portal: Password reset request", msg, sender, recipients) - return render(request,'pass_reset_sent.html') # Redirect after POST - else: - return self._display (request, form) - - def get (self, request): - return self._display (request, PassResetForm()) # A fresh unbound form - - def _display (self, request, form): - return render(request, 'pass_reset.html', { - 'form': form, - }) - diff --git a/portal/registrationview.py b/portal/registrationview.py index 6db03df1..62c09347 100644 --- a/portal/registrationview.py +++ b/portal/registrationview.py @@ -2,7 +2,7 @@ import os.path, re import json from django.core.mail import send_mail - +from django.contrib.auth.models import User from django.views.generic import View from django.template.loader import render_to_string from django.shortcuts import render @@ -108,6 +108,8 @@ class RegistrationView (FreeAccessView): #keypair = re.sub("\n", "\\n", keypair) #keypair = keypair.rstrip('\r\n') #keypair = ''.join(keypair.split()) + #for sending email: removing existing double qoute + public_key = public_key.replace('"', ''); else: up_file = request.FILES['user_public_key'] file_content = up_file.read() @@ -122,6 +124,7 @@ class RegistrationView (FreeAccessView): auth_type = 'user' # for sending email public_key = file_content + public_key = ''.join(public_key.split()) else: errors.append('Please upload a valid RSA public key [.txt or .pub].') @@ -141,6 +144,8 @@ class RegistrationView (FreeAccessView): keypair = keypair, ) b.save() + # saves the user to django auth_user table [needed for password reset] + user = User.objects.create_user(reg_fname, reg_email, request.POST['password']) #creating user to manifold local:user config = '{"firstname":"'+ reg_fname + '", "lastname":"'+ reg_lname + '", "authority":"'+ reg_auth + '"}' user_params = {'email': reg_email, 'password': request.POST['password'], 'config': config} @@ -157,7 +162,7 @@ class RegistrationView (FreeAccessView): 'authority_hrn' : reg_auth, 'email' : reg_email, 'user_hrn' : user_hrn, - 'keypair' : 'Public Key :' + public_key, + 'keypair' : 'Public Key: ' + public_key, 'cc_myself' : True # form.cleaned_data['cc_myself'] } recipients = authority_get_pi_emails(request,reg_auth) diff --git a/portal/templates/pass_reset.html b/portal/templates/pass_reset.html deleted file mode 100644 index 8fa828f1..00000000 --- a/portal/templates/pass_reset.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "layout-unfold1.html" %} - -{% block head %} -{{ wizard.form.media }} -{% endblock %} - -{% block unfold_main %} - - -
-

Onelab Password Reset

-
- - -
-
{% csrf_token %} -
- {% for field in form %} -
- -
{{ field.errors }} {{ field }}

{{ field.help_text }}

-
- {% endfor %} - -
-
-
-{% endblock %} - diff --git a/portal/templates/pass_reset_sent.html b/portal/templates/pass_reset_sent.html deleted file mode 100644 index fbdf3047..00000000 --- a/portal/templates/pass_reset_sent.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "layout-unfold1.html" %} - -{% block unfold_main %} - -

Under Construction.

-

Please Contact Support for password recovery.

- -{% endblock %} - diff --git a/portal/urls.py b/portal/urls.py index 87ff1e1e..bcd56e5f 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -37,7 +37,7 @@ from portal.passresetview import PassResetView # hopefully these should move in dedicated source files too from portal.views import PresViewView, pres_view_static, pres_view_methods, pres_view_animation from portal.views import ValidatePendingView - +from portal.django_passresetview import password_reset, password_reset_done, password_reset_confirm, password_reset_complete # DEPRECATED #named_register_forms = ( # DEPRECATED # ("step1", RegisterUserForm), @@ -66,7 +66,7 @@ urlpatterns = patterns('', url(r'^account/account_process/?$', account_process), url(r'^register/?$', RegistrationView.as_view(), name='registration'), url(r'^contact/?$', ContactView.as_view(), name='contact'), - url(r'^pass_reset/?$', PassResetView.as_view(), name='pass_rest'), + #url(r'^pass_reset/?$', PassResetView.as_view(), name='pass_rest'), # Slice request url(r'^slice_request/?$', SliceRequestView.as_view(), name='slice_request'), # Validate pending requests @@ -82,6 +82,18 @@ urlpatterns = patterns('', #url(r'^slice/request/?$', views.slice_request, name='slice_request'), # Slice confirmation #url(r'^slice/validate/?$', views.slice_validate, name='slice_validate'), + url(r'^pass_reset/$', + 'portal.django_passresetview.password_reset', + {'post_reset_redirect' : '/portal/password/reset/done/'}), + (r'^password/reset/done/$', + 'portal.django_passresetview.password_reset_done'), + (r'^password/reset/(?P[0-9A-Za-z]+)-(?P.+)/$', + 'portal.django_passresetview.password_reset_confirm', + {'post_reset_redirect' : '/portal/password/done/'}), + (r'^password/done/$', + 'portal.django_passresetview.password_reset_complete'), + # ... + ) # (r'^accounts/', include('registration.backends.default.urls')), @@ -89,4 +101,4 @@ urlpatterns = patterns('', # DEPRECATED # url(r'^$', views.index, name='index'), # DEPRECATED # url(r"^registerwizard/(?P[-\w]+)/$", register_wizard, # DEPRECATED # name="register_wizard_step"), -# DEPRECATED # url(r"^registerwizard/$", register_wizard, name="register_wizard") +# DEPRECATED # url(r"^registerwizard/$", regster_wizard, name="register_wizard") diff --git a/ui/templates/registration/password_reset_complete.html b/ui/templates/registration/password_reset_complete.html new file mode 100644 index 00000000..63ce14e6 --- /dev/null +++ b/ui/templates/registration/password_reset_complete.html @@ -0,0 +1,18 @@ +{% extends "layout-unfold1.html" %} +{% load i18n %} + +{% block unfold_main %} + + +{% block content %} + +

{% trans 'OneLab secured Password reset wizard' %}

+

{% trans 'Password reset successful' %}

+ +

{% trans "Your password has been set. You may go ahead and log in now." %}

+ +

{% trans 'Log in' %}

+ +{% endblock %} +{% endblock %} + diff --git a/ui/templates/registration/password_reset_confirm.html b/ui/templates/registration/password_reset_confirm.html new file mode 100644 index 00000000..0ea6867e --- /dev/null +++ b/ui/templates/registration/password_reset_confirm.html @@ -0,0 +1,36 @@ +{% extends "layout-unfold1.html" %} +{% load i18n %} + +{% block unfold_main %} + + +{% block content %} + +{% if validlink %} + +

{% trans 'OneLab secured Password reset wizard' %}

+

{% trans 'Enter new password' %}

+ +

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

+ +
{% csrf_token %} +{{ form.new_password1.errors }} +

{{ form.new_password1 }}

+{{ form.new_password2.errors }} +

{{ form.new_password2 }}

+

+
+ +{% else %} + +

{% trans 'OneLab secured Password reset wizard' %}

+

{% trans 'Password reset unsuccessful!' %}

+ +

The password reset link was invalid, possibly because it has already been used. Please request a new password reset.

+ +

If you still encounter problem please Contact Support for password recovery.

+{% endif %} + +{% endblock %} +{% endblock %} + diff --git a/ui/templates/registration/password_reset_done.html b/ui/templates/registration/password_reset_done.html new file mode 100644 index 00000000..caeb4ebd --- /dev/null +++ b/ui/templates/registration/password_reset_done.html @@ -0,0 +1,14 @@ +{% extends "layout-unfold1.html" %} +{% load i18n %} + +{% block unfold_main %} + +{% block content %} + +

{% trans 'Onelab secured Password reset wizard' %}

+ +

{% trans "We've emailed you instructions for setting your password to the email address you submitted. You should be receiving it shortly." %}

+ +{% endblock %} +{% endblock %} + diff --git a/ui/templates/registration/password_reset_email.html b/ui/templates/registration/password_reset_email.html new file mode 100644 index 00000000..51431c73 --- /dev/null +++ b/ui/templates/registration/password_reset_email.html @@ -0,0 +1,14 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} + +{% trans "Please go to the following page and choose a new password:" %} +{% block reset_link %} +{{ protocol }}://{{ domain }}{% url 'portal.django_passresetview.password_reset_confirm' uidb36=uid token=token %} +{% endblock %} +{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/ui/templates/registration/password_reset_form.html b/ui/templates/registration/password_reset_form.html new file mode 100644 index 00000000..8fba3201 --- /dev/null +++ b/ui/templates/registration/password_reset_form.html @@ -0,0 +1,21 @@ +{% extends "layout-unfold1.html" %} +{% load i18n %} + +{% block unfold_main %} + + + +{% block content %} + +

{% trans "Welcome to Onelab secured Password reset wizard" %}

+ +

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

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

{{ form.email }}

+
+ +{% endblock %} +{% endblock %} +