From 4923f1cef0fdb5decf850cbb58e1b615f3277f33 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 28 Oct 2020 16:23:46 -0700 Subject: [PATCH 1/4] user credentials pass claims challenges to MSAL --- .../azure-identity/azure/identity/_credentials/browser.py | 6 ++++-- .../azure/identity/_credentials/device_code.py | 7 +++++-- .../azure/identity/_credentials/shared_cache.py | 6 ++++-- .../azure/identity/_credentials/user_password.py | 5 ++++- .../azure-identity/azure/identity/_internal/interactive.py | 6 +++++- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/browser.py b/sdk/identity/azure-identity/azure/identity/_credentials/browser.py index 433baba44b1d..b5386f8d8aeb 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/browser.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/browser.py @@ -97,7 +97,7 @@ def _request_token(self, *scopes, **kwargs): request_state = str(uuid.uuid4()) app = self._get_app() auth_url = app.get_authorization_request_url( - scopes, redirect_uri=redirect_uri, state=request_state, prompt="select_account", **kwargs + scopes, redirect_uri=redirect_uri, state=request_state, prompt="select_account" ) # open browser to that url @@ -113,7 +113,9 @@ def _request_token(self, *scopes, **kwargs): # redeem the authorization code for a token code = self._parse_response(request_state, response) - return app.acquire_token_by_authorization_code(code, scopes=scopes, redirect_uri=redirect_uri, **kwargs) + return app.acquire_token_by_authorization_code( + code, scopes=scopes, redirect_uri=redirect_uri, claims_challenge=kwargs.get("claims_challenge") + ) @staticmethod def _parse_response(request_state, response): diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py b/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py index 48b1d896dab3..af9db0ba139c 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py @@ -84,13 +84,16 @@ def _request_token(self, *scopes, **kwargs): else: print(flow["message"]) + claims_challenge = kwargs.get("claims_challenge") if self._timeout is not None and self._timeout < flow["expires_in"]: # user specified an effective timeout we will observe deadline = int(time.time()) + self._timeout - result = app.acquire_token_by_device_flow(flow, exit_condition=lambda flow: time.time() > deadline) + result = app.acquire_token_by_device_flow( + flow, exit_condition=lambda flow: time.time() > deadline, claims_challenge=claims_challenge + ) else: # MSAL will stop polling when the device code expires - result = app.acquire_token_by_device_flow(flow) + result = app.acquire_token_by_device_flow(flow, claims_challenge=claims_challenge) if "access_token" not in result: if result.get("error") == "authorization_pending": diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py b/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py index bd6d9ef30f72..21e7ee209910 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py @@ -87,7 +87,7 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument raise CredentialUnavailableError(message="Shared token cache unavailable") if self._auth_record: - return self._acquire_token_silent(*scopes) + return self._acquire_token_silent(*scopes, **kwargs) account = self._get_account(self._username, self._tenant_id) @@ -146,7 +146,9 @@ def _acquire_token_silent(self, *scopes, **kwargs): continue now = int(time.time()) - result = self._app.acquire_token_silent_with_error(list(scopes), account=account, **kwargs) + result = self._app.acquire_token_silent_with_error( + list(scopes), account=account, claims_challenge=kwargs.get("claims_challenge") + ) if result and "access_token" in result and "expires_in" in result: return AccessToken(result["access_token"], now + int(result["expires_in"])) diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py b/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py index 5c381a8f90bb..c45f8abe2db0 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py @@ -56,5 +56,8 @@ def _request_token(self, *scopes, **kwargs): # type: (*str, **Any) -> dict app = self._get_app() return app.acquire_token_by_username_password( - username=self._username, password=self._password, scopes=list(scopes) + username=self._username, + password=self._password, + scopes=list(scopes), + claims_challenge=kwargs.get("claims_challenge"), ) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py index 333a884e80e4..12562be30bfa 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py @@ -103,6 +103,8 @@ def get_token(self, *scopes, **kwargs): This method is called automatically by Azure SDK clients. :param str scopes: desired scopes for the access token. This method requires at least one scope. + :keyword str claims_challenge: a claims challenge returned by a resource provider following an authorization + failure :rtype: :class:`azure.core.credentials.AccessToken` :raises CredentialUnavailableError: the credential is unable to attempt authentication because it lacks required data, state, or platform support @@ -187,7 +189,9 @@ def _acquire_token_silent(self, *scopes, **kwargs): continue now = int(time.time()) - result = app.acquire_token_silent_with_error(list(scopes), account=account, **kwargs) + result = app.acquire_token_silent_with_error( + list(scopes), account=account, claims_challenge=kwargs.get("claims_challenge") + ) if result and "access_token" in result and "expires_in" in result: return AccessToken(result["access_token"], now + int(result["expires_in"])) From 888d16f918e012c8dd762302f7e47ccd7703a005 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 20 Jan 2021 14:42:34 -0800 Subject: [PATCH 2/4] client_capabilities for public client credentials --- .../azure/identity/_credentials/shared_cache.py | 1 + .../azure-identity/azure/identity/_internal/interactive.py | 2 +- .../azure/identity/_internal/msal_credentials.py | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py b/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py index 21e7ee209910..edeaa6a06752 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py @@ -121,6 +121,7 @@ def _initialize(self): authority="https://{}/{}".format(self._auth_record.authority, self._tenant_id), token_cache=self._cache, http_client=MsalClient(**self._client_kwargs), + client_capabilities=["CP1"] ) self._initialized = True diff --git a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py index 12562be30bfa..8bf71f4f57c3 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py @@ -204,7 +204,7 @@ def _acquire_token_silent(self, *scopes, **kwargs): def _get_app(self): # type: () -> msal.PublicClientApplication if not self._msal_app: - self._msal_app = self._create_app(msal.PublicClientApplication) + self._msal_app = self._create_app(msal.PublicClientApplication, client_capabilities=["CP1"]) return self._msal_app @abc.abstractmethod diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index bb8437453031..b924c050d9ed 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -57,14 +57,15 @@ def _get_app(self): # type: () -> msal.ClientApplication pass - def _create_app(self, cls): - # type: (Type[msal.ClientApplication]) -> msal.ClientApplication + def _create_app(self, cls, **kwargs): + # type: (Type[msal.ClientApplication], **Any) -> msal.ClientApplication app = cls( client_id=self._client_id, client_credential=self._client_credential, authority="{}/{}".format(self._authority, self._tenant_id), token_cache=self._cache, http_client=self._client, + **kwargs ) return app From fc3311b4f719036e58c4efddceba25094568a40e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 28 Oct 2020 16:24:33 -0700 Subject: [PATCH 3/4] azure-identity tests --- .../tests/recording_processors.py | 77 ++ .../recordings/test_cae.test_device_code.yaml | 865 ++++++++++++++++++ .../test_cae.test_username_password.yaml | 559 +++++++++++ .../tests/test_browser_credential.py | 38 + sdk/identity/azure-identity/tests/test_cae.py | 139 +++ .../tests/test_device_code_credential.py | 49 +- .../tests/test_shared_cache_credential.py | 38 + .../test_username_password_credential.py | 44 + 8 files changed, 1807 insertions(+), 2 deletions(-) create mode 100644 sdk/identity/azure-identity/tests/recording_processors.py create mode 100644 sdk/identity/azure-identity/tests/recordings/test_cae.test_device_code.yaml create mode 100644 sdk/identity/azure-identity/tests/recordings/test_cae.test_username_password.yaml create mode 100644 sdk/identity/azure-identity/tests/test_cae.py diff --git a/sdk/identity/azure-identity/tests/recording_processors.py b/sdk/identity/azure-identity/tests/recording_processors.py new file mode 100644 index 000000000000..957eb1d7d7fd --- /dev/null +++ b/sdk/identity/azure-identity/tests/recording_processors.py @@ -0,0 +1,77 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import base64 +import binascii +import hashlib +import json +import re +import time + +from azure_devtools.scenario_tests import RecordingProcessor +import six + + +SECRETS = frozenset({ + "access_token", + "client_secret", + "code", + "device_code", + "message", + "password", + "refresh_token", + "user_code", +}) + + +class RecordingRedactor(RecordingProcessor): + """Removes authentication secrets from recordings""" + + def process_request(self, request): + # don't record the body because it probably contains secrets and is formed by msal anyway, + # i.e. it isn't this library's responsibility + request.body = None + return request + + def process_response(self, response): + try: + body = json.loads(response["body"]["string"]) + except (KeyError, ValueError): + return response + + for field in body: + if field in SECRETS: + # record a hash of the secret instead of a simple replacement like "redacted" + # because some tests (e.g. for CAE) require unique, consistent values + digest = hashlib.sha256(six.ensure_binary(body[field])).digest() + body[field] = six.ensure_str(binascii.hexlify(digest)) + + response["body"]["string"] = json.dumps(body) + return response + + +class IdTokenProcessor(RecordingProcessor): + def process_response(self, response): + """Changes the "exp" claim of recorded id tokens to be in the future during playback + + This is necessary because msal always validates id tokens, raising an exception when they've expired. + """ + try: + # decode the recorded token + body = json.loads(six.ensure_str(response["body"]["string"])) + header, encoded_payload, signed = body["id_token"].split(".") + decoded_payload = base64.b64decode(encoded_payload + "=" * (4 - len(encoded_payload) % 4)) + + # set the token's expiry time to one hour from now + payload = json.loads(six.ensure_str(decoded_payload)) + payload["exp"] = int(time.time()) + 3600 + + # write the modified token to the response body + new_payload = six.ensure_binary(json.dumps(payload)) + body["id_token"] = ".".join((header, base64.b64encode(new_payload).decode("utf-8"), signed)) + response["body"]["string"] = six.ensure_binary(json.dumps(body)) + except KeyError: + pass + + return response diff --git a/sdk/identity/azure-identity/tests/recordings/test_cae.test_device_code.yaml b/sdk/identity/azure-identity/tests/recordings/test_cae.test_device_code.yaml new file mode 100644 index 000000000000..a52808dd1278 --- /dev/null +++ b/sdk/identity/azure-identity/tests/recordings/test_cae.test_device_code.yaml @@ -0,0 +1,865 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://login.microsoftonline.com/tenant/v2.0/.well-known/openid-configuration + response: + body: + string: '{"token_endpoint": "https://login.microsoftonline.com/tenant/oauth2/v2.0/token", + "token_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", + "client_secret_basic"], "jwks_uri": "https://login.microsoftonline.com/tenant/discovery/v2.0/keys", + "response_modes_supported": ["query", "fragment", "form_post"], "subject_types_supported": + ["pairwise"], "id_token_signing_alg_values_supported": ["RS256"], "response_types_supported": + ["code", "id_token", "code id_token", "id_token token"], "scopes_supported": + ["openid", "profile", "email", "offline_access"], "issuer": "https://login.microsoftonline.com/tenant/v2.0", + "request_uri_parameter_supported": false, "userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo", + "authorization_endpoint": "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize", + "device_authorization_endpoint": "https://login.microsoftonline.com/tenant/oauth2/v2.0/devicecode", + "http_logout_supported": true, "frontchannel_logout_supported": true, "end_session_endpoint": + "https://login.microsoftonline.com/tenant/oauth2/v2.0/logout", "claims_supported": + ["sub", "iss", "cloud_instance_name", "cloud_instance_host_name", "cloud_graph_host_name", + "msgraph_host", "aud", "exp", "iat", "auth_time", "acr", "nonce", "preferred_username", + "name", "tid", "ver", "at_hash", "c_hash", "email"], "tenant_region_scope": + "NA", "cloud_instance_name": "microsoftonline.com", "cloud_graph_host_name": + "graph.windows.net", "msgraph_host": "graph.microsoft.com", "rbac_url": "https://pas.windows.net"}' + headers: + access-control-allow-methods: + - GET, OPTIONS + access-control-allow-origin: + - '*' + cache-control: + - max-age=86400, private + content-length: + - '1651' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:48:47 GMT + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwIQ; expires=Fri, 19-Feb-2021 17:48:48 GMT; path=/; + secure; HttpOnly; SameSite=None + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + domain=.login.microsoftonline.com; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=prod; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-ests-server: + - 2.1.11397.13 - SAN ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwIQ; stsservicecookie=ests; x-ms-gateway-slice=prod + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/devicecode + response: + body: + string: '{"user_code": "c934976f1393725db44d1d419b378baf00f383c5f3120eae059eb20bb86c08b0", + "device_code": "f942eaaebc9683c361e2038648fe4cf1ddb80d515ff47e10d48af420330eab5c", + "verification_uri": "https://microsoft.com/devicelogin", "expires_in": 900, + "interval": 5, "message": "524aa9e7bd498505c8f902c5fe65bd4679d45709259cb590e75f46c980c05aa9"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '473' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:48:47 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAA; expires=Fri, 19-Feb-2021 + 17:48:48 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,0,0,, + x-ms-ests-server: + - 2.1.11397.13 - EUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '654' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAA; stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|622,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"error": "authorization_pending", "error_description": "AADSTS70016: + OAuth 2.0 device flow error. Authorization is pending. Continue polling.\r\nTrace + ID: e9b0989d-88df-4b2b-9059-6ebaf060b401\r\nCorrelation ID: 68ba2961-0b13-4411-8f91-adf1a39530b9\r\nTimestamp: + 2021-01-20 17:49:04Z", "error_codes": [70016], "timestamp": "2021-01-20 17:49:04Z", + "trace_id": "e9b0989d-88df-4b2b-9059-6ebaf060b401", "correlation_id": "68ba2961-0b13-4411-8f91-adf1a39530b9", + "error_uri": "https://login.microsoftonline.com/error?code=70016"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '510' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:49:03 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAA; expires=Fri, 19-Feb-2021 + 17:49:04 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,70016,0,, + x-ms-ests-server: + - 2.1.11397.13 - SCUS ProdSlices + status: + code: 400 + message: Bad Request +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '654' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAA; stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|622,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"error": "authorization_pending", "error_description": "AADSTS70016: + OAuth 2.0 device flow error. Authorization is pending. Continue polling.\r\nTrace + ID: f0f691d8-d0d5-4c90-80ef-b928d1edb401\r\nCorrelation ID: 68ba2961-0b13-4411-8f91-adf1a39530b9\r\nTimestamp: + 2021-01-20 17:49:09Z", "error_codes": [70016], "timestamp": "2021-01-20 17:49:09Z", + "trace_id": "f0f691d8-d0d5-4c90-80ef-b928d1edb401", "correlation_id": "68ba2961-0b13-4411-8f91-adf1a39530b9", + "error_uri": "https://login.microsoftonline.com/error?code=70016"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '510' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:49:08 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAA; expires=Fri, 19-Feb-2021 + 17:49:09 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,70016,0,, + x-ms-ests-server: + - 2.1.11397.13 - SCUS ProdSlices + status: + code: 400 + message: Bad Request +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '654' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAA; stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|622,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"token_type": "Bearer", "scope": "https://management.azure.com/user_impersonation + https://management.azure.com/.default", "expires_in": 86399, "ext_expires_in": + 86399, "access_token": "27a75655adaa9cd9cd7e604a510829826acb0da287713872a9ea5658df01830f", + "refresh_token": "4c9049a37c3f1bdc3c662fb0749e887cf6c0a100f9899ef0d77029867bc9e98e", + "foci": "1", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhL3YyLjAiLCJpYXQiOjE2MTExNjQ2NTQsIm5iZiI6MTYxMTE2NDY1NCwiZXhwIjoxNjExMTY4NTU0LCJhaW8iOiJBVFFBeS84U0FBQUE4Zjl5WTlKeUFBRFQ5bEZkdlVKYU4ybk1NT3ROeGkwVlk1MWRUQUE0eFlWU3psdndwQ3NDNmdIcFRFSzB1MG1kIiwibmFtZSI6ImNobG93ZS10ZXN0Iiwib2lkIjoiYTcyOGM5MWEtNDdjOS00NTAyLWJhMDUtMzYwODQwODI2NjM2IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hsb3dlLXRlc3RAYXp1cmVzZGt0ZWFtLm9ubWljcm9zb2Z0LmNvbSIsInJoIjoiMC5BVGNBSW11Q1ZOWTRzay02MmJlNU9qNmNXcFYzc0FUYmpScEd1LTRDLWVHX2UwWTNBSkEuIiwic3ViIjoiSE5QcW9raHV6WUFmRlVlRkEtVm1rQ0M1Z253Mkp1b0xTSEtIZ2NDbkdHYyIsInRpZCI6IjU0ODI2YjIyLTM4ZDYtNGZiMi1iYWQ5LWI3YjkzYTNlOWM1YSIsInV0aSI6Ii10aTBqY1VrTjA2eHlxcTAzY0tFQVEiLCJ2ZXIiOiIyLjAifQ.cnsvmmnQzua0aZ96pXJOGq8hdgyUhy2DMTN2Yzjvs5TZCRd0voB0Ox_4VmjID3CFiixFOD58tn69pu5DkWEk8T3cBXMBecBRefdjEJcVuDeAQ_GnKZgVGVMjYHERcDxanukaknzs69an8nFASBJVHcLvaiRvy5UtJ4tJPpL1yM1XXwJd6EV4e2mqDt_5SgyWM_O2QPNtQF3IuozWRGrJ84ap2VN-zgkLrZRHeiE9XtNByQDfcwEXcxznx64lOXdJmUyfpxu8iiwChUzCa01PA-gM7UjTIBe_e71ALWdIj3fLmKp3HlquSDDV-eb9jTQrhtn7MtzONk6WbQTOahVMRA", + "client_info": "eyJ1aWQiOiJhNzI4YzkxYS00N2M5LTQ1MDItYmEwNS0zNjA4NDA4MjY2MzYiLCJ1dGlkIjoiNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhIn0"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '4361' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:49:14 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAAeUvQaQEAAAAaZJrXDgAAAA; expires=Fri, + 19-Feb-2021 17:49:15 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,0,0,, + x-ms-ests-server: + - 2.1.11397.13 - NCUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-mgmt-resource/0.1.0 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://management.azure.com/subscriptions?api-version=2019-11-01 + response: + body: + string: '{"value": [], "count": {"type": "Total", "value": 0}}' + headers: + cache-control: + - no-cache + content-length: + - '47' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:49:17 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-mgmt-resource/0.1.0 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://management.azure.com/subscriptions?api-version=2019-11-01 + response: + body: + string: '{"error": {"code": "AuthenticationFailed", "message": "Authentication + failed."}}' + headers: + cache-control: + - no-cache + connection: + - close + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:55:59 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer authorization_uri="https://login.windows.net/", error="invalid_token", + error_description="User session has been revoked", claims="eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTYxMTE2NTM1OSJ9fX0=" + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1508' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwIQguT8RAQAAAP9jmtcOAAAAeUvQaQEAAAAaZJrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|84,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"error": "invalid_grant", "error_description": "AADSTS50173: The provided + grant has expired due to it being revoked, a fresh auth token is needed. The + user might have changed or reset their password. The grant was issued on ''2021-01-20T17:49:09.5646304Z'' + and the TokensValidFrom date (before which tokens are not valid) for this + user is ''2021-01-20T17:49:18.0000000Z''.\r\nTrace ID: fb266710-409a-4c3e-a217-551bf350d700\r\nCorrelation + ID: 2a23310f-0df8-4f45-a7f9-80345ed363f0\r\nTimestamp: 2021-01-20 17:56:00Z", + "error_codes": [50173], "timestamp": "2021-01-20 17:56:00Z", "trace_id": "fb266710-409a-4c3e-a217-551bf350d700", + "correlation_id": "2a23310f-0df8-4f45-a7f9-80345ed363f0", "error_uri": "https://login.microsoftonline.com/error?code=50173", + "suberror": "bad_token"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '760' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:56:00 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAeUvQaQEAAAAaZJrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + expires=Fri, 19-Feb-2021 17:56:00 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,50173,0,402450.2092, + x-ms-ests-server: + - 2.1.11397.13 - WUS2 ProdSlices + status: + code: 400 + message: Bad Request +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAeUvQaQEAAAAaZJrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/common/oauth2/authorize + response: + body: + string: '{"tenant_discovery_endpoint": "https://login.microsoftonline.com/common/.well-known/openid-configuration", + "api-version": "1.1", "metadata": [{"preferred_network": "login.microsoftonline.com", + "preferred_cache": "login.windows.net", "aliases": ["login.microsoftonline.com", + "login.windows.net", "login.microsoft.com", "sts.windows.net"]}, {"preferred_network": + "login.partner.microsoftonline.cn", "preferred_cache": "login.partner.microsoftonline.cn", + "aliases": ["login.partner.microsoftonline.cn", "login.chinacloudapi.cn"]}, + {"preferred_network": "login.microsoftonline.de", "preferred_cache": "login.microsoftonline.de", + "aliases": ["login.microsoftonline.de"]}, {"preferred_network": "login.microsoftonline.us", + "preferred_cache": "login.microsoftonline.us", "aliases": ["login.microsoftonline.us", + "login.usgovcloudapi.net"]}, {"preferred_network": "login-us.microsoftonline.com", + "preferred_cache": "login-us.microsoftonline.com", "aliases": ["login-us.microsoftonline.com"]}]}' + headers: + access-control-allow-methods: + - GET, OPTIONS + access-control-allow-origin: + - '*' + cache-control: + - max-age=86400, private + content-length: + - '945' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:56:00 GMT + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAeUvQaQEAAAAaZJrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + expires=Fri, 19-Feb-2021 17:56:00 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-ests-server: + - 2.1.11397.13 - EUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAeUvQaQEAAAAaZJrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/devicecode + response: + body: + string: '{"user_code": "37014a978b95ae6e752f54107987c3409b3cf801c3835261952f8740cb20d1c8", + "device_code": "428a20171292a6f89bedf5ef1b83a82a94f3196d1cdc914229be57fb5bdb6b1c", + "verification_uri": "https://microsoft.com/devicelogin", "expires_in": 900, + "interval": 5, "message": "a92bcf4b00e3e6e783399306f14035815428ee07c9e2a0c7920a27fbdb9a9b27"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '473' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:56:00 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAILk_EQEAAACvZZrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + expires=Fri, 19-Feb-2021 17:56:00 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,0,0,, + x-ms-ests-server: + - 2.1.11397.13 - NCUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '735' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAILk_EQEAAACvZZrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|622,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"error": "authorization_pending", "error_description": "AADSTS70016: + OAuth 2.0 device flow error. Authorization is pending. Continue polling.\r\nTrace + ID: c0b1f362-14e3-4811-93ca-434e3c99d400\r\nCorrelation ID: 6ba32921-617b-40ec-b511-241b3c98a7d5\r\nTimestamp: + 2021-01-20 17:56:16Z", "error_codes": [70016], "timestamp": "2021-01-20 17:56:16Z", + "trace_id": "c0b1f362-14e3-4811-93ca-434e3c99d400", "correlation_id": "6ba32921-617b-40ec-b511-241b3c98a7d5", + "error_uri": "https://login.microsoftonline.com/error?code=70016"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '510' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:56:16 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAILk_EQEAAACvZZrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + expires=Fri, 19-Feb-2021 17:56:16 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,70016,0,, + x-ms-ests-server: + - 2.1.11397.13 - EUS ProdSlices + status: + code: 400 + message: Bad Request +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '735' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAILk_EQEAAACvZZrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|622,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"error": "authorization_pending", "error_description": "AADSTS70016: + OAuth 2.0 device flow error. Authorization is pending. Continue polling.\r\nTrace + ID: 281a0b66-df4c-448e-ba01-89cf75f7c101\r\nCorrelation ID: 6ba32921-617b-40ec-b511-241b3c98a7d5\r\nTimestamp: + 2021-01-20 17:56:22Z", "error_codes": [70016], "timestamp": "2021-01-20 17:56:22Z", + "trace_id": "281a0b66-df4c-448e-ba01-89cf75f7c101", "correlation_id": "6ba32921-617b-40ec-b511-241b3c98a7d5", + "error_uri": "https://login.microsoftonline.com/error?code=70016"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '510' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:56:21 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAILk_EQEAAACvZZrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + expires=Fri, 19-Feb-2021 17:56:22 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,70016,0,, + x-ms-ests-server: + - 2.1.11397.13 - EUS ProdSlices + status: + code: 400 + message: Bad Request +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '735' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL4pTcs2nJCalp3mS66FDSyP9ddacNcUjMn-NBJHtqPUbDaud7LQftlxdSSmF9tpZRKmtm_ajTzGa-JaJWMwMRoRWgTZpvPCSzL4DTgR3htWfaPWgkKM7BppxI58iZ-z1YHg2xu8-9ChyvvA0R9BBcUY4xVxdNaK0Ru0IZXj51YTcQgAA; + fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAILk_EQEAAACvZZrXDgAAAN-d3AMBAAAAHWSa1w4AAAA; + stsservicecookie=ests; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|622,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"token_type": "Bearer", "scope": "https://management.azure.com/user_impersonation + https://management.azure.com/.default", "expires_in": 86399, "ext_expires_in": + 86399, "access_token": "4eae20b397129621daea0169160d06c2d3d6a26bcac8c6aed6d722f89952d41b", + "refresh_token": "5013d6d09c2f1ff82a870572f6066aecebea1137e8b1c731be5bf01ae0f67f24", + "foci": "1", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhL3YyLjAiLCJpYXQiOjE2MTExNjUwODcsIm5iZiI6MTYxMTE2NTA4NywiZXhwIjoxNjExMTY4OTg3LCJhaW8iOiJBVFFBeS84U0FBQUE4L1lxaDZrd3UrRnJ3TjV2bENVQ2Z5QVlmVDR1dExpTitpeFhZS0ZsWEhZZndiSDZITW9JZEpMazlJMVZnMy9XIiwibmFtZSI6ImNobG93ZS10ZXN0Iiwib2lkIjoiYTcyOGM5MWEtNDdjOS00NTAyLWJhMDUtMzYwODQwODI2NjM2IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hsb3dlLXRlc3RAYXp1cmVzZGt0ZWFtLm9ubWljcm9zb2Z0LmNvbSIsInJoIjoiMC5BVGNBSW11Q1ZOWTRzay02MmJlNU9qNmNXcFYzc0FUYmpScEd1LTRDLWVHX2UwWTNBSkEuIiwic3ViIjoiSE5QcW9raHV6WUFmRlVlRkEtVm1rQ0M1Z253Mkp1b0xTSEtIZ2NDbkdHYyIsInRpZCI6IjU0ODI2YjIyLTM4ZDYtNGZiMi1iYWQ5LWI3YjkzYTNlOWM1YSIsInV0aSI6Ik9WY3ZkbXVncEV1YllWQU9va0szQVEiLCJ2ZXIiOiIyLjAifQ.b17pJRXmw9YnC97aQbdKihqjTnqYuFSGH0AxdtDk4cpt9ZviJV19dz5wVxaUORGNf6K6vnFhZRlEX89bhMLH1BpVq5lc28IQ-8zk-Q6ItIap2xVS4u2jjHHoSZLoIPtNXMXPnOz_KPuff_L46mJpKoBxpEOBUvB6Tx_k-hLyrabpNYo_swBemamDylbrDPUpyNZCIlHi_qMH60Hrtfg9JlnGUeUUi-seY4vRQcTnAB-wvsQtMWbKYsEpBgjUihoqScRZMNC9j3HuvAsWurrCIJ4vZ6WKsjg7aoqC-Z-AWnOmvVSlUTajCI1HFzhjcTYDRS_WrcKnModSLvR1I6eMrw", + "client_info": "eyJ1aWQiOiJhNzI4YzkxYS00N2M5LTQ1MDItYmEwNS0zNjA4NDA4MjY2MzYiLCJ1dGlkIjoiNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhIn0"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '4361' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:56:27 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=ApqwGRndfkRDiC2IHIqnwISY_f5LAQAAALBlmtcOAAAAILk_EQEAAACvZZrXDgAAAHlL0GkBAAAAy2Wa1w4AAAA; + expires=Fri, 19-Feb-2021 17:56:27 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=ests; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,0,0,, + x-ms-ests-server: + - 2.1.11397.13 - WUS2 ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-mgmt-resource/0.1.0 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://management.azure.com/subscriptions?api-version=2019-11-01 + response: + body: + string: '{"value": [], "count": {"type": "Total", "value": 0}}' + headers: + cache-control: + - no-cache + content-length: + - '47' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:56:29 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/identity/azure-identity/tests/recordings/test_cae.test_username_password.yaml b/sdk/identity/azure-identity/tests/recordings/test_cae.test_username_password.yaml new file mode 100644 index 000000000000..bccbe948fc44 --- /dev/null +++ b/sdk/identity/azure-identity/tests/recordings/test_cae.test_username_password.yaml @@ -0,0 +1,559 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://login.microsoftonline.com/tenant/v2.0/.well-known/openid-configuration + response: + body: + string: '{"token_endpoint": "https://login.microsoftonline.com/tenant/oauth2/v2.0/token", + "token_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", + "client_secret_basic"], "jwks_uri": "https://login.microsoftonline.com/tenant/discovery/v2.0/keys", + "response_modes_supported": ["query", "fragment", "form_post"], "subject_types_supported": + ["pairwise"], "id_token_signing_alg_values_supported": ["RS256"], "response_types_supported": + ["code", "id_token", "code id_token", "id_token token"], "scopes_supported": + ["openid", "profile", "email", "offline_access"], "issuer": "https://login.microsoftonline.com/tenant/v2.0", + "request_uri_parameter_supported": false, "userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo", + "authorization_endpoint": "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize", + "device_authorization_endpoint": "https://login.microsoftonline.com/tenant/oauth2/v2.0/devicecode", + "http_logout_supported": true, "frontchannel_logout_supported": true, "end_session_endpoint": + "https://login.microsoftonline.com/tenant/oauth2/v2.0/logout", "claims_supported": + ["sub", "iss", "cloud_instance_name", "cloud_instance_host_name", "cloud_graph_host_name", + "msgraph_host", "aud", "exp", "iat", "auth_time", "acr", "nonce", "preferred_username", + "name", "tid", "ver", "at_hash", "c_hash", "email"], "tenant_region_scope": + "NA", "cloud_instance_name": "microsoftonline.com", "cloud_graph_host_name": + "graph.windows.net", "msgraph_host": "graph.microsoft.com", "rbac_url": "https://pas.windows.net"}' + headers: + access-control-allow-methods: + - GET, OPTIONS + access-control-allow-origin: + - '*' + cache-control: + - max-age=86400, private + content-length: + - '1651' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:32:19 GMT + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + set-cookie: + - fpc=Aq2CQNlpjAxDssTcAilRGLY; expires=Fri, 19-Feb-2021 17:32:19 GMT; path=/; + secure; HttpOnly; SameSite=None + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL41VttHWaSdWFW4op5D1v8wRYT0Le8LI-vbF7BnHd4S77mIU_zlxYknnw07xOCtnkPU-2KykWHxlYYRdnQA6vbogoL3vOKUYHDJfHb7odwv3xiSWTlEunCm4GpfWqWg3tjbjJBVxt2c8brqSulOpCUoCE5wsN-wzsrI8eVRZ23TaAgAA; + domain=.login.microsoftonline.com; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=estsfd; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-ests-server: + - 2.1.11397.13 - NCUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL41VttHWaSdWFW4op5D1v8wRYT0Le8LI-vbF7BnHd4S77mIU_zlxYknnw07xOCtnkPU-2KykWHxlYYRdnQA6vbogoL3vOKUYHDJfHb7odwv3xiSWTlEunCm4GpfWqWg3tjbjJBVxt2c8brqSulOpCUoCE5wsN-wzsrI8eVRZ23TaAgAA; + fpc=Aq2CQNlpjAxDssTcAilRGLY; stsservicecookie=estsfd; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://login.microsoftonline.com/common/userrealm/username?api-version=1.0 + response: + body: + string: '{"ver": "1.0", "account_type": "Managed", "domain_name": "azuresdkteam.onmicrosoft.com", + "cloud_instance_name": "microsoftonline.com", "cloud_audience_urn": "urn:federation:MicrosoftOnline"}' + headers: + cache-control: + - no-store, no-cache + content-disposition: + - inline; filename=userrealm.json + content-length: + - '181' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:32:19 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=Aq2CQNlpjAxDssTcAilRGLY; expires=Fri, 19-Feb-2021 17:32:20 GMT; path=/; + secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=estsfd; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-ests-server: + - 2.1.11397.13 - NCUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '333' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL41VttHWaSdWFW4op5D1v8wRYT0Le8LI-vbF7BnHd4S77mIU_zlxYknnw07xOCtnkPU-2KykWHxlYYRdnQA6vbogoL3vOKUYHDJfHb7odwv3xiSWTlEunCm4GpfWqWg3tjbjJBVxt2c8brqSulOpCUoCE5wsN-wzsrI8eVRZ23TaAgAA; + fpc=Aq2CQNlpjAxDssTcAilRGLY; stsservicecookie=estsfd; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|301,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"token_type": "Bearer", "scope": "https://management.azure.com/user_impersonation + https://management.azure.com/.default", "expires_in": 86399, "ext_expires_in": + 86399, "access_token": "90bfb5b224af86355666990646b4560e01bd8e134409cf3b8957eb3439e1198d", + "refresh_token": "5cd3b6421bdc3b15ae207aa2dbd5382287ab90b682e8382d6e295dd2c9c63b95", + "foci": "1", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhL3YyLjAiLCJpYXQiOjE2MTExNjM2NDAsIm5iZiI6MTYxMTE2MzY0MCwiZXhwIjoxNjExMTY3NTQwLCJhaW8iOiJBVFFBeS84U0FBQUFjSU9MUjhRbmJJd0lKb1lTYkxoakhndzMrL3BEdWJaVTdlUHB1anFjQjBvMzNzQjVXRjJWV0w4bjEwWmhkM3dWIiwibmFtZSI6ImNobG93ZS10ZXN0Iiwib2lkIjoiYTcyOGM5MWEtNDdjOS00NTAyLWJhMDUtMzYwODQwODI2NjM2IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hsb3dlLXRlc3RAYXp1cmVzZGt0ZWFtLm9ubWljcm9zb2Z0LmNvbSIsInJoIjoiMC5BVGNBSW11Q1ZOWTRzay02MmJlNU9qNmNXcFYzc0FUYmpScEd1LTRDLWVHX2UwWTNBSkEuIiwic3ViIjoiSE5QcW9raHV6WUFmRlVlRkEtVm1rQ0M1Z253Mkp1b0xTSEtIZ2NDbkdHYyIsInRpZCI6IjU0ODI2YjIyLTM4ZDYtNGZiMi1iYWQ5LWI3YjkzYTNlOWM1YSIsInV0aSI6IjMyLWpHQW1tNkVPckVEM2J0dl9NQVEiLCJ2ZXIiOiIyLjAifQ.Selp9Tu_RXqJjKw2Jr9tlIDEO5ZDoEQmURRZyYVHHdv7VAntq6khKkxTbcWq_UMf-XQLV9_vislkdaydPzZaM8rb-9O3DDN9Mfug8s9clxWPAUu8Cv7A8qc-eMTYF7nSjZXvcDRcAINunnUeeXjYBYYa-Cm9XrVzKiXyjb9Ue4S7c42YCdE_PUgQPQq-qr_umkWmIYLa5cOB-a017vUdGQ2DOAT3scPKCwjXb2ZlSUfeVs7bXXmvzgNGaBkCe2g2m97Fbe9JCjfmsIj89D8gkYuSup7JYV6DJzw3XAXGH1f46hEAhcHhoLoVuTTbMRKhIUV_TSQpnWSvv2CrRGEBeQ", + "client_info": "eyJ1aWQiOiJhNzI4YzkxYS00N2M5LTQ1MDItYmEwNS0zNjA4NDA4MjY2MzYiLCJ1dGlkIjoiNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhIn0"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '4308' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:32:20 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA; expires=Fri, 19-Feb-2021 + 17:32:20 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=estsfd; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,0,0,, + x-ms-ests-server: + - 2.1.11397.13 - EUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-mgmt-resource/0.1.0 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://management.azure.com/subscriptions?api-version=2019-11-01 + response: + body: + string: '{"value": [], "count": {"type": "Total", "value": 0}}' + headers: + cache-control: + - no-cache + content-length: + - '47' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:32:22 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-mgmt-resource/0.1.0 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://management.azure.com/subscriptions?api-version=2019-11-01 + response: + body: + string: '{"error": {"code": "AuthenticationFailed", "message": "Authentication + failed."}}' + headers: + cache-control: + - no-cache + connection: + - close + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:39:04 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer authorization_uri="https://login.windows.net/", error="invalid_token", + error_description="User session has been revoked", claims="eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTYxMTE2NDM0NCJ9fX0=" + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1460' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL41VttHWaSdWFW4op5D1v8wRYT0Le8LI-vbF7BnHd4S77mIU_zlxYknnw07xOCtnkPU-2KykWHxlYYRdnQA6vbogoL3vOKUYHDJfHb7odwv3xiSWTlEunCm4GpfWqWg3tjbjJBVxt2c8brqSulOpCUoCE5wsN-wzsrI8eVRZ23TaAgAA; + fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA353cAwEAAAAlYJrXDgAAAA; stsservicecookie=estsfd; + x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|84,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"error": "invalid_grant", "error_description": "AADSTS50173: The provided + grant has expired due to it being revoked, a fresh auth token is needed. The + user might have changed or reset their password. The grant was issued on ''2021-01-20T17:32:20.2232320Z'' + and the TokensValidFrom date (before which tokens are not valid) for this + user is ''2021-01-20T17:32:23.0000000Z''.\r\nTrace ID: 5ee79893-4981-495d-ae90-afc983ccc901\r\nCorrelation + ID: 1f52d4cd-6545-40bb-84b2-41e4f6d70fe0\r\nTimestamp: 2021-01-20 17:39:05Z", + "error_codes": [50173], "timestamp": "2021-01-20 17:39:05Z", "trace_id": "5ee79893-4981-495d-ae90-afc983ccc901", + "correlation_id": "1f52d4cd-6545-40bb-84b2-41e4f6d70fe0", "error_uri": "https://login.microsoftonline.com/error?code=50173", + "suberror": "bad_token"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '760' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:39:04 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA353cAwEAAAAlYJrXDgAAAJj9_ksBAAAAuGGa1w4AAAA; + expires=Fri, 19-Feb-2021 17:39:05 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=estsfd; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,50173,0,402769.7905, + x-ms-ests-server: + - 2.1.11397.13 - EUS ProdSlices + status: + code: 400 + message: Bad Request +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL41VttHWaSdWFW4op5D1v8wRYT0Le8LI-vbF7BnHd4S77mIU_zlxYknnw07xOCtnkPU-2KykWHxlYYRdnQA6vbogoL3vOKUYHDJfHb7odwv3xiSWTlEunCm4GpfWqWg3tjbjJBVxt2c8brqSulOpCUoCE5wsN-wzsrI8eVRZ23TaAgAA; + fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA353cAwEAAAAlYJrXDgAAAJj9_ksBAAAAuGGa1w4AAAA; + stsservicecookie=estsfd; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/common/oauth2/authorize + response: + body: + string: '{"tenant_discovery_endpoint": "https://login.microsoftonline.com/common/.well-known/openid-configuration", + "api-version": "1.1", "metadata": [{"preferred_network": "login.microsoftonline.com", + "preferred_cache": "login.windows.net", "aliases": ["login.microsoftonline.com", + "login.windows.net", "login.microsoft.com", "sts.windows.net"]}, {"preferred_network": + "login.partner.microsoftonline.cn", "preferred_cache": "login.partner.microsoftonline.cn", + "aliases": ["login.partner.microsoftonline.cn", "login.chinacloudapi.cn"]}, + {"preferred_network": "login.microsoftonline.de", "preferred_cache": "login.microsoftonline.de", + "aliases": ["login.microsoftonline.de"]}, {"preferred_network": "login.microsoftonline.us", + "preferred_cache": "login.microsoftonline.us", "aliases": ["login.microsoftonline.us", + "login.usgovcloudapi.net"]}, {"preferred_network": "login-us.microsoftonline.com", + "preferred_cache": "login-us.microsoftonline.com", "aliases": ["login-us.microsoftonline.com"]}]}' + headers: + access-control-allow-methods: + - GET, OPTIONS + access-control-allow-origin: + - '*' + cache-control: + - max-age=86400, private + content-length: + - '945' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:39:04 GMT + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + set-cookie: + - fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA353cAwEAAAAlYJrXDgAAAJj9_ksBAAAAuGGa1w4AAAA; + expires=Fri, 19-Feb-2021 17:39:05 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=estsfd; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-ests-server: + - 2.1.11419.13 - WUS2 ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL41VttHWaSdWFW4op5D1v8wRYT0Le8LI-vbF7BnHd4S77mIU_zlxYknnw07xOCtnkPU-2KykWHxlYYRdnQA6vbogoL3vOKUYHDJfHb7odwv3xiSWTlEunCm4GpfWqWg3tjbjJBVxt2c8brqSulOpCUoCE5wsN-wzsrI8eVRZ23TaAgAA; + fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA353cAwEAAAAlYJrXDgAAAJj9_ksBAAAAuGGa1w4AAAA; + stsservicecookie=estsfd; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://login.microsoftonline.com/common/userrealm/username?api-version=1.0 + response: + body: + string: '{"ver": "1.0", "account_type": "Managed", "domain_name": "azuresdkteam.onmicrosoft.com", + "cloud_instance_name": "microsoftonline.com", "cloud_audience_urn": "urn:federation:MicrosoftOnline"}' + headers: + cache-control: + - no-store, no-cache + content-disposition: + - inline; filename=userrealm.json + content-length: + - '181' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:39:04 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA353cAwEAAAAlYJrXDgAAAJj9_ksBAAAAuGGa1w4AAAA; + expires=Fri, 19-Feb-2021 17:39:05 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=estsfd; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-ests-server: + - 2.1.11397.13 - SCUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '414' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - esctx=AQABAAAAAABeStGSRwwnTq2vHplZ9KL41VttHWaSdWFW4op5D1v8wRYT0Le8LI-vbF7BnHd4S77mIU_zlxYknnw07xOCtnkPU-2KykWHxlYYRdnQA6vbogoL3vOKUYHDJfHb7odwv3xiSWTlEunCm4GpfWqWg3tjbjJBVxt2c8brqSulOpCUoCE5wsN-wzsrI8eVRZ23TaAgAA; + fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAACNgmtcOAAAA353cAwEAAAAlYJrXDgAAAJj9_ksBAAAAuGGa1w4AAAA; + stsservicecookie=estsfd; x-ms-gateway-slice=estsfd + User-Agent: + - azsdk-python-identity/1.5.1 Python/3.8.0 (Windows-10-10.0.19041-SP0) + x-client-cpu: + - x86 + x-client-current-telemetry: + - 1|301,0| + x-client-os: + - win32 + x-client-sku: + - MSAL.Python + x-client-ver: + - 1.8.0 + method: POST + uri: https://login.microsoftonline.com/tenant/oauth2/v2.0/token + response: + body: + string: '{"token_type": "Bearer", "scope": "https://management.azure.com/user_impersonation + https://management.azure.com/.default", "expires_in": 86399, "ext_expires_in": + 86399, "access_token": "1cd53f96bc681d6e3c2c1e44d52166dc319ef18e0cff11cd164dcab68400703a", + "refresh_token": "543af57a4e02de41aab38056eee6672b51f7f2e4119bc09ec2f5698a94aeb569", + "foci": "1", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhL3YyLjAiLCJpYXQiOjE2MTExNjQwNDUsIm5iZiI6MTYxMTE2NDA0NSwiZXhwIjoxNjExMTY3OTQ1LCJhaW8iOiJBVFFBeS84U0FBQUFLODI3b3FSMjdsMGcyRG5qM0RuR2RmeTUxcjgrZk1wYTFUb3dOK2gvMTdtdllyRHlQY0t0aTBQV0NoQ2g5TmpsIiwibmFtZSI6ImNobG93ZS10ZXN0Iiwib2lkIjoiYTcyOGM5MWEtNDdjOS00NTAyLWJhMDUtMzYwODQwODI2NjM2IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2hsb3dlLXRlc3RAYXp1cmVzZGt0ZWFtLm9ubWljcm9zb2Z0LmNvbSIsInJoIjoiMC5BVGNBSW11Q1ZOWTRzay02MmJlNU9qNmNXcFYzc0FUYmpScEd1LTRDLWVHX2UwWTNBSkEuIiwic3ViIjoiSE5QcW9raHV6WUFmRlVlRkEtVm1rQ0M1Z253Mkp1b0xTSEtIZ2NDbkdHYyIsInRpZCI6IjU0ODI2YjIyLTM4ZDYtNGZiMi1iYWQ5LWI3YjkzYTNlOWM1YSIsInV0aSI6ImcxOTZhbTJNYlV5R0FqOE44cDdIQVEiLCJ2ZXIiOiIyLjAifQ.Uhx_g7vsHlPMQzeSbkTbeCgAVq1Yj7OWk18zJr5huT9vGX2NlFIiLJa2fUGW_thsYtSNPrmRAFCaAPD402N1-gZwFyCId0ObpmgW4m-XUXwVScGbQ4malAOIAuxX_BFQ4tLKSUzi1JWryWBvs2eBh3ZCzATGw_gJGm83TYm4Ih-QrmGULMEQP0gC0gpqvfOo2giM8via-xSh0vDTZA07ip17C5Te_IAS3AJ1ChcnJvB5uCpTLd5a-zD2fsCvK602PDtCMMZNwnbmrXVUseP_1E_3JQGRa6dfBGFq4HSdV2EvQ5s5zq_Dy3aAgpGzLQiJQgf7rzcxUd0RRHoSSLp5XQ", + "client_info": "eyJ1aWQiOiJhNzI4YzkxYS00N2M5LTQ1MDItYmEwNS0zNjA4NDA4MjY2MzYiLCJ1dGlkIjoiNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhIn0"}' + headers: + cache-control: + - no-store, no-cache + content-length: + - '4308' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:39:05 GMT + expires: + - '-1' + p3p: + - CP="DSP CUR OTPi IND OTRi ONL FIN" + pragma: + - no-cache + set-cookie: + - fpc=Aq2CQNlpjAxDssTcAilRGLaFz2TkAQAAAE9hmtcOAAAA353cAwEAAAAlYJrXDgAAAJj9_ksBAAAAuGGa1w4AAAA; + expires=Fri, 19-Feb-2021 17:39:05 GMT; path=/; secure; HttpOnly; SameSite=None + - x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly + - stsservicecookie=estsfd; path=/; secure; samesite=none; httponly + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-ms-clitelem: + - 1,0,0,, + x-ms-ests-server: + - 2.1.11397.13 - SCUS ProdSlices + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-mgmt-resource/0.1.0 Python/3.8.0 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://management.azure.com/subscriptions?api-version=2019-11-01 + response: + body: + string: '{"value": [], "count": {"type": "Total", "value": 0}}' + headers: + cache-control: + - no-cache + content-length: + - '47' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 20 Jan 2021 17:39:06 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/identity/azure-identity/tests/test_browser_credential.py b/sdk/identity/azure-identity/tests/test_browser_credential.py index aa3dbba67327..867b32e5c539 100644 --- a/sdk/identity/azure-identity/tests/test_browser_credential.py +++ b/sdk/identity/azure-identity/tests/test_browser_credential.py @@ -21,6 +21,7 @@ build_aad_response, build_id_token, get_discovery_response, + id_token_claims, mock_response, msal_validating_transport, Request, @@ -361,3 +362,40 @@ def _validate_auth_request_url(url): # when used as a Mock's side_effect, this method's return value is the Mock's return value # (the real webbrowser.open returns a bool) return True + + +def test_claims_challenge(): + """get_token should pass any claims challenge to MSAL token acquisition APIs""" + + expected_claims = '{"access_token": {"essential": "true"}' + + oauth_state = "..." + auth_code_response = {"code": "authorization-code", "state": [oauth_state]} + server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response)) + + msal_acquire_token_result = dict( + build_aad_response(access_token="**", id_token=build_id_token()), + id_token_claims=id_token_claims("issuer", "subject", "audience", upn="upn"), + ) + + transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) + credential = InteractiveBrowserCredential(_server_class=server_class, transport=transport) + with patch.object(InteractiveBrowserCredential, "_get_app") as get_mock_app: + msal_app = get_mock_app() + msal_app.acquire_token_by_authorization_code.return_value = msal_acquire_token_result + + with patch(InteractiveBrowserCredential.__module__ + ".uuid.uuid4", lambda: oauth_state): + with patch(WEBBROWSER_OPEN, lambda _: True): + credential.get_token("scope", claims_challenge=expected_claims) + + assert msal_app.acquire_token_by_authorization_code.call_count == 1 + args, kwargs = msal_app.acquire_token_by_authorization_code.call_args + assert kwargs["claims_challenge"] == expected_claims + + msal_app.get_accounts.return_value = [{"home_account_id": credential._auth_record.home_account_id}] + msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result + credential.get_token("scope", claims_challenge=expected_claims) + + assert msal_app.acquire_token_silent_with_error.call_count == 1 + args, kwargs = msal_app.acquire_token_silent_with_error.call_args + assert kwargs["claims_challenge"] == expected_claims diff --git a/sdk/identity/azure-identity/tests/test_cae.py b/sdk/identity/azure-identity/tests/test_cae.py new file mode 100644 index 000000000000..e29050d38b1e --- /dev/null +++ b/sdk/identity/azure-identity/tests/test_cae.py @@ -0,0 +1,139 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import base64 +import json +import os +import time + +from azure.core.pipeline.transport import HttpRequest +from azure.core.exceptions import ResourceNotFoundError +from azure.identity import ( + AzureAuthorityHosts, + DeviceCodeCredential, + UsernamePasswordCredential, + InteractiveBrowserCredential, +) +from azure.identity._constants import DEVELOPER_SIGN_ON_CLIENT_ID +from azure.mgmt.resource.subscriptions import SubscriptionClient +from azure_devtools.scenario_tests import GeneralNameReplacer, RequestUrlNormalizer, patch_time_sleep_api +from devtools_testutils import AzureTestCase +import pytest +import requests +from six.moves.urllib_parse import urlparse + +from recording_processors import IdTokenProcessor, RecordingRedactor + + +@pytest.mark.skip("these tests require support in azure-core") +class CaeTestCase(AzureTestCase): + def __init__(self, *args, **kwargs): + scrubber = GeneralNameReplacer() + super(CaeTestCase, self).__init__( + *args, + recording_processors=[RecordingRedactor(), scrubber], + replay_processors=[RequestUrlNormalizer(), IdTokenProcessor()], + **kwargs + ) + self.scrubber = scrubber + if self.is_live: + if "CAE_TENANT_ID" not in os.environ: + pytest.skip("Missing a tenant ID for CAE tests") + if "CAE_ARM_URL" not in os.environ: + pytest.skip("Missing an ARM URL for CAE tests") + + self.cae_settings = { + "arm_scope": os.environ.get("CAE_ARM_SCOPE", "https://management.azure.com/.default"), + "arm_url": os.environ["CAE_ARM_URL"], + "authority": os.environ.get("CAE_AUTHORITY", AzureAuthorityHosts.AZURE_PUBLIC_CLOUD), + "graph_url": os.environ.get("CAE_GRAPH_URL", "https://graph.microsoft.com"), + "password": os.environ.get("CAE_PASSWORD"), + "tenant_id": os.environ["CAE_TENANT_ID"], + "username": os.environ.get("CAE_USERNAME"), + } + real = urlparse(self.cae_settings["arm_url"]) + self.scrubber.register_name_pair(real.netloc, "management.azure.com") + self.scrubber.register_name_pair(self.cae_settings["tenant_id"], "tenant") + self.scrubber.register_name_pair(self.cae_settings["username"], "username") + else: + self.cae_settings = { + "arm_scope": "https://management.azure.com/.default", + "arm_url": "https://management.azure.com/", + "authority": AzureAuthorityHosts.AZURE_PUBLIC_CLOUD, + "password": "password", + "tenant_id": "tenant", + "username": "username", + } + self.replay_patches.append(patch_time_sleep_api) + + def cae_test(self, credential): + client = SubscriptionClient(credential, base_url=self.cae_settings["arm_url"]) + + # get an access token for ARM + list(client.subscriptions.list()) + first_token = credential.get_token(self.cae_settings["arm_scope"]) + + if self.is_live: + validate_ssm_token(first_token.token) + + # revoking sessions revokes access and refresh tokens + self.disable_recording = True + graph_token = credential.get_token("User.ReadWrite") + response = credential._client.post( + self.cae_settings["graph_url"].rstrip("/") + "/v1.0/me/revokeSignInSessions", + headers={"Authorization": "Bearer " + graph_token.token}, + ) + self.disable_recording = False + assert 200 <= response.status_code < 300, "session revocation failed: " + response.text() + + # wait for the resource provider to observe the revocation event + time.sleep(400) + + # The client should authorize this request with a revoked token, and receive a challenge. + # Silent authentication will fail because the refresh token has been revoked. + list(client.subscriptions.list()) + + # the credential should have reauthenticated the user and acquired a new access token + second_token = credential.get_token(self.cae_settings["arm_scope"]) + assert second_token.token != first_token.token + + @pytest.mark.manual + def test_browser(self): + credential = InteractiveBrowserCredential( + authority=self.cae_settings["authority"], tenant_id=self.cae_settings["tenant_id"] + ) + self.cae_test(credential) + + def test_device_code(self): + credential = DeviceCodeCredential( + authority=self.cae_settings["authority"], tenant_id=self.cae_settings["tenant_id"] + ) + self.cae_test(credential) + + def test_username_password(self): + if self.is_live and not ("username" in self.cae_settings and "password" in self.cae_settings): + pytest.skip("Missing a username or password for CAE test") + + credential = UsernamePasswordCredential( + DEVELOPER_SIGN_ON_CLIENT_ID, + authority=self.cae_settings["authority"], + tenant_id=self.cae_settings["tenant_id"], + username=self.cae_settings["username"], + password=self.cae_settings["password"], + ) + self.cae_test(credential) + + +def validate_ssm_token(access_token): + """Ensure an access token is enabled for smart session management i.e. it is subject to CAE""" + + _, payload, _ = access_token.split(".") + decoded_payload = base64.urlsafe_b64decode(payload + "==").decode() + parsed_payload = json.loads(decoded_payload) + + assert ( + "xms_cc" in parsed_payload and "CP1" in parsed_payload["xms_cc"] + ), 'the token request did not include client capability "CP1"' + + assert "xms_ssm" in parsed_payload and parsed_payload["xms_ssm"] == "1", "CAE isn't enabled for the user" diff --git a/sdk/identity/azure-identity/tests/test_device_code_credential.py b/sdk/identity/azure-identity/tests/test_device_code_credential.py index ec2615570995..b381e1ed7d19 100644 --- a/sdk/identity/azure-identity/tests/test_device_code_credential.py +++ b/sdk/identity/azure-identity/tests/test_device_code_credential.py @@ -15,15 +15,16 @@ build_aad_response, build_id_token, get_discovery_response, + id_token_claims, mock_response, Request, validating_transport, ) try: - from unittest.mock import Mock + from unittest.mock import Mock, patch except ImportError: # python < 3.3 - from mock import Mock # type: ignore + from mock import Mock, patch # type: ignore def test_tenant_id_validation(): @@ -260,3 +261,47 @@ def test_timeout(): with pytest.raises(ClientAuthenticationError) as ex: credential.get_token("scope") assert "timed out" in ex.value.message.lower() + + +def test_client_capabilities(): + """the credential should configure MSAL for capability CP1 (ability to handle claims challenges)""" + + transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) + credential = DeviceCodeCredential(transport=transport) + + with patch("msal.PublicClientApplication") as PublicClientApplication: + credential._get_app() + + assert PublicClientApplication.call_count == 1 + _, kwargs = PublicClientApplication.call_args + assert kwargs["client_capabilities"] == ["CP1"] + + +def test_claims_challenge(): + """get_token should pass any claims challenge to MSAL token acquisition APIs""" + + msal_acquire_token_result = dict( + build_aad_response(access_token="**", id_token=build_id_token()), + id_token_claims=id_token_claims("issuer", "subject", "audience", upn="upn"), + ) + expected_claims = '{"access_token": {"essential": "true"}' + + transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) + credential = DeviceCodeCredential(transport=transport) + with patch.object(DeviceCodeCredential, "_get_app") as get_mock_app: + msal_app = get_mock_app() + msal_app.initiate_device_flow.return_value = {"message": "it worked"} + msal_app.acquire_token_by_device_flow.return_value = msal_acquire_token_result + credential.get_token("scope", claims_challenge=expected_claims) + + assert msal_app.acquire_token_by_device_flow.call_count == 1 + args, kwargs = msal_app.acquire_token_by_device_flow.call_args + assert kwargs["claims_challenge"] == expected_claims + + msal_app.get_accounts.return_value = [{"home_account_id": credential._auth_record.home_account_id}] + msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result + credential.get_token("scope", claims_challenge=expected_claims) + + assert msal_app.acquire_token_silent_with_error.call_count == 1 + args, kwargs = msal_app.acquire_token_silent_with_error.call_args + assert kwargs["claims_challenge"] == expected_claims diff --git a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py index 9634fe8dced6..c6778cce1b43 100644 --- a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py +++ b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py @@ -800,6 +800,44 @@ def mock_send(request, **_): assert transport.send.called +def test_client_capabilities(): + """the credential should configure MSAL for capability CP1 (ability to handle claims challenges)""" + + record = AuthenticationRecord("tenant-id", "client_id", "authority", "home_account_id", "username") + transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) + credential = SharedTokenCacheCredential(transport=transport, authentication_record=record, _cache=TokenCache()) + + with patch(SharedTokenCacheCredential.__module__ + ".PublicClientApplication") as PublicClientApplication: + credential._initialize() + + assert PublicClientApplication.call_count == 1 + _, kwargs = PublicClientApplication.call_args + assert kwargs["client_capabilities"] == ["CP1"] + + +def test_claims_challenge(): + """get_token should pass any claims challenge to MSAL token acquisition APIs""" + + expected_claims = '{"access_token": {"essential": "true"}' + + record = AuthenticationRecord("tenant-id", "client_id", "authority", "home_account_id", "username") + + msal_app = Mock() + msal_app.get_accounts.return_value = [{"home_account_id": record.home_account_id}] + msal_app.acquire_token_silent_with_error.return_value = dict( + build_aad_response(access_token="**", id_token=build_id_token()) + ) + + transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) + credential = SharedTokenCacheCredential(transport=transport, authentication_record=record, _cache=TokenCache()) + with patch(SharedTokenCacheCredential.__module__ + ".PublicClientApplication", lambda *_, **__: msal_app): + credential.get_token("scope", claims_challenge=expected_claims) + + assert msal_app.acquire_token_silent_with_error.call_count == 1 + args, kwargs = msal_app.acquire_token_silent_with_error.call_args + assert kwargs["claims_challenge"] == expected_claims + + def get_account_event( username, uid, utid, authority=None, client_id="client-id", refresh_token="refresh-token", scopes=None, **kwargs ): diff --git a/sdk/identity/azure-identity/tests/test_username_password_credential.py b/sdk/identity/azure-identity/tests/test_username_password_credential.py index ffa529163209..baefce5f7bd4 100644 --- a/sdk/identity/azure-identity/tests/test_username_password_credential.py +++ b/sdk/identity/azure-identity/tests/test_username_password_credential.py @@ -11,6 +11,7 @@ build_aad_response, build_id_token, get_discovery_response, + id_token_claims, mock_response, Request, validating_transport, @@ -145,3 +146,46 @@ def test_authenticate(): # credential should have a cached access token for the scope passed to authenticate token = credential.get_token(scope) assert token.token == access_token + + +def test_client_capabilities(): + """the credential should configure MSAL for capability CP1 (ability to handle claims challenges)""" + + transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) + credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) + + with patch("msal.PublicClientApplication") as PublicClientApplication: + credential._get_app() + + assert PublicClientApplication.call_count == 1 + _, kwargs = PublicClientApplication.call_args + assert kwargs["client_capabilities"] == ["CP1"] + + +def test_claims_challenge(): + """get_token should pass any claims challenge to MSAL token acquisition APIs""" + + msal_acquire_token_result = dict( + build_aad_response(access_token="**", id_token=build_id_token()), + id_token_claims=id_token_claims("issuer", "subject", "audience", upn="upn"), + ) + expected_claims = '{"access_token": {"essential": "true"}' + + transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) + credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) + with patch.object(UsernamePasswordCredential, "_get_app") as get_mock_app: + msal_app = get_mock_app() + msal_app.acquire_token_by_username_password.return_value = msal_acquire_token_result + credential.get_token("scope", claims_challenge=expected_claims) + + assert msal_app.acquire_token_by_username_password.call_count == 1 + args, kwargs = msal_app.acquire_token_by_username_password.call_args + assert kwargs["claims_challenge"] == expected_claims + + msal_app.get_accounts.return_value = [{"home_account_id": credential._auth_record.home_account_id}] + msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result + credential.get_token("scope", claims_challenge=expected_claims) + + assert msal_app.acquire_token_silent_with_error.call_count == 1 + args, kwargs = msal_app.acquire_token_silent_with_error.call_args + assert kwargs["claims_challenge"] == expected_claims From 8390578d17624891370f240c4624d8b8555af8c0 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 27 Jan 2021 14:05:05 -0800 Subject: [PATCH 4/4] claims_challenge -> claims --- .../azure-identity/azure/identity/_credentials/browser.py | 2 +- .../azure/identity/_credentials/device_code.py | 5 ++--- .../azure/identity/_credentials/shared_cache.py | 4 +++- .../azure/identity/_credentials/user_password.py | 2 +- .../azure-identity/azure/identity/_internal/interactive.py | 6 +++--- .../azure-identity/tests/test_browser_credential.py | 4 ++-- .../azure-identity/tests/test_device_code_credential.py | 4 ++-- .../azure-identity/tests/test_shared_cache_credential.py | 2 +- .../tests/test_username_password_credential.py | 4 ++-- 9 files changed, 17 insertions(+), 16 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/browser.py b/sdk/identity/azure-identity/azure/identity/_credentials/browser.py index b5386f8d8aeb..18dd793ca1fb 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/browser.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/browser.py @@ -114,7 +114,7 @@ def _request_token(self, *scopes, **kwargs): # redeem the authorization code for a token code = self._parse_response(request_state, response) return app.acquire_token_by_authorization_code( - code, scopes=scopes, redirect_uri=redirect_uri, claims_challenge=kwargs.get("claims_challenge") + code, scopes=scopes, redirect_uri=redirect_uri, claims_challenge=kwargs.get("claims") ) @staticmethod diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py b/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py index af9db0ba139c..f8acc94e34ee 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/device_code.py @@ -84,16 +84,15 @@ def _request_token(self, *scopes, **kwargs): else: print(flow["message"]) - claims_challenge = kwargs.get("claims_challenge") if self._timeout is not None and self._timeout < flow["expires_in"]: # user specified an effective timeout we will observe deadline = int(time.time()) + self._timeout result = app.acquire_token_by_device_flow( - flow, exit_condition=lambda flow: time.time() > deadline, claims_challenge=claims_challenge + flow, exit_condition=lambda flow: time.time() > deadline, claims_challenge=kwargs.get("claims") ) else: # MSAL will stop polling when the device code expires - result = app.acquire_token_by_device_flow(flow, claims_challenge=claims_challenge) + result = app.acquire_token_by_device_flow(flow, claims_challenge=kwargs.get("claims")) if "access_token" not in result: if result.get("error") == "authorization_pending": diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py b/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py index edeaa6a06752..c5469dfdbcc9 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py @@ -71,6 +71,8 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument This method is called automatically by Azure SDK clients. :param str scopes: desired scopes for the access token. This method requires at least one scope. + :keyword str claims: additional claims required in the token, such as those returned in a resource provider's + claims challenge following an authorization failure :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.identity.CredentialUnavailableError: the cache is unavailable or contains insufficient user information @@ -148,7 +150,7 @@ def _acquire_token_silent(self, *scopes, **kwargs): now = int(time.time()) result = self._app.acquire_token_silent_with_error( - list(scopes), account=account, claims_challenge=kwargs.get("claims_challenge") + list(scopes), account=account, claims_challenge=kwargs.get("claims") ) if result and "access_token" in result and "expires_in" in result: return AccessToken(result["access_token"], now + int(result["expires_in"])) diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py b/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py index c45f8abe2db0..a7bb9975d60c 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/user_password.py @@ -59,5 +59,5 @@ def _request_token(self, *scopes, **kwargs): username=self._username, password=self._password, scopes=list(scopes), - claims_challenge=kwargs.get("claims_challenge"), + claims_challenge=kwargs.get("claims"), ) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py index 8bf71f4f57c3..00ce1ace6f54 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py @@ -103,8 +103,8 @@ def get_token(self, *scopes, **kwargs): This method is called automatically by Azure SDK clients. :param str scopes: desired scopes for the access token. This method requires at least one scope. - :keyword str claims_challenge: a claims challenge returned by a resource provider following an authorization - failure + :keyword str claims: additional claims required in the token, such as those returned in a resource provider's + claims challenge following an authorization failure :rtype: :class:`azure.core.credentials.AccessToken` :raises CredentialUnavailableError: the credential is unable to attempt authentication because it lacks required data, state, or platform support @@ -190,7 +190,7 @@ def _acquire_token_silent(self, *scopes, **kwargs): now = int(time.time()) result = app.acquire_token_silent_with_error( - list(scopes), account=account, claims_challenge=kwargs.get("claims_challenge") + list(scopes), account=account, claims_challenge=kwargs.get("claims") ) if result and "access_token" in result and "expires_in" in result: return AccessToken(result["access_token"], now + int(result["expires_in"])) diff --git a/sdk/identity/azure-identity/tests/test_browser_credential.py b/sdk/identity/azure-identity/tests/test_browser_credential.py index 867b32e5c539..191521f63cc5 100644 --- a/sdk/identity/azure-identity/tests/test_browser_credential.py +++ b/sdk/identity/azure-identity/tests/test_browser_credential.py @@ -386,7 +386,7 @@ def test_claims_challenge(): with patch(InteractiveBrowserCredential.__module__ + ".uuid.uuid4", lambda: oauth_state): with patch(WEBBROWSER_OPEN, lambda _: True): - credential.get_token("scope", claims_challenge=expected_claims) + credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_by_authorization_code.call_count == 1 args, kwargs = msal_app.acquire_token_by_authorization_code.call_args @@ -394,7 +394,7 @@ def test_claims_challenge(): msal_app.get_accounts.return_value = [{"home_account_id": credential._auth_record.home_account_id}] msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result - credential.get_token("scope", claims_challenge=expected_claims) + credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_silent_with_error.call_count == 1 args, kwargs = msal_app.acquire_token_silent_with_error.call_args diff --git a/sdk/identity/azure-identity/tests/test_device_code_credential.py b/sdk/identity/azure-identity/tests/test_device_code_credential.py index b381e1ed7d19..3924ae6036b8 100644 --- a/sdk/identity/azure-identity/tests/test_device_code_credential.py +++ b/sdk/identity/azure-identity/tests/test_device_code_credential.py @@ -292,7 +292,7 @@ def test_claims_challenge(): msal_app = get_mock_app() msal_app.initiate_device_flow.return_value = {"message": "it worked"} msal_app.acquire_token_by_device_flow.return_value = msal_acquire_token_result - credential.get_token("scope", claims_challenge=expected_claims) + credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_by_device_flow.call_count == 1 args, kwargs = msal_app.acquire_token_by_device_flow.call_args @@ -300,7 +300,7 @@ def test_claims_challenge(): msal_app.get_accounts.return_value = [{"home_account_id": credential._auth_record.home_account_id}] msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result - credential.get_token("scope", claims_challenge=expected_claims) + credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_silent_with_error.call_count == 1 args, kwargs = msal_app.acquire_token_silent_with_error.call_args diff --git a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py index c6778cce1b43..593eb560273b 100644 --- a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py +++ b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py @@ -831,7 +831,7 @@ def test_claims_challenge(): transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) credential = SharedTokenCacheCredential(transport=transport, authentication_record=record, _cache=TokenCache()) with patch(SharedTokenCacheCredential.__module__ + ".PublicClientApplication", lambda *_, **__: msal_app): - credential.get_token("scope", claims_challenge=expected_claims) + credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_silent_with_error.call_count == 1 args, kwargs = msal_app.acquire_token_silent_with_error.call_args diff --git a/sdk/identity/azure-identity/tests/test_username_password_credential.py b/sdk/identity/azure-identity/tests/test_username_password_credential.py index baefce5f7bd4..ea9ad5005677 100644 --- a/sdk/identity/azure-identity/tests/test_username_password_credential.py +++ b/sdk/identity/azure-identity/tests/test_username_password_credential.py @@ -176,7 +176,7 @@ def test_claims_challenge(): with patch.object(UsernamePasswordCredential, "_get_app") as get_mock_app: msal_app = get_mock_app() msal_app.acquire_token_by_username_password.return_value = msal_acquire_token_result - credential.get_token("scope", claims_challenge=expected_claims) + credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_by_username_password.call_count == 1 args, kwargs = msal_app.acquire_token_by_username_password.call_args @@ -184,7 +184,7 @@ def test_claims_challenge(): msal_app.get_accounts.return_value = [{"home_account_id": credential._auth_record.home_account_id}] msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result - credential.get_token("scope", claims_challenge=expected_claims) + credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_silent_with_error.call_count == 1 args, kwargs = msal_app.acquire_token_silent_with_error.call_args