Repository: pennersr/django-allauth

Issue:
Microsoft Provider: In extract_uid, handle response when "id" not found.
https://github.com/pennersr/django-allauth/blob/52978e2e5c7e1755f8b904aea65b3cf305d2c92c/allauth/socialaccount/providers/microsoft/provider.py#L28

Currently it raises a `KeyError` which results in an internal server error. It would be nice to handle that better.


Discussion:

Resolving patch:
diff --git a/allauth/socialaccount/providers/microsoft/views.py b/allauth/socialaccount/providers/microsoft/views.py
--- a/allauth/socialaccount/providers/microsoft/views.py
+++ b/allauth/socialaccount/providers/microsoft/views.py
@@ -1,7 +1,9 @@
 from __future__ import unicode_literals
 
+import json
 import requests
 
+from allauth.socialaccount.providers.oauth2.client import OAuth2Error
 from allauth.socialaccount.providers.oauth2.views import (
     OAuth2Adapter,
     OAuth2CallbackView,
@@ -11,6 +13,24 @@
 from .provider import MicrosoftGraphProvider
 
 
+def _check_errors(response):
+    try:
+        data = response.json()
+    except json.decoder.JSONDecodeError:
+        raise OAuth2Error(
+            "Invalid JSON from Microsoft Graph API: {}".format(response.text)
+        )
+
+    if "id" not in data:
+        error_message = "Error retrieving Microsoft profile"
+        microsoft_error_message = data.get("error", {}).get("message")
+        if microsoft_error_message:
+            error_message = ": ".join((error_message, microsoft_error_message))
+        raise OAuth2Error(error_message)
+
+    return data
+
+
 class MicrosoftGraphOAuth2Adapter(OAuth2Adapter):
     provider_id = MicrosoftGraphProvider.id
 
@@ -25,8 +45,8 @@ def __init__(self, request):
 
     def complete_login(self, request, app, token, **kwargs):
         headers = {"Authorization": "Bearer {0}".format(token.token)}
-        resp = requests.get(self.profile_url, headers=headers)
-        extra_data = resp.json()
+        response = requests.get(self.profile_url, headers=headers)
+        extra_data = _check_errors(response)
         return self.get_provider().sociallogin_from_response(request, extra_data)
 
 


Test patch:
diff --git a/allauth/socialaccount/providers/microsoft/tests.py b/allauth/socialaccount/providers/microsoft/tests.py
--- a/allauth/socialaccount/providers/microsoft/tests.py
+++ b/allauth/socialaccount/providers/microsoft/tests.py
@@ -1,7 +1,11 @@
+import json
+
+from allauth.socialaccount.providers.oauth2.client import OAuth2Error
 from allauth.socialaccount.tests import OAuth2TestsMixin
 from allauth.tests import MockedResponse, TestCase
 
 from .provider import MicrosoftGraphProvider
+from .views import _check_errors
 
 
 class MicrosoftGraphTests(OAuth2TestsMixin, TestCase):
@@ -25,3 +29,28 @@ def get_mocked_response(self):
         }
         """  # noqa
         return MockedResponse(200, response_data)
+
+    def test_invalid_data(self):
+        response = MockedResponse(200, json.dumps({}))
+        with self.assertRaises(OAuth2Error):
+            # No id, raises
+            _check_errors(response)
+
+    def test_profile_invalid_response(self):
+        data = {
+            "error": {
+                "code": "InvalidAuthenticationToken",
+                "message": "Access token validation failure. Invalid audience.",
+            }
+        }
+        response = MockedResponse(401, json.dumps(data))
+
+        with self.assertRaises(OAuth2Error):
+            # no id, 4xx code, raises with message
+            _check_errors(response)
+
+    def test_invalid_response(self):
+        response = MockedResponse(200, "invalid json data")
+        with self.assertRaises(OAuth2Error):
+            # bad json, raises
+            _check_errors(response)