Merged in [19967] from rjsparks@nostrum.com:
From Kesara Rathnayake: Expire password reset links on use, password change through other mechanics, login, or a short configurable time (initially one hour). Patched in at 7.45.0.p2. - Legacy-Id: 19968 Note: SVN reference [19967] has been migrated to Git commit 682392081bddbd1b8653df9135388e6b7c48ee1c
This commit is contained in:
parent
95cb3f041e
commit
e3aa43eea5
ietf
|
@ -373,9 +373,11 @@ class IetfAuthTests(TestCase):
|
||||||
|
|
||||||
def test_reset_password(self):
|
def test_reset_password(self):
|
||||||
url = urlreverse(ietf.ietfauth.views.password_reset)
|
url = urlreverse(ietf.ietfauth.views.password_reset)
|
||||||
|
email = 'someone@example.com'
|
||||||
|
password = 'foobar'
|
||||||
|
|
||||||
user = User.objects.create(username="someone@example.com", email="someone@example.com")
|
user = User.objects.create(username=email, email=email)
|
||||||
user.set_password("forgotten")
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
p = Person.objects.create(name="Some One", ascii="Some One", user=user)
|
p = Person.objects.create(name="Some One", ascii="Some One", user=user)
|
||||||
Email.objects.create(address=user.username, person=p, origin=user.username)
|
Email.objects.create(address=user.username, person=p, origin=user.username)
|
||||||
|
@ -414,6 +416,39 @@ class IetfAuthTests(TestCase):
|
||||||
self.assertEqual(len(q("form .has-error")), 0)
|
self.assertEqual(len(q("form .has-error")), 0)
|
||||||
self.assertTrue(self.username_in_htpasswd_file(user.username))
|
self.assertTrue(self.username_in_htpasswd_file(user.username))
|
||||||
|
|
||||||
|
# reuse reset url
|
||||||
|
r = self.client.get(confirm_url)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
# login after reset request
|
||||||
|
empty_outbox()
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
r = self.client.post(url, { 'username': user.username })
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(len(outbox), 1)
|
||||||
|
confirm_url = self.extract_confirm_url(outbox[-1])
|
||||||
|
|
||||||
|
r = self.client.post(urlreverse(ietf.ietfauth.views.login), {'username': email, 'password': password})
|
||||||
|
|
||||||
|
r = self.client.get(confirm_url)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
# change password after reset request
|
||||||
|
empty_outbox()
|
||||||
|
|
||||||
|
r = self.client.post(url, { 'username': user.username })
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(len(outbox), 1)
|
||||||
|
confirm_url = self.extract_confirm_url(outbox[-1])
|
||||||
|
|
||||||
|
user.set_password('newpassword')
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
r = self.client.get(confirm_url)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
def test_review_overview(self):
|
def test_review_overview(self):
|
||||||
review_req = ReviewRequestFactory()
|
review_req = ReviewRequestFactory()
|
||||||
assignment = ReviewAssignmentFactory(review_request=review_req,reviewer=EmailFactory(person__user__username='reviewer'))
|
assignment = ReviewAssignmentFactory(review_request=review_req,reviewer=EmailFactory(person__user__username='reviewer'))
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
from datetime import date as Date
|
from datetime import date as Date, datetime as DateTime
|
||||||
# needed if we revert to higher barrier for account creation
|
# needed if we revert to higher barrier for account creation
|
||||||
#from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date
|
#from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
@ -418,7 +418,16 @@ def password_reset(request):
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
username = form.cleaned_data['username']
|
username = form.cleaned_data['username']
|
||||||
|
|
||||||
auth = django.core.signing.dumps(username, salt="password_reset")
|
data = { 'username': username }
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
data['password'] = user.password and user.password[-4:]
|
||||||
|
if user.last_login:
|
||||||
|
data['last_login'] = user.last_login.timestamp()
|
||||||
|
else:
|
||||||
|
data['last_login'] = None
|
||||||
|
|
||||||
|
auth = django.core.signing.dumps(data, salt="password_reset")
|
||||||
|
|
||||||
domain = Site.objects.get_current().domain
|
domain = Site.objects.get_current().domain
|
||||||
subject = 'Confirm password reset at %s' % domain
|
subject = 'Confirm password reset at %s' % domain
|
||||||
|
@ -429,7 +438,7 @@ def password_reset(request):
|
||||||
'domain': domain,
|
'domain': domain,
|
||||||
'auth': auth,
|
'auth': auth,
|
||||||
'username': username,
|
'username': username,
|
||||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
'expire': settings.MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK,
|
||||||
})
|
})
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
|
@ -443,11 +452,16 @@ def password_reset(request):
|
||||||
|
|
||||||
def confirm_password_reset(request, auth):
|
def confirm_password_reset(request, auth):
|
||||||
try:
|
try:
|
||||||
username = django.core.signing.loads(auth, salt="password_reset", max_age=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK * 24 * 60 * 60)
|
data = django.core.signing.loads(auth, salt="password_reset", max_age=settings.MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK * 60)
|
||||||
|
username = data['username']
|
||||||
|
password = data['password']
|
||||||
|
last_login = None
|
||||||
|
if data['last_login']:
|
||||||
|
last_login = DateTime.fromtimestamp(data['last_login'])
|
||||||
except django.core.signing.BadSignature:
|
except django.core.signing.BadSignature:
|
||||||
raise Http404("Invalid or expired auth")
|
raise Http404("Invalid or expired auth")
|
||||||
|
|
||||||
user = get_object_or_404(User, username=username)
|
user = get_object_or_404(User, username=username, password__endswith=password, last_login=last_login)
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
|
|
@ -1007,6 +1007,7 @@ DE_GFM_BINARY = '/usr/bin/de-gfm.ruby2.5'
|
||||||
|
|
||||||
# Account settings
|
# Account settings
|
||||||
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
|
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
|
||||||
|
MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK = 60
|
||||||
HTPASSWD_COMMAND = "/usr/bin/htpasswd"
|
HTPASSWD_COMMAND = "/usr/bin/htpasswd"
|
||||||
HTPASSWD_FILE = "/www/htpasswd"
|
HTPASSWD_FILE = "/www/htpasswd"
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ Hello,
|
||||||
|
|
||||||
https://{{ domain }}{% url "ietf.ietfauth.views.confirm_password_reset" auth %}
|
https://{{ domain }}{% url "ietf.ietfauth.views.confirm_password_reset" auth %}
|
||||||
|
|
||||||
This link will expire in {{ expire }} days.
|
This link will expire in {{ expire }} minutes.
|
||||||
|
|
||||||
If you have not requested a password reset you can ignore this email, your
|
If you have not requested a password reset you can ignore this email, your
|
||||||
credentials have been left untouched.
|
credentials have been left untouched.
|
||||||
|
|
Loading…
Reference in a new issue