mirror of
https://github.com/ae-utbm/sith.git
synced 2025-04-15 18:40:23 +00:00
Check that uploaded images are actually images
This commit is contained in:
parent
13f417ba30
commit
376af35bfb
@ -1,16 +1,29 @@
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Any
|
||||
|
||||
from annotated_types import MinLen
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext as _
|
||||
from haystack.query import SearchQuerySet
|
||||
from ninja import FilterSchema, ModelSchema, Schema
|
||||
from ninja import FilterSchema, ModelSchema, Schema, UploadedFile
|
||||
from pydantic import AliasChoices, Field
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from core.models import Group, SithFile, User
|
||||
from core.utils import is_image
|
||||
|
||||
|
||||
class UploadedImage(UploadedFile):
|
||||
@classmethod
|
||||
def _validate(cls, v: Any, info: ValidationInfo) -> Any:
|
||||
super()._validate(v, info)
|
||||
if not is_image(v):
|
||||
msg = _("This file is not a valid image")
|
||||
raise ValueError(msg)
|
||||
return v
|
||||
|
||||
|
||||
class SimpleUserSchema(ModelSchema):
|
||||
|
@ -22,6 +22,7 @@ from typing import Final
|
||||
import PIL
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.uploadedfile import UploadedFile
|
||||
from django.http import HttpRequest
|
||||
from django.utils.timezone import localdate
|
||||
from PIL import ExifTags
|
||||
@ -111,6 +112,18 @@ def get_semester_code(d: date | None = None) -> str:
|
||||
return "P" + str(start.year)[-2:]
|
||||
|
||||
|
||||
def is_image(file: UploadedFile):
|
||||
try:
|
||||
im = PIL.Image.open(file.file)
|
||||
im.verify()
|
||||
# go back to the start of the file, without closing it.
|
||||
# Otherwise, further checks on django side will fail
|
||||
file.seek(0)
|
||||
except PIL.UnidentifiedImageError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def resize_image(
|
||||
im: Image, edge: int, img_format: str, *, optimize: bool = True
|
||||
) -> ContentFile:
|
||||
|
@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-06 15:54+0200\n"
|
||||
"POT-Creation-Date: 2025-04-08 16:20+0200\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@ -116,7 +116,7 @@ msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur"
|
||||
msgid "You should specify a role"
|
||||
msgstr "Vous devez choisir un rôle"
|
||||
|
||||
#: club/forms.py sas/views.py
|
||||
#: club/forms.py sas/forms.py
|
||||
msgid "You do not have the permission to do that"
|
||||
msgstr "Vous n'avez pas la permission de faire cela"
|
||||
|
||||
@ -1047,7 +1047,7 @@ msgid "Posters - edit"
|
||||
msgstr "Affiche - modifier"
|
||||
|
||||
#: com/templates/com/poster_list.jinja com/templates/com/screen_list.jinja
|
||||
#: sas/templates/sas/main.jinja
|
||||
#: sas/templates/sas/fragments/album_create_form.jinja
|
||||
msgid "Create"
|
||||
msgstr "Créer"
|
||||
|
||||
@ -1644,6 +1644,10 @@ msgstr "étiquette"
|
||||
msgid "operation type"
|
||||
msgstr "type d'opération"
|
||||
|
||||
#: core/schemas.py
|
||||
msgid "This file is not a valid image"
|
||||
msgstr "Ce fichier n'est pas une image valide"
|
||||
|
||||
#: core/templates/core/403.jinja
|
||||
msgid "403, Forbidden"
|
||||
msgstr "403, Non autorisé"
|
||||
@ -2729,7 +2733,7 @@ msgstr "Ajouter un nouveau dossier"
|
||||
msgid "Error creating folder %(folder_name)s: %(msg)s"
|
||||
msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s"
|
||||
|
||||
#: core/views/files.py core/views/forms.py sas/forms.py
|
||||
#: core/views/files.py core/views/forms.py
|
||||
#, python-format
|
||||
msgid "Error uploading file %(file_name)s: %(msg)s"
|
||||
msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s"
|
||||
@ -4715,11 +4719,6 @@ msgstr "Ajouter un nouvel album"
|
||||
msgid "Upload images"
|
||||
msgstr "Envoyer les images"
|
||||
|
||||
#: sas/forms.py
|
||||
#, python-format
|
||||
msgid "Error creating album %(album)s: %(msg)s"
|
||||
msgstr "Erreur de création de l'album %(album)s : %(msg)s"
|
||||
|
||||
#: sas/forms.py
|
||||
msgid "You already requested moderation for this picture."
|
||||
msgstr "Vous avez déjà déposé une demande de retrait pour cette photo."
|
||||
|
@ -2,7 +2,7 @@ from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import F
|
||||
from django.urls import reverse
|
||||
from ninja import Body, Query, UploadedFile
|
||||
from ninja import Body, File, Query
|
||||
from ninja.errors import HttpError
|
||||
from ninja_extra import ControllerBase, api_controller, paginate, route
|
||||
from ninja_extra.exceptions import NotFound, PermissionDenied
|
||||
@ -19,6 +19,7 @@ from core.auth.api_permissions import (
|
||||
IsRoot,
|
||||
)
|
||||
from core.models import Notification, User
|
||||
from core.schemas import UploadedImage
|
||||
from sas.models import Album, PeoplePictureRelation, Picture
|
||||
from sas.schemas import (
|
||||
AlbumAutocompleteSchema,
|
||||
@ -106,7 +107,7 @@ class PicturesController(ControllerBase):
|
||||
response={200: None, 409: dict[str, list[str]]},
|
||||
url_name="upload_picture",
|
||||
)
|
||||
def upload_picture(self, album_id: Body[int], picture: UploadedFile):
|
||||
def upload_picture(self, album_id: Body[int], picture: File[UploadedImage]):
|
||||
album = self.get_object_or_exception(Album, pk=album_id)
|
||||
user = self.context.request.user
|
||||
self_moderate = user.has_perm("sas.moderate_sasfile")
|
||||
|
@ -266,3 +266,23 @@ def test_upload_picture(client: Client):
|
||||
assert picture.file.name == "SAS/test album/img.png"
|
||||
assert picture.compressed.name == ".compressed/SAS/test album/img.webp"
|
||||
assert picture.thumbnail.name == ".thumbnails/SAS/test album/img.webp"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_upload_invalid_picture(client: Client):
|
||||
sas = SithFile.objects.get(pk=settings.SITH_SAS_ROOT_DIR_ID)
|
||||
album = baker.make(Album, is_in_sas=True, parent=sas, name="test album")
|
||||
user = baker.make(User, is_superuser=True)
|
||||
client.force_login(user)
|
||||
file = SimpleUploadedFile(
|
||||
name="file.txt",
|
||||
content=b"azerty",
|
||||
content_type="image/png", # the server shouldn't blindly trust the content_type
|
||||
)
|
||||
res = client.post(
|
||||
reverse("api:upload_picture"), {"album_id": album.id, "picture": file}
|
||||
)
|
||||
assert res.status_code == 422
|
||||
assert res.json()["detail"][0]["ctx"]["error"] == (
|
||||
"Ce fichier n'est pas une image valide"
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user