Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8699bc1
Rework mime type white list
Tschuppi81 Nov 10, 2025
66ac404
Ensure mime type validator for file fields in formcode
Tschuppi81 Nov 17, 2025
5ebe0fb
Adds mime type validator by default
Tschuppi81 Nov 20, 2025
9416abf
Merge branch 'master' into feature/ogc-2738-pentest-arbitrary-file-up…
Tschuppi81 Nov 20, 2025
7eed62a
Fix wrongly attached validators
Tschuppi81 Dec 1, 2025
4ab42df
Revert
Tschuppi81 Dec 1, 2025
cf252e2
improve tests and fix linter issues
Tschuppi81 Dec 1, 2025
6e9afd6
Rework tests
Tschuppi81 Dec 1, 2025
3f718d2
Fix file size validator and align
Tschuppi81 Dec 1, 2025
5572020
Set mime types for all upload fields
Tschuppi81 Dec 2, 2025
a6bd385
Merge branch 'master' into feature/ogc-2738-pentest-arbitrary-file-up…
Tschuppi81 Dec 2, 2025
1293c8e
Fix missing validator
Tschuppi81 Dec 4, 2025
af685f5
Cleanup unused import
Tschuppi81 Dec 4, 2025
a50f6f0
Add fixme
Tschuppi81 Dec 4, 2025
458a82d
Fix linting errors
Tschuppi81 Dec 4, 2025
8d99429
Extend test
Tschuppi81 Dec 4, 2025
c31d0e3
Fix more linter issues
Tschuppi81 Dec 4, 2025
eee980c
Remove validators from field list
Tschuppi81 Dec 5, 2025
248819a
Add old ms office doc types
Tschuppi81 Dec 5, 2025
5621994
Remove non-standard svg type
Tschuppi81 Dec 5, 2025
21274d1
Update supported image mime type
Tschuppi81 Dec 5, 2025
c4a1eef
Remove unused json validator
Tschuppi81 Dec 5, 2025
e77bc49
Pass validators only to FieldList and introduce allowed mime types
Tschuppi81 Dec 8, 2025
9a6a187
Adjust tests
Tschuppi81 Dec 8, 2025
d122951
Fix wrong default value for UploadField
Tschuppi81 Dec 8, 2025
1e7b28b
Fix linter issues
Tschuppi81 Dec 8, 2025
427563e
Limit event import to excel kind of files
Tschuppi81 Dec 8, 2025
fa438e0
Improve validator type
Tschuppi81 Dec 8, 2025
0f7b210
Verify file type for file collection upload
Tschuppi81 Dec 15, 2025
e5552d6
Adjust tests
Tschuppi81 Dec 15, 2025
a545477
Set default mime type white list for translator directory as well
Tschuppi81 Dec 15, 2025
5f7bcb2
Revert "Verify file type for file collection upload"
Tschuppi81 Dec 18, 2025
5c54c06
Make unsupported media type error visible on files view
Tschuppi81 Dec 18, 2025
f839024
Align upload columns with already uploaded files
Tschuppi81 Dec 18, 2025
ef4b1fe
Merge branch 'master' into feature/ogc-2738-pentest-arbitrary-file-up…
Tschuppi81 Dec 29, 2025
789e503
Ensure required value error is shown for multiple upload widget
Tschuppi81 Dec 30, 2025
f6f9d7c
Extend tests with required upload field
Tschuppi81 Dec 30, 2025
c170834
Fix syntax error
Tschuppi81 Dec 30, 2025
00b1614
Extend tests
Tschuppi81 Jan 5, 2026
0e9c164
Merge master
Tschuppi81 Feb 7, 2026
a7a000f
Let's simplify this again, since it doesn't need to work on a FieldLi…
Tschuppi81 Feb 7, 2026
2511b0b
Revert changing mimetypes for PIL
Tschuppi81 Feb 9, 2026
9d9a819
Merge branch 'feature/ogc-2738-pentest-arbitrary-file-upload' of gith…
Tschuppi81 Feb 9, 2026
15d752d
Change back to StrictFileDict for UploadField
Tschuppi81 Feb 9, 2026
44c347b
Extend FileSizeLimit for UploadMultipleField
Tschuppi81 Feb 9, 2026
03aa665
Move mimetype validation into post validation step
Tschuppi81 Feb 9, 2026
8a8b6d0
Fix mimtypes type
Tschuppi81 Feb 9, 2026
e313a24
Fix mimtypes type
Tschuppi81 Feb 9, 2026
d4eabb6
Adjust for election day and swissvotes
Tschuppi81 Feb 9, 2026
5b4cfa5
Minor changes and cleanup
Tschuppi81 Feb 9, 2026
85084a5
Fix wrong mimetypes
Tschuppi81 Feb 10, 2026
7be6af8
Move swissvotes post validation to a later point
Tschuppi81 Feb 10, 2026
01b0af2
Fix some linter issues
Tschuppi81 Feb 10, 2026
0b047e1
Linter: Revert back to Collection, remove obsolete post validate
Tschuppi81 Feb 10, 2026
20b5c85
Revert "Fix some linter issues"
Tschuppi81 Feb 10, 2026
f92840a
Fixes most linter issues
Tschuppi81 Feb 10, 2026
39b3ce0
Fix wrong type
Tschuppi81 Feb 10, 2026
4b21d7d
Fix more linter issues
Tschuppi81 Feb 10, 2026
e6422d5
Resolves rest of linter issues
Tschuppi81 Feb 10, 2026
0ca7d5a
Resolve linter issues in tests
Tschuppi81 Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/onegov/agency/forms/agency.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from onegov.form.fields import ChosenSelectField, HtmlField
from onegov.form.fields import MultiCheckboxField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_IMAGE
from onegov.form.validators import WhitelistedMimeType
from onegov.gis import CoordinatesField
from sqlalchemy import func
Expand Down Expand Up @@ -73,10 +73,7 @@ class ExtendedAgencyForm(Form):
organigram = UploadField(
label=_('Organigram'),
validators=[
WhitelistedMimeType({
'image/jpeg',
'image/png',
}),
WhitelistedMimeType(MIME_TYPES_IMAGE),
FileSizeLimit(1 * 1024 * 1024)
]
)
Expand Down
4 changes: 2 additions & 2 deletions src/onegov/election_day/forms/election.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from onegov.form.fields import ChosenSelectMultipleField
from onegov.form.fields import PanelField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_PDF
from onegov.form.validators import WhitelistedMimeType
from re import findall
from sqlalchemy import or_
Expand Down Expand Up @@ -323,7 +323,7 @@ class ElectionForm(Form):
explanations_pdf = UploadField(
label=_('Explanations (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link')
Expand Down
8 changes: 4 additions & 4 deletions src/onegov/election_day/forms/election_compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from onegov.form.fields import ChosenSelectMultipleField
from onegov.form.fields import PanelField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_PDF
from onegov.form.validators import WhitelistedMimeType
from re import findall
from sqlalchemy import or_
Expand Down Expand Up @@ -228,7 +228,7 @@ class ElectionCompoundForm(Form):
explanations_pdf = UploadField(
label=_('Explanations (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link')
Expand All @@ -237,7 +237,7 @@ class ElectionCompoundForm(Form):
upper_apportionment_pdf = UploadField(
label=_('Upper apportionment (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link'),
Expand All @@ -247,7 +247,7 @@ class ElectionCompoundForm(Form):
lower_apportionment_pdf = UploadField(
label=_('Lower apportionment (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link'),
Expand Down
4 changes: 2 additions & 2 deletions src/onegov/election_day/forms/upload/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
'text/csv'
}
ALLOWED_MIME_TYPES_XML = {
'application/xml',
'text/xml',
'application/xml', # official, standard
'text/xml', # deprecated MIME type for XML content
'text/plain'
}

Expand Down
4 changes: 2 additions & 2 deletions src/onegov/election_day/forms/vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from onegov.form.fields import ChosenSelectField
from onegov.form.fields import PanelField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_PDF
from onegov.form.validators import WhitelistedMimeType
from wtforms.fields import BooleanField
from wtforms.fields import DateField
Expand Down Expand Up @@ -280,7 +280,7 @@ class VoteForm(Form):
explanations_pdf = UploadField(
label=_('Explanations (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link')
Expand Down
7 changes: 5 additions & 2 deletions src/onegov/form/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from onegov.file.utils import IMAGE_MIME_TYPES_AND_SVG
from onegov.form import log, _
from onegov.form.utils import path_to_filename
from onegov.form.validators import ValidPhoneNumber
from onegov.form.validators import ValidPhoneNumber, WhitelistedMimeType
from onegov.form.widgets import ChosenSelectWidget
from onegov.form.widgets import LinkPanelWidget
from onegov.form.widgets import DurationInput
Expand Down Expand Up @@ -260,6 +260,7 @@ class UploadField(FileField):
action: Literal['keep', 'replace', 'delete']
file: IO[bytes] | None
filename: str | None
validators = [WhitelistedMimeType()]

if TYPE_CHECKING:
def __init__(
Expand Down Expand Up @@ -448,6 +449,7 @@ def _add_entry(self, d: _MultiDictLikeWithGetlist, /) -> UploadField:

upload_field_class: type[UploadField] = UploadField
upload_widget: Widget[UploadField] = UploadWidget()
validators = [WhitelistedMimeType()]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here


def __init__(
self,
Expand Down Expand Up @@ -479,11 +481,11 @@ def __init__(

# a lot of the arguments we just pass through to the subfield
unbound_field = self.upload_field_class(
validators=validators, # type:ignore[arg-type]
filters=filters,
description=description,
widget=upload_widget,
render_kw=render_kw,
validators=validators, # type:ignore[arg-type]
**extra_arguments
)
super().__init__(
Expand All @@ -496,6 +498,7 @@ def __init__(
widget=widget, # type:ignore[arg-type]
render_kw=render_kw,
name=name,
validators=validators,
_form=_form,
_prefix=_prefix,
_translations=_translations,
Expand Down
114 changes: 99 additions & 15 deletions src/onegov/form/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
from wtforms.validators import ValidationError


from typing import Generic, TYPE_CHECKING
from typing import Generic, TYPE_CHECKING, Any

if TYPE_CHECKING:
from collections.abc import Collection, Sequence
from onegov.core.orm import Base
Expand Down Expand Up @@ -114,13 +115,88 @@ def __call__(self, form: Form, field: Field) -> None:
if not field.data:
return

if field.data.get('size', 0) > self.max_bytes:
if isinstance(field.data, list): # UploadMultipleField
for data in field.data:
if not data:
continue # in case of file deletion

self.validate_filesize(field, data)

else:
self.validate_filesize(field, field.data)

def validate_filesize(self, field: Field, data: dict[Any, Any]) -> None:
if data.get('size', 0) > self.max_bytes:
message = field.gettext(self.message).format(
humanize.naturalsize(self.max_bytes)
)
raise ValidationError(message)


MIME_TYPES_PDF = {
'application/pdf',
}

# for now not allowed by default
MIME_TYPES_JSON = {
'application/json',
}

MIME_TYPES_DOCUMENT = {
'application/msword', # doc
'application/rtf',
*MIME_TYPES_PDF,
'application/vnd.ms-excel', # xls
('application/vnd.openxmlformats-officedocument.'
'presentationml.presentation'), # pptx
('application/vnd.openxmlformats-officedocument.'
'spreadsheetml.sheet'), # xlsx
('application/vnd.openxmlformats-officedocument.'
'wordprocessingml.document'), # docx
}

MIME_TYPES_XML = {
'application/xml',
}

MIME_TYPES_ARCHIVE = {
'application/zip',
}

MIME_TYPES_TEXT_DATA = {
'text/csv',
'text/plain',
}

MIME_TYPES_IMAGE = {
'image/bmp',
'image/gif',
'image/jpeg', # jpeg, jpg
'image/png',
'image/svg',
'image/svg+xml',
'image/tiff',
'image/webp', # shall we allow it?
'image/x-ms-bmp',
}

MIME_TYPES_AUDIO = {
'audio/mp4',
'audio/mpeg',
'audio/wav',
'audio/webm', # weba
}

MIME_TYPES_VIDEO = {
'video/mp4',
'video/mpeg', # mpg, mpeg
'video/ogg',
'video/quicktime', # mov
'video/webm', # webm
'video/x-msvideo', # avi
}


class WhitelistedMimeType:
""" Makes sure an uploaded file is in a whitelist of allowed mimetypes.

Expand All @@ -129,17 +205,13 @@ class WhitelistedMimeType:
"""

whitelist: Collection[str] = {
'application/excel',
'application/vnd.ms-excel',
'application/msword',
'application/pdf',
'application/zip',
'image/gif',
'image/jpeg',
'image/png',
'image/x-ms-bmp',
'text/plain',
'text/csv'
*MIME_TYPES_DOCUMENT,
*MIME_TYPES_XML,
*MIME_TYPES_ARCHIVE,
*MIME_TYPES_TEXT_DATA,
*MIME_TYPES_IMAGE,
*MIME_TYPES_AUDIO,
*MIME_TYPES_VIDEO,
}

message = _('Files of this type are not supported.')
Expand All @@ -152,8 +224,20 @@ def __call__(self, form: Form, field: Field) -> None:
if not field.data:
return

if field.data['mimetype'] not in self.whitelist:
raise ValidationError(field.gettext(self.message))
if isinstance(field.data, list): # UploadMultipleField
for data in field.data:
if not data:
continue # in case of file deletion

self.validate_mimetype(field, data)

else:
self.validate_mimetype(field, field.data)

def validate_mimetype(self, field: Field, data: dict[Any, Any]) -> None:
if data['mimetype'] not in self.whitelist:
message = field.gettext(self.message)
raise ValidationError(field.gettext(message))


class ExpectedExtensions(WhitelistedMimeType):
Expand Down
12 changes: 8 additions & 4 deletions src/onegov/landsgemeinde/forms/agenda.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
from onegov.form.fields import TimeField
from onegov.form.fields import UploadField
from onegov.form.forms import NamedFileForm
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import WhitelistedMimeType
from onegov.form.validators import (
FileSizeLimit,
MIME_TYPES_PDF,
MIME_TYPES_ARCHIVE,
WhitelistedMimeType
)
from onegov.landsgemeinde import _
from onegov.landsgemeinde.layouts import DefaultLayout
from onegov.landsgemeinde.models import AgendaItem, LandsgemeindeFile
Expand Down Expand Up @@ -80,7 +84,7 @@ class AgendaItemForm(NamedFileForm):
label=_('Excerpt from the Memorial (PDF)'),
fieldset=_('Memorial'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
]
)
Expand Down Expand Up @@ -226,7 +230,7 @@ class AgendaItemUploadForm(Form):
label=_('Agenda Item ZIP'),
fieldset=_('Import'),
validators=[
WhitelistedMimeType({'application/zip'}),
WhitelistedMimeType(MIME_TYPES_ARCHIVE),
FileSizeLimit(100 * 1024 * 1024)
]
)
Expand Down
Loading