Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ccd64b9
Add tests
mccoyp Sep 12, 2024
34fcfed
Implement CAE support
mccoyp Sep 12, 2024
d07e0a5
Share implementation across libraries
mccoyp Sep 13, 2024
84351af
Enable CAE; provide claims only in challenges
mccoyp Sep 18, 2024
c22cb08
Update tests for success scenarios
mccoyp Sep 19, 2024
d854071
Handle non-consecutive challenges (in Keys)
mccoyp Sep 19, 2024
6c19bbc
Cover invalid challenge flows
mccoyp Sep 20, 2024
c919056
Handle (in)valid challenge flows
mccoyp Sep 20, 2024
ff731e8
Share updates across libraries
mccoyp Sep 20, 2024
237c57b
Fix spelling, pylint
mccoyp Sep 20, 2024
013673b
Update changelogs
mccoyp Sep 26, 2024
5da13ff
Update tests for feedback
mccoyp Sep 26, 2024
36cb9fd
Use super() instead of private attribute
mccoyp Sep 26, 2024
f9ff176
Add live test; assert scope
mccoyp Sep 26, 2024
bf8f054
Fix auth policy to send scope correctly
mccoyp Sep 26, 2024
850e6e8
Async tests; sync challenge policy code
mccoyp Sep 26, 2024
e78d4e9
Ensure no re-sending claims in tests
mccoyp Oct 3, 2024
1a6c9f7
Fix policy to handle KV -> KV challenge
mccoyp Oct 3, 2024
3fad4db
Share bug fix across libraries
mccoyp Oct 3, 2024
ba2a954
Clarify test variable names
mccoyp Oct 4, 2024
92c6704
Correctly handle token refreshes
mccoyp Oct 5, 2024
8e65726
Bump Core dep for SupportsTokenInfo protocol support
mccoyp Oct 8, 2024
93c7eaa
(Async)SupportsTokenInfo support/tests
mccoyp Oct 8, 2024
bf25378
Pylint
mccoyp Oct 8, 2024
3cb6481
Mention Core bump, enable_cae kwarg in changelogs
mccoyp Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add tests
  • Loading branch information
mccoyp committed Oct 7, 2024
commit ccd64b9df4a6e459a10e988a95949e3ea6aeddc2
55 changes: 54 additions & 1 deletion sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
Tests for the HTTP challenge authentication implementation. These tests aren't parallelizable, because
the challenge cache is global to the process.
"""
import base64
import functools
import os
import time
Expand All @@ -21,7 +22,6 @@
from azure.core.pipeline import Pipeline
from azure.core.pipeline.policies import SansIOHTTPPolicy
from azure.core.rest import HttpRequest
from azure.identity import AzureCliCredential, AzurePowerShellCredential, 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
Expand Down Expand Up @@ -536,3 +536,56 @@ def get_token(*_, **__):
else:
key = client.get_key("key-name")
assert key.name == "key-name"


@empty_challenge_cache
def test_cae():
"""The policy should correctly handle claims in a challenge response"""

expected_content = b"a duck"

def test_with_challenge(challenge, expected_claim):
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("claims") == expected_claim
return AccessToken(expected_token, 0)

credential = Mock(spec_set=["get_token"], 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

url = f'authorization_uri="{get_random_url()}"'
resource = 'resource="https://vault.azure.net"'
cid = 'client_id="00000003-0000-0000-c000-000000000000"'
err = 'error="insufficient_claims"'
claim = '{"access_token": {"foo": "bar"}}'
# Claim token is a string of the base64 encoding of the claim
claim_token = base64.b64encode(claim.encode()).decode()
challenge = f'Bearer realm="", {url}, {resource}, {cid}, {err}, claims="{claim_token}"'

challenge_response = Mock(status_code=401, headers={"WWW-Authenticate": challenge})

test_with_challenge(challenge_response, claim)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
the challenge cache is global to the process.
"""
import asyncio
import base64
import os
import time
from unittest.mock import Mock, patch
Expand All @@ -18,7 +19,6 @@
from azure.core.pipeline import AsyncPipeline
from azure.core.pipeline.policies import SansIOHTTPPolicy
from azure.core.rest import HttpRequest
from azure.identity.aio import AzureCliCredential, AzurePowerShellCredential, ClientSecretCredential
from azure.keyvault.keys._shared import AsyncChallengeAuthPolicy,HttpChallenge, HttpChallengeCache
from azure.keyvault.keys._shared.client_base import DEFAULT_VERSION
from azure.keyvault.keys.aio import KeyClient
Expand Down Expand Up @@ -469,7 +469,6 @@ async def get_token(*_, **__):


@pytest.mark.asyncio
@empty_challenge_cache
@pytest.mark.parametrize("verify_challenge_resource", [True, False])
async def test_verify_challenge_resource_valid(verify_challenge_resource):
"""The auth policy should raise if the challenge resource isn't a valid URL unless check is disabled"""
Expand Down Expand Up @@ -502,3 +501,57 @@ async def get_token(*_, **__):
else:
key = await client.get_key("key-name")
assert key.name == "key-name"


@pytest.mark.asyncio
@empty_challenge_cache
async def test_cae():
"""The policy should correctly handle claims in a challenge response"""

expected_content = b"a duck"

async def test_with_challenge(challenge, expected_claim):
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("claims") == expected_claim
return AccessToken(expected_token, 0)

credential = Mock(spec_set=["get_token"], 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

url = f'authorization_uri="{get_random_url()}"'
resource = 'resource="https://vault.azure.net"'
cid = 'client_id="00000003-0000-0000-c000-000000000000"'
err = 'error="insufficient_claims"'
claim = '{"access_token": {"foo": "bar"}}'
# Claim token is a string of the base64 encoding of the claim
claim_token = base64.b64encode(claim.encode()).decode()
challenge = f'Bearer realm="", {url}, {resource}, {cid}, {err}, claims="{claim_token}"'

challenge_response = Mock(status_code=401, headers={"WWW-Authenticate": challenge})

await test_with_challenge(challenge_response, claim)