From 7e44325e085a2cf40e3c4441495abee002845f2e Mon Sep 17 00:00:00 2001 From: davidnzhang <130720014+davidnzhang@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:00:59 +1000 Subject: [PATCH 1/3] Fix msgraph/Power BI auth failure from empty allowed_hosts list --- .../providers/microsoft/azure/hooks/msgraph.py | 2 +- .../unit/microsoft/azure/hooks/test_msgraph.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py index 2f3bf3a40308c..f7ba09fe1d3e2 100644 --- a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py +++ b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py @@ -311,7 +311,7 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: scopes = scopes.split(",") verify = config.get("verify", True) trust_env = config.get("trust_env", False) - allowed_hosts = (config.get("allowed_hosts", authority) or "").split(",") + allowed_hosts = [host for host in (config.get("allowed_hosts", authority) or "").split(",") if host] self.log.info( "Creating Microsoft Graph SDK client %s for conn_id: %s", diff --git a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py index dab96656d43b4..bcee302360811 100644 --- a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py +++ b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py @@ -570,6 +570,19 @@ async def test_send_request_invalidates_cache_and_raises_on_any_error(self): adapter.send_no_response_content_async.assert_called_once() assert hook.conn_id not in hook.cached_request_adapters + @pytest.mark.asyncio + async def test_allowed_hosts_is_empty_list_when_not_configured(self): + """an unset allowed_hosts/authority must yield [].""" + KiotaRequestAdapterHook.cached_request_adapters.clear() + with patch_hook(): + with patch( + "airflow.providers.microsoft.azure.hooks.msgraph.AzureIdentityAuthenticationProvider" + ) as mock_auth_provider: + hook = KiotaRequestAdapterHook(conn_id="msgraph_api") + await hook.get_async_conn() + + assert mock_auth_provider.call_args.kwargs["allowed_hosts"] == [] + class TestKiotaRequestAdapterHookProtocol: """Test protocol handling in KiotaRequestAdapterHook.""" From 94391dba4fcc2273fc2f327576f390b2d4d7f2ea Mon Sep 17 00:00:00 2001 From: davidnzhang <130720014+davidnzhang@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:25:13 +1000 Subject: [PATCH 2/3] Refactor get_allowed_hosts into a static method with direct unit tests --- .../microsoft/azure/hooks/msgraph.py | 9 +++++++- .../microsoft/azure/hooks/test_msgraph.py | 23 ++++++++++--------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py index f7ba09fe1d3e2..c849cabbec1b8 100644 --- a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py +++ b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py @@ -294,6 +294,13 @@ def to_msal_proxies(self, authority: str | None, proxies: dict | None) -> dict | return proxies return None + @staticmethod + def get_allowed_hosts(authority: str, config: dict) -> list[str]: + allowed_hosts = config.get("allowed_hosts", authority) + if not allowed_hosts: + return [] + return allowed_hosts.split(",") + def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: client_id = connection.login client_secret = connection.password @@ -311,7 +318,7 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: scopes = scopes.split(",") verify = config.get("verify", True) trust_env = config.get("trust_env", False) - allowed_hosts = [host for host in (config.get("allowed_hosts", authority) or "").split(",") if host] + allowed_hosts = self.get_allowed_hosts(authority, config) self.log.info( "Creating Microsoft Graph SDK client %s for conn_id: %s", diff --git a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py index bcee302360811..d92b8552ebd9d 100644 --- a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py +++ b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py @@ -570,18 +570,19 @@ async def test_send_request_invalidates_cache_and_raises_on_any_error(self): adapter.send_no_response_content_async.assert_called_once() assert hook.conn_id not in hook.cached_request_adapters - @pytest.mark.asyncio - async def test_allowed_hosts_is_empty_list_when_not_configured(self): - """an unset allowed_hosts/authority must yield [].""" - KiotaRequestAdapterHook.cached_request_adapters.clear() - with patch_hook(): - with patch( - "airflow.providers.microsoft.azure.hooks.msgraph.AzureIdentityAuthenticationProvider" - ) as mock_auth_provider: - hook = KiotaRequestAdapterHook(conn_id="msgraph_api") - await hook.get_async_conn() + def test_allowed_hosts_is_empty_list_when_not_configured(self): + """An unset allowed_hosts/authority must yield [].""" + actual = KiotaRequestAdapterHook.get_allowed_hosts(None, {}) + + assert actual == [] + + def test_allowed_hosts_from_config(self): + """A configured allowed_hosts string must be split into a list.""" + actual = KiotaRequestAdapterHook.get_allowed_hosts( + None, {"allowed_hosts": "api.powerbi.com,login.microsoftonline.com"} + ) - assert mock_auth_provider.call_args.kwargs["allowed_hosts"] == [] + assert actual == ["api.powerbi.com", "login.microsoftonline.com"] class TestKiotaRequestAdapterHookProtocol: From 6a9df5a8d06f9f458649a3016a75ae3fdbcc83e0 Mon Sep 17 00:00:00 2001 From: davidnzhang <130720014+davidnzhang@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:29:22 +1000 Subject: [PATCH 3/3] Filter blank entries from parsed allowed_hosts --- .../src/airflow/providers/microsoft/azure/hooks/msgraph.py | 4 ++-- .../azure/tests/unit/microsoft/azure/hooks/test_msgraph.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py index c849cabbec1b8..b7c089feeb94f 100644 --- a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py +++ b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py @@ -295,11 +295,11 @@ def to_msal_proxies(self, authority: str | None, proxies: dict | None) -> dict | return None @staticmethod - def get_allowed_hosts(authority: str, config: dict) -> list[str]: + def get_allowed_hosts(authority: str | None, config: dict) -> list[str]: allowed_hosts = config.get("allowed_hosts", authority) if not allowed_hosts: return [] - return allowed_hosts.split(",") + return [host for host in allowed_hosts.split(",") if host] def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: client_id = connection.login diff --git a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py index d92b8552ebd9d..e1b2caf5b32b1 100644 --- a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py +++ b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py @@ -571,13 +571,13 @@ async def test_send_request_invalidates_cache_and_raises_on_any_error(self): assert hook.conn_id not in hook.cached_request_adapters def test_allowed_hosts_is_empty_list_when_not_configured(self): - """An unset allowed_hosts/authority must yield [].""" + """An unset allowed_hosts/authority must yield [].""" actual = KiotaRequestAdapterHook.get_allowed_hosts(None, {}) assert actual == [] def test_allowed_hosts_from_config(self): - """A configured allowed_hosts string must be split into a list.""" + """A configured allowed_hosts string must be split into a list.""" actual = KiotaRequestAdapterHook.get_allowed_hosts( None, {"allowed_hosts": "api.powerbi.com,login.microsoftonline.com"} )