Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle incoming invalid dynamic metadata #17390

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions tests/unit/forklift/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@ def test_invalid_dists(self, field_name):
metadata.parse(None, form_data=data)
_assert_invalid_metadata(excinfo.value, field_name.replace("_", "-"))

def test_valid_dynamic(self):
data = MultiDict(metadata_version="2.2", name="spam", version="2.0")
data.add("dynamic", "keywords")
data.add("dynamic", "author")
meta = metadata.parse(None, form_data=data)
assert meta.dynamic == ["keywords", "author"]

def test_invalid_dynamic(self):
data = MultiDict(metadata_version="2.2", name="spam", version="2.0")
data.add("dynamic", "Requires")
with pytest.raises(ExceptionGroup) as excinfo:
metadata.parse(None, form_data=data)
_assert_invalid_metadata(excinfo.value, "dynamic")


class TestFromFormData:
def test_valid(self):
Expand Down
22 changes: 18 additions & 4 deletions warehouse/forklift/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from trove_classifiers import all_classifiers, deprecated_classifiers
from webob.multidict import MultiDict

from warehouse.packaging.models import DynamicFieldsEnum
from warehouse.utils import http

SUPPORTED_METADATA_VERSIONS = {"1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"}
Expand Down Expand Up @@ -141,7 +142,7 @@ def _validate_metadata(metadata: Metadata, *, backfill: bool = False):
InvalidMetadata("classifier", f"{classifier!r} is not a valid classifier.")
)

# Validate that no deprecated classifers are being used.
# Validate that no deprecated classifiers are being used.
# NOTE: We only check this is we're not doing a backfill, because backfill
# operations may legitimately use deprecated classifiers.
if not backfill:
Expand Down Expand Up @@ -235,6 +236,19 @@ def _validate_metadata(metadata: Metadata, *, backfill: bool = False):
)
)

# Validate that any `dynamic` fields passed are in the allowed list
# TODO: This probably should be lifted up to packaging.metadata
for field in {"dynamic"}:
if (value := getattr(metadata, field)) is not None:
for key in value:
if key not in map(str.lower, DynamicFieldsEnum.enums):
ewdurbin marked this conversation as resolved.
Show resolved Hide resolved
errors.append(
InvalidMetadata(
_RAW_TO_EMAIL_MAPPING.get(field, field),
f"Dynamic field {key!r} is not a valid dynamic field.",
)
)

# Ensure that License and License-Expression are mutually exclusive
# See https://peps.python.org/pep-0639/#deprecate-license-field
if metadata.license and metadata.license_expression:
Expand Down Expand Up @@ -263,12 +277,12 @@ def _validate_metadata(metadata: Metadata, *, backfill: bool = False):


def parse_form_metadata(data: MultiDict) -> Metadata:
# We construct a RawMetdata using the form data, which we will later pass
# We construct a RawMetadata using the form data, which we will later pass
# to Metadata to get a validated metadata.
#
# NOTE: Form data is very similiar to the email format where the only difference
# NOTE: Form data is very similar to the email format where the only difference
# between a list and a single value is whether or not the same key is used
# multiple times. Thus we will handle things in a similiar way, always
# multiple times. Thus, we will handle things in a similar way, always
# fetching things as a list and then determining what to do based on the
# field type and how many values we found.
#
Expand Down