zip files before download in file broswer

needs to be completly rewritten in the future but for now this is more secure
This commit is contained in:
vabene1111
2022-07-04 14:39:53 +02:00
parent d9d0676bed
commit 690c486bb2
5 changed files with 255 additions and 70 deletions

View File

@@ -1,9 +1,11 @@
import traceback
from datetime import timedelta, datetime
from decimal import Decimal
from gettext import gettext as _
from html import escape
from smtplib import SMTPException
from PIL import Image
from django.contrib.auth.models import User, Group
from django.core.mail import send_mail
from django.db.models import Avg, Q, QuerySet, Sum
@@ -266,6 +268,20 @@ class UserPreferenceSerializer(WritableNestedModelSerializer):
class UserFileSerializer(serializers.ModelSerializer):
file = serializers.FileField(write_only=True)
file_download = serializers.SerializerMethodField('get_download_link')
preview = serializers.SerializerMethodField('get_preview_link')
def get_download_link(self, obj):
return self.context['request'].build_absolute_uri(reverse('api_download_file', args={obj.pk}))
def get_preview_link(self, obj):
try:
img = Image.open(obj.file.file.file)
return self.context['request'].build_absolute_uri(obj.file.url)
except Exception:
traceback.print_exc()
return ""
def check_file_limit(self, validated_data):
if 'file' in validated_data:
@@ -295,12 +311,25 @@ class UserFileSerializer(serializers.ModelSerializer):
class Meta:
model = UserFile
fields = ('name', 'file', 'file_size_kb', 'id',)
fields = ('id', 'name', 'file', 'file_download', 'preview', 'file_size_kb')
read_only_fields = ('id', 'file_size_kb')
extra_kwargs = {"file": {"required": False, }}
class UserFileViewSerializer(serializers.ModelSerializer):
file_download = serializers.SerializerMethodField('get_download_link')
preview = serializers.SerializerMethodField('get_preview_link')
def get_download_link(self, obj):
return self.context['request'].build_absolute_uri(reverse('api_download_file', args={obj.pk}))
def get_preview_link(self, obj):
try:
img = Image.open(obj.file.file.file)
return self.context['request'].build_absolute_uri(obj.file.url)
except Exception:
traceback.print_exc()
return ""
def create(self, validated_data):
raise ValidationError('Cannot create File over this view')
@@ -310,7 +339,7 @@ class UserFileViewSerializer(serializers.ModelSerializer):
class Meta:
model = UserFile
fields = ('name', 'file', 'id',)
fields = ('id', 'name', 'file_download', 'preview')
read_only_fields = ('id', 'file')
@@ -708,7 +737,7 @@ class RecipeSerializer(RecipeBaseSerializer):
fields = (
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
'internal', 'show_ingredient_overview','nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked',
'internal', 'show_ingredient_overview', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked',
)
read_only_fields = ['image', 'created_by', 'created_at']

View File

@@ -118,6 +118,7 @@ urlpatterns = [
path('api/get_facets/', api.get_facets, name='api_get_facets'),
path('api/reset-food-inheritance/', api.reset_food_inheritance, name='api_reset_food_inheritance'),
path('api/switch-active-space/<int:space_id>/', api.switch_active_space, name='api_switch_active_space'),
path('api/download-file/<int:file_id>/', api.download_file, name='api_download_file'),
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),

View File

@@ -5,6 +5,7 @@ import re
import traceback
import uuid
from collections import OrderedDict
from zipfile import ZipFile
import requests
import validators
@@ -1216,6 +1217,31 @@ def switch_active_space(request, space_id):
return Response({}, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET'])
# @schema(AutoSchema()) #TODO add proper schema
@permission_classes([CustomIsUser])
def download_file(request, file_id):
"""
function to download a user file securely (wrapping as zip to prevent any context based XSS problems)
temporary solution until a real file manager is implemented
"""
try:
uf = UserFile.objects.get(space=request.space, pk=file_id)
in_memory = io.BytesIO()
zf = ZipFile(in_memory, mode="w")
zf.writestr(uf.file.name, uf.file.file.read())
zf.close()
response = HttpResponse(in_memory.getvalue(), content_type='application/force-download')
response['Content-Disposition'] = 'attachment; filename="' + uf.name + '.zip"'
return response
except Exception as e:
traceback.print_exc()
return Response({}, status=status.HTTP_400_BAD_REQUEST)
def get_recipe_provider(recipe):
if recipe.storage.method == Storage.DROPBOX:
return Dropbox