Refactor account code to use the built-in Django signing framework
(reusing code previously written for the community lists) instead of the a custom MD5 scheme, add tests of all views, rewrite custom form handling code to use plain forms and ensure that the data is properly validated and errors visible in the UI. Move help texts on the Person form up to the model. - Legacy-Id: 11136
This commit is contained in:
parent
210188bf86
commit
a99aa32c59
|
@ -1,235 +1,96 @@
|
|||
import datetime
|
||||
import hashlib
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.forms import ModelForm
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.html import mark_safe
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.person.models import Person, Email, Alias
|
||||
from ietf.group.models import Role
|
||||
from ietf.person.models import Person, Email
|
||||
|
||||
|
||||
class RegistrationForm(forms.Form):
|
||||
|
||||
email = forms.EmailField(label="Your email (lowercase)")
|
||||
realm = 'IETF'
|
||||
expire = 3
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# why is there a save when it doesn't save?
|
||||
self.send_email()
|
||||
return True
|
||||
|
||||
def send_email(self):
|
||||
domain = Site.objects.get_current().domain
|
||||
subject = 'Confirm registration at %s' % domain
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
to_email = self.cleaned_data['email']
|
||||
today = datetime.date.today().strftime('%Y%m%d')
|
||||
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, self.realm)).hexdigest()
|
||||
context = {
|
||||
'domain': domain,
|
||||
'today': today,
|
||||
'realm': self.realm,
|
||||
'auth': auth,
|
||||
'username': to_email,
|
||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
||||
}
|
||||
send_mail(self.request, to_email, from_email, subject, 'registration/creation_email.txt', context)
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data.get('email', '')
|
||||
if not email:
|
||||
return email
|
||||
if email.lower() != email:
|
||||
raise forms.ValidationError(_('The supplied address contained uppercase letters. Please use a lowercase email address.'))
|
||||
if User.objects.filter(username=email).count():
|
||||
raise forms.ValidationError(_('An account with the email address you provided already exists.'))
|
||||
return email
|
||||
|
||||
|
||||
class RecoverPasswordForm(RegistrationForm):
|
||||
|
||||
realm = 'IETF'
|
||||
|
||||
def send_email(self):
|
||||
domain = Site.objects.get_current().domain
|
||||
subject = 'Password reset at %s' % domain
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
today = datetime.date.today().strftime('%Y%m%d')
|
||||
to_email = self.cleaned_data['email']
|
||||
today = datetime.date.today().strftime('%Y%m%d')
|
||||
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, self.realm)).hexdigest()
|
||||
context = {
|
||||
'domain': domain,
|
||||
'today': today,
|
||||
'realm': self.realm,
|
||||
'auth': auth,
|
||||
'username': to_email,
|
||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
||||
}
|
||||
send_mail(self.request, to_email, from_email, subject, 'registration/password_reset_email.txt', context)
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data.get('email', '')
|
||||
raise forms.ValidationError('The supplied address contained uppercase letters. Please use a lowercase email address.')
|
||||
if User.objects.filter(username=email).exists():
|
||||
raise forms.ValidationError('An account with the email address you provided already exists.')
|
||||
return email
|
||||
|
||||
|
||||
class PasswordForm(forms.Form):
|
||||
password = forms.CharField(widget=forms.PasswordInput)
|
||||
password_confirmation = forms.CharField(widget=forms.PasswordInput,
|
||||
help_text="Enter the same password as above, for verification.")
|
||||
|
||||
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput,
|
||||
help_text=_("Enter the same password as above, for verification."))
|
||||
def clean_password_confirmation(self):
|
||||
password = self.cleaned_data.get("password", "")
|
||||
password_confirmation = self.cleaned_data["password_confirmation"]
|
||||
if password != password_confirmation:
|
||||
raise forms.ValidationError("The two password fields didn't match.")
|
||||
return password_confirmation
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.username = kwargs.pop('username')
|
||||
self.update_user = User.objects.filter(username=self.username).count() > 0
|
||||
super(PasswordForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_password2(self):
|
||||
password1 = self.cleaned_data.get("password1", "")
|
||||
password2 = self.cleaned_data["password2"]
|
||||
if password1 != password2:
|
||||
raise forms.ValidationError(_("The two password fields didn't match."))
|
||||
return password2
|
||||
def ascii_cleaner(supposedly_ascii):
|
||||
outside_printable_ascii_pattern = r'[^\x20-\x7F]'
|
||||
if re.search(outside_printable_ascii_pattern, supposedly_ascii):
|
||||
raise forms.ValidationError("Please only enter ASCII characters.")
|
||||
return supposedly_ascii
|
||||
|
||||
def get_password(self):
|
||||
return self.cleaned_data.get('password1')
|
||||
class PersonForm(ModelForm):
|
||||
class Meta:
|
||||
model = Person
|
||||
exclude = ('time', 'user')
|
||||
|
||||
def create_user(self):
|
||||
user = User.objects.create(username=self.username,
|
||||
email=self.username)
|
||||
email = Email.objects.filter(address=self.username)
|
||||
person = None
|
||||
if email.count():
|
||||
email = email[0]
|
||||
if email.person:
|
||||
person = email.person
|
||||
else:
|
||||
email = None
|
||||
if not person:
|
||||
person = Person.objects.create(user=user,
|
||||
name=self.username,
|
||||
ascii=self.username)
|
||||
if not email:
|
||||
email = Email.objects.create(address=self.username,
|
||||
person=person)
|
||||
email.person = person
|
||||
email.save()
|
||||
person.user = user
|
||||
person.save()
|
||||
return user
|
||||
def clean_ascii(self):
|
||||
return ascii_cleaner(self.cleaned_data.get("ascii") or u"")
|
||||
|
||||
def get_user(self):
|
||||
return User.objects.get(username=self.username)
|
||||
def clean_ascii_short(self):
|
||||
return ascii_cleaner(self.cleaned_data.get("ascii_short") or u"")
|
||||
|
||||
def save_password_file(self):
|
||||
if getattr(settings, 'USE_PYTHON_HTDIGEST', None):
|
||||
pass_file = settings.HTPASSWD_FILE
|
||||
realm = settings.HTDIGEST_REALM
|
||||
password = self.get_password()
|
||||
username = self.username
|
||||
prefix = '%s:%s:' % (username, realm)
|
||||
key = hashlib.md5(prefix + password).hexdigest()
|
||||
f = open(pass_file, 'r+')
|
||||
pos = f.tell()
|
||||
line = f.readline()
|
||||
while line:
|
||||
if line.startswith(prefix):
|
||||
break
|
||||
pos=f.tell()
|
||||
line = f.readline()
|
||||
f.seek(pos)
|
||||
f.write('%s%s\n' % (prefix, key))
|
||||
f.close()
|
||||
else:
|
||||
p = subprocess.Popen([settings.HTPASSWD_COMMAND, "-b", settings.HTPASSWD_FILE, self.username, self.get_password()], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
def save(self):
|
||||
if self.update_user:
|
||||
user = self.get_user()
|
||||
else:
|
||||
user = self.create_user()
|
||||
user.set_password(self.get_password())
|
||||
user.save()
|
||||
self.save_password_file()
|
||||
return user
|
||||
class NewEmailForm(forms.Form):
|
||||
new_email = forms.EmailField(label="New email address", required=False)
|
||||
|
||||
def clean_new_email(self):
|
||||
email = self.cleaned_data.get("new_email", "")
|
||||
if email:
|
||||
existing = Email.objects.filter(address=email).first()
|
||||
if existing:
|
||||
raise forms.ValidationError("Email address '%s' is already assigned to account '%s' (%s)" % (existing, existing.person and existing.person.user, existing.person))
|
||||
return email
|
||||
|
||||
|
||||
class RoleEmailForm(forms.Form):
|
||||
email = forms.ModelChoiceField(label="Role email", queryset=Email.objects.all())
|
||||
|
||||
def __init__(self, role, *args, **kwargs):
|
||||
super(RoleEmailForm, self).__init__(*args, **kwargs)
|
||||
|
||||
f = self.fields["email"]
|
||||
f.label = u"%s in %s" % (role.name, role.group.acronym.upper())
|
||||
f.help_text = u"Email to use for <i>%s</i> role in %s" % (role.name, role.group.name)
|
||||
f.queryset = f.queryset.filter(models.Q(person=role.person_id) | models.Q(role=role))
|
||||
f.initial = role.email_id
|
||||
f.choices = [(e.pk, e.address if e.active else u"({})".format(e.address)) for e in f.queryset]
|
||||
|
||||
|
||||
class ResetPasswordForm(forms.Form):
|
||||
email = forms.EmailField(label="Your email (lowercase)")
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data["email"]
|
||||
if not User.objects.filter(username=email).exists():
|
||||
raise forms.ValidationError(mark_safe("Didn't find a matching account. If you don't have an account yet, you can <a href=\"{}\">create one</a>.".format(urlreverse("create_account"))))
|
||||
return email
|
||||
|
||||
|
||||
class TestEmailForm(forms.Form):
|
||||
email = forms.EmailField(required=False)
|
||||
|
||||
class PersonForm(ModelForm):
|
||||
request = None
|
||||
new_emails = []
|
||||
class Meta:
|
||||
model = Person
|
||||
exclude = ('time','user')
|
||||
|
||||
def confirm_address(self,email):
|
||||
person = self.instance
|
||||
domain = Site.objects.get_current().domain
|
||||
user = person.user
|
||||
if len(email) == 0:
|
||||
return
|
||||
subject = 'Confirm email address for %s' % person.name
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
to_email = email
|
||||
today = datetime.date.today().strftime('%Y%m%d')
|
||||
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, user)).hexdigest()
|
||||
context = {
|
||||
'today': today,
|
||||
'domain': domain,
|
||||
'user': user,
|
||||
'email': email,
|
||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
||||
'auth': auth,
|
||||
}
|
||||
send_mail(self.request, to_email, from_email, subject, 'registration/add_email_email.txt', context)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, commit=True):
|
||||
m = super(PersonForm, self).save(commit=False)
|
||||
self.new_emails = [v for k,v in self.data.items() if k[:10] == u'new_email_' and u'@' in v]
|
||||
|
||||
for email in self.new_emails:
|
||||
self.confirm_address(email)
|
||||
|
||||
# Process email active flags
|
||||
emails = Email.objects.filter(person=self.instance)
|
||||
for email in emails:
|
||||
email.active = self.data.__contains__(email.address)
|
||||
if commit:
|
||||
email.save()
|
||||
|
||||
# Process email for roles
|
||||
for k,v in self.data.items():
|
||||
if k[:11] == u'role_email_':
|
||||
role = Role.objects.get(id=k[11:])
|
||||
email = Email.objects.get(address = v)
|
||||
role.email = email
|
||||
if commit:
|
||||
role.save()
|
||||
|
||||
# Make sure the alias table contains any new and/or old names.
|
||||
old_names = set([x.name for x in Alias.objects.filter(person=self.instance)])
|
||||
curr_names = set([x for x in [self.instance.name,
|
||||
self.instance.ascii,
|
||||
self.instance.ascii_short,
|
||||
self.data['name'],
|
||||
self.data['ascii'],
|
||||
self.data['ascii_short']] if len(x)])
|
||||
new_names = curr_names - old_names
|
||||
for name in new_names:
|
||||
alias = Alias(person=self.instance,name=name)
|
||||
if commit:
|
||||
alias.save()
|
||||
|
||||
if commit:
|
||||
m.save()
|
||||
return m
|
||||
|
||||
|
|
24
ietf/ietfauth/htpasswd.py
Normal file
24
ietf/ietfauth/htpasswd.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import subprocess, hashlib
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
def save_htpasswd_file(username, password):
|
||||
if getattr(settings, 'USE_PYTHON_HTDIGEST', None):
|
||||
pass_file = settings.HTPASSWD_FILE
|
||||
realm = settings.HTDIGEST_REALM
|
||||
prefix = '%s:%s:' % (username, realm)
|
||||
key = hashlib.md5(prefix + password).hexdigest()
|
||||
f = open(pass_file, 'r+')
|
||||
pos = f.tell()
|
||||
line = f.readline()
|
||||
while line:
|
||||
if line.startswith(prefix):
|
||||
break
|
||||
pos=f.tell()
|
||||
line = f.readline()
|
||||
f.seek(pos)
|
||||
f.write('%s%s\n' % (prefix, key))
|
||||
f.close()
|
||||
else:
|
||||
p = subprocess.Popen([settings.HTPASSWD_COMMAND, "-b", settings.HTPASSWD_FILE, username, password], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
|
@ -1,83 +1,261 @@
|
|||
# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
||||
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
#
|
||||
# * Neither the name of the Nokia Corporation and/or its
|
||||
# subsidiary(-ies) nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import os, shutil
|
||||
from urlparse import urlsplit
|
||||
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from pyquery import PyQuery
|
||||
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.group.models import Group, Role, RoleName
|
||||
|
||||
class IetfAuthTests(TestCase):
|
||||
def setUp(self):
|
||||
self.saved_htpasswd_file = settings.HTPASSWD_FILE
|
||||
self.htpasswd_dir = os.path.abspath("tmp-htpasswd-dir")
|
||||
os.mkdir(self.htpasswd_dir)
|
||||
settings.HTPASSWD_FILE = os.path.join(self.htpasswd_dir, "htpasswd")
|
||||
open(settings.HTPASSWD_FILE, 'a').close() # create empty file
|
||||
self.saved_htdigest_realm = getattr(settings, "HTDIGEST_REALM", None)
|
||||
settings.HTDIGEST_REALM = "test-realm"
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.htpasswd_dir)
|
||||
settings.HTPASSWD_FILE = self.saved_htpasswd_file
|
||||
settings.HTDIGEST_REALM = self.saved_htdigest_realm
|
||||
|
||||
def test_index(self):
|
||||
self.assertEqual(self.client.get(urlreverse("ietf.ietfauth.views.index")).status_code, 200)
|
||||
|
||||
def test_login(self):
|
||||
def test_login_and_logout(self):
|
||||
make_test_data()
|
||||
|
||||
# try logging in without a next
|
||||
r = self.client.get('/accounts/login/')
|
||||
r = self.client.get(urlreverse("account_login"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post('/accounts/login/', {"username":"plain", "password":"plain+password"})
|
||||
r = self.client.post(urlreverse("account_login"), {"username":"plain", "password":"plain+password"})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(urlsplit(r["Location"])[2], "/accounts/profile/")
|
||||
|
||||
# try logging out
|
||||
r = self.client.get('/accounts/logout/')
|
||||
r = self.client.get(urlreverse("account_logout"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.get('/accounts/profile/')
|
||||
r = self.client.get(urlreverse("account_profile"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(urlsplit(r["Location"])[2], "/accounts/login/")
|
||||
|
||||
# try logging in with a next
|
||||
r = self.client.post('/accounts/login/?next=/foobar', {"username":"plain", "password":"plain+password"})
|
||||
r = self.client.post(urlreverse("account_login") + "?next=/foobar", {"username":"plain", "password":"plain+password"})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(urlsplit(r["Location"])[2], "/foobar")
|
||||
|
||||
def extract_confirm_url(self, confirm_email):
|
||||
# dig out confirm_email link
|
||||
msg = confirm_email.get_payload(decode=True)
|
||||
line_start = "http"
|
||||
confirm_url = None
|
||||
for line in msg.split("\n"):
|
||||
if line.strip().startswith(line_start):
|
||||
confirm_url = line.strip()
|
||||
self.assertTrue(confirm_url)
|
||||
|
||||
return confirm_url
|
||||
|
||||
def username_in_htpasswd_file(self, username):
|
||||
with open(settings.HTPASSWD_FILE) as f:
|
||||
for l in f:
|
||||
if l.startswith(username + ":" + settings.HTDIGEST_REALM):
|
||||
return True
|
||||
return False
|
||||
|
||||
def test_create_account(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('create_account')
|
||||
|
||||
# get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# register email
|
||||
email = 'new-account@example.com'
|
||||
empty_outbox()
|
||||
r = self.client.post(url, { 'email': email })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(outbox), 1)
|
||||
|
||||
# go to confirm page
|
||||
confirm_url = self.extract_confirm_url(outbox[-1])
|
||||
r = self.client.get(confirm_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# password mismatch
|
||||
r = self.client.post(confirm_url, { 'password': 'secret', 'password_confirmation': 'nosecret' })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(User.objects.filter(username=email).count(), 0)
|
||||
|
||||
# confirm
|
||||
r = self.client.post(confirm_url, { 'password': 'secret', 'password_confirmation': 'secret' })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(User.objects.filter(username=email).count(), 1)
|
||||
self.assertEqual(Person.objects.filter(user__username=email).count(), 1)
|
||||
self.assertEqual(Email.objects.filter(person__user__username=email).count(), 1)
|
||||
|
||||
self.assertTrue(self.username_in_htpasswd_file(email))
|
||||
|
||||
def test_profile(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('ietf.ietfauth.views.profile')
|
||||
login_testing_unauthorized(self, "plain", url)
|
||||
username = "plain"
|
||||
email_address = Email.objects.filter(person__user__username=username).first().address
|
||||
|
||||
url = urlreverse('account_profile')
|
||||
login_testing_unauthorized(self, username, url)
|
||||
|
||||
|
||||
# get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('.form-control-static:contains("%s")' % username)), 1)
|
||||
self.assertEqual(len(q('[name="active_emails"][value="%s"][checked]' % email_address)), 1)
|
||||
|
||||
base_data = {
|
||||
"name": u"Test Nãme",
|
||||
"ascii": u"Test Name",
|
||||
"ascii_short": u"T. Name",
|
||||
"address": "Test address",
|
||||
"affiliation": "Test Org",
|
||||
"active_emails": email_address,
|
||||
}
|
||||
|
||||
# edit details - faulty ASCII
|
||||
faulty_ascii = base_data.copy()
|
||||
faulty_ascii["ascii"] = u"Test Nãme"
|
||||
r = self.client.post(url, faulty_ascii)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q("form .has-error")) > 0)
|
||||
|
||||
# edit details
|
||||
r = self.client.post(url, base_data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
person = Person.objects.get(user__username=username)
|
||||
self.assertEqual(person.name, u"Test Nãme")
|
||||
self.assertEqual(person.ascii, u"Test Name")
|
||||
self.assertEqual(Person.objects.filter(alias__name=u"Test Name", user__username=username).count(), 1)
|
||||
self.assertEqual(Person.objects.filter(alias__name=u"Test Nãme", user__username=username).count(), 1)
|
||||
self.assertEqual(Email.objects.filter(address=email_address, person__user__username=username, active=True).count(), 1)
|
||||
|
||||
# deactivate address
|
||||
without_email_address = { k: v for k, v in base_data.iteritems() if k != "active_emails" }
|
||||
|
||||
r = self.client.post(url, without_email_address)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(Email.objects.filter(address=email_address, person__user__username="plain", active=True).count(), 0)
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('[name="%s"][checked]' % email_address)), 0)
|
||||
|
||||
# add email address
|
||||
empty_outbox()
|
||||
new_email_address = "plain2@example.com"
|
||||
with_new_email_address = base_data.copy()
|
||||
with_new_email_address["new_email"] = new_email_address
|
||||
r = self.client.post(url, with_new_email_address)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(outbox), 1)
|
||||
|
||||
# confirm new email address
|
||||
confirm_url = self.extract_confirm_url(outbox[-1])
|
||||
r = self.client.get(confirm_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('[name="action"][value=confirm]')), 1)
|
||||
|
||||
r = self.client.post(confirm_url, { "action": "confirm" })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(Email.objects.filter(address=new_email_address, person__user__username=username, active=1).count(), 1)
|
||||
|
||||
# check that we can't re-add it - that would give a duplicate
|
||||
r = self.client.get(confirm_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('[name="action"][value="confirm"]')), 0)
|
||||
|
||||
# change role email
|
||||
role = Role.objects.create(
|
||||
person=Person.objects.get(user__username=username),
|
||||
email=Email.objects.get(address=email_address),
|
||||
name=RoleName.objects.get(slug="chair"),
|
||||
group=Group.objects.get(acronym="mars"),
|
||||
)
|
||||
|
||||
role_email_input_name = "role_%s-email" % role.pk
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('[name="%s"]' % role_email_input_name)), 1)
|
||||
|
||||
with_changed_role_email = base_data.copy()
|
||||
with_changed_role_email["active_emails"] = new_email_address
|
||||
with_changed_role_email[role_email_input_name] = new_email_address
|
||||
r = self.client.post(url, with_changed_role_email)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
updated_roles = Role.objects.filter(person=role.person, name=role.name, group=role.group)
|
||||
self.assertEqual(len(updated_roles), 1)
|
||||
self.assertEqual(updated_roles[0].email_id, new_email_address)
|
||||
|
||||
|
||||
def test_reset_password(self):
|
||||
url = urlreverse('password_reset')
|
||||
|
||||
user = User.objects.create(username="someone@example.com", email="someone@example.com")
|
||||
user.set_password("forgotten")
|
||||
user.save()
|
||||
p = Person.objects.create(name="Some One", ascii="Some One", user=user)
|
||||
Email.objects.create(address=user.username, person=p)
|
||||
|
||||
# get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("plain" in unicontent(r))
|
||||
|
||||
# post
|
||||
# ... fill in
|
||||
# ask for reset, wrong username
|
||||
r = self.client.post(url, { 'email': "nobody@example.com" })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q("form .has-error")) > 0)
|
||||
|
||||
# we're missing tests of the other views
|
||||
# ask for reset
|
||||
empty_outbox()
|
||||
r = self.client.post(url, { 'email': user.username })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(outbox), 1)
|
||||
|
||||
# go to change password page
|
||||
confirm_url = self.extract_confirm_url(outbox[-1])
|
||||
r = self.client.get(confirm_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# password mismatch
|
||||
r = self.client.post(confirm_url, { 'password': 'secret', 'password_confirmation': 'nosecret' })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q("form .has-error")) > 0)
|
||||
|
||||
# confirm
|
||||
r = self.client.post(confirm_url, { 'password': 'secret', 'password_confirmation': 'secret' })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q("form .has-error")), 0)
|
||||
self.assertTrue(self.username_in_htpasswd_file(user.username))
|
||||
|
|
|
@ -6,17 +6,16 @@ from django.contrib.auth.views import login, logout
|
|||
urlpatterns = patterns('ietf.ietfauth.views',
|
||||
url(r'^$', 'index', name='account_index'),
|
||||
# url(r'^login/$', 'ietf_login'),
|
||||
url(r'^login/$', login),
|
||||
url(r'^logout/$', logout),
|
||||
url(r'^login/$', login, name="account_login"),
|
||||
url(r'^logout/$', logout, name="account_logout"),
|
||||
# url(r'^loggedin/$', 'ietf_loggedin'),
|
||||
# url(r'^loggedout/$', 'logged_out'),
|
||||
url(r'^profile/$', 'profile'),
|
||||
url(r'^profile/$', 'profile', name="account_profile"),
|
||||
# (r'^login/(?P<user>[a-z0-9.@]+)/(?P<passwd>.+)$', 'url_login'),
|
||||
url(r'^testemail/$', 'test_email'),
|
||||
url(r'^create/$', 'create_account', name='create_account'),
|
||||
url(r'^confirm/(?P<username>[\w.@+-]+)/(?P<date>[\d]+)/(?P<realm>[\w]+)/(?P<hash>[a-f0-9]+)/$', 'confirm_account', name='confirm_account'),
|
||||
url(r'^reset/$', 'password_reset_view', name='password_reset'),
|
||||
url(r'^reset/confirm/(?P<username>[\w.@+-]+)/(?P<date>[\d]+)/(?P<realm>[\w]+)/(?P<hash>[a-f0-9]+)/$', 'confirm_password_reset', name='confirm_password_reset'),
|
||||
url(r'^add_email/confirm/(?P<username>[\w.@+-]+)/(?P<date>[\d]+)/(?P<email>[\w.@+-]+)/(?P<hash>[a-f0-9]+)/$', 'confirm_new_email', name='confirm_new_email'),
|
||||
# url(r'^ajax/check_username/$', 'ajax_check_username', name='ajax_check_username'),
|
||||
url(r'^create/confirm/(?P<auth>[^/]+)/$', 'confirm_account', name='confirm_account'),
|
||||
url(r'^reset/$', 'password_reset', name='password_reset'),
|
||||
url(r'^reset/confirm/(?P<auth>[^/]+)/$', 'confirm_password_reset', name='confirm_password_reset'),
|
||||
url(r'^confirmnewemail/(?P<auth>[^/]+)/$', 'confirm_new_email', name='confirm_new_email'),
|
||||
)
|
||||
|
|
|
@ -138,4 +138,3 @@ def is_authorized_in_doc_stream(user, doc):
|
|||
group_req = Q()
|
||||
|
||||
return Role.objects.filter(Q(name__in=("chair", "secr", "delegate", "auth"), person__user=user) & group_req).exists()
|
||||
|
||||
|
|
|
@ -32,27 +32,25 @@
|
|||
|
||||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
#import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import RequestContext
|
||||
from django.http import Http404 #, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
#from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
|
||||
from django.contrib.auth.decorators import login_required
|
||||
#from django.contrib.auth.models import User
|
||||
#from django.utils.http import urlquote
|
||||
#from django.utils.translation import ugettext as _
|
||||
from django.core.exceptions import ValidationError
|
||||
import django.core.signing
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from ietf.group.models import Role
|
||||
from ietf.ietfauth.forms import RegistrationForm, PasswordForm, RecoverPasswordForm, TestEmailForm, PersonForm
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.ietfauth.forms import RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm
|
||||
from ietf.ietfauth.forms import PersonForm, RoleEmailForm, NewEmailForm
|
||||
from ietf.ietfauth.htpasswd import save_htpasswd_file
|
||||
from ietf.person.models import Person, Email, Alias
|
||||
from ietf.utils.mail import send_mail
|
||||
|
||||
def index(request):
|
||||
return render_to_response('registration/index.html', context_instance=RequestContext(request))
|
||||
return render(request, 'registration/index.html')
|
||||
|
||||
# def url_login(request, user, passwd):
|
||||
# user = authenticate(username=user, password=passwd)
|
||||
|
@ -81,154 +79,251 @@ def index(request):
|
|||
# redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
# return HttpResponseRedirect(redirect_to)
|
||||
|
||||
@login_required
|
||||
def profile(request):
|
||||
roles = []
|
||||
person = None
|
||||
try:
|
||||
person = request.user.person
|
||||
except Person.DoesNotExist:
|
||||
return render_to_response('registration/missing_person.html', context_instance=RequestContext(request))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = PersonForm(request.POST, instance=person)
|
||||
success = False
|
||||
new_emails = None
|
||||
error = None
|
||||
if form.is_valid():
|
||||
try:
|
||||
form.save()
|
||||
success = True
|
||||
new_emails = form.new_emails
|
||||
except Exception as e:
|
||||
error = e
|
||||
|
||||
return render_to_response('registration/confirm_profile_update.html',
|
||||
{ 'success': success, 'new_emails': new_emails, 'error': error} ,
|
||||
context_instance=RequestContext(request))
|
||||
else:
|
||||
roles = Role.objects.filter(person=person,group__state='active').order_by('name__name','group__name')
|
||||
emails = Email.objects.filter(person=person).order_by('-active','-time')
|
||||
|
||||
person_form = PersonForm(instance=person)
|
||||
|
||||
return render_to_response('registration/edit_profile.html',
|
||||
{ 'user': request.user, 'emails': emails, 'person': person,
|
||||
'roles': roles, 'person_form': person_form } ,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def confirm_new_email(request, username, date, email, hash):
|
||||
valid = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, date, email, username)).hexdigest() == hash
|
||||
if not valid:
|
||||
raise Http404
|
||||
request_date = datetime.date(int(date[:4]), int(date[4:6]), int(date[6:]))
|
||||
if datetime.date.today() > (request_date + datetime.timedelta(days=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK)):
|
||||
raise Http404
|
||||
success = False
|
||||
|
||||
person = None
|
||||
error = None
|
||||
new_email = None
|
||||
|
||||
try:
|
||||
# First, check whether this address exists (to give a more sensible
|
||||
# error when a duplicate is created).
|
||||
existing_email = Email.objects.get(address=email)
|
||||
print existing_email
|
||||
existing_person = existing_email.person
|
||||
print existing_person
|
||||
error = {'address': ["Email address '%s' is already assigned to user '%s' (%s)" %
|
||||
(email, existing_person.user, existing_person.name)]}
|
||||
except Exception:
|
||||
try:
|
||||
person = Person.objects.get(user__username=username)
|
||||
new_email = Email(address=email, person=person, active=True, time=datetime.datetime.now())
|
||||
new_email.full_clean()
|
||||
new_email.save()
|
||||
success = True
|
||||
except Person.DoesNotExist:
|
||||
error = {'person': ["No such user: %s" % (username)]}
|
||||
except ValidationError as e:
|
||||
error = e.message_dict
|
||||
|
||||
return render_to_response('registration/confirm_new_email.html',
|
||||
{ 'username': username, 'email': email,
|
||||
'success': success, 'error': error,
|
||||
'record': new_email},
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def create_account(request):
|
||||
success = False
|
||||
if request.method == 'POST':
|
||||
form = RegistrationForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.request = request
|
||||
form.save()
|
||||
success = True
|
||||
to_email = form.cleaned_data['email']
|
||||
|
||||
auth = django.core.signing.dumps(to_email, salt="create_account")
|
||||
|
||||
domain = Site.objects.get_current().domain
|
||||
subject = 'Confirm registration at %s' % domain
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'registration/creation_email.txt', {
|
||||
'domain': domain,
|
||||
'auth': auth,
|
||||
'username': to_email,
|
||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
||||
})
|
||||
else:
|
||||
form = RegistrationForm()
|
||||
return render_to_response('registration/create.html',
|
||||
{'form': form,
|
||||
'success': success},
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
return render(request, 'registration/create.html', {
|
||||
'form': form,
|
||||
'success': success,
|
||||
})
|
||||
|
||||
def confirm_account(request, auth):
|
||||
try:
|
||||
email = django.core.signing.loads(auth, salt="create_account", max_age=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK * 24 * 60 * 60)
|
||||
except django.core.signing.BadSignature:
|
||||
raise Http404("Invalid or expired auth")
|
||||
|
||||
if User.objects.filter(username=email).exists():
|
||||
return redirect("account_profile")
|
||||
|
||||
def process_confirmation(request, username, date, realm, hash):
|
||||
valid = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, date, username, realm)).hexdigest() == hash
|
||||
if not valid:
|
||||
raise Http404
|
||||
request_date = datetime.date(int(date[:4]), int(date[4:6]), int(date[6:]))
|
||||
if datetime.date.today() > (request_date + datetime.timedelta(days=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK)):
|
||||
raise Http404
|
||||
success = False
|
||||
if request.method == 'POST':
|
||||
form = PasswordForm(request.POST, username=username)
|
||||
form = PasswordForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save() # Also updates the httpd password file
|
||||
password = form.cleaned_data["password"]
|
||||
|
||||
user = User.objects.create(username=email, email=email)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
# password is also stored in htpasswd file
|
||||
save_htpasswd_file(email, password)
|
||||
|
||||
# make sure the rest of the person infrastructure is
|
||||
# well-connected
|
||||
email_obj = Email.objects.filter(address=email).first()
|
||||
|
||||
person = None
|
||||
if email_obj and email_obj.person:
|
||||
person = email_obj.person
|
||||
|
||||
if not person:
|
||||
person = Person.objects.create(user=user,
|
||||
name=email,
|
||||
ascii=email)
|
||||
if not email_obj:
|
||||
email_obj = Email.objects.create(address=email,
|
||||
person=person)
|
||||
email_obj.person = person
|
||||
email_obj.save()
|
||||
person.user = user
|
||||
person.save()
|
||||
|
||||
success = True
|
||||
else:
|
||||
form = PasswordForm(username=username)
|
||||
return form, username, success
|
||||
form = PasswordForm()
|
||||
|
||||
def confirm_account(request, username, date, realm, hash):
|
||||
form, username, success = process_confirmation(request, username, date, realm, hash)
|
||||
return render_to_response('registration/confirm.html',
|
||||
{'form': form, 'email': username, 'success': success},
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'registration/confirm_account.html', {
|
||||
'form': form,
|
||||
'email': email,
|
||||
'success': success,
|
||||
})
|
||||
|
||||
@login_required
|
||||
def profile(request):
|
||||
roles = []
|
||||
person = None
|
||||
|
||||
def password_reset_view(request):
|
||||
try:
|
||||
person = request.user.person
|
||||
except Person.DoesNotExist:
|
||||
return render(request, 'registration/missing_person.html')
|
||||
|
||||
roles = Role.objects.filter(person=person, group__state='active').order_by('name__name', 'group__name')
|
||||
emails = Email.objects.filter(person=person).order_by('-active','-time')
|
||||
new_email_forms = []
|
||||
|
||||
if request.method == 'POST':
|
||||
person_form = PersonForm(request.POST, instance=person)
|
||||
for r in roles:
|
||||
r.email_form = RoleEmailForm(r, request.POST, prefix="role_%s" % r.pk)
|
||||
|
||||
for e in request.POST.getlist("new_email", []):
|
||||
new_email_forms.append(NewEmailForm({ "new_email": e }))
|
||||
|
||||
forms_valid = [person_form.is_valid()] + [r.email_form.is_valid() for r in roles] + [f.is_valid() for f in new_email_forms]
|
||||
|
||||
email_confirmations = []
|
||||
|
||||
if all(forms_valid):
|
||||
updated_person = person_form.save()
|
||||
|
||||
for f in new_email_forms:
|
||||
to_email = f.cleaned_data["new_email"]
|
||||
if not to_email:
|
||||
continue
|
||||
|
||||
email_confirmations.append(to_email)
|
||||
|
||||
auth = django.core.signing.dumps([person.user.username, to_email], salt="add_email")
|
||||
|
||||
domain = Site.objects.get_current().domain
|
||||
subject = u'Confirm email address for %s' % person.name
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'registration/add_email_email.txt', {
|
||||
'domain': domain,
|
||||
'auth': auth,
|
||||
'email': to_email,
|
||||
'person': person,
|
||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
||||
})
|
||||
|
||||
|
||||
for r in roles:
|
||||
e = r.email_form.cleaned_data["email"]
|
||||
if r.email_id != e.pk:
|
||||
r.email = e
|
||||
r.save()
|
||||
|
||||
active_emails = request.POST.getlist("active_emails", [])
|
||||
for email in emails:
|
||||
email.active = email.pk in active_emails
|
||||
email.save()
|
||||
|
||||
# Make sure the alias table contains any new and/or old names.
|
||||
existing_aliases = set(Alias.objects.filter(person=person).values_list("name", flat=True))
|
||||
curr_names = set(x for x in [updated_person.name, updated_person.ascii, updated_person.ascii_short] if x)
|
||||
new_aliases = curr_names - existing_aliases
|
||||
for name in new_aliases:
|
||||
Alias.objects.create(person=updated_person, name=name)
|
||||
|
||||
return render(request, 'registration/confirm_profile_update.html', {
|
||||
'email_confirmations': email_confirmations,
|
||||
})
|
||||
else:
|
||||
for r in roles:
|
||||
r.email_form = RoleEmailForm(r, prefix="role_%s" % r.pk)
|
||||
|
||||
person_form = PersonForm(instance=person)
|
||||
|
||||
return render(request, 'registration/edit_profile.html', {
|
||||
'user': request.user,
|
||||
'person': person,
|
||||
'person_form': person_form,
|
||||
'roles': roles,
|
||||
'emails': emails,
|
||||
'new_email_forms': new_email_forms,
|
||||
})
|
||||
|
||||
def confirm_new_email(request, auth):
|
||||
try:
|
||||
username, email = django.core.signing.loads(auth, salt="add_email", max_age=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK * 24 * 60 * 60)
|
||||
except django.core.signing.BadSignature:
|
||||
raise Http404("Invalid or expired auth")
|
||||
|
||||
person = get_object_or_404(Person, user__username=username)
|
||||
|
||||
# do another round of validation since the situation may have
|
||||
# changed since submitting the request
|
||||
form = NewEmailForm({ "new_email": email })
|
||||
can_confirm = form.is_valid() and email
|
||||
new_email_obj = None
|
||||
if request.method == 'POST' and can_confirm and request.POST.get("action") == "confirm":
|
||||
new_email_obj = Email.objects.create(address=email, person=person)
|
||||
|
||||
return render(request, 'registration/confirm_new_email.html', {
|
||||
'username': username,
|
||||
'email': email,
|
||||
'can_confirm': can_confirm,
|
||||
'form': form,
|
||||
'new_email_obj': new_email_obj,
|
||||
})
|
||||
|
||||
def password_reset(request):
|
||||
success = False
|
||||
if request.method == 'POST':
|
||||
form = RecoverPasswordForm(request.POST)
|
||||
form = ResetPasswordForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.request = request
|
||||
form.save()
|
||||
to_email = form.cleaned_data['email']
|
||||
|
||||
auth = django.core.signing.dumps(to_email, salt="password_reset")
|
||||
|
||||
domain = Site.objects.get_current().domain
|
||||
subject = 'Confirm password reset at %s' % domain
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'registration/password_reset_email.txt', {
|
||||
'domain': domain,
|
||||
'auth': auth,
|
||||
'username': to_email,
|
||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
||||
})
|
||||
|
||||
success = True
|
||||
else:
|
||||
form = RecoverPasswordForm()
|
||||
return render_to_response('registration/password_reset.html',
|
||||
{'form': form,
|
||||
'success': success},
|
||||
context_instance=RequestContext(request))
|
||||
form = ResetPasswordForm()
|
||||
return render(request, 'registration/password_reset.html', {
|
||||
'form': form,
|
||||
'success': success,
|
||||
})
|
||||
|
||||
|
||||
def confirm_password_reset(request, username, date, realm, hash):
|
||||
form, username, success = process_confirmation(request, username, date, realm, hash)
|
||||
return render_to_response('registration/change_password.html',
|
||||
{'form': form,
|
||||
'success': success,
|
||||
'username': username},
|
||||
context_instance=RequestContext(request))
|
||||
def confirm_password_reset(request, auth):
|
||||
try:
|
||||
email = django.core.signing.loads(auth, salt="password_reset", max_age=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK * 24 * 60 * 60)
|
||||
except django.core.signing.BadSignature:
|
||||
raise Http404("Invalid or expired auth")
|
||||
|
||||
user = get_object_or_404(User, username=email)
|
||||
|
||||
success = False
|
||||
if request.method == 'POST':
|
||||
form = PasswordForm(request.POST)
|
||||
if form.is_valid():
|
||||
password = form.cleaned_data["password"]
|
||||
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
# password is also stored in htpasswd file
|
||||
save_htpasswd_file(user.username, password)
|
||||
else:
|
||||
form = PasswordForm()
|
||||
|
||||
return render(request, 'registration/change_password.html', {
|
||||
'form': form,
|
||||
'email': email,
|
||||
'success': success,
|
||||
})
|
||||
|
||||
# def ajax_check_username(request):
|
||||
# username = request.GET.get('username', '')
|
||||
# error = False
|
||||
# if User.objects.filter(username=username).count():
|
||||
# error = _('This email address is already registered')
|
||||
# return HttpResponse(json.dumps({'error': error}), content_type='text/plain')
|
||||
|
||||
def test_email(request):
|
||||
"""Set email address to which email generated in the system will be sent."""
|
||||
if settings.SERVER_MODE == "production":
|
||||
|
@ -249,11 +344,10 @@ def test_email(request):
|
|||
else:
|
||||
form = TestEmailForm(initial=dict(email=request.COOKIES.get('testmailcc')))
|
||||
|
||||
r = render_to_response('ietfauth/testemail.html',
|
||||
dict(form=form,
|
||||
cookie=cookie if cookie != None else request.COOKIES.get("testmailcc", "")
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
r = render(request, 'ietfauth/testemail.html', {
|
||||
"form": form,
|
||||
"cookie": cookie if cookie != None else request.COOKIES.get("testmailcc", "")
|
||||
})
|
||||
|
||||
if cookie != None:
|
||||
r.set_cookie("testmailcc", cookie)
|
||||
|
|
|
@ -14,12 +14,15 @@ from ietf.utils.mail import send_mail_preformatted
|
|||
|
||||
class PersonInfo(models.Model):
|
||||
time = models.DateTimeField(default=datetime.datetime.now) # When this Person record entered the system
|
||||
name = models.CharField(max_length=255, db_index=True) # The normal unicode form of the name. This must be
|
||||
# set to the same value as the ascii-form if equal.
|
||||
ascii = models.CharField(max_length=255) # The normal ascii-form of the name.
|
||||
ascii_short = models.CharField(max_length=32, null=True, blank=True) # The short ascii-form of the name. Also in alias table if non-null
|
||||
address = models.TextField(max_length=255, blank=True)
|
||||
affiliation = models.CharField(max_length=255, blank=True)
|
||||
# The normal unicode form of the name. This must be
|
||||
# set to the same value as the ascii-form if equal.
|
||||
name = models.CharField(max_length=255, db_index=True, help_text="Preferred form of name.")
|
||||
# The normal ascii-form of the name.
|
||||
ascii = models.CharField("ASCII", max_length=255, help_text="Name as rendered in ASCII (Latin, unaccented) characters.")
|
||||
# The short ascii-form of the name. Also in alias table if non-null
|
||||
ascii_short = models.CharField("ASCII short", max_length=32, null=True, blank=True, help_text="Example: A. Nonymous. Fill in this with initials and surname only if taking the initials and surname of the ASCII name above produces an incorrect initials-only form. (Blank is OK).")
|
||||
affiliation = models.CharField(max_length=255, blank=True, help_text="Employer, university, sponsor, etc.")
|
||||
address = models.TextField(max_length=255, blank=True, help_text="Postal mailing address.")
|
||||
|
||||
def __unicode__(self):
|
||||
return self.plain_name()
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
{% autoescape off %}
|
||||
Hello,
|
||||
|
||||
We have received a request to add the email address '{{ email }}'
|
||||
to the user account '{{ user }}' at '{{ domain }}'.
|
||||
If you requested this change, please confirm that this is your email
|
||||
address by clicking on following link:
|
||||
{% filter wordwrap:73 %}We have received a request to add the email address {{ email }} to the user account '{{ person.user }}' at {{ domain }}. If you requested this change, please confirm that this is your email address by clicking on following link:{% endfilter %}
|
||||
|
||||
https://{{ domain }}{% url "confirm_new_email" user today email auth %}
|
||||
https://{{ domain }}{% url "confirm_new_email" auth %}
|
||||
|
||||
This link will expire in {{ expire }} days.
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{% else %}
|
||||
<h1>Change password</h1>
|
||||
|
||||
<p>You can change the password below for your user {{ username }} below.</p>
|
||||
<p>You can change the password below for your user {{ email }} below.</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
{% if success %}
|
||||
<h1>Account creation successful</h1>
|
||||
|
||||
<p>Your account with login name {{ email }} has been created, using the password you have selected.</p>
|
||||
<a type="a" class="btn btn-primary" href="/accounts/login/" rel="nofollow">Sign in</a>
|
||||
<p>Your account with login {{ email }} has been created, using the password you have selected.</p>
|
||||
<a type="a" class="btn btn-primary" href="{% url "account_login" %}" rel="nofollow">Sign in</a>
|
||||
|
||||
{% else %}
|
||||
<h1>Complete account creation</h1>
|
||||
|
||||
<p>In order to complete the setup of your account with login name {{ email }}, please choose a password:</p>
|
||||
<p>In order to complete the setup of your account with login {{ email }}, please choose a password:</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load origin bootstrap3 %}
|
||||
|
||||
{% block title %}Confirm new email address{% endblock %}
|
||||
|
||||
|
@ -8,17 +8,28 @@
|
|||
{% origin %}
|
||||
<h1>Confirm new email address</h1>
|
||||
|
||||
{% if success %}
|
||||
<p>Your account with login name {{ username }} has been updated to include the email address {{ email }}.</p>
|
||||
<a class="btn btn-default" href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
||||
{% if not can_confirm %}
|
||||
<p class="alert alert-danger">An error has occured when attempting to add the email address {{ email }} to your account {{ username }}.</p>
|
||||
|
||||
{% bootstrap_form_errors form %}
|
||||
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url "account_profile" %}">Edit profile</a>
|
||||
</p>
|
||||
{% elif new_email_obj %}
|
||||
<p>Your account {{ username }} has been updated to include the email address {{ email }}.</p>
|
||||
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url "account_profile" %}">Edit profile</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="alert alert-danger">An error has occured when attempting to add the email address '{{ email }}' to your account '{{ username }}'.<p>
|
||||
<ul>
|
||||
{% for field,msgs in error.items %}
|
||||
{% for msg in msgs %}
|
||||
<li><b>{{field}}</b>: {{msg}}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>Confirm that you want to add the email address {{ email }} to your account {{ username }}.</p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-warning" name="action" value="confirm">Confirm email address</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,23 +2,18 @@
|
|||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block title %}Profile update{% endblock %}
|
||||
{% block title %}Profile update successful{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
{% if success %}
|
||||
<h1>Profile update successful</h1>
|
||||
|
||||
<p>Your account has been successfully updated to reflect the changes you submitted.</p>
|
||||
{% for email in new_emails %}
|
||||
<p class="alert alert-info"><b>A confirmation email has been sent to {{email}}.</b> The email will be activated after you click on the link it contains.</p>
|
||||
{% endfor %}
|
||||
<a class="btn btn-default" href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
||||
{% else %}
|
||||
<h1>Profile update unsuccessful</h1>
|
||||
<p >An error has occurred when attempting to update your account.<p>
|
||||
{% if error %}
|
||||
<p class="alert alert-danger">{{ error }}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<h1>Profile update successful</h1>
|
||||
|
||||
<p>Your account has been updated to reflect the changes you submitted.</p>
|
||||
|
||||
{% for email in email_confirmations %}
|
||||
<p class="alert alert-info"><b>A confirmation email has been sent to {{email}}.</b> The email will be activated after you click on the link it contains.</p>
|
||||
{% endfor %}
|
||||
|
||||
<a class="btn btn-default" href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
{% autoescape off %}
|
||||
Hello,
|
||||
|
||||
We have received an account creation request for {{ username }}
|
||||
at {{ domain }}. In order to set a new password for the
|
||||
{{ username }} account, please go to the following link and
|
||||
follow the instructions there:
|
||||
{% filter wordwrap:73 %}We have received an account creation request for {{ username }} at {{ domain }}. In order to set a new password for the {{ username }} account, please go to the following link and follow the instructions there:{% endfilter %}
|
||||
|
||||
https://{{ domain }}{% url "confirm_account" username today realm auth %}
|
||||
https://{{ domain }}{% url "confirm_account" auth %}
|
||||
|
||||
This link will expire in {{ expire }} days.
|
||||
|
||||
|
@ -14,4 +11,4 @@ Best regards,
|
|||
|
||||
The datatracker login manager service
|
||||
(for the IETF Secretariat)
|
||||
{% endautoescape %}
|
||||
{% endautoescape %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load widget_tweaks %}
|
||||
{% load widget_tweaks bootstrap3 %}
|
||||
|
||||
{% block title %}Profile for {{ user }}{% endblock %}
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
|||
<form class="form-horizontal" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form_errors person_form 'non_fields' %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">User name</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -20,32 +22,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Roles</label>
|
||||
<div class="col-sm-10">
|
||||
{% for role in roles %}
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
<select class="form-control" name="role_email_{{role.id}}">
|
||||
{% for email in emails %}
|
||||
<option value="{{email.address}}" {% if email.address == role.email.address %}selected{% endif %}>
|
||||
{% if email.active %}
|
||||
{{email}}
|
||||
{% else %}
|
||||
({{email}})
|
||||
{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="help-block">Email to use for <i>{{ role.name|lower }}</i> role in {{ role.group.acronym|upper }} ({{ role.group.type }}).</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Email addresses</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -54,8 +30,8 @@
|
|||
{% for email in emails %}
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="{{email.address}}" {% if email.active %}checked{% endif %} onchange="style_email(0, this)">
|
||||
{{email}}
|
||||
<input type="checkbox" name="active_emails" value="{{ email.pk }}" {% if email.active %}checked{% endif %}>
|
||||
{{ email }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -67,85 +43,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% for f in new_email_forms %}
|
||||
{% bootstrap_field f.new_email layout="horizontal" show_label=False %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button class="btn btn-default btn-sm" onclick="add_email(); return false">Add email address</button>
|
||||
<div class="new-emails"></div>
|
||||
|
||||
<button class="btn btn-default btn-sm add-email">Add email address</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{person_form.name.label}}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
{{person_form.name|add_class:"form-control"}}
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="help-block">The preferred form of your name.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% for role in roles %}
|
||||
{% bootstrap_field role.email_form.email layout="horizontal" show_label=False %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{person_form.ascii.label|upper}}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
{{person_form.ascii|add_class:"form-control"}}
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="help-block">Your name as rendered in ASCII (Latin, unaccented) characters.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{person_form.ascii_short.label}}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
{{person_form.ascii_short|add_class:"form-control"}}
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<span class="help-block">
|
||||
Example: A. Nonymous. Fill in this with initials and surname only if
|
||||
taking the initials and surname of your ASCII name above produces an incorrect
|
||||
initials-only form. (Blank is ok).
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{person_form.affiliation.label}}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
{{person_form.affiliation|add_class:"form-control"}}
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="help-block">Employer, university, sponsor, etc.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Postal address</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
{{person_form.address|add_class:"form-control"}}
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="help-block">Postal mailing address.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_form person_form layout="horizontal" %}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
|
@ -158,23 +72,24 @@
|
|||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("input[type=checkbox]").each(style_email);
|
||||
$("input[name=active_emails]").on("change keypress click", function () {
|
||||
if (this.checked) {
|
||||
$(this).parent().addClass("text-success");;
|
||||
$(this).parent().removeClass("text-danger line-through");
|
||||
} else {
|
||||
$(this).parent().addClass("text-danger line-through");
|
||||
$(this).parent().removeClass("text-success");
|
||||
}
|
||||
}).trigger("change");
|
||||
|
||||
$(".add-email").click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var container = $(this).closest("form").find(".new-emails");
|
||||
|
||||
$('<input class="form-control" name="new_email" placeholder="Enter new email address...">').appendTo(container).focus();
|
||||
})
|
||||
});
|
||||
|
||||
function style_email(i, e) {
|
||||
if (e.checked) {
|
||||
$(e).parent().addClass("text-success");;
|
||||
$(e).parent().removeClass("text-danger line-through");
|
||||
} else {
|
||||
$(e).parent().addClass("text-danger line-through");
|
||||
$(e).parent().removeClass("text-success");
|
||||
}
|
||||
}
|
||||
|
||||
function add_email() {
|
||||
$("#emails").append('<input type="email" class="form-control" name="new_email_' + $.now() + '" placeholder="Enter new email address...">');
|
||||
$("#emails").children().last().focus();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -27,6 +27,5 @@
|
|||
<button type="submit" class="btn btn-warning">Reset password</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
{% autoescape off %}
|
||||
Hello,
|
||||
|
||||
We have received a password reset request for {{ username }}
|
||||
at {{ domain }}. In order to set a new password for the
|
||||
{{ username }} account, please go to the following link and
|
||||
follow the instructions there:
|
||||
{% filter wordwrap:73 %}We have received a password reset request for {{ username }} at {{ domain }}. In order to set a new password for the {{ username }} account, please go to the following link and follow the instructions there:{% endfilter %}
|
||||
|
||||
https://{{ domain }}{% url "confirm_password_reset" username today realm auth %}
|
||||
https://{{ domain }}{% url "confirm_password_reset" auth %}
|
||||
|
||||
This link will expire in {{ expire }} days.
|
||||
|
||||
|
@ -17,4 +14,4 @@ Best regards,
|
|||
|
||||
The datatracker login manager service
|
||||
(for the IETF Secretariat)
|
||||
{% endautoescape %}
|
||||
{% endautoescape %}
|
||||
|
|
Loading…
Reference in a new issue