potiuk commented on code in PR #70681:
URL: https://github.com/apache/airflow/pull/70681#discussion_r3778989846


##########
airflow-core/src/airflow/secrets/__init__.py:
##########
@@ -29,12 +29,11 @@
 
 from airflow.utils.deprecation_tools import add_deprecated_classes
 
-__all__ = ["BaseSecretsBackend", "DEFAULT_SECRETS_SEARCH_PATH"]
+__all__ = [

Review Comment:
   `DEFAULT_SECRETS_SEARCH_PATH` was in `__all__` and re-exported here, so 
`from airflow.secrets import DEFAULT_SECRETS_SEARCH_PATH` was public API. After 
this change it raises `AttributeError`.
   
   The sibling symbol in this same file, `DEFAULT_SECRETS_SEARCH_PATH_WORKERS`, 
gets a `__getattr__` shim with a `DeprecatedImportWarning` and a back-compat 
fallback. This one gets nothing. Nothing in-tree still imports it, but 
out-of-tree code may.
   
   Please either add the same deprecation shim, or keep the removal and 
document it in a `.significant.rst` newsfragment.



##########
task-sdk/src/airflow/sdk/configuration.py:
##########
@@ -340,3 +410,6 @@ def __getattr__(name: str):
         globals()[name] = val
         return val
     raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
+
+
+secrets_backend_list = initialize_secrets_backends()

Review Comment:
   This runs at module import, and `airflow.sdk.configuration` is imported at 
module level by `sdk/bases/sensor.py`, `sdk/definitions/asset/__init__.py` and 
`sdk/definitions/_internal/abstractoperator.py` — so it executes on every SDK 
import, i.e. every Dag parse. Previously this was computed lazily inside 
`ensure_secrets_loaded()`.
   
   Two effects worth weighing:
   
   - With the default `[secrets] backends_order`, this imports and constructs 
`airflow.secrets.metastore.MetastoreBackend` — a core, metadata-DB-backed 
backend — inside the Dag File Processor and on workers, purely as an import 
side effect.
   - A misconfigured `backends_order` now raises `AirflowConfigException` at 
import time, so it surfaces as a Dag import error rather than a config error at 
first secrets lookup.
   
   I realise core does the same at module scope and this is probably 
consistency with it — but core is the heavyweight side, and this module's 
`conf` is deliberately lazy behind `__getattr__`, which this line forces 
eagerly. Could this stay lazy in the SDK?



##########
airflow-core/src/airflow/ui/src/pages/Variables/BackendsOrderButton.tsx:
##########
@@ -0,0 +1,76 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Box, HStack, Skeleton, Text } from "@chakra-ui/react";
+import { FiChevronRight } from "react-icons/fi";
+import { Link as RouterLink } from "react-router-dom";
+
+import type { TaskInstanceState } from "openapi/requests/types.gen";
+import { StateBadge } from "src/components/StateBadge";
+
+export const BackendsOrderButton = ({
+  colorScheme,
+  icon,
+  isLoading = false,
+  label,
+  link,
+  onClick,
+  state = null,
+}: {
+  readonly colorScheme: string;
+  readonly icon?: React.ReactNode;
+  readonly isLoading?: boolean;
+  readonly label: string;
+  readonly link?: string;
+  readonly onClick?: () => void;
+  readonly state?: TaskInstanceState | null;

Review Comment:
   This component is a near-verbatim copy of `src/components/StatsCard.tsx` 
with `count` and `isRTL` removed. It keeps `state?: TaskInstanceState | null`, 
the `StateBadge` and the `link`/`RouterLink` branch — none of which the single 
call site in `BackendsOrderCard` uses, and task-instance state has no meaning 
for a settings button.
   
   It also drops `StatsCard`'s RTL handling (`isRTL ? <FiChevronLeft /> : 
<FiChevronRight />`), so the chevron points the wrong way in the `ar` and `he` 
locales.
   
   Could this just use `StatsCard` directly? If a separate component is 
genuinely needed, please drop the unused props and keep the RTL branch.



##########
airflow-core/src/airflow/ui/src/pages/Variables/BackendsOrderModal.tsx:
##########
@@ -0,0 +1,67 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Heading, Text, HStack } from "@chakra-ui/react";
+import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { LuSettings } from "react-icons/lu";
+
+import { useConfigServiceGetBackendsOrderValue } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+import { Dialog } from "src/components/ui";
+
+type BackendsOrderModalProps = {
+  onClose: () => void;
+  open: boolean;
+};
+
+export const BackendsOrderModal: React.FC<BackendsOrderModalProps> = ({ 
onClose, open }) => {
+  const { t: translate } = useTranslation("admin");
+  const [backendsOrder, setBackendsOrder] = useState<Array<string> | string>();
+  const { data, error } = useConfigServiceGetBackendsOrderValue();
+
+  const onOpenChange = () => {
+    onClose();
+  };
+
+  useEffect(() => {
+    setBackendsOrder(data?.sections[0]?.options[0]?.value ?? "");
+  }, [data, open]);

Review Comment:
   `backendsOrder` is derived entirely from `data`, so this is the `useState + 
useEffect` sync pattern the review guidelines call out:
   
   > Avoid `useState + useEffect` to sync derived state. Use nullish coalescing 
or nullable override patterns instead.
   
   — `.github/instructions/code-review.instructions.md` § UI Code
   
   ```suggestion
     const { data, error } = useConfigServiceGetBackendsOrderValue();
     const backendsOrder = data?.sections[0]?.options[0]?.value ?? "";
   
     const onOpenChange = () => {
       onClose();
     };
   ```
   
   That also lets the `useState`/`useEffect` imports go, along with the 
`Array<string> | string` type (the value is only ever a string).
   
   Unrelated but nearby: this modal is always mounted by `BackendsOrderCard`, 
so the query fires on every Variables page render even when the dialog is 
closed — `enabled: open` would avoid that.



##########
airflow-core/src/airflow/api_fastapi/core_api/routes/ui/config.py:
##########
@@ -74,3 +81,32 @@ def get_configs() -> ConfigResponse:
     config.update({key: value for key, value in additional_config.items()})
 
     return ConfigResponse.model_validate(config)
+
+
+@config_router.get(
+    "/backends_order",
+    responses={
+        **create_openapi_http_exception_doc(
+            [
+                status.HTTP_404_NOT_FOUND,
+                status.HTTP_406_NOT_ACCEPTABLE,
+            ]
+        ),
+    },
+    response_model=Config,
+    dependencies=[Depends(requires_authenticated())],

Review Comment:
   This handler is `get_config_value` from `routes/public/config.py` with both 
of its guards removed: `requires_access_configuration("GET")` became 
`requires_authenticated()`, and `_check_expose_config()` is gone.
   
   `[api] expose_config` defaults to `"False"`, and `_check_expose_config()` 
raises 403 "Your Airflow administrator chose not to expose the configuration, 
most likely for security reasons." So on a default install, `GET 
/api/v2/config/section/secrets/option/backends_order` returns 403 while this 
returns 200 with the value to the lowest-privileged authenticated user.
   
   `backends_order` isn't a secret, so I'm not calling this a vulnerability — 
but it is a control the deployment manager chose, and the same value now has 
two different authorization answers.
   
   The neighbouring `/ui/config` endpoint is authenticated-only because it 
serves a fixed curated allowlist (`API_CONFIG_KEYS` plus `instance_name`, 
`theme`, …), not arbitrary config reads. Folding `backends_order` into that 
`get_configs()` payload would match the established pattern; otherwise please 
restore both guards.
   
   Either way `TestGetBackendsOrder` should gain an `unauthorized_test_client` 
case and an `expose_config=False` case — neither is covered today.



##########
airflow-core/src/airflow/configuration.py:
##########
@@ -740,35 +739,103 @@ def get_custom_secret_backend(worker_mode: bool = False) 
-> BaseSecretsBackend |
     return conf._get_custom_secret_backend(worker_mode=worker_mode)
 
 
+def get_importable_secret_backend(class_name: str | None) -> 
BaseSecretsBackend | None:
+    """Get secret backend defined in the given class name."""
+    if class_name is not None:
+        secrets_backend_cls = import_string(class_name)
+        return secrets_backend_cls()
+    return None
+
+
+class Backends(Enum):
+    """Type of the secrets backend."""
+
+    ENVIRONMENT_VARIABLE = "environment_variable"
+    EXECUTION_API = "execution_api"
+    CUSTOM = "custom"
+    METASTORE = "metastore"
+
+
 def initialize_secrets_backends(
-    default_backends: list[str] = DEFAULT_SECRETS_SEARCH_PATH,
+    default_backends: list[str] | None = None,
 ) -> list[BaseSecretsBackend]:
     """
     Initialize secrets backend.
 
     * import secrets backend classes
     * instantiate them and return them in a list
     """
-    backend_list = []
     worker_mode = False
-    if default_backends != DEFAULT_SECRETS_SEARCH_PATH:
+    search_section = "secrets"
+    environment_variable_args: str | None = (

Review Comment:
   These three class paths are the ones 
`scripts/ci/prek/check_secrets_search_path_sync.py` exists to keep in sync 
(registered at `.pre-commit-config.yaml:588`) — it compares core's 
`DEFAULT_SECRETS_SEARCH_PATH` against task-sdk's 
`_SERVER_DEFAULT_SECRETS_SEARCH_PATH`.
   
   This PR stops using both constants and embeds the same literals here and 
again in `task-sdk/src/airflow/sdk/configuration.py`. That's four copies, of 
which the hook only checks two — so the two new ones can drift silently, which 
is the exact failure the hook was written to prevent.
   
   Cleanest fix is to build `backends_map` from the existing constants. Failing 
that, please extend the hook to cover these literals too.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to