This is an automated email from the ASF dual-hosted git repository.
dabla pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 0e920aed73b fix: Add protocol validation to KiotaRequestAdapterHook
(#61103)
0e920aed73b is described below
commit 0e920aed73b6ada84be3e47dccbcbcd09de256f5
Author: Dhananjay Gupta <[email protected]>
AuthorDate: Thu Jan 29 12:13:28 2026 +0000
fix: Add protocol validation to KiotaRequestAdapterHook (#61103)
* fix: Add protocol validation to KiotaRequestAdapterHook
Fixes #61081
PowerBIDatasetRefreshOperator was failing with 'Request URL missing
protocol'
error when URLs were configured without https:// prefix.
Changes:
- Add _ensure_protocol() method to validate and fix URLs
- Fix logic bug in get_host() (changed 'or' to proper tuple check)
- Add safety fallback when connection.schema is None
- Add comprehensive tests for protocol handling
This ensures URLs always have proper protocol prefix while maintaining
backward compatibility.
* refactor: Address review feedback
- Remove redundant if host else None check in __init__
- Use _ensure_protocol in get_host to stay DRY
- Ensure _ensure_protocol returns None for empty strings
All tests passing.
* fix: Update _ensure_protocol return type to include None
* fix: Update type hints to properly handle None values
* fix: Update type hints to properly handle None values
- Changed _ensure_protocol parameter to accept str | None
- Changed get_host return type to str (never returns None)
- Used cast() for type narrowing where needed
* made the code DRY
---
.../providers/microsoft/azure/hooks/msgraph.py | 24 +++++--
.../unit/microsoft/azure/hooks/test_msgraph.py | 79 ++++++++++++++++++++++
2 files changed, 99 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 376292e1b3c..1fc35d779a2 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
@@ -160,13 +160,29 @@ class KiotaRequestAdapterHook(BaseHook):
self.conn_id = conn_id
self.timeout = timeout
self.proxies = proxies
- self.host = host
+ self.host = self._ensure_protocol(host)
if isinstance(scopes, str):
self.scopes = [scopes]
else:
self.scopes = scopes or [self.DEFAULT_SCOPE]
self.api_version = self.resolve_api_version_from_value(api_version)
+ def _ensure_protocol(self, host: str | None, schema: str = "https") -> str
| None:
+ """Ensure URL has http:// or https:// protocol prefix."""
+ if not host:
+ return None
+
+ if host.startswith(("http://", "https://")):
+ return host
+
+ self.log.warning(
+ "URL '%s' is missing protocol prefix. Automatically adding
'%s://'. "
+ "Please update your connection configuration to include the full
URL with protocol.",
+ host,
+ schema,
+ )
+ return f"{schema}://{host}"
+
@classmethod
def get_connection_form_widgets(cls) -> dict[str, Any]:
"""Return connection widgets to add to connection form."""
@@ -232,9 +248,9 @@ class KiotaRequestAdapterHook(BaseHook):
if connection.schema and connection.host:
return f"{connection.schema}://{connection.host}"
return NationalClouds.Global.value
- if not self.host.startswith("http://") or not
self.host.startswith("https://"):
- return f"{connection.schema}://{self.host}"
- return self.host
+
+ schema = connection.schema or "https"
+ return cast("str", self._ensure_protocol(self.host, schema))
def get_base_url(self, host: str, api_version: str, config: dict) -> str:
base_url = config.get("base_url", urljoin(host, api_version)).strip()
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 6f6216d8fa9..a414663d6f4 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
@@ -433,6 +433,85 @@ class TestKiotaRequestAdapterHook:
mock_redact.assert_any_call("my_secret_password",
name="client_secret")
+class TestKiotaRequestAdapterHookProtocol:
+ """Test protocol handling in KiotaRequestAdapterHook."""
+
+ def test_init_with_https_protocol(self):
+ """Test that URL with https protocol is preserved."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api",
host="https://api.powerbi.com")
+ assert hook.host == "https://api.powerbi.com"
+
+ def test_init_with_http_protocol(self):
+ """Test that URL with http protocol is preserved."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api",
host="http://api.powerbi.com")
+ assert hook.host == "http://api.powerbi.com"
+
+ def test_init_without_protocol(self):
+ """Test that URL without protocol gets https added."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api",
host="api.powerbi.com")
+ assert hook.host == "https://api.powerbi.com"
+
+ def test_init_with_none_host(self):
+ """Test that None host remains None."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api", host=None)
+ assert hook.host is None
+
+ def test_init_with_empty_host(self):
+ """Test that empty string host becomes None."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api", host="")
+ assert hook.host is None
+
+ def test_get_host_with_protocol_in_host_parameter(self):
+ """Test get_host returns self.host when it already has protocol."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api",
host="https://api.powerbi.com")
+ connection = mock_connection(schema="https",
host="graph.microsoft.com")
+ actual = hook.get_host(connection)
+ assert actual == "https://api.powerbi.com"
+
+ def test_get_host_without_host_parameter_uses_connection(self):
+ """Test get_host builds URL from connection when self.host is None."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api", host=None)
+ connection = mock_connection(schema="https",
host="graph.microsoft.com")
+ actual = hook.get_host(connection)
+ assert actual == "https://graph.microsoft.com"
+
+ def test_get_host_fallback_to_default_when_no_connection_info(self):
+ """Test get_host returns default when no host info available."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api", host=None)
+ connection = mock_connection(schema=None, host=None)
+ actual = hook.get_host(connection)
+ assert actual == NationalClouds.Global.value
+
+ def test_get_host_with_none_schema_uses_https_fallback(self):
+ """Test get_host uses https fallback when connection.schema is None
but host exists."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api", host=None)
+ hook.host = "api.powerbi.com"
+ connection = mock_connection(schema=None, host="dummy.com")
+ actual = hook.get_host(connection)
+ assert actual == "https://api.powerbi.com"
+
+ def test_ensure_protocol_warns_when_adding_protocol(self):
+ """Test that _ensure_protocol logs warning when adding protocol."""
+ with patch_hook():
+ hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
+
+ with patch.object(hook.log, "warning") as mock_warning:
+ result = hook._ensure_protocol("api.powerbi.com")
+
+ assert result == "https://api.powerbi.com"
+ mock_warning.assert_called_once()
+ assert "missing protocol prefix" in
mock_warning.call_args[0][0].lower()
+
+
class TestResponseHandler:
def test_default_response_handler_when_json(self):
users = load_json_from_resources(dirname(__file__), "..", "resources",
"users.json")