Skip to content
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
1 change: 1 addition & 0 deletions CHANGES/6456.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unified `pulp_labels` key validation across create/update, `set_label`/`unset_label`, and label filters to consistently allow alphanumerics, underscores, spaces, hyphens, and dots.
1 change: 1 addition & 0 deletions CHANGES/6593.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a traceback when setting `pulp_labels` with null values on create or update.
10 changes: 8 additions & 2 deletions pulpcore/app/serializers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,16 +564,22 @@ class SetLabelSerializer(serializers.Serializer):
Serializer for synchronously setting a label.
"""

key = serializers.SlugField(required=True)
key = serializers.CharField(required=True)
value = serializers.CharField(required=True, allow_null=True, allow_blank=True)

def validate(self, data):
from pulpcore.app.serializers.fields import pulp_labels_validator

pulp_labels_validator({data["key"]: data["value"]})
return super().validate(data)


class UnsetLabelSerializer(serializers.Serializer):
"""
Serializer for synchronously UNsetting a label.
"""

key = serializers.SlugField(required=True)
key = serializers.CharField(required=True)
value = serializers.CharField(read_only=True)

def validate_key(self, value):
Expand Down
12 changes: 9 additions & 3 deletions pulpcore/app/serializers/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from rest_framework.fields import empty

from pulpcore.app import models
from pulpcore.constants import LABEL_KEY_REGEX
from pulpcore.app.serializers import DetailIdentityField, IdentityField, RelatedField
from pulpcore.app.util import reverse

Expand Down Expand Up @@ -427,9 +428,14 @@ def pulp_labels_validator(value):
value = json.loads(value)

for k, v in value.items():
if not re.match(r"^[\w ]+$", k):
raise serializers.ValidationError(_("Key '{}' contains non-alphanumerics.").format(k))
if re.search(r"[,()]", v):
if not re.match(LABEL_KEY_REGEX, k):
raise serializers.ValidationError(
_(
"Key '{}' contains invalid characters. Only alphanumerics, underscores,"
" spaces, hyphens, and dots are allowed."
).format(k)
)
if v is not None and re.search(r"[,()]", v):
raise serializers.ValidationError(
_("Key '{}' contains value with comma or parenthesis.").format(k)
)
Expand Down
3 changes: 2 additions & 1 deletion pulpcore/app/viewsets/custom_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from gettext import gettext as _

from django.conf import settings
from pulpcore.constants import LABEL_KEY_CHARS
from django.db.models import ObjectDoesNotExist
from django_filters import BaseInFilter, CharFilter, Filter
from drf_spectacular.types import OpenApiTypes
Expand Down Expand Up @@ -313,7 +314,7 @@ def filter(self, qs, value):
return qs

for term in value.split(","):
match = re.match(r"(!?[\w\s]+)(=|!=|~)?(.*)?", term)
match = re.match(rf"(!?{LABEL_KEY_CHARS}+)(=|!=|~)?(.*)?", term)
if not match:
raise DRFValidationError(_("Invalid search term: '{}'.").format(term))
key, op, val = match.groups()
Expand Down
4 changes: 4 additions & 0 deletions pulpcore/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@
ORPHAN_PROTECTION_TIME_LOWER_BOUND = 0
ORPHAN_PROTECTION_TIME_UPPER_BOUND = 4294967295 # (2^32)-1

# Valid characters for pulp_labels keys: alphanumerics, underscores, spaces, hyphens, and dots.
LABEL_KEY_CHARS = r"[\w .\-]"
LABEL_KEY_REGEX = rf"^{LABEL_KEY_CHARS}+$"

# VULNERABILITY REPORT CONSTANTS
# OSV API URL
OSV_QUERY_URL = "https://api.osv.dev/v1/query"
38 changes: 38 additions & 0 deletions pulpcore/tests/unit/serializers/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,44 @@
from rest_framework import serializers

from pulpcore.app.serializers import fields
from pulpcore.app.serializers.fields import pulp_labels_validator


@pytest.mark.parametrize(
"labels",
[
pytest.param({"key": "value"}, id="normal"),
pytest.param({"key": ""}, id="empty-value"),
pytest.param({"key": None}, id="none-value"),
pytest.param({"key1": "value", "key2": None, "key3": ""}, id="multiple-keys"),
pytest.param({"my-key": "value"}, id="dash-key"),
pytest.param({"my.key": "value"}, id="dotted-key"),
pytest.param({"my key": "value"}, id="spaced-key"),
pytest.param({"my-dotted.key": "value"}, id="dotted-dash-key"),
pytest.param({"spaced key-with.mixed_chars": "value"}, id="all-key"),
],
)
def test_pulp_labels_validator_valid(labels):
"""Valid label keys and values should pass validation."""
result = pulp_labels_validator(labels)
assert result == labels


@pytest.mark.parametrize(
"labels",
[
pytest.param({"key": "val,ue"}, id="comma-value"),
pytest.param({"key": "val(ue"}, id="open-parenthesis-value"),
pytest.param({"key": "val)ue"}, id="close-parenthesis-value"),
pytest.param({"bad!key": "value"}, id="exclamation-key"),
pytest.param({"bad:key": "value"}, id="colon-key"),
pytest.param({"bad@key": "value"}, id="at-sign-key"),
],
)
def test_pulp_labels_validator_invalid(labels):
"""Invalid label keys or values should raise ValidationError."""
with pytest.raises(serializers.ValidationError):
pulp_labels_validator(labels)


@pytest.mark.parametrize(
Expand Down