Copilot commented on code in PR #65493:
URL: https://github.com/apache/airflow/pull/65493#discussion_r3115567526


##########
providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py:
##########
@@ -189,6 +200,30 @@ def _get_secret_with_base(self, base_path: str | None, 
key: str) -> dict | None:
             secret_path=(mount_point + "/" if mount_point else "") + 
secret_path
         )
 
+    def _get_team_or_global_secret(self, base_path: str | None, team_name: str 
| None, key: str):
+        """
+        Get a secret from a team specific path or the global path.
+
+        If multi team is enabled, check {base_path}/{team_name}/{key}, then 
fallback to {base_path}/{global_path}/{key} or {base_path}/{key}.
+        """
+        if base_path is None:
+            return None
+        if (
+            conf.get("core", "multi_team", fallback=False)
+            and self.use_team_secrets_path
+            and team_name is not None
+        ):
+            response = self._get_secret_with_base(base_path + "/" + team_name, 
key)
+            if response is not None:
+                return response
+        # Fallback to global secret
+        if conf.get("core", "multi_team", fallback=False) and 
self.global_secrets_path is not None:
+            path = base_path + "/" + self.global_secrets_path

Review Comment:
   When `connections_path`/`variables_path` is configured as an empty string 
(which the backend already treats specially in `_get_secret_with_base`), 
building `base_path + "/" + team_name` produces a leading slash (e.g. "/team") 
and changes the secret path format. Consider using `build_path(base_path, 
team_name)` (or handling `base_path == ""`) when constructing the team/global 
base paths to preserve the existing no-leading-slash behavior.
   ```suggestion
               response = self._get_secret_with_base(self.build_path(base_path, 
team_name), key)
               if response is not None:
                   return response
           # Fallback to global secret
           if conf.get("core", "multi_team", fallback=False) and 
self.global_secrets_path is not None:
               path = self.build_path(base_path, self.global_secrets_path)
   ```



##########
providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py:
##########
@@ -189,6 +200,30 @@ def _get_secret_with_base(self, base_path: str | None, 
key: str) -> dict | None:
             secret_path=(mount_point + "/" if mount_point else "") + 
secret_path
         )
 
+    def _get_team_or_global_secret(self, base_path: str | None, team_name: str 
| None, key: str):
+        """
+        Get a secret from a team specific path or the global path.
+
+        If multi team is enabled, check {base_path}/{team_name}/{key}, then 
fallback to {base_path}/{global_path}/{key} or {base_path}/{key}.
+        """
+        if base_path is None:
+            return None
+        if (
+            conf.get("core", "multi_team", fallback=False)
+            and self.use_team_secrets_path
+            and team_name is not None
+        ):
+            response = self._get_secret_with_base(base_path + "/" + team_name, 
key)
+            if response is not None:
+                return response
+        # Fallback to global secret
+        if conf.get("core", "multi_team", fallback=False) and 
self.global_secrets_path is not None:

Review Comment:
   `multi_team` is a boolean config, but this code uses `conf.get(...)` in 
boolean contexts. If the config value is the string "False" (common when set 
via env/ini), it will still be truthy and incorrectly enable team/global 
lookup. Use `conf.getboolean("core", "multi_team", fallback=False)` for these 
checks (and avoid mixing different fallbacks in different places).
   ```suggestion
   
           multi_team_enabled = conf.getboolean("core", "multi_team", 
fallback=False)
   
           if multi_team_enabled and self.use_team_secrets_path and team_name 
is not None:
               response = self._get_secret_with_base(base_path + "/" + 
team_name, key)
               if response is not None:
                   return response
           # Fallback to global secret
           if multi_team_enabled and self.global_secrets_path is not None:
   ```



##########
providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py:
##########
@@ -214,7 +249,10 @@ def get_connection(self, conn_id: str, team_name: str | 
None = None) -> Connecti
         # problems when instantiating the backend during configuration
         from airflow.models.connection import Connection
 
-        response = self.get_response(conn_id)
+        if conf.get("core", "multi_team", fallback=True):
+            response = self._get_team_or_global_secret(self.connections_path, 
team_name, conn_id)
+        else:
+            response = self.get_response(conn_id)

Review Comment:
   `get_connection()` currently uses `conf.get("core", "multi_team", 
fallback=True)` which (a) defaults to enabling the multi-team path even when 
unset, and (b) has the same truthy-string problem as above. This should be 
`conf.getboolean("core", "multi_team", fallback=False)` to match core/provider 
usage and ensure team scoping is only active when explicitly enabled.



##########
providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py:
##########
@@ -52,6 +53,10 @@ class VaultBackend(BaseSecretsBackend, LoggingMixin):
         (default: 'variables'). If set to None (null), requests for variables 
will not be sent to Vault.
     :param config_path: Specifies the path of the secret to read Airflow 
Configurations
         (default: 'config'). If set to None (null), requests for 
configurations will not be sent to Vault.
+    :param use_team_secrets_paths: Flag to enable team scoped secret retrieval 
from {base_path}/{team_name}/{key}

Review Comment:
   Docstring parameter name looks inconsistent: it documents 
`use_team_secrets_paths` but the actual init kwarg is `use_team_secrets_path`. 
Align the docstring with the real argument name so users configuring 
`backend_kwargs` don’t get misled.
   ```suggestion
       :param use_team_secrets_path: Flag to enable team scoped secret 
retrieval from {base_path}/{team_name}/{key}
   ```



##########
providers/hashicorp/tests/unit/hashicorp/secrets/test_vault.py:
##########
@@ -68,6 +70,175 @@ def test_get_connection(self, mock_hvac):
         connection = test_client.get_connection(conn_id="test_postgres")
         assert connection.get_uri() == 
"postgresql://airflow:airflow@host:5432/airflow?foo=bar&baz=taz"
 
+    
@mock.patch("airflow.providers.hashicorp._internal_client.vault_client.hvac")
+    def test_get_connection_team_value(self, mock_hvac):
+        mock_client = mock.MagicMock()
+        mock_hvac.Client.return_value = mock_client
+        mock_client.secrets.kv.v2.read_secret_version.return_value = {
+            "request_id": "94011e25-f8dc-ec29-221b-1f9c1d9ad2ae",
+            "lease_id": "",
+            "renewable": False,
+            "lease_duration": 0,
+            "data": {
+                "data": {
+                    "conn_type": "postgresql",
+                    "login": "airflow",
+                    "password": "airflow",
+                    "host": "host",
+                    "port": "5432",
+                    "schema": "airflow",
+                    "extra": '{"foo":"bar","baz":"taz"}',
+                },
+                "metadata": {
+                    "created_time": "2020-03-16T21:01:43.331126Z",
+                    "deletion_time": "",
+                    "destroyed": False,
+                    "version": 1,
+                },
+            },
+            "wrap_info": None,
+            "warnings": None,
+            "auth": None,
+        }
+
+        kwargs = {
+            "connections_path": "connections",
+            "mount_point": "airflow",
+            "auth_type": "token",
+            "url": "http://127.0.0.1:8200";,
+            "token": "s.7AU0I51yv1Q1lxOIg1F3ZRAS",
+        }
+
+        with conf_vars({("core", "multi_team"): "True"}):
+            test_client = VaultBackend(**kwargs)
+            connection = test_client.get_connection(conn_id="test_postgres", 
team_name="foo")
+        mock_client.secrets.kv.v2.read_secret_version.assert_called_with(

Review Comment:
   The new multi-team tests cover the enabled (`multi_team=True`) cases, but 
there’s no regression test ensuring that when `core.multi_team` is disabled and 
a `team_name` is passed, VaultBackend does *not* add the team prefix. Adding 
that case would prevent future regressions (and would have caught the 
`conf.get(...)` truthy-string issue).



-- 
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