Repository: jazzband/django-oauth-toolkit

Issue:
Return appropriate error messages when response from authentication server is not successful
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
If the introspection_token is not correct in the resource server, then a JSONDecoderError is raised when there is a token to be verified via oauth2_provider. This kind of logger error and exception would make the debugging procedure quite complicated because the user wouldn't know the cause of why there is a JSON_DecoderError.

Related method: [oauth2_validators.py line 307 - 311.
](https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/oauth2_validators.py#L307)

Example traceback in resource server when the authentication server returns a 403 response to the resource server.
```
Introspection: Failed to parse response as json
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\oauth2_provider\oauth2_validators.py", line 308, in _get_token_from_authentication_server
    content = response.json()
  File "C:\Python37\lib\site-packages\requests\models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "C:\Python37\lib\json\__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "C:\Python37\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Python37\lib\json\decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
If the response from authentication server is not successful(e.g. status code is not 200), then an exception should be logged with the response's status code and its cause.

E.g. If we add the following code block before [line 307](https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/oauth2_validators.py#L307):
```
        if response.status_code != 200:
            log.exception("Introspection: Failed to get a valid response "
                          "from authentication server. status code ={}, "
                          "reason {}".format(response.status_code, 
                                             response.reason))
```

Then, the traceback error message would be:
```
Introspection: Failed to get a valid response from authentication server. status code =403, reason Forbidden
Traceback (most recent call last):
  File "/home/jun/.local/share/virtualenvs/maio-engine-OAc_wQKR/lib/python3.8/site-packages/oauth2_provider/oauth2_validators.py", line 371, in validate_bearer_token
    access_token = AccessToken.objects.select_related("application", "user").get(token=token)
  File "/home/jun/.local/share/virtualenvs/maio-engine-OAc_wQKR/lib/python3.8/site-packages/django/db/models/query.py", line 406, in get
    raise self.model.DoesNotExist(
oauth2_provider.models.AccessToken.DoesNotExist: AccessToken matching query does not exist.
```

**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
Ideally, all connections with authentication server using RESOURCE_SERVER_AUTH_TOKEN should return the error messages and its status code back to resource server if the response is not successful.

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Response from authentication server:
`WARNING 2020-06-16 11:53:11,274 basehttp 18318 140462266509056 "POST /oauth2/introspect/ HTTP/1.1" 403 0`

Traceback image without verification:
![image](https://user-images.githubusercontent.com/33321001/84770924-632a5b00-afd0-11ea-8091-5965c56b77ad.png)


Return appropriate error messages when response from authentication server is not successful
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
If the introspection_token is not correct in the resource server, then a JSONDecoderError is raised when there is a token to be verified via oauth2_provider. This kind of logger error and exception would make the debugging procedure quite complicated because the user wouldn't know the cause of why there is a JSON_DecoderError.

Related method: [oauth2_validators.py line 307 - 311.
](https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/oauth2_validators.py#L307)

Example traceback in resource server when the authentication server returns a 403 response to the resource server.
```
Introspection: Failed to parse response as json
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\oauth2_provider\oauth2_validators.py", line 308, in _get_token_from_authentication_server
    content = response.json()
  File "C:\Python37\lib\site-packages\requests\models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "C:\Python37\lib\json\__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "C:\Python37\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Python37\lib\json\decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
If the response from authentication server is not successful(e.g. status code is not 200), then an exception should be logged with the response's status code and its cause.

E.g. If we add the following code block before [line 307](https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/oauth2_validators.py#L307):
```
        if response.status_code != 200:
            log.exception("Introspection: Failed to get a valid response "
                          "from authentication server. status code ={}, "
                          "reason {}".format(response.status_code, 
                                             response.reason))
```

Then, the traceback error message would be:
```
Introspection: Failed to get a valid response from authentication server. status code =403, reason Forbidden
Traceback (most recent call last):
  File "/home/jun/.local/share/virtualenvs/maio-engine-OAc_wQKR/lib/python3.8/site-packages/oauth2_provider/oauth2_validators.py", line 371, in validate_bearer_token
    access_token = AccessToken.objects.select_related("application", "user").get(token=token)
  File "/home/jun/.local/share/virtualenvs/maio-engine-OAc_wQKR/lib/python3.8/site-packages/django/db/models/query.py", line 406, in get
    raise self.model.DoesNotExist(
oauth2_provider.models.AccessToken.DoesNotExist: AccessToken matching query does not exist.
```

**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
Ideally, all connections with authentication server using RESOURCE_SERVER_AUTH_TOKEN should return the error messages and its status code back to resource server if the response is not successful.

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Response from authentication server:
`WARNING 2020-06-16 11:53:11,274 basehttp 18318 140462266509056 "POST /oauth2/introspect/ HTTP/1.1" 403 0`

Traceback image without verification:
![image](https://user-images.githubusercontent.com/33321001/84770924-632a5b00-afd0-11ea-8091-5965c56b77ad.png)


Return appropriate error messages when response from authentication server is not successful
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
If the introspection_token is not correct in the resource server, then a JSONDecoderError is raised when there is a token to be verified via oauth2_provider. This kind of logger error and exception would make the debugging procedure quite complicated because the user wouldn't know the cause of why there is a JSON_DecoderError.

Related method: [oauth2_validators.py line 307 - 311.
](https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/oauth2_validators.py#L307)

Example traceback in resource server when the authentication server returns a 403 response to the resource server.
```
Introspection: Failed to parse response as json
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\oauth2_provider\oauth2_validators.py", line 308, in _get_token_from_authentication_server
    content = response.json()
  File "C:\Python37\lib\site-packages\requests\models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "C:\Python37\lib\json\__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "C:\Python37\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Python37\lib\json\decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
If the response from authentication server is not successful(e.g. status code is not 200), then an exception should be logged with the response's status code and its cause.

E.g. If we add the following code block before [line 307](https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/oauth2_validators.py#L307):
```
        if response.status_code != 200:
            log.exception("Introspection: Failed to get a valid response "
                          "from authentication server. status code ={}, "
                          "reason {}".format(response.status_code, 
                                             response.reason))
```

Then, the traceback error message would be:
```
Introspection: Failed to get a valid response from authentication server. status code =403, reason Forbidden
Traceback (most recent call last):
  File "/home/jun/.local/share/virtualenvs/maio-engine-OAc_wQKR/lib/python3.8/site-packages/oauth2_provider/oauth2_validators.py", line 371, in validate_bearer_token
    access_token = AccessToken.objects.select_related("application", "user").get(token=token)
  File "/home/jun/.local/share/virtualenvs/maio-engine-OAc_wQKR/lib/python3.8/site-packages/django/db/models/query.py", line 406, in get
    raise self.model.DoesNotExist(
oauth2_provider.models.AccessToken.DoesNotExist: AccessToken matching query does not exist.
```

**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
Ideally, all connections with authentication server using RESOURCE_SERVER_AUTH_TOKEN should return the error messages and its status code back to resource server if the response is not successful.

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Response from authentication server:
`WARNING 2020-06-16 11:53:11,274 basehttp 18318 140462266509056 "POST /oauth2/introspect/ HTTP/1.1" 403 0`

Traceback image without verification:
![image](https://user-images.githubusercontent.com/33321001/84770924-632a5b00-afd0-11ea-8091-5965c56b77ad.png)




Discussion:
I am working on this issue to provide a PR.
I am working on this issue to provide a PR.
I am working on this issue to provide a PR.

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
@@ -1,5 +1,6 @@
 import base64
 import binascii
+import http.client
 import logging
 from collections import OrderedDict
 from datetime import datetime, timedelta
@@ -304,6 +305,14 @@ def _get_token_from_authentication_server(
             log.exception("Introspection: Failed POST to %r in token lookup", introspection_url)
             return None
 
+        # Log an exception when response from auth server is not successful
+        if response.status_code != http.client.OK:
+            log.exception("Introspection: Failed to get a valid response "
+                          "from authentication server. Status code: {}, "
+                          "Reason: {}.".format(response.status_code,
+                                               response.reason))
+            return None
+
         try:
             content = response.json()
         except ValueError:


Test patch:
diff --git a/tests/test_oauth2_validators.py b/tests/test_oauth2_validators.py
--- a/tests/test_oauth2_validators.py
+++ b/tests/test_oauth2_validators.py
@@ -2,7 +2,7 @@
 import datetime
 
 from django.contrib.auth import get_user_model
-from django.test import TransactionTestCase
+from django.test import TestCase, TransactionTestCase
 from django.utils import timezone
 from oauthlib.common import Request
 
@@ -392,3 +392,26 @@ def test_validate_bearer_token_adds_error_to_the_request_when_a_invalid_custom_t
         self.assertDictEqual(self.request.oauth2_error, {
             "error": "invalid_token",
         })
+
+
+class TestOAuth2ValidatorErrorResourceToken(TestCase):
+    """The following tests check logger information when response from oauth2
+    is unsuccessful.
+    """
+
+    def setUp(self):
+        self.token = "test_token"
+        self.introspection_url = "http://example.com/token/introspection/"
+        self.introspection_token = "test_introspection_token"
+        self.validator = OAuth2Validator()
+
+    def test_response_when_auth_server_response_return_404(self):
+        with self.assertLogs(logger="oauth2_provider") as mock_log:
+            self.validator._get_token_from_authentication_server(
+                self.token, self.introspection_url,
+                self.introspection_token, None)
+            self.assertIn("ERROR:oauth2_provider:Introspection: Failed to "
+                          "get a valid response from authentication server. "
+                          "Status code: 404, Reason: "
+                          "Not Found.\nNoneType: None",
+                          mock_log.output)