mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
build(deps): the OTP e-mail draws its QR code with segno
django-qr-code was used for one thing: the QR code in the OTP e-mail, as an inline SVG and a PNG. It brought pydantic, pydantic-core, annotated-types and typing-inspection (about 10 MB) and, from 5.0 on, Pillow. segno, which it used underneath, now draws both images with the same options (error correction M, 4-module border, 1.8 mm SVG modules, 12 px PNG modules); the SVG, the PNG and the alt text are byte for byte what django-qr-code 4.2 produced. account.html loaded the tag library without using it.
This commit is contained in:
parent
99b9f35826
commit
72681ab54e
7 changed files with 42 additions and 6 deletions
|
|
@ -542,6 +542,7 @@ A rollback leaves the clone on a detached HEAD at the restored commit; the updat
|
|||
- On a host whose libvirt cannot change network interfaces (the udev backend: RHEL 9 and later, Debian 12), the interface pages offer no create, start, stop or delete; they only failed there.
|
||||
- On a running VM the disk list, its edit form, the QoS table, the title, description, CPU, boot order, video and console fields show the settings as they will be at the next start, so saving an edit no longer reverts another pending change. A boot order that names a device missing from that definition is refused instead of clearing the order. The settings tab of a running VM says so. A QoS edit keeps the inbound floor. Buttons shown as disabled while the VM runs can no longer be submitted with Enter.
|
||||
- A new disk or CD-ROM on a running VM gets a device name that neither the running VM nor its next-start settings use; it could take the name of a pending disk and stay attached until the next start only.
|
||||
- The OTP e-mail's QR code is drawn with segno; django-qr-code is no longer required. A `settings.py` kept from an older version still lists `"qr_code"` in `INSTALLED_APPS`: delete that line, or the app does not start in a venv without django-qr-code.
|
||||
- A disk, NIC or guest agent added to a running VM is requested for both the running VM and its next-start settings in one libvirt call: a disk name or guest agent already pending is refused before anything changes, and a NIC added without a MAC gets the same MAC in both. A failure after the device is plugged can still leave it in the running VM only.
|
||||
- The VM page shows each NIC's own QoS and each CD-ROM's own pool (they showed the previous device's), and a NIC without a source or model no longer breaks it.
|
||||
- A volume of an LVM or iSCSI pool can be attached to a VM, and bridge and direct NICs can be boot devices. The NIC model `default` lets libvirt choose, and `rt18139` is `rtl8139`: a VM with either did not start; a migration corrects the default NIC type setting.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
{% load i18n %}
|
||||
{% load bootstrap_icons %}
|
||||
{% load qr_code %}
|
||||
|
||||
{% block title %}{% trans "User Profile" %} - {{ user }}{% endblock %}
|
||||
{% block page_heading %}{% trans "User Profile" %}: {{ user }}{% endblock page_heading %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
{% load i18n %}
|
||||
{% load qr_code %}
|
||||
{% blocktrans %}
|
||||
Scan this QR code to get OTP for account '{{ user }}'
|
||||
{% endblocktrans %}
|
||||
<br>
|
||||
{% qr_from_text totp_url %}
|
||||
{{ qr_svg }}
|
||||
<p class="small">{% trans 'Some e-mail clients does not render SVG, also generating PNG.' %}</p>
|
||||
{% qr_from_text totp_url size="s" image_format="png" error_correction="M" %}
|
||||
<img src="{{ qr_png }}" alt="{{ totp_url }}">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
import base64
|
||||
import hashlib
|
||||
import re
|
||||
import struct
|
||||
import zlib
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import mail
|
||||
from django.test import TestCase
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from lxml import html
|
||||
|
||||
from accounts.utils import (get_existing_totp_device, get_user_totp_device, send_email_with_otp, validate_ssh_key)
|
||||
|
||||
|
|
@ -78,3 +84,26 @@ class AccountsUtilsTestCase(TestCase):
|
|||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn("OTP QR Code", mail.outbox[0].subject)
|
||||
self.assertEqual(mail.outbox[0].to, ["user@example.com"])
|
||||
# the QR code as inline SVG and, for clients without SVG, as a PNG data URI
|
||||
doc = html.fromstring(mail.outbox[0].alternatives[0][0])
|
||||
self.assertEqual(len(doc.xpath("//svg")), 1)
|
||||
(img,) = doc.xpath("//img")
|
||||
self.assertEqual(img.get("alt"), device.config_url)
|
||||
self.assertTrue(base64.b64decode(img.get("src").removeprefix("data:image/png;base64,")).startswith(b"\x89PNG"))
|
||||
|
||||
def test_otp_email_qr_codes_are_what_django_qr_code_drew(self):
|
||||
# sha256 of the SVG and the PNG django-qr-code 4.2.0 made for this URL
|
||||
url = "otpauth://totp/WebVirtCloud:alice?secret=JBSWY3DPEHPK3PXP&issuer=WebVirtCloud"
|
||||
with patch("accounts.utils.send_mail") as send:
|
||||
send_email_with_otp(self.user, SimpleNamespace(config_url=url))
|
||||
page = send.call_args.kwargs["html_message"]
|
||||
svg = re.search(r"<svg.*?</svg>", page, re.S).group(0)
|
||||
png = base64.b64decode(re.search(r'<img src="data:image/png;base64,([^"]+)"', page).group(1))
|
||||
self.assertEqual(hashlib.sha256(svg.encode()).hexdigest(), "b57323be725891af13d862a3a3351cac3ac64a493793c0258cf3e4a5886467c2")
|
||||
# the PNG's header and pixel rows: its compressed bytes depend on the zlib build
|
||||
chunks, pos = {}, 8
|
||||
while pos < len(png):
|
||||
size, kind = struct.unpack(">I4s", png[pos:pos + 8])
|
||||
chunks[kind] = chunks.get(kind, b"") + png[pos + 8:pos + 8 + size]
|
||||
pos += 12 + size
|
||||
self.assertEqual(hashlib.sha256(chunks[b"IHDR"] + zlib.decompress(chunks[b"IDAT"])).hexdigest(), "cca16de611c73468827f70001f69dc2e48633e6aae2b570a9391eb40a1f8ae88")
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import base64
|
||||
import binascii
|
||||
import struct
|
||||
from decimal import Decimal
|
||||
|
||||
import segno
|
||||
from django.core.mail import send_mail
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _
|
||||
from django_otp import devices_for_user
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
|
|
@ -54,6 +57,10 @@ def validate_ssh_key(key):
|
|||
|
||||
|
||||
def send_email_with_otp(user, device):
|
||||
# An inline SVG (1.8 mm modules) and, for mail clients that show no SVG,
|
||||
# a PNG (12 px modules); error correction M, 4-module border.
|
||||
qr = segno.make(device.config_url, error="m", mode="byte", encoding="utf-8", micro=False)
|
||||
style = {"border": 4, "dark": "black", "light": "white"}
|
||||
send_mail(
|
||||
_("OTP QR Code"),
|
||||
_("Please view HTML version of this message."),
|
||||
|
|
@ -64,6 +71,8 @@ def send_email_with_otp(user, device):
|
|||
{
|
||||
"totp_url": device.config_url,
|
||||
"user": user,
|
||||
"qr_svg": mark_safe(qr.svg_inline(scale=Decimal("1.8"), unit="mm", **style)),
|
||||
"qr_png": qr.png_data_uri(scale=12, **style),
|
||||
},
|
||||
),
|
||||
fail_silently=False,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ Django==5.2.18
|
|||
django_bootstrap5==26.3
|
||||
django-bootstrap-icons==0.9.0
|
||||
django-otp==1.7.4
|
||||
django-qr-code==4.2.0
|
||||
django-auth-ldap==5.3.0
|
||||
# the installer's venv can otherwise keep older distro copies (openSUSE 15.6)
|
||||
python-ldap>=3.4.5
|
||||
|
|
@ -25,6 +24,7 @@ paramiko==5.0.0
|
|||
psycopg[binary]==3.3.6
|
||||
python-engineio==4.14.0
|
||||
python-socketio==5.17.0
|
||||
segno==1.6.6
|
||||
websockify==0.13.0
|
||||
whitenoise==6.12.0
|
||||
crypt-r>=3.13.0; python_version >= "3.13"
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ INSTALLED_APPS = [
|
|||
"storages",
|
||||
"virtsecrets",
|
||||
"logs",
|
||||
"qr_code",
|
||||
"rest_framework",
|
||||
"axes",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue