diff --git a/libraries/microsoft-agents-authentication-msal/microsoft/agents/authentication/msal/msal_connection_manager.py b/libraries/microsoft-agents-authentication-msal/microsoft/agents/authentication/msal/msal_connection_manager.py index 591240d6..6b2accb8 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft/agents/authentication/msal/msal_connection_manager.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft/agents/authentication/msal/msal_connection_manager.py @@ -1,5 +1,5 @@ from typing import Dict, List, Optional -from microsoft.agents.hosting.core.authorization import ( +from microsoft.agents.hosting.core import ( AgentAuthConfiguration, AccessTokenProviderBase, ClaimsIdentity, diff --git a/libraries/microsoft-agents-authentication-msal/tests/test_msal_auth.py b/libraries/microsoft-agents-authentication-msal/tests/test_msal_auth.py new file mode 100644 index 00000000..041f49f9 --- /dev/null +++ b/libraries/microsoft-agents-authentication-msal/tests/test_msal_auth.py @@ -0,0 +1,83 @@ +import unittest +from unittest.mock import Mock +import pytest +from msal import ManagedIdentityClient, ConfidentialClientApplication +from microsoft.agents.authentication.msal import MsalAuth +from microsoft.agents.hosting.core.authorization import AgentAuthConfiguration + + +class TestingMsalAuth(MsalAuth): + """ + Mock object for MsalAuth + """ + + def __init__(self, client_type): + super().__init__(AgentAuthConfiguration()) + mock_client = Mock(spec=client_type) + + mock_client.acquire_token_for_client = Mock( + return_value={"access_token": "token"} + ) + mock_client.acquire_token_on_behalf_of = Mock( + return_value={"access_token": "token"} + ) + self.mock_client = mock_client + + self._create_client_application = Mock(return_value=self.mock_client) + + +class TestMsalAuth: + """ + Test suite for testing MsalAuth functionality + """ + + @pytest.mark.asyncio + async def test_get_access_token_managed_identity(self): + mock_auth = TestingMsalAuth(ManagedIdentityClient) + token = await mock_auth.get_access_token( + "https://test.api.botframework.com", scopes=["test-scope"] + ) + + assert token == "token" + mock_auth.mock_client.acquire_token_for_client.assert_called_with( + resource="https://test.api.botframework.com" + ) + + @pytest.mark.asyncio + async def test_get_access_token_confidential(self): + mock_auth = TestingMsalAuth(ConfidentialClientApplication) + token = await mock_auth.get_access_token( + "https://test.api.botframework.com", scopes=["test-scope"] + ) + + assert token == "token" + mock_auth.mock_client.acquire_token_for_client.assert_called_with( + scopes=["test-scope"] + ) + + @pytest.mark.asyncio + async def test_aquire_token_on_behalf_of_managed_identity(self): + mock_auth = TestingMsalAuth(ManagedIdentityClient) + + try: + await mock_auth.aquire_token_on_behalf_of( + scopes=["test-scope"], user_assertion="test-assertion" + ) + except NotImplementedError: + assert True + else: + assert False + + @pytest.mark.asyncio + async def test_aquire_token_on_behalf_of_confidential(self): + mock_auth = TestingMsalAuth(ConfidentialClientApplication) + mock_auth._create_client_application = Mock(return_value=mock_auth.mock_client) + + token = await mock_auth.aquire_token_on_behalf_of( + scopes=["test-scope"], user_assertion="test-assertion" + ) + + assert token == "token" + mock_auth.mock_client.acquire_token_on_behalf_of.assert_called_with( + scopes=["test-scope"], user_assertion="test-assertion" + ) diff --git a/libraries/microsoft-agents-authentication-msal/tests/test_msal_connection_manager.py b/libraries/microsoft-agents-authentication-msal/tests/test_msal_connection_manager.py new file mode 100644 index 00000000..234796df --- /dev/null +++ b/libraries/microsoft-agents-authentication-msal/tests/test_msal_connection_manager.py @@ -0,0 +1,35 @@ +from os import environ +from microsoft.agents.activity import load_configuration_from_env +from microsoft.agents.hosting.core import AuthTypes +from microsoft.agents.authentication.msal import MsalConnectionManager + + +class TestMsalConnectionManager: + """ + Test suite for the Msal Connection Manager + """ + + def test_msal_connection_manager(self): + mock_environ = { + **environ, + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID": "test-tenant-id-SERVICE_CONNECTION", + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID": "test-client-id-SERVICE_CONNECTION", + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET": "test-client-secret-SERVICE_CONNECTION", + "CONNECTIONS__MCS__SETTINGS__TENANTID": "test-tenant-id-MCS", + "CONNECTIONS__MCS__SETTINGS__CLIENTID": "test-client-id-MCS", + "CONNECTIONS__MCS__SETTINGS__CLIENTSECRET": "test-client-secret-MCS", + } + + config = load_configuration_from_env(mock_environ) + connection_manager = MsalConnectionManager(**config) + for key in connection_manager._connections: + auth = connection_manager.get_connection(key)._msal_configuration + assert auth.AUTH_TYPE == AuthTypes.client_secret + assert auth.CLIENT_ID == f"test-client-id-{key}" + assert auth.TENANT_ID == f"test-tenant-id-{key}" + assert auth.CLIENT_SECRET == f"test-client-secret-{key}" + assert auth.ISSUERS == [ + "https://api.botframework.com", + f"https://sts.windows.net/test-tenant-id-{key}/", + f"https://login.microsoftonline.com/test-tenant-id-{key}/v2.0", + ] diff --git a/libraries/microsoft-agents-hosting-core/tests/test_auth_configuration.py b/libraries/microsoft-agents-hosting-core/tests/test_auth_configuration.py new file mode 100644 index 00000000..1390b66d --- /dev/null +++ b/libraries/microsoft-agents-hosting-core/tests/test_auth_configuration.py @@ -0,0 +1,79 @@ +from os import environ +from typing import Dict +from microsoft.agents.activity import load_configuration_from_env +from microsoft.agents.hosting.core import AgentAuthConfiguration, AuthTypes + + +class TestAuthorizationConfiguration: + """ + Unit tests to validate Authorization Configuration cases + """ + + def test_auth_configuration_basic(self): + # test AgentAuthConfiguration with manual insertion of fields + auth_config = AgentAuthConfiguration( + auth_type=AuthTypes.client_secret, + tenant_id="test-tenant-id", + client_id="test-client-id", + client_secret="test-client-secret", + cert_pem_file="test-cert.pem", + cert_key_file="test-cert.key", + connection_name="test-connection", + authority="https://login.microsoftonline.com", + scopes=["test-scope-1", "test-scope-2"], + ) + + assert auth_config.AUTH_TYPE == AuthTypes.client_secret + assert auth_config.TENANT_ID == "test-tenant-id" + assert auth_config.CLIENT_ID == "test-client-id" + assert auth_config.CLIENT_SECRET == "test-client-secret" + assert auth_config.CERT_PEM_FILE == "test-cert.pem" + assert auth_config.CERT_KEY_FILE == "test-cert.key" + assert auth_config.CONNECTION_NAME == "test-connection" + assert auth_config.AUTHORITY == "https://login.microsoftonline.com" + assert auth_config.SCOPES == ["test-scope-1", "test-scope-2"] + assert auth_config.ISSUERS == [ + "https://api.botframework.com", + f"https://sts.windows.net/test-tenant-id/", + f"https://login.microsoftonline.com/test-tenant-id/v2.0", + ] + + def test_load_configuration_from_env(self): + # test load_configuration_from_env, passed to AgentAuthConfiguration + mock_environ = { + **environ, + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID": "test-tenant-id-SERVICE_CONNECTION", + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID": "test-client-id-SERVICE_CONNECTION", + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET": "test-client-secret-SERVICE_CONNECTION", + "CONNECTIONS__MCS__SETTINGS__TENANTID": "test-tenant-id-MCS", + "CONNECTIONS__MCS__SETTINGS__CLIENTID": "test-client-id-MCS", + "CONNECTIONS__MCS__SETTINGS__CLIENTSECRET": "test-client-secret-MCS", + } + + mock_config = load_configuration_from_env(mock_environ) + + raw_configurations: Dict[str, Dict] = mock_config.get("CONNECTIONS", {}) + + for name, settings in raw_configurations.items(): + auth_config = AgentAuthConfiguration(**settings["SETTINGS"]) + assert auth_config.AUTH_TYPE == AuthTypes.client_secret + assert auth_config.CLIENT_ID == f"test-client-id-{name}" + assert auth_config.TENANT_ID == f"test-tenant-id-{name}" + assert auth_config.CLIENT_SECRET == f"test-client-secret-{name}" + assert auth_config.ISSUERS == [ + "https://api.botframework.com", + f"https://sts.windows.net/test-tenant-id-{name}/", + f"https://login.microsoftonline.com/test-tenant-id-{name}/v2.0", + ] + + def test_empty_settings(self): + auth_config = AgentAuthConfiguration() + assert auth_config.AUTH_TYPE == AuthTypes.client_secret + assert auth_config.TENANT_ID == None + assert auth_config.CLIENT_ID == None + assert auth_config.CLIENT_SECRET == None + assert auth_config.CERT_PEM_FILE == None + assert auth_config.CERT_KEY_FILE == None + assert auth_config.CONNECTION_NAME == None + assert auth_config.AUTHORITY == None + assert auth_config.SCOPES == None diff --git a/libraries/microsoft-agents-hosting-core/tests/test_authorization.py b/libraries/microsoft-agents-hosting-core/tests/test_authorization.py new file mode 100644 index 00000000..e7ee8b69 --- /dev/null +++ b/libraries/microsoft-agents-hosting-core/tests/test_authorization.py @@ -0,0 +1,213 @@ +import pytest +from .tools.testing_authorization import ( + TestingAuthorization, + create_test_auth_handler, +) +from .tools.testing_utility import TestingUtility +import jwt +from unittest.mock import Mock, AsyncMock +from microsoft.agents.hosting.core import SignInState +from microsoft.agents.hosting.core.oauth_flow import FlowState + + +class TestAuthorization: + def setup_method(self): + self.turn_context = TestingUtility.create_empty_context() + + @pytest.mark.asyncio + async def test_get_token_single_handler(self): + """ + Test Authorization - get_token() with single Auth Handler + """ + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth-a"), + } + ) + + token_res = await auth.get_token(self.turn_context) + auth_handler = auth.resolver_handler("auth-handler") + assert token_res.connection_name == auth_handler.abs_oauth_connection_name + assert token_res.token == f"{auth_handler.abs_oauth_connection_name}-token" + + @pytest.mark.asyncio + async def test_get_token_multiple_handlers(self): + """ + Test Authorization - get_token() with multiple Auth Handlers + """ + auth_handlers = { + "auth-handler": create_test_auth_handler("test-auth-a"), + "auth-handler-obo": create_test_auth_handler("test-auth-b", obo=True), + "auth-handler-with-title": create_test_auth_handler( + "test-auth-c", title="test-title" + ), + "auth-handler-with-title-text": create_test_auth_handler( + "test-auth-d", title="test-title", text="test-text" + ), + } + auth = TestingAuthorization(auth_handlers=auth_handlers) + for id, auth_handler in auth_handlers.items(): + # test value propogation + token_res = await auth.get_token(self.turn_context, id) + assert token_res.connection_name == auth_handler.abs_oauth_connection_name + assert token_res.token == f"{auth_handler.abs_oauth_connection_name}-token" + + @pytest.mark.asyncio + async def test_exchange_token_valid_token(self): + valid_token = jwt.encode({"aud": "api://botframework.test.api"}, "") + scopes = ["scope-a"] + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth", obo=True), + }, + token=valid_token, + ) + token_res = await auth.exchange_token(self.turn_context, scopes=scopes) + assert ( + token_res.token + == f"{auth.resolver_handler().obo_connection_name}-obo-token" + ) + + @pytest.mark.asyncio + async def test_exchange_token_invalid_token(self): + invalid_token = jwt.encode({"aud": "invalid://botframework.test.api"}, "") + scopes = ["scope-a"] + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth"), + }, + token=invalid_token, + ) + token_res = await auth.exchange_token(self.turn_context, scopes=scopes) + assert token_res.token == invalid_token + + @pytest.mark.asyncio + async def test_get_flow_state_unavailable(self): + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth-a"), + } + ) + + assert auth.get_flow_state() == FlowState() + + @pytest.mark.asyncio + async def test_begin_or_continue_flow_not_started(self): + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth-a"), + }, + token=None, + ) + mock_turn_state = AsyncMock(get_value=Mock(return_value=SignInState())) + + token_res = await auth.begin_or_continue_flow( + self.turn_context, + mock_turn_state, + "auth-handler", + ) + # Test value propogation + auth_handler = auth.resolver_handler("auth-handler") + assert token_res.connection_name == auth_handler.abs_oauth_connection_name + assert token_res.token == f"{auth_handler.abs_oauth_connection_name}-token" + + # Test function calls + auth_handler.flow._get_flow_state.assert_called_once() + auth_handler.flow.begin_flow.assert_called_once() + mock_turn_state.save.assert_called_once_with(self.turn_context) + mock_turn_state.set_value.assert_called_once_with( + auth.SIGN_IN_STATE_KEY, + SignInState( + continuation_activity=self.turn_context.activity, + handler_id="auth-handler", + ), + ) + + @pytest.mark.asyncio + async def test_begin_or_continue_flow_started(self): + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth-a"), + }, + token=None, + flow_started=True, + ) + mock_turn_state = AsyncMock(get_value=Mock(return_value=SignInState())) + token_res = await auth.begin_or_continue_flow( + self.turn_context, + mock_turn_state, + "auth-handler", + ) + + # Test value propogation + auth_handler = auth.resolver_handler("auth-handler") + assert token_res.connection_name == auth_handler.abs_oauth_connection_name + assert token_res.token == f"{auth_handler.abs_oauth_connection_name}-token" + + # Test function calls + auth_handler.flow._get_flow_state.assert_called_once() + auth_handler.flow.continue_flow.assert_called_once() + mock_turn_state.save.assert_called_once_with(self.turn_context) + mock_turn_state.delete_value.assert_called_once_with(auth.SIGN_IN_STATE_KEY) + + @pytest.mark.asyncio + async def test_begin_or_continue_flow_started_sign_in_success(self): + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth-a"), + }, + token=None, + flow_started=True, + ) + mock_turn_state = AsyncMock(get_value=Mock(return_value=SignInState())) + auth.on_sign_in_success(AsyncMock()) + + token_res = await auth.begin_or_continue_flow( + self.turn_context, + mock_turn_state, + "auth-handler", + ) + + # Test value propogation + auth_handler = auth.resolver_handler("auth-handler") + assert token_res.connection_name == auth_handler.abs_oauth_connection_name + assert token_res.token == f"{auth_handler.abs_oauth_connection_name}-token" + + # Test function calls + auth_handler.flow._get_flow_state.assert_called_once() + auth_handler.flow.continue_flow.assert_called_once() + mock_turn_state.save.assert_called_once_with(self.turn_context) + mock_turn_state.delete_value.assert_called_once_with(auth.SIGN_IN_STATE_KEY) + auth._sign_in_handler.assert_called_once_with( + self.turn_context, mock_turn_state, "auth-handler" + ) + + @pytest.mark.asyncio + async def test_begin_or_continue_flow_started_sign_in_failure(self): + auth = TestingAuthorization( + auth_handlers={ + "auth-handler": create_test_auth_handler("test-auth-a"), + }, + token=None, + sign_in_failed=True, + ) + mock_turn_state = AsyncMock(get_value=Mock(return_value=SignInState())) + auth.on_sign_in_failure(AsyncMock()) + + token_res = await auth.begin_or_continue_flow( + self.turn_context, + mock_turn_state, + "auth-handler", + ) + + # Test value propogation + auth_handler = auth.resolver_handler("auth-handler") + assert not token_res + + # Test function calls + auth_handler.flow._get_flow_state.assert_called_once() + auth_handler.flow.continue_flow.assert_called_once() + mock_turn_state.save.assert_called_once_with(self.turn_context) + auth._sign_in_failed_handler.assert_called_once_with( + self.turn_context, mock_turn_state, "auth-handler" + ) diff --git a/libraries/microsoft-agents-hosting-core/tests/test_turn_context.py b/libraries/microsoft-agents-hosting-core/tests/test_turn_context.py index 073a61d8..dc3c7920 100644 --- a/libraries/microsoft-agents-hosting-core/tests/test_turn_context.py +++ b/libraries/microsoft-agents-hosting-core/tests/test_turn_context.py @@ -7,7 +7,6 @@ ChannelAccount, ConversationAccount, Entity, - Mention, ResourceResponse, ) from microsoft.agents.hosting.core import ChannelAdapter, MessageFactory, TurnContext diff --git a/libraries/microsoft-agents-hosting-core/tests/tools/testing_authorization.py b/libraries/microsoft-agents-hosting-core/tests/tools/testing_authorization.py new file mode 100644 index 00000000..ac90b6c6 --- /dev/null +++ b/libraries/microsoft-agents-hosting-core/tests/tools/testing_authorization.py @@ -0,0 +1,247 @@ +""" +Testing utilities for authorization functionality + +This module provides mock implementations and helper classes for testing authorization, +authentication, and token management scenarios. It includes test doubles for token +providers, connection managers, and authorization handlers that can be configured +to simulate various authentication states and flow conditions. +""" + +from microsoft.agents.hosting.core import ( + Connections, + AccessTokenProviderBase, + AuthHandler, + Authorization, + MemoryStorage, + oauth_flow, +) +from typing import Dict, Union +from microsoft.agents.hosting.core.authorization.agent_auth_configuration import ( + AgentAuthConfiguration, +) +from microsoft.agents.hosting.core.authorization.claims_identity import ClaimsIdentity + +from microsoft.agents.activity import TokenResponse + +from unittest.mock import Mock, AsyncMock + + +def create_test_auth_handler( + name: str, obo: bool = False, title: str = None, text: str = None +): + """ + Creates a test AuthHandler instance with standardized connection names. + + This helper function simplifies the creation of AuthHandler objects for testing + by automatically generating connection names based on the provided name and + optionally including On-Behalf-Of (OBO) connection configuration. + + Args: + name: Base name for the auth handler, used to generate connection names + obo: Whether to include On-Behalf-Of connection configuration + title: Optional title for the auth handler + text: Optional descriptive text for the auth handler + + Returns: + AuthHandler: Configured auth handler instance with test-friendly connection names + """ + return AuthHandler( + name, + abs_oauth_connection_name=f"{name}-abs-connection", + obo_connection_name=f"{name}-obo-connection" if obo else None, + title=title, + text=text, + ) + + +class TestingTokenProvider(AccessTokenProviderBase): + """ + Access token provider for unit tests. + + This test double simulates an access token provider that returns predictable + token values based on the provider name. It implements both standard token + acquisition and On-Behalf-Of (OBO) token flows for comprehensive testing + of authentication scenarios. + """ + + def __init__(self, name: str): + """ + Initialize the testing token provider. + + Args: + name: Identifier used to generate predictable token values + """ + self.name = name + + async def get_access_token( + self, resource_url: str, scopes: list[str], force_refresh: bool = False + ) -> str: + """ + Get an access token for the specified resource and scopes. + + Returns a predictable token string based on the provider name for testing. + + Args: (unused in test implementation) + resource_url: URL of the resource requiring authentication + scopes: List of OAuth scopes requested + force_refresh: Whether to force token refresh + + Returns: + str: Test token in format "{name}-token" + """ + return f"{self.name}-token" + + async def aquire_token_on_behalf_of( + self, scopes: list[str], user_assertion: str + ) -> str: + """ + Acquire a token on behalf of another user (OBO flow). + + Returns a predictable OBO token string for testing scenarios involving + delegated permissions and token exchange. + + Args: (unused in test implementation) + scopes: List of OAuth scopes requested for the OBO token + user_assertion: JWT token representing the user's identity + + Returns: + str: Test OBO token in format "{name}-obo-token" + """ + return f"{self.name}-obo-token" + + +class TestingConnectionManager(Connections): + """ + Connection manager for unit tests. + + This test double provides a simplified connection management interface that + returns TestingTokenProvider instances for all connection requests. It enables + testing of authorization flows without requiring actual OAuth configurations + or external authentication services. + """ + + def get_connection(self, connection_name: str) -> AccessTokenProviderBase: + """ + Get a token provider for the specified connection name. + + Args: + connection_name: Name of the OAuth connection + + Returns: + AccessTokenProviderBase: TestingTokenProvider configured with the connection name + """ + return TestingTokenProvider(connection_name) + + def get_default_connection(self) -> AccessTokenProviderBase: + """ + Get the default token provider. + + Returns: + AccessTokenProviderBase: TestingTokenProvider configured with "default" name + """ + return TestingTokenProvider("default") + + def get_token_provider( + self, claims_identity: ClaimsIdentity, service_url: str + ) -> AccessTokenProviderBase: + """ + Get a token provider based on claims identity and service URL. + + In this test implementation, returns the default connection regardless + of the provided parameters. + + Args: (unused in test implementation) + claims_identity: User's claims and identity information + service_url: URL of the service requiring authentication + + Returns: + AccessTokenProviderBase: The default TestingTokenProvider + """ + return self.get_default_connection() + + def get_default_connection_configuration(self) -> AgentAuthConfiguration: + """ + Get the default authentication configuration. + + Returns: + AgentAuthConfiguration: Empty configuration suitable for testing + """ + return AgentAuthConfiguration() + + +class TestingAuthorization(Authorization): + """ + Authorization system for comprehensive unit testing. + + This test double extends the Authorization class to provide a fully mocked + authorization environment suitable for testing various authentication scenarios. + It automatically configures auth handlers with mock OAuth flows that can simulate + different states like successful authentication, failed sign-in, or in-progress flows. + """ + + def __init__( + self, + auth_handlers: Dict[str, AuthHandler], + token: Union[str, None] = "default", + flow_started=False, + sign_in_failed=False, + ): + """ + Initialize the testing authorization system. + + Sets up a complete test authorization environment with memory storage, + test connection manager, and configures all provided auth handlers with + mock OAuth flows. + + Args: + auth_handlers: Dictionary mapping handler names to AuthHandler instances + token: Token value to use in mock responses. "default" uses auto-generated + tokens, None simulates no token available, or provide custom jwt token string + flow_started: Simulate OAuth flows that have already started + sign_in_failed: Simulate failed sign-in attempts + """ + # Initialize with test-friendly components + storage = MemoryStorage() + connection_manager = TestingConnectionManager() + super().__init__( + storage=storage, + auth_handlers=auth_handlers, + connection_manager=connection_manager, + ) + + # Configure each auth handler with mock OAuth flow behavior + for auth_handler in self._auth_handlers.values(): + # Create default token response for this auth handler + default_token = TokenResponse( + connection_name=auth_handler.abs_oauth_connection_name, + token=f"{auth_handler.abs_oauth_connection_name}-token", + ) + + # Determine token response based on configuration + if token == "default": + token_response = default_token + elif token: + token_response = TokenResponse( + connection_name=auth_handler.abs_oauth_connection_name, + token=token, + ) + else: + token_response = None + + # Mock the OAuth flow with configurable behavior + auth_handler.flow = Mock( + get_user_token=AsyncMock(return_value=token_response), + _get_flow_state=AsyncMock( + # sign-in failed requires flow to be started + return_value=oauth_flow.FlowState( + flow_started=(flow_started or sign_in_failed) + ) + ), + begin_flow=AsyncMock(return_value=default_token), + # Mock flow continuation with optional failure simulation + continue_flow=AsyncMock( + return_value=None if sign_in_failed else default_token + ), + ) + + auth_handler.flow.flow_state = None