Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
77 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
d7db252
Remove unused validators param
Tschuppi81 Feb 12, 2026
6424a66
No need to create a list
Tschuppi81 Feb 19, 2026
7b7a450
Keep using getattr
Tschuppi81 Feb 19, 2026
cf903fe
post validate needs to test validation stopped
Tschuppi81 Feb 19, 2026
8ec5407
Remove param with default value
Tschuppi81 Feb 19, 2026
fe8496c
Remove unless tuple creation 1
Tschuppi81 Feb 19, 2026
597d67d
Remove unless tuple creation 2
Tschuppi81 Feb 19, 2026
6ebaf81
Remove unless tuple creation 3
Tschuppi81 Feb 19, 2026
0e03033
Use unpacking
Tschuppi81 Feb 19, 2026
a67425f
Fix broken field initialization
Tschuppi81 Feb 19, 2026
eb7d29a
Fix missing import
Tschuppi81 Feb 19, 2026
d3b3249
Fix missing assert
Tschuppi81 Feb 19, 2026
9e89796
Apply suggestion from @Daverball
Tschuppi81 Feb 19, 2026
64123ac
Apply suggestion from @Daverball
Tschuppi81 Feb 19, 2026
76c94cb
Align SwissvotesMetadataField to SwissvotesDatasetField
Tschuppi81 Feb 19, 2026
00b76b6
Switch to excel mimetypes for newsletterform
Tschuppi81 Feb 19, 2026
b24c85f
Fix linter issue
Tschuppi81 Feb 19, 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 @@ -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 @@ -59,7 +59,7 @@
if TYPE_CHECKING:
from collections.abc import Callable, 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 @@ -261,28 +261,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.

Copy link
Member

Choose a reason for hiding this comment

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

I think it would still be a bit cleaner to move the validator into post_validate like in my code suggestion above, instead of trying to modify the given validator chain. If someone passes in a WhitelistValidator we can emit a warning or raise an exception to use the mimetypes parameter instead.

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 @@ -461,6 +491,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 @@ -479,11 +510,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 @@ -494,6 +525,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

Copy link
Member

Choose a reason for hiding this comment

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

You're just looking for the validator in the wrong place in that test. The validator is on each field in the field list, not the field list itself. So I would get rid of this again and simplify the validators, otherwise the validator will run twice for each file. Once you move the validation from a validator in validators to post_validate you no longer will have to check for the presence of that validator anyways, since it's baked into the field itself.

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