Beispiel #1
0
async def test_request_url(authority):
    tenant_id = "expected-tenant"
    parsed_authority = urlparse(authority)
    expected_netloc = parsed_authority.netloc or authority  # "localhost" parses to netloc "", path "localhost"

    def mock_send(request, **kwargs):
        actual = urlparse(request.url)
        assert actual.scheme == "https"
        assert actual.netloc == expected_netloc
        assert actual.path.startswith("/" + tenant_id)
        return mock_response(json_payload={
            "token_type": "Bearer",
            "expires_in": 42,
            "access_token": "*"
        })

    client = AsyncAuthnClient(tenant=tenant_id,
                              transport=Mock(send=wrap_in_future(mock_send)),
                              authority=authority)
    await client.request_token(("scope", ))

    # authority can be configured via environment variable
    with patch.dict("os.environ",
                    {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority},
                    clear=True):
        client = AsyncAuthnClient(
            tenant=tenant_id, transport=Mock(send=wrap_in_future(mock_send)))
        await client.request_token(("scope", ))
Beispiel #2
0
async def test_imds_credential_retries():
    mock_response = Mock(
        text=lambda: b"{}",
        headers={
            "content-type": "application/json",
            "Retry-After": "0"
        },
        content_type="application/json",
    )
    mock_send = Mock(return_value=mock_response)

    total_retries = ImdsCredential._create_config().retry_policy.total_retries

    for status_code in (404, 429, 500):
        mock_send.reset_mock()
        mock_response.status_code = status_code
        try:
            await ImdsCredential(
                transport=Mock(send=wrap_in_future(mock_send),
                               sleep=wrap_in_future(lambda _: None))
            ).get_token("scope")
        except ClientAuthenticationError:
            pass
        # first call was availability probe, second the original request;
        # credential should have then exhausted retries for each of these status codes
        assert mock_send.call_count == 2 + total_retries
async def test_request_url():
    authority = "authority.com"
    tenant = "expected_tenant"

    def mock_send(request, **kwargs):
        scheme, netloc, path, _, _, _ = urlparse(request.url)
        assert scheme == "https"
        assert netloc == authority
        assert path.startswith("/" + tenant)
        return mock_response(json_payload={
            "token_type": "Bearer",
            "expires_in": 42,
            "access_token": "***"
        })

    client = AsyncAuthnClient(tenant=tenant,
                              transport=Mock(send=wrap_in_future(mock_send)),
                              authority=authority)
    await client.request_token(("scope", ))

    # authority can be configured via environment variable
    with patch.dict("os.environ",
                    {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority},
                    clear=True):
        client = AsyncAuthnClient(
            tenant=tenant, transport=Mock(send=wrap_in_future(mock_send)))
        await client.request_token(("scope", ))
Beispiel #4
0
async def test_cache():
    expired = "this token's expired"
    now = int(time.time())
    expired_on = now - 3600
    expired_token = AccessToken(expired, expired_on)
    token_payload = {
        "access_token": expired,
        "expires_in": 0,
        "ext_expires_in": 0,
        "expires_on": expired_on,
        "not_before": now,
        "token_type": "Bearer",
    }
    mock_send = Mock(return_value=mock_response(json_payload=token_payload))
    transport = Mock(send=wrap_in_future(mock_send))
    scope = "scope"

    credential = ClientSecretCredential("tenant-id", "client-id", "secret", transport=transport)

    # get_token initially returns the expired token because the credential
    # doesn't check whether tokens it receives from the service have expired
    token = await credential.get_token(scope)
    assert token == expired_token

    access_token = "new token"
    token_payload["access_token"] = access_token
    token_payload["expires_on"] = now + 3600
    valid_token = AccessToken(access_token, now + 3600)

    # second call should observe the cached token has expired, and request another
    token = await credential.get_token(scope)
    assert token == valid_token
    assert mock_send.call_count == 2
Beispiel #5
0
async def test_chain_returns_first_token():
    expected_token = Mock()
    first_credential = Mock(get_token=wrap_in_future(lambda _: expected_token))
    second_credential = Mock(get_token=Mock())

    aggregate = ChainedTokenCredential(first_credential, second_credential)
    credential = await aggregate.get_token("scope")

    assert credential is expected_token
    assert second_credential.get_token.call_count == 0
    async def exercise_credentials(authority_kwarg, expected_authority=None):
        expected_authority = expected_authority or authority_kwarg

        async def send(request, **_):
            url = urlparse(request.url)
            assert url.scheme == "https", "Unexpected scheme '{}'".format(
                url.scheme)
            assert url.netloc == expected_authority, "Expected authority '{}', actual was '{}'".format(
                expected_authority, url.netloc)
            return response

        # environment credential configured with client secret should respect authority
        environment = {
            EnvironmentVariables.AZURE_CLIENT_ID: "client_id",
            EnvironmentVariables.AZURE_CLIENT_SECRET: "secret",
            EnvironmentVariables.AZURE_TENANT_ID: "tenant_id",
        }
        with patch("os.environ", environment):
            transport = Mock(send=send)
            if authority_kwarg:
                credential = DefaultAzureCredential(authority=authority_kwarg,
                                                    transport=transport)
            else:
                credential = DefaultAzureCredential(transport=transport)
            access_token, _ = await credential.get_token("scope")
            assert access_token == expected_access_token

        # managed identity credential should ignore authority
        with patch("os.environ",
                   {EnvironmentVariables.MSI_ENDPOINT: "https://some.url"}):
            transport = Mock(send=wrap_in_future(lambda *_, **__: response))
            if authority_kwarg:
                credential = DefaultAzureCredential(authority=authority_kwarg,
                                                    transport=transport)
            else:
                credential = DefaultAzureCredential(transport=transport)
            access_token, _ = await credential.get_token("scope")
            assert access_token == expected_access_token

        # shared cache credential should respect authority
        upn = os.environ.get(EnvironmentVariables.AZURE_USERNAME,
                             "spam@eggs")  # preferring environment values to
        tenant = os.environ.get(EnvironmentVariables.AZURE_TENANT_ID,
                                "tenant")  # prevent failure during live runs
        account = get_account_event(username=upn,
                                    uid="guid",
                                    utid=tenant,
                                    authority=authority_kwarg)
        cache = populated_cache(account)
        with patch.object(SharedTokenCacheCredential, "supported"):
            credential = DefaultAzureCredential(_cache=cache,
                                                authority=authority_kwarg,
                                                transport=Mock(send=send))
        access_token, _ = await credential.get_token("scope")
        assert access_token == expected_access_token
async def test_auth_code_credential():
    client_id = "client id"
    tenant_id = "tenant"
    expected_code = "auth code"
    redirect_uri = "https://foo.bar"
    expected_token = AccessToken("token", 42)

    mock_client = Mock(spec=object)
    obtain_by_auth_code = Mock(return_value=expected_token)
    mock_client.obtain_token_by_authorization_code = wrap_in_future(
        obtain_by_auth_code)

    credential = AuthorizationCodeCredential(
        client_id=client_id,
        tenant_id=tenant_id,
        authorization_code=expected_code,
        redirect_uri=redirect_uri,
        client=mock_client,
    )

    # first call should redeem the auth code
    token = await credential.get_token("scope")
    assert token is expected_token
    assert obtain_by_auth_code.call_count == 1
    _, kwargs = obtain_by_auth_code.call_args
    assert kwargs["code"] == expected_code

    # no auth code -> credential should return cached token
    mock_client.obtain_token_by_authorization_code = None  # raise if credential calls this again
    mock_client.get_cached_access_token = lambda *_: expected_token
    token = await credential.get_token("scope")
    assert token is expected_token

    # no auth code, no cached token -> credential should use refresh token
    mock_client.get_cached_access_token = lambda *_: None
    mock_client.get_cached_refresh_tokens = lambda *_: [
        "this is a refresh token"
    ]
    mock_client.obtain_token_by_refresh_token = wrap_in_future(
        lambda *_, **__: expected_token)
    token = await credential.get_token("scope")
    assert token is expected_token
Beispiel #8
0
async def test_distro():

    mock_client = mock.Mock(spec=object)
    token_by_refresh_token = mock.Mock(return_value=None)
    mock_client.obtain_token_by_refresh_token = wrap_in_future(
        token_by_refresh_token)
    mock_client.get_cached_access_token = mock.Mock(return_value=None)

    with pytest.raises(CredentialUnavailableError):
        credential = VSCodeCredential(_client=mock_client)
        token = await credential.get_token("scope")
async def test_no_obtain_token_if_cached():
    expected_token = AccessToken("token", 42)

    mock_client = mock.Mock(spec=object)
    token_by_refresh_token = mock.Mock(return_value=expected_token)
    mock_client.obtain_token_by_refresh_token = wrap_in_future(token_by_refresh_token)
    mock_client.get_cached_access_token = mock.Mock(return_value="VALUE")

    with mock.patch(VSCodeCredential.__module__ + ".get_credentials", return_value="VALUE"):
        credential = VSCodeCredential(_client=mock_client)
        token = await credential.get_token("scope")
        assert token_by_refresh_token.call_count == 0
Beispiel #10
0
async def test_redeem_token():
    expected_token = AccessToken("token", 42)
    expected_value = "value"

    mock_client = mock.Mock(spec=object)
    token_by_refresh_token = mock.Mock(return_value=expected_token)
    mock_client.obtain_token_by_refresh_token = wrap_in_future(token_by_refresh_token)
    mock_client.get_cached_access_token = mock.Mock(return_value=None)

    with mock.patch(GET_REFRESH_TOKEN, return_value=expected_value):
        credential = get_credential(_client=mock_client)
        token = await credential.get_token("scope")
        assert token is expected_token
        token_by_refresh_token.assert_called_with(("scope",), expected_value)
async def test_cache_refresh_token():
    expected_token = AccessToken("token", 42)

    mock_client = mock.Mock(spec=object)
    token_by_refresh_token = mock.Mock(return_value=expected_token)
    mock_client.obtain_token_by_refresh_token = wrap_in_future(token_by_refresh_token)
    mock_client.get_cached_access_token = mock.Mock(return_value=None)
    mock_get_credentials = mock.Mock(return_value="VALUE")

    with mock.patch(VisualStudioCodeCredential.__module__ + ".get_credentials", mock_get_credentials):
        credential = VisualStudioCodeCredential(_client=mock_client)
        token = await credential.get_token("scope")
        assert mock_get_credentials.call_count == 1
        token = await credential.get_token("scope")
        assert mock_get_credentials.call_count == 1
Beispiel #12
0
async def test_cache_refresh_token():
    expected_token = AccessToken("token", 42)

    mock_client = mock.Mock(spec=object)
    token_by_refresh_token = mock.Mock(return_value=expected_token)
    mock_client.obtain_token_by_refresh_token = wrap_in_future(token_by_refresh_token)
    mock_client.get_cached_access_token = mock.Mock(return_value=None)
    mock_get_credentials = mock.Mock(return_value="VALUE")

    credential = get_credential(_client=mock_client)
    with mock.patch(GET_REFRESH_TOKEN, mock_get_credentials):
        await credential.get_token("scope")
        assert mock_get_credentials.call_count == 1
        await credential.get_token("scope")
        assert mock_get_credentials.call_count == 1
Beispiel #13
0
async def test_chain_attempts_all_credentials():
    async def raise_authn_error(message="it didn't work"):
        raise ClientAuthenticationError(message)

    expected_token = AccessToken("expected_token", 0)
    credentials = [
        Mock(get_token=Mock(wraps=raise_authn_error)),
        Mock(get_token=Mock(wraps=raise_authn_error)),
        Mock(get_token=wrap_in_future(lambda _: expected_token)),
    ]

    token = await ChainedTokenCredential(*credentials).get_token("scope")
    assert token is expected_token

    for credential in credentials[:-1]:
        assert credential.get_token.call_count == 1
Beispiel #14
0
async def test_chain_raises_for_unexpected_error():
    """the chain should not continue after an unexpected error (i.e. anything but CredentialUnavailableError)"""
    async def credential_unavailable(message="it didn't work"):
        raise CredentialUnavailableError(message)

    expected_message = "it can't be done"

    credentials = [
        Mock(get_token=Mock(wraps=credential_unavailable)),
        Mock(get_token=Mock(side_effect=ValueError(expected_message))),
        Mock(get_token=Mock(
            wraps=wrap_in_future(lambda _: AccessToken("**", 42))))
    ]

    with pytest.raises(ClientAuthenticationError) as ex:
        await ChainedTokenCredential(*credentials).get_token("scope")

    assert expected_message in ex.value.message
    assert credentials[-1].get_token.call_count == 0
Beispiel #15
0
async def test_no_obtain_token_if_cached():
    expected_token = AccessToken("token", time.time() + 3600)

    token_by_refresh_token = mock.Mock(return_value=expected_token)
    mock_client = mock.Mock(
        get_cached_access_token=mock.Mock(return_value=expected_token),
        obtain_token_by_refresh_token=wrap_in_future(token_by_refresh_token),
    )

    credential = get_credential(_client=mock_client)
    with mock.patch(
        GET_REFRESH_TOKEN,
        mock.Mock(side_effect=Exception("credential should not acquire a new token")),
    ):
        token = await credential.get_token("scope")

    assert token_by_refresh_token.call_count == 0
    assert token.token == expected_token.token
    assert token.expires_on == expected_token.expires_on
async def test_request_url():
    authority = "authority.com"
    tenant = "expected_tenant"

    def mock_send(request, **kwargs):
        scheme, netloc, path, _, _, _ = urlparse(request.url)
        assert scheme == "https"
        assert netloc == authority
        assert path.startswith("/" + tenant)
        return mock_response(json_payload={
            "token_type": "Bearer",
            "expires_in": 42,
            "access_token": "***"
        })

    client = AsyncAuthnClient(tenant=tenant,
                              transport=Mock(send=wrap_in_future(mock_send)),
                              authority=authority)
    await client.request_token(("scope", ))
Beispiel #17
0
async def test_cache():
    scope = "https://foo.bar"
    expired = "this token's expired"
    now = int(time.time())
    token_payload = {
        "access_token": expired,
        "refresh_token": "",
        "expires_in": 0,
        "expires_on": now - 300,  # expired 5 minutes ago
        "not_before": now,
        "resource": scope,
        "token_type": "Bearer",
    }

    mock_response = mock.Mock(
        text=lambda encoding=None: json.dumps(token_payload),
        headers={"content-type": "application/json"},
        status_code=200,
        content_type="application/json",
    )
    mock_send = mock.Mock(return_value=mock_response)

    credential = ImdsCredential(transport=mock.Mock(
        send=wrap_in_future(mock_send)))
    token = await credential.get_token(scope)
    assert token.token == expired
    assert mock_send.call_count == 2  # first request was probing for endpoint availability

    # calling get_token again should provoke another HTTP request
    good_for_an_hour = "this token's good for an hour"
    token_payload["expires_on"] = int(time.time()) + 3600
    token_payload["expires_in"] = 3600
    token_payload["access_token"] = good_for_an_hour
    token = await credential.get_token(scope)
    assert token.token == good_for_an_hour
    assert mock_send.call_count == 3

    # get_token should return the cached token now
    token = await credential.get_token(scope)
    assert token.token == good_for_an_hour
    assert mock_send.call_count == 3