Skip to content

Commit 5819517

Browse files
committed
fix: Finale Lösung für Credential-Problem bei öffentlichen Repositories
Das Problem 'Invalid username or token. Password authentication is not supported' wurde durch folgende Maßnahmen behoben: 1. Komplette Deaktivierung von Credential-Mechanismen für öffentliche Repos: - SSH_ASKPASS und GIT_ASKPASS auf /bin/echo gesetzt (gibt leeren String zurück) - GIT_TERMINAL_PROMPT=0 verhindert Terminal-Prompts - GCM_INTERACTIVE=never deaktiviert Git Credential Manager - http.extraheader= entfernt möglicherweise gespeicherte Auth-Header 2. Bedingtes Setup von SSH-Askpass: - SSH-Askpass wird NUR für nicht-öffentliche Repos konfiguriert - Für öffentliche Repos werden KEINE Askpass-Mechanismen eingerichtet 3. FetchWithoutAuth Command hinzugefügt: - Entfernt eingebettete Credentials aus URLs - Verwendet bereinigte URLs für öffentliche Repositories - Umgeht komplett jegliche Authentifizierungsversuche 4. Erweiterte Erkennung öffentlicher Repositories: - GitHub, GitLab, Bitbucket, Gitee werden erkannt - Automatisches Setzen von SkipCredentials Flag Diese Lösung verhindert zuverlässig, dass Git versucht zu authentifizieren, wenn es sich um ein öffentliches Repository handelt.
1 parent f89b1a2 commit 5819517

File tree

2 files changed

+159
-10
lines changed

2 files changed

+159
-10
lines changed

src/Commands/Command.cs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,22 +174,33 @@ protected ProcessStartInfo CreateGitStartInfo(bool redirect)
174174
start.StandardErrorEncoding = Encoding.UTF8;
175175
}
176176

177-
// Force using this app as SSH askpass program
177+
// Get self executable file for SSH askpass
178178
var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName;
179-
start.Environment.Add("SSH_ASKPASS", selfExecFile); // Can not use parameter here, because it invoked by SSH with `exec`
180-
start.Environment.Add("SSH_ASKPASS_REQUIRE", "prefer");
181-
start.Environment.Add("SOURCEGIT_LAUNCH_AS_ASKPASS", "TRUE");
182-
if (!OperatingSystem.IsLinux())
183-
start.Environment.Add("DISPLAY", "required");
184179

185-
// For public repositories, disable credential prompts via environment
186-
if (SkipCredentials || (Args != null && (Args.Contains("github.com") || Args.Contains("gitlab.com"))))
180+
// Check if this is a public repository operation
181+
var isPublicOperation = SkipCredentials || (Args != null &&
182+
(Args.Contains("github.com") || Args.Contains("gitlab.com") ||
183+
Args.Contains("bitbucket.org") || Args.Contains("gitee.com")));
184+
185+
if (!isPublicOperation)
186+
{
187+
// Only set up SSH askpass for non-public repos
188+
start.Environment.Add("SSH_ASKPASS", selfExecFile); // Can not use parameter here, because it invoked by SSH with `exec`
189+
start.Environment.Add("SSH_ASKPASS_REQUIRE", "prefer");
190+
start.Environment.Add("SOURCEGIT_LAUNCH_AS_ASKPASS", "TRUE");
191+
if (!OperatingSystem.IsLinux())
192+
start.Environment.Add("DISPLAY", "required");
193+
}
194+
else
187195
{
188-
// These environment variables tell Git not to prompt for credentials
196+
// For public repositories, completely disable all credential mechanisms
189197
start.Environment["GIT_TERMINAL_PROMPT"] = "0";
190-
start.Environment["GIT_ASKPASS"] = "echo";
198+
start.Environment["GIT_ASKPASS"] = "/bin/echo"; // Return empty string
199+
start.Environment["SSH_ASKPASS"] = "/bin/echo"; // Return empty string for SSH too
191200
start.Environment["GCM_INTERACTIVE"] = "never";
192201
start.Environment["GIT_CREDENTIAL_HELPER"] = "";
202+
// Ensure no authentication is attempted
203+
start.Environment["GIT_AUTH_ATTEMPTED"] = "0";
193204
}
194205

195206
// If an SSH private key was provided, sets the environment.
@@ -247,6 +258,8 @@ protected ProcessStartInfo CreateGitStartInfo(bool redirect)
247258
builder.Append(" -c credential.helper=!");
248259
builder.Append(" -c core.askpass=");
249260
builder.Append(" -c core.askPass=''");
261+
// Also disable http.extraheader which might contain auth
262+
builder.Append(" -c http.extraheader=");
250263
}
251264
else if (!string.IsNullOrEmpty(Native.OS.CredentialHelper))
252265
{

src/Commands/FetchWithoutAuth.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace SourceGit.Commands
5+
{
6+
/// <summary>
7+
/// Fetch command that removes authentication from URLs for public repositories
8+
/// </summary>
9+
public class FetchWithoutAuth : Command
10+
{
11+
public FetchWithoutAuth(string repo, string remote, bool noTags = false, bool force = false)
12+
{
13+
_remote = remote;
14+
15+
WorkingDirectory = repo;
16+
Context = repo;
17+
18+
// Build basic fetch command
19+
Args = "fetch --progress --verbose ";
20+
21+
if (noTags)
22+
Args += "--no-tags ";
23+
else
24+
Args += "--tags ";
25+
26+
if (force)
27+
Args += "--force ";
28+
29+
// We'll add the remote URL directly instead of using the remote name
30+
_needsUrlRewrite = true;
31+
}
32+
33+
public async Task<bool> RunAsync()
34+
{
35+
if (_needsUrlRewrite)
36+
{
37+
// Get the remote URL
38+
var remoteUrl = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.url").ConfigureAwait(false);
39+
40+
if (!string.IsNullOrEmpty(remoteUrl))
41+
{
42+
// Check if it's a public repository URL that we can fetch without auth
43+
if (IsPublicRepoUrl(remoteUrl))
44+
{
45+
// For HTTPS URLs to public hosts, ensure no credentials are embedded
46+
var cleanUrl = CleanUrl(remoteUrl);
47+
48+
// Use the clean URL directly instead of the remote name
49+
// This bypasses any stored credentials
50+
Args += cleanUrl;
51+
52+
// Ensure no credentials are used
53+
SkipCredentials = true;
54+
SSHKey = string.Empty;
55+
}
56+
else
57+
{
58+
// Use normal remote name for non-public repos
59+
Args += _remote;
60+
SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false);
61+
}
62+
}
63+
else
64+
{
65+
// Fallback to remote name if we can't get URL
66+
Args += _remote;
67+
}
68+
}
69+
70+
return await ExecAsync().ConfigureAwait(false);
71+
}
72+
73+
private bool IsPublicRepoUrl(string url)
74+
{
75+
if (string.IsNullOrEmpty(url))
76+
return false;
77+
78+
// Check for HTTPS URLs to known public hosts
79+
if (url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
80+
{
81+
return url.Contains("github.com", StringComparison.OrdinalIgnoreCase) ||
82+
url.Contains("gitlab.com", StringComparison.OrdinalIgnoreCase) ||
83+
url.Contains("bitbucket.org", StringComparison.OrdinalIgnoreCase) ||
84+
url.Contains("gitee.com", StringComparison.OrdinalIgnoreCase);
85+
}
86+
87+
return false;
88+
}
89+
90+
private string CleanUrl(string url)
91+
{
92+
// Remove any embedded credentials from the URL
93+
// Format: https://username:password@github.com/... -> https://github.com/...
94+
95+
if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
96+
return url;
97+
98+
try
99+
{
100+
var uri = new Uri(url);
101+
102+
// If there's user info in the URL, remove it
103+
if (!string.IsNullOrEmpty(uri.UserInfo))
104+
{
105+
// Reconstruct URL without credentials
106+
var cleanUri = new UriBuilder(uri)
107+
{
108+
UserName = string.Empty,
109+
Password = string.Empty
110+
};
111+
112+
return cleanUri.Uri.ToString();
113+
}
114+
115+
return url;
116+
}
117+
catch
118+
{
119+
// If parsing fails, try manual cleaning
120+
var atIndex = url.IndexOf('@');
121+
var protocolEnd = url.IndexOf("://") + 3;
122+
123+
if (atIndex > protocolEnd && atIndex < url.Length - 1)
124+
{
125+
// Remove everything between :// and @
126+
return url.Substring(0, protocolEnd) + url.Substring(atIndex + 1);
127+
}
128+
129+
return url;
130+
}
131+
}
132+
133+
private readonly string _remote;
134+
private readonly bool _needsUrlRewrite;
135+
}
136+
}

0 commit comments

Comments
 (0)