rjgoyln commented on code in PR #64105:
URL: https://github.com/apache/airflow/pull/64105#discussion_r4062840826


##########
providers/git/src/airflow/providers/git/hooks/git.py:
##########
@@ -242,73 +247,91 @@ def _ensure_github_app_token(self) -> None:
             )
             self.user_name, self.auth_token, self.github_app_token_exp = 
self._get_github_app_token()
 
+    def _strip_embedded_credentials(self) -> tuple[str | None, str | None]:
+        """Take any ``user:password@`` out of the repo url and return what it 
held."""
+        if not isinstance(self.repo_url, str) or not 
self.repo_url.startswith(("http://";, "https://";)):
+            return None, None
+        scheme, separator, rest = self.repo_url.partition("://")
+        authority, slash, path = rest.partition("/")
+        userinfo, at_sign, host = authority.rpartition("@")
+        user, _, password = userinfo.partition(":")
+        # A bare ``user@`` holds no secret, so leave those urls exactly as the 
connection wrote
+        # them; anything git clones from a stripped url would lose the 
username for nothing.
+        if not at_sign or not password:
+            return None, None
+        self.repo_url = f"{scheme}{separator}{host}{slash}{path}"
+        return unquote(user) or None, unquote(password)
+
+    def _extract_credential_scope(self) -> str:
+        """Return the ``<scheme>://<host>[:port]`` git matches a credential 
config against."""
+        scheme, _, rest = str(self.repo_url).partition("://")
+        host = rest.partition("/")[0].rpartition("@")[2]
+        return f"{scheme}://{host}" if host else ""
+
     @contextlib.contextmanager
-    def _github_app_askpass_env(self) -> Generator[None]:
-        if not self.auth_token:
+    def _token_credential_env(self) -> Generator[None]:
+        """Hand the token to git through a credential helper scoped to the 
repository's host."""
+        # Credential helpers only serve http(s); an SSH connection that 
happens to carry a
+        # password would gain nothing from one.
+        if not self.auth_token or not 
str(self.repo_url).startswith(("http://";, "https://";)):
             yield
             return
 
-        token = shlex.quote(self.auth_token)
-        with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=True) 
as askpass_script:
-            askpass_script.write(
-                "#!/bin/sh\n"
-                'case "$1" in\n'
-                "  *Username*) echo x-access-token;;\n"
-                f"  *Password*) echo {token};;\n"
-                f"  *) echo {token};;\n"
-                "esac\n"
-            )
-            askpass_script.flush()
-            os.chmod(askpass_script.name, stat.S_IRWXU)
+        scope = self._extract_credential_scope()
+        if not scope:
+            yield
+            return
+
+        with tempfile.TemporaryDirectory() as helper_dir:
+            helper_path = os.path.join(helper_dir, "credential-helper.sh")
+            # git matches the configured scope against the url it parsed, then 
hands the helper
+            # structured fields on stdin. A submodule elsewhere never reaches 
this helper, and no
+            # part of the decision depends on the wording of a human-readable 
prompt.
+            # Written and closed before git runs: Linux refuses to exec a file 
that is still
+            # open for writing, which git surfaces as "cannot exec: Text file 
busy".
+            with open(helper_path, "w") as helper_script:
+                helper_script.write(
+                    r"""#!/bin/sh
+cat > /dev/null
+[ "$1" = get ] || exit 0
+printf 'username=%s\npassword=%s\n' "$AIRFLOW_GIT_USER" "$AIRFLOW_GIT_TOKEN"
+"""
+                )
+            os.chmod(helper_path, stat.S_IRWXU)
 
-            old_askpass = os.environ.get("GIT_ASKPASS")
-            old_lc_all = os.environ.get("LC_ALL")
-            old_terminal_prompt = os.environ.get("GIT_TERMINAL_PROMPT")
+            # Append to any GIT_CONFIG_* the deployment already exports rather 
than replacing it.
+            try:
+                index = int(os.environ.get("GIT_CONFIG_COUNT", "0"))
+            except ValueError:
+                index = 0
+            # System/global config loads before env-supplied config, so a 
deployment-wide
+            # `credential.helper` would otherwise answer `get` first and our 
token would never
+            # be used. git also invokes every helper on `approve`, so a 
`store` helper would
+            # persist it to ~/.git-credentials. Reset the scope to empty first 
(git help credentials).
+            values = {
+                "GIT_CONFIG_COUNT": str(index + 2),
+                f"GIT_CONFIG_KEY_{index}": f"credential.{scope}.helper",
+                f"GIT_CONFIG_VALUE_{index}": "",
+                f"GIT_CONFIG_KEY_{index + 1}": f"credential.{scope}.helper",
+                f"GIT_CONFIG_VALUE_{index + 1}": helper_path,

Review Comment:
   Fixed in b0ac2fd24e with ! + shlex.quote(helper_path), and added a 
regression test for TMPDIR paths containing spaces.



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