Skip to content

Commit 6c93e25

Browse files
committed
Add htpasswd management functionality.
- Legacy-Id: 3386
1 parent f47ca87 commit 6c93e25

5 files changed

Lines changed: 237 additions & 3 deletions

File tree

ietf/ietfauth/forms.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import datetime
2+
import hashlib
3+
import subprocess
4+
5+
from django import forms
6+
from django.conf import settings
7+
from django.contrib.auth.models import User
8+
from django.contrib.auth.forms import PasswordResetForm
9+
from django.contrib.sites.models import Site
10+
from django.utils.translation import ugettext, ugettext_lazy as _
11+
12+
from ietf.utils.mail import send_mail
13+
from ietf.utils import debug
14+
#from redesign.person.models import Person, Email
15+
16+
17+
class RegistrationForm(forms.Form):
18+
19+
email = forms.EmailField(label="Your email")
20+
realm = 'IETF'
21+
expire = 3
22+
23+
def save(self, *args, **kwargs):
24+
self.send_email()
25+
return True
26+
27+
def send_email(self):
28+
domain = Site.objects.get_current().domain
29+
subject = 'Confirm registration at %s' % domain
30+
from_email = settings.DEFAULT_FROM_EMAIL
31+
to_email = self.cleaned_data['email']
32+
today = datetime.date.today().strftime('%Y%m%d')
33+
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, self.realm)).hexdigest()
34+
context = {
35+
'domain': domain,
36+
'today': today,
37+
'realm': self.realm,
38+
'auth': auth,
39+
'username': to_email,
40+
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
41+
}
42+
send_mail(None, to_email, from_email, subject, 'registration/creation_email.txt', context)
43+
44+
def clean_email(self):
45+
email = self.cleaned_data.get('email', '')
46+
if not email:
47+
return email
48+
# if User.objects.filter(username=email).count():
49+
# raise forms.ValidationError(_('Email already in use'))
50+
return email
51+
52+
53+
class RecoverPasswordForm(RegistrationForm):
54+
55+
realm = 'IETF'
56+
57+
def send_email(self):
58+
domain = Site.objects.get_current().domain
59+
subject = 'Password reset at %s' % domain
60+
from_email = settings.DEFAULT_FROM_EMAIL
61+
today = datetime.date.today().strftime('%Y%m%d')
62+
to_email = self.cleaned_data['email']
63+
today = datetime.date.today().strftime('%Y%m%d')
64+
auth = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, today, to_email, self.realm)).hexdigest()
65+
context = {
66+
'domain': domain,
67+
'today': today,
68+
'realm': self.realm,
69+
'auth': auth,
70+
'username': to_email,
71+
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
72+
}
73+
send_mail(None, to_email, from_email, subject, 'registration/password_reset_email.txt', context)
74+
75+
76+
class PasswordForm(forms.Form):
77+
78+
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
79+
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput,
80+
help_text=_("Enter the same password as above, for verification."))
81+
82+
def __init__(self, *args, **kwargs):
83+
self.username = kwargs.pop('username')
84+
self.update_user = kwargs.pop('update_user', False)
85+
super(PasswordForm, self).__init__(*args, **kwargs)
86+
87+
def clean_password2(self):
88+
password1 = self.cleaned_data.get("password1", "")
89+
password2 = self.cleaned_data["password2"]
90+
if password1 != password2:
91+
raise forms.ValidationError(_("The two password fields didn't match."))
92+
return password2
93+
94+
def get_password(self):
95+
return self.cleaned_data.get('password1')
96+
97+
def create_user(self):
98+
# user = User.objects.create(username=self.username,
99+
# email=self.username)
100+
# person = Person.objects.create(user=user,
101+
# name=self.username,
102+
# ascii=self.username)
103+
# Email.objects.create(person=person,
104+
# address=self.username)
105+
# return user
106+
return None
107+
108+
def get_user(self):
109+
return User.objects.get(username=self.username)
110+
111+
@debug.trace
112+
def save(self):
113+
# if self.update_user:
114+
# user = self.get_user()
115+
# else:
116+
# user = self.create_user()
117+
# user.set_password(self.get_password())
118+
# user.save()
119+
# return user
120+
debug.show("[settings.HTPASSWD_COMMAND, settings.HTPASSWD_FILE, self.username, self.get_password()]")
121+
p = subprocess.Popen([settings.HTPASSWD_COMMAND, "-b", settings.HTPASSWD_FILE, self.username, self.get_password()], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
122+
stdout, stderr = p.communicate()
123+
debug.show('stdout')
124+
debug.show('stderr')
125+
return p.returncode

ietf/ietfauth/urls.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# Copyright The IETF Trust 2007, 2009, All Rights Reserved
22

3-
from django.conf.urls.defaults import patterns
3+
from django.conf.urls.defaults import patterns, url
44
from ietf.ietfauth import views
55

66
urlpatterns = patterns('',
7+
(r'^$', views.index, None, 'account_index'),
78
(r'^login/$', views.ietf_login),
89
(r'^loggedin/$', views.ietf_loggedin),
910
(r'^profile/$', views.profile),
1011
# (r'^login/(?P<user>[a-z0-9.@]+)/(?P<passwd>.+)$', views.url_login),
1112
)
13+
14+
urlpatterns += patterns('ietf.ietfauth.views',
15+
url(r'^create/$', 'create_account', name='create_account'),
16+
url(r'^confirm/(?P<username>[\w.@+-]+)/(?P<date>[\d]+)/(?P<realm>[\w]+)/(?P<registration_hash>[a-f0-9]+)/$', 'confirm_account', name='confirm_account'),
17+
url(r'^reset/$', 'password_reset_view', name='password_reset'),
18+
url(r'^reset/confirm/(?P<username>[\w.@+-]+)/(?P<date>[\d]+)/(?P<realm>[\w]+)/(?P<reset_hash>[a-f0-9]+)/$', 'confirm_password_reset', name='confirm_password_reset'),
19+
url(r'^ajax/check_username/$', 'ajax_check_username', name='ajax_check_username'),
20+
21+
)

ietf/ietfauth/views.py

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,28 @@
3232

3333
# Copyright The IETF Trust 2007, All Rights Reserved
3434

35+
import datetime
36+
import hashlib
37+
3538
from django.template import RequestContext
36-
from django.shortcuts import render_to_response
39+
from django.http import HttpResponse, Http404, HttpResponseRedirect
40+
from django.shortcuts import get_object_or_404, render_to_response
3741
from django.contrib.auth.decorators import login_required
38-
from django.http import HttpResponseRedirect, HttpResponse
3942
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
4043
from django.contrib.auth.models import User
4144
from django.conf import settings
4245
from django.utils.http import urlquote
4346

47+
from django.contrib.auth.models import User
48+
from django.utils import simplejson as json
49+
from django.utils.translation import ugettext as _
50+
51+
from forms import (RegistrationForm, PasswordForm, RecoverPasswordForm)
52+
53+
54+
def index(request):
55+
return render_to_response('registration/index.html', context_instance=RequestContext(request))
56+
4457
def url_login(request, user, passwd):
4558
user = authenticate(username=user, password=passwd)
4659
redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '')
@@ -70,3 +83,82 @@ def ietf_loggedin(request):
7083
@login_required
7184
def profile(request):
7285
return render_to_response('registration/profile.html', context_instance=RequestContext(request))
86+
87+
88+
def create_account(request):
89+
success = False
90+
if request.method == 'POST':
91+
form = RegistrationForm(request.POST)
92+
if form.is_valid():
93+
form.save()
94+
success = True
95+
else:
96+
form = RegistrationForm()
97+
return render_to_response('registration/create.html',
98+
{'form': form,
99+
'success': success},
100+
context_instance=RequestContext(request))
101+
102+
103+
def confirm_account(request, username, date, realm, registration_hash):
104+
valid = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, date, username, realm)).hexdigest() == registration_hash
105+
if not valid:
106+
raise Http404
107+
request_date = datetime.date(int(date[:4]), int(date[4:6]), int(date[6:]))
108+
if datetime.date.today() > (request_date + datetime.timedelta(days=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK)):
109+
raise Http404
110+
success = False
111+
if request.method == 'POST':
112+
form = PasswordForm(request.POST, username=username)
113+
if form.is_valid():
114+
form.save()
115+
# TODO: Add the user in the htdigest file
116+
success = True
117+
else:
118+
form = PasswordForm(username=username)
119+
return render_to_response('registration/confirm.html',
120+
{'form': form, 'email': username, 'success': success},
121+
context_instance=RequestContext(request))
122+
123+
124+
def password_reset_view(request):
125+
success = False
126+
if request.method == 'POST':
127+
form = RecoverPasswordForm(request.POST)
128+
if form.is_valid():
129+
form.save()
130+
success = True
131+
else:
132+
form = RecoverPasswordForm()
133+
return render_to_response('registration/password_reset.html',
134+
{'form': form,
135+
'success': success},
136+
context_instance=RequestContext(request))
137+
138+
139+
def confirm_password_reset(request, username, date, realm, reset_hash):
140+
valid = hashlib.md5('%s%s%s%s' % (settings.SECRET_KEY, date, username, realm)).hexdigest() == reset_hash
141+
if not valid:
142+
raise Http404
143+
success = False
144+
if request.method == 'POST':
145+
form = PasswordForm(request.POST, update_user=True, username=username)
146+
if form.is_valid():
147+
form.save()
148+
# TODO: Update the user in the htdigest file
149+
success = True
150+
else:
151+
form = PasswordForm(username=username)
152+
return render_to_response('registration/change_password.html',
153+
{'form': form,
154+
'success': success,
155+
'username': username},
156+
context_instance=RequestContext(request))
157+
158+
def ajax_check_username(request):
159+
username = request.GET.get('username', '')
160+
error = False
161+
if User.objects.filter(username=username).count():
162+
error = _('This email is already in use')
163+
return HttpResponse(simplejson.dumps({'error': error}), mimetype='text/plain')
164+

ietf/settings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@
229229
MAX_DAILY_SUBMISSION_SIZE = 2000
230230
# End of ID Submission Tool settings
231231

232+
# Account settings
233+
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
234+
HTPASSWD_COMMAND = "/usr/bin/htpasswd2"
235+
HTPASSWD_FILE = "/www/htpasswd"
236+
232237
# Put SECRET_KEY in here, or any other sensitive or site-specific
233238
# changes. DO NOT commit settings_local.py to svn.
234239
from settings_local import *

ietf/templates/base_leftmenu.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
{% load wg_menu %}
3636
{% load ietf_filters %}
3737
<ul>
38+
<li class="sect first">Accounts</li>
39+
<li><a href="{% url account_index %}">New Account</a></li>
3840
{% if user|in_group:"Area_Director" %}
3941
<li class="sect first">AD Dashboard</li>
4042
<li><a href="{% url doc_search_by_ad name=user.get_profile.person.full_name_as_key %}">My Documents</a></li>

0 commit comments

Comments
 (0)