Revert "Merge pull request 'Diverse Formular-Verbessungen' (#155) from form-tweaks into main"

Das Formular funktioniert nicht mehr sauber
This reverts commit 74cb447ac9, reversing
changes made to f36e4d1548.
This commit is contained in:
Matthias Hemmerich 2024-04-04 22:55:32 +02:00
parent 74cb447ac9
commit a054bc1b07
No known key found for this signature in database
GPG key ID: A4F6345580BB4814

View file

@ -1,17 +1,17 @@
#!/usr/bin/env python #!/usr/bin/env python
import base64 import base64
import io
import cgi import cgi
import collections import collections
from collections.abc import Mapping from collections.abc import Mapping
import hmac import hmac
import json
import mimetypes import mimetypes
import os
import re import re
import os
import secrets import secrets
import json
from typing import Any, Optional, overload from typing import Any, Optional, overload
from urllib.parse import urljoin
import itsdangerous import itsdangerous
import requests import requests
@ -34,20 +34,6 @@ except IOError:
HONEYPOT_FIELD_NAME = "addressline1" HONEYPOT_FIELD_NAME = "addressline1"
# This regex merely validates what the in-browser form validation already checks and
# isn't all too strict.
EMAIL_REGEX = re.compile(r"^[^ ]+@[^ ]+\.[^ ]+$")
# Mapping from site-defined devices (see sites/angestoepselt/_data/config.json in this
# repository) to the corresponding Zammad categories:
# https://codeberg.org/angestoepselt/homepage/issues/120#issuecomment-1727768
# This is a (str | int) -> str map because some keys we check against below might be
# integers and it's just easier to type this way.
FORM_CATEGORY_MAP: dict[str | int, str] = {
"Laptop": "laptop",
"Laptop ohne Akku": "laptop-battery-missing",
"Desktop-Computer": "desktop",
}
SITE_DIRECTORY = os.environ.get("SITE_DIRECTORY", "") SITE_DIRECTORY = os.environ.get("SITE_DIRECTORY", "")
request_uri = os.environ.get("REQUEST_URI", "").lower().rstrip("/") request_uri = os.environ.get("REQUEST_URI", "").lower().rstrip("/")
@ -142,16 +128,15 @@ if form_disabled:
form = cgi.FieldStorage() form = cgi.FieldStorage()
@overload @overload
def get_form_value(name: str, default: None = ..., *, cast: type[bytes]) -> tuple[str, bytes]:... def get_form_value(name: str, default: Optional[str], cast: type[str] = str) -> str: ...
@overload @overload
def get_form_value(name: str, default: Optional[int] = ..., *, cast: type[int]) -> int:... def get_form_value(name: str, default: Optional[int], cast: type[int]) -> int:...
@overload @overload
def get_form_value(name: str, default: Optional[str] = ..., *, cast: type[str] = ...) -> str: ... def get_form_value(name: str, default: None = ..., cast: type[bytes] = ...) -> tuple[str, bytes]:...
def get_form_value( def get_form_value(
name: str, name: str,
default: Any = None, default: Any = None,
*, cast: type[str] | type[int] | type[io.BytesIO] = str,
cast: type[str] | type[int] | type[bytes] = str,
) -> Any: ) -> Any:
if name not in form: if name not in form:
if default is None: if default is None:
@ -169,10 +154,7 @@ def get_form_value(
return (value_object.filename or "upload"), value_object.file.read() return (value_object.filename or "upload"), value_object.file.read()
else: else:
try: try:
result = cast(form.getfirst(name)) return cast(form.getfirst(name))
if isinstance(result, str):
result = result.strip()
return result
except (TypeError, ValueError): except (TypeError, ValueError):
fail("400 Bad Request", f"Invalid value for field: {name}") fail("400 Bad Request", f"Invalid value for field: {name}")
@ -181,7 +163,7 @@ def get_form_value(
# constant-time string comparison here. # constant-time string comparison here.
given_csrf_token = get_form_value("csrftoken") given_csrf_token = get_form_value("csrftoken")
if not hmac.compare_digest(csrf_token, given_csrf_token): if not hmac.compare_digest(csrf_token, given_csrf_token):
fail("400 Bad Request", "Invalid CSRF token") fail("400 Bad Request", f"Invalid CSRF token")
# If the honeypot field was not empty, back off. # If the honeypot field was not empty, back off.
@ -201,21 +183,15 @@ if not isinstance(hcaptcha_data, Mapping) or not hcaptcha_data.get("success", Fa
# Extract all the actually provided form data. This is different from form to # Extract all the actually provided form data. This is different from form to
# form (see the match block below). # form (see the match block below).
contact_name = get_form_value("contactname") contact_name = get_form_value("contactname")
contact_names = contact_name.split(" ")
contact_email = get_form_value("contactemail") contact_email = get_form_value("contactemail")
if not EMAIL_REGEX.fullmatch(contact_email):
fail("400 Bad Request", "Invalid Email address")
message = get_form_value("message", "[Keine Nachricht hinterlassen]") message = get_form_value("message", "[Keine Nachricht hinterlassen]")
attachment: Optional[tuple[str, bytes]] = None attachment: Optional[tuple[str, bytes]] = None
ticket_details = collections.OrderedDict[str, str | int]() ticket_details = collections.OrderedDict()
ticket_details["Kontaktperson"] = contact_name ticket_details["Kontaktperson"] = contact_name
ticket_details["Email"] = contact_email ticket_details["Email"] = contact_email
form_group = "csw-Allgemein" form_group = "csw-Allgemein"
form_category: str | None = None
match request_uri: match request_uri:
case "/kontakt": case "/kontakt":
@ -237,17 +213,14 @@ match request_uri:
ticket_details["Adresse"] = get_form_value("addressline") ticket_details["Adresse"] = get_form_value("addressline")
ticket_details["PLZ"] = get_form_value("postalcode") ticket_details["PLZ"] = get_form_value("postalcode")
ticket_details["Stadt"] = get_form_value("city") ticket_details["Stadt"] = get_form_value("city")
ticket_details["Anzahl Desktops"] = get_form_value("desktopcount", 0, cast=int) ticket_details["Anzahl Desktops"] = get_form_value("desktopcount", 0, int)
ticket_details["Anzahl Laptops"] = get_form_value("laptopcount", 0, cast=int) ticket_details["Anzahl Laptops"] = get_form_value("laptopcount", 0, int)
ticket_details["Anzahl Drucker"] = get_form_value("printercount", 0, cast=int) ticket_details["Anzahl Drucker"] = get_form_value("printercount", 0, int)
case "/computer-beantragen/privat": case "/computer-beantragen/privat":
form_name = "Computerantrag (privat)" form_name = "Computerantrag (privat)"
form_group = "csw-Anfragen" form_group = "csw-Anfragen"
ticket_details["Gewünschte Hardware"] = get_form_value("hardware", default="Unbekannt") ticket_details["Gewünschte Hardware"] = get_form_value("hardware", default="Unbekannt")
form_category = FORM_CATEGORY_MAP.get(ticket_details["Gewünschte Hardware"], None)
ticket_details["Adresse"] = get_form_value("addressline") ticket_details["Adresse"] = get_form_value("addressline")
ticket_details["PLZ"] = get_form_value("postalcode") ticket_details["PLZ"] = get_form_value("postalcode")
ticket_details["Stadt"] = get_form_value("city") ticket_details["Stadt"] = get_form_value("city")
@ -303,46 +276,18 @@ ticket_details["Kontaktformular"] = form_name
# testing). # testing).
form_group = os.environ.get("ZAMMAD_GROUP", "") or form_group form_group = os.environ.get("ZAMMAD_GROUP", "") or form_group
ZAMMAD_URL = os.environ.get("ZAMMAD_URL", "") ZAMMAD_URL = os.environ.get("ZAMMAD_URL", "").rstrip("/")
ZAMMAD_TOKEN = os.environ.get("ZAMMAD_TOKEN", "") ZAMMAD_TOKEN = os.environ.get("ZAMMAD_TOKEN", "")
session.headers.update(Authorization=f"Token token={ZAMMAD_TOKEN}") session.headers.update(Authorization=f"Token token={ZAMMAD_TOKEN}")
try: try:
# Create a new user for the client. For some reason, using "guess:{email}" as the
# customer_id when creating the ticket doesn't really work, as described in the
# Zammad documentation [1]. Instead, we sometimes need to explictily create the
# user beforehand. See this discussion [2] for more details.
# [1]: https://docs.zammad.org/en/latest/api/ticket/index.html#create
# [2]: https://codeberg.org/angestoepselt/homepage/issues/141
response = session.post(
urljoin(ZAMMAD_URL, "api/v1/users"),
json=dict(
# Yes, yes... This goes against pretty much all best practices for parsing
# names. But: it's only internal and we save the name verbatim again below
# so we're going to go ahead and do it anyway.
firstname=" ".join(contact_names[:-1]) if len(contact_names) >= 2 else "?",
lastname=contact_names[-1],
email=contact_email,
)
)
if response.status_code == 422:
# This email address is already in use by another user.
customer_id = f"guess:{contact_email}"
else:
response.raise_for_status()
customer_id = response.json()["id"]
assert isinstance(customer_id, (str, int))
# Add the actual ticket to the system. # Add the actual ticket to the system.
response = session.post( response = session.post(
urljoin(ZAMMAD_URL, "api/v1/tickets"), f"{ZAMMAD_URL}/api/v1/tickets",
headers={
"X-On-Behalf-Of": contact_email,
},
json=dict( json=dict(
title=f"Kontaktformular {contact_name} {form_name}", title=f"Kontaktformular {contact_name} {form_name}",
group=form_group, group=form_group,
customer_id=customer_id, customer_id=f"guess:{contact_email}",
article=dict( article=dict(
type="web", type="web",
internal=True, internal=True,
@ -364,7 +309,7 @@ try:
# Add a second article to the ticket that contains all the other information # Add a second article to the ticket that contains all the other information
# from the contact form. # from the contact form.
response = session.post( response = session.post(
urljoin(ZAMMAD_URL, "api/v1/ticket_articles"), f"{ZAMMAD_URL}/api/v1/ticket_articles",
json=dict( json=dict(
ticket_id=ticket_id, ticket_id=ticket_id,
type="note", type="note",
@ -379,7 +324,7 @@ try:
# Add a tag to the ticket, denoting which contact form it came from. # Add a tag to the ticket, denoting which contact form it came from.
response = session.post( response = session.post(
urljoin(ZAMMAD_URL, "api/v1/tags/add"), f"{ZAMMAD_URL}/api/v1/tags/add",
json=dict( json=dict(
object="Ticket", object="Ticket",
o_id=ticket_id, o_id=ticket_id,