serializers.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from urllib.parse import urlparse
  2. from django.db import transaction
  3. from rest_framework import serializers
  4. from sentry.eventtypes.error import ErrorEvent
  5. from sentry.eventtypes.base import DefaultEvent
  6. from issues.models import EventType, Event, Issue
  7. class StoreDefaultSerializer(serializers.Serializer):
  8. """
  9. Default serializer. Used as both a base class and for default error types
  10. """
  11. type = EventType.DEFAULT
  12. breadcrumbs = serializers.JSONField(required=False)
  13. contexts = serializers.JSONField(required=False)
  14. event_id = serializers.UUIDField()
  15. extra = serializers.JSONField(required=False)
  16. level = serializers.CharField()
  17. logentry = serializers.JSONField(required=False)
  18. message = serializers.CharField(required=False)
  19. platform = serializers.CharField()
  20. release = serializers.CharField(required=False)
  21. request = serializers.JSONField(required=False)
  22. sdk = serializers.JSONField()
  23. timestamp = serializers.DateTimeField(required=False)
  24. transaction = serializers.CharField(required=False)
  25. modules = serializers.JSONField(required=False)
  26. def get_eventtype(self):
  27. """ Get event type class from self.type """
  28. if self.type is EventType.DEFAULT:
  29. return DefaultEvent()
  30. if self.type is EventType.ERROR:
  31. return ErrorEvent()
  32. def create(self, project, data):
  33. eventtype = self.get_eventtype()
  34. metadata = eventtype.get_metadata(data)
  35. title = eventtype.get_title(metadata)
  36. culprit = eventtype.get_location(data)
  37. request = data.get("request")
  38. if request:
  39. headers = request.get("headers")
  40. if headers:
  41. request["inferred_content_type"] = headers.get("Content-Type")
  42. request["headers"] = sorted([pair for pair in headers.items()])
  43. with transaction.atomic():
  44. issue, _ = Issue.objects.get_or_create(
  45. title=title,
  46. culprit=culprit,
  47. project=project,
  48. type=self.type,
  49. defaults={"metadata": metadata},
  50. )
  51. params = {
  52. "event_id": data["event_id"],
  53. "issue": issue,
  54. "timestamp": data.get("timestamp"),
  55. "data": {
  56. "contexts": data.get("contexts"),
  57. "culprit": culprit,
  58. "exception": data.get("exception"),
  59. "metadata": metadata,
  60. "packages": data.get("modules"),
  61. "platform": data["platform"],
  62. "request": request,
  63. "sdk": data["sdk"],
  64. "title": title,
  65. "type": self.type.label,
  66. },
  67. }
  68. event = Event.objects.create(**params)
  69. return event
  70. class StoreErrorSerializer(StoreDefaultSerializer):
  71. """ Primary difference is the presense of exception attribute """
  72. type = EventType.ERROR
  73. exception = serializers.JSONField(required=False)
  74. class StoreCSPReportSerializer(serializers.Serializer):
  75. """
  76. CSP Report Serializer
  77. Very different format from others Store serializers.
  78. Does not extend base class due to differences.
  79. """
  80. type = EventType.CSP
  81. def __init__(self, *args, **kwargs):
  82. super().__init__(*args, **kwargs)
  83. # This is done to support the hyphen
  84. self.fields.update({"csp-report": serializers.JSONField()})
  85. def create(self, project, data):
  86. csp = data["csp-report"]
  87. title = self.get_title(csp)
  88. culprit = self.get_culprit(csp)
  89. uri = self.get_uri(csp)
  90. directive = self.get_effective_directive(csp)
  91. metadata = {
  92. "message": title,
  93. "uri": uri,
  94. "directive": directive,
  95. }
  96. issue, _ = Issue.objects.get_or_create(
  97. title=title,
  98. culprit=culprit,
  99. project=project,
  100. type=EventType.CSP,
  101. defaults={"metadata": metadata},
  102. )
  103. # Convert - to _
  104. normalized_csp = dict((k.replace("-", "_"), v) for k, v in csp.items())
  105. if "effective_directive" not in normalized_csp:
  106. normalized_csp["effective_directive"] = directive
  107. params = {
  108. "issue": issue,
  109. "data": {
  110. "culprit": culprit,
  111. "csp": normalized_csp,
  112. "title": title,
  113. "metadata": metadata,
  114. "message": title,
  115. "type": EventType.CSP.label,
  116. },
  117. }
  118. return Event.objects.create(**params)
  119. def get_effective_directive(self, data):
  120. """
  121. Some browers return effective-directive and others don't.
  122. Infer missing ones from violated directive
  123. """
  124. if "effective-directive" in data:
  125. return data["effective-directive"]
  126. first_violation = data["violated-directive"].split()[0]
  127. return first_violation
  128. def get_uri(self, data):
  129. url = data["blocked-uri"]
  130. return urlparse(url).netloc
  131. def get_title(self, data):
  132. effective_directive = self.get_effective_directive(data)
  133. humanized_directive = effective_directive.replace("-src", "")
  134. uri = self.get_uri(data)
  135. return f"Blocked '{humanized_directive}' from '{uri}'"
  136. def get_culprit(self, data):
  137. # "style-src cdn.example.com"
  138. return data.get("violated-directive")