Prevent URLs from being added to ratings text (#22604)

This commit is contained in:
Mathieu Pillard 2024-08-28 18:08:03 +02:00 коммит произвёл GitHub
Родитель 3351d7febe
Коммит 132be7de2e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 32 добавлений и 48 удалений

Просмотреть файл

@ -32,6 +32,7 @@ from olympia.api.fields import (
) )
from olympia.api.serializers import AMOModelSerializer, BaseESSerializer from olympia.api.serializers import AMOModelSerializer, BaseESSerializer
from olympia.api.utils import is_gate_active from olympia.api.utils import is_gate_active
from olympia.api.validators import NoURLsValidator
from olympia.applications.models import AppVersion from olympia.applications.models import AppVersion
from olympia.bandwagon.models import Collection from olympia.bandwagon.models import Collection
from olympia.constants.applications import APP_IDS, APPS_ALL from olympia.constants.applications import APP_IDS, APPS_ALL
@ -85,7 +86,6 @@ from .validators import (
MatchingGuidValidator, MatchingGuidValidator,
NoFallbackDefaultLocaleValidator, NoFallbackDefaultLocaleValidator,
NoThemesValidator, NoThemesValidator,
NoURLsValidator,
ReviewedSourceFileValidator, ReviewedSourceFileValidator,
VerifyMozillaTrademark, VerifyMozillaTrademark,
VersionAddonMetadataValidator, VersionAddonMetadataValidator,

Просмотреть файл

@ -5,7 +5,7 @@ from django.utils.translation import gettext
from rest_framework import exceptions, fields from rest_framework import exceptions, fields
from olympia import amo from olympia import amo
from olympia.amo.utils import find_language, verify_no_urls from olympia.amo.utils import find_language
from olympia.versions.models import License from olympia.versions.models import License
from .models import Addon from .models import Addon
@ -318,14 +318,6 @@ class CanSetCompatibilityValidator:
) )
class NoURLsValidator:
def __call__(self, value):
try:
verify_no_urls(value)
except forms.ValidationError as exc:
raise exceptions.ValidationError(exc.message)
class NoThemesValidator: class NoThemesValidator:
requires_context = True requires_context = True

Просмотреть файл

@ -1,7 +1,8 @@
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from rest_framework import serializers from rest_framework import exceptions
from olympia.amo.utils import verify_no_urls
from olympia.amo.validators import OneOrMorePrintableCharacterValidator from olympia.amo.validators import OneOrMorePrintableCharacterValidator
@ -13,4 +14,12 @@ class OneOrMorePrintableCharacterAPIValidator(OneOrMorePrintableCharacterValidat
try: try:
return super().__call__(value) return super().__call__(value)
except DjangoValidationError: except DjangoValidationError:
raise serializers.ValidationError(self.message) raise exceptions.ValidationError(self.message)
class NoURLsValidator:
def __call__(self, value):
try:
verify_no_urls(value)
except DjangoValidationError as exc:
raise exceptions.ValidationError(exc.message)

Просмотреть файл

@ -1,11 +1,9 @@
import re import re
from collections import OrderedDict from collections import OrderedDict
from urllib.parse import unquote
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext, ngettext from django.utils.translation import gettext, ngettext
from bleach.linkifier import TLDS
from rest_framework import serializers from rest_framework import serializers
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
@ -13,23 +11,13 @@ from olympia.accounts.serializers import BaseUserSerializer
from olympia.addons.serializers import SimpleAddonSerializer, SimpleVersionSerializer from olympia.addons.serializers import SimpleAddonSerializer, SimpleVersionSerializer
from olympia.api.serializers import AMOModelSerializer from olympia.api.serializers import AMOModelSerializer
from olympia.api.utils import is_gate_active from olympia.api.utils import is_gate_active
from olympia.api.validators import NoURLsValidator
from olympia.users.utils import RestrictionChecker from olympia.users.utils import RestrictionChecker
from olympia.versions.models import Version from olympia.versions.models import Version
from .models import DeniedRatingWord, Rating, RatingFlag from .models import DeniedRatingWord, Rating, RatingFlag
# This matches the following three types of patterns:
# http://... or https://..., generic domain names, and IPv4 octets. It does not
# match IPv6 addresses or long strings such as "example dot com".
link_pattern = re.compile(
r'((://)|' # Protocols (e.g.: http://)
r'((\d{1,3}\.){3}(\d{1,3}))|'
r'([0-9a-z\-%%]+\.(%s)))' % '|'.join(TLDS),
(re.I | re.U | re.M),
)
class RatingAddonSerializer(SimpleAddonSerializer): class RatingAddonSerializer(SimpleAddonSerializer):
def get_attribute(self, obj): def get_attribute(self, obj):
# Avoid database queries if possible by re-using the addon object from # Avoid database queries if possible by re-using the addon object from
@ -39,6 +27,12 @@ class RatingAddonSerializer(SimpleAddonSerializer):
class BaseRatingSerializer(AMOModelSerializer): class BaseRatingSerializer(AMOModelSerializer):
addon = RatingAddonSerializer(read_only=True) addon = RatingAddonSerializer(read_only=True)
body = serializers.CharField(
allow_blank=True,
allow_null=True,
required=False,
validators=[NoURLsValidator()],
)
is_deleted = serializers.BooleanField(read_only=True, source='deleted') is_deleted = serializers.BooleanField(read_only=True, source='deleted')
is_developer_reply = serializers.SerializerMethodField() is_developer_reply = serializers.SerializerMethodField()
is_latest = serializers.BooleanField(read_only=True) is_latest = serializers.BooleanField(read_only=True)
@ -149,12 +143,10 @@ class BaseRatingSerializer(AMOModelSerializer):
f'{user.email} or {{{ip_address}}} matched a UserRestriction', f'{user.email} or {{{ip_address}}} matched a UserRestriction',
) )
# Flag the review if there was a word match or a URL was in it. if hasattr(self, '_rating_flag_to_save'):
# Unquote when searching for links, in case someone tries 'example%2ecom'. # If we have the _rating_flag_to_save set, we should be set the
if ( # flag and editorreview attributes on the instance we're about to
hasattr(self, '_rating_flag_to_save') # save.
or link_pattern.search(unquote(data.get('body') or '')) is not None
):
data['flag'] = True data['flag'] = True
data['editorreview'] = True data['editorreview'] = True
@ -188,7 +180,12 @@ class BaseRatingSerializer(AMOModelSerializer):
class RatingSerializerReply(BaseRatingSerializer): class RatingSerializerReply(BaseRatingSerializer):
"""Serializer used for replies only.""" """Serializer used for replies only."""
body = serializers.CharField(allow_null=False, required=True, allow_blank=False) body = serializers.CharField(
allow_null=False,
required=True,
allow_blank=False,
validators=[NoURLsValidator()],
)
def to_representation(self, obj): def to_representation(self, obj):
should_access_deleted = getattr( should_access_deleted = getattr(

Просмотреть файл

@ -1824,7 +1824,6 @@ class TestRatingViewSetPost(TestCase):
self.client.login_api(self.user) self.client.login_api(self.user)
assert not Rating.objects.exists() assert not Rating.objects.exists()
body = 'Trying to spam <br> http://éxample.com' body = 'Trying to spam <br> http://éxample.com'
cleaned_body = 'Trying to spam \n http://éxample.com'
response = self.client.post( response = self.client.post(
self.url, self.url,
{ {
@ -1834,21 +1833,8 @@ class TestRatingViewSetPost(TestCase):
'version': self.addon.current_version.pk, 'version': self.addon.current_version.pk,
}, },
) )
assert response.status_code == 201 assert response.status_code == 400
review = Rating.objects.latest('pk') assert response.data['body'] == ['URLs are not allowed.']
assert review.pk == response.data['id']
assert str(review.body) == response.data['body'] == cleaned_body
assert review.rating == response.data['score'] == 5
assert review.user == self.user
assert review.reply_to is None
assert review.addon == self.addon
assert review.version == self.addon.current_version
assert response.data['version'] == {
'id': review.version.id,
'version': review.version.version,
}
assert review.flag
assert review.editorreview
def test_post_rating_float(self): def test_post_rating_float(self):
self.user = user_factory() self.user = user_factory()