feat(web): Seite „Schlüssel“ und Schlüsselauswahl in der Anbindung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-10-10 23:43:40 +02:00
co-authored by Claude Opus 5.5
parent 686542ff6f
commit fb975f1eae
6 changed files with 234 additions and 9 deletions
+67 -1
View File
@@ -154,7 +154,16 @@ def test_security_headers(client: TestClient) -> None:
def test_no_inline_scripts_or_styles(client: TestClient) -> None:
for url in ("/", "/mounts", "/mounts/new", "/rules", "/rules/new", "/log", "/settings"):
for url in (
"/",
"/mounts",
"/mounts/new",
"/keys",
"/rules",
"/rules/new",
"/log",
"/settings",
):
html = client.get(url).text
assert "style=" not in html, url
assert not re.search(r"<script(?![^>]*\bsrc=)", html), url
@@ -555,3 +564,60 @@ def test_export_excludes_secrets_and_import_roundtrip(
files={"file": ("export.json", text, "application/json")},
)
assert "Übersprungen" in c2.get("/settings").text
# -- Schlüsselverwaltung -------------------------------------------------------------------
def test_keys_page_generate_import_select_delete(client: TestClient, state: NetMover) -> None:
from netmover.crypto import generate_ssh_keypair
r = post(client, "/keys/generate", {"name": "paperless-lxc"})
assert r.status_code == 303
private, public = generate_ssh_keypair()
r = post(client, "/keys/import", {"name": "alt", "private_key": private, "passphrase": ""})
assert r.status_code == 303
page = client.get("/keys").text
assert "paperless-lxc" in page and "alt" in page
assert public.split()[1] in page # öffentlicher Teil sichtbar
for line in private.splitlines()[1:-1]: # keine Zeile des privaten Schlüssels
assert line not in page
assert "restrict,command=&#34;/usr/lib/openssh/sftp-server&#34;" in page
with state.session_factory() as s:
key_id = s.scalar(select(Credential.id).where(Credential.name == "alt"))
form = client.get("/mounts/new").text
assert f'value="{key_id}"' in form and "neuer Schlüssel nur für diese Anbindung" in form
r = post(
client,
"/mounts/new",
{
"name": "paperless",
"type": "sftp",
"server": "10.0.0.5",
"share": "/consume",
"port": "22",
"auth": "ssh_key",
"key_id": str(key_id),
"username": "root",
"enabled": "on",
},
)
assert r.status_code == 303, r.text
mounts = client.get("/mounts").text
assert "gespeichertem Schlüssel" in mounts and "„alt“" in mounts
edit = client.get(f"/mounts/{_mount_id(state, 'paperless')}/edit").text
assert f'<option value="{key_id}" selected>' in edit and 'value="root"' in edit
post(client, f"/keys/{key_id}/delete")
assert "noch benutzt" in client.get("/keys").text
post(client, f"/mounts/{_mount_id(state, 'paperless')}/delete")
post(client, f"/keys/{key_id}/delete")
page = client.get("/keys").text
assert "gelöscht" in page and ">alt<" not in page
def test_key_import_error_is_shown(client: TestClient) -> None:
post(client, "/keys/import", {"name": "x", "private_key": "kein schlüssel", "passphrase": ""})
assert "Kein lesbarer privater Schlüssel" in client.get("/keys").text