Skip to content

Commit 608991f

Browse files
author
Paul Martin
committed
Fix handling of osxkeychain addtional keys (#391)
git-credential-helper v2.45 < v2.47.0 output additional garbage bytes after the username. git-credential-helper >= v2.46.0 outputs additional `capability[]` and `state` keys.
1 parent a38304b commit 608991f

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

src/scmrepo/git/credentials.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,29 @@ def get(self, credential: "Credential", **kwargs) -> "Credential":
177177
for line in res.stdout.splitlines():
178178
try:
179179
key, value = line.split("=", maxsplit=1)
180-
credentials[key] = value
180+
# Only include credential values that are used in the Credential
181+
# constructor.
182+
# Other values may be returned by the subprocess, but they must be
183+
# ignored.
184+
# e.g. osxkeychain credential helper >= 2.46.0 can return
185+
# `capability[]` and `state`)
186+
if key in [
187+
"protocol",
188+
"host",
189+
"path",
190+
"username",
191+
"password",
192+
"password_expiry_utc",
193+
"url",
194+
]:
195+
# Assume that any garbage bytes begin with a 0-byte
196+
# Garbage bytes were output from git-credential-osxkeychain from
197+
# 2.45.0 to 2.47.0:
198+
# https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69
199+
zero = value.find(chr(0))
200+
if zero >= 0:
201+
value = value[0:zero]
202+
credentials[key] = value
181203
except ValueError:
182204
continue
183205
if not credentials:

tests/test_credentials.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import io
22
import os
3+
import random
34

45
import pytest
56

@@ -45,6 +46,45 @@ def test_subprocess_get_use_http_path(git_helper, mocker):
4546
assert creds == Credential(username="foo", password="bar")
4647

4748

49+
def test_subprocess_ignore_unexpected_credential_keys(git_helper, mocker):
50+
git_helper.use_http_path = True
51+
run = mocker.patch(
52+
"subprocess.run",
53+
# Simulate git-credential-osxkeychain (version >=2.45)
54+
return_value=mocker.Mock(
55+
stdout="username=foo\npassword=bar\ncapability[]=state\nstate[]=osxkeychain:seen=1"
56+
),
57+
)
58+
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
59+
assert run.call_args.args[0] == ["git-credential-foo", "get"]
60+
assert (
61+
run.call_args.kwargs.get("input")
62+
== "protocol=https\nhost=foo.com\npath=foo.git\n"
63+
)
64+
assert creds == Credential(username="foo", password="bar")
65+
66+
67+
def test_subprocess_strip_trailing_garbage_bytes(git_helper, mocker):
68+
"""Garbage bytes were output from git-credential-osxkeychain from 2.45.0 to 2.47.0
69+
so must be removed
70+
https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69"""
71+
git_helper.use_http_path = True
72+
run = mocker.patch(
73+
"subprocess.run",
74+
# Simulate git-credential-osxkeychain (version 2.45), assuming initial 0-byte
75+
return_value=mocker.Mock(
76+
stdout=f"username=foo\npassword=bar{chr(0)}{random.randbytes(15)}"
77+
),
78+
)
79+
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
80+
assert run.call_args.args[0] == ["git-credential-foo", "get"]
81+
assert (
82+
run.call_args.kwargs.get("input")
83+
== "protocol=https\nhost=foo.com\npath=foo.git\n"
84+
)
85+
assert creds == Credential(username="foo", password="bar")
86+
87+
4888
def test_subprocess_get_failed(git_helper, mocker):
4989
from subprocess import CalledProcessError
5090

0 commit comments

Comments
 (0)