test_mirror_credentials.py
python
sha256:33412f8ca4d4d6c3b72e9f6a6097ef8395b863f35f7451bf475a3cc0fb98ce9c
docs: restore exact original site presentation
Human
7 days ago
| 1 | """Exercise Git's credential protocol with a fake gh; never use real secrets.""" |
| 2 | import os |
| 3 | from pathlib import Path |
| 4 | import shlex |
| 5 | import subprocess |
| 6 | import sys |
| 7 | from unittest.mock import patch |
| 8 | |
| 9 | import pytest |
| 10 | |
| 11 | from cli import v1_mirror as mirror |
| 12 | from cli.v1_io import Refusal |
| 13 | |
| 14 | |
| 15 | @pytest.fixture |
| 16 | def adapter(tmp_path): |
| 17 | executable = tmp_path / "gh with ' quotes" |
| 18 | executable.write_text('#!/bin/sh\n[ "$*" = "auth token --hostname github.com" ] || exit 9\n' |
| 19 | 'printf "%s\\n" fixture-token-never-a-real-credential\n') |
| 20 | executable.chmod(0o755) |
| 21 | return mirror.NetworkDelivery(str(executable.resolve())) |
| 22 | |
| 23 | |
| 24 | def credential(adapter, request, operation='fill'): |
| 25 | dest = {'url': 'https://github.com/fixture/mirror.git', 'repository': 'fixture/mirror'} |
| 26 | return subprocess.run(['/usr/bin/git', *adapter.git_options(dest), 'credential', operation], |
| 27 | cwd='/', input=request, env=mirror.env(), capture_output=True, timeout=10) |
| 28 | |
| 29 | |
| 30 | def test_git_credential_fill_scoped_helper_and_no_persistent_config(adapter, tmp_path): |
| 31 | request = b'protocol=https\nhost=github.com\npath=fixture/mirror.git\n\n' |
| 32 | before = {p: p.read_bytes() for p in tmp_path.iterdir()} |
| 33 | result = credential(adapter, request) |
| 34 | assert result.returncode == 0, result.stderr |
| 35 | assert b'password=fixture-token-never-a-real-credential\n' in result.stdout |
| 36 | assert b'username=x-access-token\n' in result.stdout |
| 37 | assert result.stderr == b'' |
| 38 | for operation in ('approve', 'reject'): |
| 39 | assert credential(adapter, request + b'password=fixture-only\n', operation).returncode == 0 |
| 40 | assert {p: p.read_bytes() for p in tmp_path.iterdir()} == before |
| 41 | assert 'fixture-token' not in repr(adapter.git_options({'url': 'https://github.com/fixture/mirror.git'})) |
| 42 | |
| 43 | |
| 44 | @pytest.mark.parametrize('payload', [ |
| 45 | b'protocol=http\nhost=github.com\npath=fixture/mirror.git\n\n', |
| 46 | b'protocol=https\nhost=example.invalid\npath=fixture/mirror.git\n\n', |
| 47 | b'protocol=https\nhost=github.com\npath=fixture/other.git\n\n', |
| 48 | b'protocol=https\nhost=github.com\npath=fixture/mirror.git/extra\n\n', |
| 49 | b'protocol=https\nhost=github.com\n\n', |
| 50 | b'protocol=https\nhost=github.com:443\npath=fixture/mirror.git\n\n', |
| 51 | ]) |
| 52 | def test_wrong_credential_scope_never_returns_token(adapter, payload): |
| 53 | result = credential(adapter, payload) |
| 54 | assert result.returncode != 0 |
| 55 | assert b'fixture-token' not in result.stdout + result.stderr |
| 56 | |
| 57 | |
| 58 | def test_credential_failure_hides_gh_output(adapter): |
| 59 | adapter.gh.write_text('#!/bin/sh\necho fixture-secret-error >&2\necho fixture-secret-token\nexit 1\n') |
| 60 | result = credential(adapter, b'protocol=https\nhost=github.com\npath=fixture/mirror.git\n\n') |
| 61 | assert result.returncode != 0 and b'fixture-secret' not in result.stdout + result.stderr |
| 62 | |
| 63 | |
| 64 | def test_transport_credentials_are_ephemeral_and_redirects_disabled(adapter, monkeypatch): |
| 65 | monkeypatch.setenv('GIT_TRACE', '/not-used') |
| 66 | monkeypatch.setenv('GIT_CONFIG_COUNT', '1') |
| 67 | monkeypatch.setenv('SSH_ASKPASS', '/unapproved-helper') |
| 68 | monkeypatch.setenv('GIT_ASKPASS', '/unapproved-helper') |
| 69 | monkeypatch.setenv('GH_DEBUG', 'api') |
| 70 | dest = {'url': 'https://github.com/fixture/mirror.git', 'repository': 'fixture/mirror', 'branch': 'muse-mirror'} |
| 71 | with patch.object(mirror.subprocess, 'run') as run: |
| 72 | run.return_value.returncode = 0 |
| 73 | run.return_value.stdout = b'' |
| 74 | adapter.push(Path('/'), dest, 'a' * 40) |
| 75 | adapter.remote_head(dest) |
| 76 | for call in run.call_args_list: |
| 77 | argv = call.args[0] |
| 78 | assert 'credential.helper=' in argv and 'credential.useHttpPath=true' in argv |
| 79 | assert 'http.followRedirects=false' in argv and 'protocol.allow=never' in argv |
| 80 | assert '--force' not in argv and '--force-with-lease' not in argv |
| 81 | assert 'GIT_TRACE' not in call.kwargs['env'] and 'GIT_CONFIG_COUNT' not in call.kwargs['env'] |
| 82 | assert 'SSH_ASKPASS' not in call.kwargs['env'] and 'GH_DEBUG' not in call.kwargs['env'] |
| 83 | assert call.kwargs['env']['GIT_ASKPASS'] == '/usr/bin/false' |
| 84 | assert 'fixture-token' not in repr(call) |
| 85 | |
| 86 | |
| 87 | @pytest.mark.parametrize('url', ['http://github.com/fixture/mirror.git', |
| 88 | 'https://[email protected]/fixture/mirror.git', 'https://github.com/../mirror.git', |
| 89 | 'https://github.com/fixture/mirror.git?token=secret', 'https://example.invalid/fixture/mirror.git']) |
| 90 | def test_transport_rejects_unscoped_urls(adapter, url): |
| 91 | with pytest.raises(Refusal, match='credential_destination'): |
| 92 | adapter.git_options({'url': url}) |
| 93 | |
| 94 | |
| 95 | SCOPE = b'protocol=https\nhost=github.com\npath=fixture/mirror.git\n' |
| 96 | CHALLENGE = (b'capability[]=authtype\ncapability[]=state\n' |
| 97 | b'wwwauth[]=Basic realm="GitHub"\n') |
| 98 | |
| 99 | |
| 100 | def helper(adapter, payload, operation='get'): |
| 101 | return subprocess.run([sys.executable, '-I', '-B', |
| 102 | str(Path(mirror.__file__).with_name('v1_git_credential.py')), |
| 103 | str(adapter.gh), 'fixture/mirror', operation], |
| 104 | input=payload, env=mirror.env(), capture_output=True, timeout=10) |
| 105 | |
| 106 | |
| 107 | @pytest.mark.parametrize('metadata', [ |
| 108 | b'capability[]=authtype\ncapability[]=state\ncapability[]=state\n', |
| 109 | b'wwwauth[]=Basic realm="GitHub"\nwwwauth[]=Bearer realm="fixture"\n', |
| 110 | b'wwwauth[]=\nwwwauth[]=Basic realm="GitHub"\n', |
| 111 | CHALLENGE, |
| 112 | ]) |
| 113 | def test_helper_accepts_repeatable_challenge_metadata(adapter, metadata): |
| 114 | result = helper(adapter, metadata + SCOPE + b'\n') |
| 115 | assert result.returncode == 0 |
| 116 | assert result.stdout == b'username=x-access-token\npassword=fixture-token-never-a-real-credential\n\n' |
| 117 | assert result.stderr == b'' |
| 118 | # Offers/challenges are not echoed or acknowledged as negotiated capabilities. |
| 119 | assert b'capability' not in result.stdout and b'wwwauth' not in result.stdout |
| 120 | |
| 121 | |
| 122 | def test_real_git_credential_fill_with_http_challenge(adapter): |
| 123 | result = credential(adapter, CHALLENGE + SCOPE + b'\n') |
| 124 | assert result.returncode == 0, result.stderr |
| 125 | assert b'username=x-access-token\n' in result.stdout |
| 126 | assert b'password=fixture-token-never-a-real-credential\n' in result.stdout |
| 127 | assert result.stderr == b'' |
| 128 | |
| 129 | |
| 130 | @pytest.mark.parametrize('payload', [ |
| 131 | CHALLENGE + SCOPE.replace(b'https', b'http'), |
| 132 | CHALLENGE + SCOPE.replace(b'github.com', b'example.invalid'), |
| 133 | CHALLENGE + SCOPE.replace(b'fixture/mirror.git', b'fixture/other.git'), |
| 134 | CHALLENGE + SCOPE.replace(b'fixture/mirror.git', b'fixture/mirror.git/extra'), |
| 135 | CHALLENGE + SCOPE.replace(b'path=fixture/mirror.git\n', b''), |
| 136 | CHALLENGE + SCOPE + b'protocol=https\n', |
| 137 | CHALLENGE + SCOPE + b'host=example.invalid\n', |
| 138 | CHALLENGE + SCOPE + b'path=fixture/other.git\n', |
| 139 | CHALLENGE + SCOPE + b'username=first\nusername=second\n', |
| 140 | SCOPE + b'state[]=not-negotiated\n', |
| 141 | SCOPE + b'credential=not-negotiated\n', |
| 142 | SCOPE + b'unknown[]=ignored-by-mistake\n', |
| 143 | SCOPE + b'capability[]\n', |
| 144 | SCOPE + b'wwwauth[]=bad\x00challenge\n', |
| 145 | SCOPE + b'wwwauth[]=bad\rchallenge\n', |
| 146 | SCOPE + b'wwwauth[]=bad\x0bchallenge\n', |
| 147 | SCOPE + b'wwwauth[]=bad\x7fchallenge\n', |
| 148 | SCOPE + b'wwwauth[]=\xff\n', |
| 149 | SCOPE + b'wwwauth[]=' + b'x' * 16384 + b'\n', |
| 150 | ]) |
| 151 | def test_challenge_never_bypasses_scope_or_parser_before_token_lookup(adapter, payload): |
| 152 | marker = adapter.gh.with_name('token-provider-invoked') |
| 153 | adapter.gh.write_text('#!/bin/sh\n: > ' + shlex.quote(str(marker)) + '\n' |
| 154 | 'printf "%s\\n" fixture-token-never-a-real-credential\n') |
| 155 | result = helper(adapter, payload + b'\n') |
| 156 | assert result.returncode == 1 |
| 157 | assert result.stdout == result.stderr == b'' |
| 158 | assert not marker.exists() |
| 159 | |
| 160 | |
| 161 | def test_challenge_total_request_byte_limit(adapter): |
| 162 | prefix = SCOPE + b'wwwauth[]=' |
| 163 | payload = prefix + b'x' * (16384 - len(prefix) - 2) + b'\n\n' |
| 164 | assert len(payload) == 16384 |
| 165 | assert helper(adapter, payload).returncode == 0 |
| 166 | oversized = helper(adapter, payload + b'\n') |
| 167 | assert oversized.returncode == 1 and oversized.stdout == oversized.stderr == b'' |
File History
1 commit
sha256:33412f8ca4d4d6c3b72e9f6a6097ef8395b863f35f7451bf475a3cc0fb98ce9c
docs: restore exact original site presentation
Human
7 days ago