mirror of
https://github.com/suitenumerique/drive.git
synced 2026-08-17 20:15:40 +02:00
✨(backend) add an allowed file extension list
We want to limit what type of file can be uploaded. For this we use an allowed list of file extension. This allowed list can be disabled using a setting and this list is configurable using a setting. When enabled, file with no extension and hidden file are also rejected.
This commit is contained in:
@@ -14,6 +14,7 @@ and this project adheres to
|
||||
- ✨(backend) add throttle mechanism to limit indexation job
|
||||
- 🌐(front) set html lang attribute on language change
|
||||
- ✨(front) add download and preview events
|
||||
- ✨(backend) add an allowed file extension list
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
This document lists all configurable environment variables for the Drive application, extracted from the Django settings configuration.
|
||||
|
||||
|
||||
| Environment Variable | Description | Default Value |
|
||||
|---------------------|-------------|---------------|
|
||||
| `ALLOWED_HOSTS` | List of allowed hosts for the application (used in Production) | `[]` |
|
||||
@@ -43,6 +44,7 @@ This document lists all configurable environment variables for the Drive applica
|
||||
| `EMAIL_USE_TLS` | Use TLS for SMTP connection | `False` |
|
||||
| `FEATURES_ALPHA` | Enable alpha features | `False` |
|
||||
| `FEATURES_INDEXED_SEARCH` | Enable the search of indexed files through the API | `True` |
|
||||
| `FILE_EXTENSIONS_ALLOWED` | List of file extension allowed to be uploaded | See in the settings.py file |
|
||||
| `FRONTEND_THEME` | Frontend theme configuration | `None` |
|
||||
| `FRONTEND_FEEDBACK_BUTTON_SHOW` | Show feedback button | `False` |
|
||||
| `FRONTEND_FEEDBACK_BUTTON_IDLE` | Make feedback button idle (e.g. to bind to external library) | `False` |
|
||||
@@ -86,6 +88,7 @@ This document lists all configurable environment variables for the Drive applica
|
||||
| `POSTHOG_HOST` | PostHog analytics host URL | `https://eu.i.posthog.com` |
|
||||
| `POSTHOG_KEY` | PostHog analytics API key | `None` |
|
||||
| `REDIS_URL` | Redis connection URL | `redis://redis:6379/0` |
|
||||
| `RESTRICT_UPLOAD_FILE_TYPE` | Boolean to enable or not upload restriction based on file type (extension + mimetype) | `True` |
|
||||
| `SEARCH_INDEXER_ALLOWED_MIMETYPES` | Indexable files mimetypes | `["text/"]` |
|
||||
| `SEARCH_INDEXER_CLASS` | Class of the backend for item indexation & search ||
|
||||
| `SEARCH_INDEXER_BATCH_SIZE` | Size of each batch for indexation of all items | `1000` |
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""Client serializers for the drive core app."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from os.path import splitext
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.conf import settings
|
||||
@@ -15,6 +17,8 @@ from core.api import utils
|
||||
from core.storage import get_storage_compute_backend
|
||||
from wopi import utils as wopi_utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class UsageMetricSerializer(serializers.BaseSerializer):
|
||||
@@ -429,6 +433,15 @@ class CreateItemSerializer(ItemSerializer):
|
||||
code="item_create_file_filename_required",
|
||||
)
|
||||
|
||||
if settings.RESTRICT_UPLOAD_FILE_TYPE:
|
||||
_root, extension = splitext(attrs["filename"])
|
||||
if extension not in settings.FILE_EXTENSIONS_ALLOWED:
|
||||
logger.info("create_item: file extension not allowed %s for filename %s", extension, attrs["filename"])
|
||||
raise serializers.ValidationError(
|
||||
{"filename": _("This file extension is not allowed.")},
|
||||
code="item_create_file_extension_not_allowed",
|
||||
)
|
||||
|
||||
# When it's a file we force the title with the filename
|
||||
attrs["title"] = attrs["filename"]
|
||||
|
||||
|
||||
@@ -299,6 +299,61 @@ def test_api_items_children_create_related_success_override_s3_endpoint(settings
|
||||
assert len(query_params) == 0
|
||||
|
||||
|
||||
def test_api_items_children_create_file_extension_not_allowed(settings):
|
||||
"""
|
||||
Creating a file item with an extension not allowed should fail.
|
||||
"""
|
||||
settings.RESTRICT_UPLOAD_FILE_TYPE = True
|
||||
user = factories.UserFactory()
|
||||
item = factories.ItemFactory(link_reach="restricted", type=ItemTypeChoices.FOLDER)
|
||||
factories.UserItemAccessFactory(user=user, item=item, role="owner")
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
f"/api/v1.0/items/{item.id!s}/children/",
|
||||
{
|
||||
"type": ItemTypeChoices.FILE,
|
||||
"filename": "file.notallowed",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"errors": [
|
||||
{
|
||||
"attr": "filename",
|
||||
"code": "item_create_file_extension_not_allowed",
|
||||
"detail": "This file extension is not allowed.",
|
||||
},
|
||||
],
|
||||
"type": "validation_error",
|
||||
}
|
||||
|
||||
|
||||
def test_api_items_children_create_file_extension_not_allowed_not_checking_extension(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
Creating a file item with an extension not allowed should fail.
|
||||
"""
|
||||
settings.RESTRICT_UPLOAD_FILE_TYPE = False
|
||||
user = factories.UserFactory()
|
||||
item = factories.ItemFactory(link_reach="restricted", type=ItemTypeChoices.FOLDER)
|
||||
factories.UserItemAccessFactory(user=user, item=item, role="owner")
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
f"/api/v1.0/items/{item.id!s}/children/",
|
||||
{
|
||||
"type": ItemTypeChoices.FILE,
|
||||
"filename": "file.notallowed",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 201
|
||||
child = Item.objects.get(id=response.json()["id"])
|
||||
assert child.title == "file.notallowed"
|
||||
|
||||
|
||||
def test_api_items_children_create_force_id_success():
|
||||
"""It should be possible to force the item ID when creating a nested item."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
@@ -134,6 +134,117 @@ def test_api_items_create_file_authenticated_success():
|
||||
assert len(query_params) == 0
|
||||
|
||||
|
||||
def test_api_items_create_file_authenticated_extension_not_allowed():
|
||||
"""
|
||||
Creating a file item with an extension not allowed should fail.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
"/api/v1.0/items/",
|
||||
{
|
||||
"type": ItemTypeChoices.FILE,
|
||||
"filename": "file.notallowed",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"errors": [
|
||||
{
|
||||
"attr": "filename",
|
||||
"code": "item_create_file_extension_not_allowed",
|
||||
"detail": "This file extension is not allowed.",
|
||||
},
|
||||
],
|
||||
"type": "validation_error",
|
||||
}
|
||||
|
||||
|
||||
def test_api_items_create_file_authenticated_not_checking_extension(settings):
|
||||
"""
|
||||
Creating a file item with an extension not allowed should fail.
|
||||
"""
|
||||
settings.RESTRICT_UPLOAD_FILE_TYPE = False
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
"/api/v1.0/items/",
|
||||
{
|
||||
"type": ItemTypeChoices.FILE,
|
||||
"filename": "file.notallowed",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 201
|
||||
item = Item.objects.exclude(id=user.get_main_workspace().id).get()
|
||||
assert item.title == "file.notallowed"
|
||||
|
||||
|
||||
def test_api_items_create_file_authenticated_no_extension_but_checking_it_should_fail(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
Creating a file without an extension but checking the extension should fail.
|
||||
"""
|
||||
settings.RESTRICT_UPLOAD_FILE_TYPE = True
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
"/api/v1.0/items/",
|
||||
{
|
||||
"type": ItemTypeChoices.FILE,
|
||||
"filename": "file",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"errors": [
|
||||
{
|
||||
"attr": "filename",
|
||||
"code": "item_create_file_extension_not_allowed",
|
||||
"detail": "This file extension is not allowed.",
|
||||
},
|
||||
],
|
||||
"type": "validation_error",
|
||||
}
|
||||
|
||||
|
||||
def test_api_items_create_file_authenticated_hidden_file_but_checking_extension_should_fail(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
Creating a hidden file (starting with a dot) but checking the extension should fail.
|
||||
"""
|
||||
settings.RESTRICT_UPLOAD_FILE_TYPE = True
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
"/api/v1.0/items/",
|
||||
{
|
||||
"type": ItemTypeChoices.FILE,
|
||||
"filename": ".file",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"errors": [
|
||||
{
|
||||
"attr": "filename",
|
||||
"code": "item_create_file_extension_not_allowed",
|
||||
"detail": "This file extension is not allowed.",
|
||||
},
|
||||
],
|
||||
"type": "validation_error",
|
||||
}
|
||||
|
||||
|
||||
def test_api_items_create_authenticated_title_null():
|
||||
"""It should not be possible to create several items with a null title."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
@@ -210,6 +210,347 @@ class Base(Configuration):
|
||||
environ_name="DATA_UPLOAD_MAX_MEMORY_SIZE",
|
||||
environ_prefix=None,
|
||||
)
|
||||
RESTRICT_UPLOAD_FILE_TYPE = values.BooleanValue(
|
||||
default=True,
|
||||
environ_name="RESTRICT_UPLOAD_FILE_TYPE",
|
||||
environ_prefix=None,
|
||||
)
|
||||
FILE_EXTENSIONS_ALLOWED = values.ListValue(
|
||||
[
|
||||
".001",
|
||||
".002",
|
||||
".003",
|
||||
".3dm",
|
||||
".3dmf",
|
||||
".3g2",
|
||||
".3gp",
|
||||
".7z",
|
||||
".7z.001",
|
||||
".FCStd",
|
||||
".LDR",
|
||||
".N",
|
||||
".SLDDRW",
|
||||
".SLDPRT",
|
||||
".STEP",
|
||||
".aac",
|
||||
".accdb",
|
||||
".acidcsa",
|
||||
".acidppr",
|
||||
".acidsca",
|
||||
".acidssa",
|
||||
".afr",
|
||||
".ai",
|
||||
".aif",
|
||||
".alx",
|
||||
".apafis",
|
||||
".ape",
|
||||
".apk",
|
||||
".arc",
|
||||
".asc",
|
||||
".ase",
|
||||
".asy",
|
||||
".au",
|
||||
".aux",
|
||||
".avi",
|
||||
".axx",
|
||||
".bed",
|
||||
".bib",
|
||||
".bin",
|
||||
".bmp",
|
||||
".bpm",
|
||||
".bpmn",
|
||||
".bz",
|
||||
".bz2",
|
||||
".c",
|
||||
".cdc",
|
||||
".cdxml",
|
||||
".cer",
|
||||
".cif",
|
||||
".clmc",
|
||||
".clmx",
|
||||
".cpg",
|
||||
".crt",
|
||||
".crypto",
|
||||
".csr",
|
||||
".csv",
|
||||
".dat",
|
||||
".dbf",
|
||||
".deb",
|
||||
".dgn",
|
||||
".direct",
|
||||
".djvu",
|
||||
".dna",
|
||||
".doc",
|
||||
".docm",
|
||||
".docx",
|
||||
".docxf",
|
||||
".dot",
|
||||
".dotm",
|
||||
".dotx",
|
||||
".drawio",
|
||||
".drodm",
|
||||
".dsf",
|
||||
".dsn",
|
||||
".dwg",
|
||||
".dwt",
|
||||
".dxf",
|
||||
".dxi",
|
||||
".ecw",
|
||||
".eda",
|
||||
".edi",
|
||||
".egg",
|
||||
".emf",
|
||||
".eml",
|
||||
".ems",
|
||||
".enlx",
|
||||
".eot",
|
||||
".epgz",
|
||||
".epoc",
|
||||
".eps",
|
||||
".epub",
|
||||
".err",
|
||||
".excalidraw",
|
||||
".ext1",
|
||||
".ext2",
|
||||
".ext3",
|
||||
".f0",
|
||||
".f3d",
|
||||
".fas",
|
||||
".fasta",
|
||||
".fcstd",
|
||||
".fic",
|
||||
".fke",
|
||||
".fodg",
|
||||
".fodp",
|
||||
".fods",
|
||||
".fodt",
|
||||
".freeplane",
|
||||
".gan",
|
||||
".gbk",
|
||||
".gcode",
|
||||
".geo",
|
||||
".geojson",
|
||||
".gif",
|
||||
".gml",
|
||||
".gpkg",
|
||||
".gpx",
|
||||
".gradle",
|
||||
".graffle",
|
||||
".graphml",
|
||||
".grist",
|
||||
".gz",
|
||||
".heic",
|
||||
".ico",
|
||||
".ics",
|
||||
".id",
|
||||
".ifc",
|
||||
".igs",
|
||||
".ind",
|
||||
".indd",
|
||||
".int",
|
||||
".ipynb",
|
||||
".jif",
|
||||
".jp2",
|
||||
".jpe",
|
||||
".jpeg",
|
||||
".jpg",
|
||||
".json",
|
||||
".kdbx",
|
||||
".key",
|
||||
".kml",
|
||||
".kmz",
|
||||
".layout",
|
||||
".loc",
|
||||
".log",
|
||||
".lss",
|
||||
".lst",
|
||||
".m",
|
||||
".m4a",
|
||||
".m4b",
|
||||
".m4p",
|
||||
".m4r",
|
||||
".m4v",
|
||||
".map",
|
||||
".mbz",
|
||||
".mcd",
|
||||
".md",
|
||||
".mdb",
|
||||
".mkv",
|
||||
".mm",
|
||||
".moo",
|
||||
".mov",
|
||||
".mp3",
|
||||
".mp4",
|
||||
".mpd",
|
||||
".mpeg",
|
||||
".mpga",
|
||||
".mph",
|
||||
".mpp",
|
||||
".mri",
|
||||
".mrk",
|
||||
".msg",
|
||||
".mtgl",
|
||||
".mts",
|
||||
".mtz",
|
||||
".mvdx",
|
||||
".naf",
|
||||
".nist",
|
||||
".nitrace",
|
||||
".numbers",
|
||||
".oaf",
|
||||
".obj",
|
||||
".odb",
|
||||
".odc",
|
||||
".odf",
|
||||
".odg",
|
||||
".odi",
|
||||
".odm",
|
||||
".odp",
|
||||
".ods",
|
||||
".odt",
|
||||
".oform",
|
||||
".oft",
|
||||
".one",
|
||||
".opj",
|
||||
".opju",
|
||||
".osf",
|
||||
".otc",
|
||||
".otf",
|
||||
".otg",
|
||||
".oth",
|
||||
".oti",
|
||||
".oto",
|
||||
".otp",
|
||||
".ots",
|
||||
".ott",
|
||||
".p7s",
|
||||
".pages",
|
||||
".pbix",
|
||||
".pdb",
|
||||
".pdf",
|
||||
".pdfxml",
|
||||
".pgw",
|
||||
".ply",
|
||||
".png",
|
||||
".pot",
|
||||
".potm",
|
||||
".potx",
|
||||
".pps",
|
||||
".ppsm",
|
||||
".ppsx",
|
||||
".ppt",
|
||||
".pptm",
|
||||
".pptx",
|
||||
".pr1",
|
||||
".pr2",
|
||||
".prism",
|
||||
".prj",
|
||||
".properties",
|
||||
".proto",
|
||||
".prproj",
|
||||
".psd",
|
||||
".pub",
|
||||
".puw",
|
||||
".py",
|
||||
".pyc",
|
||||
".pzfx",
|
||||
".qgr",
|
||||
".qgs",
|
||||
".qgz",
|
||||
".qix",
|
||||
".qmd",
|
||||
".qml",
|
||||
".qpj",
|
||||
".qpt",
|
||||
".r",
|
||||
".rar",
|
||||
".rdata",
|
||||
".rds",
|
||||
".rep",
|
||||
".ris",
|
||||
".rmd",
|
||||
".rtf",
|
||||
".rvt",
|
||||
".sb2",
|
||||
".sb3",
|
||||
".sbn",
|
||||
".sbx",
|
||||
".scwsp",
|
||||
".sh",
|
||||
".shp",
|
||||
".shx",
|
||||
".sig",
|
||||
".skp",
|
||||
".sldasm",
|
||||
".slddrw",
|
||||
".slk",
|
||||
".sphd",
|
||||
".sphx",
|
||||
".sql",
|
||||
".srt",
|
||||
".ssp",
|
||||
".stc",
|
||||
".std",
|
||||
".stl",
|
||||
".stp",
|
||||
".stw",
|
||||
".svg",
|
||||
".swu",
|
||||
".sxc",
|
||||
".sxd",
|
||||
".sxg",
|
||||
".sxi",
|
||||
".sxm",
|
||||
".sxw",
|
||||
".tab",
|
||||
".tar",
|
||||
".tex",
|
||||
".text",
|
||||
".textgrid",
|
||||
".tfw",
|
||||
".tif",
|
||||
".tiff",
|
||||
".tsv",
|
||||
".ttc",
|
||||
".ttf",
|
||||
".twbx",
|
||||
".txt",
|
||||
".vcf",
|
||||
".vsd",
|
||||
".vsdx",
|
||||
".vtt",
|
||||
".vuw",
|
||||
".wa1",
|
||||
".wav",
|
||||
".weba",
|
||||
".webm",
|
||||
".webp",
|
||||
".wfl",
|
||||
".woff",
|
||||
".woff2",
|
||||
".wsp",
|
||||
".xcf",
|
||||
".xd",
|
||||
".xhl",
|
||||
".xls",
|
||||
".xlsb",
|
||||
".xlsm",
|
||||
".xlsx",
|
||||
".xlt",
|
||||
".xltm",
|
||||
".xltx",
|
||||
".xmind",
|
||||
".xml",
|
||||
".xps",
|
||||
".xsd",
|
||||
".xz",
|
||||
".yml",
|
||||
".zed",
|
||||
".zip",
|
||||
],
|
||||
environ_name="FILE_EXTENSIONS_ALLOWED",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
ITEM_PREVIEWABLE_MIME_TYPES = values.ListValue(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user