mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
import json # TODO consider faster APIs
|
|
|
|
from django.shortcuts import get_object_or_404
|
|
|
|
from rest_framework import permissions, status
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
from rest_framework import exceptions
|
|
|
|
# from projects.models import Project
|
|
from compat.auth import parse_auth_header_value
|
|
|
|
from projects.models import Project
|
|
from issues.models import Issue
|
|
from issues.utils import get_hash_for_data
|
|
|
|
|
|
from .negotiation import IgnoreClientContentNegotiation
|
|
from .parsers import EnvelopeParser
|
|
from .models import DecompressedEvent
|
|
|
|
|
|
class BaseIngestAPIView(APIView):
|
|
permission_classes = [permissions.AllowAny]
|
|
authentication_classes = []
|
|
content_negotiation_class = IgnoreClientContentNegotiation
|
|
http_method_names = ["post"]
|
|
|
|
@classmethod
|
|
def get_sentry_key_for_request(cls, request):
|
|
# we simply pick the first authentication mechanism that matches, rather than raising a SuspiciousOperation as
|
|
# sentry does (I found the supplied reasons unconvincing). See https://github.com/getsentry/relay/pull/602
|
|
|
|
# "In situations where it's not possible to send [..] header, it's possible [..] values via the querystring"
|
|
# https://github.com/getsentry/develop/blob/b24a602de05b/src/docs/sdk/overview.mdx#L171
|
|
if "sentry_key" in request.GET:
|
|
return request.GET["sentry_key"]
|
|
|
|
# Sentry used to support HTTP_AUTHORIZATION too, but that is unused since Sept. 27 2011
|
|
if "HTTP_X_SENTRY_AUTH" in request.META:
|
|
auth_dict = parse_auth_header_value(request.META["HTTP_X_SENTRY_AUTH"])
|
|
return auth_dict.get("sentry_key")
|
|
|
|
raise exceptions.NotAuthenticated("Unable to find authentication information")
|
|
|
|
@classmethod
|
|
def get_project(cls, request, project_id):
|
|
# NOTE this gives a 404 for non-properly authorized. Is this really something we care about, i.e. do we want to
|
|
# raise NotAuthenticated? In that case we need to get the project first, and then do a constant-time-comp on the
|
|
# sentry_key
|
|
sentry_key = cls.get_sentry_key_for_request(request)
|
|
return get_object_or_404(Project, pk=project_id, sentry_key=sentry_key)
|
|
|
|
def process_event(self, event_data, request, project):
|
|
event = DecompressedEvent.objects.create(
|
|
project=project,
|
|
data=json.dumps(event_data), # TODO don't parse-then-print for BaseIngestion
|
|
)
|
|
|
|
hash_ = get_hash_for_data(event_data)
|
|
|
|
issue, _ = Issue.objects.get_or_create(
|
|
project=project,
|
|
hash=hash_,
|
|
)
|
|
issue.events.add(event)
|
|
|
|
|
|
class IngestEventAPIView(BaseIngestAPIView):
|
|
|
|
def post(self, request, project_id=None):
|
|
project = self.get_project(request, project_id)
|
|
|
|
self.process_event(request.data, request, project)
|
|
return Response()
|
|
|
|
|
|
class IngestEnvelopeAPIView(BaseIngestAPIView):
|
|
parser_classes = [EnvelopeParser]
|
|
|
|
def post(self, request, project_id=None):
|
|
project = self.get_project(request, project_id)
|
|
|
|
if len(request.data) != 3:
|
|
# multi-part envelopes trigger an error too
|
|
print("!= 3")
|
|
return Response({"message": "Missing headers / unsupported type"}, status=status.HTTP_501_NOT_IMPLEMENTED)
|
|
|
|
if request.data[1].get("type") != "event":
|
|
print("!= event")
|
|
return Response({"message": "Only events are supported"}, status=status.HTTP_501_NOT_IMPLEMENTED)
|
|
|
|
# TODO think about a good order to handle this in. Namely: if no project Header is provided, you are basically
|
|
# forced to do some parsing of the envelope... and this could be costly.
|
|
# https://gitlab.com/glitchtip/glitchtip-backend/-/issues/181
|
|
|
|
"""
|
|
# KvS: this is presumably the path that is used for envelopes (and then also when the above are not provided)
|
|
# TODO I'd much rather deal with that explicitly
|
|
from urllib.parse import urlparse
|
|
if isinstance(request.data, list):
|
|
if data_first := next(iter(request.data), None):
|
|
if isinstance(data_first, dict):
|
|
dsn = urlparse(data_first.get("dsn"))
|
|
if dsn.username:
|
|
return dsn.username
|
|
"""
|
|
|
|
event = request.data[2]
|
|
self.process_event(event, request, project)
|
|
return Response()
|