Files
shynet/shynet/analytics/views/ingress.py
2023-02-23 13:39:26 -08:00

171 lines
5.8 KiB
Python

import base64
import json
from urllib.parse import urlparse
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseForbidden,
)
from django.shortcuts import render, reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
from ipware import get_client_ip
from core.models import Service
from ..tasks import ingress_request
def ingress(request, service_uuid, identifier, tracker, payload):
time = timezone.now()
client_ip, is_routable = get_client_ip(request)
location = request.META.get("HTTP_REFERER", "").strip()
user_agent = request.META.get("HTTP_USER_AGENT", "").strip()
dnt = request.META.get("HTTP_DNT", "0").strip() == "1"
gpc = request.META.get("HTTP_SEC_GPC", "0").strip() == "1"
if gpc or dnt:
dnt = True
ingress_request.delay(
service_uuid,
tracker,
time,
payload,
client_ip,
location,
user_agent,
dnt=dnt,
identifier=identifier,
)
class ValidateServiceOriginsMixin:
def dispatch(self, request, *args, **kwargs):
try:
service_uuid = self.kwargs.get("service_uuid")
origins = cache.get(f"service_origins_{service_uuid}")
if origins is None:
service = Service.objects.get(uuid=service_uuid)
origins = service.origins
cache.set(f"service_origins_{service_uuid}", origins, timeout=3600)
allow_origin = "*"
if origins != "*":
remote_origin = request.META.get("HTTP_ORIGIN")
if (
remote_origin is None
and request.META.get("HTTP_REFERER") is not None
):
parsed = urlparse(request.META.get("HTTP_REFERER"))
remote_origin = f"{parsed.scheme}://{parsed.netloc}".lower()
origins = [origin.strip().lower() for origin in origins.split(",")]
if remote_origin in origins:
allow_origin = remote_origin
else:
return HttpResponseForbidden()
resp = super().dispatch(request, *args, **kwargs)
resp["Access-Control-Allow-Origin"] = allow_origin
resp["Access-Control-Allow-Methods"] = "GET,HEAD,OPTIONS,POST"
resp[
"Access-Control-Allow-Headers"
] = "Origin, X-Requested-With, Content-Type, Accept, Authorization, Referer"
return resp
except Service.DoesNotExist:
raise Http404()
except ValidationError:
return HttpResponseBadRequest()
class PixelView(ValidateServiceOriginsMixin, View):
# Fallback view to serve an unobtrusive 1x1 transparent tracking pixel for browsers with
# JavaScript disabled.
def get(self, *args, **kwargs):
# Extract primary data
ingress(
self.request,
self.kwargs.get("service_uuid"),
self.kwargs.get("identifier", ""),
"PIXEL",
{},
)
data = base64.b64decode(
"R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
)
resp = HttpResponse(data, content_type="image/gif")
resp["Cache-Control"] = "no-cache, no-store, must-revalidate"
resp["Access-Control-Allow-Origin"] = "*"
return resp
@method_decorator(csrf_exempt, name="dispatch")
class ScriptView(ValidateServiceOriginsMixin, View):
def get(self, *args, **kwargs):
protocol = "https" if settings.SCRIPT_USE_HTTPS else "http"
endpoint = (
reverse(
"ingress:endpoint_script",
kwargs={
"service_uuid": self.kwargs.get("service_uuid"),
},
)
if self.kwargs.get("identifier") is None
else reverse(
"ingress:endpoint_script_id",
kwargs={
"service_uuid": self.kwargs.get("service_uuid"),
"identifier": self.kwargs.get("identifier"),
},
)
)
heartbeat_frequency = settings.SCRIPT_HEARTBEAT_FREQUENCY
dnt = self.request.META.get("HTTP_DNT", "0").strip() == "1"
service_uuid = self.kwargs.get("service_uuid")
service = Service.objects.get(pk=service_uuid, status=Service.ACTIVE)
return render(
self.request,
"analytics/scripts/page.js",
context=dict(
{
"endpoint": endpoint,
"protocol": protocol,
"heartbeat_frequency": heartbeat_frequency,
"script_inject": self.get_script_inject(),
"dnt": dnt and service.respect_dnt,
}
),
content_type="application/javascript",
)
def post(self, *args, **kwargs):
payload = json.loads(self.request.body)
ingress(
self.request,
self.kwargs.get("service_uuid"),
self.kwargs.get("identifier", ""),
"JS",
payload,
)
return HttpResponse(
json.dumps({"status": "OK"}), content_type="application/json"
)
def get_script_inject(self):
service_uuid = self.kwargs.get("service_uuid")
script_inject = cache.get(f"script_inject_{service_uuid}")
if script_inject is None:
service = Service.objects.get(uuid=service_uuid)
script_inject = service.script_inject
cache.set(f"script_inject_{service_uuid}", script_inject, timeout=3600)
return script_inject