1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/webvirtcloud/middleware.py
catborise 90c148d287 fix: a form libvirt refuses goes back to its page with the reason
A libvirt error that a view left to ExceptionMiddleware showed the "500
Internal Server Error" page, which blames the server, at the form's
action URL; its only way on was the browser's Back. Adding a NIC, a new
or existing disk, editing a disk, setting a NIC's QoS and creating a
volume end there when libvirt refuses, as do the other VM actions.

For a form (any method but GET or HEAD) the middleware now goes back to
the page the form was on, with the error in the messages, as it already
does for a busy compute; a page that cannot be shown keeps the error
page. same_origin_referer moves from the instances views to the
middleware module, next to error_text, for both to use.
2026-10-20 22:37:40 +03:00

81 lines
3.6 KiB
Python

from django.contrib import messages
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _
from libvirt import libvirtError
from operations import claims, jobs
from operations.utils import busy_message, public_error_text, summary
API_PREFIX = "/api/"
# the directives that need no change to the templates' inline scripts and styles
CONTENT_SECURITY_POLICY = "object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"
def same_origin_referer(request, default):
"""The Referer without its fragment if it is on this site, else default."""
referer = request.META.get("HTTP_REFERER")
if referer and url_has_allowed_host_and_scheme(
url=referer, allowed_hosts={request.get_host()}, require_https=request.is_secure()
):
return referer.split("#")[0]
return default
def error_text(request, exception):
"""
The libvirt error to show. Administrators (superusers and the global
view_instances permission, who see the hosts) get the full error; other
users the public text.
"""
user = getattr(request, "user", None)
if user and (user.is_superuser or user.has_perm("instances.view_instances")):
return str(exception)
return public_error_text(exception)
class ContentSecurityPolicyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response.setdefault("Content-Security-Policy", CONTENT_SECURITY_POLICY)
return response
class ExceptionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
# API clients get JSON with a failure status, not the HTML error page.
if request.path_info.startswith(API_PREFIX):
if isinstance(exception, libvirtError):
return JsonResponse({"detail": error_text(request, exception)}, status=400)
if isinstance(exception, claims.Busy):
return JsonResponse({"detail": busy_message(exception, request.user),
"operation": summary(exception.blocker, request.user) if exception.blocker else None},
status=409)
if isinstance(exception, jobs.SubmissionConflict):
return JsonResponse({"detail": str(exception)}, status=409)
if isinstance(exception, TimeoutError):
return JsonResponse({"detail": "Operation is busy. Please retry."}, status=409)
return None
if isinstance(exception, claims.Busy) and request.method not in ("GET", "HEAD"):
# back to the form with the reason; a GET is never redirected to itself
messages.error(request, busy_message(exception, request.user))
return redirect(request.get_full_path())
if isinstance(exception, libvirtError):
messages.error(
request,
_("libvirt Error - %(exception)s") % {"exception": error_text(request, exception)},
)
if request.method not in ("GET", "HEAD"):
# a refused form: back to the page it was on, with the reason
return redirect(same_origin_referer(request, "/"))
return render(request, "500.html", {"libvirt_error": True}, status=500)
# TODO: check connecting to host via VPN