Ratelimit the registration endpoint

PROD-880
This commit is contained in:
uzairr
2020-08-03 22:28:58 +05:00
parent 8c43855cfe
commit 7bc17c7dd9
8 changed files with 51 additions and 43 deletions

View File

@@ -2287,6 +2287,9 @@ DISABLE_DEPRECATED_SIGNUP_URL = False
##### LOGISTRATION RATE LIMIT SETTINGS #####
LOGISTRATION_RATELIMIT_RATE = '100/5m'
##### REGISTRATION RATE LIMIT SETTINGS #####
REGISTRATION_VALIDATION_RATELIMIT = '30/7d'
##### PASSWORD RESET RATE LIMIT SETTINGS #####
PASSWORD_RESET_IP_RATE = '1/m'
PASSWORD_RESET_EMAIL_RATE = '2/h'

View File

@@ -267,6 +267,11 @@ COMPREHENSIVE_THEME_LOCALE_PATHS = ENV_TOKENS.get('COMPREHENSIVE_THEME_LOCALE_PA
#Timezone overrides
TIME_ZONE = ENV_TOKENS.get('CELERY_TIMEZONE', CELERY_TIMEZONE)
##### REGISTRATION RATE LIMIT SETTINGS #####
REGISTRATION_VALIDATION_RATELIMIT = ENV_TOKENS.get(
'REGISTRATION_VALIDATION_RATELIMIT', REGISTRATION_VALIDATION_RATELIMIT
)
# Push to LMS overrides
GIT_REPO_EXPORT_DIR = ENV_TOKENS.get('GIT_REPO_EXPORT_DIR', '/edx/var/edxapp/export_course_repos')

View File

@@ -305,3 +305,5 @@ PROCTORING_SETTINGS = {}
##### LOGISTRATION RATE LIMIT SETTINGS #####
LOGISTRATION_RATELIMIT_RATE = '5/5m'
REGISTRATION_VALIDATION_RATELIMIT = '5/minute'

View File

@@ -2575,6 +2575,8 @@ REST_FRAMEWORK = {
},
}
REGISTRATION_VALIDATION_RATELIMIT = '30/7d'
SWAGGER_SETTINGS = {
'DEFAULT_INFO': 'openedx.core.apidocs.api_info',
}

View File

@@ -603,6 +603,11 @@ 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)
##### REGISTRATION RATE LIMIT SETTINGS #####
REGISTRATION_VALIDATION_RATELIMIT = ENV_TOKENS.get(
'REGISTRATION_VALIDATION_RATELIMIT', REGISTRATION_VALIDATION_RATELIMIT
)
#### PASSWORD POLICY SETTINGS #####
AUTH_PASSWORD_VALIDATORS = ENV_TOKENS.get("AUTH_PASSWORD_VALIDATORS", AUTH_PASSWORD_VALIDATORS)

View File

@@ -518,11 +518,6 @@ ACTIVATION_EMAIL_FROM_ADDRESS = 'test_activate@edx.org'
TEMPLATES[0]['OPTIONS']['debug'] = True
########################### DRF default throttle rates ############################
# Increasing rates to enable test cases hitting registration view succesfully.
# Lower rate is causing view to get blocked, causing test case failure.
REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['registration_validation'] = '100/minute'
########################## VIDEO TRANSCRIPTS STORAGE ############################
VIDEO_TRANSCRIPTS_SETTINGS = dict(
VIDEO_TRANSCRIPTS_MAX_BYTES=3 * 1024 * 1024, # 3 MB
@@ -603,3 +598,5 @@ RATELIMIT_RATE = '2/m'
##### LOGISTRATION RATE LIMIT SETTINGS #####
LOGISTRATION_RATELIMIT_RATE = '5/5m'
REGISTRATION_VALIDATION_RATELIMIT = '5/minute'

View File

@@ -15,19 +15,20 @@ from django.core.validators import ValidationError
from django.db import transaction
from django.dispatch import Signal
from django.http import HttpResponse, HttpResponseForbidden
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.decorators.debug import sensitive_post_parameters
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.translation import get_language
from django.utils.translation import ugettext as _
from pytz import UTC
from requests import HTTPError
from six import text_type
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.decorators.debug import sensitive_post_parameters
from ipware.ip import get_ip
from pytz import UTC
from ratelimit.decorators import ratelimit
from requests import HTTPError
from rest_framework.response import Response
from rest_framework.throttling import AnonRateThrottle
from rest_framework.views import APIView
from six import text_type
from social_core.exceptions import AuthAlreadyAssociated, AuthException
from social_django import utils as social_utils
@@ -48,27 +49,27 @@ from openedx.core.djangoapps.user_api.accounts.api import (
get_username_existence_validation_error,
get_username_validation_error
)
from openedx.core.djangoapps.user_authn.utils import generate_password, is_registration_api_v1
from openedx.core.djangoapps.user_api.preferences import api as preferences_api
from openedx.core.djangoapps.user_authn.cookies import set_logged_in_cookies
from openedx.core.djangoapps.user_authn.utils import generate_password, is_registration_api_v1
from openedx.core.djangoapps.user_authn.views.registration_form import (
get_registration_extension_form,
AccountCreationForm,
RegistrationFormFactory
RegistrationFormFactory,
get_registration_extension_form
)
from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace
from student.helpers import (
AccountValidationError,
authenticate_new_user,
create_or_set_user_attribute_created_on_site,
do_create_account,
AccountValidationError,
do_create_account
)
from student.models import (
RegistrationCookieConfiguration,
UserAttribute,
create_comments_service_user,
email_exists_or_retired,
username_exists_or_retired,
username_exists_or_retired
)
from student.views import compose_and_send_activation_email
from third_party_auth import pipeline, provider
@@ -110,6 +111,7 @@ REGISTRATION_FAILURE_LOGGING_FLAG = WaffleFlag(
waffle_namespace=WaffleFlagNamespace(name=u'registration'),
flag_name=u'enable_failure_logging',
)
REAL_IP_KEY = 'openedx.core.djangoapps.util.ratelimit.real_ip'
@transaction.non_atomic_requests
@@ -575,19 +577,6 @@ class RegistrationView(APIView):
pass
class RegistrationValidationThrottle(AnonRateThrottle):
"""
Custom throttle rate for /api/user/v1/validation/registration
endpoint's use case.
"""
scope = 'registration_validation'
def get_ident(self, request):
client_ip = get_ip(request)
return client_ip
# pylint: disable=line-too-long
class RegistrationValidationView(APIView):
"""
@@ -677,7 +666,6 @@ class RegistrationValidationView(APIView):
# This end-point is available to anonymous users, so no authentication is needed.
authentication_classes = []
throttle_classes = (RegistrationValidationThrottle,)
def name_handler(self, request):
name = request.data.get('name')
@@ -725,6 +713,9 @@ class RegistrationValidationView(APIView):
"country": country_handler
}
@method_decorator(
ratelimit(key=REAL_IP_KEY, rate=settings.REGISTRATION_VALIDATION_RATELIMIT, method='POST', block=True)
)
def post(self, request):
"""
POST /api/user/v1/validation/registration/

View File

@@ -2,24 +2,23 @@
"""Tests for account creation"""
import json
from unittest import skipIf, skipUnless
from datetime import datetime
from unittest import skipIf, skipUnless
import ddt
import httpretty
import mock
import six
from six.moves import range
from django.conf import settings
from django.contrib.auth.models import User
from django.core import mail
from django.core.cache import cache
from django.test import TransactionTestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from pytz import UTC
from six.moves import range
from social_django.models import Partial, UserSocialAuth
from openedx.core.djangoapps.site_configuration.helpers import get_value
@@ -32,25 +31,24 @@ from openedx.core.djangoapps.user_api.accounts import (
EMAIL_MIN_LENGTH,
NAME_MAX_LENGTH,
REQUIRED_FIELD_CONFIRM_EMAIL_MSG,
USERNAME_MAX_LENGTH,
USERNAME_MIN_LENGTH,
USERNAME_BAD_LENGTH_MSG,
USERNAME_CONFLICT_MSG,
USERNAME_INVALID_CHARS_ASCII,
USERNAME_INVALID_CHARS_UNICODE,
USERNAME_MAX_LENGTH,
USERNAME_MIN_LENGTH
)
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
from openedx.core.djangoapps.user_api.accounts.tests import testutils
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import ( # pylint: disable=unused-import
RetirementTestCase,
fake_requested_retirement,
setup_retirement_states,
setup_retirement_states
)
from openedx.core.djangoapps.user_api.tests.test_helpers import TestCaseForm
from openedx.core.djangoapps.user_api.tests.test_constants import SORTED_COUNTRIES
from openedx.core.djangoapps.user_api.tests.test_helpers import TestCaseForm
from openedx.core.djangoapps.user_api.tests.test_views import UserAPITestCase
from openedx.core.djangoapps.user_authn.views.register import RegistrationValidationThrottle, \
REGISTRATION_FAILURE_LOGGING_FLAG
from openedx.core.djangoapps.user_authn.views.register import REGISTRATION_FAILURE_LOGGING_FLAG
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from openedx.core.lib.api import test_utils
@@ -2098,6 +2096,10 @@ class RegistrationValidationViewTests(test_utils.ApiTestCase):
endpoint_name = 'registration_validation'
path = reverse(endpoint_name)
def setUp(self):
super(RegistrationValidationViewTests, self).setUp()
cache.clear()
def get_validation_decision(self, data):
response = self.client.post(self.path, data)
return response.data.get('validation_decisions', {})
@@ -2297,7 +2299,8 @@ class RegistrationValidationViewTests(test_utils.ApiTestCase):
to enforce limits; that's why this test needs a "real"
default cache (as opposed to the usual-for-tests DummyCache)
"""
for _ in range(RegistrationValidationThrottle().num_requests):
self.request_without_auth('post', self.path)
for _ in range(int(settings.REGISTRATION_VALIDATION_RATELIMIT.split('/')[0])):
response = self.request_without_auth('post', self.path)
self.assertNotEqual(response.status_code, 403)
response = self.request_without_auth('post', self.path)
self.assertEqual(response.status_code, 429)
self.assertEqual(response.status_code, 403)