ExtJS 5 application + Django rest framework CORS error when changing URL of store - django

I am developing a ExtJS application that uses a Django-rest-framework service. I am using CORS headers to allow fetching the data from the service (https://github.com/OttoYiu/django-cors-headers).
What happens is that at a point in time I want to change the URL from the store. And when I do that I get the following error:
XMLHttpRequest cannot load http://10.98.0.241:8000/reacsearch/as?_dc=1418831884352&page=1&start=0&limit=25. The request was redirected to 'http://10.98.0.241:8000/reacsearch/as/?_dc=1418831884352&page=1&start=0&limit=25', which is disallowed for cross-origin requests that require preflight.
In the settings.oy I define the following properties for the CORS
CORS_ALLOW_METHODS = (
'GET',
'OPTIONS'
)
CORS_ORIGIN_ALLOW_ALL = True
This works fine when I use URLs to list all the elements in my database, however when I change the store for another URL I get the error above. Also the link works fine in the browser.
The store url change is made this way:
var store = Ext.getStore(storeName);
store.getProxy().setUrl(newURL);
store.load();
The difference between the views, is that the two that work on the application are viewsets, while the other is just a generic list
class Example1viewset(viewsets.ModelViewSet):
"""
API endpoing that allows metabolites to be viewed.
"""
queryset = examples1.objects.all()
serializer_class = Example1Serializer
class Example1SearchList(generics.ListAPIView):
serializer_class = Example1Serializer
def get_queryset(self):
queryset = Example.objects.all()
if 'attr' in self.kwargs:
queryset = queryset.filter(Q(attribute1__contains=self.kwargs['attr']) | Q(attribute2__contains=self.kwargs['abbr']))
return queryset
Like I mentioned both examples work fine in the browser (even accessing through other computers in the network), however in the application when changing the URL of the store I get the CORS error. Does anyone has any idea why this is happening?
Thank you.
Edit:
Just for clarification, the problem is not in changing the url of the store. As I tried to set those urls as defaults, but they are not working when accessing from the application.
My urls.py file:
router = routers.DefaultRouter()
router.register(r'example', views.Example1ViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^reacsearch/(?P<attr>.+)/$', Example1SearchList.as_view()),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
Can it be that the problem is related with the fact that I am not adding the search list to the router?
Edit2
Problem solved since I was trying to fetch data from a different domain. I changed the type of store to jsonp in Extjs, and I also allowed my rest service to render data as jsonp.
Just a reminder if anyone comes accross this same problem, it is necessary to add ?format=jsonp to the store url:
http://my/url/?format=jsonp

Since it looks like an alternate solution was found, I'll explain what the issue appeared to be as well as why the alternative works.
XMLHttpRequest cannot load first url. The request was redirected to 'second url', which is disallowed for cross-origin requests that require preflight.
The issue here is that you are telling Django to enforce the trailing slash, which makes it automatically redirect urls without a trailing slash to urls with a trailing slash, assuming that one exists. This is why, as stated in the error, the request was redirected to the second url, which you can tell has the missing trailing slash. This is controlled by the APPEND_SLASH Django setting which is True by default.
The problem is that when CORS is doing a preflight request, which is what allows it to determine if the request can be made, there must be a valid response at the requested URL. Because you are redirecting the request, the preflight request fails and you're stuck without your information.
You can fix this by adding the trailing slash in your code. There appear to be a few solutions for doing this with ext, but I personally can't recommend a specific one. You can also manually set the url to use the trailing slash, which sounds like what you were doing previously.
Or you can use JSONP...
You've found the alternative solution, which is to use JSONP to make the request instead of relying on CORS. This gets around the preflight issue and works in all major browsers, but there are some drawbacks to consider. You can find more information on CORS vs JSONP by looking around.
You're going to need CORS if you want to push any changes to your API, as JSONP only supports GET requests. There are other advantages, such as the ability to abort requests, that also comes with CORS.

Related

Django CSRF Token present but still getting 403 Forbidden Error

I am trying to set up a Django API that receives POST requests with some JSON data and basically sends emails to a list of recipients. The logic is rather simple:
First I have the view for when I create a blog post. In the template, I include the csrf_token as specified on the Django Documentation. When I hit the submit button, behind the scene the create-post view, in addition to creating the post, makes a request (I am using the requests module) to the API which is charged with sending the emails. This is the piece of logic the sends the request to the API:
data = {
"title": new_post.title,
"summary": new_post.summary,
"link": var["BASE_URL"] + f"blog/post/{new_post.slug}"
}
csrf_token = get_token(request)
# print(csrf_token)
headers = {"X-CSRFToken": csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers)
As you can see I am adding the X-CSRFToken to the headers which I generate through the get_token() method, as per the Django docs. However, the response in the API is a 403 Forbidden status CSRF Token not set.
I have checked the headers in the request and the token is indeed present. In addition, I have been providing other routes to the API and I have been using it for AJAX calls which again is very simple just follow the Django docs and they work perfectly well.
The problem seems to arise when I make the call from within the view, AJAX calls are handle by Javascript static files, and as I said they work fine.
I have thought that Django didn't allow the use of 2 CSRF tokens on the same page (one for the submit form and the other in the view by get_token()), but that's not the problem.
This is typically the error I get:
>>> Forbidden (CSRF cookie not set.): /_api/send-notification/
>>> "POST /_api/send-notification/ HTTP/1.1" 403 2864
I have read several similar questions on SO but they mostly involved using the csrf_exempt decorator, which in my opinion is not really a solution. It just gets rid of the CRSF token usefulness altogether.
Does anyone have any idea what might be causing this problem?
Thanks
Error tries to tell you that you need to add token into cookie storage like that:
cookies = {'csrftoken': csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers, cookies=cookies)

How can I send a request to a view from an admin command without hard coding the url

I am trying to create an admin command that will simulate some api calls associated with a view but I don't want to hard code the url, for example like that url='http://127.0.0.1:8000/api/viewname', in order to send the request.
If I use the reverse option I can obtain half the url /api/viewname.
If I try to post the request that way
url = reverse('name-of-view')
requests.post(url, data=some_data)
I get
requests.exceptions.MissingSchema: Invalid URL '/api/viewname/': No schema supplied. Perhaps you meant http:///api/viewname/?
Do I have to look whether the server is running on the localhost or is there a more generic way?
requests module needs the absolute url to post to. you need
url = 'http://%s%s' % (request.META['HTTP_HOST'], reverse('name-of-view'))
requests.post(url, data=some_data)

Django - get the running server absolute url

Is there any way I can set a variable in settings.py to point to the current url ?
For example, If I'm running a debug server on http://0.0.0.0:8080 or http://127.0.0.1:9000 or https://www.mydomain.com.
I basically need to generate full path for image fields returned from an API. The trick is that I don't always have a request object (POST on DRF for example - The request does not exist in the context when transform_xxx is called).
Appreciate any help with this.
Assuming you're using the contrib.sites framework you can pull the site URL from there.
There's a good example in the docs for when you don't have access to the request:
current_site = Site.objects.get_current()
if current_site.domain == 'foo.com':
# Do something
pass
else:
# Do something else.
pass
Hopefully that's what you need.
UPDATE: I see from the comments that used correctly you would have the request available. I'll leave this here answering the challenge for when you don't.

Login Required Middleware gets HTTP/1.1" 302

I've done a simple login that works fine while the authenticated user is redirected to a page with url:
url(r'^(?P<user_id>\d+)/$', 'auth.views.main', name='main'),
Now I'm trying to use a LoginRequired-Middleware but when I do and I try to login I get:
"POST /login/ HTTP/1.1" 302 0
"GET /1000/ HTTP/1.1" 302 0
and I remain to the initial login-page.
I use a common snippet for doing that with
LOGIN_URL = ( '/login/' )
What's going wrong?
Unfortunately your provided code is insufficient to determine your error, so I can only give you pointers:
Make sure you aren't actually still caching an HTTP 302 REDIRECT site, from earlier experimenting. See here for useful hints.
Reset your browser-cache. If you are using Chrome, you can get several cache clearance options by pressing CTRL+SHIFT+J (starting the Developer Tools), and long pressing the Reload button next to the top navigation url-bar.
For Firefox, see also these useful suggestions.
http://support.mozilla.org/es/questions/848678
https://superuser.com/questions/23134/how-to-turn-off-firefox-cache
Did you just upgrade Django from version <1.3.x? I came across a situation where passwords were rewritten with a new default-Hasher, and thus one could no longer log in. Check password-hasher consistency directly in your database, within the auth_users - table. For instance SHA1 password-hashes start with sha1...
Make sure to setup the list PASSWORD_HASHERS in your settings.py, is in an order that retains the original project's primary Hasher on top. i.e. the hashing-algorithm that was used to initially hash the user passwords.
Is the url(r'...'...) in the root urls.py of your project?
Make sure 'django.contrib.auth' is listed in your INSTALLED_APPS section of settings.py (though you should get error's if that is not the case) and apps that must be loaded beforehand, are actually placed uppermost.
Check the setup of your TEMPLATE_CONTEXT_PROCESSORS, MIDDLEWARE_CLASSES in settings.py according to your snippet's comments and make sure it is compatible with the SessionMiddleware of your Django version.
Check all codelines in your project that make redirects:
Check for:
from django.views.generic.base import RedirectView
url(r'^.*$', RedirectView.as_view(url='<url_to_view>', permanent=False), name='index')
or
from django.http import HttpResponsePermanentRedirect
HttpResponsePermanentRedirect('url...')
Note that in Django 1.5 the deprecated redirect_to function has been replaced by RedirectView, according to their generic view paradigm.
redirect(...)
url('^pattern/$', lambda _: redirect('/redirecttourl/'))
url(r'^pattern$', redirect('example.com')),
url(r'^pattern$', redirect(projectname.views.home)),
(r'^accounts/profile/$', 'redirect_to', {'url': 'generic_account_url'}),
HttpResponseRedirect(....)
HttpResponseRedirect(request.META.get('HTTP_REFERER','/'))
Django's built-in authentication login page includes a next query string. next= determines the the page to return to after login:
See: http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.decorators.login_required
Check your server's HTTP access logs. Is the content-size of the login-site after the redirect the same as the login before the redirect?
It helps providing the entire HTTP access history during the login process. As you do not hang in an redirect-loop, more HTTP's than the two mentioned must follow.

Setting HTTP_REFERER header in Django test

I'm working on a Django web application which (amongst other things) needs to handle transaction status info sent using a POST request.
In addition to the HTTP security supported by the payment gateway, my view checks request.META['HTTP_REFERER'] against an entry in settings.py to try to prevent funny business:
if request.META.get('HTTP_REFERER', '') != settings.PAYMENT_URL and not settings.DEBUG:
return HttpResponseForbidden('Incorrect source URL for updating payment status')
Now I'd like to work out how to test this behaviour.
I can generate a failure easily enough; HTTP_REFERER is (predictably) None with a normal page load:
def test_transaction_status_succeeds(self):
response = self.client.post(reverse('transaction_status'), { ... })
self.assertEqual(response.status_code, 403)
How, though, can I fake a successful submission? I've tried setting HTTP_REFERER in extra, e.g. self.client.post(..., extra={'HTTP_REFERER': 'http://foo/bar'}), but this isn't working; the view is apparently still seeing a blank header.
Does the test client even support custom headers? Is there a work-around if not? I'm using Django 1.1, and would prefer not to upgrade just yet if at all possible.
Almost right. It's actually:
def transaction_status_suceeds(self):
response = self.client.post(reverse('transaction_status'), {}, HTTP_REFERER='http://foo/bar')
I'd missed a ** (scatter operator / keyword argument unpacking operator / whatever) when reading the source of test/client.py; extra ends up being a dictionary of extra keyword arguments to the function itself.
You can pass HTTP headers to the constructor of Client:
from django.test import Client
from django.urls import reverse
client = Client(
HTTP_USER_AGENT='Mozilla/5.0',
HTTP_REFERER='http://www.google.com',
)
response1 = client.get(reverse('foo'))
response2 = client.get(reverse('bar'))
This way you don't need to pass headers every time you make a request.