I use the getCookie function from the django documentation to get the csrfmiddlewaretoken value.
I have the following ajax call:
var url = reverse_removeprofile.replace(/deadbeef/, key);
$.ajax({
type: "DELETE",
url: url,
data: "csrfmiddlewaretoken=" + getCookie("csrftoken"),
success: function() { ... },
});
When this code gets executed then django raises a 403 exception telling me that the CSRF verification failed. However, if I change the type from DELETE to POST then django is happy about it and doesn't complain at all.
I was not really able to find something useful in Google about this, but I've found this (now closed and fixed) ticket: https://code.djangoproject.com/ticket/15258
If I understand it correctly then this issue has been fixed in the 1.4 milestone. I use django 1.4 but still I cannot verify the CSRF token with a DELETE request.
Am I missing something here?
This appears to be a jQuery bug, caused by some confusion as to whether DELETE data should be attached to the URL (like a GET request) or the request body (like a POST)
See this bug report.
You can probably get around this by using the alternative CSRF method for AJAX calls, setting an X-CSRFToken header on the request. Try changing your AJAX call to look like this:
$.ajax({
type: "DELETE",
url: url,
beforeSend: function(xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
},
success: function() { ... },
});
Please note, when it comes to DELETE requests DJango does not check for csrfmiddlewaretoken in the request body. Rather it looks for X-CSRFToken header
Coming to working of DJango CSRFMiddleware you can see the source code of django > middleware > csrf.py > CsrfViewMiddleware in which it is very clear that DJango does not scan for csrfmiddlewaretoken in request body if the request is of DELETE type:
# Check non-cookie token for match.
request_csrf_token = ""
if request.method == "POST":
try:
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
except OSError:
# Handle a broken connection before we've completed reading
# the POST data. process_view shouldn't raise any
# exceptions, so we'll ignore and serve the user a 403
# (assuming they're still listening, which they probably
# aren't because of the error).
pass
if request_csrf_token == "":
# Fall back to X-CSRFToken, to make things easier for AJAX,
# and possible for PUT/DELETE.
request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
Related
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)
I want get data in JSON format from odoo controllery.py
Example:
import openerp.http as http
from openerp.http import request
class MyController(http.Controller):
#http.route('/test_html', type="http", auth="public")
def some_html(self):
return "<h1>Test</h1>"
#Work fine when open http://localhost:8069/test.html
#http.route('/test_json', type="json", website=True, auth="public")
def some_json(self):
return [{"name": "Odoo", 'website': 'www.123.com'}]
How get data in json format, I want data from json read in other app with ajax.
Is it possible view json after open url http://localhost:8069/test_json ???
The important part is to define the contentType of your request properly.
import json
#http.route('/test_json', type="json", auth="public")
def some_json(self):
return json.dumps({"name": "Odoo", 'website': 'www.123.com'})
In your client using javascript you can request the json like this.
$.ajax({
type: "POST",
url: "/test_json",
async: false,
data: JSON.stringify({}),
contentType: "application/json",
complete: function (data) {
console.log(data);
}
});
Or using requests in python
import requests,json
res = requests.post("http://localhost:8069/test_json",data=json.dumps({}),headers={"Content-Type":"application/json"})
To access the response body
body = res.text
As to whether you can simply open a browser and view the json. No, not by default.
Here is what I get
Bad Request
<function some_json at 0x7f48386ceb90>, /test_json: Function declared as capable of handling request of type 'json' but called with a request of type 'http'
You could probably do something pretty fancy with a controller if you really wanted to be able to view it in a browser as well as make json requests. I would post a second question though.
Your controller endpoint looks ok and should function correctly, so I guess your main question is how to test it.
Once you declare that the endpoint type is json, Odoo will check that the request content type header is in fact JSON, so in order to test it your requests will need to have Content-Type: application/json header set. This is a bit difficult using a regular browser, unless you edit the request headers before seinding or call your JSON endpoint from JavaScript via Ajax.
Alternatively, you can test your API from command line using a tool like curl:
curl 'http://localhost:8069/test_json' -H 'Content-Type: application/json' --data "{}"
--data "{}" here indicates an empty JSON structure which will be passed to your endpoint as request parameters.
Please note that you might also have to pass an additional header containing your session_id cookie if you are using more than one Odoo database.
I have a very simple form that I've added the uploader to. When I invoke the uploader, django returns
{"detail":"CSRF Failed: CSRF token missing or incorrect."}
This is the uploader:
var ul = new Uploader(
{
label:"Programmed uploader",
multiple:false,
uploadOnSelect:true,
url:Environment.apiRoot + "upload/",
headers:{
"Accept" : "application/json",
"X-CSRFToken" : dojo.cookie("csrftoken")
}
}).placeAt(form);
I created simple "test" button that invokes a function that performs the same post.
new Button({
name:"Cancel2",
//id:"Cancel",
label:"Cancel" ,
placement:"secondary",
onClick:lang.hitch(this,function(event){
this._testpost()
})
}).placeAt(form);
This is the relavent header from the uploader post
Cookie djdt=hide; csrftoken=WwlARc9OUevblKfgNEDU2Ae4eT9z0kos;sessionid=du37rjyam6v69mw0bgctkbw708xlvc5g
This is the _testpost()
_testpost: function (){
xhr.post({
url: Environment.apiRoot + "upload/",
handleAs: "json",
postData: json.stringify(data),
headers: {
"Content-Type": "application/json",
"Accept" : "application/json",
"X-CSRFToken" : dojo.cookie("csrftoken")
},
loadingMessage: "Submitting form..."
}).then(
lang.hitch(this,function(result) {
form = t._f_form;
dojo.destroy(form);
this._float.destroyRecursive();
alert(result['result_text']);
result['message'] = "Update Request Accepted";
}),lang.hitch(this, function(err){
form = t._f_form;
dojo.destroy(form);
this._float.destroyRecursive();
topic.publish("/application/message","An error occurred.");
}));
this is the relevant header from invoking the _testpost function
Cookie djdt=hide; csrftoken=WwlARc9OUevblKfgNEDU2Ae4eT9z0kos;sessionid=du37rjyam6v69mw0bgctkbw708xlvc5g
X-CSRFToken WwlARc9OUevblKfgNEDU2Ae4eT9z0kos
The key difference being that in the _testpost the X-CSRFToken is put into the header, but on the Uploader post, I don't have any means to put in an X-CSRFToken (my headers attribute seems to just be ignored - i tried it to see if I could get this to work)
Is there any way to get additional headers into the Uploader
Unfortunately, dojox.form.Uploader does not allow headers to be added.
There are a couple options. It sounds like you have access to the csrf token and could append it to the url. Another option may be to provide the csrf token as a cookie and it should be sent with XHR and Flash request.
What I have done (and i'm not sure this is correct), within the django view, disabled csrf checking, and then pull the csrf value out of the header and compare it against the csrf value that is kept in the session record on the server.
you may use dojo.aspect to add the headers to the dojox.form.Uploader.
In case you are using HTML5 upload "plugin", that looks like since you have left the default, you may use something like:
aspect.after(ul, "createXhr", function(xhr) {
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("X-CSRFToken", dojo.cookie("csrftoken"));
return xhr;
});
Add this just after you create the Uploader. Also remember to require dojo/aspect.
Notice that this is a bit of a hack and prone to breakage if some change happen in dojox.form.Uploader structure (e.g. they update it to use dojo.promise or other fixes). Also it's implied that this works only for HTML5 plugin, but you may extend the code in the same way to cope for other plugins by inspecting ul.uploadType and make the change specific for that plugin.
This solution works up to and including dojo version 1.12. In 2017 the above announced breakage did happen an this does not work anymore with version of dojo from 1.13 and upward.
I'm trying to create a POST request using angular.js to this Django view.
class PostJSON4SlickGrid(View):
"""
REST POST Interface for SlickGrid to update workpackages
"""
def post(self, request, root_id, wp_id, **kwargs):
print "in PostJSON4SlickGrid"
print request.POST
return HttpResponse(status=200)
Therefore I created this resource.
myModule.factory('gridData', function($resource) {
//define resource class
var root = {{ root.pk }};
return $resource('{% url getJSON4SlickGrid root.pk %}:wpID/', {wpID:'#id'},{
get: {method:'GET', params:{}, isArray:true},
update:{method:'POST'}
});
});
Calling the get method in a controller works fine. The url gets translated to http://127.0.0.1:8000/pm/rest/tree/1/.
function gridController($scope, gridData){
gridData.get(function(result) {
console.log(result);
$scope.treeData = result;
//broadcast that asynchronous xhr call finished
$scope.$broadcast('mySignal', {fake: 'Hello!'});
});
}
While I m facing issues executing the update/POST method.
item.$update();
The URL gets translated to http://127.0.0.1:8000/pm/rest/tree/1/345, which is missing a trailing slash. This can be easily circumvented when not using a trailing slash in your URL definition.
url(r'^rest/tree/(?P<root_id>\d+)/(?P<wp_id>\d+)$', PostJSON4SlickGrid.as_view(), name='postJSON4SlickGrid'),
instead of
url(r'^rest/tree/(?P<root_id>\d+)/(?P<wp_id>\d+)/$', PostJSON4SlickGrid.as_view(), name='postJSON4SlickGrid'),
Using the workaround without the trailing slash I get now a 403 (Forbidden) status code, which is probably due to that I do not pass a CSRF token in the POST request. Therefore my question boils down to how I can pass the CSRF token into the POST request created by angular?
I know about this approach to pass the csrf token via the headers, but I m looking for a possibility to add the token to the body of the post request, as suggested here. Is it possible in angular to add data to the post request body?
As additional readings one can look at these discussions regarding resources, removed trailing slashes, and the limitations resources currently have: disc1 and disc2.
In one of the discussions one of the authors recommended to currently not use resources, but use this approach instead.
I know this is more than 1 year old, but if someone stumbles upon the same issue, angular JS already has a CSRF cookie fetching mechanism (versions of AngularJS starting at 1.1.5), and you just have to tell angular what is the name of the cookie that django uses, and also the HTTP header that it should use to communicate with the server.
Use module configuration for that:
var app = angular.module('yourApp');
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);
Now every request will have the correct django CSRF token. In my opinion this is much more correct than manually placing the token on every request, because it uses built-in systems from both frameworks (django and angularJS).
Can't you make a call like this:
$http({
method: 'POST',
url: url,
data: xsrf,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
The data can be whatever you wish to pass and then just append &{{csrf_token}} to that.
In your resource params:{}, try adding csrfmiddlewaretoken:{{csrf_token}} inside the params
Edit:
You can pass data to the request body as
item.$update({csrfmiddlewaretoken:{{csrf_token}}})
and to headers as
var csrf = '{{ csrf_token }}';
update:{method:'POST', headers: {'X-CSRFToken' : csrf }}
It is an undocumented issue
In recent angularjs version giving solution is not working . So i tried the following
First add django tag {% csrf_token %} in the markup.
Add a $http inspector in your app config file
angular.module('myApp').config(function ( $httpProvider) {
$httpProvider.interceptors.push('myHttpRequestInterceptor');
});
Then define that myHttpRequestInterceptor
angular.module("myApp").factory('myHttpRequestInterceptor', function ( ) {
return {
config.headers = {
'X-CSRFToken': $('input[name=csrfmiddlewaretoken]').val() }
}
return config;
}};
});
it'll add the X-CSRFToken in all angular request
And lastly you need to add the Django middleware " django.middleware.csrf.CsrfViewMiddleware'"
It'll solve the CSRF issue
var app = angular.module('angularFoo', ....
app.config(["$httpProvider", function(provider) {
provider.defaults.headers.common['X-CSRFToken'] = '<<csrftoken value from template or cookie>>';
}])
I use this:
In Django view:
#csrf_protect
def index(request):
#Set cstf-token cookie for rendered template
return render_to_response('index.html', RequestContext(request))
In App.js:
(function(A) {
"use strict";
A.module('DesktopApplication', 'ngCookies' ]).config(function($interpolateProvider, $resourceProvider) {
//I use {$ and $} as Angular directives
$interpolateProvider.startSymbol('{$');
$interpolateProvider.endSymbol('$}');
//Without this Django not processed urls without trailing slash
$resourceProvider.defaults.stripTrailingSlashes = false;
}).run(function($http, $cookies) {
//Set csrf-kookie for every request
$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
});
}(this.angular));
For sending correct request you must convert object to param-form:
$http.post('/items/add/', $.param({name: 'Foo'}));//Here $ is jQuery
I'm creating two POST calls. One using a django form and one using angular js via a resource xhr.
The angular setup looks like this:
myModule.factory('gridData', function($resource) {
//define resource class
var root = {{ root.pk }};
var csrf = '{{ csrf_token }}';
return $resource('{% url getJSON4SlickGrid root.pk %}:wpID/', {wpID:'#id'},{
get: {method:'GET', params:{}, isArray:true},
update:{method:'POST', headers: {'X-CSRFToken' : csrf }}
});
});
With creating an xhr post request as such:
item.$update();
This post request is send to the server as expected, but when I want to access the QueryDict I cannot access the data passed using:
name = request.POST.get('name', None)
name is always None like this.
The issue behind this is that the QueryDict object is getting parsed quite strange.
print request.POST
<QueryDict: {u'{"name":"name update","schedule":0"}':[u'']}>
Whereas I would have expected this result, which I got when I send the data via a "normal" Post request:
<QueryDict: {u'name': [u'name update'], u'schedule': [u'0']}>
So it seems to be that Django receives something in the POST request which instructs Django to parse the parameters into one string. Any idea how to circumvent this?
Update:
I found this discussion where they say that the issue is if you provide any content type other than MULTIPART_CONTENT the parameters will be parsed into one string. I checked the content-type send with the POST request and it is really set to 'CONTENT_TYPE': 'application/json;charset=UTF-8'. Thus this is likely the issue. Therefore my question is: How can I set the CONTENT_TYPE for a xhr post request created using angular.js resources to MULTIPART_CONTENT?
you could either:
fiddle with the client to send data instead of json
use json.loads(request.raw_post_data).get('name', None) (django < 1.4)
use json.loads(request.body).get('name', None) (django >= 1.4)
The Angular documentation talks about transforming requests and responses
To override these transformation locally, specify transform functions as transformRequest and/or transformResponse properties of the config object. To globally override the default transforms, override the $httpProvider.defaults.transformRequest and $httpProvider.defaults.transformResponse properties of the $httpProvider.
you can find an example here as was previously pointed at.