Add throttling to validate token and reset password end points

VAN-312
This commit is contained in:
Adeel Khan
2021-02-18 08:20:00 +05:00
parent 2d047b943f
commit d9ec42c425
7 changed files with 97 additions and 1 deletions

View File

@@ -2340,6 +2340,9 @@ DISABLE_DEPRECATED_SIGNUP_URL = False
LOGISTRATION_RATELIMIT_RATE = '100/5m'
LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '30/5m'
LOGISTRATION_API_RATELIMIT = '20/m'
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = '30/7d'
RESET_PASSWORD_API_RATELIMIT = '30/7d'
##### REGISTRATION RATE LIMIT SETTINGS #####
REGISTRATION_VALIDATION_RATELIMIT = '30/7d'

View File

@@ -331,7 +331,8 @@ LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '6/5m'
LOGISTRATION_API_RATELIMIT = '5/m'
REGISTRATION_VALIDATION_RATELIMIT = '5/minute'
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = '2/m'
RESET_PASSWORD_API_RATELIMIT = '2/m'
# Don't tolerate deprecated edx-platform import usage in tests.
ERROR_ON_DEPRECATED_EDX_PLATFORM_IMPORTS = True

View File

@@ -4370,6 +4370,8 @@ RATELIMIT_RATE = '120/m'
LOGISTRATION_RATELIMIT_RATE = '100/5m'
LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '30/5m'
LOGISTRATION_API_RATELIMIT = '20/m'
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = '30/7d'
RESET_PASSWORD_API_RATELIMIT = '30/7d'
##### PASSWORD RESET RATE LIMIT SETTINGS #####
PASSWORD_RESET_IP_RATE = '1/m'

View File

@@ -605,6 +605,10 @@ MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = ENV_TOKENS.get(
##### LOGISTRATION RATE LIMIT SETTINGS #####
LOGISTRATION_RATELIMIT_RATE = ENV_TOKENS.get('LOGISTRATION_RATELIMIT_RATE', LOGISTRATION_RATELIMIT_RATE)
LOGISTRATION_API_RATELIMIT = ENV_TOKENS.get('LOGISTRATION_API_RATELIMIT', LOGISTRATION_API_RATELIMIT)
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = ENV_TOKENS.get(
'RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT', RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT
)
RESET_PASSWORD_API_RATELIMIT = ENV_TOKENS.get('RESET_PASSWORD_API_RATELIMIT', RESET_PASSWORD_API_RATELIMIT)
##### REGISTRATION RATE LIMIT SETTINGS #####
REGISTRATION_VALIDATION_RATELIMIT = ENV_TOKENS.get(

View File

@@ -599,6 +599,8 @@ LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '6/5m'
LOGISTRATION_API_RATELIMIT = '5/m'
REGISTRATION_VALIDATION_RATELIMIT = '5/minute'
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = '2/m'
RESET_PASSWORD_API_RATELIMIT = '2/m'
# Don't tolerate deprecated edx-platform import usage in tests.
ERROR_ON_DEPRECATED_EDX_PLATFORM_IMPORTS = True

View File

@@ -26,6 +26,7 @@ from edx_ace.recipient import Recipient
from eventtracking import tracker
from ratelimit.decorators import ratelimit
from rest_framework.response import Response
from rest_framework.throttling import AnonRateThrottle
from rest_framework.views import APIView
from common.djangoapps.edxmako.shortcuts import render_to_string
@@ -642,7 +643,35 @@ def password_change_request_handler(request):
return HttpResponseBadRequest(_("No email address provided."))
def _get_rate(rate):
"""
Given the request rate string, return a two tuple of:
<allowed number of requests>, <period of time in seconds>
"""
requests, duration = rate.split('/')
num_requests = int(requests)
num = int(duration[:-1] if duration[:-1] else 1)
symbol = duration[-1:]
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[symbol] * num
return (num_requests, duration)
class ResetTokenValidationThrottle(AnonRateThrottle):
"""
Setting rate limit for token validation
"""
rate = settings.RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT
def parse_rate(self, rate):
return _get_rate(rate)
class PasswordResetTokenValidation(APIView): # lint-amnesty, pylint: disable=missing-class-docstring
"""
API to validate generated password reset token
"""
throttle_classes = [ResetTokenValidationThrottle]
def post(self, request):
""" HTTP end-point to validate password reset token. """
@@ -668,7 +697,21 @@ class PasswordResetTokenValidation(APIView): # lint-amnesty, pylint: disable=mi
return Response({'is_valid': is_valid})
class PasswordResetThrottle(AnonRateThrottle):
"""
Setting rate limit for password reset
"""
rate = settings.RESET_PASSWORD_API_RATELIMIT
def parse_rate(self, rate):
return _get_rate(rate)
class LogistrationPasswordResetView(APIView): # lint-amnesty, pylint: disable=missing-class-docstring
"""
API to update new password credentials for a correct token
"""
throttle_classes = [PasswordResetThrottle]
def post(self, request, **kwargs):
""" Reset learner password using passed token and new credentials """

View File

@@ -709,6 +709,24 @@ class PasswordResetTokenValidateViewTest(UserAPITestCase):
self.user = User.objects.get(pk=self.user.pk)
assert not self.user.is_active
@override_settings(
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'validate_token',
}
}
)
def test_reset_password_token_api_throttle(self):
"""
Test that the reset password token validation endpoint is throttling
"""
for _ in range(int(settings.RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT.split('/')[0])):
response = self.client.post(self.url, data={'token': self.token})
assert response.status_code != 429
response = self.client.post(self.url, data={'token': self.token})
assert response.status_code == 429
@ddt.ddt
@unittest.skipUnless(
@@ -831,3 +849,26 @@ class ResetPasswordAPITests(EventTestMixin, CacheIsolationTestCase):
assert sent_message.from_email == from_email
assert len(sent_message.to) == 1
assert updated_user.email in sent_message.to[0]
@override_settings(
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'reset_password',
}
}
)
def test_password_reset_api_throttle(self):
"""
Test that the reset password end point is throttling
"""
path = reverse(
"logistration_password_reset",
kwargs={"uidb36": self.uidb36, "token": self.token}
)
request_param = {'new_password1': 'new_password1', 'new_password2': 'new_password1'}
for _ in range(int(settings.RESET_PASSWORD_API_RATELIMIT.split('/')[0])):
response = self.client.post(path, request_param)
assert response.status_code != 429
response = self.client.post(path, request_param)
assert response.status_code == 429