RealmTeam / django-rest-framework-social-oauth2

python-social-auth and oauth2 support for django-rest-framework
MIT License
1.06k stars 190 forks source link

AttributeError: can't set attribute when trying to use ConvertTokenView.as_view() in middleware #58

Closed JoabMendes closed 8 years ago

JoabMendes commented 8 years ago

I'm trying to make a middleware where my final user don't need to send all the parameters unless the facebook token for ConvertTokenView:

My middleware view looks like this:

from rest_framework_social_oauth2.views import ConvertTokenView

#[.....]

    def post(self, request,  *args, **kwargs):
        #Edit request.data to call the ConvertTokenView with the needed parameters
        request.data.__setitem__("grant_type", "convert_token")
        request.data.__setitem__("backend", "facebook")
        request.data.__setitem__('client_id', Application.objects.last().client_id)
        request.data.__setitem__('client_secret', Application.objects.last().client_secret)
        request.data.__setitem__("token", request.data.get('user_access_token'))
        request.data.__setitem__("isteacher", 0)
        request._request.POST._mutable = True #Force to be mutable
        view = ConvertTokenView.as_view()
        return Response(view(request,  *args, **kwargs))

And I am getting this error:

framework_social_oauth2/views.py", line 37, in post
    request._request.POST = request._request.POST.copy()

Even when I force the request._request.POST to be mutable, it still cannot be set when I call the view.

I know I am not using the library in a trivial way. However, I don't wan't to make my clients to store all those parameters that I can get on my app.

Is there a way I can make it work without having to change the library?

PhilipGarnero commented 8 years ago

Instead of a middleware, you could try to do the same thing using the pipeline of python social auth. You should manage to achieve the same results.

JoabMendes commented 8 years ago

Well, I will try. I'm not using the Django middlware framework. It's more like a function that handles my first endpoint with one single token on the playload's post and call the /auth/convert-token endpoint.

JoabMendes commented 8 years ago

So, I believe (after I tested) the pipeline commands are called after the call of the /auth/convert-token what makes the parameters to be missing.

{'error_description': 'Missing backend parameter.', 'error': 'invalid_request'}

I even put the function that edits the request at the first place on the pipeline flow, thinking it could help, but it doesn't.

SOCIAL_AUTH_PIPELINE = (
        'user_auth.pipelines.edit_post_request'
        'social.pipeline.social_auth.social_details',
        '...'
     )

pipelines.py

def edit_post_request(request, *args, **kwargs):
    if request.method == 'POST':
        request.data.__setitem__("grant_type", "convert_token")
        request.data.__setitem__("backend", "facebook")
        request.data.__setitem__('client_id', Application.objects.last().client_id)
        request.data.__setitem__('client_secret', Application.objects.last().client_secret)
        request.data.__setitem__("token", request.get('token'))
        request.data.__setitem__("isteacher", request.get('isteacher'))
    return request

Is there another way I could do it or am I doing it wrong?

PhilipGarnero commented 8 years ago

Sorry, I misunderstood your initial intent. I thought you wanted to modify the request to add/remove informations for the client. You just want a shortcut it seems. I would suggest you inherit from ConvertTokenView instead of using it as you are currently doing. The request you are trying to modify will be a rest framework Request object. data is not an attribute but a property. If you want to modify it, you will have to modify _full_data. Access data first in order to populate _full_data before hand. _full_data will be an instance of QueryDict from django.http.

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
        return self._full_data
JoabMendes commented 8 years ago

I followed your suggestion and in the class of my post function I inherited from ConvertTokenView. Then I overwrite the post function like this:

class SelfCreateUserAPI(ConvertTokenView):
    def post(self, request, *args, **kwargs):

        # Use the rest framework `.data` to fake the post body of the django request.
        request._request.POST = request._request.POST.copy()
        #Editing my request function
        request._request.POST["grant_type"] = "convert_token"
        request._request.POST["backend"] = "facebook"
        request._request.POST["client_id"] = Application.objects.last().client_id
        request._request.POST["client_secret"] = Application.objects.last().client_secret
        request._request.POST["token"] = request.data.get('user_access_token') #The only thing send from my client.
        request._request.POST["isteacher"] = 0

        url, headers, body, status = self.create_token_response(request._request)
        response = Response(data=json.loads(body), status=status)

        for k, v in headers.items():
            response[k] = v
        return response

And It's working fine now. Thank you for your help again, @PhilipGarnero :rocket: