snapshot: preserve upload hardening phase

This commit is contained in:
Md Bayazid Bostame
2026-03-27 12:44:53 +01:00
parent b9441f2503
commit eb0fb811e4
3 changed files with 302 additions and 47 deletions

View File

@@ -1,5 +1,4 @@
from django import forms
from pathlib import Path
from datetime import timedelta
from django.contrib.auth import authenticate, get_user_model, password_validation
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm, PasswordResetForm, SetPasswordForm
@@ -12,6 +11,13 @@ from .form_builder import apply_form_field_config
from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, PortalBranding, PortalCompanyConfig, PortalTrialConfig, UserProfile, WorkflowConfig
from .roles import ROLE_ADMIN, ROLE_GROUP_NAMES, ROLE_IT_STAFF, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_STAFF, ROLE_SUPER_ADMIN, assign_user_role, user_has_capability
from .totp import normalize_recovery_code, normalize_totp_token, verify_totp_token
from .upload_validation import (
validate_avatar_upload,
validate_favicon_upload,
validate_logo_upload,
validate_pdf_upload,
validate_signature_upload,
)
YES_NO_CHOICES = [('', '--'), ('ja', 'Ja'), ('nein', 'Nein')]
@@ -233,10 +239,7 @@ class AccountAvatarForm(forms.ModelForm):
def clean_avatar_image(self):
avatar = self.cleaned_data.get('avatar_image')
if not avatar:
return avatar
if getattr(avatar, 'size', 0) > 5 * 1024 * 1024:
raise forms.ValidationError(_('Das Profilbild darf maximal 5 MB groß sein.'))
validate_avatar_upload(avatar)
return avatar
@@ -565,26 +568,17 @@ class PortalBrandingForm(forms.ModelForm):
def clean_logo_image(self):
logo = self.cleaned_data.get('logo_image')
if not logo:
return logo
if getattr(logo, 'size', 0) > 5 * 1024 * 1024:
raise forms.ValidationError(_('Das Logo darf maximal 5 MB groß sein.'))
validate_logo_upload(logo)
return logo
def clean_pdf_letterhead(self):
letterhead = self.cleaned_data.get('pdf_letterhead')
if not letterhead:
return letterhead
if getattr(letterhead, 'size', 0) > 10 * 1024 * 1024:
raise forms.ValidationError(_('Der PDF-Briefkopf darf maximal 10 MB groß sein.'))
validate_pdf_upload(letterhead)
return letterhead
def clean_favicon_image(self):
favicon = self.cleaned_data.get('favicon_image')
if not favicon:
return favicon
if getattr(favicon, 'size', 0) > 2 * 1024 * 1024:
raise forms.ValidationError(_('Das Favicon darf maximal 2 MB groß sein.'))
validate_favicon_upload(favicon)
return favicon
@@ -832,36 +826,7 @@ class OnboardingRequestForm(forms.ModelForm):
def clean_signature_image(self):
image = self.cleaned_data.get('signature_image')
if not image:
return image
max_size = 4 * 1024 * 1024 # 4 MB
if image.size > max_size:
raise forms.ValidationError('Die Signatur-Datei ist zu groß (max. 4 MB).')
content_type = (getattr(image, 'content_type', '') or '').lower().strip()
extension = Path(getattr(image, 'name', '')).suffix.lower()
allowed_content_types = {
'image/png',
'image/x-png',
'image/jpeg',
'image/jpg',
'image/pjpeg',
}
allowed_extensions = {'.png', '.jpg', '.jpeg'}
if content_type and not content_type.startswith('image/'):
raise forms.ValidationError('Bitte eine PNG- oder JPG-Datei hochladen.')
if content_type and content_type not in allowed_content_types and extension not in allowed_extensions:
raise forms.ValidationError('Bitte eine PNG- oder JPG-Datei hochladen.')
if not content_type and extension not in allowed_extensions:
raise forms.ValidationError('Bitte eine PNG- oder JPG-Datei hochladen.')
try:
header = image.read(16)
image.seek(0)
except Exception:
raise forms.ValidationError('Die Signatur-Datei konnte nicht gelesen werden.')
is_png = header.startswith(b'\x89PNG\r\n\x1a\n')
is_jpeg = header.startswith(b'\xff\xd8\xff')
if not (is_png or is_jpeg):
raise forms.ValidationError('Die Signatur-Datei ist kein gültiges PNG/JPG-Bild.')
validate_signature_upload(image)
return image
def clean(self):