56 lines
2.1 KiB
Python
56 lines
2.1 KiB
Python
"""Secure live-stream access.
|
|
|
|
Camera credentials are never exposed. A customer can request short-lived,
|
|
HMAC-signed access tokens for streams authorized for their own orders; the
|
|
`verify` endpoint validates the token and returns the stream URL.
|
|
"""
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import time
|
|
|
|
from django.conf import settings
|
|
|
|
from apps.live_monitoring.models import Camera, WorkflowStepCamera
|
|
from apps.orders.models import ProcessingOrder
|
|
from apps.workflows.models import WorkflowStep
|
|
|
|
|
|
class StreamAccessService:
|
|
def can_access(self, user, camera: Camera) -> bool:
|
|
if not user or not user.is_authenticated:
|
|
return False
|
|
if user.is_superuser:
|
|
return True
|
|
version_ids = ProcessingOrder.objects.filter(customer__user=user).values_list(
|
|
"workflow_version_id", flat=True
|
|
)
|
|
step_ids = WorkflowStep.objects.filter(
|
|
version_id__in=version_ids, live_monitoring_enabled=True
|
|
).values_list("id", flat=True)
|
|
return WorkflowStepCamera.objects.filter(camera=camera, step_id__in=step_ids).exists()
|
|
|
|
def issue_token(self, camera: Camera, ttl: int = 3600) -> tuple:
|
|
payload = {"camera": str(camera.id), "exp": int(time.time()) + ttl}
|
|
raw = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
|
|
sig = hmac.new(settings.SECRET_KEY.encode(), raw.encode(), hashlib.sha256).hexdigest()
|
|
return f"{raw}.{sig}", payload["exp"]
|
|
|
|
def verify_token(self, token: str):
|
|
try:
|
|
raw, sig = token.split(".", 1)
|
|
except (ValueError, AttributeError):
|
|
return None
|
|
expected = hmac.new(settings.SECRET_KEY.encode(), raw.encode(), hashlib.sha256).hexdigest()
|
|
if not hmac.compare_digest(expected, sig):
|
|
return None
|
|
try:
|
|
pad = raw + "=" * (-len(raw) % 4)
|
|
payload = json.loads(base64.urlsafe_b64decode(pad).decode())
|
|
except Exception:
|
|
return None
|
|
if payload.get("exp", 0) < int(time.time()):
|
|
return None
|
|
return payload
|