Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to authenticate and serve private media files #7702

Closed
4 of 6 tasks
johnthagen opened this issue Jan 26, 2021 · 9 comments
Closed
4 of 6 tasks

Document how to authenticate and serve private media files #7702

johnthagen opened this issue Jan 26, 2021 · 9 comments

Comments

@johnthagen
Copy link
Contributor

Checklist

  • I have verified that that issue exists against the master branch of Django REST framework.
  • I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • This is not a usage question. (Those should be directed to the discussion group instead.)
  • This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
  • I have reduced the issue to the simplest possible case.
  • I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

Expected behavior

The docs explain how to use FileFields to serve media file URLs, but if these files are sensitive and need authentication/permissions, there is no mention of how to authenticate their download.

Django packages exist for this, such as django-downloadview, but it's not clear how (or if) the built in DRF authentication can interact with this. For example, these views don't interact with REST_FRAMEWORK 'DEFAULT_AUTHENTICATION_CLASSES'.

Does DRF have a solution for efficient media file serving that links into its authentication system? If so, I'd be happy to open a PR to clarify the docs.

@akx
Copy link
Contributor

akx commented Jan 26, 2021

I don't think DRF has anything built in for this – django-downloadview's efficiency bits seems like it wouldn't be too hard to hook up to DRF.

@carltongibson
Copy link
Collaborator

This sort of thing is what Nginx's X-Accel is for.

https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/

Authenticate as normal in Django or DRF then have nginx serve the file.

@xordoquy
Copy link
Collaborator

DRF has no default for it for it is outside the scope of DRF.
django-downloadview looks promising but doesn't look like it supports DRF. It would probably be a good addition there.

@johnthagen
Copy link
Contributor Author

johnthagen commented Jan 26, 2021

Thank you for the responses.

The specific challenges I'm facing when I am trying to integrate django-downloadview (which supports Nginx X-Accel) and DRF is that django-downloadview does not integrate with DRF's 'DEFAULT_AUTHENTICATION_CLASSES'. So the views cannot take advantage of 'rest_framework.authentication.BasicAuthentication' or (third-party) 'rest_framework_simplejwt.authentication.JWTAuthentication' authentication middleware. This results in the django-downloadview View being presented the AnonymousUser user, and thus being rejected.

@johnthagen
Copy link
Contributor Author

Related: edoburu/django-private-storage#39

@akx
Copy link
Contributor

akx commented Jan 26, 2021

You could delegate to django-downloadview in your DRF viewset's response phase, so have DRF do everything else such as authentication and so on. Looks like BaseDownloadView could be easily extended for that...

@johnthagen
Copy link
Contributor Author

I was able to patch everything together. In case this is useful to others, here is a minimal reproduction:

from typing import Any, List, Optional, Tuple, Type

from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.core.handlers.wsgi import WSGIRequest
from django_downloadview import DownloadResponse, ObjectDownloadView
from rest_framework.authentication import BaseAuthentication, BasicAuthentication
from rest_framework.permissions import BasePermission, DjangoObjectPermissions
from rest_framework_simplejwt.authentication import JWTAuthentication


class DRFAuthenticatedObjectDownloadView(ObjectDownloadView):
    """A generic file download view that automatically authenticates the user and
    validates permissions using DRF middleware."""

    permissions_class: Type[BasePermission] = DjangoObjectPermissions

    # Note: This needs to be kept in sync with
    #   settings.py REST_FRAMEWORK DEFAULT_AUTHENTICATION_CLASSES
    auth_classes: List[Type[BaseAuthentication]] = [BasicAuthentication, JWTAuthentication]

    def authenticate(self, request: WSGIRequest) -> None:
        """Updates request.user if the client has sent headers that configured ``auth_classes``
        successfully authenticate.
        """
        for auth_class in self.auth_classes:
            auth_resp: Optional[Tuple[User, None]] = auth_class().authenticate(request)
            if auth_resp is not None:
                request.user = auth_resp[0]
                return

    def has_permission(self, request: WSGIRequest) -> None:
        """Validate that the current User has appropriate access permissions to a Model.

        Raises:
            PermissionDenied: If the user does not have the required permissions.
        """
        instance = self.get_object()
        permissions = self.permissions_class()
        if not (
            permissions.has_permission(request, self)
            and permissions.has_object_permission(request, self, instance)
        ):
            raise PermissionDenied()

    def get(self, request: WSGIRequest, *args: Any, **kwargs: Any) -> DownloadResponse:
        """Authenticate user and check permissions before returning the file download."""
        self.authenticate(request)
        self.has_permission(request)
        return super().get(request, *args, **kwargs)

@carltongibson
Copy link
Collaborator

Nice example @johnthagen.

I'm going to close this as I think it's probably out of scope for DRF itself. Thanks.

@aryaniyaps
Copy link

This helped me a lot! Thanks a lot @johnthagen I am using django along with apache x-sendfile and have a complex permissions system to validate through before serving the file. Yet to implement but this looks very promising

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants