Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 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
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
9 changes: 1 addition & 8 deletions src/onegov/file/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,8 @@ def get_supported_image_mime_types() -> set[str]:

# Not all PIL formats register a mime type, fill in the blanks ourselves.
supported_types = {
'image/bmp',
'image/x-bmp',
'image/x-MS-bmp',
'image/x-icon',
'image/x-ico',
'image/x-win-bitmap',
'image/x-pcx',
'image/x-portable-pixmap',
'image/x-tga'
'image/x-xcf',
}

for mime in Image.MIME.values():
Expand Down
82 changes: 57 additions & 25 deletions src/onegov/form/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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 @@ -59,7 +59,7 @@
if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Iterator, Sequence
from datetime import datetime
from onegov.core.types import FileDict as StrictFileDict
from onegov.core.types import FileDict as StrictFileDict, LaxFileDict
from onegov.file import File
from onegov.form import Form
from onegov.form.types import (
Expand Down Expand Up @@ -274,28 +274,58 @@ class UploadField(FileField):
file: IO[bytes] | None
filename: str | None

if TYPE_CHECKING:
def __init__(
self,
label: str | None = None,
validators: Validators[FormT, Self] | None = None,
filters: Sequence[Filter] = (),
description: str = '',
id: str | None = None,
default: Sequence[StrictFileDict] = (),
widget: Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = '',
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
# onegov specific kwargs that get popped off
*,
fieldset: str | None = None,
depends_on: Sequence[Any] | None = None,
pricing: PricingRules | None = None,
): ...
def __init__(
self,
label: str | None = None,
validators: Validators[FormT, Self] | None = None,
filters: Sequence[Filter] = (),
description: str = '',
id: str | None = None,
default: LaxFileDict | None = None,
widget: Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
allowed_mimetypes: Sequence[str] | None = None,
_form: BaseForm | None = None,
_prefix: str = '',
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
# onegov specific kwargs that get popped off
*,
fieldset: str | None = None,
depends_on: Sequence[Any] | None = None,
pricing: PricingRules | None = None,
):
validator = (
WhitelistedMimeType(allowed_mimetypes)
if allowed_mimetypes
else WhitelistedMimeType()
)

if validators:
validators = list(validators)
if not any(isinstance(validator, WhitelistedMimeType)
for validator in validators
):
validators.append(validator)
Copy link
Member

Choose a reason for hiding this comment

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

Make sure this logic still holds up when field dependencies are in play, I have a feeling it does not, since it will wrap everything in a conditional validator, so you probably need to detect that case, so you don't add an unconditional and potentially redundant mimetype validator.

Copy link
Member

@Daverball Daverball Dec 8, 2025

Choose a reason for hiding this comment

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

It might be easier and more robust to store the mimetypes in an extra attribute and to override the post_validate hook, since validation_stopped is one of the parameters, which would be True if the dependency condition is unfulfilled.

So you can do something like the following in post_validate:

if not validation_stopped:
    WhitelistedMimeType(self.mimetypes)(form, self)

Copy link
Member

Choose a reason for hiding this comment

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

Actually nevermind, this has the same problem, StrictOptional only stops validation if there is no data. Although technically there should be no data, when the field is hidden, so it might not matter in the end.

else:
validators = [validator]

super().__init__(
label=label,
validators=validators,
filters=filters,
description=description,
id=id,
default=default,
widget=widget,
render_kw=render_kw,
name=name,
_form=_form,
_prefix=_prefix,
_translations=_translations,
_meta=_meta,
)

# this is not quite accurate, since it is either a dictionary with all
# the keys or none of the keys, which would make type narrowing easier
Expand Down Expand Up @@ -474,6 +504,7 @@ def __init__(
render_kw: dict[str, Any] | None = None,
name: str | None = None,
upload_widget: Widget[UploadField] | None = None,
allowed_mimetypes: Sequence[str] | None = None,
_form: BaseForm | None = None,
_prefix: str = '',
_translations: _SupportsGettextAndNgettext | None = None,
Expand All @@ -492,11 +523,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,
allowed_mimetypes=allowed_mimetypes,
**extra_arguments
)
super().__init__(
Expand All @@ -507,6 +538,7 @@ def __init__(
id=id,
default=default,
widget=widget, # type:ignore[arg-type]
validators=[*(validators or ())],
Copy link
Member

Choose a reason for hiding this comment

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

I would still pass these to the unbound field, not the field list. There are still things like the file size validator that makes more sense on the individual field. It allows you to re-simplify the validators back to what they were before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without having it on the FieldList it does not work. On the unbound field the validators will be overwritten from the FieldList defaulted validators which results in the wrong mime type(s)

Copy link
Member

Choose a reason for hiding this comment

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

Are you sure this still happens? Before it made sense because you set a default validator on the class, now this should no longer happen, as long as you only pass the validators to the unbound field.

It's possible however that this also interacts badly with field dependencies, since there's no special casing for field lists. It might make sense to change how we implement field dependencies, since right now it is pretty fragile.

Copy link
Contributor Author

@Tschuppi81 Tschuppi81 Dec 8, 2025

Choose a reason for hiding this comment

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

It fails when parsing a form from formcode, see test_parse_multiplefileinput.
I am looking into why it fails once I move the validators from the FieldList to the UnboundField.

My guess is that add_field skips something where as the new UploadMultipleField ctor does not

render_kw=render_kw,
name=name,
_form=_form,
Expand Down
Loading