Skip to content
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

JSONOAuthLibCore breaks multipart form-data request for django-rest-framework #296

Closed
sacgup opened this issue Sep 23, 2015 · 7 comments · May be fixed by #1382
Closed

JSONOAuthLibCore breaks multipart form-data request for django-rest-framework #296

sacgup opened this issue Sep 23, 2015 · 7 comments · May be fixed by #1382
Labels

Comments

@sacgup
Copy link

sacgup commented Sep 23, 2015

I used JSONOAuthLibCore to accept JSON data for getting tokens.
But after using it gives error for multipart form-data request calls to my api's which are made using django-rest-framework. I need to use multipart-form data to upload files only and json for all other requests.

I used below class for authentication in settings.py

'DEFAULT_AUTHENTICATION_CLASSES': (
    'oauth2_provider.ext.rest_framework.OAuth2Authentication',
),

Error given is "You cannot access body after reading from request's data stream".

On investigation i found django doesn't support to read from request body twice. Once it is read in JSONOAuthLibCore so when django reads data again in "django/http/request.py" it gives error at that time.

I used JSONOAuthLibCore so that my all api call to my view and oauth2 to get token are similar and accepts only json.

1.) Why oauth2 need to read body data only to authenticate request ? It should only read Authorization header and check the token to validate the user. It should read body data only when granting tokens.

2.) Any workaround for this problem ?

I can make my oauth2 call to send "x-www-form-urlencoded" data from client apps and json for rest of my views but i really don't want to do that.

PS: I am new to django-rest-framework, oauth2 and token authentication. So if i am missing something obvious please bear with me.

@masci
Copy link
Contributor

masci commented Sep 26, 2015

Hi @sacgup could you provide a minimum working example I can work with? I haven't found a simple workaround and need to investigate further.

@sacgup
Copy link
Author

sacgup commented Sep 26, 2015

Hi @masci

Just override the default oauth2 backend class from OauthLibCore to JSONOauthLibCore in settings.py.

OAUTH2_PROVIDER = {
    # this is the list of available scopes
    'SCOPES': {'read': 'Read scope', 'write': 'Write scope'},
    'OAUTH2_VALIDATOR_CLASS': 'external.validators.EmailOAuth2Validator', # Self over ride : To use email as username to get access tokens
    # 'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.JSONOAuthLibCore'
}

and use the authentication class from oauth2_provider in django rest framework.
Make a request to any api of rest framework in multipart form-data or x-www-form-urlencoded content type.

You will be able to see the error.

What i have found out till now:
JSONOauthLibCore reads the request body to get the json data and authenticate the request.
Then, django also reads the body later in it's http request class and gives error at that point of time because django allows to read request body only once. ( Don't know Why ? )

The calls to oauth2_provider to get access token will work. Only call to other rest Api's will fail.
IMO, Oauth2_provider shouldn't read request body to authenticate api calls. Only Authorization token should be read from header to authenticate the request.

@dulacp
Copy link
Contributor

dulacp commented Oct 19, 2015

I stubble upon the same issue today. You can't read the request body twice indeed.

A simple fix would be to force users to use the Authorization header, no ?
Or we could specify this behavior in the documentation ?

It that's a yes, I'm happy to make a pull request for this.

For now my work around is to override the OAUTH2_BACKEND_CLASS setting with a class that overrides the verify_request to not parse the body at this authentication stage.


class OAuthHeaderBasedAuthenticationCore(OAuthLibCore):

    def extract_body(self, request):
        """
        Explanation on why we override this method 

        NB: because the authentication stage doesn't need to read the request body
            indeed the authentication credentials are available in the `Authorization` header
            and recent versions of Django it can't be read twice.
            So without this custom OAuth2 core class multipart-form request are broken
        """
        return {}

OAUTH2_PROVIDER = {
    #...

    # NB: switch the oauth backend to avoid reading the body twice and breaking multipart-forms
    'OAUTH2_BACKEND_CLASS': 'apps.apiv1.oauth2_backends.OAuthHeaderBasedAuthenticationCore',
}

@pySilver
Copy link

@dulaccc simply use your own backend this way:

from rest_framework.request import Request
from oauth2_provider import oauth2_backends


class OAuthLibCore(oauth2_backends.OAuthLibCore):
    """Backend for Django Rest Framework"""

    def extract_body(self, request):
        """
        DRF object keeps data in request.DATA whether django request
        inside POST
        """
        if isinstance(request, Request):
            try:
                return request.DATA.items()

            # complex json request (list?) is not easily serializable
            except (ValueError, AttributeError):
                return ""

        return super(OAuthLibCore, self).extract_body(request)

@dulacp
Copy link
Contributor

dulacp commented Oct 19, 2015

Nice @pySilver that's a way better implementation, more general in the case handling.
Shouldn't it the default backend when using DRF ? it is not clear in the documentation and it is a very subtle bug.

@pySilver
Copy link

@dulaccc also, about built in integration with DRF be aware of:

  • Authentication backend is not checking whether user is active like DRF authenticators do, so it's better to subclass it or create pull here

@anurag-rudra
Copy link

Hi @dulacp,

I am facing the same issue that is "You cannot access body after reading from request's data stream" when I am trying to POST data to a Django REST API.
I have added 'oauth2_provider.contrib.rest_framework.OAuth2Authentication' in the "REST_FRAMEWORK" like so in the settings.py -

REST_FRAMEWORK = { ... 'DEFAULT_AUTHENTICATION_CLASSES': ( ... 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', ... ),

I have downloaded "django-oauth-toolkit" last week using the command:
pip3 install django-oauth-toolkit

Could you please help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants