From c5c3bf29b815b0508bd71aada1a365f34a198935 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 11 Jun 2023 18:11:25 +0200 Subject: [PATCH 1/6] Bump to version 2.2.5 (#1354) * Bump to version 2.2.5 * Update pre-commit: isort version --- .pre-commit-config.yaml | 2 +- CHANGELOG.rst | 9 +++++++++ filer/__init__.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd8a1884c..123a42bb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,6 +37,6 @@ repos: - id: mixed-line-ending - repo: https://github.com/pycqa/isort - rev: 5.11.4 + rev: 5.12.0 hooks: - id: isort diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f9538773b..0b9d61d75 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,15 @@ CHANGELOG ========= +2.2.5 (2023-06-11) +================== + +* Security patch (https://github.com/django-cms/django-filer/pull/1352): + While admin options shown correctly represented the user rights, some admin + end-points were available directly. A staff user without any permissions + could browse the filer folder structure, list files in a folder, add files, + and move files and folders. + 2.2.4 (2023-01-13) ================== * Add Django 4.1 support diff --git a/filer/__init__.py b/filer/__init__.py index 91aaaee35..85c5ac9ff 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -13,6 +13,6 @@ 8. Publish the release and it will automatically release to pypi """ -__version__ = '2.2.4' +__version__ = '2.2.5' default_app_config = 'filer.apps.FilerConfig' From 256fa145fad3efe04a585ac149f8c4b885c91047 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Mon, 3 Jul 2023 11:00:01 +0200 Subject: [PATCH 2/6] feat: Version 2.2.6 - Support for Pillow 10 (#1374) * Bump to version 2.2.5 * Update pre-commit: isort version * Add Pillow 10 support * Add Pillow 10 support, bump to 2.2.6 --- CHANGELOG.rst | 6 ++++++ filer/__init__.py | 2 +- filer/thumbnail_processors.py | 9 +++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0b9d61d75..4f3da7048 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,12 @@ CHANGELOG ========= +2.2.6 (2023-07-03) +================== + +* Add support for Pillow 10 + + 2.2.5 (2023-06-11) ================== diff --git a/filer/__init__.py b/filer/__init__.py index 85c5ac9ff..0401c529e 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -13,6 +13,6 @@ 8. Publish the release and it will automatically release to pypi """ -__version__ = '2.2.5' +__version__ = '2.2.6' default_app_config = 'filer.apps.FilerConfig' diff --git a/filer/thumbnail_processors.py b/filer/thumbnail_processors.py index 17c339933..053e92a44 100644 --- a/filer/thumbnail_processors.py +++ b/filer/thumbnail_processors.py @@ -75,8 +75,13 @@ def scale_and_crop_with_subject_location(im, size, subject_location=False, scale *= (100 + int(zoom)) / 100.0 if scale < 1.0 or (scale > 1.0 and upscale): - im = im.resize((int(source_x * scale), int(source_y * scale)), - resample=Image.ANTIALIAS) + try: + im = im.resize((int(source_x * scale), int(source_y * scale)), + resample=Image.LANCZOS) + except AttributeError: # pragma: no cover + im = im.resize((int(source_x * scale), int(source_y * scale)), + resample=Image.ANTIALIAS) + # --endsnip-- begin real code # =============================== From 4dc26f44ddac093942a8aebd3a9dcf8f77daa5e0 Mon Sep 17 00:00:00 2001 From: Josh Yu Date: Tue, 16 Apr 2024 04:00:51 +0800 Subject: [PATCH 3/6] Feat: add Django 42 Support (#1459) * feat: Add Django 4.2 support * update * update tox.ini * update test requirements * fix: flake8 and isort errors * update setup.py * doc: update readme.rst file --------- Co-authored-by: Josh Yu --- .github/workflows/test.yml | 5 +- CHANGELOG.rst | 5 ++ README.rst | 3 + aldryn_config.py | 7 ++- filer/__init__.py | 2 - filer/admin/clipboardadmin.py | 34 +++++------ filer/admin/folderadmin.py | 60 +++++++++---------- filer/utils/files.py | 2 +- setup.py | 1 + tests/requirements/base.txt | 1 + tests/requirements/django-4.2.txt | 8 +++ .../migrations/0004_alter_image_file_ptr.py | 20 +++++++ tests/utils/test_app/admin.py | 4 +- tests/utils/urls.py | 6 +- tox.ini | 9 ++- 15 files changed, 108 insertions(+), 59 deletions(-) create mode 100644 tests/requirements/django-4.2.txt create mode 100644 tests/utils/custom_image/migrations/0004_alter_image_file_ptr.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 022670216..30cb3b650 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,13 +15,16 @@ jobs: django-3.1.txt, django-3.2.txt, django-4.0.txt, - django-4.1.txt + django-4.1.txt, + django-4.2.txt, ] exclude: - python-version: 3.7 requirements-file: django-4.0.txt - python-version: 3.7 requirements-file: django-4.1.txt + - python-version: 3.7 + requirements-file: django-4.2.txt - python-version: 3.9 requirements-file: django-2.2.txt - python-version: 3.10 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4f3da7048..9d64545b8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ CHANGELOG ========= +Unreleased +========== +* Add Django 4.2 support +* fix tests + 2.2.6 (2023-07-03) ================== diff --git a/README.rst b/README.rst index 8e22ed370..7b495328d 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,9 @@ Django Filer **django Filer** is a file management application for django that makes handling of files and images a breeze. +.. warning:: + django-filer 2.x is susceptible to SVG XSS attacks and we strongly recommend upgrading it to 3.x. + .. note:: This project is endorsed by the `django CMS Association `_. diff --git a/aldryn_config.py b/aldryn_config.py index 145fe1ded..3572d4ec2 100644 --- a/aldryn_config.py +++ b/aldryn_config.py @@ -47,7 +47,12 @@ def to_settings(self, data, settings): # If the DEFAULT_FILE_STORAGE has been set to a value known by # aldryn-django, then use that as THUMBNAIL_DEFAULT_STORAGE as well. for storage_backend in storage.SCHEMES.values(): - if storage_backend == settings['DEFAULT_FILE_STORAGE']: + # Process before django 4.2 + if storage_backend == settings.get('DEFAULT_FILE_STORAGE', None): + settings['THUMBNAIL_DEFAULT_STORAGE'] = storage_backend + break + # Process django 4.2 and after + if storage_backend == settings.get('STORAGES', {}).get('default', {}).get('BACKEND', None): settings['THUMBNAIL_DEFAULT_STORAGE'] = storage_backend break return settings diff --git a/filer/__init__.py b/filer/__init__.py index 0401c529e..ed363ac3b 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -14,5 +14,3 @@ """ __version__ = '2.2.6' - -default_app_config = 'filer.apps.FilerConfig' diff --git a/filer/admin/clipboardadmin.py b/filer/admin/clipboardadmin.py index 3a727a4a5..9c2d23bb3 100644 --- a/filer/admin/clipboardadmin.py +++ b/filer/admin/clipboardadmin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.forms.models import modelform_factory from django.http import JsonResponse -from django.urls import re_path +from django.urls import path from django.views.decorators.csrf import csrf_exempt from .. import settings as filer_settings @@ -35,21 +35,21 @@ class ClipboardAdmin(admin.ModelAdmin): def get_urls(self): return [ - re_path(r'^operations/paste_clipboard_to_folder/$', - self.admin_site.admin_view(views.paste_clipboard_to_folder), - name='filer-paste_clipboard_to_folder'), - re_path(r'^operations/discard_clipboard/$', - self.admin_site.admin_view(views.discard_clipboard), - name='filer-discard_clipboard'), - re_path(r'^operations/delete_clipboard/$', - self.admin_site.admin_view(views.delete_clipboard), - name='filer-delete_clipboard'), - re_path(r'^operations/upload/(?P[0-9]+)/$', - ajax_upload, - name='filer-ajax_upload'), - re_path(r'^operations/upload/no_folder/$', - ajax_upload, - name='filer-ajax_upload'), + path('operations/paste_clipboard_to_folder/', + self.admin_site.admin_view(views.paste_clipboard_to_folder), + name='filer-paste_clipboard_to_folder'), + path('operations/discard_clipboard/', + self.admin_site.admin_view(views.discard_clipboard), + name='filer-discard_clipboard'), + path('operations/delete_clipboard/', + self.admin_site.admin_view(views.delete_clipboard), + name='filer-delete_clipboard'), + path('operations/upload//', + ajax_upload, + name='filer-ajax_upload'), + path('operations/upload/no_folder/', + ajax_upload, + name='filer-ajax_upload'), ] + super().get_urls() def get_model_perms(self, *args, **kwargs): @@ -123,7 +123,7 @@ def ajax_upload(request, folder_id=None): 'file_id': file_obj.pk, } # prepare preview thumbnail - if type(file_obj) == Image: + if isinstance(file_obj, Image): thumbnail_180_options = { 'size': (180, 180), 'crop': True, diff --git a/filer/admin/folderadmin.py b/filer/admin/folderadmin.py index 5e93cc62b..08dfba06a 100644 --- a/filer/admin/folderadmin.py +++ b/filer/admin/folderadmin.py @@ -15,7 +15,7 @@ from django.db import models, router from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render -from django.urls import re_path, reverse +from django.urls import path, reverse from django.utils.encoding import force_str from django.utils.html import escape, format_html from django.utils.safestring import mark_safe @@ -200,35 +200,35 @@ def get_urls(self): return [ # we override the default list view with our own directory listing # of the root directories - re_path(r'^$', - self.admin_site.admin_view(self.directory_listing), - name='filer-directory_listing-root'), - - re_path(r'^last/$', - self.admin_site.admin_view(self.directory_listing), - {'viewtype': 'last'}, - name='filer-directory_listing-last'), - - re_path(r'^(?P\d+)/list/$', - self.admin_site.admin_view(self.directory_listing), - name='filer-directory_listing'), - - re_path(r'^(?P\d+)/make_folder/$', - self.admin_site.admin_view(views.make_folder), - name='filer-directory_listing-make_folder'), - re_path(r'^make_folder/$', - self.admin_site.admin_view(views.make_folder), - name='filer-directory_listing-make_root_folder'), - - re_path(r'^images_with_missing_data/$', - self.admin_site.admin_view(self.directory_listing), - {'viewtype': 'images_with_missing_data'}, - name='filer-directory_listing-images_with_missing_data'), - - re_path(r'^unfiled_images/$', - self.admin_site.admin_view(self.directory_listing), - {'viewtype': 'unfiled_images'}, - name='filer-directory_listing-unfiled_images'), + path('', + self.admin_site.admin_view(self.directory_listing), + name='filer-directory_listing-root'), + + path('last/', + self.admin_site.admin_view(self.directory_listing), + {'viewtype': 'last'}, + name='filer-directory_listing-last'), + + path('/list/', + self.admin_site.admin_view(self.directory_listing), + name='filer-directory_listing'), + + path('/make_folder/', + self.admin_site.admin_view(views.make_folder), + name='filer-directory_listing-make_folder'), + path('make_folder/', + self.admin_site.admin_view(views.make_folder), + name='filer-directory_listing-make_root_folder'), + + path('images_with_missing_data/', + self.admin_site.admin_view(self.directory_listing), + {'viewtype': 'images_with_missing_data'}, + name='filer-directory_listing-images_with_missing_data'), + + path('unfiled_images/', + self.admin_site.admin_view(self.directory_listing), + {'viewtype': 'unfiled_images'}, + name='filer-directory_listing-unfiled_images'), ] + super().get_urls() # custom views diff --git a/filer/utils/files.py b/filer/utils/files.py index 8bbffbf3d..fe360d7f1 100644 --- a/filer/utils/files.py +++ b/filer/utils/files.py @@ -22,7 +22,7 @@ def handle_upload(request): filename = request.GET.get('qqfile', False) or request.GET.get('filename', False) or '' try: - content_length = int(request.META['CONTENT_LENGTH']) + content_length = int(request.headers['content-length']) except (IndexError, TypeError, ValueError): content_length = None diff --git a/setup.py b/setup.py index 2601510d8..0fee49cd2 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ 'Framework :: Django :: 3.2', 'Framework :: Django :: 4.0', 'Framework :: Django :: 4.1', + 'Framework :: Django :: 4.2', 'Framework :: Django CMS', 'Framework :: Django CMS :: 3.6', 'Framework :: Django CMS :: 3.7', diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt index 9a0be2f19..2ff68a7dc 100644 --- a/tests/requirements/base.txt +++ b/tests/requirements/base.txt @@ -6,3 +6,4 @@ django-mptt coverage isort flake8 +packaging \ No newline at end of file diff --git a/tests/requirements/django-4.2.txt b/tests/requirements/django-4.2.txt new file mode 100644 index 000000000..192e81265 --- /dev/null +++ b/tests/requirements/django-4.2.txt @@ -0,0 +1,8 @@ +-r base.txt + +django-app-helper>=3.3.1 + +django>=4.2,<5 +django_polymorphic>=3.1 +django-app-helper +#https://github.com/jrief/django-app-helper/archive/refs/heads/develop.zip \ No newline at end of file diff --git a/tests/utils/custom_image/migrations/0004_alter_image_file_ptr.py b/tests/utils/custom_image/migrations/0004_alter_image_file_ptr.py new file mode 100644 index 000000000..eca5e335a --- /dev/null +++ b/tests/utils/custom_image/migrations/0004_alter_image_file_ptr.py @@ -0,0 +1,20 @@ +# Generated by Django 4.1.9 on 2023-06-13 20:22 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('filer', '0015_alter_file_owner_alter_file_polymorphic_ctype_and_more'), + ('custom_image', '0003_auto_20180414_2059'), + ] + + operations = [ + migrations.AlterField( + model_name='image', + name='file_ptr', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='%(app_label)s_%(class)s_file', serialize=False, to='filer.file'), + ), + ] diff --git a/tests/utils/test_app/admin.py b/tests/utils/test_app/admin.py index f1c73ed51..2b6b6b44d 100644 --- a/tests/utils/test_app/admin.py +++ b/tests/utils/test_app/admin.py @@ -3,8 +3,6 @@ from .models import MyModel +@admin.register(MyModel) class MyModelAdmin(admin.ModelAdmin): model = MyModel - - -admin.site.register(MyModel, MyModelAdmin) diff --git a/tests/utils/urls.py b/tests/utils/urls.py index 882eca68d..888e0b671 100644 --- a/tests/utils/urls.py +++ b/tests/utils/urls.py @@ -1,6 +1,6 @@ from django.conf import settings from django.contrib import admin -from django.urls import include, re_path +from django.urls import include, path, re_path from django.views.static import serve @@ -11,6 +11,6 @@ re_path(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), re_path(r'^admin/', admin_urls), - re_path(r'^', include('filer.server.urls')), - re_path(r'^filer/', include('filer.urls')), + path('', include('filer.server.urls')), + path('filer/', include('filer.urls')), ] diff --git a/tox.ini b/tox.ini index 27f4bc2ff..1d868b739 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ envlist = py{36,37,38,39}-dj{30,31}-noswap py{36,37,38,39,310}-dj32-swap py{36,37,38,39,310}-dj32-noswap + py{38,39,310,311}-dj{40,41,42}-{swap,noswap} [gh-actions] python = @@ -18,20 +19,26 @@ python = 3.8: py38 3.9: py39 3.10: py310 + 3.11: py311 skip_missing_interpreters=True [testenv] deps = - dj22: -r tests/requirements/django-2.2.txt +dj22: -r tests/requirements/django-2.2.txt dj30: -r tests/requirements/django-3.0.txt dj31: -r tests/requirements/django-3.1.txt dj32: -r tests/requirements/django-3.2.txt + dj40: -r tests/requirements/django-4.0.txt + dj41: -r tests/requirements/django-4.1.txt + dj42: -r tests/requirements/django-4.2.txt commands = {envpython} --version {env:COMMAND:coverage} erase {env:COMMAND:coverage} run setup.py test {env:COMMAND:coverage} report +allowlist_externals = + {env:COMMAND:coverage} setenv = swap: CUSTOM_IMAGE=custom_image.Image From 5440e47808acba3c3d370d2df9f0c245fe54edb4 Mon Sep 17 00:00:00 2001 From: Josh Yu Date: Thu, 16 May 2024 18:17:07 +0800 Subject: [PATCH 4/6] Release 2.2.7 (#1460) * Bump version to 2.2.7 * format * format * revert format * Update .pre-commit-config.yaml * update release date --------- Co-authored-by: Josh Yu Co-authored-by: Fabian Braun --- .pre-commit-config.yaml | 2 +- CHANGELOG.rst | 3 +++ filer/__init__.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 123a42bb4..fcce4d855 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: # args: [--target-version, "2.2"] - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 7.0.0 hooks: - id: flake8 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9d64545b8..0f99f0d4c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ CHANGELOG Unreleased ========== + +2.2.7 (2024-05-16) +================== * Add Django 4.2 support * fix tests diff --git a/filer/__init__.py b/filer/__init__.py index ed363ac3b..758a70803 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -13,4 +13,4 @@ 8. Publish the release and it will automatically release to pypi """ -__version__ = '2.2.6' +__version__ = '2.2.7' From 330d1dacb0b5dde0cf3aa77334e0d1671bb7bacf Mon Sep 17 00:00:00 2001 From: Frankie Dintino Date: Tue, 10 Jun 2025 16:17:21 -0400 Subject: [PATCH 5/6] fix(2.2.x backport): respect upload and directory listing permissions (#1527) * fix: respect upload and directory listing permissions (#1352) * fix: respect `can_use_directory_listing`, `change_folder`, `add_folder`, `add_file` permissions * Update tests * fix flake8 error * Close files in tests * Add test for has_... permissions of File and Folder class * Remove unused variables from tests * Remove unnecessary noqa * Run isort * ci: use ubuntu-latest runner in gha actions * update dependencies and tox matrix to something workable --------- Co-authored-by: Fabian Braun --- .github/workflows/frontend.yml | 2 +- .github/workflows/test.yml | 12 +- .readthedocs.yaml | 2 +- filer/admin/clipboardadmin.py | 15 +- filer/admin/folderadmin.py | 2 + filer/models/filemodels.py | 4 +- filer/models/foldermodels.py | 4 +- tests/requirements/django-2.2.txt | 1 + tests/requirements/django-3.0.txt | 1 + tests/requirements/django-3.1.txt | 1 + tests/requirements/django-3.2.txt | 1 + tests/requirements/django-4.0.txt | 1 + tests/requirements/django-4.1.txt | 1 + tests/test_admin.py | 370 +++++++++++++++++++----------- tests/test_dump.py | 18 +- tests/test_permissions.py | 5 +- tests/test_tools.py | 1 + tox.ini | 12 +- 18 files changed, 281 insertions(+), 172 deletions(-) diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 4ec1563fc..1b7b6dba3 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -4,7 +4,7 @@ on: [push] jobs: gulp: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: node-version: [14.15.x] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30cb3b650..fcebed035 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] + python-version: [ '3.8', '3.9', '3.10', '3.11' ] requirements-file: [ django-2.2.txt, django-3.0.txt, @@ -19,12 +19,6 @@ jobs: django-4.2.txt, ] exclude: - - python-version: 3.7 - requirements-file: django-4.0.txt - - python-version: 3.7 - requirements-file: django-4.1.txt - - python-version: 3.7 - requirements-file: django-4.2.txt - python-version: 3.9 requirements-file: django-2.2.txt - python-version: 3.10 @@ -40,7 +34,7 @@ jobs: - python-version: 3.11 requirements-file: django-3.1.txt os: [ - ubuntu-20.04, + ubuntu-latest, ] steps: @@ -50,7 +44,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: library prerequisites - run: sudo apt-get install python-dev libpq-dev libmagic1 gcc libxml2-dev libxslt1-dev libjpeg62 libopenjp2-7 -y + run: sudo apt-get install python-dev-is-python3 libpq-dev libmagic1 gcc libxml2-dev libxslt1-dev libjpeg62 libopenjp2-7 -y - name: Install extra dependencies run: pip install lxml if: matrix.python-version == '3.10' diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e6f30f627..1db2e5343 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -7,7 +7,7 @@ version: 2 # Set the version of Python and other tools you might need build: - os: ubuntu-20.04 + os: ubuntu-latest tools: python: "3.9" # You can also specify other tool versions: diff --git a/filer/admin/clipboardadmin.py b/filer/admin/clipboardadmin.py index 9c2d23bb3..6572c1434 100644 --- a/filer/admin/clipboardadmin.py +++ b/filer/admin/clipboardadmin.py @@ -1,7 +1,8 @@ -from django.contrib import admin +from django.contrib import admin, messages from django.forms.models import modelform_factory from django.http import JsonResponse from django.urls import path +from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from .. import settings as filer_settings @@ -11,8 +12,9 @@ from . import views -NO_FOLDER_ERROR = "Can't find folder to upload. Please refresh and try again" -NO_PERMISSIONS_FOR_FOLDER = ( +NO_PERMISSIONS = _("You do not have permission to upload files.") +NO_FOLDER_ERROR = _("Can't find folder to upload. Please refresh and try again") +NO_PERMISSIONS_FOR_FOLDER = _( "Can't use this folder, Permission Denied. Please select another folder." ) @@ -68,17 +70,24 @@ def ajax_upload(request, folder_id=None): """ Receives an upload from the uploader. Receives only one file at a time. """ + + if not request.user.has_perm("filer.add_file"): + messages.error(request, NO_PERMISSIONS) + return JsonResponse({'error': NO_PERMISSIONS}) + if folder_id: try: # Get folder folder = Folder.objects.get(pk=folder_id) except Folder.DoesNotExist: + messages.error(request, NO_FOLDER_ERROR) return JsonResponse({'error': NO_FOLDER_ERROR}) else: folder = Folder.objects.filter(pk=request.session.get('filer_last_folder_id', 0)).first() # check permissions if folder and not folder.has_add_children_permission(request): + messages.error(request, NO_PERMISSIONS_FOR_FOLDER) return JsonResponse({'error': NO_PERMISSIONS_FOR_FOLDER}) if len(request.FILES) == 1: diff --git a/filer/admin/folderadmin.py b/filer/admin/folderadmin.py index 08dfba06a..2d80574ed 100644 --- a/filer/admin/folderadmin.py +++ b/filer/admin/folderadmin.py @@ -233,6 +233,8 @@ def get_urls(self): # custom views def directory_listing(self, request, folder_id=None, viewtype=None): + if not request.user.has_perm("filer.can_use_directory_listing"): + raise PermissionDenied() clipboard = tools.get_user_clipboard(request.user) if viewtype == 'images_with_missing_data': folder = ImagesWithMissingData() diff --git a/filer/models/filemodels.py b/filer/models/filemodels.py index 61f5c7516..f188c707f 100644 --- a/filer/models/filemodels.py +++ b/filer/models/filemodels.py @@ -300,13 +300,13 @@ def __lt__(self, other): return self.label.lower() < other.label.lower() def has_edit_permission(self, request): - return self.has_generic_permission(request, 'edit') + return request.user.has_perm("filer.change_file") and self.has_generic_permission(request, 'edit') def has_read_permission(self, request): return self.has_generic_permission(request, 'read') def has_add_children_permission(self, request): - return self.has_generic_permission(request, 'add_children') + return request.user.has_perm("filer.add_file") and self.has_generic_permission(request, 'add_children') def has_generic_permission(self, request, permission_type): """ diff --git a/filer/models/foldermodels.py b/filer/models/foldermodels.py index d89a69b62..904b23498 100644 --- a/filer/models/foldermodels.py +++ b/filer/models/foldermodels.py @@ -200,13 +200,13 @@ def quoted_logical_path(self): return urlquote(self.pretty_logical_path) def has_edit_permission(self, request): - return self.has_generic_permission(request, 'edit') + return request.user.has_perm("filer.change_folder") and self.has_generic_permission(request, 'edit') def has_read_permission(self, request): return self.has_generic_permission(request, 'read') def has_add_children_permission(self, request): - return self.has_generic_permission(request, 'add_children') + return request.user.has_perm("filer.change_folder") and self.has_generic_permission(request, 'add_children') def has_generic_permission(self, request, permission_type): """ diff --git a/tests/requirements/django-2.2.txt b/tests/requirements/django-2.2.txt index b11817dee..67429a151 100644 --- a/tests/requirements/django-2.2.txt +++ b/tests/requirements/django-2.2.txt @@ -3,3 +3,4 @@ django>=2.2,<3.0 django_polymorphic>=2.0,<2.1 django-app-helper +easy-thumbnails[svg]<2.10 diff --git a/tests/requirements/django-3.0.txt b/tests/requirements/django-3.0.txt index 7acd7b8cd..9888afb33 100644 --- a/tests/requirements/django-3.0.txt +++ b/tests/requirements/django-3.0.txt @@ -3,3 +3,4 @@ django>=3.0,<3.1 django_polymorphic>=2.1,<2.2 django-app-helper +easy-thumbnails[svg]<2.10 diff --git a/tests/requirements/django-3.1.txt b/tests/requirements/django-3.1.txt index a02465ea6..6ccb0621d 100644 --- a/tests/requirements/django-3.1.txt +++ b/tests/requirements/django-3.1.txt @@ -3,3 +3,4 @@ django>=3.1,<3.2 django_polymorphic>=2,<3.1 django-app-helper +easy-thumbnails[svg]<2.10 diff --git a/tests/requirements/django-3.2.txt b/tests/requirements/django-3.2.txt index 52ee4482d..e51c93320 100644 --- a/tests/requirements/django-3.2.txt +++ b/tests/requirements/django-3.2.txt @@ -3,3 +3,4 @@ django>=3.2,<4 django_polymorphic>=2,<3.1 django-app-helper +easy-thumbnails[svg]<2.10 diff --git a/tests/requirements/django-4.0.txt b/tests/requirements/django-4.0.txt index 1a782168a..e942141dd 100644 --- a/tests/requirements/django-4.0.txt +++ b/tests/requirements/django-4.0.txt @@ -3,3 +3,4 @@ django>=4.0,<4.1 django_polymorphic>=3.1 https://github.com/jrief/django-app-helper/archive/refs/heads/develop.zip +easy-thumbnails[svg]<2.10 diff --git a/tests/requirements/django-4.1.txt b/tests/requirements/django-4.1.txt index e47bf45d9..33eea6114 100644 --- a/tests/requirements/django-4.1.txt +++ b/tests/requirements/django-4.1.txt @@ -3,3 +3,4 @@ django>=4.1,<4.2 django_polymorphic>=3.1 https://github.com/jrief/django-app-helper/archive/refs/heads/develop.zip +easy-thumbnails[svg]<2.10 diff --git a/tests/test_admin.py b/tests/test_admin.py index 00f601b9f..29aeb5030 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -6,7 +6,9 @@ from django.contrib import admin from django.contrib.admin import helpers from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission from django.forms.models import model_to_dict as model_to_dict_django +from django.http import HttpRequest, HttpResponseForbidden from django.test import TestCase from django.urls import reverse @@ -223,14 +225,16 @@ def tearDown(self): def test_filer_upload_file(self, extra_headers={}): self.assertEqual(Image.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.filename, 'rb')) - url = reverse('admin:filer-ajax_upload', kwargs={'folder_id': folder.pk}) - post_data = { - 'Filename': self.image_name, - 'Filedata': file_obj, - 'jsessionid': self.client.session.session_key - } - response = self.client.post(url, post_data, **extra_headers) # noqa + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse('admin:filer-ajax_upload', kwargs={'folder_id': folder.pk}) + post_data = { + 'Filename': self.image_name, + 'Filedata': file_obj, + 'jsessionid': self.client.session.session_key + } + self.client.post(url, post_data, **extra_headers) + self.assertEqual(Image.objects.count(), 1) self.assertEqual(Image.objects.all()[0].original_filename, self.image_name) @@ -244,14 +248,16 @@ def test_filer_upload_video(self, extra_headers={}): )): self.assertEqual(Video.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.video_filename, 'rb')) - url = reverse('admin:filer-ajax_upload', kwargs={'folder_id': folder.pk}) - post_data = { - 'Filename': self.video_name, - 'Filedata': file_obj, - 'jsessionid': self.client.session.session_key - } - response = self.client.post(url, post_data, **extra_headers) # noqa + with open(self.video_filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse('admin:filer-ajax_upload', kwargs={'folder_id': folder.pk}) + post_data = { + 'Filename': self.video_name, + 'Filedata': file_obj, + 'jsessionid': self.client.session.session_key + } + self.client.post(url, post_data, **extra_headers) + self.assertEqual(Video.objects.count(), 1) self.assertEqual(Video.objects.all()[0].original_filename, self.video_name) @@ -264,62 +270,67 @@ def test_filer_upload_extimage(self, extra_headers={}): )): self.assertEqual(ExtImage.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.filename, 'rb')) - url = reverse('admin:filer-ajax_upload', kwargs={'folder_id': folder.pk}) + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse('admin:filer-ajax_upload', kwargs={'folder_id': folder.pk}) + post_data = { + 'Filename': self.image_name, + 'Filedata': file_obj, + 'jsessionid': self.client.session.session_key + } + self.client.post(url, post_data, **extra_headers) + + self.assertEqual(ExtImage.objects.count(), 1) + self.assertEqual(ExtImage.objects.all()[0].original_filename, self.image_name) + + def test_filer_upload_file_no_folder(self, extra_headers={}): + self.assertEqual(Image.objects.count(), 0) + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse('admin:filer-ajax_upload') post_data = { 'Filename': self.image_name, 'Filedata': file_obj, 'jsessionid': self.client.session.session_key } response = self.client.post(url, post_data, **extra_headers) # noqa - self.assertEqual(ExtImage.objects.count(), 1) - self.assertEqual(ExtImage.objects.all()[0].original_filename, self.image_name) - - def test_filer_upload_file_no_folder(self, extra_headers={}): - self.assertEqual(Image.objects.count(), 0) - file_obj = django.core.files.File(open(self.filename, 'rb')) - url = reverse('admin:filer-ajax_upload') - post_data = { - 'Filename': self.image_name, - 'Filedata': file_obj, - 'jsessionid': self.client.session.session_key - } - response = self.client.post(url, post_data, **extra_headers) # noqa - self.assertEqual(Image.objects.count(), 1) - stored_image = Image.objects.first() - self.assertEqual(stored_image.original_filename, self.image_name) - self.assertEqual(stored_image.mime_type, 'image/jpeg') + self.assertEqual(Image.objects.count(), 1) + stored_image = Image.objects.first() + self.assertEqual(stored_image.original_filename, self.image_name) + self.assertEqual(stored_image.mime_type, 'image/jpeg') def test_filer_upload_binary_data(self, extra_headers={}): self.assertEqual(File.objects.count(), 0) - file_obj = django.core.files.File(open(self.binary_filename, 'rb')) - url = reverse('admin:filer-ajax_upload') - post_data = { - 'Filename': self.binary_name, - 'Filedata': file_obj, - 'jsessionid': self.client.session.session_key - } - response = self.client.post(url, post_data, **extra_headers) # noqa - self.assertEqual(Image.objects.count(), 0) - self.assertEqual(File.objects.count(), 1) - stored_file = File.objects.first() - self.assertEqual(stored_file.original_filename, self.binary_name) - self.assertEqual(stored_file.mime_type, 'application/octet-stream') + with open(self.binary_filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse('admin:filer-ajax_upload') + post_data = { + 'Filename': self.binary_name, + 'Filedata': file_obj, + 'jsessionid': self.client.session.session_key + } + self.client.post(url, post_data, **extra_headers) + self.assertEqual(Image.objects.count(), 0) + self.assertEqual(File.objects.count(), 1) + stored_file = File.objects.first() + self.assertEqual(stored_file.original_filename, self.binary_name) + self.assertEqual(stored_file.mime_type, 'application/octet-stream') def test_filer_ajax_upload_file(self): self.assertEqual(Image.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.filename, 'rb')) - url = reverse( - 'admin:filer-ajax_upload', - kwargs={'folder_id': folder.pk} - ) + '?filename=%s' % self.image_name - response = self.client.post( # noqa - url, - data=file_obj.read(), - content_type='image/jpeg', - **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} - ) + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse( + 'admin:filer-ajax_upload', + kwargs={'folder_id': folder.pk} + ) + '?filename=%s' % self.image_name + response = self.client.post( # noqa + url, + data=file_obj.read(), + content_type='image/jpeg', + **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + ) self.assertEqual(Image.objects.count(), 1) stored_image = Image.objects.first() self.assertEqual(stored_image.original_filename, self.image_name) @@ -328,17 +339,18 @@ def test_filer_ajax_upload_file(self): def test_filer_ajax_upload_file_using_content_type(self): self.assertEqual(Image.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.binary_filename, 'rb')) - url = reverse( - 'admin:filer-ajax_upload', - kwargs={'folder_id': folder.pk} - ) + '?filename=renamed.pdf' - response = self.client.post( # noqa - url, - data=file_obj.read(), - content_type='application/pdf', - **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} - ) + with open(self.binary_filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse( + 'admin:filer-ajax_upload', + kwargs={'folder_id': folder.pk} + ) + '?filename=renamed.pdf' + self.client.post( + url, + data=file_obj.read(), + content_type='application/pdf', + **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + ) self.assertEqual(Image.objects.count(), 0) self.assertEqual(File.objects.count(), 1) stored_file = File.objects.first() @@ -347,16 +359,17 @@ def test_filer_ajax_upload_file_using_content_type(self): def test_filer_ajax_upload_file_no_folder(self): self.assertEqual(Image.objects.count(), 0) - file_obj = django.core.files.File(open(self.filename, 'rb')) - url = reverse( - 'admin:filer-ajax_upload' - ) + '?filename=%s' % self.image_name - response = self.client.post( # noqa - url, - data=file_obj.read(), - content_type='image/jpeg', - **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} - ) + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse( + 'admin:filer-ajax_upload' + ) + '?filename=%s' % self.image_name + self.client.post( + url, + data=file_obj.read(), + content_type='image/jpeg', + **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + ) self.assertEqual(Image.objects.count(), 1) stored_image = Image.objects.first() self.assertEqual(stored_image.original_filename, self.image_name) @@ -365,15 +378,16 @@ def test_filer_ajax_upload_file_no_folder(self): def test_filer_upload_file_error(self, extra_headers={}): self.assertEqual(Image.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.filename, 'rb')) - url = reverse('admin:filer-ajax_upload', - kwargs={'folder_id': folder.pk + 1}) - post_data = { - 'Filename': self.image_name, - 'Filedata': file_obj, - 'jsessionid': self.client.session.session_key - } - response = self.client.post(url, post_data, **extra_headers) + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse('admin:filer-ajax_upload', + kwargs={'folder_id': folder.pk + 1}) + post_data = { + 'Filename': self.image_name, + 'Filedata': file_obj, + 'jsessionid': self.client.session.session_key + } + response = self.client.post(url, post_data, **extra_headers) from filer.admin.clipboardadmin import NO_FOLDER_ERROR self.assertContains(response, NO_FOLDER_ERROR) self.assertEqual(Image.objects.count(), 0) @@ -381,18 +395,19 @@ def test_filer_upload_file_error(self, extra_headers={}): def test_filer_ajax_upload_file_error(self): self.assertEqual(Image.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.filename, 'rb')) - url = reverse( - 'admin:filer-ajax_upload', - kwargs={ - 'folder_id': folder.pk + 1} - ) + '?filename={0}'.format(self.image_name) - response = self.client.post( - url, - data=file_obj.read(), - content_type='application/octet-stream', - **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} - ) + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + url = reverse( + 'admin:filer-ajax_upload', + kwargs={ + 'folder_id': folder.pk + 1} + ) + '?filename={0}'.format(self.image_name) + response = self.client.post( + url, + data=file_obj.read(), + content_type='application/octet-stream', + **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + ) from filer.admin.clipboardadmin import NO_FOLDER_ERROR self.assertContains(response, NO_FOLDER_ERROR) self.assertEqual(Image.objects.count(), 0) @@ -403,35 +418,38 @@ def test_filer_upload_permissions_error(self, extra_headers={}): username='joe_new', password='x', email='joe@mata.com') staff_user.is_staff = True staff_user.save() + staff_user.user_permissions.add(*Permission.objects.filter(codename="add_file")) self.client.login(username='joe_new', password='x') self.assertEqual(Image.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.filename, 'rb')) - - with SettingsOverride(filer_settings, FILER_ENABLE_PERMISSIONS=True): - - # give permissions over BAR - FolderPermission.objects.create( - folder=folder, - user=staff_user, - type=FolderPermission.THIS, - can_edit=FolderPermission.DENY, - can_read=FolderPermission.ALLOW, - can_add_children=FolderPermission.DENY) - url = reverse('admin:filer-ajax_upload', - kwargs={'folder_id': folder.pk}) - post_data = { - 'Filename': self.image_name, - 'Filedata': file_obj, - 'jsessionid': self.client.session.session_key - } - response = self.client.post(url, post_data, **extra_headers) + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + + with SettingsOverride(filer_settings, FILER_ENABLE_PERMISSIONS=True): + + # give permissions over BAR + FolderPermission.objects.create( + folder=folder, + user=staff_user, + type=FolderPermission.THIS, + can_edit=FolderPermission.DENY, + can_read=FolderPermission.ALLOW, + can_add_children=FolderPermission.DENY) + url = reverse('admin:filer-ajax_upload', + kwargs={'folder_id': folder.pk}) + post_data = { + 'Filename': self.image_name, + 'Filedata': file_obj, + 'jsessionid': self.client.session.session_key + } + response = self.client.post(url, post_data, **extra_headers) from filer.admin.clipboardadmin import NO_PERMISSIONS_FOR_FOLDER self.assertContains(response, NO_PERMISSIONS_FOR_FOLDER) self.assertEqual(Image.objects.count(), 0) - def test_filer_ajax_upload_permissions_error(self, extra_headers={}): + def test_filer_ajax_upload_without_permissions_error(self, extra_headers={}): + """User without add_file permission cannot upload""" self.client.logout() staff_user = User.objects.create_user( username='joe_new', password='x', email='joe@mata.com') @@ -440,18 +458,9 @@ def test_filer_ajax_upload_permissions_error(self, extra_headers={}): self.client.login(username='joe_new', password='x') self.assertEqual(Image.objects.count(), 0) folder = Folder.objects.create(name='foo') - file_obj = django.core.files.File(open(self.filename, 'rb')) - - with SettingsOverride(filer_settings, FILER_ENABLE_PERMISSIONS=True): + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) - # give permissions over BAR - FolderPermission.objects.create( - folder=folder, - user=staff_user, - type=FolderPermission.THIS, - can_edit=FolderPermission.DENY, - can_read=FolderPermission.ALLOW, - can_add_children=FolderPermission.DENY) url = reverse( 'admin:filer-ajax_upload', kwargs={ @@ -463,6 +472,73 @@ def test_filer_ajax_upload_permissions_error(self, extra_headers={}): content_type='application/octet-stream', **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} ) + + from filer.admin.clipboardadmin import NO_PERMISSIONS + + self.assertContains(response, NO_PERMISSIONS) + self.assertEqual(Image.objects.count(), 0) + + def test_filer_add_file_permissions(self, extra_headers={}): + """Add_file permissions reflect in has_... methods of File and Folder classes""" + self.client.logout() + staff_user = User.objects.create_user( + username='joe_new', password='x', email='joe@mata.com') + staff_user.is_staff = True + staff_user.save() + self.client.login(username='joe_new', password='x') + self.assertEqual(Image.objects.count(), 0) + folder = Folder.objects.create(name='foo') + + file_data = django.core.files.base.ContentFile('some data') + file_data.name = self.filename + file = File.objects.create( + owner=self.superuser, + original_filename=self.filename, + file=file_data, + folder=folder + ) + file.save() + request = HttpRequest() + setattr(request, "user", staff_user) + + self.assertEqual(folder.has_add_children_permission(request), False) + self.assertEqual(file.has_add_children_permission(request), False) + + def test_filer_ajax_upload_permissions_error(self, extra_headers={}): + self.client.logout() + staff_user = User.objects.create_user( + username='joe_new', password='x', email='joe@mata.com') + staff_user.is_staff = True + staff_user.save() + staff_user.user_permissions.add(*Permission.objects.filter(codename="add_file")) + self.client.login(username='joe_new', password='x') + self.assertEqual(Image.objects.count(), 0) + folder = Folder.objects.create(name='foo') + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh) + + with SettingsOverride(filer_settings, FILER_ENABLE_PERMISSIONS=True): + + # give permissions over BAR + FolderPermission.objects.create( + folder=folder, + user=staff_user, + type=FolderPermission.THIS, + can_edit=FolderPermission.DENY, + can_read=FolderPermission.ALLOW, + can_add_children=FolderPermission.DENY) + url = reverse( + 'admin:filer-ajax_upload', + kwargs={ + 'folder_id': folder.pk} + ) + '?filename={0}'.format(self.image_name) + response = self.client.post( + url, + data=file_obj.read(), + content_type='application/octet-stream', + **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + ) + from filer.admin.clipboardadmin import NO_PERMISSIONS_FOR_FOLDER self.assertContains(response, NO_PERMISSIONS_FOR_FOLDER) self.assertEqual(Image.objects.count(), 0) @@ -471,9 +547,10 @@ def test_templatetag_file_icon_url(https://github.com/self): filename = os.path.join(settings.FILE_UPLOAD_TEMP_DIR, 'invalid.svg') with open(filename, 'wb') as fh: fh.write(b'') - file_obj = django.core.files.File(open(filename, 'rb'), name=filename) - image_obj = Image.objects.create(owner=self.superuser, original_filename=self.image_name, file=file_obj, mime_type='image/svg+xml') - image_obj.save() + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh, name=filename) + image_obj = Image.objects.create(owner=self.superuser, original_filename=self.image_name, file=file_obj, mime_type='image/svg+xml') + image_obj.save() url = file_icon_url(https://github.com/image_obj) self.assertEqual(url, '/static/filer/icons/file\\u002Dunknown.svg') @@ -517,9 +594,10 @@ def create_src_and_dst_folders(self): def create_image(self, folder, filename=None): filename = filename or 'test_image.jpg' - file_obj = django.core.files.File(open(self.filename, 'rb'), name=filename) - image_obj = Image.objects.create(owner=self.superuser, original_filename=self.image_name, file=file_obj, folder=folder, mime_type='image/jpeg') - image_obj.save() + with open(self.filename, 'rb') as fh: + file_obj = django.core.files.File(fh, name=filename) + image_obj = Image.objects.create(owner=self.superuser, original_filename=self.image_name, file=file_obj, folder=folder, mime_type='image/jpeg') + image_obj.save() return image_obj def create_file(self, folder, filename=None): @@ -869,6 +947,8 @@ def setUp(self): username='joe', password='x', email='joe@mata.com') self.staff_user.is_staff = True self.staff_user.save() + perms = Permission.objects.filter(codename__in=["view_folder", "add_file", "add_folder", "can_use_directory_listing"]) + self.staff_user.user_permissions.add(*perms) self.parent = Folder.objects.create(name='bar', parent=None, owner=superuser) self.foo_folder = Folder.objects.create(name='foo', parent=self.parent, owner=self.staff_user) @@ -882,6 +962,18 @@ def setUp(self): file=file_data, folder=self.parent) self.client.login(username='joe', password='x') + def test_with_without_permissions(self): + staff_user_wo_permissions = User.objects.create_user( + username='joemata', password='x', email='joe@mata.com') + staff_user_wo_permissions.is_staff = True + staff_user_wo_permissions.save() + self.client.login(username='joemata', password='x') + with SettingsOverride(filer_settings, FILER_ENABLE_PERMISSIONS=False): + response = self.client.get( + reverse('admin:filer-directory_listing', + kwargs={'folder_id': self.parent.id})) + self.assertIsInstance(response, HttpResponseForbidden) + def test_with_permissions_disabled(self): with SettingsOverride(filer_settings, FILER_ENABLE_PERMISSIONS=False): response = self.client.get( diff --git a/tests/test_dump.py b/tests/test_dump.py index 9121ef184..d8295ddfd 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -35,17 +35,19 @@ def tearDown(self): pass def create_filer_image(self, folder=None): - file_obj = DjangoFile(open(self.filename, 'rb'), name=self.image_name) - image = Image.objects.create(owner=self.superuser, - original_filename=self.image_name, - file=file_obj, folder=folder) + with open(self.filename, 'rb') as file: + file_obj = DjangoFile(file, name=self.image_name) + image = Image.objects.create(owner=self.superuser, + original_filename=self.image_name, + file=file_obj, folder=folder) return image def create_filer_file(self, folder=None): - file_obj = DjangoFile(open(self.filename, 'rb'), name=self.image_name) - fileobj = File.objects.create(owner=self.superuser, - original_filename=self.image_name, - file=file_obj, folder=folder) + with open(self.filename, 'rb') as file: + file_obj = DjangoFile(file, name=self.image_name) + fileobj = File.objects.create(owner=self.superuser, + original_filename=self.image_name, + file=file_obj, folder=folder) return fileobj def test_dump_data_base(self): diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 23e7d93bc..1dacb2a38 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -1,7 +1,7 @@ import os from django.conf import settings -from django.contrib.auth.models import Group +from django.contrib.auth.models import Group, Permission from django.core.files import File as DjangoFile from django.test.testcases import TestCase @@ -33,8 +33,11 @@ def setUp(self): self.owner = User.objects.create(username='owner') + perms = Permission.objects.filter(codename="change_folder") self.test_user1 = User.objects.create(username='test1', password='secret') self.test_user2 = User.objects.create(username='test2', password='secret') + self.test_user1.user_permissions.add(*perms) + self.test_user2.user_permissions.add(*perms) self.group1 = Group.objects.create(name='name1') self.group2 = Group.objects.create(name='name2') diff --git a/tests/test_tools.py b/tests/test_tools.py index 2b548597a..914aabd4c 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -35,6 +35,7 @@ def setUp(self): self.folder = Folder.objects.create(name='test_folder') def tearDown(self): + self.file.close() self.client.logout() os.remove(self.filename) for img in Image.objects.all(): diff --git a/tox.ini b/tox.ini index 1d868b739..159ba6974 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,12 @@ envlist = isort docs frontend - py{36,37,38}-dj{22}-swap - py{36,37,38}-dj{22}-noswap - py{36,37,38,39}-dj{30,31}-swap - py{36,37,38,39}-dj{30,31}-noswap - py{36,37,38,39,310}-dj32-swap - py{36,37,38,39,310}-dj32-noswap + py{38}-dj{22}-swap + py{38}-dj{22}-noswap + py{38,39}-dj{30,31}-swap + py{38,39}-dj{30,31}-noswap + py{38,39,310}-dj32-swap + py{38,39,310}-dj32-noswap py{38,39,310,311}-dj{40,41,42}-{swap,noswap} [gh-actions] From aad7f4e4f63173561630b899ed5040938e29cb28 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Tue, 10 Jun 2025 22:32:22 +0200 Subject: [PATCH 6/6] chore: Prepare 2.2.8 release (#1530) * Update __init__.py * Update CHANGELOG.rst --- CHANGELOG.rst | 6 ++++-- filer/__init__.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0f99f0d4c..1abc74739 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,10 @@ CHANGELOG ========= -Unreleased -========== +2.2.8 (2025-06-10) +================== + +* fix(2.2.x backport): respect upload and directory listing permissions by @fdintino in https://github.com/django-cms/django-filer/pull/1527 2.2.7 (2024-05-16) ================== diff --git a/filer/__init__.py b/filer/__init__.py index 758a70803..379e474bf 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -13,4 +13,4 @@ 8. Publish the release and it will automatically release to pypi """ -__version__ = '2.2.7' +__version__ = '2.2.8'