def test_username_password_credential(): expected_token = "access-token" client_id = "client-id" transport = validating_transport( requests=[Request()] * 3, # not validating requests because they're formed by MSAL responses=[ # tenant discovery mock_response( json_payload={ "authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b" }), # user realm discovery, interests MSAL only when the response body contains account_type == "Federated" mock_response(json_payload={}), # token request mock_response( json_payload=build_aad_response(access_token=expected_token, id_token=build_id_token( aud=client_id))), ], ) credential = UsernamePasswordCredential( client_id=client_id, username="******", password="******", transport=transport, instance_discovery= False, # kwargs are passed to MSAL; this one prevents an AAD verification request ) token = credential.get_token("scope") assert token.token == expected_token
def get_account_event(username, uid, utid, authority=None, client_id="client-id", refresh_token="refresh-token", scopes=None, **kwargs): if authority: endpoint = "https://" + "/".join(( authority, utid, "path", )) else: endpoint = get_default_authority() + "/{}/{}".format(utid, "path") return { "response": build_aad_response(uid=uid, utid=utid, refresh_token=refresh_token, id_token=build_id_token(aud=client_id, username=username), foci="1", **kwargs), "client_id": client_id, "token_endpoint": endpoint, "scope": scopes or ["scope"], }
def get_account_event(username, uid, utid, authority=None, client_id="client-id", refresh_token="refresh-token", scopes=None): return { "response": build_aad_response( uid=uid, utid=utid, refresh_token=refresh_token, id_token=build_id_token(aud=client_id, preferred_username=username), foci="1", ), "client_id": client_id, "token_endpoint": "https://" + "/".join( (authority or KnownAuthorities.AZURE_PUBLIC_CLOUD, utid, "/path")), "scope": scopes or ["scope"], }
def test_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) client_id = "client-id" transport = validating_transport( requests=[Request()] * 2, responses=[ get_discovery_response(), mock_response(json_payload=build_aad_response( access_token="**", id_token=build_id_token(aud=client_id))), ], ) # mock local server fakes successful authentication by immediately returning a well-formed response oauth_state = "oauth-state" auth_code_response = {"code": "authorization-code", "state": [oauth_state]} server_class = Mock(return_value=Mock( wait_for_redirect=lambda: auth_code_response)) credential = InteractiveBrowserCredential(policies=[policy], client_id=client_id, transport=transport, _server_class=server_class, _cache=TokenCache()) with patch("azure.identity._credentials.browser.uuid.uuid4", lambda: oauth_state): credential.get_token("scope") assert policy.on_request.called
def test_user_agent(): client_id = "client-id" transport = validating_transport( requests=[Request()] * 2 + [Request(required_headers={"User-Agent": USER_AGENT})], responses=[ get_discovery_response(), mock_response( json_payload={ "device_code": "_", "user_code": "user-code", "verification_uri": "verification-uri", "expires_in": 42, }), mock_response(json_payload=dict(build_aad_response( access_token="**", id_token=build_id_token(aud=client_id)), scope="scope")), ], ) credential = DeviceCodeCredential(client_id=client_id, prompt_callback=Mock(), transport=transport) credential.get_token("scope")
def test_claims_challenge(): """get_token should and authenticate pass any claims challenge to MSAL token acquisition APIs""" 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"), ) expected_claims = '{"access_token": {"essential": "true"}' transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) with patch.object(UsernamePasswordCredential, "_get_app") as get_mock_app: msal_app = get_mock_app() msal_app.acquire_token_by_username_password.return_value = msal_acquire_token_result credential.authenticate(scopes=["scope"], claims=expected_claims) assert msal_app.acquire_token_by_username_password.call_count == 1 args, kwargs = msal_app.acquire_token_by_username_password.call_args assert kwargs["claims_challenge"] == expected_claims credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_by_username_password.call_count == 2 args, kwargs = msal_app.acquire_token_by_username_password.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_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 should pass any claims challenge to MSAL token acquisition APIs""" expected_claims = '{"access_token": {"essential": "true"}' record = AuthenticationRecord("tenant-id", "client_id", "authority", "home_account_id", "username") msal_app = Mock() msal_app.get_accounts.return_value = [{ "home_account_id": record.home_account_id }] msal_app.acquire_token_silent_with_error.return_value = dict( build_aad_response(access_token="**", id_token=build_id_token())) transport = Mock(send=Mock(side_effect=Exception( "this test mocks MSAL, so no request should be sent"))) credential = SharedTokenCacheCredential(transport=transport, authentication_record=record, _cache=TokenCache()) with patch( SharedTokenCacheCredential.__module__ + ".PublicClientApplication", lambda *_, **__: msal_app): 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_device_code_credential(): client_id = "client-id" expected_token = "access-token" user_code = "user-code" verification_uri = "verification-uri" expires_in = 42 transport = validating_transport( requests=[Request()] * 3, # not validating requests because they're formed by MSAL responses=[ # expected requests: discover tenant, start device code flow, poll for completion mock_response( json_payload={ "authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b" }), mock_response( json_payload={ "device_code": "_", "user_code": user_code, "verification_uri": verification_uri, "expires_in": expires_in, }), mock_response(json_payload=dict( build_aad_response( access_token=expected_token, expires_in=expires_in, refresh_token="_", id_token=build_id_token(aud=client_id), ), scope="scope", ), ), ], ) callback = Mock() credential = DeviceCodeCredential( client_id=client_id, prompt_callback=callback, transport=transport, instance_discovery=False, _cache=TokenCache(), ) now = datetime.datetime.utcnow() token = credential.get_token("scope") assert token.token == expected_token # prompt_callback should have been called as documented assert callback.call_count == 1 uri, code, expires_on = callback.call_args[0] assert uri == verification_uri assert code == user_code # validating expires_on exactly would require depending on internals of the credential and # patching time, so we'll be satisfied if expires_on is a datetime at least expires_in # seconds later than our call to get_token assert isinstance(expires_on, datetime.datetime) assert expires_on - now >= datetime.timedelta(seconds=expires_in)
def test_claims_challenge(): """get_token should pass any claims challenge to MSAL token acquisition APIs""" 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"), ) expected_claims = '{"access_token": {"essential": "true"}' transport = Mock(send=Mock(side_effect=Exception( "this test mocks MSAL, so no request should be sent"))) credential = DeviceCodeCredential(transport=transport) with patch.object(DeviceCodeCredential, "_get_app") as get_mock_app: msal_app = get_mock_app() msal_app.initiate_device_flow.return_value = {"message": "it worked"} msal_app.acquire_token_by_device_flow.return_value = msal_acquire_token_result credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_by_device_flow.call_count == 1 args, kwargs = msal_app.acquire_token_by_device_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_user_agent(): client_id = "client-id" transport = validating_transport( requests=[ Request(), Request(required_headers={"User-Agent": USER_AGENT}) ], responses=[ get_discovery_response(), mock_response(json_payload=build_aad_response( access_token="**", id_token=build_id_token(aud=client_id))), ], ) # mock local server fakes successful authentication by immediately returning a well-formed response oauth_state = "oauth-state" auth_code_response = {"code": "authorization-code", "state": [oauth_state]} server_class = Mock(return_value=Mock( wait_for_redirect=lambda: auth_code_response)) credential = InteractiveBrowserCredential(client_id=client_id, transport=transport, _server_class=server_class, _cache=TokenCache()) with patch("azure.identity._credentials.browser.uuid.uuid4", lambda: oauth_state): credential.get_token("scope")
def test_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) client_id = "client-id" transport = validating_transport( requests=[Request()] * 3, responses=[ # expected requests: discover tenant, start device code flow, poll for completion get_discovery_response(), mock_response( json_payload={ "device_code": "_", "user_code": "user-code", "verification_uri": "verification-uri", "expires_in": 42, }), mock_response(json_payload=dict(build_aad_response( access_token="**", id_token=build_id_token(aud=client_id)), scope="scope")), ], ) credential = DeviceCodeCredential(client_id=client_id, prompt_callback=Mock(), policies=[policy], transport=transport) credential.get_token("scope") assert policy.on_request.called
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)] * 4, responses=[get_discovery_response(authority)] * 2 # instance and tenant discovery + [ mock_response( # start device code flow json_payload={ "device_code": "_", "user_code": "user-code", "verification_uri": "verification-uri", "expires_in": 42, } ), mock_response(json_payload=dict(auth_response, scope=scope)), # poll for completion ], ) credential = DeviceCodeCredential( client_id, prompt_callback=Mock(), # prevent credential from printing to stdout transport=transport, authority=environment, tenant_id=tenant_id, _cache=TokenCache(), ) record = credential.authenticate(scopes=(scope, )) # credential should have a cached access token for the scope used in authenticate token = credential.get_token(scope) assert token.token == access_token assert record.authority == environment assert record.home_account_id == object_id + "." + home_tenant assert record.tenant_id == home_tenant assert record.username == username
def test_user_agent(): transport = validating_transport( requests=[Request()] * 2 + [Request(required_headers={"User-Agent": USER_AGENT})], responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) credential.get_token("scope")
def test_writes_to_cache(): """the credential should write tokens it acquires to the cache""" scope = "scope" expected_access_token = "access token" first_refresh_token = "first refresh token" second_refresh_token = "second refresh token" username = "******" uid = "uid" utid = "utid" account = get_account_event(username=username, uid=uid, utid=utid, refresh_token=first_refresh_token) cache = TokenCache() cache.add(account) transport = validating_transport( requests=[Request(required_data={"refresh_token": first_refresh_token})], # credential redeems refresh token responses=[ mock_response( json_payload=build_aad_response( # AAD responds with an access token and new refresh token uid=uid, utid=utid, access_token=expected_access_token, refresh_token=second_refresh_token, id_token=build_id_token( aud=DEVELOPER_SIGN_ON_CLIENT_ID, object_id=uid, tenant_id=utid, username=username ), ) ) ], ) credential = SharedTokenCacheCredential(_cache=cache, transport=transport) token = credential.get_token(scope) assert token.token == expected_access_token # access token should be in the cache, and another instance should retrieve it credential = SharedTokenCacheCredential( _cache=cache, transport=Mock(send=Mock(side_effect=Exception("the credential should return a cached token"))) ) token = credential.get_token(scope) assert token.token == expected_access_token # and the credential should have updated the cached refresh token second_access_token = "second access token" transport = validating_transport( requests=[Request(required_data={"refresh_token": second_refresh_token})], responses=[mock_response(json_payload=build_aad_response(access_token=second_access_token))], ) credential = SharedTokenCacheCredential(_cache=cache, transport=transport) token = credential.get_token("some other " + scope) assert token.token == second_access_token # verify the credential didn't add a new cache entry assert len(cache.find(TokenCache.CredentialType.REFRESH_TOKEN)) == 1
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)] * 4, responses=[ get_discovery_response(authority), # instance discovery get_discovery_response(authority), # tenant discovery mock_response(status_code=404), # user realm discovery mock_response(json_payload=auth_response ), # token request following authenticate() ], ) credential = UsernamePasswordCredential( username=username, password="******", authority=environment, client_id=client_id, tenant_id=tenant_id, transport=transport, ) record = credential.authenticate(scopes=(scope, )) for auth_record in (record, credential.authentication_record): assert auth_record.authority == environment assert auth_record.home_account_id == object_id + "." + home_tenant assert auth_record.tenant_id == home_tenant assert auth_record.username == username # credential should have a cached access token for the scope passed to authenticate token = credential.get_token(scope) assert token.token == access_token
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_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) transport = validating_transport( requests=[Request()] * 3, responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", policies=[policy], transport=transport) credential.get_token("scope") assert policy.on_request.called
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
def test_wsl_fallback(uname, is_wsl): """the credential should invoke powershell.exe to open a browser in WSL when webbrowser.open fails""" auth_uri = "http://localhost" expected_access_token = "**" msal_acquire_token_result = dict( build_aad_response(access_token=expected_access_token, id_token=build_id_token()), id_token_claims=id_token_claims("issuer", "subject", "audience", upn="upn"), ) msal_app = Mock( initiate_auth_code_flow=Mock(return_value={"auth_uri": auth_uri}), acquire_token_by_auth_code_flow=Mock( return_value=msal_acquire_token_result), ) transport = Mock(send=Mock(side_effect=Exception( "this test mocks MSAL, so no request should be sent"))) credential = InteractiveBrowserCredential(_server_class=Mock(), transport=transport) with patch(InteractiveBrowserCredential.__module__ + ".subprocess.call") as subprocess_call: subprocess_call.return_value = 0 with patch(InteractiveBrowserCredential.__module__ + ".platform.uname", lambda: uname): with patch.object(InteractiveBrowserCredential, "_get_app", lambda _: msal_app): with patch(WEBBROWSER_OPEN, lambda _: False): try: token = credential.get_token("scope") except CredentialUnavailableError: assert not is_wsl, "credential should invoke powershell.exe in WSL" return assert is_wsl, "credential should raise CredentialUnavailableError when not in WSL" assert token.token == expected_access_token assert subprocess_call.call_count == 1 args, kwargs = subprocess_call.call_args assert args[0][0] == "powershell.exe" assert auth_uri in args[0][-1] if platform.python_version() >= "3.3": assert "timeout" in kwargs
def test_tenant_id(): client_id = "client-id" expected_token = "access-token" user_code = "user-code" verification_uri = "verification-uri" expires_in = 42 transport = validating_transport( requests=[Request()] * 3, # not validating requests because they're formed by MSAL responses=[ # expected requests: discover tenant, start device code flow, poll for completion mock_response(json_payload={"authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b"}), mock_response( json_payload={ "device_code": "_", "user_code": user_code, "verification_uri": verification_uri, "expires_in": expires_in, } ), mock_response( json_payload=dict( build_aad_response( access_token=expected_token, expires_in=expires_in, refresh_token="_", id_token=build_id_token(aud=client_id), ), scope="scope", ), ), ], ) callback = Mock() credential = DeviceCodeCredential( client_id=client_id, prompt_callback=callback, transport=transport, instance_discovery=False, ) now = datetime.datetime.utcnow() token = credential.get_token("scope", tenant_id="tenant_id") assert token.token == expected_token
def test_interactive_credential(mock_open): mock_open.side_effect = _validate_auth_request_url oauth_state = "state" client_id = "client-id" expected_refresh_token = "refresh-token" expected_token = "access-token" expires_in = 3600 authority = "authority" tenant_id = "tenant_id" endpoint = "https://{}/{}".format(authority, tenant_id) discovery_response = get_discovery_response(endpoint=endpoint) transport = validating_transport( requests=[Request(url_substring=endpoint)] * 3 + [ Request( authority=authority, url_substring=endpoint, required_data={"refresh_token": expected_refresh_token} ) ], responses=[ discovery_response, # instance discovery discovery_response, # tenant discovery mock_response( json_payload=build_aad_response( access_token=expected_token, expires_in=expires_in, refresh_token=expected_refresh_token, uid="uid", utid=tenant_id, id_token=build_id_token(aud=client_id, object_id="uid", tenant_id=tenant_id, iss=endpoint), token_type="Bearer", ) ), mock_response( json_payload=build_aad_response(access_token=expected_token, expires_in=expires_in, token_type="Bearer") ), ], ) # mock local server fakes successful authentication by immediately returning a well-formed response auth_code_response = {"code": "authorization-code", "state": [oauth_state]} server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response)) credential = InteractiveBrowserCredential( authority=authority, tenant_id=tenant_id, client_id=client_id, server_class=server_class, transport=transport, instance_discovery=False, validate_authority=False, _cache=TokenCache(), ) # The credential's auth code request includes a uuid which must be included in the redirect. Patching to # set the uuid requires less code here than a proper mock server. with patch("azure.identity._credentials.browser.uuid.uuid4", lambda: oauth_state): token = credential.get_token("scope") assert token.token == expected_token assert mock_open.call_count == 1 # token should be cached, get_token shouldn't prompt again token = credential.get_token("scope") assert token.token == expected_token assert mock_open.call_count == 1 # As of MSAL 1.0.0, applications build a new client every time they redeem a refresh token. # Here we patch the private method they use for the sake of test coverage. # TODO: this will probably break when this MSAL behavior changes app = credential._get_app() app._build_client = lambda *_: app.client # pylint:disable=protected-access now = time.time() # expired access token -> credential should use refresh token instead of prompting again with patch("time.time", lambda: now + expires_in): token = credential.get_token("scope") assert token.token == expected_token assert mock_open.call_count == 1 # ensure all expected requests were sent assert transport.send.call_count == 4
def test_interactive_credential(mock_open, redirect_url): mock_open.side_effect = _validate_auth_request_url oauth_state = "state" client_id = "client-id" expected_refresh_token = "refresh-token" expected_token = "access-token" expires_in = 3600 authority = "authority" tenant_id = "tenant-id" endpoint = "https://{}/{}".format(authority, tenant_id) transport = msal_validating_transport( endpoint="https://{}/{}".format(authority, tenant_id), requests=[Request(url_substring=endpoint)] + [ Request(authority=authority, url_substring=endpoint, required_data={"refresh_token": expected_refresh_token}) ], responses=[ mock_response(json_payload=build_aad_response( access_token=expected_token, expires_in=expires_in, refresh_token=expected_refresh_token, uid="uid", utid=tenant_id, id_token=build_id_token(aud=client_id, object_id="uid", tenant_id=tenant_id, iss=endpoint), token_type="Bearer", )), mock_response( json_payload=build_aad_response(access_token=expected_token, expires_in=expires_in, token_type="Bearer")), ], ) # mock local server fakes successful authentication by immediately returning a well-formed response auth_code_response = {"code": "authorization-code", "state": [oauth_state]} server_class = Mock(return_value=Mock( wait_for_redirect=lambda: auth_code_response)) args = { "authority": authority, "tenant_id": tenant_id, "client_id": client_id, "transport": transport, "_cache": TokenCache(), "_server_class": server_class, } if redirect_url: # avoid passing redirect_url=None args["redirect_uri"] = redirect_url credential = InteractiveBrowserCredential(**args) # The credential's auth code request includes a uuid which must be included in the redirect. Patching to # set the uuid requires less code here than a proper mock server. with patch("azure.identity._credentials.browser.uuid.uuid4", lambda: oauth_state): token = credential.get_token("scope") assert token.token == expected_token assert mock_open.call_count == 1 assert server_class.call_count == 1 if redirect_url: server_class.assert_called_once_with(redirect_url, timeout=ANY) # token should be cached, get_token shouldn't prompt again token = credential.get_token("scope") assert token.token == expected_token assert mock_open.call_count == 1 assert server_class.call_count == 1 # expired access token -> credential should use refresh token instead of prompting again now = time.time() with patch("time.time", lambda: now + expires_in): token = credential.get_token("scope") assert token.token == expected_token assert mock_open.call_count == 1 # ensure all expected requests were sent assert transport.send.call_count == 4