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
|
@ -373,9 +373,11 @@ class IetfAuthTests(TestCase):
|
|||
|
||||
def test_reset_password(self):
|
||||
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.set_password("forgotten")
|
||||
user = User.objects.create(username=email, email=email)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
p = Person.objects.create(name="Some One", ascii="Some One", user=user)
|
||||
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.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):
|
||||
review_req = ReviewRequestFactory()
|
||||
assignment = ReviewAssignmentFactory(review_request=review_req,reviewer=EmailFactory(person__user__username='reviewer'))
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
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
|
||||
#from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date
|
||||
from collections import defaultdict
|
||||
|
@ -418,7 +418,16 @@ def password_reset(request):
|
|||
if form.is_valid():
|
||||
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
|
||||
subject = 'Confirm password reset at %s' % domain
|
||||
|
@ -429,7 +438,7 @@ def password_reset(request):
|
|||
'domain': domain,
|
||||
'auth': auth,
|
||||
'username': username,
|
||||
'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
|
||||
'expire': settings.MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK,
|
||||
})
|
||||
|
||||
success = True
|
||||
|
@ -443,11 +452,16 @@ def password_reset(request):
|
|||
|
||||
def confirm_password_reset(request, auth):
|
||||
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:
|
||||
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
|
||||
if request.method == 'POST':
|
||||
|
|
|
@ -1007,6 +1007,7 @@ DE_GFM_BINARY = '/usr/bin/de-gfm.ruby2.5'
|
|||
|
||||
# Account settings
|
||||
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
|
||||
MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK = 60
|
||||
HTPASSWD_COMMAND = "/usr/bin/htpasswd"
|
||||
HTPASSWD_FILE = "/www/htpasswd"
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ Hello,
|
|||
|
||||
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
|
||||
credentials have been left untouched.
|
||||
|
|
Loading…
Reference in a new issue