diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py index e253906de71c77020b483b9af6d3cd9b974dd3e8..e7bbf8f1f00c44935ce972792bdba580979377af 100644 --- a/api/funkwhale_api/common/serializers.py +++ b/api/funkwhale_api/common/serializers.py @@ -1,8 +1,12 @@ import collections +import io +import PIL +import os from rest_framework import serializers from django.core.exceptions import ObjectDoesNotExist +from django.core.files.uploadedfile import SimpleUploadedFile from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -190,3 +194,25 @@ def track_fields_for_update(*fields): return serializer_class return decorator + + +class StripExifImageField(serializers.ImageField): + def to_internal_value(self, data): + file_obj = super().to_internal_value(data) + + image = PIL.Image.open(file_obj) + data = list(image.getdata()) + image_without_exif = PIL.Image.new(image.mode, image.size) + image_without_exif.putdata(data) + + with io.BytesIO() as output: + image_without_exif.save( + output, + format=PIL.Image.EXTENSION[os.path.splitext(file_obj.name)[-1]], + quality=100, + ) + content = output.getvalue() + + return SimpleUploadedFile( + file_obj.name, content, content_type=file_obj.content_type + ) diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py index bcacc3144614c9780034f9a5db1c1c044306671b..c75604f6eb94ea97b26247604b13bf2c01fa6dfe 100644 --- a/api/funkwhale_api/users/serializers.py +++ b/api/funkwhale_api/users/serializers.py @@ -11,7 +11,7 @@ from rest_framework import serializers from versatileimagefield.serializers import VersatileImageFieldSerializer from funkwhale_api.activity import serializers as activity_serializers - +from funkwhale_api.common import serializers as common_serializers from . import models @@ -66,7 +66,13 @@ class UserActivitySerializer(activity_serializers.ModelSerializer): return "Person" -avatar_field = VersatileImageFieldSerializer(allow_null=True, sizes="square") +class AvatarField( + common_serializers.StripExifImageField, VersatileImageFieldSerializer +): + pass + + +avatar_field = AvatarField(allow_null=True, sizes="square") class UserBasicSerializer(serializers.ModelSerializer): diff --git a/api/tests/common/exif.jpg b/api/tests/common/exif.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c13141aff10c549e994468090649702c5c13c086 Binary files /dev/null and b/api/tests/common/exif.jpg differ diff --git a/api/tests/common/test_serializers.py b/api/tests/common/test_serializers.py index bf04e8d2ae015533dadabbf88e96ac1ed6cdf2b2..6d20443af6b97d603f029482816893c5afeab683 100644 --- a/api/tests/common/test_serializers.py +++ b/api/tests/common/test_serializers.py @@ -1,3 +1,8 @@ +import os +import PIL + +from django.core.files.uploadedfile import SimpleUploadedFile + import django_filters from funkwhale_api.common import serializers @@ -163,3 +168,17 @@ def test_track_fields_for_update(mocker): {"field1": "value1", "field2": "value2"}, {"field1": "newvalue1", "field2": "newvalue2"}, ) + + +def test_strip_exif_field(): + source_path = os.path.join(os.path.dirname(__file__), "exif.jpg") + source = PIL.Image.open(source_path) + + assert bool(source._getexif()) + + with open(source_path, "rb") as f: + uploaded = SimpleUploadedFile("source.jpg", f.read(), content_type="image/jpeg") + field = serializers.StripExifImageField() + + cleaned = PIL.Image.open(field.to_internal_value(uploaded)) + assert cleaned._getexif() is None diff --git a/changes/changelog.d/374.enhancement b/changes/changelog.d/374.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..71d3e911ffca6fde3f68637a9c7b5a7ffc67c8f8 --- /dev/null +++ b/changes/changelog.d/374.enhancement @@ -0,0 +1 @@ +Strip EXIF metadata from uploaded avatars to avoid leaking private data (#374)