Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions airflow/api_fastapi/core_api/openapi/v1-generated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2832,6 +2832,8 @@ paths:
- Config
summary: Get Config
operationId: get_config
security:
- OAuth2PasswordBearer: []
parameters:
- name: section
in: query
Expand Down Expand Up @@ -2912,6 +2914,8 @@ paths:
- Config
summary: Get Config Value
operationId: get_config_value
security:
- OAuth2PasswordBearer: []
parameters:
- name: section
in: path
Expand Down
5 changes: 4 additions & 1 deletion airflow/api_fastapi/core_api/routes/public/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import textwrap

from fastapi import HTTPException, status
from fastapi import Depends, HTTPException, status
from fastapi.responses import Response

from airflow.api_fastapi.common.headers import HeaderAcceptJsonOrText
Expand All @@ -30,6 +30,7 @@
ConfigSection,
)
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import requires_access_configuration
from airflow.configuration import conf

text_example_response_for_get_config_value = {
Expand Down Expand Up @@ -108,6 +109,7 @@ def _response_based_on_accept(accept: Mimetype, config: Config):
},
},
response_model=Config,
dependencies=[Depends(requires_access_configuration("GET"))],
)
def get_config(
accept: HeaderAcceptJsonOrText,
Expand Down Expand Up @@ -153,6 +155,7 @@ def get_config(
},
},
response_model=Config,
dependencies=[Depends(requires_access_configuration("GET"))],
)
def get_config_value(
section: str,
Expand Down
19 changes: 19 additions & 0 deletions airflow/api_fastapi/core_api/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from airflow.auth.managers.models.base_user import BaseUser
from airflow.auth.managers.models.resource_details import (
AssetDetails,
ConfigurationDetails,
ConnectionDetails,
DagAccessEntity,
DagDetails,
Expand Down Expand Up @@ -123,6 +124,24 @@ def inner(
return inner


def requires_access_configuration(method: ResourceMethod) -> Callable[[Request, BaseUser | None], None]:
def inner(
request: Request,
user: Annotated[BaseUser | None, Depends(get_user)] = None,
) -> None:
section: str | None = request.query_params.get("section") or request.path_params.get("section")

_requires_access(
is_authorized_callback=lambda: get_auth_manager().is_authorized_configuration(
method=method,
details=ConfigurationDetails(section=section),
user=user,
)
)

return inner


def requires_access_variable(method: ResourceMethod) -> Callable[[Request, BaseUser | None], None]:
def inner(
request: Request,
Expand Down
20 changes: 20 additions & 0 deletions tests/api_fastapi/core_api/routes/public/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,14 @@ def test_get_config_non_sensitive_only(
response = test_client.get("/public/config", headers=headers)
self._validate_response(headers, expected_response, expected_status_code, response)

def test_get_config_should_response_401(self, unauthenticated_test_client):
response = unauthenticated_test_client.get("/public/config")
assert response.status_code == 401

def test_get_config_should_response_403(self, unauthorized_test_client):
response = unauthorized_test_client.get("/public/config")
assert response.status_code == 403


class TestGetConfigValue(TestConfigEndpoint):
@pytest.mark.parametrize(
Expand Down Expand Up @@ -474,3 +482,15 @@ def test_get_config_value_non_sensitive_only(
with conf_vars(AIRFLOW_CONFIG_NON_SENSITIVE_ONLY_CONFIG):
response = test_client.get(f"/public/config/section/{section}/option/{option}", headers=headers)
self._validate_response(headers, expected_response, expected_status_code, response)

def test_get_config_value_should_response_401(self, unauthenticated_test_client):
response = unauthenticated_test_client.get(
f"/public/config/section/{SECTION_DATABASE}/option/{OPTION_KEY_SQL_ALCHEMY_CONN}"
)
assert response.status_code == 401

def test_get_config_value_should_response_403(self, unauthorized_test_client):
response = unauthorized_test_client.get(
f"/public/config/section/{SECTION_DATABASE}/option/{OPTION_KEY_SQL_ALCHEMY_CONN}"
)
assert response.status_code == 403