-
Notifications
You must be signed in to change notification settings - Fork 467
feat: Add rate limiting to identity search endpoint #6438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
eb6c827
db1cc77
14dc93f
aa244d5
a11a21e
c207b15
b17cd1a
61b5b7e
761d0a8
7dbb0dc
ba7ab9c
4d4dced
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -302,8 +302,9 @@ | |
|
|
||
| LOGIN_THROTTLE_RATE = env("LOGIN_THROTTLE_RATE", "20/min") | ||
| SIGNUP_THROTTLE_RATE = env("SIGNUP_THROTTLE_RATE", "10000/min") | ||
| USER_THROTTLE_RATE = env("USER_THROTTLE_RATE", default=None) | ||
| MASTER_API_KEY_THROTTLE_RATE = env("MASTER_API_KEY_THROTTLE_RATE", default=None) | ||
| USER_THROTTLE_RATE = env("USER_THROTTLE_RATE", "500/min") | ||
| MASTER_API_KEY_THROTTLE_RATE = env("MASTER_API_KEY_THROTTLE_RATE", USER_THROTTLE_RATE) | ||
| IDENTITY_SEARCH_THROTTLE_RATE = env("IDENTITY_SEARCH_THROTTLE_RATE", "30/min") | ||
|
||
| DEFAULT_THROTTLE_CLASSES = env.list("DEFAULT_THROTTLE_CLASSES", subcast=str, default=[]) | ||
| REST_FRAMEWORK = { | ||
| "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], | ||
|
|
@@ -324,6 +325,7 @@ | |
| "invite": "10/min", | ||
| "user": USER_THROTTLE_RATE, | ||
| "influx_query": "5/min", | ||
| "identity_search": IDENTITY_SEARCH_THROTTLE_RATE, | ||
Zaimwa9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], | ||
| "DEFAULT_RENDERER_CLASSES": [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |||||
| from rest_framework import status, viewsets | ||||||
| from rest_framework.permissions import IsAuthenticated | ||||||
| from rest_framework.response import Response | ||||||
| from rest_framework.throttling import ScopedRateThrottle | ||||||
|
|
||||||
| from app.pagination import CustomPagination | ||||||
| from core.constants import FLAGSMITH_UPDATED_AT_HEADER, SDK_ENVIRONMENT_KEY_HEADER | ||||||
|
|
@@ -41,6 +42,16 @@ | |||||
| class IdentityViewSet(viewsets.ModelViewSet): # type: ignore[type-arg] | ||||||
| serializer_class = IdentitySerializer | ||||||
| pagination_class = CustomPagination | ||||||
| throttle_scope = "identity_search" | ||||||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
1-23-smy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| def get_throttles(self): # type: ignore[no-untyped-def] | ||||||
| """ | ||||||
| Apply identity_search throttle only to list (search) requests. | ||||||
| For other actions, return the global default throttle classes. | ||||||
| """ | ||||||
| if getattr(self, "action", None) == "list": | ||||||
|
||||||
| if getattr(self, "action", None) == "list": | |
| if getattr(self, "action", None) == "list" and "q" in self.request.query_params: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -305,6 +305,31 @@ def test_search_identities_still_allows_paging( | |
| assert response2.data["results"] | ||
|
|
||
|
|
||
| def test_identity_search_is_throttled( | ||
| admin_client: APIClient, | ||
| environment: Environment, | ||
| reset_cache: None, | ||
| mocker: MockerFixture, | ||
| ) -> None: | ||
| # Given - mock the throttle rate to be restrictive for testing | ||
| mocker.patch( | ||
| "rest_framework.throttling.ScopedRateThrottle.get_rate", return_value="1/minute" | ||
| ) | ||
| base_url = reverse( | ||
| "api-v1:environments:environment-identities-list", | ||
| args=[environment.api_key], | ||
| ) | ||
| url = f"{base_url}?q=test" | ||
|
|
||
| # When - make 2 requests in quick succession | ||
| response1 = admin_client.get(url) | ||
| response2 = admin_client.get(url) | ||
|
|
||
| # Then - first should succeed, second should be throttled | ||
| assert response1.status_code == status.HTTP_200_OK | ||
| assert response2.status_code == status.HTTP_429_TOO_MANY_REQUESTS | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
Comment on lines
+308
to
+331
|
||
|
|
||
| def test_can_delete_identity( | ||
| environment: Environment, | ||
| admin_client: APIClient, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undocumented breaking change to global throttle rate defaults
Medium Severity
The PR changes
USER_THROTTLE_RATEandMASTER_API_KEY_THROTTLE_RATEdefaults fromNone(no throttling) to"500/min", but this behavioral change isn't mentioned in the PR description. The stated scope is only adding rate limiting to the identity search endpoint. Existing deployments that hadDEFAULT_THROTTLE_CLASSESconfigured but relied on theNonedefault for these rates would suddenly have 500/min rate limiting applied to all admin API requests. If intentional, this change warrants explicit documentation in the PR description.