Django DRF Имидж-магазин закрыт для пользователей только на S3 - PullRequest
0 голосов
/ 19 декабря 2018

Я использую DRF для хранения загруженных пользователем изображений на S3, и на S3 я вижу, что изображения доступны для общественности с помощью URL.

Мое беспокойство здесь заключается в том, что существует какой-либо лучший способ обеспечить этоизображения и ограничить их для владельца этого изображения только для его просмотра.

Я использую Heroku для развертывания моей DRF API Framework, но я вижу это как проблему безопасности для моего пользователя, который загружает файлы изображений в S3bucket.

Я пытаюсь изолировать изображения пользователей по их именам. Это само собой. Но все же это общедоступно, поэтому я могу получить доступ к этим изображениям для другого пользователя, просто выясни это там по имени.

ВотS3 URL для медиа-изображений

https://xxx.s3.amazonaws.com/media/persons/niravjoshi/20181218152410.jpg

Вот мой settings.py для Django

import os
import pymysql  # noqa: 402
pymysql.install_as_MySQLdb()
import dj_database_url
from decouple import config
import django_heroku

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

#SECRET_KEY = 'feufm)u(pvsvb%&_%%*)p_bpa+sv8zt$#_-do5q3(vou-j*d#p'

SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
DATABASES = {
    'default': dj_database_url.config(
        default=config('DATABASE_URL')
    )
}

DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    #Django Project Apps
    'persons',
    'rest_framework',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    #'social_django',
]

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
AWS_REGION = os.environ.get('AWS_REGION', '')  # e.g. eu-west-1
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY', '')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_KEY', '')
AWS_STORAGE_BUCKET_NAME = os.environ.get('S3_BUCKET', '')
AWS_QUERYSTRING_AUTH = False
AWS_S3_CUSTOM_DOMAIN = os.environ.get("AWS_S3_CUSTOM_DOMAIN", "")

MEDIAFILES_LOCATION = 'media'
DEFAULT_FILE_STORAGE = 'DjangoE2ISAapi.storage_backends.MediaStorage'
MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

AWS_STATIC_LOCATION = 'static'
STATICFILES_STORAGE = 'DjangoE2ISAapi.storage_backends.StaticStorage'
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_STATIC_LOCATION)




STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
django_heroku.settings(locals())

from DjangoE2ISAapi.restconf.main import *

Вот мой storage_backends.py

from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage

class StaticStorage(S3Boto3Storage):
    location = settings.AWS_STATIC_LOCATION

class MediaStorage(S3Boto3Storage):
    location = settings.MEDIAFILES_LOCATION

Вот моя личность model.py.

from django.core.serializers import serialize
from django.db import models
from django.conf import settings
import json
from django.core.serializers.json import DjangoJSONEncoder
# Create your models here.

def upload_file(instance,filename):
    import os
    from django.utils.timezone import now
    filename_base, filename_ext = os.path.splitext(filename)
    return "persons/{user}/{filename}".format(user=instance.UserName, filename=now().strftime("%Y%m%d%H%M%S")+filename_ext.lower())



class PersonQuerySet(models.QuerySet):
    def serialize(self):
        list_values=list(self.values('UserName','PersonId','PersonName','Person_Image','Person_sex','Person_BDate'))
        print (list_values)
        return json.dumps(list_values,sort_keys=True,indent=1,cls=DjangoJSONEncoder)

class PersonManager(models.Manager):
        def get_queryset(self):
            return PersonQuerySet(self.model,using=self._db)


class Person(models.Model):
    UserName = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
    PersonId = models.AutoField(primary_key=True)
    PersonName = models.CharField("person's first name", max_length=30,null=False)
    Person_Image = models.ImageField(upload_to=upload_file,null=True, blank=True)
    SEX = (('M','Male'),('F','Female'), ('N','None'), )
    Person_sex = models.CharField(max_length=1,choices=SEX,null=False)
    Person_BDate = models.DateField(null=False)
    Person_CDate =  models.DateField(null=False,auto_now_add=True)
    objects = PersonManager()


    def __str__(self):
        return str(self.PersonName) or ""

    def serialize(self):
        data={
            'UserName': self.UserName,
            'PersonId': self.PersonId,
            'PersonName': self.PersonName,
            'Person_Image':self.Person_Image,
            'Person_sex': self.Person_sex,
            'Person_Bdate': self.Person_BDate
        }
        data = json.dumps(data,sort_keys=True,indent=1,cls=DjangoJSONEncoder)
        return data

    @property
    def owner(self):
        return self.UserName

enter image description here

Здесьответ Person API View:

enter image description here

Ответы [ 2 ]

0 голосов
/ 19 декабря 2018

Документы для ACL Бото: здесь .Я предлагаю просто использовать private «стандартную политику» - поскольку у ваших пользователей все равно нет учетных записей S3, это самая простая идея.Ваше приложение, конечно, должно будет отслеживать, какой пользователь «владеет» какими файлами (что должно быть очень, очень простой моделью Django!).

Чтобы пользователи могли загружать только через свой собственный файл.приложения, просто передайте небольшое значение параметру expires_in при генерации URL.Пользователи получат только действительную ссылку для скачивания через ваше приложение, и эта ссылка будет недействительной после их загрузки.

Вот пример кода, использованного для создания ссылки для скачивания:

@login_required
def download_document(request, file_id):
    '''
     Request handler to download file
    '''
    file = Document.objects.get(pk=file_id)
    s3 = get_aws_s3_client() #function to create s3 session
    download_url = s3.generate_presigned_url(
        'get_object',
        Params= {'Bucket': file.bucket_name, 'Key': file.key},
        ExpiresIn=5, #the url won't be valid after only 5 seconds
    )
    return redirect(download_url)

Вы можете пойти дальше и сделать представление действительным только для владельца файла, добавив этот код:

if file.owner == request.user : 
   return redirect(download_url)
else :
   # render 403.html  since access denied. 

Редактировать: В соответствии с запросом для этого решения требуется использовать конкретную модель для храненияинформация, связанная с каждым документом.Модель будет выглядеть примерно так:

class Image(models.Model):
    customer     = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE,)
    key          = models.CharField(max_length=120) #uuid64 will be stored here and used for s3 urls
    name         = models.CharField(max_length=120, null=True, blank=True)
    size         = models.FloatField()
    human_size   = models.CharField(max_length=120, null=True, blank=True)
    filetype     = models.CharField(max_length=120, null=True, blank=True)
    fextension   = models.CharField(max_length=30, null=True, blank=True)
    bucket_name  = models.CharField(max_length=120, null=True, blank=True)
    region       = models.CharField(max_length=120, null=True, blank=True)
    s3link       = models.CharField(max_length=170, null=True, blank=True)
    timestamp    = models.DateTimeField(auto_now_add=True)
    updated      = models.DateTimeField(auto_now=True)
    uploaded     = models.BooleanField(default=False)
    active       = models.BooleanField(default=True)

    def __str__(self):
        return self.name

Я не могу обсуждать детали, связанные с сериализацией, так как никогда не использовал DRF.

0 голосов
/ 19 декабря 2018

Я бы добавил поле uuid для каждого пользователя, например.

import uuid

class Person(models.Model):
    uuid = models.UUID(default=uuid.uuid4)
    ....

Вы можете установить его также как первичный ключ вместо AutoField.

и поставить этот уникальный uuidв URL вместо name, чтобы он выглядел следующим образом:

https://xxx.s3.amazonaws.com/media/persons/b3810ec3-dd9d-4f11-a1e1-47835c0058ec/20181218152410.jpg

Это изображение будет по-прежнему общедоступным, но к нему невозможно получить доступ, если вы не знаете uuid конкретного пользователя.

Если вы хотите более безопасное решение, не зависящее только от URL, вам нужно добавить логику аутентификации.

...