diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index 94b6d4c8d..c9f7fa122 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -276,7 +276,8 @@ class Executor: encoded_credentials = credentials headers[authorization.config.header] = f"Basic {encoded_credentials}" elif self.auth.config.type == "custom": - headers[authorization.config.header] = authorization.config.api_key or "" + if authorization.config.header and authorization.config.api_key: + headers[authorization.config.header] = authorization.config.api_key # Handle Content-Type for multipart/form-data requests # Fix for issue #23829: Missing boundary when using multipart/form-data diff --git a/api/tests/integration_tests/workflow/nodes/test_http.py b/api/tests/integration_tests/workflow/nodes/test_http.py index 344539d51..f7bb7c460 100644 --- a/api/tests/integration_tests/workflow/nodes/test_http.py +++ b/api/tests/integration_tests/workflow/nodes/test_http.py @@ -160,6 +160,177 @@ def test_custom_authorization_header(setup_http_mock): assert "?A=b" in data assert "X-Header: 123" in data + # Custom authorization header should be set (may be masked) + assert "X-Auth:" in data + + +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) +def test_custom_auth_with_empty_api_key_does_not_set_header(setup_http_mock): + """Test: In custom authentication mode, when the api_key is empty, no header should be set.""" + from core.workflow.entities.variable_pool import VariablePool + from core.workflow.nodes.http_request.entities import ( + HttpRequestNodeAuthorization, + HttpRequestNodeData, + HttpRequestNodeTimeout, + ) + from core.workflow.nodes.http_request.executor import Executor + from core.workflow.system_variable import SystemVariable + + # Create variable pool + variable_pool = VariablePool( + system_variables=SystemVariable(user_id="test", files=[]), + user_inputs={}, + environment_variables=[], + conversation_variables=[], + ) + + # Create node data with custom auth and empty api_key + node_data = HttpRequestNodeData( + title="http", + desc="", + url="http://example.com", + method="get", + authorization=HttpRequestNodeAuthorization( + type="api-key", + config={ + "type": "custom", + "api_key": "", # Empty api_key + "header": "X-Custom-Auth", + }, + ), + headers="", + params="", + body=None, + ssl_verify=True, + ) + + # Create executor + executor = Executor( + node_data=node_data, timeout=HttpRequestNodeTimeout(connect=10, read=30, write=10), variable_pool=variable_pool + ) + + # Get assembled headers + headers = executor._assembling_headers() + + # When api_key is empty, the custom header should NOT be set + assert "X-Custom-Auth" not in headers + + +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) +def test_bearer_authorization_with_custom_header_ignored(setup_http_mock): + """ + Test that when switching from custom to bearer authorization, + the custom header settings don't interfere with bearer token. + This test verifies the fix for issue #23554. + """ + node = init_http_node( + config={ + "id": "1", + "data": { + "title": "http", + "desc": "", + "method": "get", + "url": "http://example.com", + "authorization": { + "type": "api-key", + "config": { + "type": "bearer", + "api_key": "test-token", + "header": "", # Empty header - should default to Authorization + }, + }, + "headers": "", + "params": "", + "body": None, + }, + } + ) + + result = node._run() + assert result.process_data is not None + data = result.process_data.get("request", "") + + # In bearer mode, should use Authorization header (value is masked with *) + assert "Authorization: " in data + # Should contain masked Bearer token + assert "*" in data + + +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) +def test_basic_authorization_with_custom_header_ignored(setup_http_mock): + """ + Test that when switching from custom to basic authorization, + the custom header settings don't interfere with basic auth. + This test verifies the fix for issue #23554. + """ + node = init_http_node( + config={ + "id": "1", + "data": { + "title": "http", + "desc": "", + "method": "get", + "url": "http://example.com", + "authorization": { + "type": "api-key", + "config": { + "type": "basic", + "api_key": "user:pass", + "header": "", # Empty header - should default to Authorization + }, + }, + "headers": "", + "params": "", + "body": None, + }, + } + ) + + result = node._run() + assert result.process_data is not None + data = result.process_data.get("request", "") + + # In basic mode, should use Authorization header (value is masked with *) + assert "Authorization: " in data + # Should contain masked Basic credentials + assert "*" in data + + +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) +def test_custom_authorization_with_empty_api_key(setup_http_mock): + """ + Test that custom authorization doesn't set header when api_key is empty. + This test verifies the fix for issue #23554. + """ + node = init_http_node( + config={ + "id": "1", + "data": { + "title": "http", + "desc": "", + "method": "get", + "url": "http://example.com", + "authorization": { + "type": "api-key", + "config": { + "type": "custom", + "api_key": "", # Empty api_key + "header": "X-Custom-Auth", + }, + }, + "headers": "", + "params": "", + "body": None, + }, + } + ) + + result = node._run() + assert result.process_data is not None + data = result.process_data.get("request", "") + + # Custom header should NOT be set when api_key is empty + assert "X-Custom-Auth:" not in data @pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) @@ -239,6 +410,7 @@ def test_json(setup_http_mock): assert "X-Header: 123" in data +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) def test_x_www_form_urlencoded(setup_http_mock): node = init_http_node( config={ @@ -285,6 +457,7 @@ def test_x_www_form_urlencoded(setup_http_mock): assert "X-Header: 123" in data +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) def test_form_data(setup_http_mock): node = init_http_node( config={ @@ -334,6 +507,7 @@ def test_form_data(setup_http_mock): assert "X-Header: 123" in data +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) def test_none_data(setup_http_mock): node = init_http_node( config={ @@ -366,6 +540,7 @@ def test_none_data(setup_http_mock): assert "123123123" not in data +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) def test_mock_404(setup_http_mock): node = init_http_node( config={ @@ -394,6 +569,7 @@ def test_mock_404(setup_http_mock): assert "Not Found" in resp.get("body", "") +@pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True) def test_multi_colons_parse(setup_http_mock): node = init_http_node( config={