Eine Kamera am Vogelhaus, ein Rechner der erkennt wer da war — und eine Sammlung, die sich von allein fuellt. Zwei neuronale Netze: eines findet den Vogel im Bild, eines bestimmt die Art. 148 heimische Arten, auf Deutschland eingeschraenkt und neu normiert — ohne diese Einschraenkung wird aus einer Kohlmeise eine nordamerikanische Schwesterart. Daraus entsteht ein Sammelalbum: nummerierte Seiten, gefundene Arten in Farbe, fehlende als Schatten, dazu Erstfang, Punkte, Stufen und 15 Abzeichen. Laeuft vollstaendig auf eigener Hardware. Kein Cloud-Dienst, kein Konto, keine Telemetrie; die Bilder verlassen das eigene Netz nicht. Zwei getrennt deploybare Teile: die Sammlung als Django-Anwendung, die Erkennung als eigener Dienst. Der Erkennungs-Dienst fragt selbst an und braucht deshalb weder eine erreichbare Adresse noch einen offenen Port — er laeuft hinter einem gewoehnlichen Router. Dabei: Firmware fuer Raspberry Pi und ESP32-S3, Anbindung an Home Assistant ueber MQTT, Oberflaeche auf Deutsch und Englisch, Einstellungen je Kamera aus dem Browser, und ein Weg fuer Fotos von unterwegs. AGPL-3.0.
102 lines
3.9 KiB
Python
102 lines
3.9 KiB
Python
"""Dass die Absicherung im Betrieb auch wirklich anspringt.
|
|
|
|
🔑 **Warum das einen eigenen Test braucht.** Die Einstellungen hängen an
|
|
``DEBUG=0``, und in der Entwicklung *und in den Tests* ist ``DJANGO_DEBUG``
|
|
standardmäßig ``1``. Die ganze Absicherung läuft also in keinem einzigen
|
|
normalen Testlauf — sie stünde richtig in der Datei und wäre nie geprüft.
|
|
Genau die Sorte Sicherheitsnetz, die stumm kaputt ist.
|
|
|
|
Deshalb hier die Einstellungsdatei einmal frisch mit ``DJANGO_DEBUG=0``
|
|
einlesen und nachsehen, was dabei herauskommt.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
from unittest import mock
|
|
|
|
from django.test import SimpleTestCase
|
|
|
|
|
|
def betriebs_einstellungen():
|
|
"""Lädt ``vogelalbum.settings`` neu, als liefe es im Betrieb."""
|
|
umgebung = {
|
|
"DJANGO_DEBUG": "0",
|
|
# Im Betrieb ist ein eigener Schlüssel Pflicht, sonst bricht der
|
|
# Import ab — hier einer, der nur in diesem Test lebt.
|
|
"DJANGO_SECRET_KEY": "nur-fuer-diesen-test-" + "x" * 40,
|
|
}
|
|
with mock.patch.dict(os.environ, umgebung):
|
|
modul = importlib.import_module("vogelalbum.settings")
|
|
return importlib.reload(modul)
|
|
|
|
|
|
class BetriebsEinstellungenTests(SimpleTestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.betrieb = betriebs_einstellungen()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
# ⚠️ Zurückladen, sonst sieht jeder nachfolgende Test die
|
|
# Betriebsfassung — und scheitert an Dingen, die mit ihm nichts zu
|
|
# tun haben.
|
|
import vogelalbum.settings
|
|
|
|
importlib.reload(vogelalbum.settings)
|
|
super().tearDownClass()
|
|
|
|
def test_debug_ist_aus(self):
|
|
self.assertFalse(self.betrieb.DEBUG)
|
|
|
|
def test_cookies_nur_ueber_https(self):
|
|
"""🔴 Der Kern: Die Umleitung auf https kommt zu spät.
|
|
|
|
Traefik antwortet mit 307 — da hat der Browser das Cookie aber schon
|
|
mitgeschickt. Erst ``Secure`` hält ihn davon ab.
|
|
"""
|
|
self.assertTrue(self.betrieb.SESSION_COOKIE_SECURE)
|
|
self.assertTrue(self.betrieb.CSRF_COOKIE_SECURE)
|
|
|
|
def test_hsts_ist_gesetzt(self):
|
|
self.assertGreaterEqual(self.betrieb.SECURE_HSTS_SECONDS, 31536000)
|
|
|
|
def test_hsts_nicht_fuer_fremde_namen(self):
|
|
"""Keine Zusagen für Namen, die es nicht gibt, und kein preload.
|
|
|
|
``includeSubDomains`` gilt auch für Namen, die noch niemand angelegt
|
|
hat, und ``preload`` nimmt einem die Entscheidung aus der Hand: Aus
|
|
fremden Listen kommt man nur langsam wieder heraus.
|
|
"""
|
|
self.assertFalse(self.betrieb.SECURE_HSTS_INCLUDE_SUBDOMAINS)
|
|
self.assertFalse(self.betrieb.SECURE_HSTS_PRELOAD)
|
|
|
|
def test_proxy_kopf_wird_ausgewertet(self):
|
|
"""Ohne das baut build_absolute_uri() http://-Adressen."""
|
|
self.assertEqual(
|
|
self.betrieb.SECURE_PROXY_SSL_HEADER,
|
|
("HTTP_X_FORWARDED_PROTO", "https"),
|
|
)
|
|
|
|
def test_referrer_bleibt_im_haus(self):
|
|
self.assertEqual(self.betrieb.SECURE_REFERRER_POLICY, "same-origin")
|
|
|
|
def test_in_der_entwicklung_gilt_nichts_davon(self):
|
|
"""⚠️ Sonst kann sich auf http://localhost niemand mehr anmelden.
|
|
|
|
Ein ``Secure``-Cookie wird über einfaches HTTP gar nicht gesetzt — die
|
|
Anmeldung schlüge fehl, und die Ursache wäre ausgerechnet die
|
|
Einstellung, die Sicherheit bringen soll.
|
|
"""
|
|
from django.conf import settings
|
|
|
|
# ⚠️ ``settings.DEBUG`` taugt hier NICHT als Probe: Der Testläufer
|
|
# setzt es grundsätzlich auf False, egal was die Datei gerechnet hat.
|
|
# Entscheidend ist, was der ``if not DEBUG``-Block beim IMPORT gesehen
|
|
# hat — und das war ``DJANGO_DEBUG=1``. Also die Flaggen prüfen.
|
|
self.assertFalse(getattr(settings, "SESSION_COOKIE_SECURE", False))
|
|
self.assertFalse(getattr(settings, "CSRF_COOKIE_SECURE", False))
|
|
self.assertEqual(getattr(settings, "SECURE_HSTS_SECONDS", 0), 0)
|