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.
    """

    url = get_random_url()

    token = "**"

    async def get_token(*_, **__):
        return AccessToken(token, 0)

    credential = Mock(get_token=Mock(wraps=get_token))

    transport = async_validating_transport(
        requests=[Request()] * 2 + [Request(required_headers={"Authorization": "Bearer " + token})],
        responses=[
            mock_response(
                status_code=401, headers={"WWW-Authenticate": 'Bearer authorization="{}", resource=foo'.format(url)}
            )
        ]
        + [mock_response()] * 2,
    )
    challenge_policy = AsyncChallengeAuthPolicy(credential=credential)
    policies = get_policies_for_request_mutation_test(challenge_policy)
    pipeline = AsyncPipeline(policies=policies, transport=transport)

    response = 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"
def test_preserves_options_and_headers():
    """After a challenge, the policy should send the original request with its options and headers preserved"""

    url = get_random_url()
    token = "**"

    def get_token(*_, **__):
        return AccessToken(token, 0)

    credential = Mock(get_token=Mock(wraps=get_token))

    transport = validating_transport(
        requests=[Request()] * 2 +
        [Request(required_headers={"Authorization": "Bearer " + token})],
        responses=[
            mock_response(
                status_code=401,
                headers={
                    "WWW-Authenticate":
                    'Bearer authorization="{}", resource=foo'.format(url)
                })
        ] + [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 = 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 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))

    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"
async def test_token_expiration():
    """policy should not use a cached token which has expired"""

    url = get_random_url()

    expires_on = time.time() + 3600
    first_token = "*"
    second_token = "**"

    token = AccessToken(first_token, expires_on)

    async def get_token(*_, **__):
        return token

    credential = Mock(get_token=Mock(wraps=get_token))
    transport = async_validating_transport(
        requests=[
            Request(),
            Request(
                required_headers={"Authorization": "Bearer " + first_token}),
            Request(
                required_headers={"Authorization": "Bearer " + first_token}),
            Request(
                required_headers={"Authorization": "Bearer " + second_token}),
        ],
        responses=[
            mock_response(
                status_code=401,
                headers={
                    "WWW-Authenticate":
                    'Bearer authorization="{}", resource=foo'.format(url)
                })
        ] + [mock_response()] * 3,
    )
    pipeline = AsyncPipeline(
        policies=[AsyncChallengeAuthPolicy(credential=credential)],
        transport=transport)

    for _ in range(2):
        await pipeline.run(HttpRequest("GET", url))
        assert credential.get_token.call_count == 1

    token = AccessToken(second_token, time.time() + 3600)
    with patch("time.time", lambda: expires_on):
        await pipeline.run(HttpRequest("GET", url))
    assert credential.get_token.call_count == 2
async def test_policy_updates_cache():
    """
    It's possible for the challenge returned for a request to change, e.g. when a vault is moved to a new tenant.
    When the policy receives a 401, it should update the cached challenge for the requested URL, if one exists.
    """

    url = get_random_url()
    first_scope = "https://first-scope"
    first_token = "first-scope-token"
    second_scope = "https://second-scope"
    second_token = "second-scope-token"
    challenge_fmt = 'Bearer authorization="https://login.authority.net/tenant", resource={}'

    # mocking a tenant change:
    # 1. first request -> respond with challenge
    # 2. second request should be authorized according to the challenge
    # 3. third request should match the second (using a cached access token)
    # 4. fourth request should also match the second -> respond with a new challenge
    # 5. fifth request should be authorized according to the new challenge
    # 6. sixth request should match the fifth
    transport = async_validating_transport(
        requests=(
            Request(url),
            Request(url, required_headers={"Authorization": "Bearer {}".format(first_token)}),
            Request(url, required_headers={"Authorization": "Bearer {}".format(first_token)}),
            Request(url, required_headers={"Authorization": "Bearer {}".format(first_token)}),
            Request(url, required_headers={"Authorization": "Bearer {}".format(second_token)}),
            Request(url, required_headers={"Authorization": "Bearer {}".format(second_token)}),
        ),
        responses=(
            mock_response(status_code=401, headers={"WWW-Authenticate": challenge_fmt.format(first_scope)}),
            mock_response(status_code=200),
            mock_response(status_code=200),
            mock_response(status_code=401, headers={"WWW-Authenticate": challenge_fmt.format(second_scope)}),
            mock_response(status_code=200),
            mock_response(status_code=200),
        ),
    )

    token = AccessToken(first_token, time.time() + 3600)

    async def get_token(*_, **__):
        return 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
    for _ in range(2):
        await pipeline.run(HttpRequest("GET", url))
        assert credential.get_token.call_count == 1

    # The next request will receive a new challenge. The policy should handle it and update caches.
    token = AccessToken(second_token, time.time() + 3600)
    for _ in range(2):
        await pipeline.run(HttpRequest("GET", url))
        assert credential.get_token.call_count == 2
Exemplo n.º 5
0
def test_error_map():
    """error_map should map all error codes to a subclass of HttpResponseError"""

    error_code = "oops"
    error_message = "something went wrong"
    error_body = {
        "error": {
            "code": error_code,
            "message": error_message
        }
    }  # Key Vault error responses look like this

    for status_code in range(400, 600):
        response = mock_response(status_code=status_code,
                                 json_payload=error_body)

        with pytest.raises(HttpResponseError) as ex:
            map_error(status_code, response, error_map)

        # the concrete error should include error information returned by Key Vault
        assert error_code in ex.value.message
        assert error_message in ex.value.message
Exemplo n.º 6
0
async def test_propagates_unexpected_error():
    """The polling method should raise when Key Vault responds with an unexpected error"""

    response = mock_response(
        status_code=418,
        json_payload={"error": {
            "code": 418,
            "message": "I'm a teapot."
        }})
    error = HttpResponseError(response=response)
    command = mock.Mock(side_effect=error)
    polling_method = AsyncDeleteRecoverPollingMethod(command,
                                                     final_resource=None,
                                                     finished=False)

    with mock.patch(
            SLEEP,
            raise_exception(
                "polling method shouldn't sleep after an unexpected error")):
        with pytest.raises(HttpResponseError):
            await polling_method.run()

    assert command.call_count == 1