Files
NugetSecretCredential/GitTokenCredentialProvider.cs

133 lines
5.2 KiB
C#

using System;
using System.Diagnostics;
using NuGet.Protocol.Plugins;
namespace NugetSecretCredential;
sealed class GitTokenCredentialProvider : ICredentialProvider
{
private const string TokenUserName = "nugetToken"; // can be anything constant
private const int MaxCredentialHelperTimeout = 2000;
private static Lazy<Task<string?>> _credentialHelper = new Lazy<Task<string?>>(DetermineGitCredentialHelper);
public async Task<bool> CanProvideCredentialsAsync(Uri uri, CancellationToken cancel)
{
Task<string?> credHelper;
if (!_credentialHelper.IsValueCreated || !_credentialHelper.Value.IsCompleted)
{
TaskCompletionSource cancelTrigger = new TaskCompletionSource();
using (CancellationTokenRegistration cancelReg = cancel.Register(() => cancelTrigger.SetResult()))
{
credHelper = _credentialHelper.Value;
Task t = await Task.WhenAny(credHelper, cancelTrigger.Task);
if (t != credHelper)
throw new TaskCanceledException();
}
}
else
credHelper = _credentialHelper.Value;
return credHelper.Result != null;
}
public async Task<GetAuthenticationCredentialsResponse> HandleRequestAsync(GetAuthenticationCredentialsRequest request, CancellationToken cancel)
{
ProcessStartInfo psiHelper = new ProcessStartInfo();
psiHelper.FileName = _credentialHelper.Value.Result;
psiHelper.ArgumentList.Add("get");
psiHelper.RedirectStandardInput = true;
psiHelper.RedirectStandardOutput = true;
Process? pHelper = Process.Start(psiHelper);
if (pHelper == null)
// Credential helper executable not found
return new GetAuthenticationCredentialsResponse(null, null, null, null, responseCode: MessageResponseCode.Error);
pHelper.StandardInput.WriteLine("protocol=https");
pHelper.StandardInput.WriteLine("username=" + TokenUserName);
pHelper.StandardInput.WriteLine("host=" + request.Uri.DnsSafeHost);
pHelper.StandardInput.WriteLine();
await pHelper.WaitForExitAsync(cancel);
string? line;
while ((line = pHelper.StandardOutput.ReadLine()) != null)
{
const string PasswordKey = "password=";
if (line.StartsWith(PasswordKey))
// Password found
return new GetAuthenticationCredentialsResponse(
TokenUserName, line.Substring(PasswordKey.Length), null,
new List<string> { "Basic" }, MessageResponseCode.Success
);
}
// Password not found (we may be allowed to ask for it interactively)
if (!request.IsNonInteractive)
{
ProcessStartInfo psiDialog = new ProcessStartInfo();
psiDialog.FileName = "zenity";
foreach (string i in (IEnumerable<string>)["--forms", "--title", "Nuget repository", "--text", request.Uri.DnsSafeHost, "--add-password=Token"])
psiDialog.ArgumentList.Add(i);
psiDialog.RedirectStandardOutput = true;
Process? pDialog = Process.Start(psiDialog);
if (pDialog != null)
{
// Zenity dialog executable found
await pDialog.WaitForExitAsync(cancel);
string? password = pDialog.StandardOutput.ReadLine();
if (password != null && password.Length != 0 && pDialog.StandardOutput.ReadLine() == null)
{
// We have a new password, persist it also
ProcessStartInfo psiHelperStore = new ProcessStartInfo();
psiHelperStore.FileName = _credentialHelper.Value.Result;
psiHelperStore.ArgumentList.Add("store");
psiHelperStore.RedirectStandardInput = true;
Process? pHelperStore = Process.Start(psiHelperStore);
if (pHelperStore == null)
// Credential helper executable not found
return new GetAuthenticationCredentialsResponse(null, null, null, null, responseCode: MessageResponseCode.Error);
pHelperStore.StandardInput.WriteLine("protocol=https");
pHelperStore.StandardInput.WriteLine("username=" + TokenUserName);
pHelperStore.StandardInput.WriteLine("password=" + password);
pHelperStore.StandardInput.WriteLine("host=" + request.Uri.DnsSafeHost);
pHelper.StandardInput.WriteLine();
await pHelperStore.WaitForExitAsync(cancel);
return new GetAuthenticationCredentialsResponse(
TokenUserName, password, null,
new List<string> { "Basic" }, MessageResponseCode.Success
);
}
}
}
return new GetAuthenticationCredentialsResponse(null, null, null, null, responseCode: MessageResponseCode.NotFound);
}
private static async Task<string?> DetermineGitCredentialHelper()
{
CancellationTokenSource cancel = new CancellationTokenSource(MaxCredentialHelperTimeout);
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "git";
psi.ArgumentList.Add("config");
psi.ArgumentList.Add("--global");
psi.ArgumentList.Add("credential.helper");
psi.RedirectStandardOutput = true;
Process? p = Process.Start(psi);
if (p == null)
// Git executable not found
return null;
await p.WaitForExitAsync(cancel.Token);
string? result = p.StandardOutput.ReadLine();
if (p.StandardOutput.ReadLine() != null)
// Expected a single line with the credential helper executable path
return null;
return result;
}
}