Skip to content

Commit

Permalink
Fix handling of osxkeychain addtional keys (#391) (#392)
Browse files Browse the repository at this point in the history
* 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.

* Fix typing of credentials Dict

---------

Co-authored-by: Paul Martin <paul.martin@telekom.com>
  • Loading branch information
pgpx and Paul Martin authored Nov 25, 2024
1 parent a38304b commit 93861d0
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 2 deletions.
35 changes: 33 additions & 2 deletions src/scmrepo/git/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,29 @@ def get(self, credential: "Credential", **kwargs) -> "Credential":
if res.stderr:
logger.debug(res.stderr)

credentials = {}
credentials: dict[str, Any] = {}
for line in res.stdout.splitlines():
try:
key, value = line.split("=", maxsplit=1)
credentials[key] = value
# Only include credential values that are used in the Credential
# constructor.
# Other values may be returned by the subprocess, but they must be
# ignored.
# e.g. osxkeychain credential helper >= 2.46.0 can return
# `capability[]` and `state`)
if key in [
"protocol",
"host",
"path",
"username",
"password",
"password_expiry_utc",
"url",
]:
# Garbage bytes were output from git-credential-osxkeychain from
# 2.45.0 to 2.47.0:
# https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69
credentials[key] = _strip_garbage_bytes(value)
except ValueError:
continue
if not credentials:
Expand Down Expand Up @@ -265,6 +283,19 @@ def get_matching_commands(
)


def _strip_garbage_bytes(s: str) -> str:
"""
Garbage (random) bytes were output from git-credential-osxkeychain from
2.45.0 to 2.47.0 so must be removed.
https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69
:param s: string that might contain garbage/random bytes
:return str: The string with the garbage bytes removed
"""
# Assume that any garbage bytes begin with a 0-byte
zero = s.find(chr(0))
return s[0:zero] if zero >= 0 else s


class _CredentialKey(NamedTuple):
protocol: str
host: Optional[str]
Expand Down
40 changes: 40 additions & 0 deletions tests/test_credentials.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io
import os
import random

import pytest

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


def test_subprocess_ignore_unexpected_credential_keys(git_helper, mocker):
git_helper.use_http_path = True
run = mocker.patch(
"subprocess.run",
# Simulate git-credential-osxkeychain (version >=2.45)
return_value=mocker.Mock(
stdout="username=foo\npassword=bar\ncapability[]=state\nstate[]=osxkeychain:seen=1"
),
)
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
assert run.call_args.args[0] == ["git-credential-foo", "get"]
assert (
run.call_args.kwargs.get("input")
== "protocol=https\nhost=foo.com\npath=foo.git\n"
)
assert creds == Credential(username="foo", password="bar")


def test_subprocess_strip_trailing_garbage_bytes(git_helper, mocker):
"""Garbage bytes were output from git-credential-osxkeychain from 2.45.0 to 2.47.0
so must be removed
https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69"""
git_helper.use_http_path = True
run = mocker.patch(
"subprocess.run",
# Simulate git-credential-osxkeychain (version 2.45), assuming initial 0-byte
return_value=mocker.Mock(
stdout=f"username=foo\npassword=bar{chr(0)}{random.randbytes(15)}"
),
)
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
assert run.call_args.args[0] == ["git-credential-foo", "get"]
assert (
run.call_args.kwargs.get("input")
== "protocol=https\nhost=foo.com\npath=foo.git\n"
)
assert creds == Credential(username="foo", password="bar")


def test_subprocess_get_failed(git_helper, mocker):
from subprocess import CalledProcessError

Expand Down

0 comments on commit 93861d0

Please sign in to comment.