-
-
Notifications
You must be signed in to change notification settings - Fork 799
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
Add CORS Middleware #1150
Add CORS Middleware #1150
Conversation
a368e7a
to
6184aeb
Compare
@rcmurphy Thanks for this. Please take a look at the failed tests. I recommend running tox locally before pushing up to github. Details here: https://django-oauth-toolkit.readthedocs.io/en/latest/contributing.html |
@n2ygk Yep! I am. I thought I was getting false negatives from not having every version of python installed; once I realized there were significant changes that need to be made I tried to convert it to a draft PR until I had it sorted, but was unable to do so. The recent couple force pushes were trying to figure out why pre-commit is still saying there need to be changes even though it passes locally. Edit: turns out some of my changes weren't getting pushed properly, causing the failures. |
56000c3
to
4bac4f9
Compare
@rcmurphy I've converted it to draft. Let me know when ready for review. I'll try to make time this weekend. |
Codecov Report
@@ Coverage Diff @@
## master #1150 +/- ##
=======================================
Coverage 96.90% 96.91%
=======================================
Files 31 31
Lines 1812 1850 +38
=======================================
+ Hits 1756 1793 +37
- Misses 56 57 +1
Continue to review full report at Codecov.
|
Removes an exception used for control flow.
@rcmurphy I'm going to need a little extra time to try and understand this code by running it through a few test cases... |
My issue with this middleware approach (and I have had a similar issue with django-cors-headers) is that it applies to all urls that are handled by Django. I.e. with this middleware installed client apps are also allowed to access Django's admin urls. I've worked around this in projects in several ways. One is a rather complicated I'm not sure what the best approach is to be honest. The middleware is convenient if all views in the Django application should be accesible to clients, but it's hard to tell if that's the majority of use cases. |
@jaap3 Would you agree that @rcmurphy's approach is an improvement, even if not the perfect fix? Incremental progress beyond I'm just trying to understand the implications and still need to set up my test to see how this PR changes things. Sorry @rcmurphy but "next week" has become "next month"! |
Well CORS (cross-origin resource sharing) is about removing security barriers that are in place by default, so I'm hesitant in saying that this is an improvement. IMHO an overly permissive CORS policy is worse than not providing CORS headers at all, as it can have unforeseen consequences. From a library perspective DOT should really only apply CORS headers to resources it provides and can be reasonably expected to be requested by cross origin scripts. Going through the views that are in DOT I'd say that the following views should probably be served with CORS headers, as they are likely used by public clients:
I'd say that having CORS headers for e.g. the AuthorizationView might even pose a security risk. Is there any OAuth2/OIDC flow that requires a public client to directly access the authorization endpoint (e.g. not through a redirect)? If instead of a middleware a mixin approach were used I'd be much more comfortable. I.e. (untested example code): class CrossOriginSharedResource:
def dispatch(self, request, *args, **kwargs):
# If this is a preflight-request, we must always return 200
if request.method == "OPTIONS" and "HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META:
response = http.HttpResponse()
else:
response = super().dispatch(request, *args, **kwargs)
try:
cors_allow_origin = _get_cors_allow_origin_header(request)
except AbstractApplication.NoSuitableOriginFoundError:
pass
else:
response["Vary"] = "Origin"
response["Access-Control-Allow-Origin"] = cors_allow_origin
response["Access-Control-Allow-Credentials"] = "true"
if request.method == "OPTIONS":
response["Access-Control-Allow-Headers"] = ", ".join(HEADERS)
response["Access-Control-Allow-Methods"] = ", ".join(METHODS)
return response (I added a Vary header as that might help prevent confusing caching issues, i.e. when using a caching proxy). This can be combined with In addition, I'm not sure if redirect_url is always a good indicator of where other request will be coming from. I have had clients that use something like com.example://callback as the redirect url, but subsequent API request are coming from example.com. This is the case for Electron apps and some "native" iOS and Android apps that use a custom scheme to handle these callbacks (and other in-app deep links). So maybe an additional "origins" field needs to be added to applications? I hope this makes sense? P.S. I also believe confidential clients shouldn't really be taken into account when looking for CORS origins, as they normally are only really used by server side software where CORS does not apply. |
@rcmurphy @jaap3 Would it be possible for |
@n2ygk @jaap3 sorry, I've been swamped with my day job. Two thoughts:
|
To me the suggested mixin approach (which could probably also be turned into a decorator) seems to be the most pragmatic. It allows DOT to selectivly apply CORS to the minimal set of views, and gives developers the option to use DOT to provide CORS headers to other views. Here's an example decorator (untested): def cross_origin_shared_resource(view):
def cors_view(request, *args, **kwargs):
# If this is a preflight-request, we must always return 200
if request.method == "OPTIONS" and "HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META:
response = http.HttpResponse()
else:
response = view(request, *args, **kwargs)
try:
cors_allow_origin = _get_cors_allow_origin_header(request)
except AbstractApplication.NoSuitableOriginFoundError:
pass
else:
response["Vary"] = "Origin"
response["Access-Control-Allow-Origin"] = cors_allow_origin
response["Access-Control-Allow-Credentials"] = "true"
if request.method == "OPTIONS":
response["Access-Control-Allow-Headers"] = ", ".join(HEADERS)
response["Access-Control-Allow-Methods"] = ", ".join(METHODS)
return response
functools.update_wrapper(cors_view, view) It could then be apllied to class based views using Django's |
@rcmurphy any inclination to continue working on this PR or rebased it on master to help keep it current? |
@rcmurphy you can fix this conflicts ? |
@rcmurphy This Function |
I would strongly support an approach that adds allowed origins to application and utilizes decorators on the built in views. I'm not in favor of implementing this as a middleware that runs on every request when it can be done at the view level on this specific requests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rcmurphy first, I really appreciate the time and energy you've put into this. I hope you don't find my position on this PR discouraging.
I don't think the middleware approach here is the best approach. At the core of my concern is performance and support. With the middleware approach, we are processing on every request, not just for the DOT views. This creates overhead for every site that consumes it on requests that are not our purview. It could cause issues with unrelated applications which would likely become support overhead for the DOT maintainers. It could also create support overhead dealing with settings issues like middleware and application ordering.
I would strongly support an approach that adds allowed origins to application and utilizes decorators on the built in views.
Would you be willing to pursue this alternate implementation path?
We merged additional CORS work in Fix CORS by passing 'Origin' header to OAuthLib, in #1229. There is an example at https://github.com/jazzband/django-oauth-toolkit/blob/master/tests/app/idp/idp/apps.py of using the coreheaders middleware to set explicit headers for a given view for people who need additional help with cors. I feel that satisfies the middleware use case presented here with an existing middleware. It would still be nice to have an internal DOT way to set the cors headers on the userinfo endpoint. @rcmurphy has your need with regard to this PR been addressed in that work? Or is there still something that could improve you situation that needs doing? |
I'm closing this as stale and possibly resolved. The |
Description of the Change
Adds automatic CORS header generating middleware. As discussed in #287 and #300.
Checklist
CHANGELOG.md
updated (only for user relevant changes)AUTHORS