Skip to content
Open
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
13 changes: 11 additions & 2 deletions git/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,7 @@ def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[


def remove_password_if_present(cmdline: Sequence[str]) -> List[str]:
"""Parse any command line argument and if one of the elements is an URL with a
username and/or password, replace them by stars (in-place).
"""Redact credentials in URLs and HTTP Authorization extra headers in a command line.

If nothing is found, this just returns the command line as-is.

Expand All @@ -551,6 +550,16 @@ def remove_password_if_present(cmdline: Sequence[str]) -> List[str]:
new_cmdline = []
for index, to_parse in enumerate(cmdline):
new_cmdline.append(to_parse)
config_key, separator, header = to_parse.partition("=")
header_name, colon, _ = header.partition(":")
if (
separator
and colon
and config_key.lower().endswith(".extraheader")
and header_name.strip().lower() == "authorization"
):
new_cmdline[index] = "%s%s%s%s *****" % (config_key, separator, header_name, colon)
continue
try:
url = urlsplit(to_parse)
# Remove password from the URL if present.
Expand Down
18 changes: 18 additions & 0 deletions test/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,24 @@ def test_it_raises_proper_exception_with_output_stream(self):
with self.assertRaises(GitCommandError):
self.git.checkout("non-existent-branch", output_stream=tmp_file)

def test_it_redacts_authorization_extra_header_from_error(self):
token = "fake-token-1234"
command = [
"git",
"-c",
"http.extraHeader=Authorization: Bearer %s" % token,
"rev-parse",
"--verify",
"refs/does-not-exist",
]

with self.assertRaises(GitCommandError) as context:
self.git.execute(command)

message = str(context.exception)
self.assertNotIn(token, message)
self.assertIn("http.extraHeader=Authorization: *****", message)

def test_it_accepts_environment_variables(self):
filename = fixture_path("ls_tree_empty")
with open(filename, "r") as fh:
Expand Down
6 changes: 6 additions & 0 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ def test_pickle_tzoffset(self):
def test_remove_password_from_command_line(self):
username = "fakeuser"
password = "fakepassword1234"
authorization = "Bearer fake-token-1234"
url_with_user_and_pass = "https://{}:{}@fakerepo.example.com/testrepo".format(username, password)
url_with_user = "https://{}@fakerepo.example.com/testrepo".format(username)
url_with_pass = "https://:{}@fakerepo.example.com/testrepo".format(password)
Expand All @@ -681,6 +682,7 @@ def test_remove_password_from_command_line(self):
cmd_3 = ["git", "clone", "-v", url_with_pass]
cmd_4 = ["git", "clone", "-v", url_without_user_or_pass]
cmd_5 = ["no", "url", "in", "this", "one"]
cmd_6 = ["git", "-c", "http.extraHeader=Authorization: %s" % authorization, "fetch"]

redacted_cmd_1 = remove_password_if_present(cmd_1)
assert username not in " ".join(redacted_cmd_1)
Expand All @@ -700,3 +702,7 @@ def test_remove_password_from_command_line(self):

assert cmd_4 == remove_password_if_present(cmd_4)
assert cmd_5 == remove_password_if_present(cmd_5)

redacted_cmd_6 = remove_password_if_present(cmd_6)
assert authorization not in " ".join(redacted_cmd_6)
assert "http.extraHeader=Authorization: *****" in redacted_cmd_6
Loading