65 lines
2.5 KiB
Python
65 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from ipaddress import ip_address
|
|
|
|
from django.utils import timezone
|
|
from rest_framework import authentication, exceptions, permissions, throttling
|
|
from rest_framework_api_key.permissions import KeyParser
|
|
|
|
from .models import InventoryAPIKey
|
|
from .permissions import can_edit
|
|
|
|
|
|
class InventoryAPIKeyAuthentication(authentication.BaseAuthentication):
|
|
"""Authentifiziert ausschließlich `Authorization: Api-Key <Schlüssel>`."""
|
|
|
|
key_parser = KeyParser()
|
|
|
|
def authenticate(self, request):
|
|
raw_key = self.key_parser.get(request)
|
|
if not raw_key:
|
|
return None
|
|
try:
|
|
api_key = InventoryAPIKey.objects.get_from_key(raw_key)
|
|
except (InventoryAPIKey.DoesNotExist, ValueError):
|
|
raise exceptions.AuthenticationFailed("Ungültiger API-Schlüssel.")
|
|
if api_key.has_expired:
|
|
raise exceptions.AuthenticationFailed("Der API-Schlüssel ist abgelaufen.")
|
|
if not api_key.service_user.is_active:
|
|
raise exceptions.AuthenticationFailed("Das zugehörige Konto ist deaktiviert.")
|
|
|
|
# Letzte Nutzung höchstens alle fünf Minuten schreiben, damit häufige
|
|
# Lesezugriffe nicht unnötig viele Datenbankänderungen erzeugen.
|
|
now = timezone.now()
|
|
if not api_key.last_used_at or api_key.last_used_at < now - timedelta(minutes=5):
|
|
remote_addr = request.META.get("REMOTE_ADDR", "")
|
|
try:
|
|
last_used_ip = str(ip_address(remote_addr)) if remote_addr else None
|
|
except ValueError:
|
|
last_used_ip = None
|
|
InventoryAPIKey.objects.filter(pk=api_key.pk).update(last_used_at=now, last_used_ip=last_used_ip)
|
|
api_key.last_used_at = now
|
|
api_key.last_used_ip = last_used_ip
|
|
|
|
return api_key.service_user, api_key
|
|
|
|
def authenticate_header(self, request):
|
|
return 'Api-Key realm="inventory-api"'
|
|
|
|
|
|
class InventoryAPIPermission(permissions.BasePermission):
|
|
message = "Dieser API-Schlüssel besitzt nicht die erforderliche Berechtigung."
|
|
|
|
def has_permission(self, request, view):
|
|
api_key = request.auth
|
|
if not isinstance(api_key, InventoryAPIKey):
|
|
return False
|
|
if request.method in permissions.SAFE_METHODS:
|
|
return api_key.can_read
|
|
return api_key.can_write and can_edit(request.user)
|
|
|
|
|
|
class APIClientRateThrottle(throttling.UserRateThrottle):
|
|
scope = "api_client"
|