From 362a29dca80b4be6bec7857839edbaec6bc82955 Mon Sep 17 00:00:00 2001 From: Christian Merten Date: Fri, 7 Feb 2025 23:37:04 +0100 Subject: [PATCH] fix(media_access): unprotect members and website images Since access to all media content required staff login, images intended for display on the website were no longer accessible without login. This commit removes the protection for image files of members and startpage posts. We also add tests to prevent this regression from happening again. --- jdav_web/jdav_web/views.py | 18 +++++++++++++++-- jdav_web/members/tests.py | 25 +++++++++++++++++++++++- jdav_web/startpage/tests.py | 39 ++++++++++++++++++++++++++++++++----- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/jdav_web/jdav_web/views.py b/jdav_web/jdav_web/views.py index e433312..1d6c251 100644 --- a/jdav_web/jdav_web/views.py +++ b/jdav_web/jdav_web/views.py @@ -3,8 +3,10 @@ from django.views.static import serve from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required -@staff_member_required -def media_access(request, path): +import re + + +def media_unprotected(request, path): if settings.DEBUG: # if DEBUG is enabled, directly serve file return serve(request, path, document_root=settings.MEDIA_ROOT) @@ -14,3 +16,15 @@ def media_access(request, path): del response['Content-Type'] response['X-Accel-Redirect'] = '/protected/' + path return response + + +@staff_member_required +def media_protected(request, path): + return media_unprotected(request, path) + + +def media_access(request, path): + if re.match('^(people|images)/', path): + return media_unprotected(request, path) + else: + return media_protected(request, path) diff --git a/jdav_web/members/tests.py b/jdav_web/members/tests.py index 7e4b3b3..e6d75e7 100644 --- a/jdav_web/members/tests.py +++ b/jdav_web/members/tests.py @@ -136,8 +136,11 @@ class MemberTestCase(BasicMemberTestCase): email=settings.TEST_MAIL, gender=MALE) self.anna = Member.objects.create(prename="Anna", lastname="Keks", birth_date=timezone.now().date(), email=settings.TEST_MAIL, gender=FEMALE) + img = SimpleUploadedFile("image.jpg", b"file_content", content_type="image/jpeg") + pdf = SimpleUploadedFile("form.pdf", b"very sensitive!", content_type="application/pdf") self.lisa = Member.objects.create(prename="Lisa", lastname="Keks", birth_date=timezone.now().date(), - email=settings.TEST_MAIL, gender=DIVERSE) + email=settings.TEST_MAIL, gender=DIVERSE, + image=img, registration_form=pdf) self.peter.group.add(self.ja) self.anna.group.add(self.ja) self.lisa.group.add(self.ja) @@ -184,6 +187,26 @@ class MemberTestCase(BasicMemberTestCase): s2 = set(other for other in Member.objects.all() if member.may_list(other)) self.assertEqual(s1, s2) + def test_image_visible(self): + url = self.lisa.image.url + c = Client() + response = c.get('/de' + url) + self.assertEqual(response.status_code, 200, 'Members images should be visible without login.') + + def test_registration_form_not_visible(self): + url = self.lisa.registration_form.url + c = Client() + response = c.get('/de' + url) + self.assertEqual(response.status_code, 302, 'Members registration forms should not be visible without login.') + + User.objects.create_user( + username='user', password='secret', is_staff=True + ) + res = c.login(username='user', password='secret') + assert res + response = c.get('/de' + url) + self.assertEqual(response.status_code, 200, 'Members registration forms should be visible after staff login.') + class PDFTestCase(TestCase): def setUp(self): diff --git a/jdav_web/startpage/tests.py b/jdav_web/startpage/tests.py index 8a8ceef..ef81f7d 100644 --- a/jdav_web/startpage/tests.py +++ b/jdav_web/startpage/tests.py @@ -1,10 +1,13 @@ from django.test import TestCase, Client from django.urls import reverse from django.conf import settings +from django.templatetags.static import static +from django.utils import timezone +from django.core.files.uploadedfile import SimpleUploadedFile -from members.models import Group +from members.models import Member, Group, DIVERSE -from .models import Post, Section +from .models import Post, Section, Image class BasicTestCase(TestCase): @@ -16,10 +19,22 @@ class BasicTestCase(TestCase): section=recent) Post.objects.create(title='Last trip', urlname='last-trip', website_text='A fun trip.', section=reports) - Post.objects.create(title='Staff', urlname='staff', website_text='This is our staff: Peter.', - section=orga) - Group.objects.create(name='CrazyClimbers', show_website=True) + file = SimpleUploadedFile("post_image.jpg", b"file_content", content_type="image/jpeg") + staff_post = Post.objects.create(title='Staff', urlname='staff', website_text='This is our staff: Peter.', + section=orga) + Image.objects.create(post=staff_post, f=file) + file = SimpleUploadedFile("member_image.jpg", b"file_content", content_type="image/jpeg") + m = Member.objects.create(prename='crazy', lastname='cool', birth_date=timezone.now().date(), + email=settings.TEST_MAIL, gender=DIVERSE, + image=file) + crazy_group = Group.objects.create(name='CrazyClimbers', show_website=True) + m.group.add(crazy_group) + m.save() Group.objects.create(name='SuperClimbers', show_website=False) + crazy_post = Post.objects.create(title='The crazy climbers', urlname='crazy', website_text='foobar', + section=orga) + crazy_post.groups.add(crazy_group) + crazy_post.save() class ModelsTestCase(BasicTestCase): @@ -110,3 +125,17 @@ class ViewTestCase(BasicTestCase): url = reverse('startpage:gruppe_detail', args=('SuperClimbersNotExisting',)) response = c.get(url) self.assertEqual(response.status_code, 404, 'Response code is not 404 for group.') + + def test_post_with_groups(self): + c = Client() + url = reverse('startpage:post', args=('orga', 'crazy')) + response = c.get(url) + self.assertEqual(response.status_code, 200) + + def test_post_image(self): + c = Client() + staff_post = Post.objects.get(urlname='staff') + img = Image.objects.get(post=staff_post) + url = img.f.url + response = c.get('/de' + url) + self.assertEqual(response.status_code, 200, 'Images on posts should be visible without login.')