async def test_close():
    transport = AsyncMockTransport()
    credential = SharedTokenCacheCredential(
        _cache=populated_cache(get_account_event("test@user", "uid", "utid")), transport=transport
    )

    await credential.close()

    assert transport.__aexit__.call_count == 1
    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=asyncio.coroutine(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
예제 #3
0
async def test_user_agent():
    transport = async_validating_transport(
        requests=[Request(required_headers={"User-Agent": USER_AGENT})],
        responses=[mock_response(json_payload=build_aad_response(access_token="**"))],
    )

    credential = SharedTokenCacheCredential(
        _cache=populated_cache(get_account_event("test@user", "uid", "utid")), transport=transport
    )

    await credential.get_token("scope")
예제 #4
0
async def test_context_manager():
    transport = AsyncMockTransport()
    credential = SharedTokenCacheCredential(_cache=populated_cache(
        get_account_event("test@user", "uid", "utid")),
                                            transport=transport)

    async with credential:
        assert transport.__aenter__.call_count == 1

    assert transport.__aenter__.call_count == 1
    assert transport.__aexit__.call_count == 1
예제 #5
0
def test_shared_cache_username():
    expected_access_token = "expected-access-token"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"
    upn_a = "spam@eggs"
    upn_b = "eggs@spam"
    tenant_id = "the-tenant"

    # two cached accounts, same tenant, different usernames -> shared_cache_username should prevail
    account_a = get_account_event(username=upn_a,
                                  uid="another-guid",
                                  utid=tenant_id,
                                  refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn_b,
                                  uid="more-guid",
                                  utid=tenant_id,
                                  refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    credential = get_credential_for_shared_cache_test(
        refresh_token_a,
        expected_access_token,
        cache,
        shared_cache_username=upn_a)
    token = credential.get_token("scope")
    assert token.token == expected_access_token

    # redundantly specifying shared_cache_tenant_id makes no difference
    credential = get_credential_for_shared_cache_test(
        refresh_token_a,
        expected_access_token,
        cache,
        shared_cache_tenant_id=tenant_id,
        shared_cache_username=upn_a)
    token = credential.get_token("scope")
    assert token.token == expected_access_token

    # shared_cache_username should prevail over AZURE_USERNAME
    with patch("os.environ", {EnvironmentVariables.AZURE_USERNAME: upn_b}):
        credential = get_credential_for_shared_cache_test(
            refresh_token_a,
            expected_access_token,
            cache,
            shared_cache_username=upn_a)
    token = credential.get_token("scope")
    assert token.token == expected_access_token

    # AZURE_USERNAME should be used when shared_cache_username isn't specified
    with patch("os.environ", {EnvironmentVariables.AZURE_USERNAME: upn_b}):
        credential = get_credential_for_shared_cache_test(
            refresh_token_b, expected_access_token, cache)
    token = credential.get_token("scope")
    assert token.token == expected_access_token
예제 #6
0
async def test_shared_cache_tenant_id():
    expected_access_token = "expected-access-token"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"
    upn = "spam@eggs"
    tenant_a = "tenant-a"
    tenant_b = "tenant-b"

    # two cached accounts, same username, different tenants -> shared_cache_tenant_id should prevail
    account_a = get_account_event(username=upn,
                                  uid="another-guid",
                                  utid=tenant_a,
                                  refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn,
                                  uid="more-guid",
                                  utid=tenant_b,
                                  refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    credential = get_credential_for_shared_cache_test(
        refresh_token_b,
        expected_access_token,
        cache,
        shared_cache_tenant_id=tenant_b)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token

    # redundantly specifying shared_cache_username makes no difference
    credential = get_credential_for_shared_cache_test(
        refresh_token_b,
        expected_access_token,
        cache,
        shared_cache_tenant_id=tenant_b,
        shared_cache_username=upn)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token

    # shared_cache_tenant_id should prevail over AZURE_TENANT_ID
    with patch("os.environ", {EnvironmentVariables.AZURE_TENANT_ID: tenant_a}):
        credential = get_credential_for_shared_cache_test(
            refresh_token_b,
            expected_access_token,
            cache,
            shared_cache_tenant_id=tenant_b)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token

    # AZURE_TENANT_ID should be used when shared_cache_tenant_id isn't specified
    with patch("os.environ", {EnvironmentVariables.AZURE_TENANT_ID: tenant_b}):
        credential = get_credential_for_shared_cache_test(
            refresh_token_b, expected_access_token, cache)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token
async def test_authority_aliases():
    """the credential should use a refresh token valid for any known alias of its authority"""

    expected_access_token = "access-token"

    for authority in KNOWN_ALIASES:
        # cache a token for this authority
        expected_refresh_token = authority.replace(".", "")
        account = get_account_event("spam@eggs",
                                    "uid",
                                    "tenant",
                                    authority=authority,
                                    refresh_token=expected_refresh_token)
        cache = populated_cache(account)

        # the token should be acceptable for this authority itself
        transport = async_validating_transport(
            requests=[
                Request(
                    authority=authority,
                    required_data={"refresh_token": expected_refresh_token})
            ],
            responses=[
                mock_response(json_payload=build_aad_response(
                    access_token=expected_access_token))
            ],
        )
        credential = SharedTokenCacheCredential(authority=authority,
                                                _cache=cache,
                                                transport=transport)
        token = await credential.get_token("scope")
        assert token.token == expected_access_token

        # it should also be acceptable for every known alias of this authority
        for alias in KNOWN_ALIASES[authority]:
            transport = async_validating_transport(
                requests=[
                    Request(authority=alias,
                            required_data={
                                "refresh_token": expected_refresh_token
                            })
                ],
                responses=[
                    mock_response(json_payload=build_aad_response(
                        access_token=expected_access_token))
                ],
            )
            credential = SharedTokenCacheCredential(authority=alias,
                                                    _cache=cache,
                                                    transport=transport)
            token = await credential.get_token("scope")
            assert token.token == expected_access_token
예제 #8
0
async def test_no_matching_account_for_tenant():
    """one cached account, tenant specified, tenant doesn't match -> credential should raise"""

    upn = "spam@eggs"
    tenant = "some-guid"
    account = get_account_event(username=upn, uid="uid", utid=tenant, refresh_token="refresh-token")
    cache = populated_cache(account)

    with pytest.raises(CredentialUnavailableError) as ex:
        await SharedTokenCacheCredential(_cache=cache, tenant_id="not-" + tenant).get_token("scope")

    assert ex.value.message.startswith(NO_MATCHING_ACCOUNTS[: NO_MATCHING_ACCOUNTS.index("{")])
    assert "not-" + tenant in ex.value.message
async def test_no_matching_account_for_username():
    """one cached account, username specified, username doesn't match -> credential should raise"""

    upn = "spam@eggs"
    tenant = "some-guid"
    account = get_account_event(username=upn, uid="uid", utid=tenant, refresh_token="refresh-token")
    cache = populated_cache(account)

    with pytest.raises(ClientAuthenticationError) as ex:
        await SharedTokenCacheCredential(_cache=cache, username="******" + upn).get_token("scope")

    assert ex.value.message.startswith(NO_MATCHING_ACCOUNTS[: NO_MATCHING_ACCOUNTS.index("{")])
    discovered_accounts = ex.value.message.splitlines()[-1]
    assert upn in discovered_accounts and tenant in discovered_accounts
예제 #10
0
async def test_no_refresh_token():
    """one cached account, account has no refresh token -> credential should raise"""

    account = get_account_event(uid="uid_a", utid="utid", username="******", refresh_token=None)
    cache = populated_cache(account)

    transport = Mock(side_effect=Exception())  # credential shouldn't use the network

    credential = SharedTokenCacheCredential(_cache=cache, transport=transport)
    with pytest.raises(CredentialUnavailableError, match=NO_ACCOUNTS):
        await credential.get_token("scope")

    credential = SharedTokenCacheCredential(_cache=cache, transport=transport, username="******")
    with pytest.raises(CredentialUnavailableError, match=NO_ACCOUNTS):
        await credential.get_token("scope")
예제 #11
0
async def test_authority_with_no_known_alias():
    """given an appropriate token, an authority with no known aliases should work"""

    authority = "unknown.authority"
    expected_access_token = "access-token"
    expected_refresh_token = "refresh-token"
    account = get_account_event("spam@eggs", "uid", "tenant", authority=authority, refresh_token=expected_refresh_token)
    cache = populated_cache(account)
    transport = async_validating_transport(
        requests=[Request(authority=authority, required_data={"refresh_token": expected_refresh_token})],
        responses=[mock_response(json_payload=build_aad_response(access_token=expected_access_token))],
    )
    credential = SharedTokenCacheCredential(authority=authority, _cache=cache, transport=transport)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token
예제 #12
0
async def test_policies_configurable():
    policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock())

    async def send(*_, **__):
        return mock_response(json_payload=build_aad_response(access_token="**"))

    credential = SharedTokenCacheCredential(
        _cache=populated_cache(get_account_event("test@user", "uid", "utid")),
        policies=[policy],
        transport=Mock(send=send),
    )

    await credential.get_token("scope")

    assert policy.on_request.called
예제 #13
0
async def test_close():
    async def send(*_, **__):
        return mock_response(json_payload=build_aad_response(access_token="**"))

    transport = AsyncMockTransport(send=send)
    credential = SharedTokenCacheCredential(
        _cache=populated_cache(get_account_event("test@user", "uid", "utid")), transport=transport
    )

    # the credential doesn't open a transport session before one is needed, so we send a request
    await credential.get_token("scope")

    await credential.close()

    assert transport.__aexit__.call_count == 1
async def test_shared_cache_username():
    expected_access_token = "expected-access-token"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"
    upn_a = "spam@eggs"
    upn_b = "eggs@spam"

    # The value of tenant_id is arbitrary because this test verifies the credential's behavior given a specified
    # username. During a complete live test run, $AZURE_TENANT_ID will have a value which DefaultAzureCredential
    # should pass to SharedTokenCacheCredential. This test will fail if the mock accounts don't match that value.
    tenant_id = os.environ.get(EnvironmentVariables.AZURE_TENANT_ID,
                               "the-tenant")

    # two cached accounts, same tenant, different usernames -> shared_cache_username should prevail
    account_a = get_account_event(username=upn_a,
                                  uid="another-guid",
                                  utid=tenant_id,
                                  refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn_b,
                                  uid="more-guid",
                                  utid=tenant_id,
                                  refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    credential = get_credential_for_shared_cache_test(
        refresh_token_a,
        expected_access_token,
        cache,
        shared_cache_username=upn_a)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token

    # shared_cache_username should prevail over AZURE_USERNAME
    with patch("os.environ", {EnvironmentVariables.AZURE_USERNAME: upn_b}):
        credential = get_credential_for_shared_cache_test(
            refresh_token_a,
            expected_access_token,
            cache,
            shared_cache_username=upn_a)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token

    # AZURE_USERNAME should be used when shared_cache_username isn't specified
    with patch("os.environ", {EnvironmentVariables.AZURE_USERNAME: upn_b}):
        credential = get_credential_for_shared_cache_test(
            refresh_token_b, expected_access_token, cache)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token
예제 #15
0
async def test_two_accounts_no_username_or_tenant():
    """two cached accounts, no username or tenant specified -> credential should raise"""

    upn_a = "a@foo"
    upn_b = "b@foo"
    account_a = get_account_event(username=upn_a, uid="uid_a", utid="utid")
    account_b = get_account_event(username=upn_b, uid="uid_b", utid="utid")
    cache = populated_cache(account_a, account_b)

    # credential can't select an identity => it shouldn't use the network
    transport = Mock(side_effect=Exception())

    # two users in the cache, no username specified -> CredentialUnavailableError
    credential = SharedTokenCacheCredential(_cache=cache, transport=transport)
    with pytest.raises(ClientAuthenticationError, match=MULTIPLE_ACCOUNTS) as ex:
        await credential.get_token("scope")
예제 #16
0
async def test_authority_environment_variable():
    """the credential should accept an authority by environment variable when none is otherwise specified"""

    authority = "localhost"
    expected_access_token = "access-token"
    expected_refresh_token = "refresh-token"
    account = get_account_event("spam@eggs", "uid", "tenant", authority=authority, refresh_token=expected_refresh_token)
    cache = populated_cache(account)
    transport = async_validating_transport(
        requests=[Request(authority=authority, required_data={"refresh_token": expected_refresh_token})],
        responses=[mock_response(json_payload=build_aad_response(access_token=expected_access_token))],
    )
    with patch.dict("os.environ", {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority}, clear=True):
        credential = SharedTokenCacheCredential(transport=transport, _cache=cache)
    token = await credential.get_token("scope")
    assert token.token == expected_access_token
예제 #17
0
async def test_multitenant_authentication_not_allowed():
    """get_token(tenant_id=...) should raise when allow_multitenant_authentication is False (the default)"""

    default_tenant = "organizations"
    expected_token = "***"

    async def send(request, **_):
        parsed = urlparse(request.url)
        tenant_id = parsed.path.split("/")[1]
        assert tenant_id == default_tenant
        return mock_response(json_payload=build_aad_response(
            access_token=expected_token,
            id_token_claims=id_token_claims(aud="...", iss="...", sub="..."),
        ))

    authority = "localhost"
    expected_account = get_account_event("user",
                                         "object-id",
                                         "tenant-id",
                                         authority=authority,
                                         client_id="client-id",
                                         refresh_token="**")
    cache = populated_cache(expected_account)

    credential = SharedTokenCacheCredential(authority=authority,
                                            transport=Mock(send=send),
                                            _cache=cache)

    token = await credential.get_token("scope")
    assert token.token == expected_token

    # explicitly specifying the configured tenant is okay
    token = await credential.get_token("scope", tenant_id=default_tenant)
    assert token.token == expected_token

    # but any other tenant should get an error
    with pytest.raises(ClientAuthenticationError,
                       match="allow_multitenant_authentication"):
        await credential.get_token("scope", tenant_id="some tenant")

    # ...unless the compat switch is enabled
    with patch.dict("os.environ", {
            EnvironmentVariables.AZURE_IDENTITY_ENABLE_LEGACY_TENANT_SELECTION:
            "true"
    }):
        token = await credential.get_token("scope", tenant_id="some tenant")
    assert token.token == expected_token, "credential should ignore tenant_id kwarg when the compat switch is enabled"
async def test_auth_record_multiple_accounts_for_username():
    tenant_id = "tenant-id"
    client_id = "client-id"
    authority = "localhost"
    object_id = "object-id"
    home_account_id = object_id + "." + tenant_id
    username = "******"
    record = AuthenticationRecord(tenant_id, client_id, authority,
                                  home_account_id, username)

    expected_access_token = "****"
    expected_refresh_token = "**"
    expected_account = get_account_event(username,
                                         object_id,
                                         tenant_id,
                                         authority=authority,
                                         client_id=client_id,
                                         refresh_token=expected_refresh_token)
    cache = populated_cache(
        expected_account,
        get_account_event(  # this account matches all but the record's tenant
            username,
            object_id,
            "different-" + tenant_id,
            authority=authority,
            client_id=client_id,
            refresh_token="not-" + expected_refresh_token,
        ),
    )

    transport = async_validating_transport(
        requests=[
            Request(authority=authority,
                    required_data={"refresh_token": expected_refresh_token})
        ],
        responses=[
            mock_response(json_payload=build_aad_response(
                access_token=expected_access_token))
        ],
    )
    credential = SharedTokenCacheCredential(_authentication_record=record,
                                            transport=transport,
                                            _cache=cache)

    token = await credential.get_token("scope")
    assert token.token == expected_access_token
예제 #19
0
async def test_single_account():
    """one cached account, no username specified -> credential should auth that account"""

    refresh_token = "refresh-token"
    scope = "scope"
    account = get_account_event(uid="uid_a", utid="utid", username="******", refresh_token=refresh_token)
    cache = populated_cache(account)

    expected_token = "***"
    transport = async_validating_transport(
        requests=[Request(required_data={"refresh_token": refresh_token, "scope": scope})],
        responses=[mock_response(json_payload=build_aad_response(access_token=expected_token))],
    )
    credential = SharedTokenCacheCredential(_cache=cache, transport=transport)

    token = await credential.get_token(scope)
    assert token.token == expected_token
async def test_no_matching_account_for_tenant_or_username():
    """two cached accounts, username and tenant specified, one account matches each -> credential should raise"""

    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"
    upn_a = "a@foo"
    upn_b = "b@foo"
    tenant_a = "tenant-a"
    tenant_b = "tenant-b"
    account_a = get_account_event(username=upn_a,
                                  uid="uid_a",
                                  utid=tenant_a,
                                  refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn_b,
                                  uid="uid_b",
                                  utid=tenant_b,
                                  refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    transport = Mock(
        side_effect=Exception())  # credential shouldn't use the network

    credential = SharedTokenCacheCredential(username=upn_a,
                                            tenant_id=tenant_b,
                                            _cache=cache,
                                            transport=transport)
    with pytest.raises(ClientAuthenticationError) as ex:
        await credential.get_token("scope")
    assert ex.value.message.startswith(
        NO_MATCHING_ACCOUNTS[:NO_MATCHING_ACCOUNTS.index("{")])
    discovered_accounts = ex.value.message.splitlines()[-1]
    assert all(s in discovered_accounts
               for s in (upn_a, upn_b, tenant_a, tenant_b))

    credential = SharedTokenCacheCredential(username=upn_b,
                                            tenant_id=tenant_a,
                                            _cache=cache,
                                            transport=transport)
    with pytest.raises(ClientAuthenticationError) as ex:
        await credential.get_token("scope")
    assert ex.value.message.startswith(
        NO_MATCHING_ACCOUNTS[:NO_MATCHING_ACCOUNTS.index("{")])
    discovered_accounts = ex.value.message.splitlines()[-1]
    assert all(s in discovered_accounts
               for s in (upn_a, upn_b, tenant_a, tenant_b))
예제 #21
0
def test_shared_cache_tenant_id():
    expected_access_token = "expected-access-token"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"

    # The value of the UPN is arbitrary because this test verifies the credential's behavior given a specified
    # tenant ID. During a complete live test run, $AZURE_USERNAME will have a value which DefaultAzureCredential
    # should pass to SharedTokenCacheCredential. This test will fail if the mock accounts don't match that value.
    upn = os.environ.get(EnvironmentVariables.AZURE_USERNAME, "spam@eggs")

    tenant_a = "tenant-a"
    tenant_b = "tenant-b"

    # two cached accounts, same username, different tenants -> shared_cache_tenant_id should prevail
    account_a = get_account_event(username=upn, uid="another-guid", utid=tenant_a, refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn, uid="more-guid", utid=tenant_b, refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    credential = get_credential_for_shared_cache_test(
        refresh_token_b, expected_access_token, cache, shared_cache_tenant_id=tenant_b
    )
    token = credential.get_token("scope")
    assert token.token == expected_access_token

    # redundantly specifying shared_cache_username makes no difference
    credential = get_credential_for_shared_cache_test(
        refresh_token_b, expected_access_token, cache, shared_cache_tenant_id=tenant_b, shared_cache_username=upn
    )
    token = credential.get_token("scope")
    assert token.token == expected_access_token

    # shared_cache_tenant_id should prevail over AZURE_TENANT_ID
    with patch("os.environ", {EnvironmentVariables.AZURE_TENANT_ID: tenant_a}):
        credential = get_credential_for_shared_cache_test(
            refresh_token_b, expected_access_token, cache, shared_cache_tenant_id=tenant_b
        )
    token = credential.get_token("scope")
    assert token.token == expected_access_token

    # AZURE_TENANT_ID should be used when shared_cache_tenant_id isn't specified
    with patch("os.environ", {EnvironmentVariables.AZURE_TENANT_ID: tenant_b}):
        credential = get_credential_for_shared_cache_test(refresh_token_b, expected_access_token, cache)
    token = credential.get_token("scope")
    assert token.token == expected_access_token
예제 #22
0
async def test_authentication_record_no_match():
    tenant_id = "tenant-id"
    client_id = "client-id"
    authority = "localhost"
    object_id = "object-id"
    home_account_id = object_id + "." + tenant_id
    username = "******"
    record = AuthenticationRecord(tenant_id, client_id, authority, home_account_id, username)

    transport = Mock(side_effect=Exception("the credential shouldn't send a request"))
    cache = populated_cache(
        get_account_event(
            "not-" + username, "not-" + object_id, "different-" + tenant_id, client_id="not-" + client_id,
        ),
    )
    credential = SharedTokenCacheCredential(authentication_record=record, transport=transport, _cache=cache)

    with pytest.raises(CredentialUnavailableError):
        await credential.get_token("scope")
async def test_same_tenant_different_usernames():
    """two cached accounts, same tenant, different usernames"""

    access_token_a = "access-token-a"
    access_token_b = "access-token-b"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"

    upn_a = "spam@eggs"
    upn_b = "eggs@spam"
    tenant_id = "the-tenant"
    account_a = get_account_event(username=upn_a, uid="another-guid", utid=tenant_id, refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn_b, uid="more-guid", utid=tenant_id, refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    # with no username specified the credential can't select an identity
    transport = Mock(side_effect=Exception())  # (so it shouldn't use the network)
    credential = SharedTokenCacheCredential(tenant_id=tenant_id, _cache=cache, transport=transport)
    with pytest.raises(ClientAuthenticationError) as ex:
        await credential.get_token("scope")
    # error message should indicate multiple matching accounts, and list discovered accounts
    assert ex.value.message.startswith(MULTIPLE_MATCHING_ACCOUNTS[: MULTIPLE_MATCHING_ACCOUNTS.index("{")])
    discovered_accounts = ex.value.message.splitlines()[-1]
    assert discovered_accounts.count(tenant_id) == 2
    assert upn_a in discovered_accounts and upn_b in discovered_accounts

    # with a username specified, the credential should auth the matching account
    scope = "scope"
    transport = async_validating_transport(
        requests=[Request(required_data={"refresh_token": refresh_token_b, "scope": scope})],
        responses=[mock_response(json_payload=build_aad_response(access_token=access_token_a))],
    )
    credential = SharedTokenCacheCredential(username=upn_b, _cache=cache, transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_a

    transport = async_validating_transport(
        requests=[Request(required_data={"refresh_token": refresh_token_a, "scope": scope})],
        responses=[mock_response(json_payload=build_aad_response(access_token=access_token_a))],
    )
    credential = SharedTokenCacheCredential(username=upn_a, _cache=cache, transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_a
예제 #24
0
async def test_two_accounts_username_specified():
    """two cached accounts, username specified, one account matches -> credential should auth that account"""

    scope = "scope"
    expected_refresh_token = "refresh-token-a"
    upn_a = "a@foo"
    upn_b = "b@foo"
    account_a = get_account_event(username=upn_a, uid="uid_a", utid="utid", refresh_token=expected_refresh_token)
    account_b = get_account_event(username=upn_b, uid="uid_b", utid="utid", refresh_token="refresh_token_b")
    cache = populated_cache(account_a, account_b)

    expected_token = "***"
    transport = async_validating_transport(
        requests=[Request(required_data={"refresh_token": expected_refresh_token, "scope": scope})],
        responses=[mock_response(json_payload=build_aad_response(access_token=expected_token))],
    )
    credential = SharedTokenCacheCredential(username=upn_a, _cache=cache, transport=transport)
    token = await credential.get_token(scope)
    assert token.token == expected_token
예제 #25
0
async def test_allow_multitenant_authentication():
    """When allow_multitenant_authentication is True, the credential should respect get_token(tenant_id=...)"""

    first_token = "***"
    second_tenant = "second-tenant"
    second_token = first_token * 2

    async def send(request, **_):
        parsed = urlparse(request.url)
        tenant_id = parsed.path.split("/")[1]
        return mock_response(json_payload=build_aad_response(
            access_token=second_token if tenant_id ==
            second_tenant else first_token,
            id_token_claims=id_token_claims(aud="...", iss="...", sub="..."),
        ))

    authority = "localhost"
    expected_account = get_account_event("user",
                                         "object-id",
                                         "tenant-id",
                                         authority=authority,
                                         client_id="client-id",
                                         refresh_token="**")
    cache = populated_cache(expected_account)

    credential = SharedTokenCacheCredential(
        allow_multitenant_authentication=True,
        authority=authority,
        transport=Mock(send=send),
        _cache=cache)
    token = await credential.get_token("scope")
    assert token.token == first_token

    token = await credential.get_token("scope", tenant_id="organizations")
    assert token.token == first_token

    token = await credential.get_token("scope", tenant_id=second_tenant)
    assert token.token == second_token

    # should still default to the first tenant
    token = await credential.get_token("scope")
    assert token.token == first_token
async def test_two_accounts_no_username_or_tenant():
    """two cached accounts, no username or tenant specified -> credential should raise"""

    upn_a = "a@foo"
    upn_b = "b@foo"
    account_a = get_account_event(username=upn_a, uid="uid_a", utid="utid")
    account_b = get_account_event(username=upn_b, uid="uid_b", utid="utid")
    cache = populated_cache(account_a, account_b)

    # credential can't select an identity => it shouldn't use the network
    transport = Mock(side_effect=Exception())

    # two users in the cache, no username specified -> ClientAuthenticationError
    credential = SharedTokenCacheCredential(_cache=cache, transport=transport)
    with pytest.raises(ClientAuthenticationError) as ex:
        await credential.get_token("scope")

    # error message should mention multiple accounts and list usernames in the cache
    assert upn_a in ex.value.message and upn_b in ex.value.message
    assert ex.value.message.splitlines()[:-1] == MULTIPLE_ACCOUNTS.splitlines()[:-1]
예제 #27
0
async def test_same_username_different_tenants():
    """two cached accounts, same username, different tenants"""

    access_token_a = "access-token-a"
    access_token_b = "access-token-b"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"

    upn = "spam@eggs"
    tenant_a = "tenant-a"
    tenant_b = "tenant-b"
    account_a = get_account_event(username=upn, uid="another-guid", utid=tenant_a, refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn, uid="more-guid", utid=tenant_b, refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    # with no tenant specified the credential can't select an identity
    transport = Mock(side_effect=Exception())  # (so it shouldn't use the network)
    credential = SharedTokenCacheCredential(username=upn, _cache=cache, transport=transport)
    with pytest.raises(CredentialUnavailableError) as ex:
        await credential.get_token("scope")

    assert ex.value.message.startswith(MULTIPLE_MATCHING_ACCOUNTS[: MULTIPLE_MATCHING_ACCOUNTS.index("{")])
    assert upn in ex.value.message

    # with tenant specified, the credential should auth the matching account
    scope = "scope"
    transport = async_validating_transport(
        requests=[Request(required_data={"refresh_token": refresh_token_a, "scope": scope})],
        responses=[mock_response(json_payload=build_aad_response(access_token=access_token_a))],
    )
    credential = SharedTokenCacheCredential(tenant_id=tenant_a, _cache=cache, transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_a

    transport = async_validating_transport(
        requests=[Request(required_data={"refresh_token": refresh_token_b, "scope": scope})],
        responses=[mock_response(json_payload=build_aad_response(access_token=access_token_b))],
    )
    credential = SharedTokenCacheCredential(tenant_id=tenant_b, _cache=cache, transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_b
예제 #28
0
async def test_context_manager():
    async def send(*_, **__):
        return mock_response(json_payload=build_aad_response(access_token="**"))

    transport = AsyncMockTransport(send=send)
    credential = SharedTokenCacheCredential(
        _cache=populated_cache(get_account_event("test@user", "uid", "utid")), transport=transport
    )

    # async with before initialization: credential should call aexit but not aenter
    async with credential:
        await credential.get_token("scope")

    assert transport.__aenter__.call_count == 0
    assert transport.__aexit__.call_count == 1

    # async with after initialization: credential should call aenter and aexit
    async with credential:
        await credential.get_token("scope")
        assert transport.__aenter__.call_count == 1
    assert transport.__aexit__.call_count == 2
async def test_multitenant_authentication_not_allowed():
    default_tenant = "organizations"
    expected_token = "***"

    async def send(request, **_):
        parsed = urlparse(request.url)
        tenant_id = parsed.path.split("/")[1]
        assert tenant_id == default_tenant
        return mock_response(json_payload=build_aad_response(
            access_token=expected_token,
            id_token_claims=id_token_claims(aud="...", iss="...", sub="..."),
        ))

    authority = "localhost"
    expected_account = get_account_event("user",
                                         "object-id",
                                         "tenant-id",
                                         authority=authority,
                                         client_id="client-id",
                                         refresh_token="**")
    cache = populated_cache(expected_account)

    credential = SharedTokenCacheCredential(authority=authority,
                                            transport=Mock(send=send),
                                            _cache=cache)

    token = await credential.get_token("scope")
    assert token.token == expected_token

    token = await credential.get_token("scope", tenant_id=default_tenant)
    assert token.token == expected_token

    with patch.dict(
            "os.environ",
        {EnvironmentVariables.AZURE_IDENTITY_DISABLE_MULTITENANTAUTH: "true"}):
        token = await credential.get_token("scope", tenant_id="some tenant")
        assert token.token == expected_token