Skip to content

Commit

Permalink
fix(api_connexion): handle the cases that webserver.expose_config is …
Browse files Browse the repository at this point in the history
…set to non-sensitive-only instead of boolean value (#32261)
  • Loading branch information
Lee-W committed Jun 29, 2023
1 parent 2ce51ac commit 4a525e8
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 4 deletions.
25 changes: 21 additions & 4 deletions airflow/api_connexion/endpoints/config_endpoint.py
Expand Up @@ -74,12 +74,19 @@ def get_config(*, section: str | None = None) -> Response:
"application/json": _config_to_json,
}
return_type = request.accept_mimetypes.best_match(serializer.keys())
if conf.get("webserver", "expose_config").lower() == "non-sensitive-only":
expose_config = True
display_sensitive = False
else:
expose_config = conf.getboolean("webserver", "expose_config")
display_sensitive = True

if return_type not in serializer:
return Response(status=HTTPStatus.NOT_ACCEPTABLE)
elif conf.getboolean("webserver", "expose_config"):
elif expose_config:
if section and not conf.has_section(section):
raise NotFound("section not found.", detail=f"section={section} not found.")
conf_dict = conf.as_dict(display_source=False, display_sensitive=True)
conf_dict = conf.as_dict(display_source=False, display_sensitive=display_sensitive)
if section:
conf_section_value = conf_dict[section]
conf_dict.clear()
Expand All @@ -103,14 +110,24 @@ def get_value(section: str, option: str) -> Response:
"application/json": _config_to_json,
}
return_type = request.accept_mimetypes.best_match(serializer.keys())
if conf.get("webserver", "expose_config").lower() == "non-sensitive-only":
expose_config = True
else:
expose_config = conf.getboolean("webserver", "expose_config")

if return_type not in serializer:
return Response(status=HTTPStatus.NOT_ACCEPTABLE)
elif conf.getboolean("webserver", "expose_config"):
elif expose_config:
if not conf.has_option(section, option):
raise NotFound(
"Config not found.", detail=f"The option [{section}/{option}] is not found in config."
)
value = conf.get(section, option)

print(conf.sensitive_config_values)
if (section, option) in conf.sensitive_config_values:
value = "< hidden >"
else:
value = conf.get(section, option)

config = Config(
sections=[ConfigSection(name=section, options=[ConfigOption(key=option, value=value)])]
Expand Down
52 changes: 52 additions & 0 deletions tests/api_connexion/endpoints/test_config_endpoint.py
Expand Up @@ -35,6 +35,14 @@
},
}

MOCK_CONF_WITH_SENSITIVE_VALUE = {
"core": {"parallelism": "1024", "sql_alchemy_conn": "mock_conn"},
"smtp": {
"smtp_host": "localhost",
"smtp_mail_from": "airflow@example.com",
},
}


@pytest.fixture(scope="module")
def configured_app(minimal_app_for_api):
Expand Down Expand Up @@ -65,6 +73,27 @@ def test_should_respond_200_text_plain(self, mock_as_dict):
response = self.client.get(
"/api/v1/config", headers={"Accept": "text/plain"}, environ_overrides={"REMOTE_USER": "test"}
)
mock_as_dict.assert_called_with(display_source=False, display_sensitive=True)
assert response.status_code == 200
expected = textwrap.dedent(
"""\
[core]
parallelism = 1024
[smtp]
smtp_host = localhost
smtp_mail_from = airflow@example.com
"""
)
assert expected == response.data.decode()

@patch("airflow.api_connexion.endpoints.config_endpoint.conf.as_dict", return_value=MOCK_CONF)
@conf_vars({("webserver", "expose_config"): "non-sensitive-only"})
def test_should_respond_200_text_plain_with_non_sensitive_only(self, mock_as_dict):
response = self.client.get(
"/api/v1/config", headers={"Accept": "text/plain"}, environ_overrides={"REMOTE_USER": "test"}
)
mock_as_dict.assert_called_with(display_source=False, display_sensitive=False)
assert response.status_code == 200
expected = textwrap.dedent(
"""\
Expand All @@ -85,6 +114,7 @@ def test_should_respond_200_application_json(self, mock_as_dict):
headers={"Accept": "application/json"},
environ_overrides={"REMOTE_USER": "test"},
)
mock_as_dict.assert_called_with(display_source=False, display_sensitive=True)
assert response.status_code == 200
expected = {
"sections": [
Expand Down Expand Up @@ -112,6 +142,7 @@ def test_should_respond_200_single_section_as_text_plain(self, mock_as_dict):
headers={"Accept": "text/plain"},
environ_overrides={"REMOTE_USER": "test"},
)
mock_as_dict.assert_called_with(display_source=False, display_sensitive=True)
assert response.status_code == 200
expected = textwrap.dedent(
"""\
Expand All @@ -129,6 +160,7 @@ def test_should_respond_200_single_section_as_json(self, mock_as_dict):
headers={"Accept": "application/json"},
environ_overrides={"REMOTE_USER": "test"},
)
mock_as_dict.assert_called_with(display_source=False, display_sensitive=True)
assert response.status_code == 200
expected = {
"sections": [
Expand Down Expand Up @@ -210,6 +242,26 @@ def test_should_respond_200_text_plain(self, mock_as_dict):
)
assert expected == response.data.decode()

@patch(
"airflow.api_connexion.endpoints.config_endpoint.conf.as_dict",
return_value=MOCK_CONF_WITH_SENSITIVE_VALUE,
)
@conf_vars({("webserver", "expose_config"): "non-sensitive-only"})
def test_should_respond_200_text_plain_with_non_sensitive_only(self, mock_as_dict):
response = self.client.get(
"/api/v1/config/section/core/option/sql_alchemy_conn",
headers={"Accept": "text/plain"},
environ_overrides={"REMOTE_USER": "test"},
)
assert response.status_code == 200
expected = textwrap.dedent(
"""\
[core]
sql_alchemy_conn = < hidden >
"""
)
assert expected == response.data.decode()

@patch("airflow.api_connexion.endpoints.config_endpoint.conf.as_dict", return_value=MOCK_CONF)
def test_should_respond_200_application_json(self, mock_as_dict):
response = self.client.get(
Expand Down

0 comments on commit 4a525e8

Please sign in to comment.