adamchainz / django-cors-headers

Django app for handling the server headers required for Cross-Origin Resource Sharing (CORS)
MIT License
5.39k stars 536 forks source link

Support for decorators #422

Open robd003 opened 5 years ago

robd003 commented 5 years ago

I've seen in the past people were talking about having support for decorators instead of being stuck with just CORS_URLS_REGEX

I'd like to re-open the request to support decorators so that it's very easy to limit CORS responses to specific views.

adamchainz commented 5 years ago

Thanks. This requires quite a re-imagination of the way the library works and I'd like to engage in this but don't have time right now. What's your use case, do you have something you can't achieve with CORS_URLS_REGEX, and how do you imagine the syntax working?

Thanks,

Adam

danlamanna commented 5 years ago

Coming from the flask world I feel the same. Working with a single regular expression to convey your rules is a bit clunky, I would much rather decorate an endpoint with specific options.

This is the decorator I typically use with flask-cors: https://flask-cors.readthedocs.io/en/latest/api.html#decorator. It enables setting any of those options per endpoint.

simonw commented 3 years ago

Another vote for this here: I run the Django admin on a domain that also hosts a couple of API endpoints. I want to allow CORS for those endpoints, and I was hoping I could do it with a view decorator rather than having to add a middleware that affects every request to my site.

I'll use CORS_URLS_REGEX for the moment (somehow I didn't spot that when scanning the README earlier but it turns out it's documented there, I just missed it).

matclayton commented 2 years ago

Another request here as well. We run a subdomain and need to adjust some of the cors headers in various ways on the per api basis. Unfortunately these are public apis so can't be moved about now :(

jaap3 commented 2 years ago

I had to apply CORS to exactly one view and came up with this solution:

class CustomCorsMiddleware(CorsMiddleware):
    def process_request(self, request):
        response = super().process_request(request)
        if response is not None:
            # Force process_response to run
            return super().process_response(request, response)
        else:
            return None

@method_decorator(decorator_from_middleware(CustomCorsMiddleware), name='dispatch')
class SomeView(View):
    ...

This is comparable to how Django does it for the CSRF decorators (https://github.com/django/django/blob/236e6cb5881168a79a194b43c2d8dff7a14c3f03/django/views/decorators/csrf.py#L1)

By adding a init method to the middleware class that takes extra configuration (overrides of defaults/settings) would make this even more flexible (using decorator_from_middleware_with_args to pass these arguments). This might need some reworking on how the settings wrapper works, but it looks like a feasible solution.

kbakk commented 1 year ago

Similarly to @jaap3's solution, but for function based views:

from corsheaders.middleware import CorsMiddleware
from django.utils.decorators import decorator_from_middleware
from django.views.decorators.csrf import csrf_exempt

cors = decorator_from_middleware(middleware_class=CorsMiddleware)

@cors
@csrf_exempt
def some_function(request):
    ...

It will read settings.py, so the CORS_* config values must be set there, but it does not require setting MIDDLEWARE.

martinberoiz commented 1 year ago

+1 for the decorators. I work on legacy code where the API is scattered across many different url's that don't have any sensible regex pattern. A view decorator would be very useful.

jaap3 commented 1 year ago

[decorator_from_middleware] assumes middleware that’s compatible with the old style of Django 1.9 and earlier [...]

https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.decorators.decorator_from_middleware

Turns out that PR #852 converted the CorsMiddleware from "old style" to "new style". This means it is no longer possible to selectively decorate views with CorsMiddleware using decorator_from_middleware.

Sadly there seem to be no plans to make decorator_from_middleware work with new style middleware (https://code.djangoproject.com/ticket/26626). Currently trying to come up with another way to decorate a select set of views.

jaap3 commented 1 year ago

I was able to get decorator_from_middleware working again by wrapping the new middleware and implementing process_request/process_response:

class CustomCorsMiddleware(CorsMiddleware):
    def process_request(self, request):
        response = self.check_preflight(request)
        if response is not None:
            # Add the CORS headers to the preflight response
            response = self.process_response(request, response)
        return response

    def process_response(self, request, response):
        return self.add_response_headers(request, response)

Use at your own risk. It basically re-implements the __call__ implementation of the CorsMiddleware, so if future versions change how that works this wrapper could stop working correctly (or at all).