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
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()]