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

DjangoRestFramework Integration #175

Open
johnthagen opened this issue Jan 26, 2021 · 4 comments
Open

DjangoRestFramework Integration #175

johnthagen opened this issue Jan 26, 2021 · 4 comments

Comments

@johnthagen
Copy link
Contributor

See: encode/django-rest-framework#7702

DRF does not currently have a way to authenticate file downloads or serve them efficiently through a reverse proxy such as NGINX.

Integrating django-downloadview with DRF is challenging because django-downloadview does not interact with DRF authentication middleware (e.g. REST_FRAMEWORK: 'DEFAULT_AUTHENTICATION_CLASSES'.

django-downloadview 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

I was able to get something manually patched together, in case this is useful for others:

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)

@Natim
Copy link
Collaborator

Natim commented Jan 27, 2021

I guess this proxy would be useful as part of the code of django-downloadview what do you think?

@johnthagen
Copy link
Contributor Author

Yes, some kind of integration story with DRF would be nice as DRF does not have a native story for how serve authenticated or accelerated-download files.

I've only implemented this for a project on ObjectDownloadView, but we'd probably want this to be a mixin and usable by all of the django-downloadview Views?

I'm not sure on the exact best design for a general solution.

@radokristof
Copy link

@johnthagen I'm trying to do something similar for StorageDownloadView.

Maybe could you help me with the implementation? At first, it would be enough if only the authentication is checked for a user.

I have modified your code, but I was unable to get it working correctly. It downloads the file even when the user is not logged in.
It seems for me, that my get() function never runs...

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

3 participants