Using PUT method

376 views
Skip to first unread message

Dmitry Mugtasimov

unread,
Apr 29, 2014, 7:11:30 AM4/29/14
to django-res...@googlegroups.com
pip freeze
Django==1.6.2
Markdown==2.3.1
South==0.8.4
argparse==1.2.1
django-filter==0.7
django-guardian==1.1.1
django-oauth-plus==2.2.3
django-registration==1.0
djangorestframework==2.3.11
httplib2==0.8
ipdb==0.8
ipython==2.0.0
oauth2==1.5.211
psycopg2==2.5.2
wsgiref==0.1.2

Request:
PUT /api/v1/place/14/ BODY: name=NEW3&latitude=55.74659&longitude=37.626484

Response:
{"detail": "Invalid signature. Expected signature base string: PUT&http%3A%2F%2Fyavezu.com%3A8005%2Fapi%2Fv1%2Fplace%2F14%2F&oauth_consumer_key%3Daa68ec89fc944c60880f59e18dc6e982%26oauth_nonce%3D-4587244018477714645%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1398769361%26oauth_token%3D03cd9df066244aa2884a5508ff0d9fff%26oauth_version%3D1.0"}

As you can see PUT-parameters are not included in base string.

I digged a code a little and found two reasons for it.

1. PUT-parameters are not supported in oauth_provider

see line:
if request.method == "POST" and request.META.get('CONTENT_TYPE') == "application/x-www-form-urlencoded":

oauth_provider.utils
def get_oauth_request(request):
""" Converts a Django request object into an `oauth2.Request` object. """
# Django converts Authorization header in HTTP_AUTHORIZATION
# Warning: it doesn't happen in tests but it's useful, do not remove!
auth_header = {}
if 'Authorization' in request.META:
auth_header = {'Authorization': request.META['Authorization']}
elif 'HTTP_AUTHORIZATION' in request.META:
auth_header = {'Authorization': request.META['HTTP_AUTHORIZATION']}


# include POST parameters if content type is
# 'application/x-www-form-urlencoded' and request
# see: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
parameters = {}

if request.method == "POST" and request.META.get('CONTENT_TYPE') == "application/x-www-form-urlencoded":
parameters = dict((k, v.encode('utf-8')) for (k, v) in request.POST.iteritems())

absolute_uri = request.build_absolute_uri(request.path)

if "HTTP_X_FORWARDED_PROTO" in request.META:
scheme = request.META["HTTP_X_FORWARDED_PROTO"]
absolute_uri = urlunparse((scheme, ) + urlparse(absolute_uri)[1:])

return oauth.Request.from_request(request.method,
absolute_uri,
headers=auth_header,
parameters=parameters,
query_string=request.META.get('QUERY_STRING', '')
)

2. PUT-parameters are not supported in class django.core.handlers.wsgi.WSGIRequest and django.http.request.HttpRequest:

def _load_post_and_files(self):
"""Populate self._post and self._files if the content-type is a form type"""
if self.method != 'POST':
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
return
...


I wounder how it is possible for Django REST Framework to provide RESTful API over Django without support for PUT-parameters? What am I doing wrong?
POST-requests work just fine.

Dmitry Mugtasimov

unread,
Apr 29, 2014, 7:20:53 AM4/29/14
to django-res...@googlegroups.com

Tom Christie

unread,
Apr 29, 2014, 12:46:33 PM4/29/14
to django-res...@googlegroups.com
I'd suggest either:

1. Instead putting the authentication information in the Authorization header, which django-oauth-plus also correctly supports.
2. Submitting a pull request to django-oauth-plus adding support for including x-www-form-urlencoded oauth parameters in non-POST message bodies.

Either way the issue isn't that REST framework doesn't support parsing data in PUT requests - it does, and request.DATA will still be populated as normal.

Dmitry Mugtasimov

unread,
Apr 30, 2014, 7:23:15 AM4/30/14
to django-res...@googlegroups.com
Thank you for quick reply. For number 1 I did not really get the idea. My client properly generates signature and it works fine POST-requests. However on server side PUT-parameters are not included in base string, so OAuth-signature is different than that is generated on client (because it includes PUT-parameters). Point is that server side signature calculation is done before reading client side signature, so I think it does not matter how I pass authentication information. Please, clarify if I am getting it wrong.

As for 2 you are precisely right. My draft fix looks like this:

oauth_provider.utils

if request.META.get('CONTENT_TYPE', None) == "application/x-www-form-urlencoded":
if request.method == 'POST':
parameters = request.POST
else:
parameters = getattr(request, 'DATA', {})
parameters = dict((k, v.encode('utf-8')) for (k, v) in parameters.iteritems())

It works fine for Django + Django REST Framework + django-oauth-plus

But it won't work for original Django HttpRequest: https://docs.djangoproject.com/en/1.6/ref/request-response/#django.http.HttpRequest

Because it has not DATA attribute, only REQUEST attribute which is deprecated in Django 1.7: https://docs.djangoproject.com/en/1.7/ref/request-response/#django.http.HttpRequest.REQUEST

It is good idea to make a contribution to django-oauth-plus. Could share you experience about add support for PUT-requests for Django REST Framework?

1. In Django 1.6 I see this code:
django.core.handlers.wsgi

class WSGIRequest(http.HttpRequest):
...
GET = property(_get_get, _set_get)
POST = property(_get_post, _set_post)
COOKIES = property(_get_cookies, _set_cookies)
FILES = property(_get_files)
REQUEST = property(_get_request)

! No support for PUT-parameters

django.http.request

def _load_post_and_files(self):
"""Populate self._post and self._files if the content-type is a form type"""
if self.method != 'POST':
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
return

! No support for PUT-parameters

I also found this: https://groups.google.com/forum/#!msg/django-developers/dxI4qVzrBY4/m_9IiNk_p7UJ

Is it the reason you rolled out your own class Request(object)?

2. What you could be a patch for django-oauth-plus to support integration with both: pure Django and Django + Django REST Framework?

Dmitry Mugtasimov

unread,
Apr 30, 2014, 7:40:24 AM4/30/14
to django-res...@googlegroups.com

Dmitry Mugtasimov

unread,
Apr 30, 2014, 7:59:09 AM4/30/14
to django-res...@googlegroups.com
You are doing great job, Tom. Thank you!

Dmitry Mugtasimov

unread,
Apr 30, 2014, 9:07:09 AM4/30/14
to django-res...@googlegroups.com
Similar issue: http://code.larlet.fr/django-oauth-plus/issue/36/client-should-be-able-to-transmit-oauth

среда, 30 апреля 2014 г., 15:40:24 UTC+4 пользователь Dmitry Mugtasimov написал:

Dmitry Mugtasimov

unread,
Apr 30, 2014, 10:50:47 AM4/30/14
to django-res...@googlegroups.com
In case it may be useful for someone I post a diff of my solution:


--- a/oauth_provider/utils.py
+++ b/oauth_provider/utils.py
@@ -52,8 +52,13 @@ def get_oauth_request(request):

     # see: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
     parameters = {}
 
-    if request.method == "POST" and request.META.get('CONTENT_TYPE') == "application/x-www-form-urlencoded":
-        parameters = dict((k, v.encode('utf-8')) for (k, v) in request.POST.iteritems())
+    if request.META.get('CONTENT_TYPE', None) == "application/x-www-form-urlencoded":
+        if request.method == 'POST':
+            parameters = request.POST
+        else:
+            # TODO LOW: Only Django REST Framework is supported, support pure Django
+            parameters = getattr(request, 'DATA', {})
+        parameters = dict((k, v.encode('utf-8')) for (k, v) in parameters.iteritems())
 
     absolute_uri = request.build_absolute_uri(request.path)

вторник, 29 апреля 2014 г., 15:11:30 UTC+4 пользователь Dmitry Mugtasimov написал:

Tom Christie

unread,
Apr 30, 2014, 2:57:32 PM4/30/14
to django-res...@googlegroups.com
Probably worth posting against the django-oauth-plus thread you mentioned, linking to this thread.
Reply all
Reply to author
Forward
0 new messages