diff --git a/dev.xml b/dev.xml index 9183451..83db259 100644 --- a/dev.xml +++ b/dev.xml @@ -151,6 +151,13 @@ configure_settings = { }, ], 'ROOT_URLCONF': 'tests.urls', + 'MIDDLEWARE': [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ], } settings.configure(**configure_settings) @@ -289,6 +296,13 @@ configure_settings = { }, ], 'ROOT_URLCONF': 'tests.urls', + 'MIDDLEWARE': [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ], } settings.configure(**configure_settings) diff --git a/docs-src/customization.md b/docs-src/customization.md index 4b90ceb..2cb7435 100644 --- a/docs-src/customization.md +++ b/docs-src/customization.md @@ -63,6 +63,7 @@ You may place any of the variables outlined in this page in your `settings.py`, * [`MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS`](#markdownx_markdown_extension_configs) * [`MARKDOWNX_URLS_PATH`](#markdownx_urls_path) * [`MARKDOWNX_UPLOAD_URLS_PATH`](#markdownx_upload_urls_path) +* [`MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS`](#markdownx_upload_allow_anonymous) * [`MARKDOWNX_MEDIA_PATH`](#markdownx_media_path) * [`MARKDOWNX_UPLOAD_MAX_SIZE`](#markdownx_upload_max_size) * [`MARKDOWNX_UPLOAD_CONTENT_TYPES`](#markdownx_upload_content_types) @@ -152,6 +153,18 @@ Relative URL to which the Markdown text is sent to be encoded as HTML. MARKDOWNX_URLS_PATH = '/markdownx/markdownify/' ``` + +### `MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS` + +Default: `False` + +Set to `True` if you wish to allow image uploads from anonymous (unauthenticated) users. If you are using MarkdownX in your admin exclusively, or otherwise only to users who are authenticated, you almost certainly do not want to change this from the default of `False`. + +```python +MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS = False +``` + + ### `MARKDOWNX_UPLOAD_URLS_PATH` Default: `'/markdownx/upload/'` diff --git a/markdownx/settings.py b/markdownx/settings.py index d9be7c1..fd59223 100755 --- a/markdownx/settings.py +++ b/markdownx/settings.py @@ -59,6 +59,8 @@ def _mdx(var, default): # Image # -------------------- +MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS = _mdx('UPLOAD_ALLOW_ANONYMOUS', False) + MARKDOWNX_UPLOAD_MAX_SIZE = _mdx('UPLOAD_MAX_SIZE', FIFTY_MEGABYTES) MARKDOWNX_UPLOAD_CONTENT_TYPES = _mdx('UPLOAD_CONTENT_TYPES', VALID_CONTENT_TYPES) diff --git a/markdownx/tests/tests.py b/markdownx/tests/tests.py index 1dc811f..6d621fe 100644 --- a/markdownx/tests/tests.py +++ b/markdownx/tests/tests.py @@ -1,18 +1,51 @@ import os import re +from contextlib import contextmanager +from unittest import mock + from django.test import TestCase from django.urls import reverse class SimpleTest(TestCase): - def test_upload(self): + @contextmanager + def _get_image_fp(self): + full_path = os.path.join( + os.path.dirname(__file__), + 'static', + 'django-markdownx-preview.png', + ) + with open(full_path, 'rb') as fp: + yield fp + + def test_upload_anonymous_fails(self): url = reverse('markdownx_upload') - with open('markdownx/tests/static/django-markdownx-preview.png', 'rb') as fp: + + # Test that image upload fails for an anonymous user when + # MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS is the default False. + with self._get_image_fp() as fp: response = self.client.post(url, {'image': fp}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - json = response.json() + self.assertEqual(response.status_code, 403) + + def test_upload_anonymous_succeeds_with_setting(self): + """ + Ensures that uploads succeed when MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS + is True. This implicitly tests the authenticated case as well. + """ + url = reverse('markdownx_upload') + + # A patch is required here because the view sets the + # MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS at first import, reading from + # django.conf.settings once, which means Django's standard + # override_settings helper does not work. There's probably a case for + # re-working the app-local settings. + with mock.patch('markdownx.settings.MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS', True): + with self._get_image_fp() as fp: + response = self.client.post(url, {'image': fp}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) + json = response.json() self.assertIn('image_code', json) match = re.findall(r'(markdownx/[\w\-]+\.png)', json['image_code']) diff --git a/markdownx/views.py b/markdownx/views.py index 2a72aac..e9e9bd2 100755 --- a/markdownx/views.py +++ b/markdownx/views.py @@ -1,13 +1,14 @@ +from django.core.exceptions import PermissionDenied from django.http import HttpResponse from django.http import JsonResponse from django.utils.module_loading import import_string from django.views.generic.edit import BaseFormView from django.views.generic.edit import View +from . import settings from .forms import ImageForm -from .settings import MARKDOWNX_MARKDOWNIFY_FUNCTION -markdownify_func = import_string(MARKDOWNX_MARKDOWNIFY_FUNCTION) +markdownify_func = import_string(settings.MARKDOWNX_MARKDOWNIFY_FUNCTION) class MarkdownifyView(View): @@ -39,6 +40,20 @@ class ImageUploadView(BaseFormView): form_class = ImageForm success_url = '/' + def dispatch(self, request, *args, **kwargs): + """ + Raises PermissionDenied if the current user is not authenticated and + MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS is not set. + + :param request: Django request + :type request: django.http.request.HttpRequest + :rtype: django.http.JsonResponse, django.http.HttpResponse + """ + if not settings.MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS and not request.user.is_authenticated: + raise PermissionDenied + + return super().dispatch(request, *args, **kwargs) + def form_invalid(self, form): """ Handling of invalid form events.