From bc7c6e97132b6b1781b9d8cf6e2b2fb4d77c82e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Wed, 13 Oct 2021 17:32:24 -0700 Subject: [PATCH 01/15] Added support in keys --- sdk/keyvault/azure-keyvault-keys/CHANGELOG.md | 4 ++ .../_shared/async_challenge_auth_policy.py | 4 +- .../keys/_shared/challenge_auth_policy.py | 4 +- .../keyvault/keys/_shared/http_challenge.py | 6 ++ .../tests/test_challenge_auth.py | 56 ++++++++++++++++++- .../tests/test_challenge_auth_async.py | 53 +++++++++++++++++- 6 files changed, 122 insertions(+), 5 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md index 8e3c353635c3..6076cc297aa6 100644 --- a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md @@ -4,6 +4,10 @@ ### Features Added +- Added support for multi-tenant authentication against Key Vault and Managed HSM when using + `azure-identity` 1.7.0 or newer + ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index 97f1d093e20f..db584d239c55 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -73,7 +73,9 @@ async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpCh if self._need_new_token: # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope) + # pass the tenant ID from the challenge to support multi-tenant authentication when possible + tenant_id = challenge.get_tenant_id() + self._token = await self._credential.get_token(scope, tenant_id=tenant_id) # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 3239032e9162..7a28dac58f35 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -134,7 +134,9 @@ def _handle_challenge(self, request, challenge): if self._need_new_token: # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope) + # pass the tenant ID from the challenge to support multi-tenant authentication when possible + tenant_id = challenge.get_tenant_id() + self._token = self._credential.get_token(scope, tenant_id=tenant_id) # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py index c762e1ae50ef..c83c9f6e788e 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py @@ -82,6 +82,12 @@ def get_scope(self): """ Returns the scope if present, otherwise empty string. """ return self.get_value("scope") or "" + def get_tenant_id(self): + """ Returns the tenant ID parsed from the authorization server """ + authorization_uri = self.get_authorization_server() + # the authoritzation server URI should look something like https://login.windows.net/tenant-id + return authorization_uri.split("/")[-1] + def supports_pop(self): """ Returns True if challenge supports pop token auth else False """ return self._parameters.get("supportspop", "").lower() == "true" diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py index d4f1eff32af8..acf1378b2657 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py @@ -74,7 +74,8 @@ def test_challenge_cache(): def test_challenge_parsing(): - authority = "https://login.authority.net/tenant" + tenant = "tenant" + authority = "https://login.authority.net/{}".format(tenant) resource = "https://challenge.resource" challenge = HttpChallenge( "https://request.uri", challenge="Bearer authorization={}, resource={}".format(authority, resource) @@ -82,6 +83,7 @@ def test_challenge_parsing(): assert challenge.get_authorization_server() == authority assert challenge.get_resource() == resource + assert challenge.get_tenant_id() == tenant @empty_challenge_cache @@ -111,7 +113,7 @@ def send(request): return Mock(status_code=200) raise ValueError("unexpected request") - def get_token(*scopes): + def get_token(*scopes, **_): assert len(scopes) == 1 assert scopes[0] == expected_scope return AccessToken(expected_token, 0) @@ -143,6 +145,56 @@ def get_token(*scopes): test_with_challenge(challenge_with_scope, scope) +@empty_challenge_cache +def test_tenant(): + """The policy's token requests should pass the parsed tenant ID from the challenge""" + + expected_content = b"a duck" + + def test_with_challenge(challenge, expected_tenant): + expected_token = "expected_token" + + class Requests: + count = 0 + + def send(request): + Requests.count += 1 + if Requests.count == 1: + # first request should be unauthorized and have no content + assert not request.body + assert request.headers["Content-Length"] == "0" + return challenge + elif Requests.count == 2: + # second request should be authorized according to challenge and have the expected content + assert request.headers["Content-Length"] + assert request.body == expected_content + assert expected_token in request.headers["Authorization"] + return Mock(status_code=200) + raise ValueError("unexpected request") + + def get_token(*_, **kwargs): + assert kwargs.get("tenant_id") == expected_tenant + return AccessToken(expected_token, 0) + + credential = Mock(get_token=Mock(wraps=get_token)) + pipeline = Pipeline(policies=[ChallengeAuthPolicy(credential=credential)], transport=Mock(send=send)) + request = HttpRequest("POST", get_random_url()) + request.set_bytes_body(expected_content) + pipeline.run(request) + + assert credential.get_token.call_count == 1 + + tenant = "tenant-id" + endpoint = "https://authority.net/{}".format(tenant) + + challenge = Mock( + status_code=401, + headers={"WWW-Authenticate": 'Bearer authorization="{}", resource=https://challenge.resource'.format(endpoint)}, + ) + + test_with_challenge(challenge, tenant) + + @empty_challenge_cache def test_policy_updates_cache(): """ diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py index cf00e192ffa2..b6aa078f06d9 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py @@ -65,7 +65,7 @@ async def send(request): return Mock(status_code=200) raise ValueError("unexpected request") - async def get_token(*scopes): + async def get_token(*scopes, **_): assert len(scopes) == 1 assert scopes[0] == expected_scope return AccessToken(expected_token, 0) @@ -97,6 +97,57 @@ async def get_token(*scopes): await test_with_challenge(challenge_with_scope, scope) +@pytest.mark.asyncio +@empty_challenge_cache +async def test_tenant(): + """The policy's token requests should pass the parsed tenant ID from the challenge""" + + expected_content = b"a duck" + + async def test_with_challenge(challenge, expected_tenant): + expected_token = "expected_token" + + class Requests: + count = 0 + + async def send(request): + Requests.count += 1 + if Requests.count == 1: + # first request should be unauthorized and have no content + assert not request.body + assert request.headers["Content-Length"] == "0" + return challenge + elif Requests.count == 2: + # second request should be authorized according to challenge and have the expected content + assert request.headers["Content-Length"] + assert request.body == expected_content + assert expected_token in request.headers["Authorization"] + return Mock(status_code=200) + raise ValueError("unexpected request") + + async def get_token(*_, **kwargs): + assert kwargs.get("tenant_id") == expected_tenant + return AccessToken(expected_token, 0) + + credential = Mock(get_token=Mock(wraps=get_token)) + pipeline = AsyncPipeline(policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=Mock(send=send)) + request = HttpRequest("POST", get_random_url()) + request.set_bytes_body(expected_content) + await pipeline.run(request) + + assert credential.get_token.call_count == 1 + + tenant = "tenant-id" + endpoint = "https://authority.net/{}".format(tenant) + + challenge = Mock( + status_code=401, + headers={"WWW-Authenticate": 'Bearer authorization="{}", resource=https://challenge.resource'.format(endpoint)}, + ) + + await test_with_challenge(challenge, tenant) + + @pytest.mark.asyncio @empty_challenge_cache async def test_policy_updates_cache(): From 0b999e6d0e7e911a08929b35382f66278340e5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Thu, 14 Oct 2021 10:31:16 -0700 Subject: [PATCH 02/15] Update policies in certs, secrets --- .../certificates/_shared/async_challenge_auth_policy.py | 4 +++- .../keyvault/certificates/_shared/challenge_auth_policy.py | 4 +++- .../azure/keyvault/certificates/_shared/http_challenge.py | 6 ++++++ .../keyvault/secrets/_shared/async_challenge_auth_policy.py | 4 +++- .../azure/keyvault/secrets/_shared/challenge_auth_policy.py | 4 +++- .../azure/keyvault/secrets/_shared/http_challenge.py | 6 ++++++ 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index 97f1d093e20f..db584d239c55 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -73,7 +73,9 @@ async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpCh if self._need_new_token: # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope) + # pass the tenant ID from the challenge to support multi-tenant authentication when possible + tenant_id = challenge.get_tenant_id() + self._token = await self._credential.get_token(scope, tenant_id=tenant_id) # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index 3239032e9162..7a28dac58f35 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -134,7 +134,9 @@ def _handle_challenge(self, request, challenge): if self._need_new_token: # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope) + # pass the tenant ID from the challenge to support multi-tenant authentication when possible + tenant_id = challenge.get_tenant_id() + self._token = self._credential.get_token(scope, tenant_id=tenant_id) # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py index c762e1ae50ef..c83c9f6e788e 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py @@ -82,6 +82,12 @@ def get_scope(self): """ Returns the scope if present, otherwise empty string. """ return self.get_value("scope") or "" + def get_tenant_id(self): + """ Returns the tenant ID parsed from the authorization server """ + authorization_uri = self.get_authorization_server() + # the authoritzation server URI should look something like https://login.windows.net/tenant-id + return authorization_uri.split("/")[-1] + def supports_pop(self): """ Returns True if challenge supports pop token auth else False """ return self._parameters.get("supportspop", "").lower() == "true" diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index 97f1d093e20f..db584d239c55 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -73,7 +73,9 @@ async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpCh if self._need_new_token: # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope) + # pass the tenant ID from the challenge to support multi-tenant authentication when possible + tenant_id = challenge.get_tenant_id() + self._token = await self._credential.get_token(scope, tenant_id=tenant_id) # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index 3239032e9162..7a28dac58f35 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -134,7 +134,9 @@ def _handle_challenge(self, request, challenge): if self._need_new_token: # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope) + # pass the tenant ID from the challenge to support multi-tenant authentication when possible + tenant_id = challenge.get_tenant_id() + self._token = self._credential.get_token(scope, tenant_id=tenant_id) # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py index c762e1ae50ef..c83c9f6e788e 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py @@ -82,6 +82,12 @@ def get_scope(self): """ Returns the scope if present, otherwise empty string. """ return self.get_value("scope") or "" + def get_tenant_id(self): + """ Returns the tenant ID parsed from the authorization server """ + authorization_uri = self.get_authorization_server() + # the authoritzation server URI should look something like https://login.windows.net/tenant-id + return authorization_uri.split("/")[-1] + def supports_pop(self): """ Returns True if challenge supports pop token auth else False """ return self._parameters.get("supportspop", "").lower() == "true" From 9c4f0895b19ed8b66ecd47f94c61043e3d2e7d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Thu, 14 Oct 2021 18:08:44 -0700 Subject: [PATCH 03/15] Add live tests --- .../azure-keyvault-certificates/CHANGELOG.md | 4 + ...enant_authentication_2016_10_01_vault.yaml | 102 ++++++++++++++++++ ..._multitenant_authentication_7_0_vault.yaml | 102 ++++++++++++++++++ ..._multitenant_authentication_7_1_vault.yaml | 102 ++++++++++++++++++ ...t_multitenant_authentication_7_2_mhsm.yaml | 89 +++++++++++++++ ..._multitenant_authentication_7_2_vault.yaml | 102 ++++++++++++++++++ ...enant_authentication_7_3_preview_mhsm.yaml | 89 +++++++++++++++ ...nant_authentication_7_3_preview_vault.yaml | 102 ++++++++++++++++++ ...enant_authentication_2016_10_01_vault.yaml | 38 +++++++ ..._multitenant_authentication_7_0_vault.yaml | 38 +++++++ ..._multitenant_authentication_7_1_vault.yaml | 38 +++++++ ...t_multitenant_authentication_7_2_mhsm.yaml | 33 ++++++ ..._multitenant_authentication_7_2_vault.yaml | 38 +++++++ ...enant_authentication_7_3_preview_mhsm.yaml | 33 ++++++ ...nant_authentication_7_3_preview_vault.yaml | 38 +++++++ .../tests/test_challenge_auth.py | 34 ++++++ .../tests/test_challenge_auth_async.py | 36 +++++++ .../azure-keyvault-secrets/CHANGELOG.md | 4 + 18 files changed, 1022 insertions(+) create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml create mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml diff --git a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md index f399e57e03c3..3538f484869f 100644 --- a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md @@ -4,6 +4,10 @@ ### Features Added +- Added support for multi-tenant authentication against Key Vault and Managed HSM when using + `azure-identity` 1.7.0 or newer + ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml new file mode 100644 index 000000000000..d54daa2b5434 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml @@ -0,0 +1,102 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/create?api-version=2016-10-01 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: + - no-cache + content-length: + - '97' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:11 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/create?api-version=2016-10-01 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/bae76c028ea04c418ae65eb4fba413d9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zHqAr-igexm9D4a2srndcV63YRVBwhPer10mM2fusbb4PNLK1RWHWD_kot6GKVA1WX-NeuODula1UpM7-dOtQS-Qrv4W15_EHebyZZ3YcXIK-mCFTsYGFGLvvM5pOn6GK_qoEont-voDm0-Q0LYmRv1TYi1ScSm3Ib1tQtw4Thvz-2QWF_jeHwuDk2GbbTa4iGq5WI6zin-Oj3OzlW3QQYd7eeFru_K0dCLumaaC_L_Eu-lKfXQydbylXFKIWRBHVIsxxKogOgzTfCA-yFiCd_Opns8ixkiYHHvaXICbJo1hkl0T0LiHnniJFJokf9rIeb2_yUFtv81YP1dOsLChNQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332151,"updated":1634332151,"recoveryLevel":"Recoverable+Purgeable"}}' + headers: + cache-control: + - no-cache + content-length: + - '680' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:11 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml new file mode 100644 index 000000000000..d72b3902c05e --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml @@ -0,0 +1,102 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/create?api-version=7.0 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: + - no-cache + content-length: + - '97' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/create?api-version=7.0 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/bbae0bcd529d465986b037daedcde2fa","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s6zLYcP7e4_JZy1uHRGrzGeggyc--m1k_9LWq6rHP16OcgdRYs0qc5NOsxDsEr8GWJiZnaHVeE8AjDCtUXUFlP46vDTgUT29QjSYIhvLjB7i0XqORDVq6dqd4GnFTLwTeh6-SvWLT2HvMQ_8YOL47-fXbcxWN54X0sZzqs9FeRZnPFnngec6StLan6f2DPFED_qZTRH51EAbk3nJHrhk5ivUYucwr4icwN0mL1swsi9YbO3xH8ok_DwbjBg0wg1Ioeap_9oJoCOOaW5I_8ke_aBvCFlErZkfrRpVjtb2fRECfoOyX1aWjrR2-WSUoyEN5ZEkw7idmDncx57cJphqrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332154,"updated":1634332154,"recoveryLevel":"Recoverable+Purgeable"}}' + headers: + cache-control: + - no-cache + content-length: + - '679' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml new file mode 100644 index 000000000000..4281cfb19206 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml @@ -0,0 +1,102 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/create?api-version=7.1 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: + - no-cache + content-length: + - '97' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:16 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/create?api-version=7.1 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/cdb518f0e9dc4ea08e666c75312c7a7e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"9pkY7RQSweiKIjzliBWnXd3oF2mqu04ibsjLeeLkXm6kKGOWvTT5FjFkXWBP8c383fd56bt4xewfHBDgm_S-XL3I7Es09tzyOEMzVWGpiseNt3Gfff6ZlYEPEGokMx6dAsmMHakzKOQGcSmTfd294onrIYOfn7gGVM5iW-bz5RBbeN44kJYvhD7Y6xUqKn6WhskGJ0HLgIQWdEm2YfgYnUEVz_50Ji5lzemWMtdSF3oveFnizUhh8KAcGkbg9qxSak2vzKJPt6YeqqPaKwOvIUECvQTLfmr5RtB8ZA5SC2rbdXgIjk_UE3amgMPBeSJ2rxAXWZKoYfLXmT7OP3vWxQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332157,"updated":1634332157,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: + - no-cache + content-length: + - '700' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:17 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml new file mode 100644 index 000000000000..16bd76feb2d9 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml @@ -0,0 +1,89 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/create?api-version=7.2 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + content-security-policy: + - default-src 'self' + content-type: + - application/json; charset=utf-8 + strict-transport-security: + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://managedhsm.azure.net" + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ms-server-latency: + - '0' + status: + code: 401 + message: Unauthorized +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/create?api-version=7.2 + response: + body: + string: '{"attributes":{"created":1634332160,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1634332160},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/11223d7f97bd00e2adbbd1734f8915ce","kty":"RSA-HSM","n":"p63FDvI8jWvyHP-3yvibhEv7XMyMbJms7am84eUkQA5vE0NDLjffbXXBF5iZscsQyHkE0pRUo6ZRhD8aU-TfUrVCHeblMrDx8_l3bBB8kpp00tHMOgYiRpVKyhYSleQ0sjDkxo28zDFuNlNqDfjtNKfuKeZV9iURHeSYk9qRwVUvSeblL3j-35l26RMN-RFwZGuGVOxN34hYbIhws6NRDDkF085dVHj3_Q192edyrSpuySpxT-QC-GetGqqWmY3XCv2bUBRHZXiVpvzqEc_g4_TeKIZvwXs5QKDJ4VdOVGg8Vqbk7R3sYWb1IEsBnHaBud6LBKDhxbwVE_5xjv54Vw"}}' + headers: + cache-control: + - no-cache + content-length: + - '741' + content-security-policy: + - default-src 'self' + content-type: + - application/json; charset=utf-8 + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: + - westus + x-ms-server-latency: + - '781' + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml new file mode 100644 index 000000000000..efab0417d2db --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml @@ -0,0 +1,102 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/create?api-version=7.2 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: + - no-cache + content-length: + - '97' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:22 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/create?api-version=7.2 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/4d4b6e65855847eeb846ef35a8fd0c66","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1h6dQJRspvZPzF8Wj2G3zoDnoX4sFvaby2cNMtHoEq4Z04FUSEE9V9GjE-yhe9JUfHMHZTenwwhqt_kHKeIGyDYgHCE_RLfix8kIjN-K_kqLrm17bZW2-v0olSwAEFVOulFH5seN6QNaIQ3MsCdap3hR-LefWCoAwXaK66li29H6veQV5IjEZkArtrHRoBYc9qNvp5WGSwC4KaIJTupkVj95s5Trtg6_e6lhIq_xc3SG0dDZuaiAS2gmFBFdiriJtP_Ihp5kqJ57cFmFLrhSOzsbUyt9_O6nv4ojCsLqHxfuYsrhS5Ymea45f5khX4ZcBnQCmkZwhXNI4JO7TuWbDQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332163,"updated":1634332163,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: + - no-cache + content-length: + - '700' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml new file mode 100644 index 000000000000..8dcb65c90a77 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -0,0 +1,89 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/create?api-version=7.3-preview + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + content-security-policy: + - default-src 'self' + content-type: + - application/json; charset=utf-8 + strict-transport-security: + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://managedhsm.azure.net" + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ms-server-latency: + - '0' + status: + code: 401 + message: Unauthorized +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/create?api-version=7.3-preview + response: + body: + string: '{"attributes":{"created":1634332167,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1634332167},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/0656857aaf684753ba8d7c5c375da89c","kty":"RSA-HSM","n":"iJT8MiXcnFT6p1CEpwW5SuAUEHpUKrofqbYYcsB5GVFPJW_AsoxypERUSY8ZjgxY7_FmnExXUI4lDN86Nbe_korrVwMI1DLY3wZ-Oqh5JmAIKi9V6t-NVz-VIHN9fGJgf56UcJ5oc031qvPjrPJk8GRwXWtUoA4zBeBgYzFLktISsvV4CVDJD-sVp6K-_rfT2xC44-3-feebZE3z3m02pRaF40oy2t4UNd-EhSwVkL_s8JR4b759pvjUf_0untgEuEKRE6QbePf5wzhmO7_oPzyF52QuWWa3Eqz_rXISAFFa11ZA97j6P4ylV5-fajheC9jqZRPeZaibGuVdCTCU5Q"}}' + headers: + cache-control: + - no-cache + content-length: + - '741' + content-security-policy: + - default-src 'self' + content-type: + - application/json; charset=utf-8 + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: + - westus + x-ms-server-latency: + - '735' + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml new file mode 100644 index 000000000000..7407436528e6 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml @@ -0,0 +1,102 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/create?api-version=7.3-preview + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: + - no-cache + content-length: + - '97' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:29 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/create?api-version=7.3-preview + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/e186e5bd9c104703ac32dcd859a5a104","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yvdc3KPSBHWzA-FRSXY8Af6QA_eJiFbyNwpgbiQMNS4qCixGAIPaD1hluZVFSlzHSZcNKsdjSEX9nK88M1l_WHJMFXQ3tgZPOmKttMiROPfHvfJ5_BVo73x3zMRFAvc-DWL_9ILvPLD0es_8os00267MDMKBrF8NHhDjzxesa6b2ublJKmvgyJiWaf0OYOs4atIYt6UUxs0s4I7oOzg6Uki9Slqvk_6SDh4cgRzyU4yKyNU3Ah2HQCgJjav-6LD-gvFQT6M4KM7h8OT-Jfwj05gD_BamQCokQXBcCz_O9SXgQlGS0nrLDdhioKOvxlV9ns7zd7g7jU_uQ7Q8nBZ7_Q","e":"AQAB"},"attributes":{"enabled":true,"created":1634332170,"updated":1634332170,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: + - no-cache + content-length: + - '701' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 15 Oct 2021 21:09:29 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml new file mode 100644 index 000000000000..0eb6dd0df1ff --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key62211cc5/create?api-version=2016-10-01 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: no-cache + content-length: '97' + content-type: application/json; charset=utf-8 + date: Fri, 15 Oct 2021 21:08:31 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + www-authenticate: Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 401 + message: Unauthorized + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key62211cc5/create?api-version=2016-10-01 +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml new file mode 100644 index 000000000000..eba4f5bb23af --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6b91b42/create?api-version=7.0 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: no-cache + content-length: '97' + content-type: application/json; charset=utf-8 + date: Fri, 15 Oct 2021 21:08:34 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + www-authenticate: Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 401 + message: Unauthorized + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6b91b42/create?api-version=7.0 +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml new file mode 100644 index 000000000000..2bb817e2bb79 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c01b43/create?api-version=7.1 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: no-cache + content-length: '97' + content-type: application/json; charset=utf-8 + date: Fri, 15 Oct 2021 21:08:36 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + www-authenticate: Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 401 + message: Unauthorized + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6c01b43/create?api-version=7.1 +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml new file mode 100644 index 000000000000..9479aa68df45 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml @@ -0,0 +1,33 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key8b711acd/create?api-version=7.2 + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + content-security-policy: default-src 'self' + content-type: application/json; charset=utf-8 + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://managedhsm.azure.net" + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + x-ms-server-latency: '1' + status: + code: 401 + message: Unauthorized + url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key8b711acd/create?api-version=7.2 +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml new file mode 100644 index 000000000000..201aed885ccf --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c71b44/create?api-version=7.2 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: no-cache + content-length: '97' + content-type: application/json; charset=utf-8 + date: Fri, 15 Oct 2021 21:08:41 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + www-authenticate: Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 401 + message: Unauthorized + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6c71b44/create?api-version=7.2 +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml new file mode 100644 index 000000000000..a799ab7d477c --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -0,0 +1,33 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + content-security-policy: default-src 'self' + content-type: application/json; charset=utf-8 + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://managedhsm.azure.net" + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + x-ms-server-latency: '1' + status: + code: 401 + message: Unauthorized + url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml new file mode 100644 index 000000000000..d15a62879cbc --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview + response: + body: + string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing + a Bearer or PoP token."}}' + headers: + cache-control: no-cache + content-length: '97' + content-type: application/json; charset=utf-8 + date: Fri, 15 Oct 2021 21:08:46 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + www-authenticate: Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 401 + message: Unauthorized + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview +version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py index acf1378b2657..bd1ca41151d2 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py @@ -7,6 +7,7 @@ the challenge cache is global to the process. """ import functools +import os import time from uuid import uuid4 @@ -20,11 +21,44 @@ from azure.core.pipeline import Pipeline from azure.core.pipeline.policies import SansIOHTTPPolicy from azure.core.pipeline.transport import HttpRequest +from azure.identity import ClientSecretCredential +from azure.keyvault.keys import KeyClient from azure.keyvault.keys._shared import ChallengeAuthPolicy, HttpChallenge, HttpChallengeCache import pytest from _shared.helpers import mock_response, Request, validating_transport +from _shared.test_case import KeyVaultTestCase +from _test_case import client_setup, get_decorator, KeysTestCase + + +all_api_versions = get_decorator() + + +class ChallengeAuthTests(KeysTestCase, KeyVaultTestCase): + def __init__(self, *args, **kwargs): + super(ChallengeAuthTests, self).__init__(*args, match_body=False, **kwargs) + + @all_api_versions() + @client_setup + def test_multitenant_authentication(self, client, is_hsm, **kwargs): + client_id = os.environ.get("KEYVAULT_CLIENT_ID") + client_secret = os.environ.get("KEYVAULT_CLIENT_SECRET") + if self.is_live and not client_id and client_secret: + pytest.skip("Values for KEYVAULT_CLIENT_ID and KEYVAULT_CLIENT_SECRET are required") + + # we set up a client for this method to align with the async test, but we actually want to create a new client + # this new client should use a credential with an initially fake tenant ID and still succeed with a real request + api_version = client.api_version + credential = ClientSecretCredential(tenant_id=str(uuid4()), client_id=client_id, client_secret=client_secret) + vault_url = self.managed_hsm_url if is_hsm else self.vault_url + client = KeyClient(vault_url=vault_url, credential=credential, api_version=api_version) + + if self.is_live: + time.sleep(2) # to avoid throttling by the service + key_name = self.get_resource_name("multitenant-key") + key = client.create_rsa_key(key_name) + assert key.id def empty_challenge_cache(fn): diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py index b6aa078f06d9..cef73c7132ca 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py @@ -6,7 +6,10 @@ Tests for the HTTP challenge authentication implementation. These tests aren't parallelizable, because the challenge cache is global to the process. """ +import asyncio +import os import time +from uuid import uuid4 try: from unittest.mock import Mock, patch @@ -17,14 +20,47 @@ from azure.core.exceptions import ServiceRequestError from azure.core.pipeline import AsyncPipeline from azure.core.pipeline.transport import HttpRequest +from azure.identity.aio import ClientSecretCredential +from azure.keyvault.keys.aio import KeyClient from azure.keyvault.keys._shared import AsyncChallengeAuthPolicy, HttpChallenge, HttpChallengeCache import pytest from _shared.helpers import mock_response, Request from _shared.helpers_async import async_validating_transport +from _shared.test_case_async import KeyVaultTestCase +from _test_case import client_setup, get_decorator, KeysTestCase from test_challenge_auth import empty_challenge_cache, get_policies_for_request_mutation_test, get_random_url +all_api_versions = get_decorator(is_async=True) + + +class ChallengeAuthTests(KeysTestCase, KeyVaultTestCase): + def __init__(self, *args, **kwargs): + super().__init__(*args, match_body=False, **kwargs) + + @all_api_versions() + @client_setup + async def test_multitenant_authentication(self, client, is_hsm, **kwargs): + client_id = os.environ.get("KEYVAULT_CLIENT_ID") + client_secret = os.environ.get("KEYVAULT_CLIENT_SECRET") + if self.is_live and not client_id and client_secret: + pytest.skip("Values for KEYVAULT_CLIENT_ID and KEYVAULT_CLIENT_SECRET are required") + + # we set up a client for this method so it gets awaited, but we actually want to create a new client + # this new client should use a credential with an initially fake tenant ID and still succeed with a real request + api_version = client.api_version + credential = ClientSecretCredential(tenant_id=str(uuid4()), client_id=client_id, client_secret=client_secret) + vault_url = self.managed_hsm_url if is_hsm else self.vault_url + client = KeyClient(vault_url=vault_url, credential=credential, api_version=api_version) + + if self.is_live: + await asyncio.sleep(2) # to avoid throttling by the service + key_name = self.get_resource_name("multitenant-key") + key = await client.create_rsa_key(key_name) + assert key.id + + @pytest.mark.asyncio @empty_challenge_cache async def test_enforces_tls(): diff --git a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md index 679cdf8ae1ba..0da3a14b2f62 100644 --- a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md @@ -4,6 +4,10 @@ ### Features Added +- Added support for multi-tenant authentication against Key Vault and Managed HSM when using + `azure-identity` 1.7.0 or newer + ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) + ### Breaking Changes ### Bugs Fixed From e50dfb1bc6361db37ecd5f61f5060a5d38ca5a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Thu, 4 Nov 2021 17:40:02 -0700 Subject: [PATCH 04/15] Use BearerTokenCredentialPolicy --- .../CHANGELOG.md | 4 + .../administration/_internal/__init__.py | 3 +- .../_internal/async_challenge_auth_policy.py | 92 ++++++------- .../_internal/challenge_auth_policy.py | 128 +++++++---------- .../_internal/http_challenge.py | 4 + .../azure-keyvault-administration/setup.py | 2 +- .../azure-keyvault-certificates/CHANGELOG.md | 6 +- .../keyvault/certificates/_shared/__init__.py | 3 +- .../_shared/async_challenge_auth_policy.py | 94 ++++++------- .../_shared/challenge_auth_policy.py | 130 +++++++----------- .../certificates/_shared/http_challenge.py | 10 +- .../azure-keyvault-certificates/setup.py | 2 +- sdk/keyvault/azure-keyvault-keys/CHANGELOG.md | 4 +- .../azure/keyvault/keys/_shared/__init__.py | 3 +- .../_shared/async_challenge_auth_policy.py | 94 ++++++------- .../keys/_shared/challenge_auth_policy.py | 130 +++++++----------- .../keyvault/keys/_shared/http_challenge.py | 10 +- sdk/keyvault/azure-keyvault-keys/setup.py | 2 +- .../azure-keyvault-secrets/CHANGELOG.md | 6 +- .../keyvault/secrets/_shared/__init__.py | 3 +- .../_shared/async_challenge_auth_policy.py | 94 ++++++------- .../secrets/_shared/challenge_auth_policy.py | 130 +++++++----------- .../secrets/_shared/http_challenge.py | 10 +- sdk/keyvault/azure-keyvault-secrets/setup.py | 2 +- 24 files changed, 396 insertions(+), 570 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md b/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md index 5bbecd93b402..24334a26b26c 100644 --- a/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md @@ -3,12 +3,16 @@ ## 4.1.0b2 (Unreleased) ### Features Added +- Added support for multi-tenant authentication against Managed HSM when using + `azure-identity` 1.7.1 or newer + ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes ### Bugs Fixed ### Other Changes +- Updated minimum `azure-core` version to 1.15.0 ## 4.1.0b1 (2021-09-09) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/__init__.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/__init__.py index f73bf01e0e89..905e81953480 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/__init__.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/__init__.py @@ -6,7 +6,7 @@ from six.moves.urllib_parse import urlparse -from .challenge_auth_policy import ChallengeAuthPolicy, ChallengeAuthPolicyBase +from .challenge_auth_policy import ChallengeAuthPolicy from .client_base import KeyVaultClientBase from .http_challenge import HttpChallenge from . import http_challenge_cache as HttpChallengeCache @@ -14,7 +14,6 @@ __all__ = [ "ChallengeAuthPolicy", - "ChallengeAuthPolicyBase", "HttpChallenge", "HttpChallengeCache", "KeyVaultClientBase", diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py index 97f1d093e20f..5abf15c3ec3e 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py @@ -15,65 +15,61 @@ """ from typing import TYPE_CHECKING -from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy -from . import HttpChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_request, _update_challenge, ChallengeAuthPolicyBase +from . import http_challenge_cache as ChallengeCache +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any - from azure.core.credentials_async import AsyncTokenCredential - from azure.core.pipeline import PipelineRequest - from azure.core.pipeline.transport import AsyncHttpResponse - from . import HttpChallenge + from typing import Any, Optional + from azure.core.pipeline import PipelineRequest, PipelineResponse -class AsyncChallengeAuthPolicy(ChallengeAuthPolicyBase, AsyncHTTPPolicy): +class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential: "AsyncTokenCredential", **kwargs: "Any") -> None: - self._credential = credential - super(AsyncChallengeAuthPolicy, self).__init__(**kwargs) - - async def send(self, request: "PipelineRequest") -> "AsyncHttpResponse": + def __init__(self, *args: "Any", **kwargs: "Any") -> None: + self._last_tenant_id = None # type: Optional[str] + super().__init__(*args, **kwargs) + + async def on_request(self, request: "PipelineRequest") -> None: + challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + await super().on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" - challenge = HttpChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = await self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: + try: + challenge = _update_challenge(request, response) + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + except ValueError: + return False - return response + self._scopes = (scope,) - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """authenticate according to challenge, add Authorization header to request""" + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope) + self._last_tenant_id = challenge.tenant_id + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py index 3239032e9162..347bd93214c8 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py @@ -14,13 +14,9 @@ protocol again. """ -import copy -import time - from azure.core.exceptions import ServiceRequestError -from azure.core.pipeline import PipelineContext, PipelineRequest -from azure.core.pipeline.policies import HTTPPolicy -from azure.core.pipeline.transport import HttpRequest +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import BearerTokenCredentialPolicy from .http_challenge import HttpChallenge from . import http_challenge_cache as ChallengeCache @@ -32,7 +28,6 @@ if TYPE_CHECKING: from typing import Any, Optional - from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -44,22 +39,6 @@ def _enforce_tls(request): ) -def _get_challenge_request(request): - # type: (PipelineRequest) -> PipelineRequest - - # The challenge request is intended to provoke an authentication challenge from Key Vault, to learn how the - # service request should be authenticated. It should be identical to the service request but with no body. - challenge_request = HttpRequest( - request.http_request.method, request.http_request.url, headers=request.http_request.headers - ) - challenge_request.headers["Content-Length"] = "0" - - options = copy.deepcopy(request.context.options) - context = PipelineContext(request.context.transport, **options) - - return PipelineRequest(http_request=challenge_request, context=context) - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -73,68 +52,53 @@ def _update_challenge(request, challenger): return challenge -class ChallengeAuthPolicyBase(object): - """Sans I/O base for challenge authentication policies""" - - def __init__(self, **kwargs): - self._token = None # type: Optional[AccessToken] - super(ChallengeAuthPolicyBase, self).__init__(**kwargs) - - @property - def _need_new_token(self): - # type: () -> bool - return not self._token or self._token.expires_on - time.time() < 300 - - -class ChallengeAuthPolicy(ChallengeAuthPolicyBase, HTTPPolicy): +class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential, **kwargs): - # type: (TokenCredential, **Any) -> None - self._credential = credential - super(ChallengeAuthPolicy, self).__init__(**kwargs) - - def send(self, request): - # type: (PipelineRequest) -> PipelineResponse - _enforce_tls(request) + def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + self._last_tenant_id = None # type: Optional[str] + super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) + def on_request(self, request): + # type: (PipelineRequest) -> None challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - return response - - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + super(ChallengeAuthPolicy, self).on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. + _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" + + def on_challenge(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> bool + try: + challenge = _update_challenge(request, response) scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope) + except ValueError: + return False + + self._scopes = (scope,) + + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None + + self._last_tenant_id = challenge.tenant_id + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py index c762e1ae50ef..46e668a59867 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py @@ -40,6 +40,10 @@ def __init__(self, request_uri, challenge, response_headers=None): if "authorization" not in self._parameters and "authorization_uri" not in self._parameters: raise ValueError("Invalid challenge parameters") + authorization_uri = self.get_authorization_server() + # the authoritzation server URI should look something like https://login.windows.net/tenant-id + self.tenant_id = authorization_uri.split("/")[-1] or None + # if the response headers were supplied if response_headers: # get the message signing key and message key encryption key from the headers diff --git a/sdk/keyvault/azure-keyvault-administration/setup.py b/sdk/keyvault/azure-keyvault-administration/setup.py index 0231b074702c..9016754edd4b 100644 --- a/sdk/keyvault/azure-keyvault-administration/setup.py +++ b/sdk/keyvault/azure-keyvault-administration/setup.py @@ -81,7 +81,7 @@ "azure.keyvault", ] ), - install_requires=["azure-common~=1.1", "azure-core<2.0.0,>=1.11.0", "msrest>=0.6.21", "six>=1.11.0"], + install_requires=["azure-common~=1.1", "azure-core<2.0.0,>=1.15.0", "msrest>=0.6.21", "six>=1.11.0"], extras_require={ ":python_version<'3.0'": ["azure-keyvault-nspkg"], ":python_version<'3.4'": ["enum34>=1.0.4"], diff --git a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md index 3538f484869f..50230dea41a7 100644 --- a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md @@ -3,9 +3,8 @@ ## 4.4.0b2 (Unreleased) ### Features Added - -- Added support for multi-tenant authentication against Key Vault and Managed HSM when using - `azure-identity` 1.7.0 or newer +- Added support for multi-tenant authentication against Managed HSM when using + `azure-identity` 1.7.1 or newer ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes @@ -13,6 +12,7 @@ ### Bugs Fixed ### Other Changes +- Updated minimum `azure-core` version to 1.15.0 ## 4.4.0b1 (2021-09-09) diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py index 3329ce2df068..d8303b4b081d 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py @@ -9,7 +9,7 @@ import urlparse as parse # type: ignore from typing import TYPE_CHECKING -from .challenge_auth_policy import ChallengeAuthPolicy, ChallengeAuthPolicyBase +from .challenge_auth_policy import ChallengeAuthPolicy from .client_base import KeyVaultClientBase from .http_challenge import HttpChallenge from . import http_challenge_cache as HttpChallengeCache @@ -21,7 +21,6 @@ __all__ = [ "ChallengeAuthPolicy", - "ChallengeAuthPolicyBase", "HttpChallenge", "HttpChallengeCache", "KeyVaultClientBase", diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index db584d239c55..5abf15c3ec3e 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -15,67 +15,61 @@ """ from typing import TYPE_CHECKING -from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy -from . import HttpChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_request, _update_challenge, ChallengeAuthPolicyBase +from . import http_challenge_cache as ChallengeCache +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any - from azure.core.credentials_async import AsyncTokenCredential - from azure.core.pipeline import PipelineRequest - from azure.core.pipeline.transport import AsyncHttpResponse - from . import HttpChallenge + from typing import Any, Optional + from azure.core.pipeline import PipelineRequest, PipelineResponse -class AsyncChallengeAuthPolicy(ChallengeAuthPolicyBase, AsyncHTTPPolicy): +class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential: "AsyncTokenCredential", **kwargs: "Any") -> None: - self._credential = credential - super(AsyncChallengeAuthPolicy, self).__init__(**kwargs) - - async def send(self, request: "PipelineRequest") -> "AsyncHttpResponse": + def __init__(self, *args: "Any", **kwargs: "Any") -> None: + self._last_tenant_id = None # type: Optional[str] + super().__init__(*args, **kwargs) + + async def on_request(self, request: "PipelineRequest") -> None: + challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + await super().on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" - challenge = HttpChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = await self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: + try: + challenge = _update_challenge(request, response) + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + except ValueError: + return False - return response + self._scopes = (scope,) - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """authenticate according to challenge, add Authorization header to request""" + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - # pass the tenant ID from the challenge to support multi-tenant authentication when possible - tenant_id = challenge.get_tenant_id() - self._token = await self._credential.get_token(scope, tenant_id=tenant_id) + self._last_tenant_id = challenge.tenant_id + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index 7a28dac58f35..347bd93214c8 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -14,13 +14,9 @@ protocol again. """ -import copy -import time - from azure.core.exceptions import ServiceRequestError -from azure.core.pipeline import PipelineContext, PipelineRequest -from azure.core.pipeline.policies import HTTPPolicy -from azure.core.pipeline.transport import HttpRequest +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import BearerTokenCredentialPolicy from .http_challenge import HttpChallenge from . import http_challenge_cache as ChallengeCache @@ -32,7 +28,6 @@ if TYPE_CHECKING: from typing import Any, Optional - from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -44,22 +39,6 @@ def _enforce_tls(request): ) -def _get_challenge_request(request): - # type: (PipelineRequest) -> PipelineRequest - - # The challenge request is intended to provoke an authentication challenge from Key Vault, to learn how the - # service request should be authenticated. It should be identical to the service request but with no body. - challenge_request = HttpRequest( - request.http_request.method, request.http_request.url, headers=request.http_request.headers - ) - challenge_request.headers["Content-Length"] = "0" - - options = copy.deepcopy(request.context.options) - context = PipelineContext(request.context.transport, **options) - - return PipelineRequest(http_request=challenge_request, context=context) - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -73,70 +52,53 @@ def _update_challenge(request, challenger): return challenge -class ChallengeAuthPolicyBase(object): - """Sans I/O base for challenge authentication policies""" - - def __init__(self, **kwargs): - self._token = None # type: Optional[AccessToken] - super(ChallengeAuthPolicyBase, self).__init__(**kwargs) - - @property - def _need_new_token(self): - # type: () -> bool - return not self._token or self._token.expires_on - time.time() < 300 - - -class ChallengeAuthPolicy(ChallengeAuthPolicyBase, HTTPPolicy): +class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential, **kwargs): - # type: (TokenCredential, **Any) -> None - self._credential = credential - super(ChallengeAuthPolicy, self).__init__(**kwargs) - - def send(self, request): - # type: (PipelineRequest) -> PipelineResponse - _enforce_tls(request) + def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + self._last_tenant_id = None # type: Optional[str] + super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) + def on_request(self, request): + # type: (PipelineRequest) -> None challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - return response - - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + super(ChallengeAuthPolicy, self).on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. + _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" + + def on_challenge(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> bool + try: + challenge = _update_challenge(request, response) scope = challenge.get_scope() or challenge.get_resource() + "/.default" - # pass the tenant ID from the challenge to support multi-tenant authentication when possible - tenant_id = challenge.get_tenant_id() - self._token = self._credential.get_token(scope, tenant_id=tenant_id) + except ValueError: + return False + + self._scopes = (scope,) + + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None + + self._last_tenant_id = challenge.tenant_id + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py index c83c9f6e788e..46e668a59867 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py @@ -40,6 +40,10 @@ def __init__(self, request_uri, challenge, response_headers=None): if "authorization" not in self._parameters and "authorization_uri" not in self._parameters: raise ValueError("Invalid challenge parameters") + authorization_uri = self.get_authorization_server() + # the authoritzation server URI should look something like https://login.windows.net/tenant-id + self.tenant_id = authorization_uri.split("/")[-1] or None + # if the response headers were supplied if response_headers: # get the message signing key and message key encryption key from the headers @@ -82,12 +86,6 @@ def get_scope(self): """ Returns the scope if present, otherwise empty string. """ return self.get_value("scope") or "" - def get_tenant_id(self): - """ Returns the tenant ID parsed from the authorization server """ - authorization_uri = self.get_authorization_server() - # the authoritzation server URI should look something like https://login.windows.net/tenant-id - return authorization_uri.split("/")[-1] - def supports_pop(self): """ Returns True if challenge supports pop token auth else False """ return self._parameters.get("supportspop", "").lower() == "true" diff --git a/sdk/keyvault/azure-keyvault-certificates/setup.py b/sdk/keyvault/azure-keyvault-certificates/setup.py index e9d54c896c34..5eeceb33b647 100644 --- a/sdk/keyvault/azure-keyvault-certificates/setup.py +++ b/sdk/keyvault/azure-keyvault-certificates/setup.py @@ -83,7 +83,7 @@ ] ), install_requires=[ - "azure-core<2.0.0,>=1.7.0", + "azure-core<2.0.0,>=1.15.0", "msrest>=0.6.21", "azure-common~=1.1", ], diff --git a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md index 6076cc297aa6..59d7afd4ee18 100644 --- a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md @@ -3,9 +3,8 @@ ## 4.5.0b5 (Unreleased) ### Features Added - - Added support for multi-tenant authentication against Key Vault and Managed HSM when using - `azure-identity` 1.7.0 or newer + `azure-identity` 1.7.1 or newer ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes @@ -13,6 +12,7 @@ ### Bugs Fixed ### Other Changes +- Updated minimum `azure-core` version to 1.15.0 ## 4.5.0b4 (2021-10-07) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/__init__.py index 3329ce2df068..d8303b4b081d 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/__init__.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/__init__.py @@ -9,7 +9,7 @@ import urlparse as parse # type: ignore from typing import TYPE_CHECKING -from .challenge_auth_policy import ChallengeAuthPolicy, ChallengeAuthPolicyBase +from .challenge_auth_policy import ChallengeAuthPolicy from .client_base import KeyVaultClientBase from .http_challenge import HttpChallenge from . import http_challenge_cache as HttpChallengeCache @@ -21,7 +21,6 @@ __all__ = [ "ChallengeAuthPolicy", - "ChallengeAuthPolicyBase", "HttpChallenge", "HttpChallengeCache", "KeyVaultClientBase", diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index db584d239c55..5abf15c3ec3e 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -15,67 +15,61 @@ """ from typing import TYPE_CHECKING -from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy -from . import HttpChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_request, _update_challenge, ChallengeAuthPolicyBase +from . import http_challenge_cache as ChallengeCache +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any - from azure.core.credentials_async import AsyncTokenCredential - from azure.core.pipeline import PipelineRequest - from azure.core.pipeline.transport import AsyncHttpResponse - from . import HttpChallenge + from typing import Any, Optional + from azure.core.pipeline import PipelineRequest, PipelineResponse -class AsyncChallengeAuthPolicy(ChallengeAuthPolicyBase, AsyncHTTPPolicy): +class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential: "AsyncTokenCredential", **kwargs: "Any") -> None: - self._credential = credential - super(AsyncChallengeAuthPolicy, self).__init__(**kwargs) - - async def send(self, request: "PipelineRequest") -> "AsyncHttpResponse": + def __init__(self, *args: "Any", **kwargs: "Any") -> None: + self._last_tenant_id = None # type: Optional[str] + super().__init__(*args, **kwargs) + + async def on_request(self, request: "PipelineRequest") -> None: + challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + await super().on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" - challenge = HttpChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = await self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: + try: + challenge = _update_challenge(request, response) + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + except ValueError: + return False - return response + self._scopes = (scope,) - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """authenticate according to challenge, add Authorization header to request""" + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - # pass the tenant ID from the challenge to support multi-tenant authentication when possible - tenant_id = challenge.get_tenant_id() - self._token = await self._credential.get_token(scope, tenant_id=tenant_id) + self._last_tenant_id = challenge.tenant_id + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 7a28dac58f35..347bd93214c8 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -14,13 +14,9 @@ protocol again. """ -import copy -import time - from azure.core.exceptions import ServiceRequestError -from azure.core.pipeline import PipelineContext, PipelineRequest -from azure.core.pipeline.policies import HTTPPolicy -from azure.core.pipeline.transport import HttpRequest +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import BearerTokenCredentialPolicy from .http_challenge import HttpChallenge from . import http_challenge_cache as ChallengeCache @@ -32,7 +28,6 @@ if TYPE_CHECKING: from typing import Any, Optional - from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -44,22 +39,6 @@ def _enforce_tls(request): ) -def _get_challenge_request(request): - # type: (PipelineRequest) -> PipelineRequest - - # The challenge request is intended to provoke an authentication challenge from Key Vault, to learn how the - # service request should be authenticated. It should be identical to the service request but with no body. - challenge_request = HttpRequest( - request.http_request.method, request.http_request.url, headers=request.http_request.headers - ) - challenge_request.headers["Content-Length"] = "0" - - options = copy.deepcopy(request.context.options) - context = PipelineContext(request.context.transport, **options) - - return PipelineRequest(http_request=challenge_request, context=context) - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -73,70 +52,53 @@ def _update_challenge(request, challenger): return challenge -class ChallengeAuthPolicyBase(object): - """Sans I/O base for challenge authentication policies""" - - def __init__(self, **kwargs): - self._token = None # type: Optional[AccessToken] - super(ChallengeAuthPolicyBase, self).__init__(**kwargs) - - @property - def _need_new_token(self): - # type: () -> bool - return not self._token or self._token.expires_on - time.time() < 300 - - -class ChallengeAuthPolicy(ChallengeAuthPolicyBase, HTTPPolicy): +class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential, **kwargs): - # type: (TokenCredential, **Any) -> None - self._credential = credential - super(ChallengeAuthPolicy, self).__init__(**kwargs) - - def send(self, request): - # type: (PipelineRequest) -> PipelineResponse - _enforce_tls(request) + def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + self._last_tenant_id = None # type: Optional[str] + super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) + def on_request(self, request): + # type: (PipelineRequest) -> None challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - return response - - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + super(ChallengeAuthPolicy, self).on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. + _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" + + def on_challenge(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> bool + try: + challenge = _update_challenge(request, response) scope = challenge.get_scope() or challenge.get_resource() + "/.default" - # pass the tenant ID from the challenge to support multi-tenant authentication when possible - tenant_id = challenge.get_tenant_id() - self._token = self._credential.get_token(scope, tenant_id=tenant_id) + except ValueError: + return False + + self._scopes = (scope,) + + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None + + self._last_tenant_id = challenge.tenant_id + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py index c83c9f6e788e..46e668a59867 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py @@ -40,6 +40,10 @@ def __init__(self, request_uri, challenge, response_headers=None): if "authorization" not in self._parameters and "authorization_uri" not in self._parameters: raise ValueError("Invalid challenge parameters") + authorization_uri = self.get_authorization_server() + # the authoritzation server URI should look something like https://login.windows.net/tenant-id + self.tenant_id = authorization_uri.split("/")[-1] or None + # if the response headers were supplied if response_headers: # get the message signing key and message key encryption key from the headers @@ -82,12 +86,6 @@ def get_scope(self): """ Returns the scope if present, otherwise empty string. """ return self.get_value("scope") or "" - def get_tenant_id(self): - """ Returns the tenant ID parsed from the authorization server """ - authorization_uri = self.get_authorization_server() - # the authoritzation server URI should look something like https://login.windows.net/tenant-id - return authorization_uri.split("/")[-1] - def supports_pop(self): """ Returns True if challenge supports pop token auth else False """ return self._parameters.get("supportspop", "").lower() == "true" diff --git a/sdk/keyvault/azure-keyvault-keys/setup.py b/sdk/keyvault/azure-keyvault-keys/setup.py index 3a12c3d53b5d..400544558a44 100644 --- a/sdk/keyvault/azure-keyvault-keys/setup.py +++ b/sdk/keyvault/azure-keyvault-keys/setup.py @@ -82,7 +82,7 @@ ] ), install_requires=[ - "azure-core<2.0.0,>=1.7.0", + "azure-core<2.0.0,>=1.15.0", "cryptography>=2.1.4", "msrest>=0.6.21", "azure-common~=1.1", diff --git a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md index 0da3a14b2f62..238b984c3a5b 100644 --- a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md @@ -3,9 +3,8 @@ ## 4.4.0b2 (Unreleased) ### Features Added - -- Added support for multi-tenant authentication against Key Vault and Managed HSM when using - `azure-identity` 1.7.0 or newer +- Added support for multi-tenant authentication against Managed HSM when using + `azure-identity` 1.7.1 or newer ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes @@ -13,6 +12,7 @@ ### Bugs Fixed ### Other Changes +- Updated minimum `azure-core` version to 1.15.0 ## 4.4.0b1 (2021-09-09) diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/__init__.py index 3329ce2df068..d8303b4b081d 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/__init__.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/__init__.py @@ -9,7 +9,7 @@ import urlparse as parse # type: ignore from typing import TYPE_CHECKING -from .challenge_auth_policy import ChallengeAuthPolicy, ChallengeAuthPolicyBase +from .challenge_auth_policy import ChallengeAuthPolicy from .client_base import KeyVaultClientBase from .http_challenge import HttpChallenge from . import http_challenge_cache as HttpChallengeCache @@ -21,7 +21,6 @@ __all__ = [ "ChallengeAuthPolicy", - "ChallengeAuthPolicyBase", "HttpChallenge", "HttpChallengeCache", "KeyVaultClientBase", diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index db584d239c55..5abf15c3ec3e 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -15,67 +15,61 @@ """ from typing import TYPE_CHECKING -from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy -from . import HttpChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_request, _update_challenge, ChallengeAuthPolicyBase +from . import http_challenge_cache as ChallengeCache +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any - from azure.core.credentials_async import AsyncTokenCredential - from azure.core.pipeline import PipelineRequest - from azure.core.pipeline.transport import AsyncHttpResponse - from . import HttpChallenge + from typing import Any, Optional + from azure.core.pipeline import PipelineRequest, PipelineResponse -class AsyncChallengeAuthPolicy(ChallengeAuthPolicyBase, AsyncHTTPPolicy): +class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential: "AsyncTokenCredential", **kwargs: "Any") -> None: - self._credential = credential - super(AsyncChallengeAuthPolicy, self).__init__(**kwargs) - - async def send(self, request: "PipelineRequest") -> "AsyncHttpResponse": + def __init__(self, *args: "Any", **kwargs: "Any") -> None: + self._last_tenant_id = None # type: Optional[str] + super().__init__(*args, **kwargs) + + async def on_request(self, request: "PipelineRequest") -> None: + challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + await super().on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" - challenge = HttpChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = await self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: + try: + challenge = _update_challenge(request, response) + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + except ValueError: + return False - return response + self._scopes = (scope,) - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """authenticate according to challenge, add Authorization header to request""" + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - # pass the tenant ID from the challenge to support multi-tenant authentication when possible - tenant_id = challenge.get_tenant_id() - self._token = await self._credential.get_token(scope, tenant_id=tenant_id) + self._last_tenant_id = challenge.tenant_id + await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index 7a28dac58f35..347bd93214c8 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -14,13 +14,9 @@ protocol again. """ -import copy -import time - from azure.core.exceptions import ServiceRequestError -from azure.core.pipeline import PipelineContext, PipelineRequest -from azure.core.pipeline.policies import HTTPPolicy -from azure.core.pipeline.transport import HttpRequest +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import BearerTokenCredentialPolicy from .http_challenge import HttpChallenge from . import http_challenge_cache as ChallengeCache @@ -32,7 +28,6 @@ if TYPE_CHECKING: from typing import Any, Optional - from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -44,22 +39,6 @@ def _enforce_tls(request): ) -def _get_challenge_request(request): - # type: (PipelineRequest) -> PipelineRequest - - # The challenge request is intended to provoke an authentication challenge from Key Vault, to learn how the - # service request should be authenticated. It should be identical to the service request but with no body. - challenge_request = HttpRequest( - request.http_request.method, request.http_request.url, headers=request.http_request.headers - ) - challenge_request.headers["Content-Length"] = "0" - - options = copy.deepcopy(request.context.options) - context = PipelineContext(request.context.transport, **options) - - return PipelineRequest(http_request=challenge_request, context=context) - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -73,70 +52,53 @@ def _update_challenge(request, challenger): return challenge -class ChallengeAuthPolicyBase(object): - """Sans I/O base for challenge authentication policies""" - - def __init__(self, **kwargs): - self._token = None # type: Optional[AccessToken] - super(ChallengeAuthPolicyBase, self).__init__(**kwargs) - - @property - def _need_new_token(self): - # type: () -> bool - return not self._token or self._token.expires_on - time.time() < 300 - - -class ChallengeAuthPolicy(ChallengeAuthPolicyBase, HTTPPolicy): +class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, credential, **kwargs): - # type: (TokenCredential, **Any) -> None - self._credential = credential - super(ChallengeAuthPolicy, self).__init__(**kwargs) - - def send(self, request): - # type: (PipelineRequest) -> PipelineResponse - _enforce_tls(request) + def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + self._last_tenant_id = None # type: Optional[str] + super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) + def on_request(self, request): + # type: (PipelineRequest) -> None challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) - if not challenge: - challenge_request = _get_challenge_request(request) - challenger = self.next.send(challenge_request) - try: - challenge = _update_challenge(request, challenger) - except ValueError: - # didn't receive the expected challenge -> nothing more this policy can do - return challenger - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) - - return response - - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + if challenge: + if self._last_tenant_id == challenge.tenant_id: + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + super(ChallengeAuthPolicy, self).on_request(request) + else: + # acquire a new token because this vault is in a different tenant and the application has + # opted to allow tenant discovery + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + return + + # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, + # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. + # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell + # super to send it again. + _enforce_tls(request) + if request.http_request.body: + request.context["key_vault_request_data"] = request.http_request.body + request.http_request.set_json_body(None) + request.http_request.headers["Content-Length"] = "0" + + def on_challenge(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> bool + try: + challenge = _update_challenge(request, response) scope = challenge.get_scope() or challenge.get_resource() + "/.default" - # pass the tenant ID from the challenge to support multi-tenant authentication when possible - tenant_id = challenge.get_tenant_id() - self._token = self._credential.get_token(scope, tenant_id=tenant_id) + except ValueError: + return False + + self._scopes = (scope,) + + body = request.context.pop("key_vault_request_data", None) + request.http_request.set_text_body(body) # no-op when text is None + + self._last_tenant_id = challenge.tenant_id + self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + return True diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py index c83c9f6e788e..46e668a59867 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py @@ -40,6 +40,10 @@ def __init__(self, request_uri, challenge, response_headers=None): if "authorization" not in self._parameters and "authorization_uri" not in self._parameters: raise ValueError("Invalid challenge parameters") + authorization_uri = self.get_authorization_server() + # the authoritzation server URI should look something like https://login.windows.net/tenant-id + self.tenant_id = authorization_uri.split("/")[-1] or None + # if the response headers were supplied if response_headers: # get the message signing key and message key encryption key from the headers @@ -82,12 +86,6 @@ def get_scope(self): """ Returns the scope if present, otherwise empty string. """ return self.get_value("scope") or "" - def get_tenant_id(self): - """ Returns the tenant ID parsed from the authorization server """ - authorization_uri = self.get_authorization_server() - # the authoritzation server URI should look something like https://login.windows.net/tenant-id - return authorization_uri.split("/")[-1] - def supports_pop(self): """ Returns True if challenge supports pop token auth else False """ return self._parameters.get("supportspop", "").lower() == "true" diff --git a/sdk/keyvault/azure-keyvault-secrets/setup.py b/sdk/keyvault/azure-keyvault-secrets/setup.py index b8e377b75496..d74810b67dbf 100644 --- a/sdk/keyvault/azure-keyvault-secrets/setup.py +++ b/sdk/keyvault/azure-keyvault-secrets/setup.py @@ -83,7 +83,7 @@ ] ), install_requires=[ - "azure-core<2.0.0,>=1.7.0", + "azure-core<2.0.0,>=1.15.0", "msrest>=0.6.21", "azure-common~=1.1", ], From c3f17b9856fc2e84021904cb5f2eedf2d2726d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 5 Nov 2021 09:48:53 -0700 Subject: [PATCH 05/15] Tenant discovery on by default --- .../administration/_internal/async_challenge_auth_policy.py | 3 +-- .../keyvault/administration/_internal/challenge_auth_policy.py | 3 +-- .../certificates/_shared/async_challenge_auth_policy.py | 3 +-- .../keyvault/certificates/_shared/challenge_auth_policy.py | 3 +-- .../azure/keyvault/keys/_shared/async_challenge_auth_policy.py | 3 +-- .../azure/keyvault/keys/_shared/challenge_auth_policy.py | 3 +-- .../keyvault/secrets/_shared/async_challenge_auth_policy.py | 3 +-- .../azure/keyvault/secrets/_shared/challenge_auth_policy.py | 3 +-- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py index 5abf15c3ec3e..9c831f25f85b 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py @@ -41,8 +41,7 @@ async def on_request(self, request: "PipelineRequest") -> None: # tenant since our last request for it, this request will fail. await super().on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py index 347bd93214c8..8dcfffba67a3 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py @@ -70,8 +70,7 @@ def on_request(self, request): # tenant since our last request for it, this request will fail. super(ChallengeAuthPolicy, self).on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index 5abf15c3ec3e..9c831f25f85b 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -41,8 +41,7 @@ async def on_request(self, request: "PipelineRequest") -> None: # tenant since our last request for it, this request will fail. await super().on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index 347bd93214c8..8dcfffba67a3 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -70,8 +70,7 @@ def on_request(self, request): # tenant since our last request for it, this request will fail. super(ChallengeAuthPolicy, self).on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index 5abf15c3ec3e..9c831f25f85b 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -41,8 +41,7 @@ async def on_request(self, request: "PipelineRequest") -> None: # tenant since our last request for it, this request will fail. await super().on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 347bd93214c8..8dcfffba67a3 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -70,8 +70,7 @@ def on_request(self, request): # tenant since our last request for it, this request will fail. super(ChallengeAuthPolicy, self).on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index 5abf15c3ec3e..9c831f25f85b 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -41,8 +41,7 @@ async def on_request(self, request: "PipelineRequest") -> None: # tenant since our last request for it, this request will fail. await super().on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index 347bd93214c8..8dcfffba67a3 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -70,8 +70,7 @@ def on_request(self, request): # tenant since our last request for it, this request will fail. super(ChallengeAuthPolicy, self).on_request(request) else: - # acquire a new token because this vault is in a different tenant and the application has - # opted to allow tenant discovery + # acquire a new token because this vault is in a different tenant self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return From 788bd243159cb7094987c1b3114ca8b342c5265c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 5 Nov 2021 09:56:13 -0700 Subject: [PATCH 06/15] Test recordings --- ...enant_authentication_2016_10_01_vault.yaml | 10 ++--- ..._multitenant_authentication_7_0_vault.yaml | 10 ++--- ..._multitenant_authentication_7_1_vault.yaml | 10 ++--- ...t_multitenant_authentication_7_2_mhsm.yaml | 8 ++-- ..._multitenant_authentication_7_2_vault.yaml | 10 ++--- ...enant_authentication_7_3_preview_mhsm.yaml | 10 ++--- ...nant_authentication_7_3_preview_vault.yaml | 10 ++--- ...enant_authentication_2016_10_01_vault.yaml | 37 ++++++++++++++++++- ..._multitenant_authentication_7_0_vault.yaml | 37 ++++++++++++++++++- ..._multitenant_authentication_7_1_vault.yaml | 37 ++++++++++++++++++- ...t_multitenant_authentication_7_2_mhsm.yaml | 33 ++++++++++++++++- ..._multitenant_authentication_7_2_vault.yaml | 37 ++++++++++++++++++- ...enant_authentication_7_3_preview_mhsm.yaml | 35 +++++++++++++++++- ...nant_authentication_7_3_preview_vault.yaml | 37 ++++++++++++++++++- 14 files changed, 274 insertions(+), 47 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml index d54daa2b5434..a6927f5b0e81 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml @@ -13,7 +13,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/create?api-version=2016-10-01 response: @@ -28,7 +28,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:11 GMT + - Fri, 05 Nov 2021 16:55:08 GMT expires: - '-1' pragma: @@ -65,12 +65,12 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/create?api-version=2016-10-01 response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/bae76c028ea04c418ae65eb4fba413d9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zHqAr-igexm9D4a2srndcV63YRVBwhPer10mM2fusbb4PNLK1RWHWD_kot6GKVA1WX-NeuODula1UpM7-dOtQS-Qrv4W15_EHebyZZ3YcXIK-mCFTsYGFGLvvM5pOn6GK_qoEont-voDm0-Q0LYmRv1TYi1ScSm3Ib1tQtw4Thvz-2QWF_jeHwuDk2GbbTa4iGq5WI6zin-Oj3OzlW3QQYd7eeFru_K0dCLumaaC_L_Eu-lKfXQydbylXFKIWRBHVIsxxKogOgzTfCA-yFiCd_Opns8ixkiYHHvaXICbJo1hkl0T0LiHnniJFJokf9rIeb2_yUFtv81YP1dOsLChNQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332151,"updated":1634332151,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/5665f11c42d94563a09cec5d0d4b71b9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sgG9TeQOrM2CGft71-Nwq7ofI6hCKQIt-PEWf2B-dMkxOSShDT9CeSfl0mxCB6TPpoPaJ0sLh2FvDg-Lw8LWDFRXpzOUMGsYcva0egQAnsWmTn0JHOoqAp4M0rc8sghpFJX_iQVCxXj9V3bX4_4PHJVql9dabZgUIPwDNIwgdTLHjafS4j92VvU_rCimsOs99QLAmMf5-woJg1PTiF-Mz2XJzXjQV8WL9QDmNfwX451MsEqrMlWO333tCm4ATyu78cLna2-JqkGMdMrIOPiTREG_AGgx9UXnIScljNpODkaw3B-42utFXFytJowyJ4SUwPrpOglMXTKOBlnjZsDroQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131308,"updated":1636131308,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +79,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:11 GMT + - Fri, 05 Nov 2021 16:55:08 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml index d72b3902c05e..33cf9673d2e0 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml @@ -13,7 +13,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/create?api-version=7.0 response: @@ -28,7 +28,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:13 GMT + - Fri, 05 Nov 2021 16:55:10 GMT expires: - '-1' pragma: @@ -65,12 +65,12 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/bbae0bcd529d465986b037daedcde2fa","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s6zLYcP7e4_JZy1uHRGrzGeggyc--m1k_9LWq6rHP16OcgdRYs0qc5NOsxDsEr8GWJiZnaHVeE8AjDCtUXUFlP46vDTgUT29QjSYIhvLjB7i0XqORDVq6dqd4GnFTLwTeh6-SvWLT2HvMQ_8YOL47-fXbcxWN54X0sZzqs9FeRZnPFnngec6StLan6f2DPFED_qZTRH51EAbk3nJHrhk5ivUYucwr4icwN0mL1swsi9YbO3xH8ok_DwbjBg0wg1Ioeap_9oJoCOOaW5I_8ke_aBvCFlErZkfrRpVjtb2fRECfoOyX1aWjrR2-WSUoyEN5ZEkw7idmDncx57cJphqrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332154,"updated":1634332154,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/162542c668594a71a7bebd6bf8165a55","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v74EsYu7Geq-OA6aFIf4G2DXYjEbThTMpPWGNpzc12IQmELglwa2c75l1r9fSHG8DEHF8VOpxB5lAH6tmmHDO1Kd6y_vnI3Lokf8bkrCNDvtsNjhOFd6ZOHJ65wC3UK8KR_TBy0u34wyZSMZi74k8ykTWUHQx47Qi5-KZstQLqq8kVFXz0BCGx3B-WgjZOTjedR0il9Zw1z7pc9NyqiV07mLbGq1WMtO5KfLtE3paiemUPMe2o_nW0xr7b8sESRmv5KLi4zXqJ0rtLLWu6qOI4YlClym0c9m5pA2qrJLHvxDyPJnxXDZFG-HqBC_4QNahOMiNq6pqs-daD5kQA1w4Q","e":"AQAB"},"attributes":{"enabled":true,"created":1636131311,"updated":1636131311,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +79,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:13 GMT + - Fri, 05 Nov 2021 16:55:10 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml index 4281cfb19206..6e7ee6984d04 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml @@ -13,7 +13,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/create?api-version=7.1 response: @@ -28,7 +28,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:16 GMT + - Fri, 05 Nov 2021 16:55:12 GMT expires: - '-1' pragma: @@ -65,12 +65,12 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/create?api-version=7.1 response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/cdb518f0e9dc4ea08e666c75312c7a7e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"9pkY7RQSweiKIjzliBWnXd3oF2mqu04ibsjLeeLkXm6kKGOWvTT5FjFkXWBP8c383fd56bt4xewfHBDgm_S-XL3I7Es09tzyOEMzVWGpiseNt3Gfff6ZlYEPEGokMx6dAsmMHakzKOQGcSmTfd294onrIYOfn7gGVM5iW-bz5RBbeN44kJYvhD7Y6xUqKn6WhskGJ0HLgIQWdEm2YfgYnUEVz_50Ji5lzemWMtdSF3oveFnizUhh8KAcGkbg9qxSak2vzKJPt6YeqqPaKwOvIUECvQTLfmr5RtB8ZA5SC2rbdXgIjk_UE3amgMPBeSJ2rxAXWZKoYfLXmT7OP3vWxQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332157,"updated":1634332157,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/975983b8bdd1459d80df03b7e3383cbd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"p18DJDreSsUkjyaW1s0JfjtqOxKRWFvP2ZVQKqnxlEw1CekrGg5pPB1fyZrkYjdrcSg1rNe6ua1c0Xr3O-WSZZfgm7zmUr9V5zUinFl4QTBqpQ4TpYXLAIHmXCR8RTO_NyhvlaMyeIeMZ6KV7H1ga9HI5-DQ94dlIGjSPNYmppZ-JQ4pMvjrhNRBHs8NE0VCYsnFnw8T3gz-r2OObQ2vfh4jUzjDL4LVJAG5lJe1yhIgefaCUGJD-i0gemFZls1BhGVkzP1nrq6DvC3JYkfRCmuifuODgiTrQdA_zOTX6HmFsU5C8904gjs3sDSukazKR7Y_pvwq7g3Cnf_LN2lhDQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131314,"updated":1636131314,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' headers: cache-control: - no-cache @@ -79,7 +79,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:17 GMT + - Fri, 05 Nov 2021 16:55:13 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml index 16bd76feb2d9..ebabbc5d9778 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml @@ -13,7 +13,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/create?api-version=7.2 response: @@ -56,12 +56,12 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/create?api-version=7.2 response: body: - string: '{"attributes":{"created":1634332160,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1634332160},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/11223d7f97bd00e2adbbd1734f8915ce","kty":"RSA-HSM","n":"p63FDvI8jWvyHP-3yvibhEv7XMyMbJms7am84eUkQA5vE0NDLjffbXXBF5iZscsQyHkE0pRUo6ZRhD8aU-TfUrVCHeblMrDx8_l3bBB8kpp00tHMOgYiRpVKyhYSleQ0sjDkxo28zDFuNlNqDfjtNKfuKeZV9iURHeSYk9qRwVUvSeblL3j-35l26RMN-RFwZGuGVOxN34hYbIhws6NRDDkF085dVHj3_Q192edyrSpuySpxT-QC-GetGqqWmY3XCv2bUBRHZXiVpvzqEc_g4_TeKIZvwXs5QKDJ4VdOVGg8Vqbk7R3sYWb1IEsBnHaBud6LBKDhxbwVE_5xjv54Vw"}}' + string: '{"attributes":{"created":1636131317,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636131317},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/dca01d9198cb035c24ec367adf277e8c","kty":"RSA-HSM","n":"zpWKZrFAszudjizF-IxDdFP5jAYzb52X5SWRbg_4xxMv-Xt4qdHmb2ip7KzI1pFojD2nFmuJ966IBQQU7xjNNfwjDBO-fEI3ZMiEiF0Zg2_sET5TGuAwn5AGM-qPiCkQvGGVVDqbu2-_e11AgfJ4IcT1N5P1Nz8AmPlKD_KNAbsVhIkC0WrYJKhaZFsL8z5w3EBqPLzvi0aWZ4f2n_w3Y6MxkCNY1JeqjlXLYjVXOWFy29t2zY7eYWLq4IUqPQzR8J05osE97aScAddEAMtPZvn5PL7ooqGEs9W8mvlJJIOBSbWFDuElcrEGdONDiibcFlFw_mu7dvzv9st3ppSzNQ"}}' headers: cache-control: - no-cache @@ -82,7 +82,7 @@ interactions: x-ms-keyvault-region: - westus x-ms-server-latency: - - '781' + - '255' status: code: 200 message: OK diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml index efab0417d2db..c4a2b0941acf 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml @@ -13,7 +13,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/create?api-version=7.2 response: @@ -28,7 +28,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:22 GMT + - Fri, 05 Nov 2021 16:55:19 GMT expires: - '-1' pragma: @@ -65,12 +65,12 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/create?api-version=7.2 response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/4d4b6e65855847eeb846ef35a8fd0c66","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1h6dQJRspvZPzF8Wj2G3zoDnoX4sFvaby2cNMtHoEq4Z04FUSEE9V9GjE-yhe9JUfHMHZTenwwhqt_kHKeIGyDYgHCE_RLfix8kIjN-K_kqLrm17bZW2-v0olSwAEFVOulFH5seN6QNaIQ3MsCdap3hR-LefWCoAwXaK66li29H6veQV5IjEZkArtrHRoBYc9qNvp5WGSwC4KaIJTupkVj95s5Trtg6_e6lhIq_xc3SG0dDZuaiAS2gmFBFdiriJtP_Ihp5kqJ57cFmFLrhSOzsbUyt9_O6nv4ojCsLqHxfuYsrhS5Ymea45f5khX4ZcBnQCmkZwhXNI4JO7TuWbDQ","e":"AQAB"},"attributes":{"enabled":true,"created":1634332163,"updated":1634332163,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/64dd36b3399149e6bd9a9a3d8318e508","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tf4X2p2fbuObLjYmDdIPpiBWjGK5X9xtaICdcOw4vKyrv3TgLyl4C_0wHcD6y5OSyqzS-c_SYLkD7x2Wl2TDQueZvOj60Nm0BcD9fN8JbmF2z7GsdyKj-ovowRZbpXbyEo2TS0giHcWlt5LkhwwrX-13GYTIc2aUXxxChd5K9AWxQLmTksuJtIyL9lRhyzfXBRst4hBuYIhsMKcYaFcKzREvqmeYFq2ZIp4MGsxZrfO_MvEfdhpzLARrkMuXVI71urvuuF7A9qn9ZoA1J4cq_9z3sB7dXqPv0F0TA2J0PCinaVbZQLg4ydxuJkq2ZboIIUVPyWE1yJNDmqSZp3rmEQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131320,"updated":1636131320,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' headers: cache-control: - no-cache @@ -79,7 +79,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:24 GMT + - Fri, 05 Nov 2021 16:55:20 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml index 8dcb65c90a77..583377e13d8c 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -13,7 +13,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/create?api-version=7.3-preview response: @@ -38,7 +38,7 @@ interactions: x-frame-options: - SAMEORIGIN x-ms-server-latency: - - '0' + - '1' status: code: 401 message: Unauthorized @@ -56,12 +56,12 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/create?api-version=7.3-preview response: body: - string: '{"attributes":{"created":1634332167,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1634332167},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/0656857aaf684753ba8d7c5c375da89c","kty":"RSA-HSM","n":"iJT8MiXcnFT6p1CEpwW5SuAUEHpUKrofqbYYcsB5GVFPJW_AsoxypERUSY8ZjgxY7_FmnExXUI4lDN86Nbe_korrVwMI1DLY3wZ-Oqh5JmAIKi9V6t-NVz-VIHN9fGJgf56UcJ5oc031qvPjrPJk8GRwXWtUoA4zBeBgYzFLktISsvV4CVDJD-sVp6K-_rfT2xC44-3-feebZE3z3m02pRaF40oy2t4UNd-EhSwVkL_s8JR4b759pvjUf_0untgEuEKRE6QbePf5wzhmO7_oPzyF52QuWWa3Eqz_rXISAFFa11ZA97j6P4ylV5-fajheC9jqZRPeZaibGuVdCTCU5Q"}}' + string: '{"attributes":{"created":1636131323,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636131323},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/451b8057153600668fa009e735a9d996","kty":"RSA-HSM","n":"iwhflbo8-_WYe32hbVI72HZyCBMW7TF-a1LYOiKyc9eDgXsDGc7TBQhfyIDYl-AXB2n4Y4hJtZWFoU4ePSF3PI5dpHrAwtxa7SifiR256Fad90bH03jXsjrgiQz3H3LOZVPomDSnA3FJrYawp9bealfge2RhrjQY_wGjOyhHUtIY-7_6aMqbPCORG3wPDQ6cflFl0_YNjCWQfs9iNxZ_xmYk19vmypvt1p9k6eiEPo8OYMuP2hmib9MhRh40mQhoYO2WuMKAGdNZnHFQ7DYsterHxPfSLT6f-3PWAleV7tNbcrcC846hDzoELVcNLEYMYO5ytS5B-8OH1BFYgTHvFQ"}}' headers: cache-control: - no-cache @@ -82,7 +82,7 @@ interactions: x-ms-keyvault-region: - westus x-ms-server-latency: - - '735' + - '233' status: code: 200 message: OK diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml index 7407436528e6..6bc086a4c7a8 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml @@ -13,7 +13,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/create?api-version=7.3-preview response: @@ -28,7 +28,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:29 GMT + - Fri, 05 Nov 2021 16:55:26 GMT expires: - '-1' pragma: @@ -65,12 +65,12 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/create?api-version=7.3-preview response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/e186e5bd9c104703ac32dcd859a5a104","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yvdc3KPSBHWzA-FRSXY8Af6QA_eJiFbyNwpgbiQMNS4qCixGAIPaD1hluZVFSlzHSZcNKsdjSEX9nK88M1l_WHJMFXQ3tgZPOmKttMiROPfHvfJ5_BVo73x3zMRFAvc-DWL_9ILvPLD0es_8os00267MDMKBrF8NHhDjzxesa6b2ublJKmvgyJiWaf0OYOs4atIYt6UUxs0s4I7oOzg6Uki9Slqvk_6SDh4cgRzyU4yKyNU3Ah2HQCgJjav-6LD-gvFQT6M4KM7h8OT-Jfwj05gD_BamQCokQXBcCz_O9SXgQlGS0nrLDdhioKOvxlV9ns7zd7g7jU_uQ7Q8nBZ7_Q","e":"AQAB"},"attributes":{"enabled":true,"created":1634332170,"updated":1634332170,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/64bf4a78c2c5422cb10f0ce9110af961","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ufrRN_nbOhbytEUrNHcnAXm3ciujUOmheywVnVDhJ9NlIREaqA6ALOJkgv5CczPJJpI6TcLv6BWpyH-Dvn8RwjHtBkXHOnRt4r_x4N4u85nMvf9mTlxTfggY-WZyWLaAhCzmaURooon0I6tRHFRQ5km4BXOsSo2G2jcHs0p-j_KSPTQKh8l6diMPGM3H3rYhZ1vVyvQY9zpgACf8AJccssBkKQLllF-6qpPXuELR4zLLFofhE6I-2_PZDt7NKROy4VRunV9CVNJz8uDjYFElycWlYCmJNwrPVgU8A2X6MpLQgWy1jkXw8r89li_EmucAROTJWrUvJlW1PlUBlbT9FQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131326,"updated":1636131326,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' headers: cache-control: - no-cache @@ -79,7 +79,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 15 Oct 2021 21:09:29 GMT + - Fri, 05 Nov 2021 16:55:26 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml index 0eb6dd0df1ff..eb3682928b17 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_2016_10_01_vault.yaml @@ -9,7 +9,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key62211cc5/create?api-version=2016-10-01 response: @@ -20,7 +20,7 @@ interactions: cache-control: no-cache content-length: '97' content-type: application/json; charset=utf-8 - date: Fri, 15 Oct 2021 21:08:31 GMT + date: Fri, 05 Nov 2021 16:55:28 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -35,4 +35,37 @@ interactions: code: 401 message: Unauthorized url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key62211cc5/create?api-version=2016-10-01 +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key62211cc5/create?api-version=2016-10-01 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key62211cc5/2fe8efc82a53428b9d5bbc5ea90dc276","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6qDo5N0hCCRwO1IVj0XFfjFsJjno0YClIjURSlKFUOX7YIluWkw2o6RUKGyDQujcYpVj9Q8Y6NfeZn3quJUBIRHa02ir0ToghrJ9-X4c_hg8ijTt-Ag70WmkHOKvriZgVvbhu-9YwqBa_C1k65uulqz4TujXKakfcB5i4Z7RdGdxhrEdh2vRdgLu9UIv0Rn3eLb5bPjlNMpb44XiqXBP_TT_2TKSHZg2YRMx-jFNGqJAq8qb0PbM4dM8TODH76xV4_15bT49yTE_PuHA9jKYcC9H-rkPa-xhL6q9PFhKLEN4DX3oveYL0M-qBo8vkDCBkfeiHzUj7dIE_cEZ4erDgQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131329,"updated":1636131329,"recoveryLevel":"Recoverable+Purgeable"}}' + headers: + cache-control: no-cache + content-length: '680' + content-type: application/json; charset=utf-8 + date: Fri, 05 Nov 2021 16:55:29 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 200 + message: OK + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key62211cc5/create?api-version=2016-10-01 version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml index eba4f5bb23af..f441f52f0bbe 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_0_vault.yaml @@ -9,7 +9,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6b91b42/create?api-version=7.0 response: @@ -20,7 +20,7 @@ interactions: cache-control: no-cache content-length: '97' content-type: application/json; charset=utf-8 - date: Fri, 15 Oct 2021 21:08:34 GMT + date: Fri, 05 Nov 2021 16:55:31 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -35,4 +35,37 @@ interactions: code: 401 message: Unauthorized url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6b91b42/create?api-version=7.0 +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6b91b42/create?api-version=7.0 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6b91b42/940432f6b7744d0fa9122d895cb89a2c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"-fBZm71ITwyw2jgr9u422zbHE7zvY6T55146MlEwH4MFOvPxs_EdSbv25ScUGMdVP5lkn4xr64P0lQOjhI6m3nTbWvLFb81Cm5YW8Gt0YleMg_HKZgLqgdPBFDgHANLeioQCNGTkguTJ19L__8r7MhB9YuoQz0kp02-vK1ZaNNvo7lsqDMvBVcQnF-bQ0Wd-SG3oZ4Z_PRtKZMaB44WkHGbSpH8rZ2fJu8ckqSNIU6UsZokLZEy5WsDjndctrrjPFDKmMhTxePewqQ1JxL_I92F25FtW19uK9iCCsyYuBHma9_qjOCONEPNqv9GA3hDEmPqgB50jEnWbDQFzFj280Q","e":"AQAB"},"attributes":{"enabled":true,"created":1636131331,"updated":1636131331,"recoveryLevel":"Recoverable+Purgeable"}}' + headers: + cache-control: no-cache + content-length: '680' + content-type: application/json; charset=utf-8 + date: Fri, 05 Nov 2021 16:55:31 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 200 + message: OK + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6b91b42/create?api-version=7.0 version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml index 2bb817e2bb79..bae43223c033 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_1_vault.yaml @@ -9,7 +9,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c01b43/create?api-version=7.1 response: @@ -20,7 +20,7 @@ interactions: cache-control: no-cache content-length: '97' content-type: application/json; charset=utf-8 - date: Fri, 15 Oct 2021 21:08:36 GMT + date: Fri, 05 Nov 2021 16:55:34 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -35,4 +35,37 @@ interactions: code: 401 message: Unauthorized url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6c01b43/create?api-version=7.1 +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c01b43/create?api-version=7.1 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c01b43/885311947edd4eccbcb7265fb430acef","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"mEKr5BmEhUKeMQ1VT185F2qTEKcyu23WCX__Puze8iviw12lnQyCZ0S-tpOeR37JMJtNY40d4Yc5yCN-DEDeziWILoCrkJRVm3-LYD4p8m_ktjh-yPlG0WL1zNLYHm9G_OipsM0cUXlplEdEIA7Gl-5tCpkVr__yYv_jdDHoVyC8pLYsFkzWMvXXhFyujTUqHKjnMRR_jiMosC0Dc12LoNbM-WcXuhg-ihmomagI2pQ6_E_F1vywbhnqc-oO8KpCPuTbIKR3JnEhffCrOMORVelj6kvEQ4VuTgoY4uuocIJiCcU7UAU86HFDHjH7zkAu_H_e0Chgxp0en4cZxn4RXQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131334,"updated":1636131334,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: no-cache + content-length: '701' + content-type: application/json; charset=utf-8 + date: Fri, 05 Nov 2021 16:55:34 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 200 + message: OK + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6c01b43/create?api-version=7.1 version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml index 9479aa68df45..0ee86866acd2 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_mhsm.yaml @@ -9,7 +9,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key8b711acd/create?api-version=7.2 response: @@ -30,4 +30,35 @@ interactions: code: 401 message: Unauthorized url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key8b711acd/create?api-version=7.2 +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key8b711acd/create?api-version=7.2 + response: + body: + string: '{"attributes":{"created":1636131337,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636131337},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key8b711acd/fed58eedaeb804d1b59ab2bea1dfb796","kty":"RSA-HSM","n":"4UmgdeRIG5a4n9Yb0BazkZ1x4qmn0I8oX7E8lzUG1xnKTn0NkmXRgvjUvlB8pz8074rwS89FlXD3f2r2I7viPK8xqvDSlxWOTfT-5osAQcHBbFlRUd3DIgXDYkpojroOHQq9ygERVTl6yei-Dawq_D50fETMY4Hetjqbi1AhrkA8RM_T56aFzC5hL4WqVFQTZIpMsQTRXAVp598GMl1oMn8AvUKAsPhmRwgwLY8wWOoW6kN60xJPA9cR7QrZAtb6UDJOiPj6yXu4X7ceW4tKu3iVliyKNmZxSD272sCSJqaJlezZrw96Zc8VH3arIc9b-Ku8-Tw8Raxn_o9hHixD7w"}}' + headers: + cache-control: no-cache + content-length: '741' + content-security-policy: default-src 'self' + content-type: application/json; charset=utf-8 + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: westus + x-ms-server-latency: '666' + status: + code: 200 + message: OK + url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key8b711acd/create?api-version=7.2 version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml index 201aed885ccf..669f41bd0af4 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_2_vault.yaml @@ -9,7 +9,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c71b44/create?api-version=7.2 response: @@ -20,7 +20,7 @@ interactions: cache-control: no-cache content-length: '97' content-type: application/json; charset=utf-8 - date: Fri, 15 Oct 2021 21:08:41 GMT + date: Fri, 05 Nov 2021 16:55:39 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -35,4 +35,37 @@ interactions: code: 401 message: Unauthorized url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6c71b44/create?api-version=7.2 +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c71b44/create?api-version=7.2 + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keya6c71b44/7e4059bf824742cfb3fc1bfc75fac8cb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yY5-Q2uVQjCdv9IJZT2DsJzkV6FolvKSu_1XN4rhN-11TMfZroiVCtBOLeeHTgPEVy9rEonytZihEZcaW2hZmZ-M1FI6WvYabMX4Lqayc8iPrZ6cRcU3ALANla2Rn6UWbrDfiZKJg4yZhJF3Aacl_CsE5NsFkkQpRiabKSU3s_df7zCxHr6xKHdZmwRvsDameyVkkGFchVxj3Ol6t37Z_MZXU24AYyH1tKNKcgcv3dpRP2g-fW91lOJsAsGk6IMLM-efKZc4iGIe6iHseNNQQE_2zBdyFOQpNwqqJV1wFTIjpLuQXpzNPIVxBebWW6RA1vq1L53ie1kr4kHK2fBcZQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131340,"updated":1636131340,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: no-cache + content-length: '701' + content-type: application/json; charset=utf-8 + date: Fri, 05 Nov 2021 16:55:40 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 200 + message: OK + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-keya6c71b44/create?api-version=7.2 version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml index a799ab7d477c..5a6f4d20e701 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -9,7 +9,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview response: @@ -25,9 +25,40 @@ interactions: resource="https://managedhsm.azure.net" x-content-type-options: nosniff x-frame-options: SAMEORIGIN - x-ms-server-latency: '1' + x-ms-server-latency: '0' status: code: 401 message: Unauthorized url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview + response: + body: + string: '{"attributes":{"created":1636131342,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636131342},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/32f6303512674d76aecfb61ceca28159","kty":"RSA-HSM","n":"oTeTKYMH7HGsV4ScjABjV8KAmjVORXmGQ6aaqTNWMdmyjfyXFyjDTh4yK4H7YGYtf-fuZje5eFYCs9jQg1kGHdDUqVTqeNjij5fl24H2Sh3bIwtj_UIQwFh3K4tYikqq7MD2Yi9d-wlnDrjems-flEImMWWNAGkJ7GK0hnO4rLQRjb4yVBYywEvAExHUwWisE32302i7mz6PRJJrY8_2rogaWbldktTw0tZG2oTn-Q10j9rMB2zoBMVl77kzaRVNAbAmShIZh5flLz1TTdfh-QdTEivwhE3m8GdY_Le7tNbqqzHEpLcZCpCBivHbiH7EVOqs_QwDDnFzCtsd7GfQWw"}}' + headers: + cache-control: no-cache + content-length: '741' + content-security-policy: default-src 'self' + content-type: application/json; charset=utf-8 + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: westus + x-ms-server-latency: '362' + status: + code: 200 + message: OK + url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml index d15a62879cbc..73fb38702775 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml @@ -9,7 +9,7 @@ interactions: Content-Type: - application/json User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.9.0 (Windows-10-10.0.22000-SP0) + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) method: POST uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview response: @@ -20,7 +20,7 @@ interactions: cache-control: no-cache content-length: '97' content-type: application/json; charset=utf-8 - date: Fri, 15 Oct 2021 21:08:46 GMT + date: Fri, 05 Nov 2021 16:55:44 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -35,4 +35,37 @@ interactions: code: 401 message: Unauthorized url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview +- request: + body: '{"kty": "RSA"}' + headers: + Accept: + - application/json + Content-Length: + - '14' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: POST + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/41af6e7fded24fcc84dbecf0b217c0c6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"10WaAIJ6LZzb7JCqZm8nkkjxjfTV_1PgI6a54zYBG3C0IsYigoyOrUp3ZSv2i-Mx5gSrqdcN1AQnt1GFiIEQjtO1kz24l7NIelrJn0YcCM8c2b0sBAWSY1zRaZzUmHJuAGtxUgBs77bOa7B-izuK1VSAhANu2I9cZVsTywSzveIqIuBRt5dN6-YsLPep1HS-ENPtCE7IgUOVQi6kFh-zLl-OcJ5MOiDuhoU0gIJ0e4aVWvr4kVLW5UHrcFkr1fUVxHzFwZJDNiez2tr8RM3utj4Le46_7W5yJjt-s8Q6anSojUhmlp4r7-hSZ4_EfRfZlifHWix9ZqiuPHuZFOJKyQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131345,"updated":1636131345,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: no-cache + content-length: '701' + content-type: application/json; charset=utf-8 + date: Fri, 05 Nov 2021 16:55:45 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 200 + message: OK + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview version: 1 From 757368bea10ec6e317e3e9f01a2fb50a23188af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 5 Nov 2021 11:14:10 -0700 Subject: [PATCH 07/15] Fix tests for playback issues/updates --- .../_shared/async_challenge_auth_policy.py | 2 +- .../keys/_shared/challenge_auth_policy.py | 2 +- ...enant_authentication_2016_10_01_vault.yaml | 102 ------------------ ..._multitenant_authentication_7_0_vault.yaml | 102 ------------------ ..._multitenant_authentication_7_1_vault.yaml | 102 ------------------ ...t_multitenant_authentication_7_2_mhsm.yaml | 89 --------------- ..._multitenant_authentication_7_2_vault.yaml | 102 ------------------ ...enant_authentication_7_3_preview_mhsm.yaml | 4 +- ...nant_authentication_7_3_preview_vault.yaml | 6 +- ...enant_authentication_7_3_preview_mhsm.yaml | 6 +- ...nant_authentication_7_3_preview_vault.yaml | 6 +- .../tests/test_challenge_auth.py | 65 ++++------- .../tests/test_challenge_auth_async.py | 88 +++++++++------ 13 files changed, 88 insertions(+), 588 deletions(-) delete mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml delete mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml delete mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml delete mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml delete mode 100644 sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index 9c831f25f85b..e367e0934854 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -33,6 +33,7 @@ def __init__(self, *args: "Any", **kwargs: "Any") -> None: super().__init__(*args, **kwargs) async def on_request(self, request: "PipelineRequest") -> None: + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: if self._last_tenant_id == challenge.tenant_id: @@ -49,7 +50,6 @@ async def on_request(self, request: "PipelineRequest") -> None: # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 8dcfffba67a3..83206f970b22 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -62,6 +62,7 @@ def __init__(self, *args, **kwargs): def on_request(self, request): # type: (PipelineRequest) -> None + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: if self._last_tenant_id == challenge.tenant_id: @@ -78,7 +79,6 @@ def on_request(self, request): # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml deleted file mode 100644 index a6927f5b0e81..000000000000 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_2016_10_01_vault.yaml +++ /dev/null @@ -1,102 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '0' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/create?api-version=2016-10-01 - response: - body: - string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing - a Bearer or PoP token."}}' - headers: - cache-control: - - no-cache - content-length: - - '97' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:08 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - www-authenticate: - - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", - resource="https://vault.azure.net" - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 401 - message: Unauthorized -- request: - body: '{"kty": "RSA"}' - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '14' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/create?api-version=2016-10-01 - response: - body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keyb0981a48/5665f11c42d94563a09cec5d0d4b71b9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sgG9TeQOrM2CGft71-Nwq7ofI6hCKQIt-PEWf2B-dMkxOSShDT9CeSfl0mxCB6TPpoPaJ0sLh2FvDg-Lw8LWDFRXpzOUMGsYcva0egQAnsWmTn0JHOoqAp4M0rc8sghpFJX_iQVCxXj9V3bX4_4PHJVql9dabZgUIPwDNIwgdTLHjafS4j92VvU_rCimsOs99QLAmMf5-woJg1PTiF-Mz2XJzXjQV8WL9QDmNfwX451MsEqrMlWO333tCm4ATyu78cLna2-JqkGMdMrIOPiTREG_AGgx9UXnIScljNpODkaw3B-42utFXFytJowyJ4SUwPrpOglMXTKOBlnjZsDroQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131308,"updated":1636131308,"recoveryLevel":"Recoverable+Purgeable"}}' - headers: - cache-control: - - no-cache - content-length: - - '680' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:08 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 200 - message: OK -version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml deleted file mode 100644 index 33cf9673d2e0..000000000000 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_0_vault.yaml +++ /dev/null @@ -1,102 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '0' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/create?api-version=7.0 - response: - body: - string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing - a Bearer or PoP token."}}' - headers: - cache-control: - - no-cache - content-length: - - '97' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:10 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - www-authenticate: - - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", - resource="https://vault.azure.net" - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 401 - message: Unauthorized -- request: - body: '{"kty": "RSA"}' - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '14' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/create?api-version=7.0 - response: - body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6aa18c5/162542c668594a71a7bebd6bf8165a55","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v74EsYu7Geq-OA6aFIf4G2DXYjEbThTMpPWGNpzc12IQmELglwa2c75l1r9fSHG8DEHF8VOpxB5lAH6tmmHDO1Kd6y_vnI3Lokf8bkrCNDvtsNjhOFd6ZOHJ65wC3UK8KR_TBy0u34wyZSMZi74k8ykTWUHQx47Qi5-KZstQLqq8kVFXz0BCGx3B-WgjZOTjedR0il9Zw1z7pc9NyqiV07mLbGq1WMtO5KfLtE3paiemUPMe2o_nW0xr7b8sESRmv5KLi4zXqJ0rtLLWu6qOI4YlClym0c9m5pA2qrJLHvxDyPJnxXDZFG-HqBC_4QNahOMiNq6pqs-daD5kQA1w4Q","e":"AQAB"},"attributes":{"enabled":true,"created":1636131311,"updated":1636131311,"recoveryLevel":"Recoverable+Purgeable"}}' - headers: - cache-control: - - no-cache - content-length: - - '679' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:10 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 200 - message: OK -version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml deleted file mode 100644 index 6e7ee6984d04..000000000000 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_1_vault.yaml +++ /dev/null @@ -1,102 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '0' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/create?api-version=7.1 - response: - body: - string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing - a Bearer or PoP token."}}' - headers: - cache-control: - - no-cache - content-length: - - '97' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:12 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - www-authenticate: - - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", - resource="https://vault.azure.net" - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 401 - message: Unauthorized -- request: - body: '{"kty": "RSA"}' - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '14' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/create?api-version=7.1 - response: - body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b118c6/975983b8bdd1459d80df03b7e3383cbd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"p18DJDreSsUkjyaW1s0JfjtqOxKRWFvP2ZVQKqnxlEw1CekrGg5pPB1fyZrkYjdrcSg1rNe6ua1c0Xr3O-WSZZfgm7zmUr9V5zUinFl4QTBqpQ4TpYXLAIHmXCR8RTO_NyhvlaMyeIeMZ6KV7H1ga9HI5-DQ94dlIGjSPNYmppZ-JQ4pMvjrhNRBHs8NE0VCYsnFnw8T3gz-r2OObQ2vfh4jUzjDL4LVJAG5lJe1yhIgefaCUGJD-i0gemFZls1BhGVkzP1nrq6DvC3JYkfRCmuifuODgiTrQdA_zOTX6HmFsU5C8904gjs3sDSukazKR7Y_pvwq7g3Cnf_LN2lhDQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131314,"updated":1636131314,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' - headers: - cache-control: - - no-cache - content-length: - - '700' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:13 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 200 - message: OK -version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml deleted file mode 100644 index ebabbc5d9778..000000000000 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_mhsm.yaml +++ /dev/null @@ -1,89 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '0' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/create?api-version=7.2 - response: - body: - string: '' - headers: - cache-control: - - no-cache - content-length: - - '0' - content-security-policy: - - default-src 'self' - content-type: - - application/json; charset=utf-8 - strict-transport-security: - - max-age=31536000; includeSubDomains - www-authenticate: - - Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", - resource="https://managedhsm.azure.net" - x-content-type-options: - - nosniff - x-frame-options: - - SAMEORIGIN - x-ms-server-latency: - - '0' - status: - code: 401 - message: Unauthorized -- request: - body: '{"kty": "RSA"}' - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '14' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/create?api-version=7.2 - response: - body: - string: '{"attributes":{"created":1636131317,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636131317},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keyedd01850/dca01d9198cb035c24ec367adf277e8c","kty":"RSA-HSM","n":"zpWKZrFAszudjizF-IxDdFP5jAYzb52X5SWRbg_4xxMv-Xt4qdHmb2ip7KzI1pFojD2nFmuJ966IBQQU7xjNNfwjDBO-fEI3ZMiEiF0Zg2_sET5TGuAwn5AGM-qPiCkQvGGVVDqbu2-_e11AgfJ4IcT1N5P1Nz8AmPlKD_KNAbsVhIkC0WrYJKhaZFsL8z5w3EBqPLzvi0aWZ4f2n_w3Y6MxkCNY1JeqjlXLYjVXOWFy29t2zY7eYWLq4IUqPQzR8J05osE97aScAddEAMtPZvn5PL7ooqGEs9W8mvlJJIOBSbWFDuElcrEGdONDiibcFlFw_mu7dvzv9st3ppSzNQ"}}' - headers: - cache-control: - - no-cache - content-length: - - '741' - content-security-policy: - - default-src 'self' - content-type: - - application/json; charset=utf-8 - strict-transport-security: - - max-age=31536000; includeSubDomains - x-content-type-options: - - nosniff - x-frame-options: - - SAMEORIGIN - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; - x-ms-keyvault-region: - - westus - x-ms-server-latency: - - '255' - status: - code: 200 - message: OK -version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml deleted file mode 100644 index c4a2b0941acf..000000000000 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_2_vault.yaml +++ /dev/null @@ -1,102 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '0' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/create?api-version=7.2 - response: - body: - string: '{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing - a Bearer or PoP token."}}' - headers: - cache-control: - - no-cache - content-length: - - '97' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:19 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - www-authenticate: - - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", - resource="https://vault.azure.net" - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 401 - message: Unauthorized -- request: - body: '{"kty": "RSA"}' - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '14' - Content-Type: - - application/json - User-Agent: - - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) - method: POST - uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/create?api-version=7.2 - response: - body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key6b818c7/64dd36b3399149e6bd9a9a3d8318e508","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tf4X2p2fbuObLjYmDdIPpiBWjGK5X9xtaICdcOw4vKyrv3TgLyl4C_0wHcD6y5OSyqzS-c_SYLkD7x2Wl2TDQueZvOj60Nm0BcD9fN8JbmF2z7GsdyKj-ovowRZbpXbyEo2TS0giHcWlt5LkhwwrX-13GYTIc2aUXxxChd5K9AWxQLmTksuJtIyL9lRhyzfXBRst4hBuYIhsMKcYaFcKzREvqmeYFq2ZIp4MGsxZrfO_MvEfdhpzLARrkMuXVI71urvuuF7A9qn9ZoA1J4cq_9z3sB7dXqPv0F0TA2J0PCinaVbZQLg4ydxuJkq2ZboIIUVPyWE1yJNDmqSZp3rmEQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131320,"updated":1636131320,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' - headers: - cache-control: - - no-cache - content-length: - - '700' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 05 Nov 2021 16:55:20 GMT - expires: - - '-1' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000;includeSubDomains - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.9.150.1 - x-powered-by: - - ASP.NET - status: - code: 200 - message: OK -version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml index 583377e13d8c..6007ed705560 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -61,7 +61,7 @@ interactions: uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/create?api-version=7.3-preview response: body: - string: '{"attributes":{"created":1636131323,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636131323},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/451b8057153600668fa009e735a9d996","kty":"RSA-HSM","n":"iwhflbo8-_WYe32hbVI72HZyCBMW7TF-a1LYOiKyc9eDgXsDGc7TBQhfyIDYl-AXB2n4Y4hJtZWFoU4ePSF3PI5dpHrAwtxa7SifiR256Fad90bH03jXsjrgiQz3H3LOZVPomDSnA3FJrYawp9bealfge2RhrjQY_wGjOyhHUtIY-7_6aMqbPCORG3wPDQ6cflFl0_YNjCWQfs9iNxZ_xmYk19vmypvt1p9k6eiEPo8OYMuP2hmib9MhRh40mQhoYO2WuMKAGdNZnHFQ7DYsterHxPfSLT6f-3PWAleV7tNbcrcC846hDzoELVcNLEYMYO5ytS5B-8OH1BFYgTHvFQ"}}' + string: '{"attributes":{"created":1636135977,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636135977},"key":{"e":"AQAB","key_ops":["wrapKey","sign","verify","encrypt","decrypt","unwrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/9ef4797f09fe05c595a6435eb95b5993","kty":"RSA-HSM","n":"qaB0QiIXjV1Z5qy8wFtT3ouRrEaP7GyCiO6HLOip6KCwMPpWQFylbZ_t0qVAorluh78TmozicZMrvT44s6NVjowzywgPqQU7C3b0oDL4_4mgOTK_1pSFXvwkIomjanrCcrVB8rt850wDLprgKoAspls9Jn02JKtbx3223oBlTkTLJ2H6PseotOYbV7-0q7lz9JJm4mrj6OdmRuoRDI4uRjNBdCIHhO9aKPXC0EdESy4pcwEiVaLGEqzcnG97IIMdqWLVXS17Dsm8d4fTn6AMeiaT7YooiVVX3xVzB2JPccp3UY27v1LB0rF8bfhWzJoaSBwBSMGPB9MefT1D3OrJ4Q"}}' headers: cache-control: - no-cache @@ -82,7 +82,7 @@ interactions: x-ms-keyvault-region: - westus x-ms-server-latency: - - '233' + - '303' status: code: 200 message: OK diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml index 6bc086a4c7a8..b46a1bf8ef1f 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml @@ -28,7 +28,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 05 Nov 2021 16:55:26 GMT + - Fri, 05 Nov 2021 18:13:00 GMT expires: - '-1' pragma: @@ -70,7 +70,7 @@ interactions: uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/create?api-version=7.3-preview response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/64bf4a78c2c5422cb10f0ce9110af961","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ufrRN_nbOhbytEUrNHcnAXm3ciujUOmheywVnVDhJ9NlIREaqA6ALOJkgv5CczPJJpI6TcLv6BWpyH-Dvn8RwjHtBkXHOnRt4r_x4N4u85nMvf9mTlxTfggY-WZyWLaAhCzmaURooon0I6tRHFRQ5km4BXOsSo2G2jcHs0p-j_KSPTQKh8l6diMPGM3H3rYhZ1vVyvQY9zpgACf8AJccssBkKQLllF-6qpPXuELR4zLLFofhE6I-2_PZDt7NKROy4VRunV9CVNJz8uDjYFElycWlYCmJNwrPVgU8A2X6MpLQgWy1jkXw8r89li_EmucAROTJWrUvJlW1PlUBlbT9FQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131326,"updated":1636131326,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/132d1171d7e743a8a027ca63e584ec9c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rwGFPKkCV5Ykc3vQU4uNcnsnxQEp-WSPNXkQj_yJ3yR7Bic_3TH7zOThfa_SdPVcgzRio2i2gz9GL5eX5LQInnMTWIz5z4XH2kxtSljI_PDen8eZaCUAPssVt6Vr2pA46xlwYAdjLLAyLPhl0tYiJofwlJrP_FWX7qHKdwfM-PgDKivdifS36_5yfKZxk1GKSUhbo2q1-YDZhFSqo0U49I0MCxfl7YTzr0d0HjlZXa2k8JxijoRg8RNomddKuEEXyT0WOjGOeIYCvwtIf9izIFFoe4lk_2d9ePHJk44l5kAc68QHqHc_HvEDR8HINGHuzsPPZnEJmmmC13paV9AHqQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636135980,"updated":1636135980,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' headers: cache-control: - no-cache @@ -79,7 +79,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 05 Nov 2021 16:55:26 GMT + - Fri, 05 Nov 2021 18:13:00 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml index 5a6f4d20e701..19f2e41b89e4 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -25,7 +25,7 @@ interactions: resource="https://managedhsm.azure.net" x-content-type-options: nosniff x-frame-options: SAMEORIGIN - x-ms-server-latency: '0' + x-ms-server-latency: '1' status: code: 401 message: Unauthorized @@ -45,7 +45,7 @@ interactions: uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview response: body: - string: '{"attributes":{"created":1636131342,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636131342},"key":{"e":"AQAB","key_ops":["wrapKey","decrypt","encrypt","unwrapKey","sign","verify"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/32f6303512674d76aecfb61ceca28159","kty":"RSA-HSM","n":"oTeTKYMH7HGsV4ScjABjV8KAmjVORXmGQ6aaqTNWMdmyjfyXFyjDTh4yK4H7YGYtf-fuZje5eFYCs9jQg1kGHdDUqVTqeNjij5fl24H2Sh3bIwtj_UIQwFh3K4tYikqq7MD2Yi9d-wlnDrjems-flEImMWWNAGkJ7GK0hnO4rLQRjb4yVBYywEvAExHUwWisE32302i7mz6PRJJrY8_2rogaWbldktTw0tZG2oTn-Q10j9rMB2zoBMVl77kzaRVNAbAmShIZh5flLz1TTdfh-QdTEivwhE3m8GdY_Le7tNbqqzHEpLcZCpCBivHbiH7EVOqs_QwDDnFzCtsd7GfQWw"}}' + string: '{"attributes":{"created":1636135990,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636135990},"key":{"e":"AQAB","key_ops":["wrapKey","sign","verify","encrypt","decrypt","unwrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/ea983de8a9f60784bc218cea1026c919","kty":"RSA-HSM","n":"iYldKtBTOfO9M2dqQrQr_LvAFOba3-0LR4PtiunRHMyDjC2oZJ276-CEwpGTrl2y3j9Mn4KjrnY6gkAuruhS40NQ-FXN6UUaTjKYOfoDR8kZauXoKG-7qmqNhucACm2gPfXkoz3ngQAiACvHScicw8KfUZEx-r91Ck9wg2omHhU3zp3bPCTp963fj6vv8u7mdKlbkt9RVT3tyu7GnG0lZa0X_kpIPyXAEuFgzRVTeItgah0MwxgB9SlmxASgVur0JadUqkPfGR8mrNRGrRxTw_STyCpjnw356UaKgnxTABahZqugYRNfYfaMDglbaAVEJl-cWrlPXHHYLlYC7NzNBQ"}}' headers: cache-control: no-cache content-length: '741' @@ -56,7 +56,7 @@ interactions: x-frame-options: SAMEORIGIN x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; x-ms-keyvault-region: westus - x-ms-server-latency: '362' + x-ms-server-latency: '271' status: code: 200 message: OK diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml index 73fb38702775..b39a3ac1ae00 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml @@ -20,7 +20,7 @@ interactions: cache-control: no-cache content-length: '97' content-type: application/json; charset=utf-8 - date: Fri, 05 Nov 2021 16:55:44 GMT + date: Fri, 05 Nov 2021 18:13:12 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -50,12 +50,12 @@ interactions: uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/41af6e7fded24fcc84dbecf0b217c0c6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"10WaAIJ6LZzb7JCqZm8nkkjxjfTV_1PgI6a54zYBG3C0IsYigoyOrUp3ZSv2i-Mx5gSrqdcN1AQnt1GFiIEQjtO1kz24l7NIelrJn0YcCM8c2b0sBAWSY1zRaZzUmHJuAGtxUgBs77bOa7B-izuK1VSAhANu2I9cZVsTywSzveIqIuBRt5dN6-YsLPep1HS-ENPtCE7IgUOVQi6kFh-zLl-OcJ5MOiDuhoU0gIJ0e4aVWvr4kVLW5UHrcFkr1fUVxHzFwZJDNiez2tr8RM3utj4Le46_7W5yJjt-s8Q6anSojUhmlp4r7-hSZ4_EfRfZlifHWix9ZqiuPHuZFOJKyQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636131345,"updated":1636131345,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/63e1215f659f423b9e2f74b1f645802b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"5ibXDBzD5GIADx1Te_WJaLx2LQppykGOCC5Ici8SsLtAtmvRlCqhrzhCqlT1AQDp142JOv53j8iaBP_OWJ2QuUcn-1J_OwTW-bRroUdsr0UdDoSpKb7SgL-4O9ZWUMQZQCVVbUpqYhbVBE_faPskABG96XgwmMI6pdOTIkRAtu0eIgd5BhYre_O0eLGWImjNf8ICitAHC80xZ-sDF3Ckj5P3WU8T-nA64xzix62bw5WNBLD3LqRol1un9bxClEJyEwtPTwrk-SUrIhebdErzPafDRQlqVTIS1SDfvSSvViSC6EDuLhXMT6HDJOrcB9tI1hZRW0KvFeMJMyNRpnUzEQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636135993,"updated":1636135993,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' headers: cache-control: no-cache content-length: '701' content-type: application/json; charset=utf-8 - date: Fri, 05 Nov 2021 16:55:45 GMT + date: Fri, 05 Nov 2021 18:13:12 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py index bd1ca41151d2..c06f63190150 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py @@ -24,6 +24,7 @@ from azure.identity import ClientSecretCredential from azure.keyvault.keys import KeyClient from azure.keyvault.keys._shared import ChallengeAuthPolicy, HttpChallenge, HttpChallengeCache +from azure.keyvault.keys._shared.client_base import DEFAULT_VERSION import pytest @@ -32,27 +33,29 @@ from _test_case import client_setup, get_decorator, KeysTestCase -all_api_versions = get_decorator() +only_default_version = get_decorator(api_versions=[DEFAULT_VERSION]) class ChallengeAuthTests(KeysTestCase, KeyVaultTestCase): def __init__(self, *args, **kwargs): super(ChallengeAuthTests, self).__init__(*args, match_body=False, **kwargs) - @all_api_versions() + @only_default_version() @client_setup def test_multitenant_authentication(self, client, is_hsm, **kwargs): + if not self.is_live: + pytest.skip("This test is incompatible with vcrpy in playback") + client_id = os.environ.get("KEYVAULT_CLIENT_ID") client_secret = os.environ.get("KEYVAULT_CLIENT_SECRET") - if self.is_live and not client_id and client_secret: + if not (client_id and client_secret): pytest.skip("Values for KEYVAULT_CLIENT_ID and KEYVAULT_CLIENT_SECRET are required") # we set up a client for this method to align with the async test, but we actually want to create a new client # this new client should use a credential with an initially fake tenant ID and still succeed with a real request - api_version = client.api_version credential = ClientSecretCredential(tenant_id=str(uuid4()), client_id=client_id, client_secret=client_secret) vault_url = self.managed_hsm_url if is_hsm else self.vault_url - client = KeyClient(vault_url=vault_url, credential=credential, api_version=api_version) + client = KeyClient(vault_url=vault_url, credential=credential) if self.is_live: time.sleep(2) # to avoid throttling by the service @@ -117,7 +120,7 @@ def test_challenge_parsing(): assert challenge.get_authorization_server() == authority assert challenge.get_resource() == resource - assert challenge.get_tenant_id() == tenant + assert challenge.tenant_id == tenant @empty_challenge_cache @@ -328,11 +331,7 @@ def get_token(*_, **__): @empty_challenge_cache def test_preserves_options_and_headers(): - """After a challenge, the original request should be sent with its options and headers preserved. - - If a policy mutates the options or headers of the challenge (unauthorized) request, the options of the service - request should be present when it is sent with authorization. - """ + """After a challenge, the policy should send the original request with its options and headers preserved""" url = get_random_url() token = "**" @@ -351,51 +350,31 @@ def get_token(*_, **__): ] + [mock_response()] * 2, ) - challenge_policy = ChallengeAuthPolicy(credential=credential) - policies = get_policies_for_request_mutation_test(challenge_policy) - pipeline = Pipeline(policies=policies, transport=transport) - - response = pipeline.run(HttpRequest("GET", url)) - - # ensure the mock sans I/O policies were called - for policy in policies: - if hasattr(policy, "on_request"): - assert policy.on_request.called, "mock policy wasn't invoked" - -def get_policies_for_request_mutation_test(challenge_policy): - # create mock policies to add, remove, and verify an option and header key = "foo" value = "bar" - do_not_handle = lambda _: False def add(request): # add the expected option and header request.context.options[key] = value request.http_request.headers[key] = value - adder = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock(wraps=add), on_exception=do_not_handle) - - def remove(request): - # remove expected header and all options of unauthorized (challenge) requests - if not request.http_request.headers.get("Authorization"): - request.http_request.headers.pop(key, None) - request.context.options = {} - - remover = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock(wraps=remove), on_exception=do_not_handle) + adder = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock(wraps=add), on_exception=lambda _: False) def verify(request): # authorized (non-challenge) requests should have the expected option and header if request.http_request.headers.get("Authorization"): - assert request.context.options.get(key) == value, "request option not preserved across challenge" - assert request.http_request.headers.get(key) == value, "headers not preserved across challenge" + assert request.context.options.get(key) == value, "request option wasn't preserved across challenge" + assert request.http_request.headers.get(key) == value, "headers wasn't preserved across challenge" verifier = Mock(spec=SansIOHTTPPolicy, on_request=Mock(wraps=verify)) - # Mutating the challenge request shouldn't affect the authorized request. - # This is the pipeline flow: - # 1. add option and header - # 2. challenge auth - # 3. remove option, header from unauthorized request - # 4. verify option, header on authorized request - return [adder, challenge_policy, remover, verifier] + challenge_policy = ChallengeAuthPolicy(credential=credential) + policies = [adder, challenge_policy, verifier] + pipeline = Pipeline(policies=policies, transport=transport) + + pipeline.run(HttpRequest("GET", url)) + + # ensure the mock sans I/O policies were called + assert adder.on_request.called, "mock policy wasn't invoked" + assert verifier.on_request.called, "mock policy wasn't invoked" diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py index cef73c7132ca..393bb5f35861 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py @@ -11,48 +11,49 @@ import time from uuid import uuid4 -try: - from unittest.mock import Mock, patch -except ImportError: # python < 3.3 - from mock import Mock, patch # type: ignore +from unittest.mock import AsyncMock, patch from azure.core.credentials import AccessToken from azure.core.exceptions import ServiceRequestError from azure.core.pipeline import AsyncPipeline +from azure.core.pipeline.policies import SansIOHTTPPolicy from azure.core.pipeline.transport import HttpRequest from azure.identity.aio import ClientSecretCredential from azure.keyvault.keys.aio import KeyClient from azure.keyvault.keys._shared import AsyncChallengeAuthPolicy, HttpChallenge, HttpChallengeCache +from azure.keyvault.keys._shared.client_base import DEFAULT_VERSION import pytest from _shared.helpers import mock_response, Request from _shared.helpers_async import async_validating_transport from _shared.test_case_async import KeyVaultTestCase from _test_case import client_setup, get_decorator, KeysTestCase -from test_challenge_auth import empty_challenge_cache, get_policies_for_request_mutation_test, get_random_url +from test_challenge_auth import empty_challenge_cache, get_random_url -all_api_versions = get_decorator(is_async=True) +only_default_version = get_decorator(is_async=True, api_versions=[DEFAULT_VERSION]) class ChallengeAuthTests(KeysTestCase, KeyVaultTestCase): def __init__(self, *args, **kwargs): super().__init__(*args, match_body=False, **kwargs) - @all_api_versions() + @only_default_version() @client_setup async def test_multitenant_authentication(self, client, is_hsm, **kwargs): + if not self.is_live: + pytest.skip("This test is incompatible with vcrpy in playback") + client_id = os.environ.get("KEYVAULT_CLIENT_ID") client_secret = os.environ.get("KEYVAULT_CLIENT_SECRET") - if self.is_live and not client_id and client_secret: + if not (client_id and client_secret): pytest.skip("Values for KEYVAULT_CLIENT_ID and KEYVAULT_CLIENT_SECRET are required") # we set up a client for this method so it gets awaited, but we actually want to create a new client # this new client should use a credential with an initially fake tenant ID and still succeed with a real request - api_version = client.api_version credential = ClientSecretCredential(tenant_id=str(uuid4()), client_id=client_id, client_secret=client_secret) vault_url = self.managed_hsm_url if is_hsm else self.vault_url - client = KeyClient(vault_url=vault_url, credential=credential, api_version=api_version) + client = KeyClient(vault_url=vault_url, credential=credential) if self.is_live: await asyncio.sleep(2) # to avoid throttling by the service @@ -67,8 +68,8 @@ async def test_enforces_tls(): url = "http://not.secure" HttpChallengeCache.set_challenge_for_url(url, HttpChallenge(url, "Bearer authorization=_, resource=_")) - credential = Mock() - pipeline = AsyncPipeline(transport=Mock(), policies=[AsyncChallengeAuthPolicy(credential)]) + credential = AsyncMock() + pipeline = AsyncPipeline(transport=AsyncMock(), policies=[AsyncChallengeAuthPolicy(credential)]) with pytest.raises(ServiceRequestError): await pipeline.run(HttpRequest("GET", url)) @@ -98,7 +99,7 @@ async def send(request): assert request.headers["Content-Length"] assert request.body == expected_content assert expected_token in request.headers["Authorization"] - return Mock(status_code=200) + return AsyncMock(status_code=200) raise ValueError("unexpected request") async def get_token(*scopes, **_): @@ -106,8 +107,10 @@ async def get_token(*scopes, **_): assert scopes[0] == expected_scope return AccessToken(expected_token, 0) - credential = Mock(get_token=Mock(wraps=get_token)) - pipeline = AsyncPipeline(policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=Mock(send=send)) + credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) + pipeline = AsyncPipeline( + policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=AsyncMock(send=send) + ) request = HttpRequest("POST", get_random_url()) request.set_bytes_body(expected_content) await pipeline.run(request) @@ -120,12 +123,12 @@ async def get_token(*scopes, **_): resource = "https://challenge.resource" scope = resource + "/.default" - challenge_with_resource = Mock( + challenge_with_resource = AsyncMock( status_code=401, headers={"WWW-Authenticate": 'Bearer authorization="{}", resource={}'.format(endpoint, resource)}, ) - challenge_with_scope = Mock( + challenge_with_scope = AsyncMock( status_code=401, headers={"WWW-Authenticate": 'Bearer authorization="{}", scope={}'.format(endpoint, scope)} ) @@ -158,15 +161,17 @@ async def send(request): assert request.headers["Content-Length"] assert request.body == expected_content assert expected_token in request.headers["Authorization"] - return Mock(status_code=200) + return AsyncMock(status_code=200) raise ValueError("unexpected request") async def get_token(*_, **kwargs): assert kwargs.get("tenant_id") == expected_tenant return AccessToken(expected_token, 0) - credential = Mock(get_token=Mock(wraps=get_token)) - pipeline = AsyncPipeline(policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=Mock(send=send)) + credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) + pipeline = AsyncPipeline( + policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=AsyncMock(send=send) + ) request = HttpRequest("POST", get_random_url()) request.set_bytes_body(expected_content) await pipeline.run(request) @@ -176,7 +181,7 @@ async def get_token(*_, **kwargs): tenant = "tenant-id" endpoint = "https://authority.net/{}".format(tenant) - challenge = Mock( + challenge = AsyncMock( status_code=401, headers={"WWW-Authenticate": 'Bearer authorization="{}", resource=https://challenge.resource'.format(endpoint)}, ) @@ -230,7 +235,7 @@ async def test_policy_updates_cache(): async def get_token(*_, **__): return token - credential = Mock(get_token=Mock(wraps=get_token)) + credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) pipeline = AsyncPipeline(policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=transport) # policy should complete and cache the first challenge and access token @@ -261,7 +266,7 @@ async def test_token_expiration(): async def get_token(*_, **__): return token - credential = Mock(get_token=Mock(wraps=get_token)) + credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) transport = async_validating_transport( requests=[ Request(), @@ -291,11 +296,7 @@ async def get_token(*_, **__): @pytest.mark.asyncio @empty_challenge_cache async def test_preserves_options_and_headers(): - """After a challenge, the original request should be sent with its options and headers preserved. - - If a policy mutates the options or headers of the challenge (unauthorized) request, the options of the service - request should be present when it is sent with authorization. - """ + """After a challenge, the policy should send the original request with its options and headers preserved""" url = get_random_url() @@ -304,7 +305,7 @@ async def test_preserves_options_and_headers(): async def get_token(*_, **__): return AccessToken(token, 0) - credential = Mock(get_token=Mock(wraps=get_token)) + credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) transport = async_validating_transport( requests=[Request()] * 2 + [Request(required_headers={"Authorization": "Bearer " + token})], @@ -315,13 +316,30 @@ async def get_token(*_, **__): ] + [mock_response()] * 2, ) + key = "foo" + value = "bar" + + def add(request): + # add the expected option and header + request.context.options[key] = value + request.http_request.headers[key] = value + + adder = AsyncMock(spec_set=SansIOHTTPPolicy, on_request=AsyncMock(wraps=add), on_exception=lambda _: False) + + def verify(request): + # authorized (non-challenge) requests should have the expected option and header + if request.http_request.headers.get("Authorization"): + assert request.context.options.get(key) == value, "request option wasn't preserved across challenge" + assert request.http_request.headers.get(key) == value, "headers wasn't preserved across challenge" + + verifier = AsyncMock(spec=SansIOHTTPPolicy, on_request=AsyncMock(wraps=verify)) + challenge_policy = AsyncChallengeAuthPolicy(credential=credential) - policies = get_policies_for_request_mutation_test(challenge_policy) + policies = [adder, challenge_policy, verifier] pipeline = AsyncPipeline(policies=policies, transport=transport) - response = await pipeline.run(HttpRequest("GET", url)) + await pipeline.run(HttpRequest("GET", url)) - # ensure the mock sans I/O policies were used - for policy in policies: - if hasattr(policy, "on_request"): - assert policy.on_request.called, "mock policy wasn't invoked" + # ensure the mock sans I/O policies were called + assert adder.on_request.called, "mock policy wasn't invoked" + assert verifier.on_request.called, "mock policy wasn't invoked" From f4a720390e87327c3c8ce438671cb0c1ed77e566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 5 Nov 2021 11:42:21 -0700 Subject: [PATCH 08/15] Update shared_requirements for core --- .../tests/test_challenge_auth_async.py | 34 +++++++++---------- shared_requirements.txt | 4 +++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py index 393bb5f35861..971c5762ca24 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py @@ -11,7 +11,7 @@ import time from uuid import uuid4 -from unittest.mock import AsyncMock, patch +from unittest.mock import Mock, patch from azure.core.credentials import AccessToken from azure.core.exceptions import ServiceRequestError @@ -68,8 +68,8 @@ async def test_enforces_tls(): url = "http://not.secure" HttpChallengeCache.set_challenge_for_url(url, HttpChallenge(url, "Bearer authorization=_, resource=_")) - credential = AsyncMock() - pipeline = AsyncPipeline(transport=AsyncMock(), policies=[AsyncChallengeAuthPolicy(credential)]) + credential = Mock() + pipeline = AsyncPipeline(transport=Mock(), policies=[AsyncChallengeAuthPolicy(credential)]) with pytest.raises(ServiceRequestError): await pipeline.run(HttpRequest("GET", url)) @@ -99,7 +99,7 @@ async def send(request): assert request.headers["Content-Length"] assert request.body == expected_content assert expected_token in request.headers["Authorization"] - return AsyncMock(status_code=200) + return Mock(status_code=200) raise ValueError("unexpected request") async def get_token(*scopes, **_): @@ -107,9 +107,9 @@ async def get_token(*scopes, **_): assert scopes[0] == expected_scope return AccessToken(expected_token, 0) - credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) + credential = Mock(get_token=Mock(wraps=get_token)) pipeline = AsyncPipeline( - policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=AsyncMock(send=send) + policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=Mock(send=send) ) request = HttpRequest("POST", get_random_url()) request.set_bytes_body(expected_content) @@ -123,12 +123,12 @@ async def get_token(*scopes, **_): resource = "https://challenge.resource" scope = resource + "/.default" - challenge_with_resource = AsyncMock( + challenge_with_resource = Mock( status_code=401, headers={"WWW-Authenticate": 'Bearer authorization="{}", resource={}'.format(endpoint, resource)}, ) - challenge_with_scope = AsyncMock( + challenge_with_scope = Mock( status_code=401, headers={"WWW-Authenticate": 'Bearer authorization="{}", scope={}'.format(endpoint, scope)} ) @@ -161,16 +161,16 @@ async def send(request): assert request.headers["Content-Length"] assert request.body == expected_content assert expected_token in request.headers["Authorization"] - return AsyncMock(status_code=200) + return Mock(status_code=200) raise ValueError("unexpected request") async def get_token(*_, **kwargs): assert kwargs.get("tenant_id") == expected_tenant return AccessToken(expected_token, 0) - credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) + credential = Mock(get_token=Mock(wraps=get_token)) pipeline = AsyncPipeline( - policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=AsyncMock(send=send) + policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=Mock(send=send) ) request = HttpRequest("POST", get_random_url()) request.set_bytes_body(expected_content) @@ -181,7 +181,7 @@ async def get_token(*_, **kwargs): tenant = "tenant-id" endpoint = "https://authority.net/{}".format(tenant) - challenge = AsyncMock( + challenge = Mock( status_code=401, headers={"WWW-Authenticate": 'Bearer authorization="{}", resource=https://challenge.resource'.format(endpoint)}, ) @@ -235,7 +235,7 @@ async def test_policy_updates_cache(): async def get_token(*_, **__): return token - credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) + credential = Mock(get_token=Mock(wraps=get_token)) pipeline = AsyncPipeline(policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=transport) # policy should complete and cache the first challenge and access token @@ -266,7 +266,7 @@ async def test_token_expiration(): async def get_token(*_, **__): return token - credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) + credential = Mock(get_token=Mock(wraps=get_token)) transport = async_validating_transport( requests=[ Request(), @@ -305,7 +305,7 @@ async def test_preserves_options_and_headers(): async def get_token(*_, **__): return AccessToken(token, 0) - credential = AsyncMock(get_token=AsyncMock(wraps=get_token)) + credential = Mock(get_token=Mock(wraps=get_token)) transport = async_validating_transport( requests=[Request()] * 2 + [Request(required_headers={"Authorization": "Bearer " + token})], @@ -324,7 +324,7 @@ def add(request): request.context.options[key] = value request.http_request.headers[key] = value - adder = AsyncMock(spec_set=SansIOHTTPPolicy, on_request=AsyncMock(wraps=add), on_exception=lambda _: False) + adder = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock(wraps=add), on_exception=lambda _: False) def verify(request): # authorized (non-challenge) requests should have the expected option and header @@ -332,7 +332,7 @@ def verify(request): assert request.context.options.get(key) == value, "request option wasn't preserved across challenge" assert request.http_request.headers.get(key) == value, "headers wasn't preserved across challenge" - verifier = AsyncMock(spec=SansIOHTTPPolicy, on_request=AsyncMock(wraps=verify)) + verifier = Mock(spec=SansIOHTTPPolicy, on_request=Mock(wraps=verify)) challenge_policy = AsyncChallengeAuthPolicy(credential=credential) policies = [adder, challenge_policy, verifier] diff --git a/shared_requirements.txt b/shared_requirements.txt index 1a7676337da6..ff41986ef306 100644 --- a/shared_requirements.txt +++ b/shared_requirements.txt @@ -140,6 +140,10 @@ backports.functools-lru-cache >= 1.6.4; python_version == "2.7" #override azure-data-tables msrest>=0.6.21 #override azure-eventhub azure-core<2.0.0,>=1.14.0 #override azure-identity azure-core<2.0.0,>=1.11.0 +#override azure-keyvault-administration azure-core<2.0.0,>=1.15.0 +#override azure-keyvault-certificates azure-core<2.0.0,>=1.15.0 +#override azure-keyvault-keys azure-core<2.0.0,>=1.15.0 +#override azure-keyvault-secrets azure-core<2.0.0,>=1.15.0 #override azure-identity cryptography>=2.5 #override azure-keyvault-administration msrest>=0.6.21 #override azure-keyvault-administration azure-core<2.0.0,>=1.11.0 From 697f1f02015713d28782085383d67718e1e84c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 5 Nov 2021 17:50:00 -0700 Subject: [PATCH 09/15] Better parsing, remove _last_tenant_id --- .../_shared/async_challenge_auth_policy.py | 18 ++++-------------- .../keys/_shared/challenge_auth_policy.py | 19 ++++--------------- .../keyvault/keys/_shared/http_challenge.py | 3 ++- 3 files changed, 10 insertions(+), 30 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index e367e0934854..e25f69017135 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -28,22 +28,14 @@ class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args: "Any", **kwargs: "Any") -> None: - self._last_tenant_id = None # type: Optional[str] - super().__init__(*args, **kwargs) - async def on_request(self, request: "PipelineRequest") -> None: _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - await super().on_request(request) - else: - # acquire a new token because this vault is in a different tenant - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + await super().on_request(request) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -67,8 +59,6 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 83206f970b22..7f8463c08910 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -55,24 +55,15 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args, **kwargs): - # type: (*Any, **Any) -> None - self._last_tenant_id = None # type: Optional[str] - super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) - def on_request(self, request): # type: (PipelineRequest) -> None _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - super(ChallengeAuthPolicy, self).on_request(request) - else: - # acquire a new token because this vault is in a different tenant - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and + # it knows the scope to request for a new token. Note that if the vault has moved to a new + # tenant since our last request for it, this request will fail. + super(ChallengeAuthPolicy, self).on_request(request) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -96,8 +87,6 @@ def on_challenge(self, request, response): body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py index 46e668a59867..c52c90929ad9 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py @@ -42,7 +42,8 @@ def __init__(self, request_uri, challenge, response_headers=None): authorization_uri = self.get_authorization_server() # the authoritzation server URI should look something like https://login.windows.net/tenant-id - self.tenant_id = authorization_uri.split("/")[-1] or None + uri_path = parse.urlparse(authorization_uri).path.lstrip("/") + self.tenant_id = uri_path.split("/")[0] or None # if the response headers were supplied if response_headers: From 9b951a86759e6194e9bb883a3cf1670cf87ddacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Mon, 8 Nov 2021 16:08:42 -0800 Subject: [PATCH 10/15] Handle token requests w/ cached challenge --- .../_shared/async_challenge_auth_policy.py | 36 +++++-- .../keys/_shared/challenge_auth_policy.py | 37 ++++++-- ...enant_authentication_7_3_preview_mhsm.yaml | 88 ++++++++++++++++- ...nant_authentication_7_3_preview_vault.yaml | 94 ++++++++++++++++++- ...enant_authentication_7_3_preview_mhsm.yaml | 60 +++++++++++- ...nant_authentication_7_3_preview_vault.yaml | 64 ++++++++++++- .../tests/test_challenge_auth.py | 6 ++ .../tests/test_challenge_auth_async.py | 6 ++ 8 files changed, 369 insertions(+), 22 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index e25f69017135..57a762dabd43 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -21,8 +21,8 @@ from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineRequest, PipelineResponse + from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): @@ -32,11 +32,10 @@ async def on_request(self, request: "PipelineRequest") -> None: _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - await super().on_request(request) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + return await self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. @@ -62,3 +61,28 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 7f8463c08910..66941051f4a9 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -27,7 +27,6 @@ TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineResponse @@ -60,11 +59,10 @@ def on_request(self, request): _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - super(ChallengeAuthPolicy, self).on_request(request) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + self._handle_challenge(request, challenge) + response = self.next.send(request) + return self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. @@ -90,3 +88,30 @@ def on_challenge(self, request, response): self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + def _handle_challenge(self, request, challenge): + # type: (PipelineRequest, HttpChallenge) -> None + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + def _handle_response(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> PipelineResponse + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml index 6007ed705560..01d1a918d4e9 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -61,7 +61,7 @@ interactions: uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/create?api-version=7.3-preview response: body: - string: '{"attributes":{"created":1636135977,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636135977},"key":{"e":"AQAB","key_ops":["wrapKey","sign","verify","encrypt","decrypt","unwrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/9ef4797f09fe05c595a6435eb95b5993","kty":"RSA-HSM","n":"qaB0QiIXjV1Z5qy8wFtT3ouRrEaP7GyCiO6HLOip6KCwMPpWQFylbZ_t0qVAorluh78TmozicZMrvT44s6NVjowzywgPqQU7C3b0oDL4_4mgOTK_1pSFXvwkIomjanrCcrVB8rt850wDLprgKoAspls9Jn02JKtbx3223oBlTkTLJ2H6PseotOYbV7-0q7lz9JJm4mrj6OdmRuoRDI4uRjNBdCIHhO9aKPXC0EdESy4pcwEiVaLGEqzcnG97IIMdqWLVXS17Dsm8d4fTn6AMeiaT7YooiVVX3xVzB2JPccp3UY27v1LB0rF8bfhWzJoaSBwBSMGPB9MefT1D3OrJ4Q"}}' + string: '{"attributes":{"created":1636415841,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636415841},"key":{"e":"AQAB","key_ops":["wrapKey","sign","verify","encrypt","decrypt","unwrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/818c3c2903cc06289eaacf1a2070d5bd","kty":"RSA-HSM","n":"ncexSVGCn8VKmtNlZJAtTYK80rexx2xO_v_WOFuqCz3VYxqy7bzU2dsL3PWp66SqdU-OEXJ3jfuvyk4JCO2uYf8QF-7kVKCeB0N93pqh-2hMi_mDVaT0iMpB-USuJA34K0tDsouDa1B86WlXDeh1OBMFM4H_tVgDwgwS-zHXfM0L2UmTjekXSj5XRG2vaUztlIxfYKRrw4lSi5sRLv7VXiS-HWZG4x1U9miyCKtPdqAWqkqZ9O5yOQZ-B0Hk5yeO8TWbfMgyUc61H8ga3p0KExiLJatSCNPaxeOzlf-Eqb2g5pEFR74Nk9d_HkvW0bWs1elcMxZPehG_aF76U3GYwQ"}}' headers: cache-control: - no-cache @@ -82,7 +82,91 @@ interactions: x-ms-keyvault-region: - westus x-ms-server-latency: - - '303' + - '393' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/?api-version=7.3-preview + response: + body: + string: '{"attributes":{"created":1636415841,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636415841},"key":{"e":"AQAB","key_ops":["unwrapKey","decrypt","encrypt","verify","sign","wrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/818c3c2903cc06289eaacf1a2070d5bd","kty":"RSA-HSM","n":"ncexSVGCn8VKmtNlZJAtTYK80rexx2xO_v_WOFuqCz3VYxqy7bzU2dsL3PWp66SqdU-OEXJ3jfuvyk4JCO2uYf8QF-7kVKCeB0N93pqh-2hMi_mDVaT0iMpB-USuJA34K0tDsouDa1B86WlXDeh1OBMFM4H_tVgDwgwS-zHXfM0L2UmTjekXSj5XRG2vaUztlIxfYKRrw4lSi5sRLv7VXiS-HWZG4x1U9miyCKtPdqAWqkqZ9O5yOQZ-B0Hk5yeO8TWbfMgyUc61H8ga3p0KExiLJatSCNPaxeOzlf-Eqb2g5pEFR74Nk9d_HkvW0bWs1elcMxZPehG_aF76U3GYwQ"}}' + headers: + cache-control: + - no-cache + content-length: + - '741' + content-security-policy: + - default-src 'self' + content-type: + - application/json; charset=utf-8 + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ms-build-version: + - 1.0.20210929-1-5b78c022-develop + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: + - westus + x-ms-server-latency: + - '90' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/?api-version=7.3-preview + response: + body: + string: '{"attributes":{"created":1636415841,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636415841},"key":{"e":"AQAB","key_ops":["unwrapKey","decrypt","encrypt","verify","sign","wrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-keybfb31bb2/818c3c2903cc06289eaacf1a2070d5bd","kty":"RSA-HSM","n":"ncexSVGCn8VKmtNlZJAtTYK80rexx2xO_v_WOFuqCz3VYxqy7bzU2dsL3PWp66SqdU-OEXJ3jfuvyk4JCO2uYf8QF-7kVKCeB0N93pqh-2hMi_mDVaT0iMpB-USuJA34K0tDsouDa1B86WlXDeh1OBMFM4H_tVgDwgwS-zHXfM0L2UmTjekXSj5XRG2vaUztlIxfYKRrw4lSi5sRLv7VXiS-HWZG4x1U9miyCKtPdqAWqkqZ9O5yOQZ-B0Hk5yeO8TWbfMgyUc61H8ga3p0KExiLJatSCNPaxeOzlf-Eqb2g5pEFR74Nk9d_HkvW0bWs1elcMxZPehG_aF76U3GYwQ"}}' + headers: + cache-control: + - no-cache + content-length: + - '741' + content-security-policy: + - default-src 'self' + content-type: + - application/json; charset=utf-8 + strict-transport-security: + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-ms-build-version: + - 1.0.20210929-1-5b78c022-develop + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: + - westus + x-ms-server-latency: + - '0' status: code: 200 message: OK diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml index b46a1bf8ef1f..c251debdac2a 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth.test_multitenant_authentication_7_3_preview_vault.yaml @@ -28,7 +28,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 05 Nov 2021 18:13:00 GMT + - Mon, 08 Nov 2021 23:57:24 GMT expires: - '-1' pragma: @@ -70,7 +70,7 @@ interactions: uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/create?api-version=7.3-preview response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/132d1171d7e743a8a027ca63e584ec9c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rwGFPKkCV5Ykc3vQU4uNcnsnxQEp-WSPNXkQj_yJ3yR7Bic_3TH7zOThfa_SdPVcgzRio2i2gz9GL5eX5LQInnMTWIz5z4XH2kxtSljI_PDen8eZaCUAPssVt6Vr2pA46xlwYAdjLLAyLPhl0tYiJofwlJrP_FWX7qHKdwfM-PgDKivdifS36_5yfKZxk1GKSUhbo2q1-YDZhFSqo0U49I0MCxfl7YTzr0d0HjlZXa2k8JxijoRg8RNomddKuEEXyT0WOjGOeIYCvwtIf9izIFFoe4lk_2d9ePHJk44l5kAc68QHqHc_HvEDR8HINGHuzsPPZnEJmmmC13paV9AHqQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636135980,"updated":1636135980,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/fae15351fde54b7384e4145ab181b03f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yP2jt3YHJ3Kc46lv-pZtzI0iZZkbsXVwmO-GAe3kBlPeyzbFI6oT7KeUuwtVW0410mOdOUGCGWVk_BoXW09s49ScUPz0JPA8Hyc64y3MgW2u8frHWLq6EJsB7c4Sjz0oK2HwdNqLgRsmQ4DdzoXp5os2NQ0qt_hB39VNR1RL9J_25xevw7VUGiQfOcEYwTKYYnhLoxK_j74oRbd42-Ecck8riN76kL--Sp-3bqNX2fx3HzQVr8Vo4_wL84Stj64gAAJZ3q4J53qNeUfx7e99qJvBFBICTWiQq5b3y0wkcAxk6Bhq_2MB_WnZReaZ3pCm-XhJJU9MqwQ1Phkgb8bv8Q","e":"AQAB"},"attributes":{"enabled":true,"created":1636415845,"updated":1636415845,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' headers: cache-control: - no-cache @@ -79,7 +79,95 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 05 Nov 2021 18:13:00 GMT + - Mon, 08 Nov 2021 23:57:25 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/?api-version=7.3-preview + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/fae15351fde54b7384e4145ab181b03f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yP2jt3YHJ3Kc46lv-pZtzI0iZZkbsXVwmO-GAe3kBlPeyzbFI6oT7KeUuwtVW0410mOdOUGCGWVk_BoXW09s49ScUPz0JPA8Hyc64y3MgW2u8frHWLq6EJsB7c4Sjz0oK2HwdNqLgRsmQ4DdzoXp5os2NQ0qt_hB39VNR1RL9J_25xevw7VUGiQfOcEYwTKYYnhLoxK_j74oRbd42-Ecck8riN76kL--Sp-3bqNX2fx3HzQVr8Vo4_wL84Stj64gAAJZ3q4J53qNeUfx7e99qJvBFBICTWiQq5b3y0wkcAxk6Bhq_2MB_WnZReaZ3pCm-XhJJU9MqwQ1Phkgb8bv8Q","e":"AQAB"},"attributes":{"enabled":true,"created":1636415845,"updated":1636415845,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: + - no-cache + content-length: + - '701' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 08 Nov 2021 23:57:25 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.9.150.1 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/?api-version=7.3-preview + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-keydbee1c29/fae15351fde54b7384e4145ab181b03f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yP2jt3YHJ3Kc46lv-pZtzI0iZZkbsXVwmO-GAe3kBlPeyzbFI6oT7KeUuwtVW0410mOdOUGCGWVk_BoXW09s49ScUPz0JPA8Hyc64y3MgW2u8frHWLq6EJsB7c4Sjz0oK2HwdNqLgRsmQ4DdzoXp5os2NQ0qt_hB39VNR1RL9J_25xevw7VUGiQfOcEYwTKYYnhLoxK_j74oRbd42-Ecck8riN76kL--Sp-3bqNX2fx3HzQVr8Vo4_wL84Stj64gAAJZ3q4J53qNeUfx7e99qJvBFBICTWiQq5b3y0wkcAxk6Bhq_2MB_WnZReaZ3pCm-XhJJU9MqwQ1Phkgb8bv8Q","e":"AQAB"},"attributes":{"enabled":true,"created":1636415845,"updated":1636415845,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: + - no-cache + content-length: + - '701' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 08 Nov 2021 23:57:25 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml index 19f2e41b89e4..356c41e3422f 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_mhsm.yaml @@ -45,7 +45,7 @@ interactions: uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview response: body: - string: '{"attributes":{"created":1636135990,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636135990},"key":{"e":"AQAB","key_ops":["wrapKey","sign","verify","encrypt","decrypt","unwrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/ea983de8a9f60784bc218cea1026c919","kty":"RSA-HSM","n":"iYldKtBTOfO9M2dqQrQr_LvAFOba3-0LR4PtiunRHMyDjC2oZJ276-CEwpGTrl2y3j9Mn4KjrnY6gkAuruhS40NQ-FXN6UUaTjKYOfoDR8kZauXoKG-7qmqNhucACm2gPfXkoz3ngQAiACvHScicw8KfUZEx-r91Ck9wg2omHhU3zp3bPCTp963fj6vv8u7mdKlbkt9RVT3tyu7GnG0lZa0X_kpIPyXAEuFgzRVTeItgah0MwxgB9SlmxASgVur0JadUqkPfGR8mrNRGrRxTw_STyCpjnw356UaKgnxTABahZqugYRNfYfaMDglbaAVEJl-cWrlPXHHYLlYC7NzNBQ"}}' + string: '{"attributes":{"created":1636416230,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636416230},"key":{"e":"AQAB","key_ops":["wrapKey","sign","verify","encrypt","decrypt","unwrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/b9455b5cd9a34d3b2c3a637e61a550f9","kty":"RSA-HSM","n":"r5y21Ndv_xZgYFpqLHejepFyjnoBIQQ8usaUK0d4K6YyEYVRb2MbokiZqvKoOQ3jcvpOfoFlikEYi207ykpi8ukRjiY-vD_MjjkN_iJRASZZ7Z_jP5NsqGanHLIwQGA5orTH_rRxNeGBmRx6lCxnUT2CKKOOTDEorQK9H1FhaWpekKhdfJDUOVKRy-SER46fRDd7lWwqAQ5uUJ9nP0JM1vF8zeaYXlU7b7TmH4dQsTFeEY9TcRJPCGEnkadHk17l6xHK17DTg9YrVN_T6TcALQLdevVb5wefIF6yHin_1_hwjcTG6WxDorZzP-XVlHplGAXoW_KvJZ_akW-chShkXQ"}}' headers: cache-control: no-cache content-length: '741' @@ -56,9 +56,65 @@ interactions: x-frame-options: SAMEORIGIN x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; x-ms-keyvault-region: westus - x-ms-server-latency: '271' + x-ms-server-latency: '376' status: code: 200 message: OK url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/create?api-version=7.3-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/?api-version=7.3-preview + response: + body: + string: '{"attributes":{"created":1636416230,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636416230},"key":{"e":"AQAB","key_ops":["unwrapKey","decrypt","encrypt","verify","sign","wrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/b9455b5cd9a34d3b2c3a637e61a550f9","kty":"RSA-HSM","n":"r5y21Ndv_xZgYFpqLHejepFyjnoBIQQ8usaUK0d4K6YyEYVRb2MbokiZqvKoOQ3jcvpOfoFlikEYi207ykpi8ukRjiY-vD_MjjkN_iJRASZZ7Z_jP5NsqGanHLIwQGA5orTH_rRxNeGBmRx6lCxnUT2CKKOOTDEorQK9H1FhaWpekKhdfJDUOVKRy-SER46fRDd7lWwqAQ5uUJ9nP0JM1vF8zeaYXlU7b7TmH4dQsTFeEY9TcRJPCGEnkadHk17l6xHK17DTg9YrVN_T6TcALQLdevVb5wefIF6yHin_1_hwjcTG6WxDorZzP-XVlHplGAXoW_KvJZ_akW-chShkXQ"}}' + headers: + cache-control: no-cache + content-length: '741' + content-security-policy: default-src 'self' + content-type: application/json; charset=utf-8 + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + x-ms-build-version: 1.0.20210929-1-5b78c022-develop + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: westus + x-ms-server-latency: '92' + status: + code: 200 + message: OK + url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/?api-version=7.3-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/?api-version=7.3-preview + response: + body: + string: '{"attributes":{"created":1636416230,"enabled":true,"exportable":false,"recoverableDays":7,"recoveryLevel":"CustomizedRecoverable+Purgeable","updated":1636416230},"key":{"e":"AQAB","key_ops":["unwrapKey","decrypt","encrypt","verify","sign","wrapKey"],"kid":"https://managedhsmname.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/b9455b5cd9a34d3b2c3a637e61a550f9","kty":"RSA-HSM","n":"r5y21Ndv_xZgYFpqLHejepFyjnoBIQQ8usaUK0d4K6YyEYVRb2MbokiZqvKoOQ3jcvpOfoFlikEYi207ykpi8ukRjiY-vD_MjjkN_iJRASZZ7Z_jP5NsqGanHLIwQGA5orTH_rRxNeGBmRx6lCxnUT2CKKOOTDEorQK9H1FhaWpekKhdfJDUOVKRy-SER46fRDd7lWwqAQ5uUJ9nP0JM1vF8zeaYXlU7b7TmH4dQsTFeEY9TcRJPCGEnkadHk17l6xHK17DTg9YrVN_T6TcALQLdevVb5wefIF6yHin_1_hwjcTG6WxDorZzP-XVlHplGAXoW_KvJZ_akW-chShkXQ"}}' + headers: + cache-control: no-cache + content-length: '741' + content-security-policy: default-src 'self' + content-type: application/json; charset=utf-8 + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + x-ms-build-version: 1.0.20210929-1-5b78c022-develop + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=Ipv4; + x-ms-keyvault-region: westus + x-ms-server-latency: '0' + status: + code: 200 + message: OK + url: https://mcpatinotesthsm.managedhsm.azure.net/keys/livekvtestmultitenant-key713c1e2f/?api-version=7.3-preview version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml index b39a3ac1ae00..840be1178954 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_challenge_auth_async.test_multitenant_authentication_7_3_preview_vault.yaml @@ -20,7 +20,7 @@ interactions: cache-control: no-cache content-length: '97' content-type: application/json; charset=utf-8 - date: Fri, 05 Nov 2021 18:13:12 GMT + date: Tue, 09 Nov 2021 00:03:53 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -50,12 +50,12 @@ interactions: uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview response: body: - string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/63e1215f659f423b9e2f74b1f645802b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"5ibXDBzD5GIADx1Te_WJaLx2LQppykGOCC5Ici8SsLtAtmvRlCqhrzhCqlT1AQDp142JOv53j8iaBP_OWJ2QuUcn-1J_OwTW-bRroUdsr0UdDoSpKb7SgL-4O9ZWUMQZQCVVbUpqYhbVBE_faPskABG96XgwmMI6pdOTIkRAtu0eIgd5BhYre_O0eLGWImjNf8ICitAHC80xZ-sDF3Ckj5P3WU8T-nA64xzix62bw5WNBLD3LqRol1un9bxClEJyEwtPTwrk-SUrIhebdErzPafDRQlqVTIS1SDfvSSvViSC6EDuLhXMT6HDJOrcB9tI1hZRW0KvFeMJMyNRpnUzEQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636135993,"updated":1636135993,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/f875421ae9684ee89ffa78d33c4b917c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"uh7LV8qFwSDSazU05d4aArOVG7HK72oX8Qvdlq-5FJwPRbbCTU9ZX7BJcI7dnhvQHHpYnvZ54aHaNX4nrqYRJRKv9fgte_ZyGaaBqnUVd_sjf3f8US7O6ayPzhzV-F8x2ML12yeRuXbsquxG05D3_e7OtAm5aSwqHxPoIYKIjh4hI3FtpryM7ECA3op1M9QH4e-_3CGCSbLEomih0_FZ-WkebiHNryYJi2l02p-b-CNAEYlcRpRlZUm8Gt6ZtKJ-YvaT1uvPw83DSW6Q_LDjhojmswJBUYCtWszDjBbcUsi5dKD0Zo7--ANcC-66H-CnTM5cF5RRygj1N5l2DU8xyQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636416233,"updated":1636416233,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' headers: cache-control: no-cache content-length: '701' content-type: application/json; charset=utf-8 - date: Fri, 05 Nov 2021 18:13:12 GMT + date: Tue, 09 Nov 2021 00:03:53 GMT expires: '-1' pragma: no-cache strict-transport-security: max-age=31536000;includeSubDomains @@ -68,4 +68,62 @@ interactions: code: 200 message: OK url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/create?api-version=7.3-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/?api-version=7.3-preview + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/f875421ae9684ee89ffa78d33c4b917c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"uh7LV8qFwSDSazU05d4aArOVG7HK72oX8Qvdlq-5FJwPRbbCTU9ZX7BJcI7dnhvQHHpYnvZ54aHaNX4nrqYRJRKv9fgte_ZyGaaBqnUVd_sjf3f8US7O6ayPzhzV-F8x2ML12yeRuXbsquxG05D3_e7OtAm5aSwqHxPoIYKIjh4hI3FtpryM7ECA3op1M9QH4e-_3CGCSbLEomih0_FZ-WkebiHNryYJi2l02p-b-CNAEYlcRpRlZUm8Gt6ZtKJ-YvaT1uvPw83DSW6Q_LDjhojmswJBUYCtWszDjBbcUsi5dKD0Zo7--ANcC-66H-CnTM5cF5RRygj1N5l2DU8xyQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636416233,"updated":1636416233,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: no-cache + content-length: '701' + content-type: application/json; charset=utf-8 + date: Tue, 09 Nov 2021 00:03:54 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 200 + message: OK + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/?api-version=7.3-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-keys/4.5.0b5 Python/3.10.0 (Windows-10-10.0.22000-SP0) + method: GET + uri: https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/?api-version=7.3-preview + response: + body: + string: '{"key":{"kid":"https://vaultname.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/f875421ae9684ee89ffa78d33c4b917c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"uh7LV8qFwSDSazU05d4aArOVG7HK72oX8Qvdlq-5FJwPRbbCTU9ZX7BJcI7dnhvQHHpYnvZ54aHaNX4nrqYRJRKv9fgte_ZyGaaBqnUVd_sjf3f8US7O6ayPzhzV-F8x2ML12yeRuXbsquxG05D3_e7OtAm5aSwqHxPoIYKIjh4hI3FtpryM7ECA3op1M9QH4e-_3CGCSbLEomih0_FZ-WkebiHNryYJi2l02p-b-CNAEYlcRpRlZUm8Gt6ZtKJ-YvaT1uvPw83DSW6Q_LDjhojmswJBUYCtWszDjBbcUsi5dKD0Zo7--ANcC-66H-CnTM5cF5RRygj1N5l2DU8xyQ","e":"AQAB"},"attributes":{"enabled":true,"created":1636416233,"updated":1636416233,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}' + headers: + cache-control: no-cache + content-length: '701' + content-type: application/json; charset=utf-8 + date: Tue, 09 Nov 2021 00:03:54 GMT + expires: '-1' + pragma: no-cache + strict-transport-security: max-age=31536000;includeSubDomains + x-content-type-options: nosniff + x-ms-keyvault-network-info: conn_type=Ipv4;addr=172.92.159.124;act_addr_fam=InterNetwork; + x-ms-keyvault-region: westus + x-ms-keyvault-service-version: 1.9.150.1 + x-powered-by: ASP.NET + status: + code: 200 + message: OK + url: https://mcpatinotest.vault.azure.net/keys/livekvtestmultitenant-key8ff41ea6/?api-version=7.3-preview version: 1 diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py index c06f63190150..955141d98943 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py @@ -63,6 +63,12 @@ def test_multitenant_authentication(self, client, is_hsm, **kwargs): key = client.create_rsa_key(key_name) assert key.id + # try making another request with the credential's token revoked + # the challenge policy should correctly request a new token for the correct tenant when a challenge is cached + client._client._config.authentication_policy._token = None + fetched_key = client.get_key(key_name) + assert key.id == fetched_key.id + def empty_challenge_cache(fn): @functools.wraps(fn) diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py index 971c5762ca24..0ef669809548 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py @@ -61,6 +61,12 @@ async def test_multitenant_authentication(self, client, is_hsm, **kwargs): key = await client.create_rsa_key(key_name) assert key.id + # try making another request with the credential's token revoked + # the challenge policy should correctly request a new token for the correct tenant when a challenge is cached + client._client._config.authentication_policy._token = None + fetched_key = await client.get_key(key_name) + assert key.id == fetched_key.id + @pytest.mark.asyncio @empty_challenge_cache From 0c583c50d90498527bf76322a63799780d8a73fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Mon, 8 Nov 2021 16:12:16 -0800 Subject: [PATCH 11/15] Changes in all packages --- .../_internal/async_challenge_auth_policy.py | 48 +++++++++++------- .../_internal/challenge_auth_policy.py | 50 ++++++++++++------- .../_internal/http_challenge.py | 3 +- .../_shared/async_challenge_auth_policy.py | 48 +++++++++++------- .../_shared/challenge_auth_policy.py | 50 ++++++++++++------- .../certificates/_shared/http_challenge.py | 3 +- .../_shared/async_challenge_auth_policy.py | 48 +++++++++++------- .../secrets/_shared/challenge_auth_policy.py | 50 ++++++++++++------- .../secrets/_shared/http_challenge.py | 3 +- 9 files changed, 195 insertions(+), 108 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py index 9c831f25f85b..57a762dabd43 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py @@ -21,35 +21,26 @@ from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineRequest, PipelineResponse + from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args: "Any", **kwargs: "Any") -> None: - self._last_tenant_id = None # type: Optional[str] - super().__init__(*args, **kwargs) - async def on_request(self, request: "PipelineRequest") -> None: + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - await super().on_request(request) - else: - # acquire a new token because this vault is in a different tenant - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + return await self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) @@ -67,8 +58,31 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py index 8dcfffba67a3..66941051f4a9 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py @@ -27,7 +27,6 @@ TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineResponse @@ -55,30 +54,20 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args, **kwargs): - # type: (*Any, **Any) -> None - self._last_tenant_id = None # type: Optional[str] - super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) - def on_request(self, request): # type: (PipelineRequest) -> None + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - super(ChallengeAuthPolicy, self).on_request(request) - else: - # acquire a new token because this vault is in a different tenant - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + self._handle_challenge(request, challenge) + response = self.next.send(request) + return self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) @@ -96,8 +85,33 @@ def on_challenge(self, request, response): body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + def _handle_challenge(self, request, challenge): + # type: (PipelineRequest, HttpChallenge) -> None + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + def _handle_response(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> PipelineResponse + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py index 46e668a59867..c52c90929ad9 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/http_challenge.py @@ -42,7 +42,8 @@ def __init__(self, request_uri, challenge, response_headers=None): authorization_uri = self.get_authorization_server() # the authoritzation server URI should look something like https://login.windows.net/tenant-id - self.tenant_id = authorization_uri.split("/")[-1] or None + uri_path = parse.urlparse(authorization_uri).path.lstrip("/") + self.tenant_id = uri_path.split("/")[0] or None # if the response headers were supplied if response_headers: diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index 9c831f25f85b..57a762dabd43 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -21,35 +21,26 @@ from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineRequest, PipelineResponse + from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args: "Any", **kwargs: "Any") -> None: - self._last_tenant_id = None # type: Optional[str] - super().__init__(*args, **kwargs) - async def on_request(self, request: "PipelineRequest") -> None: + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - await super().on_request(request) - else: - # acquire a new token because this vault is in a different tenant - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + return await self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) @@ -67,8 +58,31 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index 8dcfffba67a3..66941051f4a9 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -27,7 +27,6 @@ TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineResponse @@ -55,30 +54,20 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args, **kwargs): - # type: (*Any, **Any) -> None - self._last_tenant_id = None # type: Optional[str] - super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) - def on_request(self, request): # type: (PipelineRequest) -> None + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - super(ChallengeAuthPolicy, self).on_request(request) - else: - # acquire a new token because this vault is in a different tenant - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + self._handle_challenge(request, challenge) + response = self.next.send(request) + return self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) @@ -96,8 +85,33 @@ def on_challenge(self, request, response): body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + def _handle_challenge(self, request, challenge): + # type: (PipelineRequest, HttpChallenge) -> None + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + def _handle_response(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> PipelineResponse + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py index 46e668a59867..c52c90929ad9 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/http_challenge.py @@ -42,7 +42,8 @@ def __init__(self, request_uri, challenge, response_headers=None): authorization_uri = self.get_authorization_server() # the authoritzation server URI should look something like https://login.windows.net/tenant-id - self.tenant_id = authorization_uri.split("/")[-1] or None + uri_path = parse.urlparse(authorization_uri).path.lstrip("/") + self.tenant_id = uri_path.split("/")[0] or None # if the response headers were supplied if response_headers: diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index 9c831f25f85b..57a762dabd43 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -21,35 +21,26 @@ from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineRequest, PipelineResponse + from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args: "Any", **kwargs: "Any") -> None: - self._last_tenant_id = None # type: Optional[str] - super().__init__(*args, **kwargs) - async def on_request(self, request: "PipelineRequest") -> None: + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - await super().on_request(request) - else: - # acquire a new token because this vault is in a different tenant - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + return await self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) @@ -67,8 +58,31 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index 8dcfffba67a3..66941051f4a9 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -27,7 +27,6 @@ TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Any, Optional from azure.core.pipeline import PipelineResponse @@ -55,30 +54,20 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" - def __init__(self, *args, **kwargs): - # type: (*Any, **Any) -> None - self._last_tenant_id = None # type: Optional[str] - super(ChallengeAuthPolicy, self).__init__(*args, **kwargs) - def on_request(self, request): # type: (PipelineRequest) -> None + _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: - if self._last_tenant_id == challenge.tenant_id: - # Super can handle this. Its cached token, if any, probably isn't from a different tenant, and - # it knows the scope to request for a new token. Note that if the vault has moved to a new - # tenant since our last request for it, this request will fail. - super(ChallengeAuthPolicy, self).on_request(request) - else: - # acquire a new token because this vault is in a different tenant - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) - return + # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. + self._handle_challenge(request, challenge) + response = self.next.send(request) + return self._handle_response(request, response) # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. # on_challenge will parse that challenge, reattach any body removed here, authorize the request, and tell # super to send it again. - _enforce_tls(request) if request.http_request.body: request.context["key_vault_request_data"] = request.http_request.body request.http_request.set_json_body(None) @@ -96,8 +85,33 @@ def on_challenge(self, request, response): body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - - self._last_tenant_id = challenge.tenant_id self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) return True + + def _handle_challenge(self, request, challenge): + # type: (PipelineRequest, HttpChallenge) -> None + """Authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) + + def _handle_response(self, request, response): + # type: (PipelineRequest, PipelineResponse) -> PipelineResponse + """Return a response and attempt to handle any authentication challenges""" + + if response.http_response.status_code == 401: + # any cached token must be invalid + self._token = None + + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = _update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + return response diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py index 46e668a59867..c52c90929ad9 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py @@ -42,7 +42,8 @@ def __init__(self, request_uri, challenge, response_headers=None): authorization_uri = self.get_authorization_server() # the authoritzation server URI should look something like https://login.windows.net/tenant-id - self.tenant_id = authorization_uri.split("/")[-1] or None + uri_path = parse.urlparse(authorization_uri).path.lstrip("/") + self.tenant_id = uri_path.split("/")[0] or None # if the response headers were supplied if response_headers: From c323d134d8ad04a68782e264c260630c5891d90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Mon, 8 Nov 2021 18:05:37 -0800 Subject: [PATCH 12/15] mypy/pylint --- .../administration/_internal/async_challenge_auth_policy.py | 3 ++- .../keyvault/administration/_internal/challenge_auth_policy.py | 3 ++- .../certificates/_shared/async_challenge_auth_policy.py | 3 ++- .../keyvault/certificates/_shared/challenge_auth_policy.py | 3 ++- .../azure/keyvault/keys/_shared/async_challenge_auth_policy.py | 3 ++- .../azure/keyvault/keys/_shared/challenge_auth_policy.py | 3 ++- .../keyvault/secrets/_shared/async_challenge_auth_policy.py | 3 ++- .../azure/keyvault/secrets/_shared/challenge_auth_policy.py | 3 ++- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py index 57a762dabd43..b7535c7ec516 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py @@ -35,7 +35,8 @@ async def on_request(self, request: "PipelineRequest") -> None: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) response = await self.next.send(request) - return await self._handle_response(request, response) + await self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py index 66941051f4a9..4633ce17a0ff 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py @@ -62,7 +62,8 @@ def on_request(self, request): # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) response = self.next.send(request) - return self._handle_response(request, response) + self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index 57a762dabd43..b7535c7ec516 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -35,7 +35,8 @@ async def on_request(self, request: "PipelineRequest") -> None: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) response = await self.next.send(request) - return await self._handle_response(request, response) + await self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index 66941051f4a9..4633ce17a0ff 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -62,7 +62,8 @@ def on_request(self, request): # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) response = self.next.send(request) - return self._handle_response(request, response) + self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index 57a762dabd43..b7535c7ec516 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -35,7 +35,8 @@ async def on_request(self, request: "PipelineRequest") -> None: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) response = await self.next.send(request) - return await self._handle_response(request, response) + await self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 66941051f4a9..4633ce17a0ff 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -62,7 +62,8 @@ def on_request(self, request): # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) response = self.next.send(request) - return self._handle_response(request, response) + self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index 57a762dabd43..b7535c7ec516 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -35,7 +35,8 @@ async def on_request(self, request: "PipelineRequest") -> None: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) response = await self.next.send(request) - return await self._handle_response(request, response) + await self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index 66941051f4a9..4633ce17a0ff 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -62,7 +62,8 @@ def on_request(self, request): # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) response = self.next.send(request) - return self._handle_response(request, response) + self._handle_response(request, response) + return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, # saving it for later. Key Vault will reject the request as unauthorized and respond with a challenge. From e7be78fa57458da4d3414cdee281d2c527a4bd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Tue, 9 Nov 2021 11:48:32 -0800 Subject: [PATCH 13/15] Remove extra response handling --- .../_internal/async_challenge_auth_policy.py | 28 ++++-------------- .../_internal/challenge_auth_policy.py | 29 ++++--------------- .../_shared/async_challenge_auth_policy.py | 28 ++++-------------- .../_shared/challenge_auth_policy.py | 29 ++++--------------- .../_shared/async_challenge_auth_policy.py | 28 ++++-------------- .../keys/_shared/challenge_auth_policy.py | 29 ++++--------------- .../_shared/async_challenge_auth_policy.py | 28 ++++-------------- .../secrets/_shared/challenge_auth_policy.py | 29 ++++--------------- 8 files changed, 48 insertions(+), 180 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py index b7535c7ec516..1c1f6cd35f78 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py @@ -34,8 +34,6 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) - response = await self.next.send(request) - await self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -66,24 +64,10 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + if self._need_new_token(): + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py index 4633ce17a0ff..56391028b9b9 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py @@ -61,8 +61,6 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) - response = self.next.send(request) - self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -94,25 +92,10 @@ def _handle_challenge(self, request, challenge): # type: (PipelineRequest, HttpChallenge) -> None """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - def _handle_response(self, request, response): - # type: (PipelineRequest, PipelineResponse) -> PipelineResponse - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) + if self._need_new_token: + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index b7535c7ec516..1c1f6cd35f78 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -34,8 +34,6 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) - response = await self.next.send(request) - await self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -66,24 +64,10 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + if self._need_new_token(): + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index 4633ce17a0ff..56391028b9b9 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -61,8 +61,6 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) - response = self.next.send(request) - self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -94,25 +92,10 @@ def _handle_challenge(self, request, challenge): # type: (PipelineRequest, HttpChallenge) -> None """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - def _handle_response(self, request, response): - # type: (PipelineRequest, PipelineResponse) -> PipelineResponse - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) + if self._need_new_token: + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index b7535c7ec516..1c1f6cd35f78 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -34,8 +34,6 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) - response = await self.next.send(request) - await self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -66,24 +64,10 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + if self._need_new_token(): + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 4633ce17a0ff..56391028b9b9 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -61,8 +61,6 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) - response = self.next.send(request) - self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -94,25 +92,10 @@ def _handle_challenge(self, request, challenge): # type: (PipelineRequest, HttpChallenge) -> None """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - def _handle_response(self, request, response): - # type: (PipelineRequest, PipelineResponse) -> PipelineResponse - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) + if self._need_new_token: + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index b7535c7ec516..1c1f6cd35f78 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -34,8 +34,6 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. await self._handle_challenge(request, challenge) - response = await self.next.send(request) - await self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -66,24 +64,10 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - async def _handle_response(self, request: "PipelineRequest", response: "PipelineResponse") -> "PipelineResponse": - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - await self._handle_challenge(request, challenge) - response = await self.next.send(request) + if self._need_new_token(): + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index 4633ce17a0ff..56391028b9b9 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -61,8 +61,6 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. self._handle_challenge(request, challenge) - response = self.next.send(request) - self._handle_response(request, response) return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -94,25 +92,10 @@ def _handle_challenge(self, request, challenge): # type: (PipelineRequest, HttpChallenge) -> None """Authenticate according to challenge, add Authorization header to request""" - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self.authorize_request(request, scope, tenant_id=challenge.tenant_id) - - def _handle_response(self, request, response): - # type: (PipelineRequest, PipelineResponse) -> PipelineResponse - """Return a response and attempt to handle any authentication challenges""" - - if response.http_response.status_code == 401: - # any cached token must be invalid - self._token = None - - # cached challenge could be outdated; maybe this response has a new one? - try: - challenge = _update_challenge(request, response) - except ValueError: - # 401 with no legible challenge -> nothing more this policy can do - return response - - self._handle_challenge(request, challenge) - response = self.next.send(request) + if self._need_new_token: + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - return response + # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore From d47cb3f3617dec1196322ff95a807447634c5d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Tue, 9 Nov 2021 15:21:01 -0800 Subject: [PATCH 14/15] readme; cleaner parent class division --- .../CHANGELOG.md | 3 +- .../_internal/async_challenge_auth_policy.py | 40 ++++++++++------- .../_internal/challenge_auth_policy.py | 44 ++++++++++++------- .../_shared/async_challenge_auth_policy.py | 40 ++++++++++------- .../_shared/challenge_auth_policy.py | 44 ++++++++++++------- .../_shared/async_challenge_auth_policy.py | 40 ++++++++++------- .../keys/_shared/challenge_auth_policy.py | 44 ++++++++++++------- .../_shared/async_challenge_auth_policy.py | 40 ++++++++++------- .../secrets/_shared/challenge_auth_policy.py | 44 ++++++++++++------- 9 files changed, 205 insertions(+), 134 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md b/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md index 24334a26b26c..2b87d2e07414 100644 --- a/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-administration/CHANGELOG.md @@ -3,8 +3,7 @@ ## 4.1.0b2 (Unreleased) ### Features Added -- Added support for multi-tenant authentication against Managed HSM when using - `azure-identity` 1.7.1 or newer +- Added support for multi-tenant authentication when using `azure-identity` 1.7.1 or newer ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py index 1c1f6cd35f78..3bc6c9b2bfd6 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py @@ -13,27 +13,41 @@ requirements can change. For example, a vault may move to a new tenant. In such a case the policy will attempt the protocol again. """ + +import time from typing import TYPE_CHECKING from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _update_challenge +from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken + from azure.core.credentials_async import AsyncTokenCredential from azure.core.pipeline import PipelineRequest, PipelineResponse - from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential: "AsyncTokenCredential", *scopes: str, **kwargs: "Any") -> None: + super().__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + async def on_request(self, request: "PipelineRequest") -> None: _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - await self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -49,25 +63,17 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token(): - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self) -> bool: + # pylint:disable=invalid-overridden-method + return not self._token or self._token.expires_on - time.time() < 300 diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py index 56391028b9b9..a51ff8d6aa95 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py @@ -14,6 +14,8 @@ protocol again. """ +import time + from azure.core.exceptions import ServiceRequestError from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import BearerTokenCredentialPolicy @@ -27,6 +29,8 @@ TYPE_CHECKING = False if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -38,6 +42,12 @@ def _enforce_tls(request): ) +def _get_challenge_scope(challenge): + # type: (HttpChallenge) -> str + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + return challenge.get_scope() or challenge.get_resource() + "/.default" + + def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -54,13 +64,24 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential, *scopes, **kwargs): + # type: (TokenCredential, *str, **Any) -> None + super(ChallengeAuthPolicy, self).__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + def on_request(self, request): # type: (PipelineRequest) -> None _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -76,26 +97,17 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self): + # type: () -> bool + return not self._token or self._token.expires_on - time.time() < 300 diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index 1c1f6cd35f78..3bc6c9b2bfd6 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -13,27 +13,41 @@ requirements can change. For example, a vault may move to a new tenant. In such a case the policy will attempt the protocol again. """ + +import time from typing import TYPE_CHECKING from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _update_challenge +from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken + from azure.core.credentials_async import AsyncTokenCredential from azure.core.pipeline import PipelineRequest, PipelineResponse - from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential: "AsyncTokenCredential", *scopes: str, **kwargs: "Any") -> None: + super().__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + async def on_request(self, request: "PipelineRequest") -> None: _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - await self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -49,25 +63,17 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token(): - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self) -> bool: + # pylint:disable=invalid-overridden-method + return not self._token or self._token.expires_on - time.time() < 300 diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index 56391028b9b9..a51ff8d6aa95 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -14,6 +14,8 @@ protocol again. """ +import time + from azure.core.exceptions import ServiceRequestError from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import BearerTokenCredentialPolicy @@ -27,6 +29,8 @@ TYPE_CHECKING = False if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -38,6 +42,12 @@ def _enforce_tls(request): ) +def _get_challenge_scope(challenge): + # type: (HttpChallenge) -> str + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + return challenge.get_scope() or challenge.get_resource() + "/.default" + + def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -54,13 +64,24 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential, *scopes, **kwargs): + # type: (TokenCredential, *str, **Any) -> None + super(ChallengeAuthPolicy, self).__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + def on_request(self, request): # type: (PipelineRequest) -> None _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -76,26 +97,17 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self): + # type: () -> bool + return not self._token or self._token.expires_on - time.time() < 300 diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index 1c1f6cd35f78..3bc6c9b2bfd6 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -13,27 +13,41 @@ requirements can change. For example, a vault may move to a new tenant. In such a case the policy will attempt the protocol again. """ + +import time from typing import TYPE_CHECKING from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _update_challenge +from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken + from azure.core.credentials_async import AsyncTokenCredential from azure.core.pipeline import PipelineRequest, PipelineResponse - from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential: "AsyncTokenCredential", *scopes: str, **kwargs: "Any") -> None: + super().__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + async def on_request(self, request: "PipelineRequest") -> None: _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - await self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -49,25 +63,17 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token(): - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self) -> bool: + # pylint:disable=invalid-overridden-method + return not self._token or self._token.expires_on - time.time() < 300 diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index 56391028b9b9..a51ff8d6aa95 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -14,6 +14,8 @@ protocol again. """ +import time + from azure.core.exceptions import ServiceRequestError from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import BearerTokenCredentialPolicy @@ -27,6 +29,8 @@ TYPE_CHECKING = False if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -38,6 +42,12 @@ def _enforce_tls(request): ) +def _get_challenge_scope(challenge): + # type: (HttpChallenge) -> str + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + return challenge.get_scope() or challenge.get_resource() + "/.default" + + def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -54,13 +64,24 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential, *scopes, **kwargs): + # type: (TokenCredential, *str, **Any) -> None + super(ChallengeAuthPolicy, self).__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + def on_request(self, request): # type: (PipelineRequest) -> None _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -76,26 +97,17 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self): + # type: () -> bool + return not self._token or self._token.expires_on - time.time() < 300 diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index 1c1f6cd35f78..3bc6c9b2bfd6 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -13,27 +13,41 @@ requirements can change. For example, a vault may move to a new tenant. In such a case the policy will attempt the protocol again. """ + +import time from typing import TYPE_CHECKING from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _update_challenge +from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken + from azure.core.credentials_async import AsyncTokenCredential from azure.core.pipeline import PipelineRequest, PipelineResponse - from .http_challenge import HttpChallenge class AsyncChallengeAuthPolicy(AsyncBearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential: "AsyncTokenCredential", *scopes: str, **kwargs: "Any") -> None: + super().__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + async def on_request(self, request: "PipelineRequest") -> None: _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - await self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -49,25 +63,17 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - await self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + await self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - async def _handle_challenge(self, request: "PipelineRequest", challenge: "HttpChallenge") -> None: - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token(): - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self) -> bool: + # pylint:disable=invalid-overridden-method + return not self._token or self._token.expires_on - time.time() < 300 diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index 56391028b9b9..a51ff8d6aa95 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -14,6 +14,8 @@ protocol again. """ +import time + from azure.core.exceptions import ServiceRequestError from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import BearerTokenCredentialPolicy @@ -27,6 +29,8 @@ TYPE_CHECKING = False if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.credentials import AccessToken, TokenCredential from azure.core.pipeline import PipelineResponse @@ -38,6 +42,12 @@ def _enforce_tls(request): ) +def _get_challenge_scope(challenge): + # type: (HttpChallenge) -> str + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + return challenge.get_scope() or challenge.get_resource() + "/.default" + + def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -54,13 +64,24 @@ def _update_challenge(request, challenger): class ChallengeAuthPolicy(BearerTokenCredentialPolicy): """policy for handling HTTP authentication challenges""" + def __init__(self, credential, *scopes, **kwargs): + # type: (TokenCredential, *str, **Any) -> None + super(ChallengeAuthPolicy, self).__init__(credential, *scopes, **kwargs) + self._credential = credential + self._token = None # type: Optional[AccessToken] + def on_request(self, request): # type: (PipelineRequest) -> None _enforce_tls(request) challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. - self._handle_challenge(request, challenge) + if self._need_new_token: + scope = _get_challenge_scope(challenge) + self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) + + # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token + request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore return # else: discover authentication information by eliciting a challenge from Key Vault. Remove any request data, @@ -76,26 +97,17 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = challenge.get_scope() or challenge.get_resource() + "/.default" + scope = _get_challenge_scope(challenge) except ValueError: return False - self._scopes = (scope,) - body = request.context.pop("key_vault_request_data", None) request.http_request.set_text_body(body) # no-op when text is None - self.authorize_request(request, *self._scopes, tenant_id=challenge.tenant_id) + self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True - def _handle_challenge(self, request, challenge): - # type: (PipelineRequest, HttpChallenge) -> None - """Authenticate according to challenge, add Authorization header to request""" - - if self._need_new_token: - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - scope = challenge.get_scope() or challenge.get_resource() + "/.default" - self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) - - # ignore mypy's warning because although self._token is Optional, get_token raises when it fails to get a token - request.http_request.headers["Authorization"] = "Bearer {}".format(self._token.token) # type: ignore + @property + def _need_new_token(self): + # type: () -> bool + return not self._token or self._token.expires_on - time.time() < 300 From 241de3f553ed03a6577cff0a4da1eb553cc2777e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Wed, 10 Nov 2021 09:12:15 -0800 Subject: [PATCH 15/15] Thanks, Charles! --- .../_internal/async_challenge_auth_policy.py | 8 +++++--- .../_internal/challenge_auth_policy.py | 12 ++++-------- .../azure-keyvault-certificates/CHANGELOG.md | 3 +-- .../_shared/async_challenge_auth_policy.py | 8 +++++--- .../certificates/_shared/challenge_auth_policy.py | 12 ++++-------- sdk/keyvault/azure-keyvault-keys/CHANGELOG.md | 3 +-- .../keys/_shared/async_challenge_auth_policy.py | 8 +++++--- .../keyvault/keys/_shared/challenge_auth_policy.py | 12 ++++-------- sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md | 3 +-- .../secrets/_shared/async_challenge_auth_policy.py | 8 +++++--- .../secrets/_shared/challenge_auth_policy.py | 12 ++++-------- 11 files changed, 39 insertions(+), 50 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py index 3bc6c9b2bfd6..bfe46689f5ff 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/async_challenge_auth_policy.py @@ -20,7 +20,7 @@ from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: from typing import Any, Optional @@ -43,7 +43,8 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -63,7 +64,8 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py index a51ff8d6aa95..7f6f2b93f0e4 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/challenge_auth_policy.py @@ -42,12 +42,6 @@ def _enforce_tls(request): ) -def _get_challenge_scope(challenge): - # type: (HttpChallenge) -> str - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - return challenge.get_scope() or challenge.get_resource() + "/.default" - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -77,7 +71,8 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -97,7 +92,8 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False diff --git a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md index 50230dea41a7..e187f020b952 100644 --- a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md @@ -3,8 +3,7 @@ ## 4.4.0b2 (Unreleased) ### Features Added -- Added support for multi-tenant authentication against Managed HSM when using - `azure-identity` 1.7.1 or newer +- Added support for multi-tenant authentication when using `azure-identity` 1.7.1 or newer ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py index 3bc6c9b2bfd6..bfe46689f5ff 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/async_challenge_auth_policy.py @@ -20,7 +20,7 @@ from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: from typing import Any, Optional @@ -43,7 +43,8 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -63,7 +64,8 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py index a51ff8d6aa95..7f6f2b93f0e4 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/challenge_auth_policy.py @@ -42,12 +42,6 @@ def _enforce_tls(request): ) -def _get_challenge_scope(challenge): - # type: (HttpChallenge) -> str - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - return challenge.get_scope() or challenge.get_resource() + "/.default" - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -77,7 +71,8 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -97,7 +92,8 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False diff --git a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md index 59d7afd4ee18..2320c33e2275 100644 --- a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md @@ -3,8 +3,7 @@ ## 4.5.0b5 (Unreleased) ### Features Added -- Added support for multi-tenant authentication against Key Vault and Managed HSM when using - `azure-identity` 1.7.1 or newer +- Added support for multi-tenant authentication when using `azure-identity` 1.7.1 or newer ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py index 3bc6c9b2bfd6..bfe46689f5ff 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -20,7 +20,7 @@ from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: from typing import Any, Optional @@ -43,7 +43,8 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -63,7 +64,8 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py index a51ff8d6aa95..7f6f2b93f0e4 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -42,12 +42,6 @@ def _enforce_tls(request): ) -def _get_challenge_scope(challenge): - # type: (HttpChallenge) -> str - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - return challenge.get_scope() or challenge.get_resource() + "/.default" - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -77,7 +71,8 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -97,7 +92,8 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False diff --git a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md index 238b984c3a5b..c7858f5e7165 100644 --- a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md @@ -3,8 +3,7 @@ ## 4.4.0b2 (Unreleased) ### Features Added -- Added support for multi-tenant authentication against Managed HSM when using - `azure-identity` 1.7.1 or newer +- Added support for multi-tenant authentication when using `azure-identity` 1.7.1 or newer ([#20698](https://github.com/Azure/azure-sdk-for-python/issues/20698)) ### Breaking Changes diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py index 3bc6c9b2bfd6..bfe46689f5ff 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -20,7 +20,7 @@ from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from . import http_challenge_cache as ChallengeCache -from .challenge_auth_policy import _enforce_tls, _get_challenge_scope, _update_challenge +from .challenge_auth_policy import _enforce_tls, _update_challenge if TYPE_CHECKING: from typing import Any, Optional @@ -43,7 +43,8 @@ async def on_request(self, request: "PipelineRequest") -> None: if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = await self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -63,7 +64,8 @@ async def on_request(self, request: "PipelineRequest") -> None: async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool: try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py index a51ff8d6aa95..7f6f2b93f0e4 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -42,12 +42,6 @@ def _enforce_tls(request): ) -def _get_challenge_scope(challenge): - # type: (HttpChallenge) -> str - # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource - return challenge.get_scope() or challenge.get_resource() + "/.default" - - def _update_challenge(request, challenger): # type: (PipelineRequest, PipelineResponse) -> HttpChallenge """parse challenge from challenger, cache it, return it""" @@ -77,7 +71,8 @@ def on_request(self, request): if challenge: # Note that if the vault has moved to a new tenant since our last request for it, this request will fail. if self._need_new_token: - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" self._token = self._credential.get_token(scope, tenant_id=challenge.tenant_id) # ignore mypy's warning -- although self._token is Optional, get_token raises when it fails to get a token @@ -97,7 +92,8 @@ def on_challenge(self, request, response): # type: (PipelineRequest, PipelineResponse) -> bool try: challenge = _update_challenge(request, response) - scope = _get_challenge_scope(challenge) + # azure-identity credentials require an AADv2 scope but the challenge may specify an AADv1 resource + scope = challenge.get_scope() or challenge.get_resource() + "/.default" except ValueError: return False