From d1347e47120da719079f6078fe9aa5ee2fdb6933 Mon Sep 17 00:00:00 2001 From: Faisal Mahmood Date: Wed, 3 Aug 2022 08:32:06 +0100 Subject: [PATCH 1/5] Added ability to obtain groups from MS Graph when there are too many --- django_auth_adfs/backend.py | 138 ++++++++++++++++++++++++++++------ django_auth_adfs/config.py | 5 ++ tests/test_drf_integration.py | 31 +++++++- tests/utils.py | 19 ++++- 4 files changed, 169 insertions(+), 24 deletions(-) diff --git a/django_auth_adfs/backend.py b/django_auth_adfs/backend.py index bf769b57..10f92612 100644 --- a/django_auth_adfs/backend.py +++ b/django_auth_adfs/backend.py @@ -42,6 +42,79 @@ def exchange_auth_code(self, authorization_code, request): adfs_response = response.json() return adfs_response + def get_obo_access_token(self, access_token): + """ + Gets an On Behalf Of (OBO) access token, which is required to make queries against MS Graph + + Args: + access_token (str): Original authorization access token from the user + + Returns: + obo_access_token (str): OBO access token that can be used with the MS Graph API + """ + logger.debug("Getting OBO access token: %s", provider_config.token_endpoint) + data = { + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", + "client_id": settings.CLIENT_ID, + "client_secret": settings.CLIENT_SECRET, + "assertion": access_token, + "requested_token_use": "on_behalf_of", + } + if provider_config.token_endpoint.endswith("/v2.0/token"): + data["scope"] = 'GroupMember.Read.All' + else: + data["resource"] = 'https://graph.microsoft.com' + + response = provider_config.session.get(provider_config.token_endpoint, data=data, timeout=settings.TIMEOUT) + # 200 = valid token received + # 400 = 'something' is wrong in our request + if response.status_code == 400: + logger.error("ADFS server returned an error: %s", response.json()["error_description"]) + raise PermissionDenied + + if response.status_code != 200: + logger.error("Unexpected ADFS response: %s", response.content.decode()) + raise PermissionDenied + + obo_access_token = response.json()["access_token"] + logger.debug("Received OBO access token: %s", obo_access_token) + return obo_access_token + + def get_group_memberships_from_ms_graph(self, obo_access_token): + """ + Looks up a users group membership from the MS Graph API + + Args: + obo_access_token (str): Access token obtained from the OBO authorization endpoint + + Returns: + claim_groups (list): List of the users group memberships + """ + graph_url = "https://{}/v1.0/me/transitiveMemberOf/microsoft.graph.group".format( + provider_config.msgraph_endpoint + ) + headers = {"Authorization": "Bearer {}".format(obo_access_token)} + response = provider_config.session.get(graph_url, headers=headers, timeout=settings.TIMEOUT) + # 200 = valid token received + # 400 = 'something' is wrong in our request + if response.status_code in [400, 401]: + logger.error("MS Graph server returned an error: %s", response.json()["message"]) + raise PermissionDenied + + if response.status_code != 200: + logger.error("Unexpected MS Graph response: %s", response.content.decode()) + raise PermissionDenied + + claim_groups = [] + for group_data in response.json()["value"]: + if group_data["displayName"] is None: + logger.error("The application does not have the required permission to read user groups from MS Graph") + raise PermissionDenied + + claim_groups.append(group_data["displayName"]) + return claim_groups + + def validate_access_token(self, access_token): for idx, key in enumerate(provider_config.signing_keys): try: @@ -100,10 +173,11 @@ def process_access_token(self, access_token, adfs_response=None): if not claims: raise PermissionDenied + groups = self.process_user_groups(claims, access_token) user = self.create_user(claims) self.update_user_attributes(user, claims) - self.update_user_groups(user, claims) - self.update_user_flags(user, claims) + self.update_user_groups(user, groups) + self.update_user_flags(user, claims, groups) signals.post_authenticate.send( sender=self, @@ -116,6 +190,41 @@ def process_access_token(self, access_token, adfs_response=None): user.save() return user + def process_user_groups(self, claims, access_token): + """ + Checks the user groups are in the claim or pulls them from MS Graph if + applicable + + Args: + claims (dict): claims from the access token + access_token (str): Used to make an OBO authentication request if + groups must be obtained from Microsoft Graph + + Returns: + groups (list): Groups the user is a member of, taken from the access token or MS Graph + """ + groups = [] + if settings.GROUPS_CLAIM is None: + logger.debug("No group claim has been configured") + return groups + + if settings.GROUPS_CLAIM in claims: + groups = claims[settings.GROUPS_CLAIM] + if not isinstance(groups, list): + groups = [groups, ] + elif ( + settings.TENANT_ID != "adfs" + and "_claim_names" in claims + and settings.GROUPS_CLAIM in claims["_claim_names"] + ): + obo_access_token = self.get_obo_access_token(access_token) + groups = self.get_group_memberships_from_ms_graph(obo_access_token) + else: + logger.debug("The configured groups claim %s was not found in the access token", + settings.GROUPS_CLAIM) + + return groups + def create_user(self, claims): """ Create the user if it doesn't exist yet @@ -201,26 +310,18 @@ def update_user_attributes(self, user, claims, claim_mapping=None): msg = "Model '{}' has no field named '{}'. Check ADFS claims mapping." raise ImproperlyConfigured(msg.format(user._meta.model_name, field)) - def update_user_groups(self, user, claims): + def update_user_groups(self, user, claim_groups): """ Updates user group memberships based on the GROUPS_CLAIM setting. Args: user (django.contrib.auth.models.User): User model instance - claims (dict): Claims from the access token + claim_groups (list): User groups from the access token / MS Graph """ if settings.GROUPS_CLAIM is not None: # Update the user's group memberships django_groups = [group.name for group in user.groups.all()] - if settings.GROUPS_CLAIM in claims: - claim_groups = claims[settings.GROUPS_CLAIM] - if not isinstance(claim_groups, list): - claim_groups = [claim_groups, ] - else: - logger.debug("The configured groups claim '%s' was not found in the access token", - settings.GROUPS_CLAIM) - claim_groups = [] if sorted(claim_groups) != sorted(django_groups): existing_groups = list(Group.objects.filter(name__in=claim_groups).iterator()) existing_group_names = frozenset(group.name for group in existing_groups) @@ -241,29 +342,22 @@ def update_user_groups(self, user, claims): pass user.groups.set(existing_groups + new_groups) - def update_user_flags(self, user, claims): + def update_user_flags(self, user, claims, claim_groups): """ Updates user boolean attributes based on the BOOLEAN_CLAIM_MAPPING setting. Args: user (django.contrib.auth.models.User): User model instance claims (dict): Claims from the access token + claim_groups (list): User groups from the access token / MS Graph """ if settings.GROUPS_CLAIM is not None: - if settings.GROUPS_CLAIM in claims: - access_token_groups = claims[settings.GROUPS_CLAIM] - if not isinstance(access_token_groups, list): - access_token_groups = [access_token_groups, ] - else: - logger.debug("The configured group claim was not found in the access token") - access_token_groups = [] - for flag, group in settings.GROUP_TO_FLAG_MAPPING.items(): if hasattr(user, flag): if not isinstance(group, list): group = [group] - if any(group_list_item in access_token_groups for group_list_item in group): + if any(group_list_item in claim_groups for group_list_item in group): value = True else: value = False diff --git a/django_auth_adfs/config.py b/django_auth_adfs/config.py index a1434d1b..ed9fdbd4 100644 --- a/django_auth_adfs/config.py +++ b/django_auth_adfs/config.py @@ -180,6 +180,7 @@ def __init__(self): self.token_endpoint = None self.end_session_endpoint = None self.issuer = None + self.msgraph_endpoint = None allowed_methods = frozenset([ 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST' @@ -229,6 +230,7 @@ def load_config(self): logger.info("token endpoint: %s", self.token_endpoint) logger.info("end session endpoint: %s", self.end_session_endpoint) logger.info("issuer: %s", self.issuer) + logger.info("msgraph endpoint: %s", self.msgraph_endpoint) def _load_openid_config(self): if settings.VERSION != 'v1.0': @@ -262,8 +264,10 @@ def _load_openid_config(self): self.end_session_endpoint = openid_cfg["end_session_endpoint"] if settings.TENANT_ID != 'adfs': self.issuer = openid_cfg["issuer"] + self.msgraph_endpoint = openid_cfg["msgraph_host"] else: self.issuer = openid_cfg["access_token_issuer"] + self.msgraph_endpoint = "graph.microsoft.com" except KeyError: raise ConfigLoadError return True @@ -299,6 +303,7 @@ def _load_federation_metadata(self): self.authorization_endpoint = base_url + "/oauth2/authorize" self.token_endpoint = base_url + "/oauth2/token" self.end_session_endpoint = base_url + "/ls/?wa=wsignout1.0" + self.msgraph_endpoint = "graph.microsoft.com" return True def _load_keys(self, certificates): diff --git a/tests/test_drf_integration.py b/tests/test_drf_integration.py index 70488d95..a3256bcc 100644 --- a/tests/test_drf_integration.py +++ b/tests/test_drf_integration.py @@ -6,11 +6,13 @@ from rest_framework import exceptions from rest_framework.exceptions import AuthenticationFailed +from django.contrib.auth.models import Group from django_auth_adfs.config import ProviderConfig, Settings from django_auth_adfs.rest_framework import AdfsAccessTokenAuthentication from .utils import build_access_token_adfs, build_access_token_azure, build_access_token_azure_guest, \ build_access_token_azure_guest_no_upn, build_access_token_azure_not_guest, \ - build_access_token_azure_guest_with_idp, mock_adfs + build_access_token_azure_guest_with_idp, build_access_token_azure_groups_in_claim_source, \ + mock_adfs class RestFrameworkIntegrationTests(TestCase): @@ -35,6 +37,13 @@ def setUp(self): azure_response_guest = build_access_token_azure_guest_with_idp(RequestFactory().get('/'))[2] self.access_token_azure_guest_with_idp = json.loads(azure_response_guest)['access_token'] + azure_response = build_access_token_azure_groups_in_claim_source(RequestFactory().get('/'))[2] + self.access_token_azure_groups_in_claim_source = json.loads(azure_response)['access_token'] + + Group.objects.create(name='group1') + Group.objects.create(name='group2') + Group.objects.create(name='group3') + @mock_adfs("2012") def test_access_token_2012(self): access_token_header = "Bearer {}".format(self.access_token_adfs) @@ -153,6 +162,26 @@ def test_access_token_azure_guest_but_no_upn_but_no_guest_username_claim(self): with self.assertRaises(exceptions.AuthenticationFailed): self.drf_auth_class.authenticate(request) + @mock_adfs("azure") + def test_process_group_claim_from_ms_graph(self): + access_token_header = "Bearer {}".format(self.access_token_azure_groups_in_claim_source) + request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header) + + 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" + with patch("django_auth_adfs.config.django_settings", settings): + with patch('django_auth_adfs.backend.settings', Settings()): + with patch("django_auth_adfs.config.settings", Settings()): + with patch("django_auth_adfs.backend.provider_config", ProviderConfig()): + with patch("django_auth_adfs.backend.AdfsBaseBackend.get_obo_access_token", return_value="123456"): + with patch("django_auth_adfs.backend.AdfsBaseBackend.get_group_memberships_from_ms_graph", return_value=["group1", "group2"]): + user, _ = self.drf_auth_class.authenticate(request) + self.assertEqual(user.username, "testuser") + self.assertEqual(user.groups.all()[0].name, "group1") + self.assertEqual(user.groups.all()[1].name, "group2") + @mock_adfs("2012") def test_access_token_exceptions(self): access_token_header = "Bearer non-existing-token" diff --git a/tests/utils.py b/tests/utils.py index 4cd182cf..2d7b4733 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -93,12 +93,17 @@ def build_access_token_azure_guest_with_idp(request): return do_build_access_token(request, issuer, schema='dummy_tenant_id', no_upn=True, idp="guest_idp") +def build_access_token_azure_groups_in_claim_source(request): + issuer = "https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/" + return do_build_access_token(request, issuer, groups_in_claim_names=True) + + def do_build_mfa_error(request): response = {'error_description': 'AADSTS50076'} return 400, [], json.dumps(response) -def do_build_access_token(request, issuer, schema=None, no_upn=False, idp=None): +def do_build_access_token(request, issuer, schema=None, no_upn=False, idp=None, groups_in_claim_names=False): issued_at = int(time.time()) expires = issued_at + 3600 auth_time = datetime.utcnow() @@ -130,6 +135,18 @@ def do_build_access_token(request, issuer, schema=None, no_upn=False, idp=None): claims['groups'] = claims['group'] if no_upn: del claims['upn'] + if groups_in_claim_names: + if 'groups' in claims: + del claims['groups'] + del claims['group'] + claims['_claim_names'] = { + "groups": "src1", + } + claims['_claim_sources'] = { + "src1": { + "endpoint": "https://graph.windows.net/01234567-89ab-cdef-0123-456789abcdef/users/23456789-01bc-defg-1234-56789bcdefg/getMemberObjects", + } + } token = jwt.encode(claims, signing_key_b, algorithm="RS256") response = { 'resource': 'django_website.adfs.relying_party_id', From 0e067f120cd4edb61e80ad79e62b87ab4a697c26 Mon Sep 17 00:00:00 2001 From: Faisal Mahmood Date: Wed, 3 Aug 2022 09:04:48 +0100 Subject: [PATCH 2/5] Resolve flake8 --- django_auth_adfs/backend.py | 1 - tests/test_drf_integration.py | 10 ++++++++-- tests/utils.py | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/django_auth_adfs/backend.py b/django_auth_adfs/backend.py index 10f92612..c95333a1 100644 --- a/django_auth_adfs/backend.py +++ b/django_auth_adfs/backend.py @@ -114,7 +114,6 @@ def get_group_memberships_from_ms_graph(self, obo_access_token): claim_groups.append(group_data["displayName"]) return claim_groups - def validate_access_token(self, access_token): for idx, key in enumerate(provider_config.signing_keys): try: diff --git a/tests/test_drf_integration.py b/tests/test_drf_integration.py index a3256bcc..38d8f300 100644 --- a/tests/test_drf_integration.py +++ b/tests/test_drf_integration.py @@ -175,8 +175,14 @@ def test_process_group_claim_from_ms_graph(self): with patch('django_auth_adfs.backend.settings', Settings()): with patch("django_auth_adfs.config.settings", Settings()): with patch("django_auth_adfs.backend.provider_config", ProviderConfig()): - with patch("django_auth_adfs.backend.AdfsBaseBackend.get_obo_access_token", return_value="123456"): - with patch("django_auth_adfs.backend.AdfsBaseBackend.get_group_memberships_from_ms_graph", return_value=["group1", "group2"]): + with patch( + "django_auth_adfs.backend.AdfsBaseBackend.get_obo_access_token", + return_value="123456" + ): + with patch( + "django_auth_adfs.backend.AdfsBaseBackend.get_group_memberships_from_ms_graph", + return_value=["group1", "group2"] + ): user, _ = self.drf_auth_class.authenticate(request) self.assertEqual(user.username, "testuser") self.assertEqual(user.groups.all()[0].name, "group1") diff --git a/tests/utils.py b/tests/utils.py index 2d7b4733..9c86ae31 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -144,7 +144,10 @@ def do_build_access_token(request, issuer, schema=None, no_upn=False, idp=None, } claims['_claim_sources'] = { "src1": { - "endpoint": "https://graph.windows.net/01234567-89ab-cdef-0123-456789abcdef/users/23456789-01bc-defg-1234-56789bcdefg/getMemberObjects", + "endpoint": ( + "https://graph.windows.net/01234567-89ab-cdef-0123-456789abcdef" + "/users/23456789-01bc-defg-1234-56789bcdefg/getMemberObjects" + ), } } token = jwt.encode(claims, signing_key_b, algorithm="RS256") From 1c7c89e8310595aef35083149e7b77bfe49f1fdf Mon Sep 17 00:00:00 2001 From: Faisal Mahmood Date: Thu, 4 Aug 2022 15:37:34 +0100 Subject: [PATCH 3/5] Bump version number --- django_auth_adfs/__init__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django_auth_adfs/__init__.py b/django_auth_adfs/__init__.py index faa47daf..b56e1a69 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.10.0' +__version__ = '1.10.1' diff --git a/pyproject.toml b/pyproject.toml index d425e941..523f7042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = 'django-auth-adfs' -version = "1.10.0" # Remember to also change __init__.py version +version = "1.10.1" # 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 '] From 922258fb3e132a071addcaf1ad41ac6d28d3ddd4 Mon Sep 17 00:00:00 2001 From: Faisal Mahmood Date: Thu, 4 Aug 2022 15:38:27 +0100 Subject: [PATCH 4/5] Update instructions and log regarding GroupMember.Read.All permission --- django_auth_adfs/backend.py | 5 ++++- docs/_static/AzureAD/20_add-permission-3.png | Bin 0 -> 80996 bytes docs/azure_ad_config_guide.rst | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 docs/_static/AzureAD/20_add-permission-3.png diff --git a/django_auth_adfs/backend.py b/django_auth_adfs/backend.py index c95333a1..3f7c660e 100644 --- a/django_auth_adfs/backend.py +++ b/django_auth_adfs/backend.py @@ -108,7 +108,10 @@ def get_group_memberships_from_ms_graph(self, obo_access_token): claim_groups = [] for group_data in response.json()["value"]: if group_data["displayName"] is None: - logger.error("The application does not have the required permission to read user groups from MS Graph") + logger.error( + "The application does not have the required permission to read user groups from " + "MS Graph (GroupMember.Read.All)" + ) raise PermissionDenied claim_groups.append(group_data["displayName"]) diff --git a/docs/_static/AzureAD/20_add-permission-3.png b/docs/_static/AzureAD/20_add-permission-3.png new file mode 100644 index 0000000000000000000000000000000000000000..b819eceffd3e9b17cf95ae23869fc989fb372745 GIT binary patch literal 80996 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

Hu{5=t)Zle1c?rsi&dU72|C; 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 Date: Thu, 18 Aug 2022 23:26:07 +0100 Subject: [PATCH 5/5] Added more unit tests --- .../azure-openid-configuration-v2.json | 62 ++++++ tests/test_authentication.py | 11 + tests/test_drf_integration.py | 69 ++++-- tests/utils.py | 204 +++++++++++++++++- 4 files changed, 326 insertions(+), 20 deletions(-) create mode 100644 tests/mock_files/azure-openid-configuration-v2.json diff --git a/tests/mock_files/azure-openid-configuration-v2.json b/tests/mock_files/azure-openid-configuration-v2.json new file mode 100644 index 00000000..533fdee9 --- /dev/null +++ b/tests/mock_files/azure-openid-configuration-v2.json @@ -0,0 +1,62 @@ +{ + "authorization_endpoint": "https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/authorize", + "token_endpoint": "https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/token", + "token_endpoint_auth_methods_supported": [ + "client_secret_post", + "private_key_jwt", + "client_secret_basic" + ], + "jwks_uri": "https://login.microsoftonline.com/common/discovery/keys", + "response_modes_supported": [ + "query", + "fragment", + "form_post" + ], + "subject_types_supported": [ + "pairwise" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ], + "http_logout_supported": true, + "frontchannel_logout_supported": true, + "end_session_endpoint": "https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/logout", + "response_types_supported": [ + "code", + "id_token", + "code id_token", + "token id_token", + "token" + ], + "scopes_supported": [ + "openid" + ], + "issuer": "https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/", + "claims_supported": [ + "sub", + "iss", + "cloud_instance_name", + "cloud_instance_host_name", + "cloud_graph_host_name", + "msgraph_host", + "aud", + "exp", + "iat", + "auth_time", + "acr", + "amr", + "nonce", + "email", + "given_name", + "family_name", + "nickname" + ], + "microsoft_multi_refresh_token": true, + "check_session_iframe": "https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/checksession", + "userinfo_endpoint": "https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/openid/userinfo", + "tenant_region_scope": "EU", + "cloud_instance_name": "microsoftonline.com", + "cloud_graph_host_name": "graph.windows.net", + "msgraph_host": "graph.microsoft.com", + "rbac_url": "https://pas.windows.net" + } \ No newline at end of file diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 0303d07b..9f38cbed 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -164,6 +164,17 @@ def test_group_claim(self): self.assertEqual(user.email, "john.doe@example.com") self.assertEqual(len(user.groups.all()), 0) + @mock_adfs("2016") + def test_no_group_claim(self): + backend = AdfsAuthCodeBackend() + with patch("django_auth_adfs.backend.settings.GROUPS_CLAIM", None): + user = backend.authenticate(self.request, authorization_code="dummycode") + self.assertIsInstance(user, User) + self.assertEqual(user.first_name, "John") + self.assertEqual(user.last_name, "Doe") + self.assertEqual(user.email, "john.doe@example.com") + self.assertEqual(len(user.groups.all()), 0) + @mock_adfs("2016", empty_keys=True) def test_empty_keys(self): backend = AdfsAuthCodeBackend() diff --git a/tests/test_drf_integration.py b/tests/test_drf_integration.py index 38d8f300..0a53e6c8 100644 --- a/tests/test_drf_integration.py +++ b/tests/test_drf_integration.py @@ -162,7 +162,7 @@ def test_access_token_azure_guest_but_no_upn_but_no_guest_username_claim(self): with self.assertRaises(exceptions.AuthenticationFailed): self.drf_auth_class.authenticate(request) - @mock_adfs("azure") + @mock_adfs("azure", requires_obo=True) def test_process_group_claim_from_ms_graph(self): access_token_header = "Bearer {}".format(self.access_token_azure_groups_in_claim_source) request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header) @@ -175,18 +175,61 @@ def test_process_group_claim_from_ms_graph(self): with patch('django_auth_adfs.backend.settings', Settings()): with patch("django_auth_adfs.config.settings", Settings()): with patch("django_auth_adfs.backend.provider_config", ProviderConfig()): - with patch( - "django_auth_adfs.backend.AdfsBaseBackend.get_obo_access_token", - return_value="123456" - ): - with patch( - "django_auth_adfs.backend.AdfsBaseBackend.get_group_memberships_from_ms_graph", - return_value=["group1", "group2"] - ): - user, _ = self.drf_auth_class.authenticate(request) - self.assertEqual(user.username, "testuser") - self.assertEqual(user.groups.all()[0].name, "group1") - self.assertEqual(user.groups.all()[1].name, "group2") + user, _ = self.drf_auth_class.authenticate(request) + self.assertEqual(user.username, "testuser") + self.assertEqual(user.groups.all()[0].name, "group1") + self.assertEqual(user.groups.all()[1].name, "group2") + + @mock_adfs("azure", requires_obo=True, mfa_error=True) + def test_get_obo_access_token_mfa_error(self): + access_token_header = "Bearer {}".format(self.access_token_azure_groups_in_claim_source) + request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header) + + 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" + with patch("django_auth_adfs.config.django_settings", settings): + with patch('django_auth_adfs.backend.settings', Settings()): + with patch("django_auth_adfs.config.settings", Settings()): + with patch("django_auth_adfs.backend.provider_config", ProviderConfig()): + with self.assertRaises(AuthenticationFailed): + self.drf_auth_class.authenticate(request) + + @mock_adfs("azure", requires_obo=True, version='v2.0') + def test_get_obo_access_token_version_2(self): + access_token_header = "Bearer {}".format(self.access_token_azure_groups_in_claim_source) + request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header) + + 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' + with patch("django_auth_adfs.config.django_settings", settings): + with patch('django_auth_adfs.backend.settings', Settings()): + with patch("django_auth_adfs.config.settings", Settings()): + with patch("django_auth_adfs.backend.provider_config", ProviderConfig()): + user, _ = self.drf_auth_class.authenticate(request) + self.assertEqual(user.username, "testuser") + self.assertEqual(user.groups.all()[0].name, "group1") + self.assertEqual(user.groups.all()[1].name, "group2") + + @mock_adfs("azure", requires_obo=True, missing_graph_group_perm=True) + def test_missing_ms_graph_group_permission(self): + access_token_header = "Bearer {}".format(self.access_token_azure_groups_in_claim_source) + request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header) + + 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" + with patch("django_auth_adfs.config.django_settings", settings): + with patch('django_auth_adfs.backend.settings', Settings()): + with patch("django_auth_adfs.config.settings", Settings()): + with patch("django_auth_adfs.backend.provider_config", ProviderConfig()): + with self.assertRaises(AuthenticationFailed): + self.drf_auth_class.authenticate(request) @mock_adfs("2012") def test_access_token_exceptions(self): diff --git a/tests/utils.py b/tests/utils.py index 9c86ae31..f6040d27 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -103,6 +103,14 @@ def do_build_mfa_error(request): return 400, [], json.dumps(response) +def do_build_graph_response(request): + return do_build_ms_graph_groups(request) + + +def do_build_graph_response_no_group_perm(request): + return do_build_ms_graph_groups(request, missing_group_names=True) + + def do_build_access_token(request, issuer, schema=None, no_upn=False, idp=None, groups_in_claim_names=False): issued_at = int(time.time()) expires = issued_at + 3600 @@ -163,6 +171,146 @@ def do_build_access_token(request, issuer, schema=None, no_upn=False, idp=None, return 200, [], json.dumps(response) +def do_build_obo_access_token(request): + obo_token = { + "aud": "https://graph.microsoft.com", + "iss": "https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/", + "iat": 1660851337, + "nbf": 1660851337, + "exp": 1660856510, + "acct": 0, + "acr": "1", + "aio": ( + "AUQAu/8TBCDAcvfLrjwjR53Uci8V5KCONDvJXGEFM/gMeVSp6/LV338RTspRjxIhbmNLcAGa80KVXXglM7+ea1uqRKkRNCa9bQ==" + ), + "amr": [ + "wia", + "mfa" + ], + "app_displayname": "AppName", + "appid": "2345a5bc-123a-0a1b-0a12-a12345b6cd7e", + "appidacr": "1", + "family_name": "Doe", + "given_name": "John", + "idtyp": "user", + "ipaddr": "1.2.3.4", + "name": "Doe, John (Expert)", + "oid": "2345a5bc-123a-0a1b-0a12-a12345b6cd7e", + "onprem_sid": "S-1-5-21-456123456-1364589140-123456543-563809", + "platf": "5", + "puid": "10030000AD9D1530", + "rh": "0.AS8A1AA4aCjPK0uCpKTt25xSNwMAAAAAAAAAwAAAAAAAAAAvAEQ.", + "scp": "email GroupMember.Read.All openid profile User.Read", + "signin_state": [ + "inknownntwk" + ], + "sub": "PZBipRglYn2dgemAP_qDM3QzF1nosfdylWx8hsEwzYA", + "tenant_region_scope": "EU", + "tid": "01234567-89ab-cdef-0123-456789abcdef", + "unique_name": "john.doe@example.com", + "upn": "john.doe@example.com", + "uti": "D8NUc9MAwkutG-iBUnsBAA", + "ver": "1.0", + "wids": [ + "2345a5bc-123a-0a1b-0a12-a12345b6cd7e", + ], + "xms_tcdt": 1467198948 + } + token = jwt.encode(obo_token, signing_key_b, algorithm="RS256") + response = { + 'token_type': 'bearer', + 'scope': 'email GroupMember.Read.All openid profile User.Read', + 'expires_in': '4872', + 'ext_expires_in': '4872', + 'expires_on': '1660856510', + 'not_before': '1660851337', + 'resource': 'https://graph.microsoft.com', + 'refresh_token': 'not_used', + 'access_token': token.decode() if isinstance(token, bytes) else token # PyJWT>=2 returns a str instead of bytes + } + return 200, [], json.dumps(response) + + +def do_build_ms_graph_groups(request, missing_group_names=False): + response = { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups", + "value": [ + { + "id": "12ab345c-6abc-427f-85ca-93fc0cc7f00d", + "deletedDateTime": None, + "classification": None, + "createdDateTime": "2020-11-02T13:06:02Z", + "creationOptions": [], + "description": None, + "displayName": "group1", + "expirationDateTime": None, + "groupTypes": [], + "isAssignableToRole": None, + "mail": None, + "mailEnabled": False, + "mailNickname": "group1", + "membershipRule": None, + "membershipRuleProcessingState": None, + "onPremisesDomainName": "example.com", + "onPremisesLastSyncDateTime": "2022-08-18T19:32:43Z", + "onPremisesNetBiosName": "COMPANY", + "onPremisesSamAccountName": "group1", + "onPremisesSecurityIdentifier": "S-1-5-21-1234567891-1234567891-1234567891-123456", + "onPremisesSyncEnabled": True, + "preferredDataLocation": None, + "preferredLanguage": None, + "proxyAddresses": [], + "renewedDateTime": "2020-11-02T13:06:02Z", + "resourceBehaviorOptions": [], + "resourceProvisioningOptions": [], + "securityEnabled": False, + "securityIdentifier": "S-1-12-1-1234567891-1234567891-1234567891-1234567891", + "theme": None, + "visibility": None, + "onPremisesProvisioningErrors": [] + }, + { + "id": "23ab456c-7abc-427f-85ca-93fc0cc7f00d", + "deletedDateTime": None, + "classification": None, + "createdDateTime": "2020-11-02T13:06:02Z", + "creationOptions": [], + "description": None, + "displayName": "group2", + "expirationDateTime": None, + "groupTypes": [], + "isAssignableToRole": None, + "mail": None, + "mailEnabled": False, + "mailNickname": "group2", + "membershipRule": None, + "membershipRuleProcessingState": None, + "onPremisesDomainName": "example.com", + "onPremisesLastSyncDateTime": "2022-08-18T19:32:43Z", + "onPremisesNetBiosName": "COMPANY", + "onPremisesSamAccountName": "group2", + "onPremisesSecurityIdentifier": "S-1-5-21-1234567891-1234567891-1234567891-123456", + "onPremisesSyncEnabled": True, + "preferredDataLocation": None, + "preferredLanguage": None, + "proxyAddresses": [], + "renewedDateTime": "2020-11-02T13:06:02Z", + "resourceBehaviorOptions": [], + "resourceProvisioningOptions": [], + "securityEnabled": False, + "securityIdentifier": "S-1-12-1-1234567891-1234567891-1234567891-1234567891", + "theme": None, + "visibility": None, + "onPremisesProvisioningErrors": [] + }, + ] + } + if missing_group_names: + for group in response["value"]: + group["displayName"] = None + return 200, [], json.dumps(response) + + def build_openid_keys(request, empty_keys=False): if empty_keys: keys = {"keys": []} @@ -200,7 +348,15 @@ def build_adfs_meta(request): return 200, [], data -def mock_adfs(adfs_version, empty_keys=False, mfa_error=False, guest=False, version=None): +def mock_adfs( + adfs_version, + empty_keys=False, + mfa_error=False, + guest=False, + version=None, + requires_obo=False, + missing_graph_group_perm=False, +): if adfs_version not in ["2012", "2016", "azure"]: raise NotImplementedError("This version of ADFS is not implemented") @@ -212,13 +368,16 @@ def wrapper(*original_args, **original_kwargs): "azure": "https://login.microsoftonline.com", } prefix = prefix_table[adfs_version] - if version: + ms_graph_endpoint = "https://graph.microsoft.com/" + if version == "v2.0": openid_cfg = re.compile(prefix + r".*{}/\.well-known/openid-configuration".format(version)) + token_endpoint = re.compile(prefix + r".*/oauth2/{}/token".format(version)) else: openid_cfg = re.compile(prefix + r".*\.well-known/openid-configuration") + token_endpoint = re.compile(prefix + r".*/oauth2/token") openid_keys = re.compile(prefix + r".*/discovery/keys") adfs_meta = re.compile(prefix + r".*/FederationMetadata/2007-06/FederationMetadata\.xml") - token_endpoint = re.compile(prefix + r".*/oauth2/token") + ms_graph_groups = re.compile(ms_graph_endpoint + r".*/transitiveMemberOf/microsoft.graph.group") with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: # https://github.com/getsentry/responses if adfs_version == "2016": @@ -232,10 +391,16 @@ def wrapper(*original_args, **original_kwargs): content_type='application/json', ) elif adfs_version == "azure": - rsps.add( - rsps.GET, openid_cfg, - json=load_json("mock_files/azure-openid-configuration.json") - ) + if version == "v2.0": + rsps.add( + rsps.GET, openid_cfg, + json=load_json("mock_files/azure-openid-configuration-v2.json") + ) + else: + rsps.add( + rsps.GET, openid_cfg, + json=load_json("mock_files/azure-openid-configuration.json") + ) rsps.add_callback( rsps.GET, openid_keys, callback=partial(build_openid_keys, empty_keys=empty_keys), @@ -268,6 +433,31 @@ def wrapper(*original_args, **original_kwargs): callback=build_access_token_azure, content_type='application/json', ) + if requires_obo: + if mfa_error: + rsps.add_callback( + rsps.GET, token_endpoint, + callback=do_build_mfa_error, + content_type='application/json', + ) + else: + rsps.add_callback( + rsps.GET, token_endpoint, + callback=do_build_obo_access_token, + content_type='application/json' + ) + if missing_graph_group_perm: + rsps.add_callback( + rsps.GET, ms_graph_groups, + callback=do_build_graph_response_no_group_perm, + content_type='application/json', + ) + else: + rsps.add_callback( + rsps.GET, ms_graph_groups, + callback=do_build_graph_response, + content_type='application/json', + ) else: if mfa_error: rsps.add_callback(