Files
pretix_original/src/pretix/api/middleware.py
Raphael Michel d85ddb5bda Integrate django-scopes (#1319)
* Install django-scopes

* Fix tests.api

* Update tasks and cronjobs

* Fix remaining tests

* Remove unused import

* Fix tests after rebase

* Disable scopes for get_Events_with_any_permission

* Disable scopes for a management command
2019-06-17 10:46:55 +02:00

113 lines
4.1 KiB
Python

import json
from hashlib import sha1
from django.conf import settings
from django.db import transaction
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.urls import resolve
from django.utils.timezone import now
from django_scopes import scope
from rest_framework import status
from pretix.api.models import ApiCall
from pretix.base.models import Organizer
class IdempotencyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: HttpRequest):
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return self.get_response(request)
if not request.path.startswith('/api/'):
return self.get_response(request)
if not request.headers.get('X-Idempotency-Key'):
return self.get_response(request)
auth_hash_parts = '{}:{}'.format(
request.headers.get('Authorization', ''),
request.COOKIES.get(settings.SESSION_COOKIE_NAME, '')
)
auth_hash = sha1(auth_hash_parts.encode()).hexdigest()
idempotency_key = request.headers.get('X-Idempotency-Key', '')
with transaction.atomic():
call, created = ApiCall.objects.select_for_update().get_or_create(
auth_hash=auth_hash,
idempotency_key=idempotency_key,
defaults={
'locked': now(),
'request_method': request.method,
'request_path': request.path,
'response_code': 0,
'response_headers': '{}',
'response_body': b''
}
)
if created:
resp = self.get_response(request)
with transaction.atomic():
if resp.status_code in (409, 429, 503):
# This is the exception: These calls are *meant* to be retried!
call.delete()
else:
call.response_code = resp.status_code
if isinstance(resp.content, str):
call.response_body = resp.content.encode()
elif isinstance(resp.content, memoryview):
call.response_body = resp.content.tobytes()
elif isinstance(resp.content, bytes):
call.response_body = resp.content
elif hasattr(resp.content, 'read'):
call.response_body = resp.read()
elif hasattr(resp, 'data'):
call.response_body = json.dumps(resp.data)
else:
call.response_body = repr(resp).encode()
call.response_headers = json.dumps(resp._headers)
call.locked = None
call.save(update_fields=['locked', 'response_code', 'response_headers',
'response_body'])
return resp
else:
if call.locked:
r = JsonResponse(
{'detail': 'Concurrent request with idempotency key.'},
status=status.HTTP_409_CONFLICT,
)
r['Retry-After'] = 5
return r
content = call.response_body
if isinstance(content, memoryview):
content = content.tobytes()
r = HttpResponse(
content=content,
status=call.response_code,
)
for k, v in json.loads(call.response_headers).values():
r[k] = v
return r
class ApiScopeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: HttpRequest):
if not request.path.startswith('/api/'):
return self.get_response(request)
url = resolve(request.path_info)
if 'organizer' in url.kwargs:
request.organizer = Organizer.objects.filter(
slug=url.kwargs['organizer'],
).first()
with scope(organizer=getattr(request, 'organizer', None)):
return self.get_response(request)