Skip to content

feat(api): Project key creation rate limit params #15366

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

Merged
merged 2 commits into from
Nov 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 4 additions & 21 deletions src/sentry/api/endpoints/project_key_details.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
from __future__ import absolute_import

from django.db.models import F
from rest_framework import serializers, status
from rest_framework import status
from rest_framework.response import Response

from sentry import features
from sentry.api.base import DocSection
from sentry.api.bases.project import ProjectEndpoint
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.fields.empty_integer import EmptyIntegerField
from sentry.api.serializers import serialize
from sentry.api.serializers.rest_framework import ProjectKeySerializer
from sentry.models import AuditLogEntryEvent, ProjectKey, ProjectKeyStatus
from sentry.utils.apidocs import scenario, attach_scenarios
from sentry.loader.browsersdkversion import (
get_default_sdk_version_for_project,
get_browser_sdk_version_choices,
)
from sentry.loader.browsersdkversion import get_default_sdk_version_for_project


@scenario("DeleteClientKey")
Expand All @@ -39,20 +36,6 @@ def update_key_scenario(runner):
)


class RateLimitSerializer(serializers.Serializer):
count = EmptyIntegerField(min_value=0, required=False, allow_null=True)
window = EmptyIntegerField(min_value=0, max_value=60 * 60 * 24, required=False, allow_null=True)


class KeySerializer(serializers.Serializer):
name = serializers.CharField(max_length=200, required=False, allow_blank=True, allow_null=True)
isActive = serializers.BooleanField(required=False)
rateLimit = RateLimitSerializer(allow_null=True)
browserSdkVersion = serializers.ChoiceField(
choices=get_browser_sdk_version_choices(), required=False
)


class ProjectKeyDetailsEndpoint(ProjectEndpoint):
doc_section = DocSection.PROJECTS

Expand Down Expand Up @@ -88,7 +71,7 @@ def put(self, request, project, key_id):
except ProjectKey.DoesNotExist:
raise ResourceDoesNotExist

serializer = KeySerializer(data=request.data, partial=True)
serializer = ProjectKeySerializer(data=request.data, partial=True)
default_version = get_default_sdk_version_for_project(project)

if serializer.is_valid():
Expand Down
23 changes: 15 additions & 8 deletions src/sentry/api/endpoints/project_keys.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from __future__ import absolute_import

from django.db.models import F
from rest_framework import serializers, status
from rest_framework import status
from rest_framework.response import Response

from sentry import features
from sentry.api.base import DocSection
from sentry.api.bases.project import ProjectEndpoint
from sentry.api.serializers import serialize
from sentry.api.serializers.rest_framework import ProjectKeySerializer
from sentry.models import AuditLogEntryEvent, ProjectKey, ProjectKeyStatus
from sentry.utils.apidocs import scenario, attach_scenarios

Expand All @@ -27,12 +29,6 @@ def create_key_scenario(runner):
)


class KeySerializer(serializers.Serializer):
name = serializers.CharField(max_length=64, required=False, allow_blank=True, allow_null=True)
public = serializers.RegexField(r"^[a-f0-9]{32}$", required=False, allow_null=True)
secret = serializers.RegexField(r"^[a-f0-9]{32}$", required=False, allow_null=True)


class ProjectKeysEndpoint(ProjectEndpoint):
doc_section = DocSection.PROJECTS

Expand Down Expand Up @@ -82,16 +78,27 @@ def post(self, request, project):
belong to.
:param string name: the name for the new key.
"""
serializer = KeySerializer(data=request.data)
serializer = ProjectKeySerializer(data=request.data)

if serializer.is_valid():
result = serializer.validated_data

rate_limit_count = None
rate_limit_window = None

if features.has("projects:rate-limits", project):
ratelimit = result.get("rateLimit", -1)
if ratelimit != -1 and (ratelimit["count"] and ratelimit["window"]):
rate_limit_count = result["rateLimit"]["count"]
rate_limit_window = result["rateLimit"]["window"]

key = ProjectKey.objects.create(
project=project,
label=result.get("name"),
public_key=result.get("public"),
secret_key=result.get("secret"),
rate_limit_count=rate_limit_count,
rate_limit_window=rate_limit_window,
)

self.create_audit_entry(
Expand Down
21 changes: 21 additions & 0 deletions src/sentry/api/serializers/rest_framework/project_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from __future__ import absolute_import

from rest_framework import serializers
from sentry.api.fields.empty_integer import EmptyIntegerField
from sentry.loader.browsersdkversion import get_browser_sdk_version_choices


class RateLimitSerializer(serializers.Serializer):
count = EmptyIntegerField(min_value=0, required=False, allow_null=True)
window = EmptyIntegerField(min_value=0, max_value=60 * 60 * 24, required=False, allow_null=True)


class ProjectKeySerializer(serializers.Serializer):
name = serializers.CharField(max_length=64, required=False, allow_blank=True, allow_null=True)
public = serializers.RegexField(r"^[a-f0-9]{32}$", required=False, allow_null=True)
secret = serializers.RegexField(r"^[a-f0-9]{32}$", required=False, allow_null=True)
rateLimit = RateLimitSerializer(required=False, allow_null=True)
isActive = serializers.BooleanField(required=False)
browserSdkVersion = serializers.ChoiceField(
choices=get_browser_sdk_version_choices(), required=False
)
20 changes: 19 additions & 1 deletion tests/sentry/api/endpoints/test_project_key_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.core.urlresolvers import reverse

from sentry.models import ProjectKey
from sentry.models import ProjectKey, ProjectKeyStatus
from sentry.testutils import APITestCase


Expand Down Expand Up @@ -98,6 +98,24 @@ def test_simple_rate_limit(self):
assert key.rate_limit_count == 1
assert key.rate_limit_window == 60

def test_deactivate(self):
project = self.create_project()
key = ProjectKey.objects.get_or_create(project=project)[0]
self.login_as(user=self.user)
url = reverse(
"sentry-api-0-project-key-details",
kwargs={
"organization_slug": project.organization.slug,
"project_slug": project.slug,
"key_id": key.public_key,
},
)
response = self.client.put(url, {"isActive": False, "name": "hello world"})
assert response.status_code == 200
key = ProjectKey.objects.get(id=key.id)
assert key.label == "hello world"
assert key.status == ProjectKeyStatus.INACTIVE


class DeleteProjectKeyTest(APITestCase):
def test_simple(self):
Expand Down
6 changes: 5 additions & 1 deletion tests/sentry/api/endpoints/test_project_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ def test_simple(self):
"sentry-api-0-project-keys",
kwargs={"organization_slug": project.organization.slug, "project_slug": project.slug},
)
resp = self.client.post(url, data={"name": "hello world"})
resp = self.client.post(
url, data={"name": "hello world", "rateLimit": {"count": 10, "window": 60}}
)
assert resp.status_code == 201, resp.content
key = ProjectKey.objects.get(public_key=resp.data["public"])
assert key.label == "hello world"
assert key.rate_limit_count == 10
assert key.rate_limit_window == 60

def test_minimal_args(self):
project = self.create_project()
Expand Down