Example #1
0
def test_login_hint():
    expected_username = "******"
    auth_code_response = {"code": "authorization-code", "state": ["..."]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))
    transport = Mock(send=Mock(side_effect=Exception(
        "this test mocks MSAL, so no request should be sent")))

    msal_acquire_token_result = dict(
        build_aad_response(access_token="**", id_token=build_id_token()),
        id_token_claims=id_token_claims("issuer",
                                        "subject",
                                        "audience",
                                        upn="upn"),
    )
    mock_msal_app = Mock(
        acquire_token_by_auth_code_flow=Mock(
            return_value=msal_acquire_token_result),
        initiate_auth_code_flow=Mock(
            return_value={"auth_uri": "http://localhost"}),
    )

    credential = InteractiveBrowserCredential(_server_class=server_class,
                                              transport=transport,
                                              login_hint=expected_username)
    with patch("msal.PublicClientApplication",
               Mock(return_value=mock_msal_app)):
        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential.authenticate(scopes=["scope"])

    assert mock_msal_app.initiate_auth_code_flow.call_count == 1
    _, kwargs = mock_msal_app.initiate_auth_code_flow.call_args
    assert kwargs["login_hint"] == expected_username
def test_claims_challenge():
    """get_token and authenticate should pass any claims challenge to MSAL token acquisition APIs"""

    expected_claims = '{"access_token": {"essential": "true"}'

    auth_code_response = {"code": "authorization-code", "state": ["..."]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    msal_acquire_token_result = dict(
        build_aad_response(access_token="**", id_token=build_id_token()),
        id_token_claims=id_token_claims("issuer",
                                        "subject",
                                        "audience",
                                        upn="upn"),
    )

    transport = Mock(send=Mock(side_effect=Exception(
        "this test mocks MSAL, so no request should be sent")))
    credential = InteractiveBrowserCredential(_server_class=server_class,
                                              transport=transport)
    with patch.object(InteractiveBrowserCredential,
                      "_get_app") as get_mock_app:
        msal_app = get_mock_app()
        msal_app.initiate_auth_code_flow.return_value = {
            "auth_uri": "http://localhost"
        }
        msal_app.acquire_token_by_auth_code_flow.return_value = msal_acquire_token_result

        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential.authenticate(scopes=["scope"], claims=expected_claims)

        assert msal_app.acquire_token_by_auth_code_flow.call_count == 1
        args, kwargs = msal_app.acquire_token_by_auth_code_flow.call_args
        assert kwargs["claims_challenge"] == expected_claims

        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential.get_token("scope", claims=expected_claims)

        assert msal_app.acquire_token_by_auth_code_flow.call_count == 2
        args, kwargs = msal_app.acquire_token_by_auth_code_flow.call_args
        assert kwargs["claims_challenge"] == expected_claims

        msal_app.get_accounts.return_value = [{
            "home_account_id":
            credential._auth_record.home_account_id
        }]
        msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result
        credential.get_token("scope", claims=expected_claims)

        assert msal_app.acquire_token_silent_with_error.call_count == 1
        args, kwargs = msal_app.acquire_token_silent_with_error.call_args
        assert kwargs["claims_challenge"] == expected_claims
def test_browser_credential():
    transport = Mock(wraps=RequestsTransport())
    credential = InteractiveBrowserCredential(transport=transport)
    scope = "https://management.azure.com/.default"  # N.B. this is valid only in Public Cloud

    record = credential.authenticate(scopes=(scope, ))
    assert record.authority
    assert record.home_account_id
    assert record.tenant_id
    assert record.username

    # credential should have a cached access token for the scope used in authenticate
    with patch(
            WEBBROWSER_OPEN,
            Mock(side_effect=Exception(
                "credential should authenticate silently"))):
        token = credential.get_token(scope)
    assert token.token

    credential = InteractiveBrowserCredential(transport=transport)
    token = credential.get_token(scope)
    assert token.token

    with patch(
            WEBBROWSER_OPEN,
            Mock(side_effect=Exception(
                "credential should authenticate silently"))):
        second_token = credential.get_token(scope)
    assert second_token.token == token.token

    # every request should have the correct User-Agent
    for call in transport.send.call_args_list:
        args, _ = call
        request = args[0]
        assert request.headers["User-Agent"] == USER_AGENT
def test_authenticate():
    client_id = "client-id"
    environment = "localhost"
    issuer = "https://" + environment
    tenant_id = "some-tenant"
    authority = issuer + "/" + tenant_id

    access_token = "***"
    scope = "scope"

    # mock AAD response with id token
    object_id = "object-id"
    home_tenant = "home-tenant-id"
    username = "******"
    id_token = build_id_token(aud=client_id, iss=issuer, object_id=object_id, tenant_id=home_tenant, username=username)
    auth_response = build_aad_response(
        uid=object_id, utid=home_tenant, access_token=access_token, refresh_token="**", id_token=id_token
    )

    transport = validating_transport(
        requests=[Request(url_substring=issuer)] * 3,
        responses=[get_discovery_response(authority)] * 2 + [mock_response(json_payload=auth_response)],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    oauth_state = "state"
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response))

    with patch(InteractiveBrowserCredential.__module__ + ".uuid.uuid4", lambda: oauth_state):
        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential = InteractiveBrowserCredential(
                _cache=TokenCache(),
                authority=environment,
                client_id=client_id,
                _server_class=server_class,
                tenant_id=tenant_id,
                transport=transport,
            )
            record = credential.authenticate(scopes=(scope,))

    assert record.authority == environment
    assert record.home_account_id == object_id + "." + home_tenant
    assert record.tenant_id == home_tenant
    assert record.username == username

    # credential should have a cached access token for the scope used in authenticate
    with patch(WEBBROWSER_OPEN, Mock(side_effect=Exception("credential should authenticate silently"))):
        token = credential.get_token(scope)
    assert token.token == access_token
Example #5
0
import os
import sys
from azure.identity import AuthenticationRequiredError, InteractiveBrowserCredential
from azure.keyvault.secrets import SecretClient

# This sample uses Key Vault only for demonstration. Any client accepting azure-identity credentials will work the same.
VAULT_URL = os.environ.get("VAULT_URL")
if not VAULT_URL:
    print(
        "This sample expects environment variable 'VAULT_URL' to be set with the URL of a Key Vault."
    )
    sys.exit(1)

# If it's important for your application to prompt for authentication only at certain times,
# create the credential with disable_automatic_authentication=True. This configures the credential to raise
# when interactive authentication is required, instead of immediately beginning that authentication.
credential = InteractiveBrowserCredential(
    disable_automatic_authentication=True)
client = SecretClient(VAULT_URL, credential)

try:
    secret_names = [s.name for s in client.list_properties_of_secrets()]
except AuthenticationRequiredError as ex:
    # Interactive authentication is necessary to authorize the client's request. The exception carries the
    # requested authentication scopes. If you pass these to 'authenticate', it will cache an access token
    # for those scopes.
    credential.authenticate(scopes=ex.scopes)

# the client operation should now succeed
secret_names = [s.name for s in client.list_properties_of_secrets()]
VAULT_URL = os.environ.get("VAULT_URL")
if not VAULT_URL:
    print(
        "This sample expects environment variable 'VAULT_URL' to be set with the URL of a Key Vault."
    )
    sys.exit(1)

# Persistent caching is optional. By default, interactive credentials cache in memory only.
# To enable persistent caching, give the credential an instance of TokenCachePersistenceOptions.
# (see the API documentation for more information about TokenCachePersistenceOptions)
credential = InteractiveBrowserCredential(
    cache_persistence_options=TokenCachePersistenceOptions())

# The 'authenticate' method begins interactive authentication. Call it whenever it's convenient
# for your application to authenticate a user. It returns a record of the authentication.
record = credential.authenticate()
print("\nAuthenticated first credential")

# The record contains no authentication secrets. You can serialize it to JSON for storage.
record_json = record.serialize()

# An authenticated credential is ready for use with a client. This request should succeed
# without prompting for authentication again.
client = SecretClient(VAULT_URL, credential)
secret_names = [s.name for s in client.list_properties_of_secrets()]
print("\nCompleted request with first credential")

# An authentication record stored by your application enables other credentials to access data from
# past authentications. If the cache contains sufficient data, this eliminates the need for your
# application to prompt for authentication every time it runs.
deserialized_record = AuthenticationRecord.deserialize(record_json)
        headers={"Authorization": "Bearer " + graph_token.token},
    )
    response.raise_for_status()
    print("revoked user's sessions")

    print("waiting for ARM to revoke tokens...")  # usually takes <5 minutes
    retry_delay = 10
    for i in range(600 // retry_delay):
        list(client.subscriptions.list())
        current_token = credential.get_token(ARM_SCOPE)
        if current_token.token != first_token.token:
            print("silently completed a claims challenge")
            return
        time.sleep(retry_delay)
        print("...%i seconds" % ((i + 1) * retry_delay))

    print("inconclusive test -- the first ARM token hasn't been revoked")


# az login
browser_credential = InteractiveBrowserCredential(
    tenant_id=TENANT_ID,
    disable_automatic_authentication=True,
    enable_persistent_cache=True)
record = browser_credential.authenticate()

# az foo
credential = SharedTokenCacheCredential(authentication_record=record)

test_cae(credential)
"""

import os
import sys
from azure.identity import AuthenticationRequiredError, InteractiveBrowserCredential
from azure.keyvault.secrets import SecretClient


# This sample uses Key Vault only for demonstration. Any client accepting azure-identity credentials will work the same.
VAULT_URL = os.environ.get("VAULT_URL")
if not VAULT_URL:
    print("This sample expects environment variable 'VAULT_URL' to be set with the URL of a Key Vault.")
    sys.exit(1)

# If it's important for your application to prompt for authentication only at certain times,
# create the credential with disable_automatic_authentication=True. This configures the credential to raise
# when interactive authentication is required, instead of immediately beginning that authentication.
credential = InteractiveBrowserCredential(disable_automatic_authentication=True)
client = SecretClient(VAULT_URL, credential)

try:
    secret_names = [s.name for s in client.list_properties_of_secrets()]
except AuthenticationRequiredError as ex:
    # Interactive authentication is necessary to authorize the client's request. The exception carries the
    # requested authentication scopes as well as any additional claims the service requires. If you pass
    # both to 'authenticate', it will cache an access token for the necessary scopes.
    credential.authenticate(scopes=ex.scopes, claims=ex.claims)

# the client operation should now succeed
secret_names = [s.name for s in client.list_properties_of_secrets()]