Skip to content

Commit 2ca7caf

Browse files
committed
Don't allow anonymous users to upload files by poking the /markdownx/upload endpoint directly.
1 parent 35331da commit 2ca7caf

File tree

5 files changed

+82
-5
lines changed

5 files changed

+82
-5
lines changed

dev.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ configure_settings = {
151151
},
152152
],
153153
'ROOT_URLCONF': 'tests.urls',
154+
'MIDDLEWARE': [
155+
'django.contrib.sessions.middleware.SessionMiddleware',
156+
'django.middleware.common.CommonMiddleware',
157+
'django.middleware.csrf.CsrfViewMiddleware',
158+
'django.contrib.auth.middleware.AuthenticationMiddleware',
159+
'django.contrib.messages.middleware.MessageMiddleware',
160+
],
154161
}
155162

156163
settings.configure(**configure_settings)
@@ -289,6 +296,13 @@ configure_settings = {
289296
},
290297
],
291298
'ROOT_URLCONF': 'tests.urls',
299+
'MIDDLEWARE': [
300+
'django.contrib.sessions.middleware.SessionMiddleware',
301+
'django.middleware.common.CommonMiddleware',
302+
'django.middleware.csrf.CsrfViewMiddleware',
303+
'django.contrib.auth.middleware.AuthenticationMiddleware',
304+
'django.contrib.messages.middleware.MessageMiddleware',
305+
],
292306
}
293307

294308
settings.configure(**configure_settings)

docs-src/customization.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ You may place any of the variables outlined in this page in your `settings.py`,
6363
* [`MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS`](#markdownx_markdown_extension_configs)
6464
* [`MARKDOWNX_URLS_PATH`](#markdownx_urls_path)
6565
* [`MARKDOWNX_UPLOAD_URLS_PATH`](#markdownx_upload_urls_path)
66+
* [`MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS`](#markdownx_upload_allow_anonymous)
6667
* [`MARKDOWNX_MEDIA_PATH`](#markdownx_media_path)
6768
* [`MARKDOWNX_UPLOAD_MAX_SIZE`](#markdownx_upload_max_size)
6869
* [`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.
152153
MARKDOWNX_URLS_PATH = '/markdownx/markdownify/'
153154
```
154155

156+
157+
### `MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS`
158+
159+
Default: `False`
160+
161+
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`.
162+
163+
```python
164+
MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS = False
165+
```
166+
167+
155168
### `MARKDOWNX_UPLOAD_URLS_PATH`
156169

157170
Default: `'/markdownx/upload/'`

markdownx/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ def _mdx(var, default):
5959

6060
# Image
6161
# --------------------
62+
MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS = _mdx('UPLOAD_ALLOW_ANONYMOUS', False)
63+
6264
MARKDOWNX_UPLOAD_MAX_SIZE = _mdx('UPLOAD_MAX_SIZE', FIFTY_MEGABYTES)
6365

6466
MARKDOWNX_UPLOAD_CONTENT_TYPES = _mdx('UPLOAD_CONTENT_TYPES', VALID_CONTENT_TYPES)

markdownx/tests/tests.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,51 @@
11
import os
22
import re
3+
from contextlib import contextmanager
4+
from unittest import mock
5+
36
from django.test import TestCase
47
from django.urls import reverse
58

69

710
class SimpleTest(TestCase):
811

9-
def test_upload(self):
12+
@contextmanager
13+
def _get_image_fp(self):
14+
full_path = os.path.join(
15+
os.path.dirname(__file__),
16+
'static',
17+
'django-markdownx-preview.png',
18+
)
19+
with open(full_path, 'rb') as fp:
20+
yield fp
21+
22+
def test_upload_anonymous_fails(self):
1023
url = reverse('markdownx_upload')
11-
with open('markdownx/tests/static/django-markdownx-preview.png', 'rb') as fp:
24+
25+
# Test that image upload fails for an anonymous user when
26+
# MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS is the default False.
27+
with self._get_image_fp() as fp:
1228
response = self.client.post(url, {'image': fp}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
13-
json = response.json()
29+
self.assertEqual(response.status_code, 403)
30+
31+
def test_upload_anonymous_succeeds_with_setting(self):
32+
"""
33+
Ensures that uploads succeed when MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS
34+
is True. This implicitly tests the authenticated case as well.
35+
"""
36+
url = reverse('markdownx_upload')
37+
38+
# A patch is required here because the view sets the
39+
# MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS at first import, reading from
40+
# django.conf.settings once, which means Django's standard
41+
# override_settings helper does not work. There's probably a case for
42+
# re-working the app-local settings.
43+
with mock.patch('markdownx.settings.MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS', True):
44+
with self._get_image_fp() as fp:
45+
response = self.client.post(url, {'image': fp}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
1446

1547
self.assertEqual(response.status_code, 200)
48+
json = response.json()
1649
self.assertIn('image_code', json)
1750

1851
match = re.findall(r'(markdownx/[\w\-]+\.png)', json['image_code'])

markdownx/views.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
from django.core.exceptions import PermissionDenied
12
from django.http import HttpResponse
23
from django.http import JsonResponse
34
from django.utils.module_loading import import_string
45
from django.views.generic.edit import BaseFormView
56
from django.views.generic.edit import View
67

8+
from . import settings
79
from .forms import ImageForm
8-
from .settings import MARKDOWNX_MARKDOWNIFY_FUNCTION
910

10-
markdownify_func = import_string(MARKDOWNX_MARKDOWNIFY_FUNCTION)
11+
markdownify_func = import_string(settings.MARKDOWNX_MARKDOWNIFY_FUNCTION)
1112

1213

1314
class MarkdownifyView(View):
@@ -39,6 +40,20 @@ class ImageUploadView(BaseFormView):
3940
form_class = ImageForm
4041
success_url = '/'
4142

43+
def dispatch(self, request, *args, **kwargs):
44+
"""
45+
Raises PermissionDenied if the current user is not authenticated and
46+
MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS is not set.
47+
48+
:param request: Django request
49+
:type request: django.http.request.HttpRequest
50+
:rtype: django.http.JsonResponse, django.http.HttpResponse
51+
"""
52+
if not settings.MARKDOWNX_UPLOAD_ALLOW_ANONYMOUS and not request.user.is_authenticated:
53+
raise PermissionDenied
54+
55+
return super().dispatch(request, *args, **kwargs)
56+
4257
def form_invalid(self, form):
4358
"""
4459
Handling of invalid form events.

0 commit comments

Comments
 (0)