Repository: jazzband/django-oauth-toolkit

Issue:
AttributeError: get_full_path when running any rest_framework.test.APITestCase
**Describe the bug**
After upgrading from django-oauth-toolkit==1.2.0 to django-oauth-toolkit==1.3.0 every single test using `from rest_framework.test import APITestCase` and then calling `self.client.post` and the like fails with this stack trace:

```
Traceback (most recent call last):
  File "/src/app/.venv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/src/app/.venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/src/app/.venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/utils/decorators.py", line 45, in _wrapper
    return bound_method(*args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/generic/base.py", line 97, in dispatch
    return handler(request, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/utils/decorators.py", line 45, in _wrapper
    return bound_method(*args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/decorators/debug.py", line 76, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/views/base.py", line 260, in post
    url, headers, body, status = self.create_token_response(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/views/mixins.py", line 124, in create_token_response
    return core.create_token_response(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 145, in create_token_response
    headers, extra_credentials)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py", line 116, in wrapper
    return f(endpoint, uri, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py", line 119, in create_token_response
    request, self.default_token_type)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py", line 101, in create_token_response
    self.validate_token_request(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py", line 184, in validate_token_request
    request.password, request.client, request):
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py", line 611, in validate_user
    u = authenticate(request, username=username, password=password)
  File "/src/app/.venv/lib/python3.6/site-packages/django/contrib/auth/__init__.py", line 73, in authenticate
    user = backend.authenticate(request, **credentials)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/backends.py", line 17, in authenticate
    valid, r = OAuthLibCore.verify_request(request, scopes=[])
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 172, in verify_request
    uri, http_method, body, headers = self._extract_params(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 58, in _extract_params
    uri = self._get_escaped_full_path(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 34, in _get_escaped_full_path
    parsed = list(urlparse(request.get_full_path()))
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/common.py", line 436, in __getattr__
    raise AttributeError(name)
AttributeError: get_full_path
```

**To Reproduce**

Test case:

```
from django.conf import settings
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase


class OauthAuthenticationTest(APITestCase):
    def test_should_should_return_ok_when_valid_credentials_passed(self) -> None:
        username = "user"
        password = "pw"
        User.objects.create_user(username=username, email="test@example.com", password=password)
        response = self.client.post(
            "/api/oauth/token/",
            {
                "grant_type": "password",
                "username": username,
                "password": password,
                "client_id": settings.REACT_APP_OAUTH_CLIENT_ID,
            }
        )
        self.assertEqual(status.HTTP_200_OK, response.status_code)
```

**Expected behavior**
This test should pass, as it did with version 1.2.0.

**Version**
1.3.0

- [x] I have tested with the latest published release and it's still a problem.
- [ ] I have tested with the master branch and it's still a problem.

**Additional context**

Using django==2.2.11 and djangorestframework==3.11.0.
AttributeError: get_full_path when running any rest_framework.test.APITestCase
**Describe the bug**
After upgrading from django-oauth-toolkit==1.2.0 to django-oauth-toolkit==1.3.0 every single test using `from rest_framework.test import APITestCase` and then calling `self.client.post` and the like fails with this stack trace:

```
Traceback (most recent call last):
  File "/src/app/.venv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/src/app/.venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/src/app/.venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/utils/decorators.py", line 45, in _wrapper
    return bound_method(*args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/generic/base.py", line 97, in dispatch
    return handler(request, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/utils/decorators.py", line 45, in _wrapper
    return bound_method(*args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/django/views/decorators/debug.py", line 76, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/views/base.py", line 260, in post
    url, headers, body, status = self.create_token_response(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/views/mixins.py", line 124, in create_token_response
    return core.create_token_response(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 145, in create_token_response
    headers, extra_credentials)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py", line 116, in wrapper
    return f(endpoint, uri, *args, **kwargs)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py", line 119, in create_token_response
    request, self.default_token_type)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py", line 101, in create_token_response
    self.validate_token_request(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py", line 184, in validate_token_request
    request.password, request.client, request):
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py", line 611, in validate_user
    u = authenticate(request, username=username, password=password)
  File "/src/app/.venv/lib/python3.6/site-packages/django/contrib/auth/__init__.py", line 73, in authenticate
    user = backend.authenticate(request, **credentials)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/backends.py", line 17, in authenticate
    valid, r = OAuthLibCore.verify_request(request, scopes=[])
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 172, in verify_request
    uri, http_method, body, headers = self._extract_params(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 58, in _extract_params
    uri = self._get_escaped_full_path(request)
  File "/src/app/.venv/lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 34, in _get_escaped_full_path
    parsed = list(urlparse(request.get_full_path()))
  File "/src/app/.venv/lib/python3.6/site-packages/oauthlib/common.py", line 436, in __getattr__
    raise AttributeError(name)
AttributeError: get_full_path
```

**To Reproduce**

Test case:

```
from django.conf import settings
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase


class OauthAuthenticationTest(APITestCase):
    def test_should_should_return_ok_when_valid_credentials_passed(self) -> None:
        username = "user"
        password = "pw"
        User.objects.create_user(username=username, email="test@example.com", password=password)
        response = self.client.post(
            "/api/oauth/token/",
            {
                "grant_type": "password",
                "username": username,
                "password": password,
                "client_id": settings.REACT_APP_OAUTH_CLIENT_ID,
            }
        )
        self.assertEqual(status.HTTP_200_OK, response.status_code)
```

**Expected behavior**
This test should pass, as it did with version 1.2.0.

**Version**
1.3.0

- [x] I have tested with the latest published release and it's still a problem.
- [ ] I have tested with the master branch and it's still a problem.

**Additional context**

Using django==2.2.11 and djangorestframework==3.11.0.


Discussion:
Just setup a new project and run into the same "AttributeError: get_full_path" when trying to get a Token
I had a deeper look.

With some commited "fix", the request is being passed to the authenticate() inside oauth2_validators.validate_user().
Someone was not aware that the request is one of oauthlib.Request, not a Django Request.
Breaks everything

https://github.com/jazzband/django-oauth-toolkit/pull/643#issuecomment-555606170
@dms-cat a PR with that test case above would be greatly appreciated!
Just setup a new project and run into the same "AttributeError: get_full_path" when trying to get a Token
I had a deeper look.

With some commited "fix", the request is being passed to the authenticate() inside oauth2_validators.validate_user().
Someone was not aware that the request is one of oauthlib.Request, not a Django Request.
Breaks everything

https://github.com/jazzband/django-oauth-toolkit/pull/643#issuecomment-555606170
@dms-cat a PR with that test case above would be greatly appreciated!

Resolving patch:
diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py
--- a/oauth2_provider/oauth2_validators.py
+++ b/oauth2_provider/oauth2_validators.py
@@ -14,6 +14,7 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.db import transaction
 from django.db.models import Q
+from django.http import HttpRequest
 from django.utils import dateformat, timezone
 from django.utils.timezone import make_aware
 from django.utils.translation import gettext_lazy as _
@@ -664,7 +665,15 @@ def validate_user(self, username, password, client, request, *args, **kwargs):
         """
         Check username and password correspond to a valid and active User
         """
-        u = authenticate(username=username, password=password)
+        # Passing the optional HttpRequest adds compatibility for backends
+        # which depend on its presence. Create one with attributes likely
+        # to be used.
+        http_request = HttpRequest()
+        http_request.path = request.uri
+        http_request.method = request.http_method
+        getattr(http_request, request.http_method).update(dict(request.decoded_body))
+        http_request.META = request.headers
+        u = authenticate(http_request, username=username, password=password)
         if u is not None and u.is_active:
             request.user = u
             return True


Test patch: