Add authenticated device registration for USB intake
Some checks failed
Tests / postgres (push) Has been cancelled

This commit is contained in:
2026-10-11 22:19:57 +02:00
parent 7d1d8f0fde
commit e2870c2fda
7 changed files with 241 additions and 2 deletions

View File

@@ -1,6 +1,8 @@
from __future__ import annotations
from collections.abc import Mapping
import hashlib
import json
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import transaction
@@ -9,9 +11,9 @@ from rest_framework import generics, serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import ChoiceValue, Device, Employee
from .models import AuditLog, ChoiceValue, Device, DeviceRegistration, Employee, NumberSequence
from .query import SPECIFICATION_RELATIONS
from .services import update_device_field
from .services import add_audit, update_device_field
class SecureAPIResponseMixin:
@@ -121,6 +123,16 @@ class DeviceChangesSerializer(StrictSerializer):
return attrs
class DeviceCreateSerializer(DeviceChangesSerializer):
request_id = serializers.UUIDField()
status = serializers.PrimaryKeyRelatedField(
queryset=ChoiceValue.objects.filter(kind=ChoiceValue.Kind.STATUS, active=True),
)
device_type = serializers.PrimaryKeyRelatedField(
queryset=ChoiceValue.objects.filter(kind=ChoiceValue.Kind.DEVICE_TYPE, active=True),
)
class DevicePatchSerializer(DeviceChangesSerializer):
expected_updated_at = serializers.DateTimeField(required=False, write_only=True)
expected_revision = serializers.IntegerField(required=False, min_value=1, write_only=True)
@@ -169,6 +181,29 @@ def apply_changes(device, changes, *, user, api_client, expected_updated_at=None
class DeviceListView(SecureAPIResponseMixin, generics.ListAPIView):
serializer_class = DeviceSerializer
@transaction.atomic
def post(self, request):
serializer = DeviceCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
values = dict(serializer.validated_data)
request_id = values.pop("request_id")
digest = hashlib.sha256(json.dumps(request.data, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
# Share the existing allocation lock, including across simultaneous retries.
NumberSequence.objects.select_for_update().get_or_create(name="device")
previous = DeviceRegistration.objects.select_related("device").filter(request_id=request_id).first()
if previous:
if previous.payload_hash != digest:
return Response({"detail": "request_id wurde bereits für andere Daten verwendet."}, status=409)
return Response(DeviceSerializer(previous.device).data, status=200)
try:
device = Device.objects.create(**values)
except DjangoValidationError as exc:
raise serializers.ValidationError(exc.messages)
DeviceRegistration.objects.create(request_id=request_id, payload_hash=digest, device=device)
add_audit(request.user, device, AuditLog.Action.CREATE,
new=f"Gerät #{device.number}", api_client=request.auth.name)
return Response(DeviceSerializer(device).data, status=201)
def get_queryset(self):
queryset = Device.objects.select_related(
"status", "recipient", "location", "device_type", "employee",