views.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import json
  2. import uuid
  3. import string
  4. import random
  5. from django.core.exceptions import SuspiciousOperation
  6. from django.conf import settings
  7. from django.http import HttpResponse
  8. from django.test import RequestFactory
  9. from rest_framework import permissions, exceptions
  10. from rest_framework.negotiation import BaseContentNegotiation
  11. from rest_framework.response import Response
  12. from rest_framework.views import APIView
  13. from sentry.utils.auth import parse_auth_header
  14. from projects.models import Project
  15. from .serializers import (
  16. StoreDefaultSerializer,
  17. StoreErrorSerializer,
  18. StoreCSPReportSerializer,
  19. )
  20. class IgnoreClientContentNegotiation(BaseContentNegotiation):
  21. """
  22. @sentry/browser sends an interesting content-type of text/plain when it's actually sending json
  23. We have to ignore it and assume it's actually JSON
  24. """
  25. def select_parser(self, request, parsers):
  26. """
  27. Select the first parser in the `.parser_classes` list.
  28. """
  29. return parsers[0]
  30. def select_renderer(self, request, renderers, format_suffix):
  31. """
  32. Select the first renderer in the `.renderer_classes` list.
  33. """
  34. return (renderers[0], renderers[0].media_type)
  35. def test_event_view(request):
  36. """
  37. This view is used only to test event store performance
  38. It requires DEBUG to be True
  39. """
  40. factory = RequestFactory()
  41. request = request = factory.get(
  42. "/api/6/store/?sentry_key=244703e8083f4b16988c376ea46e9a08"
  43. )
  44. with open("event_store/test_data/py_hi_event.json") as json_file:
  45. data = json.load(json_file)
  46. data["event_id"] = uuid.uuid4()
  47. data["message"] = "".join(
  48. random.choices(string.ascii_uppercase + string.digits, k=8)
  49. )
  50. request.data = data
  51. EventStoreAPIView().post(request, id=6)
  52. return HttpResponse("<html><body></body></html>")
  53. class EventStoreAPIView(APIView):
  54. permission_classes = [permissions.AllowAny]
  55. authentication_classes = []
  56. content_negotiation_class = IgnoreClientContentNegotiation
  57. http_method_names = ["post"]
  58. def get_serializer_class(self, data=[]):
  59. """ Determine event type and return serializer """
  60. if "exception" in data and data["exception"]:
  61. return StoreErrorSerializer
  62. if "platform" not in data:
  63. return StoreCSPReportSerializer
  64. return StoreDefaultSerializer
  65. def post(self, request, *args, **kwargs):
  66. if settings.EVENT_STORE_DEBUG:
  67. print(json.dumps(request.data))
  68. sentry_key = EventStoreAPIView.auth_from_request(request)
  69. project = (
  70. Project.objects.filter(
  71. id=kwargs.get("id"), projectkey__public_key=sentry_key
  72. )
  73. .select_related("organization")
  74. .only("id", "organization__is_accepting_events")
  75. .first()
  76. )
  77. if not project:
  78. raise exceptions.PermissionDenied()
  79. if not project.organization.is_accepting_events:
  80. raise exceptions.Throttled(detail="event rejected due to rate limit")
  81. serializer = self.get_serializer_class(request.data)(data=request.data)
  82. if serializer.is_valid():
  83. event = serializer.create(project.id, serializer.data)
  84. return Response({"id": event.event_id_hex})
  85. # TODO {"error": "Invalid api key"}, CSP type, valid json but no type at all
  86. return Response()
  87. @classmethod
  88. def auth_from_request(cls, request):
  89. result = {k: request.GET[k] for k in request.GET.keys() if k[:7] == "sentry_"}
  90. if request.META.get("HTTP_X_SENTRY_AUTH", "")[:7].lower() == "sentry ":
  91. if result:
  92. raise SuspiciousOperation(
  93. "Multiple authentication payloads were detected."
  94. )
  95. result = parse_auth_header(request.META["HTTP_X_SENTRY_AUTH"])
  96. elif request.META.get("HTTP_AUTHORIZATION", "")[:7].lower() == "sentry ":
  97. if result:
  98. raise SuspiciousOperation(
  99. "Multiple authentication payloads were detected."
  100. )
  101. result = parse_auth_header(request.META["HTTP_AUTHORIZATION"])
  102. if not result:
  103. raise exceptions.NotAuthenticated(
  104. "Unable to find authentication information"
  105. )
  106. return result.get("sentry_key")
  107. class CSPStoreAPIView(EventStoreAPIView):
  108. pass