mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 02:39:05 +08:00
fix: http authorization leakage (#4146)
This commit is contained in:
parent
e353809680
commit
d51f52a649
@ -1,9 +1,13 @@
|
|||||||
|
import os
|
||||||
from typing import Literal, Optional, Union
|
from typing import Literal, Optional, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
||||||
|
|
||||||
|
MAX_CONNECT_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_CONNECT_TIMEOUT', '300'))
|
||||||
|
MAX_READ_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_READ_TIMEOUT', '600'))
|
||||||
|
MAX_WRITE_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_WRITE_TIMEOUT', '600'))
|
||||||
|
|
||||||
class HttpRequestNodeData(BaseNodeData):
|
class HttpRequestNodeData(BaseNodeData):
|
||||||
"""
|
"""
|
||||||
@ -36,9 +40,9 @@ class HttpRequestNodeData(BaseNodeData):
|
|||||||
data: Union[None, str]
|
data: Union[None, str]
|
||||||
|
|
||||||
class Timeout(BaseModel):
|
class Timeout(BaseModel):
|
||||||
connect: int
|
connect: int = MAX_CONNECT_TIMEOUT
|
||||||
read: int
|
read: int = MAX_READ_TIMEOUT
|
||||||
write: int
|
write: int = MAX_WRITE_TIMEOUT
|
||||||
|
|
||||||
method: Literal['get', 'post', 'put', 'patch', 'delete', 'head']
|
method: Literal['get', 'post', 'put', 'patch', 'delete', 'head']
|
||||||
url: str
|
url: str
|
||||||
@ -46,4 +50,5 @@ class HttpRequestNodeData(BaseNodeData):
|
|||||||
headers: str
|
headers: str
|
||||||
params: str
|
params: str
|
||||||
body: Optional[Body]
|
body: Optional[Body]
|
||||||
timeout: Optional[Timeout]
|
timeout: Optional[Timeout]
|
||||||
|
mask_authorization_header: Optional[bool] = True
|
||||||
|
@ -19,7 +19,6 @@ READABLE_MAX_BINARY_SIZE = f'{MAX_BINARY_SIZE / 1024 / 1024:.2f}MB'
|
|||||||
MAX_TEXT_SIZE = int(os.environ.get('HTTP_REQUEST_NODE_MAX_TEXT_SIZE', str(1024 * 1024))) # 10MB # 1MB
|
MAX_TEXT_SIZE = int(os.environ.get('HTTP_REQUEST_NODE_MAX_TEXT_SIZE', str(1024 * 1024))) # 10MB # 1MB
|
||||||
READABLE_MAX_TEXT_SIZE = f'{MAX_TEXT_SIZE / 1024 / 1024:.2f}MB'
|
READABLE_MAX_TEXT_SIZE = f'{MAX_TEXT_SIZE / 1024 / 1024:.2f}MB'
|
||||||
|
|
||||||
|
|
||||||
class HttpExecutorResponse:
|
class HttpExecutorResponse:
|
||||||
headers: dict[str, str]
|
headers: dict[str, str]
|
||||||
response: Union[httpx.Response, requests.Response]
|
response: Union[httpx.Response, requests.Response]
|
||||||
@ -345,10 +344,13 @@ class HttpExecutor:
|
|||||||
# validate response
|
# validate response
|
||||||
return self._validate_and_parse_response(response)
|
return self._validate_and_parse_response(response)
|
||||||
|
|
||||||
def to_raw_request(self) -> str:
|
def to_raw_request(self, mask_authorization_header: Optional[bool] = True) -> str:
|
||||||
"""
|
"""
|
||||||
convert to raw request
|
convert to raw request
|
||||||
"""
|
"""
|
||||||
|
if mask_authorization_header == None:
|
||||||
|
mask_authorization_header = True
|
||||||
|
|
||||||
server_url = self.server_url
|
server_url = self.server_url
|
||||||
if self.params:
|
if self.params:
|
||||||
server_url += f'?{urlencode(self.params)}'
|
server_url += f'?{urlencode(self.params)}'
|
||||||
@ -357,6 +359,17 @@ class HttpExecutor:
|
|||||||
|
|
||||||
headers = self._assembling_headers()
|
headers = self._assembling_headers()
|
||||||
for k, v in headers.items():
|
for k, v in headers.items():
|
||||||
|
if mask_authorization_header:
|
||||||
|
# get authorization header
|
||||||
|
if self.authorization.type == 'api-key':
|
||||||
|
authorization_header = 'Authorization'
|
||||||
|
if self.authorization.config and self.authorization.config.header:
|
||||||
|
authorization_header = self.authorization.config.header
|
||||||
|
|
||||||
|
if k.lower() == authorization_header.lower():
|
||||||
|
raw_request += f'{k}: {"*" * len(v)}\n'
|
||||||
|
continue
|
||||||
|
|
||||||
raw_request += f'{k}: {v}\n'
|
raw_request += f'{k}: {v}\n'
|
||||||
|
|
||||||
raw_request += '\n'
|
raw_request += '\n'
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
from mimetypes import guess_extension
|
from mimetypes import guess_extension
|
||||||
from os import path
|
from os import path
|
||||||
from typing import cast
|
from typing import cast
|
||||||
@ -9,14 +8,15 @@ from core.tools.tool_file_manager import ToolFileManager
|
|||||||
from core.workflow.entities.node_entities import NodeRunResult, NodeType
|
from core.workflow.entities.node_entities import NodeRunResult, NodeType
|
||||||
from core.workflow.entities.variable_pool import VariablePool
|
from core.workflow.entities.variable_pool import VariablePool
|
||||||
from core.workflow.nodes.base_node import BaseNode
|
from core.workflow.nodes.base_node import BaseNode
|
||||||
from core.workflow.nodes.http_request.entities import HttpRequestNodeData
|
from core.workflow.nodes.http_request.entities import (
|
||||||
|
MAX_CONNECT_TIMEOUT,
|
||||||
|
MAX_READ_TIMEOUT,
|
||||||
|
MAX_WRITE_TIMEOUT,
|
||||||
|
HttpRequestNodeData,
|
||||||
|
)
|
||||||
from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse
|
from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse
|
||||||
from models.workflow import WorkflowNodeExecutionStatus
|
from models.workflow import WorkflowNodeExecutionStatus
|
||||||
|
|
||||||
MAX_CONNECT_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_CONNECT_TIMEOUT', '300'))
|
|
||||||
MAX_READ_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_READ_TIMEOUT', '600'))
|
|
||||||
MAX_WRITE_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_WRITE_TIMEOUT', '600'))
|
|
||||||
|
|
||||||
HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeData.Timeout(connect=min(10, MAX_CONNECT_TIMEOUT),
|
HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeData.Timeout(connect=min(10, MAX_CONNECT_TIMEOUT),
|
||||||
read=min(60, MAX_READ_TIMEOUT),
|
read=min(60, MAX_READ_TIMEOUT),
|
||||||
write=min(20, MAX_WRITE_TIMEOUT))
|
write=min(20, MAX_WRITE_TIMEOUT))
|
||||||
@ -63,7 +63,9 @@ class HttpRequestNode(BaseNode):
|
|||||||
process_data = {}
|
process_data = {}
|
||||||
if http_executor:
|
if http_executor:
|
||||||
process_data = {
|
process_data = {
|
||||||
'request': http_executor.to_raw_request(),
|
'request': http_executor.to_raw_request(
|
||||||
|
mask_authorization_header=node_data.mask_authorization_header
|
||||||
|
),
|
||||||
}
|
}
|
||||||
return NodeRunResult(
|
return NodeRunResult(
|
||||||
status=WorkflowNodeExecutionStatus.FAILED,
|
status=WorkflowNodeExecutionStatus.FAILED,
|
||||||
@ -82,7 +84,9 @@ class HttpRequestNode(BaseNode):
|
|||||||
'files': files,
|
'files': files,
|
||||||
},
|
},
|
||||||
process_data={
|
process_data={
|
||||||
'request': http_executor.to_raw_request(),
|
'request': http_executor.to_raw_request(
|
||||||
|
mask_authorization_header=node_data.mask_authorization_header
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ def test_get(setup_http_mock):
|
|||||||
'headers': 'X-Header:123',
|
'headers': 'X-Header:123',
|
||||||
'params': 'A:b',
|
'params': 'A:b',
|
||||||
'body': None,
|
'body': None,
|
||||||
|
'mask_authorization_header': False,
|
||||||
}
|
}
|
||||||
}, **BASIC_NODE_DATA)
|
}, **BASIC_NODE_DATA)
|
||||||
|
|
||||||
@ -95,6 +96,7 @@ def test_custom_authorization_header(setup_http_mock):
|
|||||||
'headers': 'X-Header:123',
|
'headers': 'X-Header:123',
|
||||||
'params': 'A:b',
|
'params': 'A:b',
|
||||||
'body': None,
|
'body': None,
|
||||||
|
'mask_authorization_header': False,
|
||||||
}
|
}
|
||||||
}, **BASIC_NODE_DATA)
|
}, **BASIC_NODE_DATA)
|
||||||
|
|
||||||
@ -126,6 +128,7 @@ def test_template(setup_http_mock):
|
|||||||
'headers': 'X-Header:123\nX-Header2:{{#a.b123.args2#}}',
|
'headers': 'X-Header:123\nX-Header2:{{#a.b123.args2#}}',
|
||||||
'params': 'A:b\nTemplate:{{#a.b123.args2#}}',
|
'params': 'A:b\nTemplate:{{#a.b123.args2#}}',
|
||||||
'body': None,
|
'body': None,
|
||||||
|
'mask_authorization_header': False,
|
||||||
}
|
}
|
||||||
}, **BASIC_NODE_DATA)
|
}, **BASIC_NODE_DATA)
|
||||||
|
|
||||||
@ -161,6 +164,7 @@ def test_json(setup_http_mock):
|
|||||||
'type': 'json',
|
'type': 'json',
|
||||||
'data': '{"a": "{{#a.b123.args1#}}"}'
|
'data': '{"a": "{{#a.b123.args1#}}"}'
|
||||||
},
|
},
|
||||||
|
'mask_authorization_header': False,
|
||||||
}
|
}
|
||||||
}, **BASIC_NODE_DATA)
|
}, **BASIC_NODE_DATA)
|
||||||
|
|
||||||
@ -193,6 +197,7 @@ def test_x_www_form_urlencoded(setup_http_mock):
|
|||||||
'type': 'x-www-form-urlencoded',
|
'type': 'x-www-form-urlencoded',
|
||||||
'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}'
|
'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}'
|
||||||
},
|
},
|
||||||
|
'mask_authorization_header': False,
|
||||||
}
|
}
|
||||||
}, **BASIC_NODE_DATA)
|
}, **BASIC_NODE_DATA)
|
||||||
|
|
||||||
@ -225,6 +230,7 @@ def test_form_data(setup_http_mock):
|
|||||||
'type': 'form-data',
|
'type': 'form-data',
|
||||||
'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}'
|
'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}'
|
||||||
},
|
},
|
||||||
|
'mask_authorization_header': False,
|
||||||
}
|
}
|
||||||
}, **BASIC_NODE_DATA)
|
}, **BASIC_NODE_DATA)
|
||||||
|
|
||||||
@ -260,6 +266,7 @@ def test_none_data(setup_http_mock):
|
|||||||
'type': 'none',
|
'type': 'none',
|
||||||
'data': '123123123'
|
'data': '123123123'
|
||||||
},
|
},
|
||||||
|
'mask_authorization_header': False,
|
||||||
}
|
}
|
||||||
}, **BASIC_NODE_DATA)
|
}, **BASIC_NODE_DATA)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user