@@ -2068,6 +2068,53 @@ def _assert_social_auth_provider_present(self, field_settings, backend):
20682068 "defaultValue" : backend .name
20692069 })
20702070
2071+ @override_settings (
2072+ REGISTRATION_RATELIMIT = '1/d' ,
2073+ CACHES = {
2074+ 'default' : {
2075+ 'BACKEND' : 'django.core.cache.backends.locmem.LocMemCache' ,
2076+ 'LOCATION' : 'registration_ratelimit_tpa' ,
2077+ }
2078+ }
2079+ )
2080+ def test_rate_limiting_exempted_for_saml_pipeline (self ):
2081+ """
2082+ Confirm that a registration POST with an active SAML/TPA pipeline is not
2083+ blocked by IP-based rate limiting, even after the limit is exhausted.
2084+ """
2085+ # Exhaust the rate limit (limit is 1/d, so one request uses the allowance)
2086+ self .client .post (self .url , {
2087+ "email" : "first@example.com" ,
2088+ "name" : self .NAME ,
2089+ "username" : "firstuser" ,
2090+ "password" : self .PASSWORD ,
2091+ "honor_code" : "true" ,
2092+ })
2093+ # Without a pipeline, the next POST from the same IP is rate limited
2094+ response = self .client .post (self .url , {
2095+ "email" : self .EMAIL ,
2096+ "name" : self .NAME ,
2097+ "username" : self .USERNAME ,
2098+ "password" : self .PASSWORD ,
2099+ "honor_code" : "true" ,
2100+ })
2101+ assert response .status_code == 403
2102+ assert response .json ().get ('error_code' ) == 'forbidden-request'
2103+
2104+ # With an active SAML/TPA pipeline, the same IP is not blocked by rate limiting
2105+ with simulate_running_pipeline (
2106+ 'openedx.core.djangoapps.user_authn.views.register.pipeline' , 'tpa-saml'
2107+ ):
2108+ response = self .client .post (self .url , {
2109+ "email" : self .EMAIL ,
2110+ "name" : self .NAME ,
2111+ "username" : self .USERNAME ,
2112+ "password" : self .PASSWORD ,
2113+ "honor_code" : "true" ,
2114+ })
2115+ self .assertHttpOK (response )
2116+ self .assertNotEqual (response .json ().get ('error_code' ), 'forbidden-request' )
2117+
20712118
20722119@ddt .ddt
20732120class RegistrationViewTestV2 (RegistrationViewTestV1 ):
@@ -3028,6 +3075,34 @@ def test_rate_limiting_registration_view(self):
30283075 response = self .request_without_auth ('post' , self .path )
30293076 assert response .status_code == 403
30303077
3078+ @override_settings (
3079+ REGISTRATION_VALIDATION_RATELIMIT = '1/d' ,
3080+ CACHES = {
3081+ 'default' : {
3082+ 'BACKEND' : 'django.core.cache.backends.locmem.LocMemCache' ,
3083+ 'LOCATION' : 'validation_ratelimit_tpa' ,
3084+ }
3085+ }
3086+ )
3087+ def test_rate_limiting_exempted_for_saml_pipeline (self ):
3088+ """
3089+ Confirm that validation requests with an active SAML/TPA pipeline are not
3090+ blocked by IP-based rate limiting, even after the limit is exhausted.
3091+ """
3092+ # Exhaust the rate limit (limit is 1/d, so one request uses the allowance)
3093+ self .request_without_auth ('post' , self .path )
3094+ # Without a pipeline, the next request from the same IP is rate limited
3095+ response = self .request_without_auth ('post' , self .path )
3096+ assert response .status_code == 403
3097+
3098+ # With an active SAML/TPA pipeline, the same IP is not blocked by rate limiting
3099+ with simulate_running_pipeline (
3100+ 'openedx.core.djangoapps.user_authn.views.register.pipeline' , 'tpa-saml'
3101+ ):
3102+ response = self .request_without_auth ('post' , self .path )
3103+ self .assertHttpOK (response )
3104+ assert response .json ().get ('validation_decisions' ) == {}
3105+
30313106 def test_single_field_validation (self ):
30323107 """
30333108 Test that if `is_authn_mfe` is provided in request along with form_field_key, only
0 commit comments