Ratelimit login_user endpoint.
Ratelimited `login_user` endpoint using `django-ratelimit`, also decreased default value of logistration rate limit to 100 requests per five minutes per IP. PROD-1877
This commit is contained in:
@@ -2253,4 +2253,4 @@ DISABLE_DEPRECATED_SIGNIN_URL = False
|
||||
DISABLE_DEPRECATED_SIGNUP_URL = False
|
||||
|
||||
##### LOGISTRATION RATE LIMIT SETTINGS #####
|
||||
LOGISTRATION_RATELIMIT_RATE = '500/5m'
|
||||
LOGISTRATION_RATELIMIT_RATE = '100/5m'
|
||||
|
||||
@@ -298,3 +298,6 @@ SYSTEM_WIDE_ROLE_CLASSES = os.environ.get("SYSTEM_WIDE_ROLE_CLASSES", [])
|
||||
DEFAULT_MOBILE_AVAILABLE = True
|
||||
|
||||
PROCTORING_SETTINGS = {}
|
||||
|
||||
##### LOGISTRATION RATE LIMIT SETTINGS #####
|
||||
LOGISTRATION_RATELIMIT_RATE = '5/5m'
|
||||
|
||||
@@ -16,6 +16,7 @@ import ddt
|
||||
import six
|
||||
from celery.states import FAILURE, SUCCESS
|
||||
from django.contrib.auth.models import User
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from mock import patch
|
||||
from six import text_type
|
||||
@@ -70,6 +71,7 @@ class TestIntegrationTask(InstructorTaskModuleTestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(RATELIMIT_ENABLE=False)
|
||||
class TestRescoringTask(TestIntegrationTask):
|
||||
"""
|
||||
Integration-style tests for rescoring problems in a background task.
|
||||
@@ -432,6 +434,7 @@ class TestRescoringTask(TestIntegrationTask):
|
||||
self.check_state(user, descriptor, 0, 1, expected_attempts=2)
|
||||
|
||||
|
||||
@override_settings(RATELIMIT_ENABLE=False)
|
||||
class TestResetAttemptsTask(TestIntegrationTask):
|
||||
"""
|
||||
Integration-style tests for resetting problem attempts in a background task.
|
||||
|
||||
@@ -3775,7 +3775,7 @@ RATELIMIT_ENABLE = True
|
||||
RATELIMIT_RATE = '120/m'
|
||||
|
||||
##### LOGISTRATION RATE LIMIT SETTINGS #####
|
||||
LOGISTRATION_RATELIMIT_RATE = '500/5m'
|
||||
LOGISTRATION_RATELIMIT_RATE = '100/5m'
|
||||
|
||||
############### Settings for Retirement #####################
|
||||
RETIRED_USERNAME_PREFIX = 'retired__user_'
|
||||
|
||||
@@ -24,6 +24,7 @@ from django.views.decorators.csrf import csrf_exempt, csrf_protect, ensure_csrf_
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from edx_django_utils.monitoring import set_custom_metric
|
||||
from ratelimit.decorators import ratelimit
|
||||
from ratelimitbackend.exceptions import RateLimitException
|
||||
from rest_framework.views import APIView
|
||||
|
||||
@@ -382,6 +383,12 @@ def finish_auth(request): # pylint: disable=unused-argument
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(['POST'])
|
||||
@ratelimit(
|
||||
key='openedx.core.djangoapps.util.ratelimit.real_ip',
|
||||
rate=settings.LOGISTRATION_RATELIMIT_RATE,
|
||||
method='POST',
|
||||
block=True
|
||||
)
|
||||
def login_user(request):
|
||||
"""
|
||||
AJAX request to log in the user.
|
||||
|
||||
@@ -18,6 +18,7 @@ from django.http import HttpResponse
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from six.moves import range
|
||||
|
||||
@@ -351,7 +352,8 @@ class LoginTest(SiteMixin, CacheIsolationTestCase):
|
||||
self._assert_audit_log(mock_audit_log, 'info', [u'Logout'])
|
||||
self._assert_not_in_audit_log(mock_audit_log, 'info', [u'test'])
|
||||
|
||||
def test_login_ratelimited_success(self):
|
||||
@override_settings(RATELIMIT_ENABLE=False)
|
||||
def test_excessive_login_attempts_success(self):
|
||||
# Try (and fail) logging in with fewer attempts than the limit of 30
|
||||
# and verify that you can still successfully log in afterwards.
|
||||
for i in range(20):
|
||||
@@ -362,7 +364,8 @@ class LoginTest(SiteMixin, CacheIsolationTestCase):
|
||||
response, _audit_log = self._login_response(self.user_email, self.password)
|
||||
self._assert_response(response, success=True)
|
||||
|
||||
def test_login_ratelimited(self):
|
||||
@override_settings(RATELIMIT_ENABLE=False)
|
||||
def test_excessive_login_attempts(self):
|
||||
# try logging in 30 times, the default limit in the number of failed
|
||||
# login attempts in one 5 minute period before the rate gets limited
|
||||
for i in range(30):
|
||||
@@ -372,6 +375,26 @@ class LoginTest(SiteMixin, CacheIsolationTestCase):
|
||||
response, _audit_log = self._login_response(self.user_email, 'wrong_password')
|
||||
self._assert_response(response, success=False, value='Too many failed login attempts')
|
||||
|
||||
def test_login_ratelimited(self):
|
||||
"""
|
||||
Test that login endpoint is IP ratelimited and only allow 5 requests
|
||||
per 5 minutes per IP.
|
||||
"""
|
||||
for i in range(5):
|
||||
password = u'test_password{0}'.format(i)
|
||||
response, _audit_log = self._login_response(self.user_email, password)
|
||||
self._assert_response(response, success=False)
|
||||
|
||||
response, _audit_log = self._login_response(self.user_email, self.password)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# now reset the time to 6 min from now in future and verify that it will
|
||||
# allow another request from same IP and user can successfully login
|
||||
reset_time = datetime.datetime.utcnow() + datetime.timedelta(seconds=361)
|
||||
with freeze_time(reset_time):
|
||||
response, _audit_log = self._login_response(self.user_email, self.password)
|
||||
self._assert_response(response, success=True)
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"DISABLE_SET_JWT_COOKIES_FOR_TESTS": False})
|
||||
def test_login_refresh(self):
|
||||
def _assert_jwt_cookie_present(response):
|
||||
|
||||
Reference in New Issue
Block a user