This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
from __future__ import unicode_literals
from allauth.socialaccount.providers.base import AuthAction, ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
class MicrosoftGraphAccount(ProviderAccount):
def get_avatar_url(self):
return self.account.extra_data.get("photo")
def to_str(self):
dflt = super(MicrosoftGraphAccount, self).to_str()
return self.account.extra_data.get("displayName", dflt)
class MicrosoftGraphProvider(OAuth2Provider):
id = str("microsoft")
name = "Microsoft Graph"
account_class = MicrosoftGraphAccount
def get_default_scope(self):
"""
Docs on Scopes and Permissions:
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#scopes-and-permissions
"""
return ["User.Read"]
def get_auth_params(self, request, action):
ret = super(MicrosoftGraphProvider, self).get_auth_params(request, action)
if action == AuthAction.REAUTHENTICATE:
ret["prompt"] = "select_account"
return ret
def extract_uid(self, data):
return str(data["id"])
def extract_common_fields(self, data):
return dict(
email=data.get("mail") or data.get("userPrincipalName"),
username=data.get("mailNickname"),
last_name=data.get("surname"),
first_name=data.get("givenName"),
)
provider_classes = [MicrosoftGraphProvider]

View File

@@ -0,0 +1,57 @@
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):
provider_id = MicrosoftGraphProvider.id
def get_mocked_response(self):
response_data = """
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"id": "16f5a7b6-5a15-4568-aa5a-31bb117e9967",
"businessPhones": [],
"displayName": "Anne Weiler",
"givenName": "Anne",
"jobTitle": "Manufacturing Lead",
"mail": "annew@CIE493742.onmicrosoft.com",
"mobilePhone": "+1 3528700812",
"officeLocation": null,
"preferredLanguage": "en-US",
"surname": "Weiler",
"userPrincipalName": "annew@CIE493742.onmicrosoft.com",
"mailNickname": "annew"
}
""" # 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)

View File

@@ -0,0 +1,6 @@
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from .provider import MicrosoftGraphProvider
urlpatterns = default_urlpatterns(MicrosoftGraphProvider)

View File

@@ -0,0 +1,88 @@
from __future__ import unicode_literals
import json
import requests
from allauth.core import context
from allauth.socialaccount import app_settings
from allauth.socialaccount.adapter import get_adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Error
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
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
def _build_tenant_url(self, path):
settings = app_settings.PROVIDERS.get(self.provider_id, {})
# Lower case "tenant" for backwards compatibility
tenant = settings.get("TENANT", settings.get("tenant", "common"))
# Prefer app based tenant setting.
app = get_adapter().get_app(context.request, provider=self.provider_id)
tenant = app.settings.get("tenant", tenant)
return f"https://login.microsoftonline.com/{tenant}{path}"
@property
def access_token_url(self):
return self._build_tenant_url("/oauth2/v2.0/token")
@property
def authorize_url(self):
return self._build_tenant_url("/oauth2/v2.0/authorize")
profile_url = "https://graph.microsoft.com/v1.0/me"
user_properties = (
"businessPhones",
"displayName",
"givenName",
"id",
"jobTitle",
"mail",
"mobilePhone",
"officeLocation",
"preferredLanguage",
"surname",
"userPrincipalName",
"mailNickname",
"companyName",
)
profile_url_params = {"$select": ",".join(user_properties)}
def complete_login(self, request, app, token, **kwargs):
headers = {"Authorization": "Bearer {0}".format(token.token)}
response = requests.get(
self.profile_url,
params=self.profile_url_params,
headers=headers,
)
extra_data = _check_errors(response)
return self.get_provider().sociallogin_from_response(request, extra_data)
oauth2_login = OAuth2LoginView.adapter_view(MicrosoftGraphOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(MicrosoftGraphOAuth2Adapter)