0

i would like to secure downloadable files in my project but dont know how to accomplish that. Each time the post_detail view get's called a new download link should get generated with a validity of 60 min and which also can only be access ones.

models.py

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(verbose_name="Post Title", max_length=25)
    content = models.TextField(verbose_name="Post Content", max_length=5000)
    tag = models.CharField(verbose_name="Tags/Meta - (sep. by comma)", max_length=50, blank=True)
    category = models.ForeignKey(Category, verbose_name="Category", on_delete=models.CASCADE, null=True)
    postattachment = fields.FileField(
        verbose_name="Post Attachment",
        blank=True,
        null=True,
        upload_to=get_file_path_user_uploads,
        validators=[file_extension_postattachment, file_size_postattachment]

    published_date = models.DateField(auto_now_add=True, null=True)


    def publish(self):
        self.published_date = timezone.now()
        self.save()

    class Meta:
        verbose_name = "Post"
        verbose_name_plural = "Post(s)"
        ordering = ['-title']

    def __str__(self):
        return self.title

views.py

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    list_comments = Comment.objects.get_queryset().filter(post_id=pk).order_by('-pk')
    paginator = Paginator(list_comments, 10)
    page = request.GET.get('commentpage')
    comments = paginator.get_page(page)
    return render(request, 'MyProject/post_detail.html', {'post': post, 'comments': comments})

If smb. maybe has some practice example it would be really helpful.

Thanks in advance

  • Your secure link will have to interact with the webserver serving your files. There are several solutions, one is explained at https://stackoverflow.com/questions/11612032/how-to-generate-a-nginx-secure-link-in-python . – Klaus D. Jan 28 '19 at 14:06
  • I already checked on that and several other posts but its always python related and the django part is sadly missing somehow :( –  Jan 28 '19 at 14:16

3 Answers3

1

I'm no Django expert, but I think this is something you cannot achieve purely in Django. Once you're request has been executed in Django, that is, you have successfully generated a download link for your user, you can't go back 60 minutes later and invalidate it. Not by pure Django (fix me!).

The other blocking reason is that Django is simply not designed to serve files. Files (static & media) are designed to be served by the webserver you have in front of Django (apache/nginx/etc...). For example, a file stored by Django can be accessed on a link like this: https://my-django-app.venom.com/media/my_file.jpg

The problem here is that, the location of your file can be easily guessed. To make it harder to guess, you should put it in a folder with a long random string, like this: https://my-django-app.venom.com/media/b926yqagf6qrzpyew7h3kghtejayxp/my_file.jpg.

To achieve such functionality, I see two ways to go (there may be dozens of other alternatives, but this two comes to my mind immediately):

Generate & delete a random path

To make a path invalid after 60 minutes, you'd have to do the following actions in order on each file request:

  1. Generate a random string
  2. Create a folder using the generated random string as its name
  3. Copy the file to be served into that folder (the original version of the file should be stored outside your MEDIA folder for higher security
  4. Present the user with the generate link
  5. Register this URL somewhere and set an expiration date for it, for example, 60 minutes from generation (I'd create a simple model for this and store it in the SQL)
  6. Run a job every minute on the stored URLs, and if it's expired, remove its folder from the file system

To implement the 6th step, you'd have to extend your Django application with Celery. Using Celery, you can easily schedule jobs (google for celery-beat). This job would execute every minute (or whatever you fancy), query for stored URLs past current time and remove the random string folder and its contents from the MEDIA folder on the file system. Celery is super simple, there are dozens of good examples available online.

Manage expiring links outside Django with an object storage server

It's very easy to manage expiring link on an infrastructure-level by storing user uploaded contents in an object storage, such as, minio. Minio is pretty much like Amazon S3, but open source and can be hosted on your own premises. Minio can generate links for stored files, and you can set the expiration time between 1 minute to 1 week. In Django, all you'd have to do is request a link from minio and specify the expiration. The rest is managed by minio.

To implement this approach, you'd have to extend Django's File Storage API (https://docs.djangoproject.com/en/2.1/ref/files/storage/) and utilize one of the minio clients written for Django. I recommend django-minio-storage (https://github.com/py-pa/django-minio-storage).

If you go for this method, you could completely decouple Django from the user-uploaded contents and stop relying on your webserver serving files from Django's MEDIA folder.

Good luck!

Update 2021:

Since my last update I've created my own Django StorageBackend implementation to easily interact with MinIO. I do my best to keep this library up to date but pull requests are always welcome: https://github.com/theriverman/django-minio-backend

Riverman
  • 503
  • 6
  • 17
  • 1
    Heyho, i already was on minio but its not compatible with django 2.1 only up to 1.11 ... djangostorages + boto3 is quite heavy to implement and maybe used by instragram or so, i dont know. Another option is maybe to use django + nginx_secure-link module but the docs i find on that are also outdated :( actually if i think about it, only a table with the one-time tokens should be needed but it seems quite diffecult to implement i guess. if smb knows how to properly implemtn minio with django 2.1, let me know. media_root always fails with the available plugins :( –  Jan 28 '19 at 14:47
  • I do have Django 2.1.5 & Python 3.6.6 (I realy heavily on f-strings) up and running with minio. I had been messing with it for a while to get it to work, but it seems to be OK now. I'm willing to share code snippets to get you started, but I must WARN you, I'm no Django expert thus you may find very ugly hackings and bad practices in my solution, sorry! I had some things hard-coded for our business logic, so I had to refactor the code a little on the go, but you'll figure it out. I have updated my answer with a link to my code snippets. – Riverman Jan 28 '19 at 15:51
0

you can checkout this pypi package django-onetimelink. It can create a site with one-time links of all the files uploaded. And It could be used to generate one-time link from a uploaded file.

HiroshiFuu
  • 311
  • 1
  • 2
  • 7
0

If you need a onetime link,

  1. create a django url with pk(ability to pass values).
  2. create a set of list hashes and store them in the db.
  3. write a view that takes pk and check it with the available list of hashes, if check passed provide downloadable file to the user, delay a celery task to delete the file(after some time) and delete the hash from the list to avoid future uses. else if check failed redirect url error page.
  4. user should be provided with unused hash that can be used only once.

hope you got the idea.