-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Expand file tree
/
Copy pathHostProvider.cs
More file actions
223 lines (189 loc) · 8.94 KB
/
HostProvider.cs
File metadata and controls
223 lines (189 loc) · 8.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
using System.Text.Json;
namespace GitCredentialManager
{
/// <summary>
/// Represents a particular Git hosting service and provides for the creation of credentials to access the remote.
/// </summary>
public interface IHostProvider : IDisposable
{
/// <summary>
/// Unique identifier of the hosting provider.
/// </summary>
string Id { get; }
/// <summary>
/// Name of the hosting provider.
/// </summary>
string Name { get; }
/// <summary>
/// Supported authority identifiers.
/// </summary>
IEnumerable<string> SupportedAuthorityIds { get; }
/// <summary>
/// Determine if the <see cref="InputArguments"/> are recognized by this particular Git hosting provider.
/// </summary>
/// <param name="input">Input arguments of a Git credential query.</param>
/// <returns>True if the provider supports the Git credential request, false otherwise.</returns>
bool IsSupported(InputArguments input);
/// <summary>
/// Determine if the <see cref="HttpResponseMessage"/> identifies a recognized Git hosting provider.
/// </summary>
/// <param name="response">Response message of an endpoint query.</param>
/// <returns>True if the provider supports the host provider at the endpoint, false otherwise.</returns>
bool IsSupported(HttpResponseMessage response);
/// <summary>
/// Get a credential for accessing the remote Git repository on this hosting service.
/// </summary>
/// <param name="input">Input arguments of a Git credential query.</param>
/// <returns>A credential Git can use to authenticate to the remote repository.</returns>
Task<ICredential> GetCredentialAsync(InputArguments input);
/// <summary>
/// Store a credential for accessing the remote Git repository on this hosting service.
/// </summary>
/// <param name="input">Input arguments of a Git credential query.</param>
Task StoreCredentialAsync(InputArguments input);
/// <summary>
/// Erase a stored credential for accessing the remote Git repository on this hosting service.
/// </summary>
/// <param name="input">Input arguments of a Git credential query.</param>
Task EraseCredentialAsync(InputArguments input);
/// <summary>
/// Save the current configuration to a file.
/// </summary>
/// <param name="filePath">Path to the file where the configuration will be saved.</param>
Task SaveConfigurationAsync(string filePath);
/// <summary>
/// Load the configuration from a file.
/// </summary>
/// <param name="filePath">Path to the file from which the configuration will be loaded.</param>
Task LoadConfigurationAsync(string filePath);
}
/// <summary>
/// Represents a Git hosting provider where credentials can be stored and recalled in/from the Operating System's
/// secure credential store.
/// </summary>
public abstract class HostProvider : DisposableObject, IHostProvider
{
protected HostProvider(ICommandContext context)
{
Context = context;
}
/// <summary>
/// The current command execution context.
/// </summary>
protected ICommandContext Context { get; }
public abstract string Id { get; }
public abstract string Name { get; }
public virtual IEnumerable<string> SupportedAuthorityIds => Enumerable.Empty<string>();
public abstract bool IsSupported(InputArguments input);
public virtual bool IsSupported(HttpResponseMessage response)
{
return false;
}
/// <summary>
/// Return a string that uniquely identifies the service that a credential should be stored against.
/// </summary>
/// <remarks>
/// <para>
/// This key forms part of the identifier used to retrieve and store credentials from the OS secure
/// credential storage system. It is important the returned value is stable over time to avoid any
/// potential re-authentication requests.
/// </para>
/// <para>
/// The default implementation returns the absolute URI formed by from the <see cref="InputArguments"/>
/// without any userinfo component. Any trailing slashes are trimmed.
/// </para>
/// </remarks>
/// <param name="input">Input arguments of a Git credential query.</param>
/// <returns>Credential service name.</returns>
public virtual string GetServiceName(InputArguments input)
{
// By default we assume the service name will be the absolute URI based on the
// input arguments from Git, without any userinfo part.
return input.GetRemoteUri(includeUser: false).AbsoluteUri.TrimEnd('/');
}
/// <summary>
/// Create a new credential used for accessing the remote Git repository on this hosting service.
/// </summary>
/// <param name="input">Input arguments of a Git credential query.</param>
/// <returns>A credential Git can use to authenticate to the remote repository.</returns>
public abstract Task<ICredential> GenerateCredentialAsync(InputArguments input);
public virtual async Task<ICredential> GetCredentialAsync(InputArguments input)
{
// Try and locate an existing credential in the OS credential store
string service = GetServiceName(input);
Context.Trace.WriteLine($"Looking for existing credential in store with service={service} account={input.UserName}...");
ICredential credential = Context.CredentialStore.Get(service, input.UserName);
if (credential == null)
{
Context.Trace.WriteLine("No existing credentials found.");
// No existing credential was found, create a new one
Context.Trace.WriteLine("Creating new credential...");
credential = await GenerateCredentialAsync(input);
Context.Trace.WriteLine("Credential created.");
}
else
{
Context.Trace.WriteLine("Existing credential found.");
}
return credential;
}
public virtual Task StoreCredentialAsync(InputArguments input)
{
string service = GetServiceName(input);
// WIA-authentication is signaled to Git as an empty username/password pair
// and we will get called to 'store' these WIA credentials.
// We avoid storing empty credentials.
if (string.IsNullOrWhiteSpace(input.UserName) && string.IsNullOrWhiteSpace(input.Password))
{
Context.Trace.WriteLine("Not storing empty credential.");
}
else
{
// Add or update the credential in the store.
Context.Trace.WriteLine($"Storing credential with service={service} account={input.UserName}...");
Context.CredentialStore.AddOrUpdate(service, input.UserName, input.Password);
Context.Trace.WriteLine("Credential was successfully stored.");
}
return Task.CompletedTask;
}
public virtual Task EraseCredentialAsync(InputArguments input)
{
string service = GetServiceName(input);
// Try to locate an existing credential
Context.Trace.WriteLine($"Erasing stored credential in store with service={service} account={input.UserName}...");
if (Context.CredentialStore.Remove(service, input.UserName))
{
Context.Trace.WriteLine("Credential was successfully erased.");
}
else
{
Context.Trace.WriteLine("No credential was erased.");
}
return Task.CompletedTask;
}
public virtual async Task SaveConfigurationAsync(string filePath)
{
var configData = new Dictionary<string, object>
{
{ "Id", Id },
{ "Name", Name },
{ "SupportedAuthorityIds", SupportedAuthorityIds }
};
var options = new JsonSerializerOptions { WriteIndented = true };
var json = JsonSerializer.Serialize(configData, options);
await File.WriteAllTextAsync(filePath, json);
}
public virtual async Task LoadConfigurationAsync(string filePath)
{
var json = await File.ReadAllTextAsync(filePath);
var configData = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
// Perform any necessary actions to apply the loaded configuration to the HostProvider
}
}
}