Initial implementation
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
obj
|
||||||
|
bin
|
||||||
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceRoot}/bin/Debug/net9.0/NugetSecretCredential.dll",
|
||||||
|
"args": ["-U", "https://www.example.org"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
60
GetAuthenticationCredentialsRequestHandler.cs
Normal file
60
GetAuthenticationCredentialsRequestHandler.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal class GetAuthenticationCredentialsRequestHandler : RequestHandlerBase<GetAuthenticationCredentialsRequest, GetAuthenticationCredentialsResponse>
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyCollection<ICredentialProvider> _credentialProviders;
|
||||||
|
private readonly TimeSpan progressReporterTimeSpan = TimeSpan.FromSeconds(2);
|
||||||
|
|
||||||
|
public GetAuthenticationCredentialsRequestHandler(IReadOnlyCollection<ICredentialProvider> credentialProviders)
|
||||||
|
{
|
||||||
|
this._credentialProviders = credentialProviders ?? throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<GetAuthenticationCredentialsResponse> HandleRequestAsync(GetAuthenticationCredentialsRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
if (request?.Uri == null)
|
||||||
|
return new GetAuthenticationCredentialsResponse(
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
message: "Uri is null",
|
||||||
|
authenticationTypes: null,
|
||||||
|
responseCode: MessageResponseCode.Error);
|
||||||
|
|
||||||
|
foreach (ICredentialProvider credentialProvider in _credentialProviders)
|
||||||
|
{
|
||||||
|
if (await credentialProvider.CanProvideCredentialsAsync(request.Uri, cancel) == false)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GetAuthenticationCredentialsResponse response = await credentialProvider.HandleRequestAsync(request, cancel);
|
||||||
|
if (response != null && response.ResponseCode == MessageResponseCode.Success)
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return new GetAuthenticationCredentialsResponse(
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
message: e.Message,
|
||||||
|
authenticationTypes: null,
|
||||||
|
responseCode: MessageResponseCode.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GetAuthenticationCredentialsResponse(
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
message: null,
|
||||||
|
authenticationTypes: null,
|
||||||
|
responseCode: MessageResponseCode.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override AutomaticProgressReporter GetProgressReporter(IConnection connection, Message message, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return AutomaticProgressReporter.Create(connection, message, progressReporterTimeSpan, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
GetOperationClaimsRequestHandler.cs
Normal file
20
GetOperationClaimsRequestHandler.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal class GetOperationClaimsRequestHandler : RequestHandlerBase<GetOperationClaimsRequest, GetOperationClaimsResponse>
|
||||||
|
{
|
||||||
|
private static readonly GetOperationClaimsResponse CanProvideCredentialsResponse =
|
||||||
|
new GetOperationClaimsResponse(new List<OperationClaim> { OperationClaim.Authentication });
|
||||||
|
|
||||||
|
private static readonly GetOperationClaimsResponse EmptyGetOperationClaimsResponse =
|
||||||
|
new GetOperationClaimsResponse(new List<OperationClaim>());
|
||||||
|
|
||||||
|
public override Task<GetOperationClaimsResponse> HandleRequestAsync(GetOperationClaimsRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
return request.PackageSourceRepository != null || request.ServiceIndex != null
|
||||||
|
? Task.FromResult(EmptyGetOperationClaimsResponse)
|
||||||
|
: Task.FromResult(CanProvideCredentialsResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
132
GitTokenCredentialProvider.cs
Normal file
132
GitTokenCredentialProvider.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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=s" + 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.CanShowDialog)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
ICredentialProvider.cs
Normal file
18
ICredentialProvider.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal interface ICredentialProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if implementation can provide credentials.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uri">The of the target.</param>
|
||||||
|
Task<bool> CanProvideCredentialsAsync(Uri uri, CancellationToken cancel);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle credential request.
|
||||||
|
/// </summary>
|
||||||
|
Task<GetAuthenticationCredentialsResponse> HandleRequestAsync(GetAuthenticationCredentialsRequest request, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
12
InitializeRequestHandler.cs
Normal file
12
InitializeRequestHandler.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal class InitializeRequestHandler : RequestHandlerBase<InitializeRequest, InitializeResponse>
|
||||||
|
{
|
||||||
|
public override Task<InitializeResponse> HandleRequestAsync(InitializeRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new InitializeResponse(MessageResponseCode.Success));
|
||||||
|
}
|
||||||
|
}
|
||||||
13
NugetSecretCredential.csproj
Normal file
13
NugetSecretCredential.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="NuGet.Protocol" Version="6.13.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
24
NugetSecretCredential.sln
Normal file
24
NugetSecretCredential.sln
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NugetSecretCredential", "NugetSecretCredential.csproj", "{E0BA52C4-F4D2-9748-5150-5C8C38E7E84A}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{E0BA52C4-F4D2-9748-5150-5C8C38E7E84A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E0BA52C4-F4D2-9748-5150-5C8C38E7E84A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E0BA52C4-F4D2-9748-5150-5C8C38E7E84A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E0BA52C4-F4D2-9748-5150-5C8C38E7E84A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {5D4949AB-9A45-4602-9433-7372AE5554B8}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
145
Program.cs
Normal file
145
Program.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
private static bool _shuttingDown = false;
|
||||||
|
public static bool IsShuttingDown => Volatile.Read(ref _shuttingDown);
|
||||||
|
|
||||||
|
public static async Task<int> Main(string[] args)
|
||||||
|
{
|
||||||
|
File.WriteAllText("/home/rv/temp/nsc.log", "Starting");
|
||||||
|
|
||||||
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
List<ICredentialProvider> credentialProviders = new List<ICredentialProvider>
|
||||||
|
{
|
||||||
|
new GitTokenCredentialProvider(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool isPlugin = false;
|
||||||
|
bool isRetry = false;
|
||||||
|
bool isNonInteractive = false;
|
||||||
|
bool canShowDialog = false;
|
||||||
|
Uri? uri = null;
|
||||||
|
for (int i = 0; i < args.Length; i++)
|
||||||
|
switch (args[i].ToLower())
|
||||||
|
{
|
||||||
|
case "-p": case "-plugin": isPlugin = true; break;
|
||||||
|
case "-i": case "-isretry": isRetry = true; break;
|
||||||
|
case "-n": case "-noninteractive": isNonInteractive = true; break;
|
||||||
|
case "-c": case "-canshowdialog": canShowDialog = true; break;
|
||||||
|
case "-u": case "-uri":
|
||||||
|
if (i + 1 == args.Length)
|
||||||
|
throw new ArgumentException("Expected uri after -U");
|
||||||
|
if (!Uri.TryCreate(args[i + 1], UriKind.Absolute, out uri))
|
||||||
|
throw new ArgumentException("Invalid uri format");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
IRequestHandlers requestHandlers = new RequestHandlerCollection
|
||||||
|
{
|
||||||
|
{ MessageMethod.GetAuthenticationCredentials, new GetAuthenticationCredentialsRequestHandler(credentialProviders) },
|
||||||
|
{ MessageMethod.GetOperationClaims, new GetOperationClaimsRequestHandler() },
|
||||||
|
{ MessageMethod.Initialize, new InitializeRequestHandler() },
|
||||||
|
{ MessageMethod.SetLogLevel, new SetLogLevelRequestHandler() },
|
||||||
|
{ MessageMethod.SetCredentials, new SetCredentialsRequestHandler() },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isPlugin)
|
||||||
|
{
|
||||||
|
// Plugin mode
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (IPlugin plugin = await PluginFactory.CreateFromCurrentProcessAsync(requestHandlers, ConnectionOptions.CreateDefault(), tokenSource.Token))
|
||||||
|
{
|
||||||
|
await WaitForPluginExitAsync(plugin, TimeSpan.FromMinutes(2)).ConfigureAwait(continueOnCapturedContext: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
// When restoring from multiple sources, one of the sources will throw an unhandled TaskCanceledException
|
||||||
|
// if it has been restored successfully from a different source.
|
||||||
|
|
||||||
|
// This is probably more confusing than interesting to users, but may be helpful in debugging,
|
||||||
|
// so log the exception but not to the console.
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Stand-alone mode
|
||||||
|
if (requestHandlers.TryGet(MessageMethod.GetAuthenticationCredentials, out IRequestHandler requestHandler) && requestHandler is GetAuthenticationCredentialsRequestHandler getAuthenticationCredentialsRequestHandler)
|
||||||
|
{
|
||||||
|
if (uri == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Uri argument -U not provided");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetAuthenticationCredentialsRequest request = new GetAuthenticationCredentialsRequest(uri, isRetry: isRetry, isNonInteractive, canShowDialog);
|
||||||
|
GetAuthenticationCredentialsResponse response = await getAuthenticationCredentialsRequestHandler.HandleRequestAsync(request, default);
|
||||||
|
|
||||||
|
// Fail if credentials are not found
|
||||||
|
if (response?.ResponseCode != MessageResponseCode.Success)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("username=" + response.Username);
|
||||||
|
Console.WriteLine("password=" + response.Password);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Error " + e.GetType().Name + ": " + e.Message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async Task WaitForPluginExitAsync(IPlugin plugin, TimeSpan shutdownTimeout)
|
||||||
|
{
|
||||||
|
var beginShutdownTaskSource = new TaskCompletionSource<object>();
|
||||||
|
var endShutdownTaskSource = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
|
plugin.Connection.Faulted += (sender, a) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine(a.Exception.ToString());
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin.BeforeClose += (sender, args) =>
|
||||||
|
{
|
||||||
|
Volatile.Write(ref _shuttingDown, true);
|
||||||
|
beginShutdownTaskSource.TrySetResult(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin.Closed += (sender, a) =>
|
||||||
|
{
|
||||||
|
// beginShutdownTaskSource should already be set in BeforeClose, but just in case do it here too
|
||||||
|
beginShutdownTaskSource.TrySetResult(null);
|
||||||
|
|
||||||
|
endShutdownTaskSource.TrySetResult(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
await beginShutdownTaskSource.Task;
|
||||||
|
using (new Timer(_ => endShutdownTaskSource.TrySetCanceled(), null, shutdownTimeout, TimeSpan.FromMilliseconds(-1)))
|
||||||
|
{
|
||||||
|
await endShutdownTaskSource.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endShutdownTaskSource.Task.IsCanceled)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Plugin timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
RequestHandlerBase.cs
Normal file
40
RequestHandlerBase.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal abstract class RequestHandlerBase<TRequest, TResponse> : IRequestHandler
|
||||||
|
where TResponse : class
|
||||||
|
{
|
||||||
|
protected RequestHandlerBase()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CancellationToken IRequestHandler.CancellationToken => default;
|
||||||
|
|
||||||
|
public IConnection? Connection { get; private set; }
|
||||||
|
|
||||||
|
public virtual CancellationToken CancellationToken { get; private set; } = CancellationToken.None;
|
||||||
|
|
||||||
|
public async Task HandleResponseAsync(IConnection connection, Message message, IResponseHandler responseHandler, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
Connection = connection;
|
||||||
|
|
||||||
|
TRequest request = MessageUtilities.DeserializePayload<TRequest>(message);
|
||||||
|
|
||||||
|
TResponse? response = null;
|
||||||
|
using (GetProgressReporter(connection, message, cancel))
|
||||||
|
{
|
||||||
|
response = await HandleRequestAsync(request, cancel);
|
||||||
|
}
|
||||||
|
// If we did not send a cancel message, we must submit the response even if cancellationToken is canceled.
|
||||||
|
await responseHandler.SendResponseAsync(message, response, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<TResponse> HandleRequestAsync(TRequest request, CancellationToken cancel);
|
||||||
|
|
||||||
|
protected virtual AutomaticProgressReporter? GetProgressReporter(IConnection connection, Message message, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
RequestHandlerCollection.cs
Normal file
28
RequestHandlerCollection.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal class RequestHandlerCollection : ConcurrentDictionary<MessageMethod, IRequestHandler>, IRequestHandlers
|
||||||
|
{
|
||||||
|
public void Add(MessageMethod method, IRequestHandler handler)
|
||||||
|
{
|
||||||
|
TryAdd(method, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdate(MessageMethod method, Func<IRequestHandler> addHandlerFunc, Func<IRequestHandler, IRequestHandler> updateHandlerFunc)
|
||||||
|
{
|
||||||
|
AddOrUpdate(method, messageMethod => addHandlerFunc(), (messageMethod, requestHandler) => updateHandlerFunc(requestHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet(MessageMethod method, out IRequestHandler requestHandler)
|
||||||
|
{
|
||||||
|
return TryGetValue(method, out requestHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryRemove(MessageMethod method)
|
||||||
|
{
|
||||||
|
return TryRemove(method, out IRequestHandler _);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
SetCredentialsRequestHandler.cs
Normal file
15
SetCredentialsRequestHandler.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal class SetCredentialsRequestHandler : RequestHandlerBase<SetCredentialsRequest, SetCredentialsResponse>
|
||||||
|
{
|
||||||
|
private static readonly SetCredentialsResponse SuccessResponse = new SetCredentialsResponse(MessageResponseCode.Success);
|
||||||
|
|
||||||
|
public override Task<SetCredentialsResponse> HandleRequestAsync(SetCredentialsRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
// There's currently no way to handle proxies, so nothing we can do here
|
||||||
|
return Task.FromResult(SuccessResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
SetLogLevelRequestHandler.cs
Normal file
14
SetLogLevelRequestHandler.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using NuGet.Protocol.Plugins;
|
||||||
|
|
||||||
|
namespace NugetSecretCredential;
|
||||||
|
|
||||||
|
internal class SetLogLevelRequestHandler : RequestHandlerBase<SetLogLevelRequest, SetLogLevelResponse>
|
||||||
|
{
|
||||||
|
private static readonly SetLogLevelResponse SuccessResponse = new SetLogLevelResponse(MessageResponseCode.Success);
|
||||||
|
|
||||||
|
public override Task<SetLogLevelResponse> HandleRequestAsync(SetLogLevelRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
return Task.FromResult(SuccessResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user