Skip to content

Commit 7e61ce8

Browse files
committed
Merged in ^/branch/iola/account-registration-fixes-r11167@11179 from olau@iola.dk, which contains fixes and refactoring for the account registration code, in order to better support the coming community app refactoring.
- Legacy-Id: 11180
2 parents 6be0aeb + b599dca commit 7e61ce8

24 files changed

Lines changed: 697 additions & 581 deletions

ietf/ietfauth/forms.py

Lines changed: 70 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -1,235 +1,97 @@
1-
import datetime
2-
import hashlib
3-
import subprocess
1+
import re
42

53
from django import forms
64
from django.forms import ModelForm
7-
from django.conf import settings
5+
from django.db import models
86
from django.contrib.auth.models import User
9-
from django.contrib.sites.models import Site
10-
from django.utils.translation import ugettext_lazy as _
7+
from django.utils.html import mark_safe
8+
from django.core.urlresolvers import reverse as urlreverse
119

12-
from ietf.utils.mail import send_mail
13-
from ietf.person.models import Person, Email, Alias
14-
from ietf.group.models import Role
10+
from ietf.person.models import Person, Email
1511

1612

1713
class RegistrationForm(forms.Form):
18-
1914
email = forms.EmailField(label="Your email (lowercase)")
20-
realm = 'IETF'
21-
expire = 3
22-
23-
def save(self, *args, **kwargs):
24-
# why is there a save when it doesn't save?
25-
self.send_email()
26-
return True
27-
28-
def send_email(self):
29-
domain = Site.objects.get_current().domain
30-
subject = 'Confirm registration at %s' % domain
31-
from_email = settings.DEFAULT_FROM_EMAIL
32-
to_email = self.cleaned_data['email']
33-
today = datetime.date.today().strftime('%Y%m%d')
34-
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, self.realm)).hexdigest()
35-
context = {
36-
'domain': domain,
37-
'today': today,
38-
'realm': self.realm,
39-
'auth': auth,
40-
'username': to_email,
41-
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
42-
}
43-
send_mail(self.request, to_email, from_email, subject, 'registration/creation_email.txt', context)
4415

4516
def clean_email(self):
4617
email = self.cleaned_data.get('email', '')
4718
if not email:
4819
return email
4920
if email.lower() != email:
50-
raise forms.ValidationError(_('The supplied address contained uppercase letters. Please use a lowercase email address.'))
51-
if User.objects.filter(username=email).count():
52-
raise forms.ValidationError(_('An account with the email address you provided already exists.'))
21+
raise forms.ValidationError('The supplied address contained uppercase letters. Please use a lowercase email address.')
22+
if User.objects.filter(username=email).exists():
23+
raise forms.ValidationError('An account with the email address you provided already exists.')
5324
return email
5425

5526

56-
class RecoverPasswordForm(RegistrationForm):
57-
58-
realm = 'IETF'
59-
60-
def send_email(self):
61-
domain = Site.objects.get_current().domain
62-
subject = 'Password reset at %s' % domain
63-
from_email = settings.DEFAULT_FROM_EMAIL
64-
today = datetime.date.today().strftime('%Y%m%d')
65-
to_email = self.cleaned_data['email']
66-
today = datetime.date.today().strftime('%Y%m%d')
67-
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, self.realm)).hexdigest()
68-
context = {
69-
'domain': domain,
70-
'today': today,
71-
'realm': self.realm,
72-
'auth': auth,
73-
'username': to_email,
74-
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
75-
}
76-
send_mail(self.request, to_email, from_email, subject, 'registration/password_reset_email.txt', context)
27+
class PasswordForm(forms.Form):
28+
password = forms.CharField(widget=forms.PasswordInput)
29+
password_confirmation = forms.CharField(widget=forms.PasswordInput,
30+
help_text="Enter the same password as above, for verification.")
7731

78-
def clean_email(self):
79-
email = self.cleaned_data.get('email', '')
32+
def clean_password_confirmation(self):
33+
password = self.cleaned_data.get("password", "")
34+
password_confirmation = self.cleaned_data["password_confirmation"]
35+
if password != password_confirmation:
36+
raise forms.ValidationError("The two password fields didn't match.")
37+
return password_confirmation
38+
39+
40+
def ascii_cleaner(supposedly_ascii):
41+
outside_printable_ascii_pattern = r'[^\x20-\x7F]'
42+
if re.search(outside_printable_ascii_pattern, supposedly_ascii):
43+
raise forms.ValidationError("Please only enter ASCII characters.")
44+
return supposedly_ascii
45+
46+
class PersonForm(ModelForm):
47+
class Meta:
48+
model = Person
49+
exclude = ('time', 'user')
50+
51+
def clean_ascii(self):
52+
return ascii_cleaner(self.cleaned_data.get("ascii") or u"")
53+
54+
def clean_ascii_short(self):
55+
return ascii_cleaner(self.cleaned_data.get("ascii_short") or u"")
56+
57+
58+
class NewEmailForm(forms.Form):
59+
new_email = forms.EmailField(label="New email address", required=False)
60+
61+
def clean_new_email(self):
62+
email = self.cleaned_data.get("new_email", "")
63+
if email:
64+
existing = Email.objects.filter(address=email).first()
65+
if existing:
66+
raise forms.ValidationError("Email address '%s' is already assigned to account '%s' (%s)" % (existing, existing.person and existing.person.user, existing.person))
8067
return email
8168

8269

83-
class PasswordForm(forms.Form):
70+
class RoleEmailForm(forms.Form):
71+
email = forms.ModelChoiceField(label="Role email", queryset=Email.objects.all())
72+
73+
def __init__(self, role, *args, **kwargs):
74+
super(RoleEmailForm, self).__init__(*args, **kwargs)
75+
76+
f = self.fields["email"]
77+
f.label = u"%s in %s" % (role.name, role.group.acronym.upper())
78+
f.help_text = u"Email to use for <i>%s</i> role in %s" % (role.name, role.group.name)
79+
f.queryset = f.queryset.filter(models.Q(person=role.person_id) | models.Q(role=role))
80+
f.initial = role.email_id
81+
f.choices = [(e.pk, e.address if e.active else u"({})".format(e.address)) for e in f.queryset]
82+
83+
84+
class ResetPasswordForm(forms.Form):
85+
username = forms.EmailField(label="Your email (lowercase)")
86+
87+
def clean_username(self):
88+
import ietf.ietfauth.views
89+
username = self.cleaned_data["username"]
90+
if not User.objects.filter(username=username).exists():
91+
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(ietf.ietfauth.views.create_account))))
92+
return username
8493

85-
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
86-
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput,
87-
help_text=_("Enter the same password as above, for verification."))
88-
89-
def __init__(self, *args, **kwargs):
90-
self.username = kwargs.pop('username')
91-
self.update_user = User.objects.filter(username=self.username).count() > 0
92-
super(PasswordForm, self).__init__(*args, **kwargs)
93-
94-
def clean_password2(self):
95-
password1 = self.cleaned_data.get("password1", "")
96-
password2 = self.cleaned_data["password2"]
97-
if password1 != password2:
98-
raise forms.ValidationError(_("The two password fields didn't match."))
99-
return password2
100-
101-
def get_password(self):
102-
return self.cleaned_data.get('password1')
103-
104-
def create_user(self):
105-
user = User.objects.create(username=self.username,
106-
email=self.username)
107-
email = Email.objects.filter(address=self.username)
108-
person = None
109-
if email.count():
110-
email = email[0]
111-
if email.person:
112-
person = email.person
113-
else:
114-
email = None
115-
if not person:
116-
person = Person.objects.create(user=user,
117-
name=self.username,
118-
ascii=self.username)
119-
if not email:
120-
email = Email.objects.create(address=self.username,
121-
person=person)
122-
email.person = person
123-
email.save()
124-
person.user = user
125-
person.save()
126-
return user
127-
128-
def get_user(self):
129-
return User.objects.get(username=self.username)
130-
131-
def save_password_file(self):
132-
if getattr(settings, 'USE_PYTHON_HTDIGEST', None):
133-
pass_file = settings.HTPASSWD_FILE
134-
realm = settings.HTDIGEST_REALM
135-
password = self.get_password()
136-
username = self.username
137-
prefix = '%s:%s:' % (username, realm)
138-
key = hashlib.md5(prefix + password).hexdigest()
139-
f = open(pass_file, 'r+')
140-
pos = f.tell()
141-
line = f.readline()
142-
while line:
143-
if line.startswith(prefix):
144-
break
145-
pos=f.tell()
146-
line = f.readline()
147-
f.seek(pos)
148-
f.write('%s%s\n' % (prefix, key))
149-
f.close()
150-
else:
151-
p = subprocess.Popen([settings.HTPASSWD_COMMAND, "-b", settings.HTPASSWD_FILE, self.username, self.get_password()], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
152-
stdout, stderr = p.communicate()
153-
154-
def save(self):
155-
if self.update_user:
156-
user = self.get_user()
157-
else:
158-
user = self.create_user()
159-
user.set_password(self.get_password())
160-
user.save()
161-
self.save_password_file()
162-
return user
16394

16495
class TestEmailForm(forms.Form):
16596
email = forms.EmailField(required=False)
16697

167-
class PersonForm(ModelForm):
168-
request = None
169-
new_emails = []
170-
class Meta:
171-
model = Person
172-
exclude = ('time','user')
173-
174-
def confirm_address(self,email):
175-
person = self.instance
176-
domain = Site.objects.get_current().domain
177-
user = person.user
178-
if len(email) == 0:
179-
return
180-
subject = 'Confirm email address for %s' % person.name
181-
from_email = settings.DEFAULT_FROM_EMAIL
182-
to_email = email
183-
today = datetime.date.today().strftime('%Y%m%d')
184-
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, user)).hexdigest()
185-
context = {
186-
'today': today,
187-
'domain': domain,
188-
'user': user,
189-
'email': email,
190-
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
191-
'auth': auth,
192-
}
193-
send_mail(self.request, to_email, from_email, subject, 'registration/add_email_email.txt', context)
194-
195-
def save(self, force_insert=False, force_update=False, commit=True):
196-
m = super(PersonForm, self).save(commit=False)
197-
self.new_emails = [v for k,v in self.data.items() if k[:10] == u'new_email_' and u'@' in v]
198-
199-
for email in self.new_emails:
200-
self.confirm_address(email)
201-
202-
# Process email active flags
203-
emails = Email.objects.filter(person=self.instance)
204-
for email in emails:
205-
email.active = self.data.__contains__(email.address)
206-
if commit:
207-
email.save()
208-
209-
# Process email for roles
210-
for k,v in self.data.items():
211-
if k[:11] == u'role_email_':
212-
role = Role.objects.get(id=k[11:])
213-
email = Email.objects.get(address = v)
214-
role.email = email
215-
if commit:
216-
role.save()
217-
218-
# Make sure the alias table contains any new and/or old names.
219-
old_names = set([x.name for x in Alias.objects.filter(person=self.instance)])
220-
curr_names = set([x for x in [self.instance.name,
221-
self.instance.ascii,
222-
self.instance.ascii_short,
223-
self.data['name'],
224-
self.data['ascii'],
225-
self.data['ascii_short']] if len(x)])
226-
new_names = curr_names - old_names
227-
for name in new_names:
228-
alias = Alias(person=self.instance,name=name)
229-
if commit:
230-
alias.save()
231-
232-
if commit:
233-
m.save()
234-
return m
235-

ietf/ietfauth/htpasswd.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import subprocess, hashlib
2+
3+
from django.conf import settings
4+
5+
def update_htpasswd_file(username, password):
6+
if getattr(settings, 'USE_PYTHON_HTDIGEST', None):
7+
pass_file = settings.HTPASSWD_FILE
8+
realm = settings.HTDIGEST_REALM
9+
prefix = '%s:%s:' % (username, realm)
10+
key = hashlib.md5(prefix + password).hexdigest()
11+
f = open(pass_file, 'r+')
12+
pos = f.tell()
13+
line = f.readline()
14+
while line:
15+
if line.startswith(prefix):
16+
break
17+
pos=f.tell()
18+
line = f.readline()
19+
f.seek(pos)
20+
f.write('%s%s\n' % (prefix, key))
21+
f.close()
22+
else:
23+
p = subprocess.Popen([settings.HTPASSWD_COMMAND, "-b", settings.HTPASSWD_FILE, username, password], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
24+
stdout, stderr = p.communicate()

0 commit comments

Comments
 (0)