From b31e0f61ce4d8660b697f454cd4b057e2f669b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Kr=C3=BCger=20Svensson?= Date: Thu, 30 Mar 2023 13:36:32 +0200 Subject: [PATCH 1/3] fix: do not hardcode scopes for azure AD v2 --- django_auth_adfs/__init__.py | 2 +- django_auth_adfs/config.py | 10 +++++++++- pyproject.toml | 2 +- tests/test_authentication.py | 27 +++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/django_auth_adfs/__init__.py b/django_auth_adfs/__init__.py index 0f3115b..fabe05d 100644 --- a/django_auth_adfs/__init__.py +++ b/django_auth_adfs/__init__.py @@ -4,4 +4,4 @@ Adding imports here will break setup.py """ -__version__ = '1.11.4' +__version__ = '1.11.5' diff --git a/django_auth_adfs/config.py b/django_auth_adfs/config.py index 12c36dc..ce843fd 100644 --- a/django_auth_adfs/config.py +++ b/django_auth_adfs/config.py @@ -78,6 +78,7 @@ def __init__(self): self.PROXIES = None self.VERSION = 'v1.0' + self.SCOPES = [] required_settings = [ "AUDIENCE", @@ -138,6 +139,10 @@ def __init__(self): elif "VERSION" in _settings: raise ImproperlyConfigured("The VERSION cannot be set when TENANT_ID is not set.") + if self.VERSION == "v2.0" and not self.SCOPES and self.RELYING_PARTY_ID: + warnings.warn('Use `SCOPES` for AzureAD instead of RELYING_PARTY_ID', DeprecationWarning) + if not isinstance(self.SCOPES, list): + raise ImproperlyConfigured("Scopes must be a list") # Overwrite defaults with user settings for setting, value in _settings.items(): if hasattr(self, setting): @@ -346,7 +351,10 @@ def build_authorization_endpoint(self, request, disable_sso=None, force_mfa=Fals }) if self._mode == "openid_connect": if settings.VERSION == 'v2.0': - query["scope"] = f"openid api://{settings.RELYING_PARTY_ID}/.default" + if settings.SCOPES: + query['scope'] = " ".join(settings.SCOPES) + else: + query["scope"] = f"openid api://{settings.RELYING_PARTY_ID}/.default" query.pop("resource") else: query["scope"] = "openid" diff --git a/pyproject.toml b/pyproject.toml index dbe143b..5baad15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = 'django-auth-adfs' -version = "1.11.4" # Remember to also change __init__.py version +version = "1.11.5" # Remember to also change __init__.py version description = 'A Django authentication backend for Microsoft ADFS and AzureAD' authors = ['Joris Beckers '] maintainers = ['Jonas Krüger Svensson ', 'Sondre Lillebø Gundersen '] diff --git a/tests/test_authentication.py b/tests/test_authentication.py index c16691f..7ee302d 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -476,6 +476,33 @@ def test_oauth_redir_azure_version_two(self): self.assertEqual(redir.path.rstrip("/"), '/01234567-89ab-cdef-0123-456789abcdef/oauth2/authorize') self.assertEqual(qs, sq_expected) + @mock_adfs("azure") + def test_scopes_generated_correctly(self): + from django_auth_adfs.config import django_settings + settings = deepcopy(django_settings) + del settings.AUTH_ADFS["SERVER"] + settings.AUTH_ADFS["TENANT_ID"] = "dummy_tenant_id" + settings.AUTH_ADFS["VERSION"] = 'v2.0' + settings.AUTH_ADFS["SCOPES"] = ['openid api://your-configured-client-id/user_impersonation'] + with patch("django_auth_adfs.config.django_settings", settings), \ + patch("django_auth_adfs.config.settings", Settings()), \ + patch("django_auth_adfs.views.provider_config", ProviderConfig()): + response = self.client.get("/oauth2/login?next=/test/") + self.assertEqual(response.status_code, 302) + redir = urlparse(response["Location"]) + qs = parse_qs(redir.query) + sq_expected = { + 'scope': ['openid api://your-configured-client-id/user_impersonation'], + 'client_id': ['your-configured-client-id'], + 'state': ['L3Rlc3Qv'], + 'response_type': ['code'], + 'redirect_uri': ['http://testserver/oauth2/callback'] + } + self.assertEqual(redir.scheme, 'https') + self.assertEqual(redir.hostname, 'login.microsoftonline.com') + self.assertEqual(redir.path.rstrip("/"), '/01234567-89ab-cdef-0123-456789abcdef/oauth2/authorize') + self.assertEqual(qs, sq_expected) + @mock_adfs("2016") def test_inactive_user(self): user = User.objects.create(**{ From 77aedee79a4432a959ba937e4eac680ed99c7ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Kr=C3=BCger=20Svensson?= Date: Thu, 30 Mar 2023 13:55:25 +0200 Subject: [PATCH 2/3] docs: add docs for scopes --- docs/settings_ref.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/settings_ref.rst b/docs/settings_ref.rst index f16d215..8d1c011 100644 --- a/docs/settings_ref.rst +++ b/docs/settings_ref.rst @@ -117,7 +117,7 @@ The dictionary can also map extra details to the Django user account using an `Extension of the User model `_ Set a dictionary as value in the CLAIM_MAPPING setting with as key the name User model. You will need to make sure the related field exists before the user authenticates. -This can be done by creating a receiver on the +This can be done by creating a receiver on the `post_save `_ signal that creates the related instance when the ``User`` instance is created. @@ -125,9 +125,9 @@ example .. code-block:: python - 'CLAIM_MAPPING': {'first_name': 'given_name', - 'last_name': 'family_name', - 'email': 'upn', + 'CLAIM_MAPPING': {'first_name': 'given_name', + 'last_name': 'family_name', + 'email': 'upn', 'userprofile': { 'employee_id': 'employeeid' }} @@ -369,6 +369,16 @@ RETRIES The number of time a request to the ADFS server is retried. It allows, in combination with :ref:`timeout_setting` to fine tune the behaviour of the connection to ADFS. + +SCOPES +------ +* **Default**: ``[]`` +* **Type**: ``list`` + +**Only used when you have v2 AzureAD config** + + + SERVER ------ * **Default**: From 1f2dc70602c80869f679271773b9ee85dbe9951c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Kr=C3=BCger=20Svensson?= Date: Thu, 30 Mar 2023 16:10:00 +0200 Subject: [PATCH 3/3] tests: fix test --- tests/test_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 7ee302d..9843441 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -483,7 +483,7 @@ def test_scopes_generated_correctly(self): del settings.AUTH_ADFS["SERVER"] settings.AUTH_ADFS["TENANT_ID"] = "dummy_tenant_id" settings.AUTH_ADFS["VERSION"] = 'v2.0' - settings.AUTH_ADFS["SCOPES"] = ['openid api://your-configured-client-id/user_impersonation'] + settings.AUTH_ADFS["SCOPES"] = ['openid', 'api://your-configured-client-id/user_impersonation'] with patch("django_auth_adfs.config.django_settings", settings), \ patch("django_auth_adfs.config.settings", Settings()), \ patch("django_auth_adfs.views.provider_config", ProviderConfig()):