mirror of
https://github.com/actions/runner.git
synced 2025-12-10 04:06:57 +00:00
Compare commits
10 Commits
v2.289.3
...
users/juli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
857770cd3d | ||
|
|
38b03139f1 | ||
|
|
c1154c0ec9 | ||
|
|
d7f2f3085c | ||
|
|
1404a73762 | ||
|
|
3ea3b5ff59 | ||
|
|
b37aa3254f | ||
|
|
9b5eab9c81 | ||
|
|
132d06dc9b | ||
|
|
f2db563c89 |
@@ -139,7 +139,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public bool HasCredentials()
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
Trace.Info("HasCredentials()");
|
||||
bool credsStored = (new FileInfo(_credFilePath)).Exists;
|
||||
Trace.Info("stored {0}", credsStored);
|
||||
@@ -149,14 +148,13 @@ namespace GitHub.Runner.Common
|
||||
public bool IsConfigured()
|
||||
{
|
||||
Trace.Info("IsConfigured()");
|
||||
bool configured = HostContext.RunMode == RunMode.Local || (new FileInfo(_configFilePath)).Exists;
|
||||
bool configured = new FileInfo(_configFilePath).Exists;
|
||||
Trace.Info("IsConfigured: {0}", configured);
|
||||
return configured;
|
||||
}
|
||||
|
||||
public bool IsServiceConfigured()
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
Trace.Info("IsServiceConfigured()");
|
||||
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
|
||||
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
|
||||
@@ -165,7 +163,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public CredentialData GetCredentials()
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
if (_creds == null)
|
||||
{
|
||||
_creds = IOUtil.LoadObject<CredentialData>(_credFilePath);
|
||||
@@ -195,7 +192,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public void SaveCredential(CredentialData credential)
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
|
||||
if (File.Exists(_credFilePath))
|
||||
{
|
||||
@@ -211,7 +207,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public void SaveSettings(RunnerSettings settings)
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
Trace.Info("Saving runner settings.");
|
||||
if (File.Exists(_configFilePath))
|
||||
{
|
||||
@@ -227,13 +222,11 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public void DeleteCredential()
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
||||
}
|
||||
|
||||
public void DeleteSettings()
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
IOUtil.Delete(_configFilePath, default(CancellationToken));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
public enum RunMode
|
||||
{
|
||||
Normal, // Keep "Normal" first (default value).
|
||||
Local,
|
||||
}
|
||||
|
||||
public enum WellKnownDirectory
|
||||
{
|
||||
Bin,
|
||||
@@ -164,9 +158,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public static class Configuration
|
||||
{
|
||||
public static readonly string AAD = "AAD";
|
||||
public static readonly string OAuthAccessToken = "OAuthAccessToken";
|
||||
public static readonly string PAT = "PAT";
|
||||
public static readonly string OAuth = "OAuth";
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
public interface IHostContext : IDisposable
|
||||
{
|
||||
RunMode RunMode { get; set; }
|
||||
StartupType StartupType { get; set; }
|
||||
CancellationToken RunnerShutdownToken { get; }
|
||||
ShutdownReason RunnerShutdownReason { get; }
|
||||
@@ -58,7 +57,6 @@ namespace GitHub.Runner.Common
|
||||
private readonly ProductInfoHeaderValue _userAgent = new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version);
|
||||
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
|
||||
private object _perfLock = new object();
|
||||
private RunMode _runMode = RunMode.Normal;
|
||||
private Tracing _trace;
|
||||
private Tracing _vssTrace;
|
||||
private Tracing _httpTrace;
|
||||
@@ -194,20 +192,6 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
public RunMode RunMode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _runMode;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_trace.Info($"Set run mode: {value}");
|
||||
_runMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDirectory(WellKnownDirectory directory)
|
||||
{
|
||||
string path;
|
||||
|
||||
@@ -32,11 +32,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public async Task ConnectAsync(VssConnection jobConnection)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connection = jobConnection;
|
||||
int attemptCount = 5;
|
||||
while (!_connection.HasAuthenticated && attemptCount-- > 0)
|
||||
@@ -73,88 +68,48 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult<TaskLog>(null);
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult<TaskAttachment>(null);
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.CreateAttachmentAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, type, name, uploadStream, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult<TaskLog>(null);
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.CreateLogAsync(scopeIdentifier, hubName, planId, log, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult<Timeline>(null);
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.CreateTimelineAsync(scopeIdentifier, hubName, planId, new Timeline(timelineId), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult<List<TimelineRecord>>(null);
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.UpdateTimelineRecordsAsync(scopeIdentifier, hubName, planId, timelineId, records, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.RaisePlanEventAsync(scopeIdentifier, hubName, planId, eventData, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult<Timeline>(null);
|
||||
}
|
||||
|
||||
CheckConnection();
|
||||
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,6 @@ namespace GitHub.Runner.Common
|
||||
private Task[] _allDequeueTasks;
|
||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new TaskCompletionSource<int>();
|
||||
private bool _queueInProcess = false;
|
||||
private ITerminal _term;
|
||||
|
||||
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||
|
||||
@@ -85,11 +84,6 @@ namespace GitHub.Runner.Common
|
||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest)
|
||||
{
|
||||
Trace.Entering();
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
_term = HostContext.GetService<ITerminal>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_queueInProcess)
|
||||
{
|
||||
@@ -129,11 +123,6 @@ namespace GitHub.Runner.Common
|
||||
// TimelineUpdate queue error will become critical when timeline records contain output variabls.
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_queueInProcess)
|
||||
{
|
||||
Trace.Info("No-op, all queue process tasks have been stopped.");
|
||||
@@ -169,32 +158,11 @@ namespace GitHub.Runner.Common
|
||||
public void QueueWebConsoleLine(Guid stepRecordId, string line)
|
||||
{
|
||||
Trace.Verbose("Enqueue web console line queue: {0}", line);
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
if ((line ?? string.Empty).StartsWith("##[section]"))
|
||||
{
|
||||
Console.WriteLine("******************************************************************************");
|
||||
Console.WriteLine(line.Substring("##[section]".Length));
|
||||
Console.WriteLine("******************************************************************************");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(line);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_webConsoleLineQueue.Enqueue(new ConsoleLineInfo(stepRecordId, line));
|
||||
}
|
||||
|
||||
public void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
|
||||
ArgUtil.NotEmpty(timelineRecordId, nameof(timelineRecordId));
|
||||
|
||||
@@ -215,11 +183,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
|
||||
ArgUtil.NotNull(timelineRecord, nameof(timelineRecord));
|
||||
ArgUtil.NotEmpty(timelineRecord.Id, nameof(timelineRecord.Id));
|
||||
|
||||
@@ -66,11 +66,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var createGenericConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
||||
var createMessageConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
|
||||
var createRequestConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
|
||||
@@ -303,29 +298,18 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult(JsonUtility.FromString<TaskAgentJobRequest>("{ lockedUntil: \"" + DateTime.Now.Add(TimeSpan.FromMinutes(5)).ToString("u") + "\" }"));
|
||||
}
|
||||
|
||||
CheckConnection(RunnerConnectionType.JobRequest);
|
||||
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
return Task.FromResult<TaskAgentJobRequest>(null);
|
||||
}
|
||||
|
||||
CheckConnection(RunnerConnectionType.JobRequest);
|
||||
return _requestTaskAgentClient.FinishAgentRequestAsync(poolId, requestId, lockToken, finishTime, result, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
CheckConnection(RunnerConnectionType.JobRequest);
|
||||
return _requestTaskAgentClient.GetAgentRequestAsync(poolId, requestId, cancellationToken: cancellationToken);
|
||||
}
|
||||
@@ -335,7 +319,6 @@ namespace GitHub.Runner.Common
|
||||
//-----------------------------------------------------------------
|
||||
public Task<List<PackageMetadata>> GetPackagesAsync(string packageType, string platform, int top, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.GetPackagesAsync(packageType, platform, top, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
_term.WriteLine("| |", ConsoleColor.White);
|
||||
_term.WriteLine("--------------------------------------------------------------------------------", ConsoleColor.White);
|
||||
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
Trace.Info(nameof(ConfigureAsync));
|
||||
if (IsConfigured())
|
||||
{
|
||||
@@ -397,7 +396,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
|
||||
public async Task UnconfigureAsync(CommandSettings command)
|
||||
{
|
||||
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
|
||||
string currentAction = string.Empty;
|
||||
|
||||
_term.WriteSection("Runner removal");
|
||||
@@ -520,7 +518,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
Trace.Info(nameof(GetCredentialProvider));
|
||||
|
||||
var credentialManager = HostContext.GetService<ICredentialManager>();
|
||||
string authType = command.GetAuth(defaultValue: Constants.Configuration.AAD);
|
||||
string authType = command.GetAuth(defaultValue: Constants.Configuration.OAuthAccessToken);
|
||||
|
||||
// Create the credential.
|
||||
Trace.Info("Creating credential for auth: {0}", authType);
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ Constants.Configuration.AAD, typeof(AadDeviceCodeAccessToken)},
|
||||
{ Constants.Configuration.PAT, typeof(PersonalAccessToken)},
|
||||
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
||||
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
||||
};
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Services.Client;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.OAuth;
|
||||
@@ -37,125 +29,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
||||
}
|
||||
|
||||
public sealed class AadDeviceCodeAccessToken : CredentialProvider
|
||||
{
|
||||
private string _azureDevOpsClientId = "97877f11-0fc6-4aee-b1ff-febb0519dd00";
|
||||
|
||||
public override Boolean RequireInteractive => true;
|
||||
|
||||
public AadDeviceCodeAccessToken() : base(Constants.Configuration.AAD) { }
|
||||
|
||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
|
||||
trace.Info(nameof(GetVssCredentials));
|
||||
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
|
||||
|
||||
CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Url, out string serverUrl);
|
||||
ArgUtil.NotNullOrEmpty(serverUrl, nameof(serverUrl));
|
||||
|
||||
var tenantAuthorityUrl = GetTenantAuthorityUrl(context, serverUrl);
|
||||
if (tenantAuthorityUrl == null)
|
||||
{
|
||||
throw new NotSupportedException($"'{serverUrl}' is not backed by Azure Active Directory.");
|
||||
}
|
||||
|
||||
LoggerCallbackHandler.LogCallback = ((LogLevel level, string message, bool containsPii) =>
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case LogLevel.Information:
|
||||
trace.Info(message);
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
trace.Error(message);
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
trace.Warning(message);
|
||||
break;
|
||||
default:
|
||||
trace.Verbose(message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
LoggerCallbackHandler.UseDefaultLogging = false;
|
||||
AuthenticationContext ctx = new AuthenticationContext(tenantAuthorityUrl.AbsoluteUri);
|
||||
var queryParameters = $"redirect_uri={Uri.EscapeDataString(new Uri(serverUrl).GetLeftPart(UriPartial.Authority))}";
|
||||
DeviceCodeResult codeResult = ctx.AcquireDeviceCodeAsync("https://management.core.windows.net/", _azureDevOpsClientId, queryParameters).GetAwaiter().GetResult();
|
||||
|
||||
var term = context.GetService<ITerminal>();
|
||||
term.WriteLine($"Please finish AAD device code flow in browser ({codeResult.VerificationUrl}), user code: {codeResult.UserCode}");
|
||||
if (string.Equals(CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser], bool.TrueString, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
Process.Start(new ProcessStartInfo() { FileName = codeResult.VerificationUrl, UseShellExecute = true });
|
||||
#elif OS_LINUX
|
||||
Process.Start(new ProcessStartInfo() { FileName = "xdg-open", Arguments = codeResult.VerificationUrl });
|
||||
#else
|
||||
Process.Start(new ProcessStartInfo() { FileName = "open", Arguments = codeResult.VerificationUrl });
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// not able to open browser, ex: xdg-open/open is not installed.
|
||||
trace.Error(ex);
|
||||
term.WriteLine($"Fail to open browser. {codeResult.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
AuthenticationResult authResult = ctx.AcquireTokenByDeviceCodeAsync(codeResult).GetAwaiter().GetResult();
|
||||
ArgUtil.NotNull(authResult, nameof(authResult));
|
||||
trace.Info($"receive AAD auth result with {authResult.AccessTokenType} token");
|
||||
|
||||
var aadCred = new VssAadCredential(new VssAadToken(authResult));
|
||||
VssCredentials creds = new VssCredentials(null, aadCred, CredentialPromptType.DoNotPrompt);
|
||||
trace.Info("cred created");
|
||||
|
||||
return creds;
|
||||
}
|
||||
|
||||
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
|
||||
trace.Info(nameof(EnsureCredential));
|
||||
ArgUtil.NotNull(command, nameof(command));
|
||||
CredentialData.Data[Constants.Runner.CommandLine.Args.Url] = serverUrl;
|
||||
CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser] = command.GetAutoLaunchBrowser().ToString();
|
||||
}
|
||||
|
||||
private Uri GetTenantAuthorityUrl(IHostContext context, string serverUrl)
|
||||
{
|
||||
using (var client = new HttpClient(context.CreateHttpClientHandler()))
|
||||
{
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
|
||||
client.DefaultRequestHeaders.UserAgent.Clear();
|
||||
client.DefaultRequestHeaders.UserAgent.AddRange(VssClientHttpRequestSettings.Default.UserAgent);
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Head, $"{serverUrl.Trim('/')}/_apis/connectiondata");
|
||||
var response = client.SendAsync(requestMessage).GetAwaiter().GetResult();
|
||||
|
||||
// Get the tenant from the Login URL, MSA backed accounts will not return `Bearer` www-authenticate header.
|
||||
var bearerResult = response.Headers.WwwAuthenticate.Where(p => p.Scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
||||
if (bearerResult != null && bearerResult.Parameter.StartsWith("authorization_uri=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var authorizationUri = bearerResult.Parameter.Substring("authorization_uri=".Length);
|
||||
if (Uri.TryCreate(authorizationUri, UriKind.Absolute, out Uri aadTenantUrl))
|
||||
{
|
||||
return aadTenantUrl;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class OAuthAccessTokenCredential : CredentialProvider
|
||||
{
|
||||
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
|
||||
@@ -190,42 +63,4 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PersonalAccessToken : CredentialProvider
|
||||
{
|
||||
public PersonalAccessToken() : base(Constants.Configuration.PAT) { }
|
||||
|
||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
|
||||
trace.Info(nameof(GetVssCredentials));
|
||||
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
|
||||
string token;
|
||||
if (!CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Token, out token))
|
||||
{
|
||||
token = null;
|
||||
}
|
||||
|
||||
ArgUtil.NotNullOrEmpty(token, nameof(token));
|
||||
|
||||
trace.Info("token retrieved: {0} chars", token.Length);
|
||||
|
||||
// PAT uses a basic credential
|
||||
VssBasicCredential basicCred = new VssBasicCredential("ActionsRunner", token);
|
||||
VssCredentials creds = new VssCredentials(null, basicCred, CredentialPromptType.DoNotPrompt);
|
||||
trace.Info("cred created");
|
||||
|
||||
return creds;
|
||||
}
|
||||
|
||||
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
|
||||
trace.Info(nameof(EnsureCredential));
|
||||
ArgUtil.NotNull(command, nameof(command));
|
||||
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace GitHub.Runner.Listener
|
||||
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
||||
bool Cancel(JobCancelMessage message);
|
||||
Task WaitAsync(CancellationToken token);
|
||||
TaskResult GetLocalRunJobResult(AgentJobRequestMessage message);
|
||||
Task ShutdownAsync();
|
||||
}
|
||||
|
||||
@@ -165,11 +164,6 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
public TaskResult GetLocalRunJobResult(AgentJobRequestMessage message)
|
||||
{
|
||||
return _localRunJobResult.Value[message.RequestId];
|
||||
}
|
||||
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
Trace.Info($"Shutting down JobDispatcher. Make sure all WorkerDispatcher has finished.");
|
||||
@@ -373,37 +367,29 @@ namespace GitHub.Runner.Listener
|
||||
ArgUtil.NotNullOrEmpty(pipeHandleOut, nameof(pipeHandleOut));
|
||||
ArgUtil.NotNullOrEmpty(pipeHandleIn, nameof(pipeHandleIn));
|
||||
|
||||
if (HostContext.RunMode == RunMode.Normal)
|
||||
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
|
||||
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
||||
{
|
||||
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
|
||||
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
||||
if (!string.IsNullOrEmpty(stdout.Data))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stdout.Data))
|
||||
lock (_outputLock)
|
||||
{
|
||||
lock (_outputLock)
|
||||
{
|
||||
workerOutput.Add(stdout.Data);
|
||||
}
|
||||
workerOutput.Add(stdout.Data);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Save STDERR from worker, worker will use STDERR on crash.
|
||||
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stderr.Data))
|
||||
{
|
||||
lock (_outputLock)
|
||||
{
|
||||
workerOutput.Add(stderr.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (HostContext.RunMode == RunMode.Local)
|
||||
// Save STDERR from worker, worker will use STDERR on crash.
|
||||
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
|
||||
{
|
||||
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => Console.WriteLine(e.Data);
|
||||
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) => Console.WriteLine(e.Data);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(stderr.Data))
|
||||
{
|
||||
lock (_outputLock)
|
||||
{
|
||||
workerOutput.Add(stderr.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Start the child process.
|
||||
HostContext.WritePerfCounter("StartingWorkerProcess");
|
||||
@@ -730,11 +716,6 @@ namespace GitHub.Runner.Listener
|
||||
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
|
||||
{
|
||||
Trace.Entering();
|
||||
if (HostContext.RunMode == RunMode.Local)
|
||||
{
|
||||
_localRunJobResult.Value[message.RequestId] = result;
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlanUtil.GetFeatures(message.Plan).HasFlag(PlanFeatures.JobCompletedPlanEvent))
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.19.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace GitHub.Runner.Worker
|
||||
TaskResult? CommandResult { get; set; }
|
||||
CancellationToken CancellationToken { get; }
|
||||
List<ServiceEndpoint> Endpoints { get; }
|
||||
List<SecureFile> SecureFiles { get; }
|
||||
|
||||
PlanFeatures Features { get; }
|
||||
Variables Variables { get; }
|
||||
@@ -136,7 +135,6 @@ namespace GitHub.Runner.Worker
|
||||
public Task ForceCompleted => _forceCompleted.Task;
|
||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
public List<ServiceEndpoint> Endpoints { get; private set; }
|
||||
public List<SecureFile> SecureFiles { get; private set; }
|
||||
public Variables Variables { get; private set; }
|
||||
public Dictionary<string, string> IntraActionState { get; private set; }
|
||||
public HashSet<string> OutputVariables => _outputvariables;
|
||||
@@ -257,7 +255,6 @@ namespace GitHub.Runner.Worker
|
||||
child.Features = Features;
|
||||
child.Variables = Variables;
|
||||
child.Endpoints = Endpoints;
|
||||
child.SecureFiles = SecureFiles;
|
||||
if (intraActionState == null)
|
||||
{
|
||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -549,9 +546,6 @@ namespace GitHub.Runner.Worker
|
||||
// Endpoints
|
||||
Endpoints = message.Resources.Endpoints;
|
||||
|
||||
// SecureFiles
|
||||
SecureFiles = message.Resources.SecureFiles;
|
||||
|
||||
// Variables
|
||||
Variables = new Variables(HostContext, message.Variables);
|
||||
|
||||
|
||||
@@ -178,15 +178,6 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add masks for secure file download tickets
|
||||
foreach (SecureFile file in message.Resources.SecureFiles ?? new List<SecureFile>())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file.Ticket))
|
||||
{
|
||||
HostContext.SecretMasker.AddValue(file.Ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCulture(Pipelines.AgentJobRequestMessage message)
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
internal static class CookieUtility
|
||||
{
|
||||
public static readonly String AcsMetadataRetrievalExceptionText = "Unable to retrieve ACS Metadata from '{0}'";
|
||||
public static readonly String FedAuthCookieName = "FedAuth";
|
||||
public static readonly String WindowsLiveSignOutUrl = "https://login.live.com/uilogout.srf";
|
||||
public static readonly Uri WindowsLiveCookieDomain = new Uri("https://login.live.com/");
|
||||
|
||||
public static CookieCollection GetFederatedCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
|
||||
Cookie cookie = GetCookieEx(cookieDomainAndPath, FedAuthCookieName).FirstOrDefault();
|
||||
|
||||
if (cookie != null)
|
||||
{
|
||||
result = new CookieCollection();
|
||||
result.Add(cookie);
|
||||
|
||||
for (Int32 x = 1; x < 50; x++)
|
||||
{
|
||||
String cookieName = FedAuthCookieName + x;
|
||||
cookie = GetCookieEx(cookieDomainAndPath, cookieName).FirstOrDefault();
|
||||
|
||||
if (cookie != null)
|
||||
{
|
||||
result.Add(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CookieCollection GetFederatedCookies(String[] token)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
|
||||
if (token != null && token.Length > 0 && token[0] != null)
|
||||
{
|
||||
result = new CookieCollection();
|
||||
result.Add(new Cookie(FedAuthCookieName, token[0]));
|
||||
|
||||
for (Int32 x = 1; x < token.Length; x++)
|
||||
{
|
||||
String cookieName = FedAuthCookieName + x;
|
||||
|
||||
if (token[x] != null)
|
||||
{
|
||||
Cookie cookie = new Cookie(cookieName, token[x]);
|
||||
cookie.HttpOnly = true;
|
||||
result.Add(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CookieCollection GetFederatedCookies(IHttpResponse webResponse)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
IEnumerable<String> cookies = null;
|
||||
|
||||
if (webResponse.Headers.TryGetValues("Set-Cookie", out cookies))
|
||||
{
|
||||
foreach (String cookie in cookies)
|
||||
{
|
||||
if (cookie != null && cookie.StartsWith(CookieUtility.FedAuthCookieName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Only take the security token field of the cookie, and discard the rest
|
||||
String fedAuthToken = cookie.Split(';').FirstOrDefault();
|
||||
Int32 index = fedAuthToken.IndexOf('=');
|
||||
|
||||
if (index > 0 && index < fedAuthToken.Length - 1)
|
||||
{
|
||||
String name = fedAuthToken.Substring(0, index);
|
||||
String value = fedAuthToken.Substring(index + 1);
|
||||
|
||||
result = result ?? new CookieCollection();
|
||||
result.Add(new Cookie(name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CookieCollection GetAllCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
List<Cookie> cookies = GetCookieEx(cookieDomainAndPath, null);
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
result = new CookieCollection();
|
||||
}
|
||||
|
||||
result.Add(cookie);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void DeleteFederatedCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection cookies = GetFederatedCookies(cookieDomainAndPath);
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteWindowsLiveCookies()
|
||||
{
|
||||
DeleteAllCookies(WindowsLiveCookieDomain);
|
||||
}
|
||||
|
||||
public static void DeleteAllCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection cookies = GetAllCookies(cookieDomainAndPath);
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public const UInt32 INTERNET_COOKIE_HTTPONLY = 0x00002000;
|
||||
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
static extern bool InternetGetCookieEx(
|
||||
String url, String cookieName, StringBuilder cookieData, ref Int32 size, UInt32 flags, IntPtr reserved);
|
||||
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
static extern bool InternetSetCookieEx(
|
||||
String url, String cookieName, String cookieData, UInt32 flags, IntPtr reserved);
|
||||
|
||||
public static Boolean DeleteCookieEx(Uri cookiePath, String cookieName)
|
||||
{
|
||||
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
|
||||
|
||||
String path = cookiePath.ToString();
|
||||
if (!path.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
|
||||
DateTime expiration = DateTime.UtcNow.AddYears(-1);
|
||||
String cookieData = String.Format(CultureInfo.InvariantCulture, "{0}=0;expires={1};path=/;domain={2};httponly", cookieName, expiration.ToString("R"), cookiePath.Host);
|
||||
|
||||
return InternetSetCookieEx(path, null, cookieData, flags, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public static Boolean SetCookiesEx(
|
||||
Uri cookiePath,
|
||||
CookieCollection cookies)
|
||||
{
|
||||
String path = cookiePath.ToString();
|
||||
if (!path.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
|
||||
Boolean successful = true;
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
// This means it doesn't expire
|
||||
if (cookie.Expires.Year == 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String cookieData = String.Format(CultureInfo.InvariantCulture,
|
||||
"{0}; path={1}; domain={2}; expires={3}; httponly",
|
||||
cookie.Value,
|
||||
cookie.Path,
|
||||
cookie.Domain,
|
||||
cookie.Expires.ToString("ddd, dd-MMM-yyyy HH:mm:ss 'GMT'"));
|
||||
|
||||
successful &= InternetSetCookieEx(path, cookie.Name, cookieData, INTERNET_COOKIE_HTTPONLY, IntPtr.Zero);
|
||||
}
|
||||
return successful;
|
||||
}
|
||||
|
||||
public static List<Cookie> GetCookieEx(Uri cookiePath, String cookieName)
|
||||
{
|
||||
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
|
||||
|
||||
List<Cookie> cookies = new List<Cookie>();
|
||||
Int32 size = 256;
|
||||
StringBuilder cookieData = new StringBuilder(size);
|
||||
String path = cookiePath.ToString();
|
||||
if (!path.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
|
||||
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
return cookies;
|
||||
}
|
||||
|
||||
cookieData = new StringBuilder(size);
|
||||
|
||||
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
|
||||
{
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
|
||||
if (cookieData.Length > 0)
|
||||
{
|
||||
String[] cookieSections = cookieData.ToString().Split(new char[] { ';' });
|
||||
|
||||
foreach (String cookieSection in cookieSections)
|
||||
{
|
||||
String[] cookieParts = cookieSection.Split(new char[] { '=' }, 2);
|
||||
|
||||
if (cookieParts.Length == 2)
|
||||
{
|
||||
Cookie cookie = new Cookie();
|
||||
cookie.Name = cookieParts[0].TrimStart();
|
||||
cookie.Value = cookieParts[1];
|
||||
cookie.HttpOnly = true;
|
||||
cookies.Add(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Security;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently it is impossible to get whether prompting is allowed from the credential itself without reproducing the logic
|
||||
/// used by VssClientCredentials. Since this is a stop gap solution to get Windows integrated authentication to work against
|
||||
/// AAD via ADFS for now this class will only support that one, non-interactive flow. We need to assess how much we want to
|
||||
/// invest in this legacy stack rather than recommending people move to the VssConnect API for future authentication needs.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssAadCredential : FederatedCredential
|
||||
{
|
||||
private string username;
|
||||
private SecureString password;
|
||||
|
||||
public VssAadCredential()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public VssAadCredential(VssAadToken initialToken)
|
||||
: base(initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
public VssAadCredential(string username)
|
||||
: base(null)
|
||||
{
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public VssAadCredential(string username, string password)
|
||||
: base(null)
|
||||
{
|
||||
this.username = username;
|
||||
|
||||
if (password != null)
|
||||
{
|
||||
this.password = new SecureString();
|
||||
|
||||
foreach (char character in password)
|
||||
{
|
||||
this.password.AppendChar(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VssAadCredential(string username, SecureString password)
|
||||
: base(null)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Aad;
|
||||
}
|
||||
}
|
||||
|
||||
internal string Username
|
||||
{
|
||||
get
|
||||
{
|
||||
return username;
|
||||
}
|
||||
}
|
||||
|
||||
internal SecureString Password => password;
|
||||
|
||||
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
bool isNonAuthenticationChallenge = false;
|
||||
return VssFederatedCredential.IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? false;
|
||||
}
|
||||
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response)
|
||||
{
|
||||
if (response == null && base.InitialToken == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new VssAadTokenProvider(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
internal static class VssAadSettings
|
||||
{
|
||||
public const string DefaultAadInstance = "https://login.microsoftonline.com/";
|
||||
|
||||
public const string CommonTenant = "common";
|
||||
|
||||
// VSTS service principal.
|
||||
public const string Resource = "499b84ac-1321-427f-aa17-267ca6975798";
|
||||
|
||||
// Visual Studio IDE client ID originally provisioned by Azure Tools.
|
||||
public const string Client = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
|
||||
|
||||
// AAD Production Application tenant.
|
||||
private const string ApplicationTenantId = "f8cdef31-a31e-4b4a-93e4-5f571e91255a";
|
||||
|
||||
#if !NETSTANDARD
|
||||
public static Uri NativeClientRedirectUri
|
||||
{
|
||||
get
|
||||
{
|
||||
Uri nativeClientRedirect = null;
|
||||
|
||||
try
|
||||
{
|
||||
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientRedirect);
|
||||
if (!string.IsNullOrEmpty(nativeRedirect))
|
||||
{
|
||||
Uri.TryCreate(nativeRedirect, UriKind.RelativeOrAbsolute, out nativeClientRedirect);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(string.Format("NativeClientRedirectUri: {0}", e));
|
||||
}
|
||||
|
||||
return nativeClientRedirect ?? new Uri("urn:ietf:wg:oauth:2.0:oob");
|
||||
}
|
||||
}
|
||||
|
||||
public static string ClientId
|
||||
{
|
||||
get
|
||||
{
|
||||
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientIdentifier);
|
||||
return nativeRedirect ?? VssAadSettings.Client;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static string AadInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
#if !NETSTANDARD
|
||||
string aadInstance = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadInstance);
|
||||
#else
|
||||
string aadInstance = null;
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(aadInstance))
|
||||
{
|
||||
aadInstance = DefaultAadInstance;
|
||||
}
|
||||
else if (!aadInstance.EndsWith("/"))
|
||||
{
|
||||
aadInstance = aadInstance + "/";
|
||||
}
|
||||
|
||||
return aadInstance;
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Application tenant either from a registry override or a constant
|
||||
/// </summary>
|
||||
public static string ApplicationTenant =>
|
||||
VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadApplicationTenant)
|
||||
?? VssAadSettings.ApplicationTenantId;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
[Serializable]
|
||||
public class VssAadToken : IssuedToken
|
||||
{
|
||||
private string accessToken;
|
||||
private string accessTokenType;
|
||||
|
||||
private AuthenticationContext authenticationContext;
|
||||
private UserCredential userCredential;
|
||||
private VssAadTokenOptions options;
|
||||
|
||||
public VssAadToken(AuthenticationResult authentication)
|
||||
{
|
||||
// Prevent any attempt to store this token.
|
||||
this.FromStorage = true;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(authentication.AccessToken))
|
||||
{
|
||||
this.Authenticated();
|
||||
}
|
||||
|
||||
this.accessToken = authentication.AccessToken;
|
||||
this.accessTokenType = authentication.AccessTokenType;
|
||||
}
|
||||
|
||||
public VssAadToken(
|
||||
string accessTokenType,
|
||||
string accessToken)
|
||||
{
|
||||
// Prevent any attempt to store this token.
|
||||
this.FromStorage = true;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(accessToken) && !string.IsNullOrWhiteSpace(accessTokenType))
|
||||
{
|
||||
this.Authenticated();
|
||||
}
|
||||
|
||||
this.accessToken = accessToken;
|
||||
this.accessTokenType = accessTokenType;
|
||||
}
|
||||
|
||||
public VssAadToken(
|
||||
AuthenticationContext authenticationContext,
|
||||
UserCredential userCredential = null,
|
||||
VssAadTokenOptions options = VssAadTokenOptions.None)
|
||||
{
|
||||
// Prevent any attempt to store this token.
|
||||
this.FromStorage = true;
|
||||
|
||||
this.authenticationContext = authenticationContext;
|
||||
this.userCredential = userCredential;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
protected internal override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Aad;
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticationResult AcquireToken()
|
||||
{
|
||||
if (this.authenticationContext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
AuthenticationResult authenticationResult = null;
|
||||
|
||||
for (int index = 0; index < 3; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.userCredential == null && !options.HasFlag(VssAadTokenOptions.AllowDialog))
|
||||
{
|
||||
authenticationResult = authenticationContext.AcquireTokenSilentAsync(VssAadSettings.Resource, VssAadSettings.Client).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
authenticationResult = authenticationContext.AcquireTokenAsync(VssAadSettings.Resource, VssAadSettings.Client, this.userCredential).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
if (authenticationResult != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Failed to get ADFS token: " + x.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
internal override void ApplyTo(IHttpRequest request)
|
||||
{
|
||||
AuthenticationResult authenticationResult = AcquireToken();
|
||||
if (authenticationResult != null)
|
||||
{
|
||||
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{authenticationResult.AccessTokenType} {authenticationResult.AccessToken}");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(this.accessTokenType) && !string.IsNullOrEmpty(this.accessToken))
|
||||
{
|
||||
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{this.accessTokenType} {this.accessToken}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum VssAadTokenOptions
|
||||
{
|
||||
None = 0,
|
||||
AllowDialog = 1
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
internal sealed class VssAadTokenProvider : IssuedTokenProvider
|
||||
{
|
||||
public VssAadTokenProvider(VssAadCredential credential)
|
||||
: base(credential, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool GetTokenIsInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private VssAadToken GetVssAadToken()
|
||||
{
|
||||
AuthenticationContext authenticationContext = new AuthenticationContext(string.Concat(VssAadSettings.AadInstance, VssAadSettings.CommonTenant));
|
||||
UserCredential userCredential = null;
|
||||
|
||||
VssAadCredential credential = this.Credential as VssAadCredential;
|
||||
|
||||
if (credential?.Username != null)
|
||||
{
|
||||
#if NETSTANDARD
|
||||
// UserPasswordCredential does not currently exist for ADAL 3.13.5 for any non-desktop build.
|
||||
userCredential = new UserCredential(credential.Username);
|
||||
#else
|
||||
if (credential.Password != null)
|
||||
{
|
||||
userCredential = new UserPasswordCredential(credential.Username, credential.Password);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
userCredential = new UserCredential(credential.Username);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
userCredential = new UserCredential();
|
||||
}
|
||||
|
||||
return new VssAadToken(authenticationContext, userCredential);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporary implementation since we don't have a good configuration story here at the moment.
|
||||
/// </summary>
|
||||
protected override Task<IssuedToken> OnGetTokenAsync(IssuedToken failedToken, CancellationToken cancellationToken)
|
||||
{
|
||||
// If we have already tried to authenticate with an AAD token retrieved from Windows integrated authentication and it is not working, clear out state.
|
||||
if (failedToken != null && failedToken.CredentialType == VssCredentialsType.Aad && failedToken.IsAuthenticated)
|
||||
{
|
||||
this.CurrentToken = null;
|
||||
return Task.FromResult<IssuedToken>(null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Task.FromResult<IssuedToken>(GetVssAadToken());
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
|
||||
return Task.FromResult<IssuedToken>(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides federated authentication with a hosted <c>VssConnection</c> instance using cookies.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssFederatedCredential : FederatedCredential
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedCredential</c> instance.
|
||||
/// </summary>
|
||||
public VssFederatedCredential()
|
||||
: this(true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedCredential</c> instance.
|
||||
/// </summary>
|
||||
public VssFederatedCredential(Boolean useCache)
|
||||
: this(useCache, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedCredential</c> instance.
|
||||
/// </summary>
|
||||
/// <param name="initialToken">The initial token if available</param>
|
||||
public VssFederatedCredential(VssFederatedToken initialToken)
|
||||
: this(false, initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
public VssFederatedCredential(
|
||||
Boolean useCache,
|
||||
VssFederatedToken initialToken)
|
||||
: base(initialToken)
|
||||
{
|
||||
#if !NETSTANDARD
|
||||
if (useCache)
|
||||
{
|
||||
Storage = new VssClientCredentialStorage();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Federated;
|
||||
}
|
||||
}
|
||||
|
||||
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
bool isNonAuthenticationChallenge = false;
|
||||
return IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? isNonAuthenticationChallenge;
|
||||
}
|
||||
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response)
|
||||
{
|
||||
// The response is only null when attempting to determine the most appropriate token provider to
|
||||
// use for the connection. The only way we should do anything here is if we have an initial token
|
||||
// since that means we can present something without making a server call.
|
||||
if (response == null && base.InitialToken == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri signInUrl = null;
|
||||
String realm = String.Empty;
|
||||
String issuer = String.Empty;
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
var location = response.Headers.GetValues(HttpHeaders.Location).FirstOrDefault();
|
||||
if (location == null)
|
||||
{
|
||||
location = response.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(location))
|
||||
{
|
||||
signInUrl = new Uri(location);
|
||||
}
|
||||
|
||||
// Inform the server that we support the javascript notify "smart client" pattern for ACS auth
|
||||
AddParameter(ref signInUrl, "protocol", "javascriptnotify");
|
||||
|
||||
// Do not automatically sign in with existing FedAuth cookie
|
||||
AddParameter(ref signInUrl, "force", "1");
|
||||
|
||||
GetRealmAndIssuer(response, out realm, out issuer);
|
||||
}
|
||||
|
||||
return new VssFederatedTokenProvider(this, serverUrl, signInUrl, issuer, realm);
|
||||
}
|
||||
|
||||
internal static void GetRealmAndIssuer(
|
||||
IHttpResponse response,
|
||||
out String realm,
|
||||
out String issuer)
|
||||
{
|
||||
realm = response.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
|
||||
issuer = response.Headers.GetValues(HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(issuer))
|
||||
{
|
||||
issuer = new Uri(issuer).GetLeftPart(UriPartial.Authority);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Boolean? IsVssFederatedAuthenticationChallenge(
|
||||
IHttpResponse webResponse,
|
||||
out Boolean isNonAuthenticationChallenge)
|
||||
{
|
||||
isNonAuthenticationChallenge = false;
|
||||
|
||||
if (webResponse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to make sure that the redirect was issued from the Tfs service. We include the TfsServiceError
|
||||
// header to avoid the possibility that a redirect from a non-tfs service is issued and we incorrectly
|
||||
// launch the credentials UI.
|
||||
if (webResponse.StatusCode == HttpStatusCode.Found ||
|
||||
webResponse.StatusCode == HttpStatusCode.Redirect)
|
||||
{
|
||||
return webResponse.Headers.GetValues(HttpHeaders.Location).Any() && webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).Any();
|
||||
}
|
||||
else if (webResponse.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
return webResponse.Headers.GetValues(HttpHeaders.WwwAuthenticate).Any(x => x.StartsWith("TFS-Federated", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else if (webResponse.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
// This is not strictly an "authentication challenge" but it is a state the user can do something about so they can get access to the resource
|
||||
// they are attempting to access. Specifically, the user will hit this when they need to update or create a profile required by business policy.
|
||||
isNonAuthenticationChallenge = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).Any();
|
||||
if (isNonAuthenticationChallenge)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AddParameter(ref Uri uri, String name, String value)
|
||||
{
|
||||
if (uri.Query.IndexOf(String.Concat(name, "="), StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
UriBuilder builder = new UriBuilder(uri);
|
||||
builder.Query = String.Concat(builder.Query.TrimStart('?'), "&", name, "=", value);
|
||||
uri = builder.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a cookie-based authentication token.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssFederatedToken : IssuedToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedToken</c> instance using the specified cookies.
|
||||
/// </summary>
|
||||
/// <param name="cookies"></param>
|
||||
public VssFederatedToken(CookieCollection cookies)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(cookies, "cookies");
|
||||
m_cookies = cookies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the CookieCollection contained within this token. For internal use only.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public CookieCollection CookieCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_cookies;
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Federated;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ApplyTo(IHttpRequest request)
|
||||
{
|
||||
// From http://www.ietf.org/rfc/rfc2109.txt:
|
||||
// Note: For backward compatibility, the separator in the Cookie header
|
||||
// is semi-colon (;) everywhere.
|
||||
//
|
||||
// HttpRequestHeaders uses comma as the default separator, so instead of returning
|
||||
// a list of cookies, the method returns one semicolon separated string.
|
||||
IEnumerable<String> values = request.Headers.GetValues(s_cookieHeader);
|
||||
request.Headers.SetValue(s_cookieHeader, GetHeaderValue(values));
|
||||
}
|
||||
|
||||
private String GetHeaderValue(IEnumerable<String> cookieHeaders)
|
||||
{
|
||||
List<String> currentCookies = new List<String>();
|
||||
if (cookieHeaders != null)
|
||||
{
|
||||
foreach (String value in cookieHeaders)
|
||||
{
|
||||
currentCookies.AddRange(value.Split(';').Select(x => x.Trim()));
|
||||
}
|
||||
}
|
||||
|
||||
currentCookies.RemoveAll(x => String.IsNullOrEmpty(x));
|
||||
|
||||
foreach (Cookie cookie in m_cookies)
|
||||
{
|
||||
// Remove all existing cookies that match the name of the cookie we are going to add.
|
||||
currentCookies.RemoveAll(x => String.Equals(x.Substring(0, x.IndexOf('=')), cookie.Name, StringComparison.OrdinalIgnoreCase));
|
||||
currentCookies.Add(String.Concat(cookie.Name, "=", cookie.Value));
|
||||
}
|
||||
|
||||
return String.Join("; ", currentCookies);
|
||||
}
|
||||
|
||||
private CookieCollection m_cookies;
|
||||
private static readonly String s_cookieHeader = HttpRequestHeader.Cookie.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using GitHub.Services.Common;
|
||||
using System.Globalization;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides authentication for internet identities using single-sign-on cookies.
|
||||
/// </summary>
|
||||
internal sealed class VssFederatedTokenProvider : IssuedTokenProvider, ISupportSignOut
|
||||
{
|
||||
public VssFederatedTokenProvider(
|
||||
VssFederatedCredential credential,
|
||||
Uri serverUrl,
|
||||
Uri signInUrl,
|
||||
String issuer,
|
||||
String realm)
|
||||
: base(credential, serverUrl, signInUrl)
|
||||
{
|
||||
Issuer = issuer;
|
||||
Realm = realm;
|
||||
}
|
||||
|
||||
protected override String AuthenticationScheme
|
||||
{
|
||||
get
|
||||
{
|
||||
return "TFS-Federated";
|
||||
}
|
||||
}
|
||||
|
||||
protected override String AuthenticationParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (String.IsNullOrEmpty(this.Issuer) && String.IsNullOrEmpty(this.Realm))
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "issuer=\"{0}\", realm=\"{1}\"", this.Issuer, this.Realm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the federated credential from which this provider was created.
|
||||
/// </summary>
|
||||
public new VssFederatedCredential Credential
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VssFederatedCredential)base.Credential;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not a call to get token will require interactivity.
|
||||
/// </summary>
|
||||
public override Boolean GetTokenIsInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.CurrentToken == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the issuer for the token provider.
|
||||
/// </summary>
|
||||
public String Issuer
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the realm for the token provider.
|
||||
/// </summary>
|
||||
public String Realm
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
protected internal override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (!base.IsAuthenticationChallenge(webResponse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This means we were proactively constructed without any connection information. In this case
|
||||
// we return false to ensure that a new provider is reconstructed with all appropriate configuration
|
||||
// to retrieve a new token.
|
||||
if (this.SignInUrl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String realm, issuer;
|
||||
VssFederatedCredential.GetRealmAndIssuer(webResponse, out realm, out issuer);
|
||||
|
||||
return this.Realm.Equals(realm, StringComparison.OrdinalIgnoreCase) &&
|
||||
this.Issuer.Equals(issuer, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
protected override IssuedToken OnValidatingToken(
|
||||
IssuedToken token,
|
||||
IHttpResponse webResponse)
|
||||
{
|
||||
// If the response has Set-Cookie headers, attempt to retrieve the FedAuth cookie from the response
|
||||
// and replace the current token with the new FedAuth cookie. Note that the server only reissues the
|
||||
// FedAuth cookie if it is issued for more than an hour.
|
||||
CookieCollection fedAuthCookies = CookieUtility.GetFederatedCookies(webResponse);
|
||||
|
||||
if (fedAuthCookies != null)
|
||||
{
|
||||
// The reissued token should have the same user information as the previous one.
|
||||
VssFederatedToken federatedToken = new VssFederatedToken(fedAuthCookies)
|
||||
{
|
||||
Properties = token.Properties,
|
||||
UserId = token.UserId,
|
||||
UserName = token.UserName
|
||||
};
|
||||
|
||||
token = federatedToken;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
public void SignOut(Uri signOutUrl, Uri replyToUrl, String identityProvider)
|
||||
{
|
||||
// The preferred implementation is to follow the signOutUrl with a browser and kill the browser whenever it
|
||||
// arrives at the replyToUrl (or if it bombs out somewhere along the way).
|
||||
// This will work for all Web-based identity providers (Live, Google, Yahoo, Facebook) supported by ACS provided that
|
||||
// the TFS server has registered sign-out urls (in the TF Registry) for each of these.
|
||||
// This is the long-term approach that should be pursued and probably the approach recommended to other
|
||||
// clients which don't have direct access to the cookie store (TEE?)
|
||||
|
||||
// In the short term we are simply going to delete the TFS cookies and the Windows Live cookies that are exposed to this
|
||||
// session. This has the drawback of not properly signing out of Live (you'd still be signed in to e.g. Hotmail, Xbox, MSN, etc.)
|
||||
// but will allow the user to re-enter their live credentials and sign-in again to TFS.
|
||||
// The other drawback is that the clients will have to be updated again when we pursue the implementation outlined above.
|
||||
|
||||
CookieUtility.DeleteFederatedCookies(replyToUrl);
|
||||
if (!String.IsNullOrEmpty(identityProvider) && identityProvider.Equals("Windows Live ID", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
CookieUtility.DeleteWindowsLiveCookies();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a common implementation for federated credentials.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class FederatedCredential : IssuedTokenCredential
|
||||
{
|
||||
protected FederatedCredential(IssuedToken initialToken)
|
||||
: base(initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (webResponse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (webResponse.StatusCode == HttpStatusCode.Found ||
|
||||
webResponse.StatusCode == HttpStatusCode.Redirect)
|
||||
{
|
||||
return webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).Any();
|
||||
}
|
||||
|
||||
return webResponse.StatusCode == HttpStatusCode.Unauthorized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
internal struct HttpRequestMessageWrapper : IHttpRequest, IHttpHeaders
|
||||
{
|
||||
public HttpRequestMessageWrapper(HttpRequestMessage request)
|
||||
{
|
||||
m_request = request;
|
||||
}
|
||||
|
||||
public IHttpHeaders Headers
|
||||
{
|
||||
get
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri RequestUri
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_request.RequestUri;
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<string, object> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_request.Properties;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<String> IHttpHeaders.GetValues(String name)
|
||||
{
|
||||
IEnumerable<String> values;
|
||||
if (!m_request.Headers.TryGetValues(name, out values))
|
||||
{
|
||||
values = Enumerable.Empty<String>();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
void IHttpHeaders.SetValue(
|
||||
String name,
|
||||
String value)
|
||||
{
|
||||
m_request.Headers.Remove(name);
|
||||
m_request.Headers.Add(name, value);
|
||||
}
|
||||
|
||||
Boolean IHttpHeaders.TryGetValues(
|
||||
String name,
|
||||
out IEnumerable<String> values)
|
||||
{
|
||||
return m_request.Headers.TryGetValues(name, out values);
|
||||
}
|
||||
|
||||
private readonly HttpRequestMessage m_request;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
internal struct HttpResponseMessageWrapper : IHttpResponse, IHttpHeaders
|
||||
{
|
||||
public HttpResponseMessageWrapper(HttpResponseMessage response)
|
||||
{
|
||||
m_response = response;
|
||||
}
|
||||
|
||||
public IHttpHeaders Headers
|
||||
{
|
||||
get
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public HttpStatusCode StatusCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_response.StatusCode;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<String> IHttpHeaders.GetValues(String name)
|
||||
{
|
||||
IEnumerable<String> values;
|
||||
if (!m_response.Headers.TryGetValues(name, out values))
|
||||
{
|
||||
values = Enumerable.Empty<String>();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
void IHttpHeaders.SetValue(
|
||||
String name,
|
||||
String value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
Boolean IHttpHeaders.TryGetValues(
|
||||
String name,
|
||||
out IEnumerable<String> values)
|
||||
{
|
||||
return m_response.Headers.TryGetValues(name, out values);
|
||||
}
|
||||
|
||||
private readonly HttpResponseMessage m_response;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public interface IHttpHeaders
|
||||
{
|
||||
IEnumerable<String> GetValues(String name);
|
||||
|
||||
void SetValue(String name, String value);
|
||||
|
||||
Boolean TryGetValues(String name, out IEnumerable<String> values);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public interface IHttpRequest
|
||||
{
|
||||
IHttpHeaders Headers
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
Uri RequestUri
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
IDictionary<string, object> Properties
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public interface IHttpResponse
|
||||
{
|
||||
IHttpHeaders Headers
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
HttpStatusCode StatusCode
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide an interface to get a new token for the credentials.
|
||||
/// </summary>
|
||||
public interface IVssCredentialPrompt
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a new token using the specified provider and the previously failed token.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider for the token to be retrieved</param>
|
||||
/// <param name="failedToken">The token which previously failed authentication, if available</param>
|
||||
/// <returns>The new token</returns>
|
||||
Task<IssuedToken> GetTokenAsync(IssuedTokenProvider provider, IssuedToken failedToken);
|
||||
|
||||
IDictionary<string, string> Parameters { get; set; }
|
||||
}
|
||||
|
||||
public interface IVssCredentialPrompts : IVssCredentialPrompt
|
||||
{
|
||||
IVssCredentialPrompt FederatedPrompt
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public interface IVssCredentialStorage
|
||||
{
|
||||
IssuedToken RetrieveToken(
|
||||
Uri serverUrl,
|
||||
VssCredentialsType credentialsType);
|
||||
|
||||
void StoreToken(
|
||||
Uri serverUrl,
|
||||
IssuedToken token);
|
||||
|
||||
void RemoveToken(
|
||||
Uri serverUrl,
|
||||
IssuedToken token);
|
||||
|
||||
bool RemoveTokenValue(
|
||||
Uri serverUrl,
|
||||
IssuedToken token);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a common base class for issued tokens.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class IssuedToken
|
||||
{
|
||||
internal IssuedToken()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this token has been successfully authenticated with the remote
|
||||
/// server.
|
||||
/// </summary>
|
||||
public bool IsAuthenticated
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_authenticated == 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected internal abstract VssCredentialsType CredentialType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the token is retrieved from token storage.
|
||||
/// </summary>
|
||||
internal bool FromStorage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata about the token in a collection of properties.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IDictionary<string, string> Properties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id of the owner of the token.
|
||||
/// </summary>
|
||||
internal Guid UserId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the owner of the token.
|
||||
/// </summary>
|
||||
internal string UserName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the issued token has been validated by successfully authenticated with the remote server.
|
||||
/// </summary>
|
||||
internal bool Authenticated()
|
||||
{
|
||||
return Interlocked.CompareExchange(ref m_authenticated, 1, 0) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of the <c>HttpHeaders.VssUserData</c> response header and
|
||||
/// populate the <c>UserId</c> and <c>UserName</c> properties.
|
||||
/// </summary>
|
||||
internal void GetUserData(IHttpResponse response)
|
||||
{
|
||||
IEnumerable<string> headerValues;
|
||||
if (response.Headers.TryGetValues(HttpHeaders.VssUserData, out headerValues))
|
||||
{
|
||||
string userData = headerValues.FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userData))
|
||||
{
|
||||
string[] split = userData.Split(':');
|
||||
|
||||
if (split.Length >= 2)
|
||||
{
|
||||
UserId = Guid.Parse(split[0]);
|
||||
UserName = split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the token to the HTTP request message.
|
||||
/// </summary>
|
||||
/// <param name="request">The HTTP request message</param>
|
||||
internal abstract void ApplyTo(IHttpRequest request);
|
||||
|
||||
private int m_authenticated;
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a common base class for issued token credentials.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class IssuedTokenCredential
|
||||
{
|
||||
protected IssuedTokenCredential(IssuedToken initialToken)
|
||||
{
|
||||
InitialToken = initialToken;
|
||||
}
|
||||
|
||||
public abstract VssCredentialsType CredentialType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The initial token to use to authenticate if available.
|
||||
/// </summary>
|
||||
internal IssuedToken InitialToken
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the synchronization context which should be used for UI prompts.
|
||||
/// </summary>
|
||||
internal TaskScheduler Scheduler
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_scheduler;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_scheduler = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The credentials prompt which is used for retrieving a new token.
|
||||
/// </summary>
|
||||
internal IVssCredentialPrompt Prompt
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_prompt;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_prompt = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal IVssCredentialStorage Storage
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_storage;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_storage = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base url for the vssconnection to be used in the token storage key.
|
||||
/// </summary>
|
||||
internal Uri TokenStorageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a token provider suitable for handling the challenge presented in the response.
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">The targeted server</param>
|
||||
/// <param name="response">The challenge response</param>
|
||||
/// <param name="failedToken">The failed token</param>
|
||||
/// <returns>An issued token provider instance</returns>
|
||||
internal IssuedTokenProvider CreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response,
|
||||
IssuedToken failedToken)
|
||||
{
|
||||
if (response != null && !IsAuthenticationChallenge(response))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (InitialToken == null && Storage != null)
|
||||
{
|
||||
if (TokenStorageUrl == null)
|
||||
{
|
||||
throw new InvalidOperationException($"The {nameof(TokenStorageUrl)} property must have a value if the {nameof(Storage)} property is set on this instance of {GetType().Name}.");
|
||||
}
|
||||
InitialToken = Storage.RetrieveToken(TokenStorageUrl, CredentialType);
|
||||
}
|
||||
|
||||
IssuedTokenProvider provider = OnCreateTokenProvider(serverUrl, response);
|
||||
if (provider != null)
|
||||
{
|
||||
provider.TokenStorageUrl = TokenStorageUrl;
|
||||
}
|
||||
|
||||
// If the initial token is the one which failed to authenticate, don't
|
||||
// use it again and let the token provider get a new token.
|
||||
if (provider != null)
|
||||
{
|
||||
if (InitialToken != null && !Object.ReferenceEquals(InitialToken, failedToken))
|
||||
{
|
||||
provider.CurrentToken = InitialToken;
|
||||
}
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
internal virtual string GetAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
IEnumerable<String> values;
|
||||
if (!webResponse.Headers.TryGetValues(Internal.HttpHeaders.WwwAuthenticate, out values))
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
return String.Join(", ", values);
|
||||
}
|
||||
|
||||
public abstract bool IsAuthenticationChallenge(IHttpResponse webResponse);
|
||||
|
||||
protected abstract IssuedTokenProvider OnCreateTokenProvider(Uri serverUrl, IHttpResponse response);
|
||||
|
||||
[NonSerialized]
|
||||
private TaskScheduler m_scheduler;
|
||||
|
||||
[NonSerialized]
|
||||
private IVssCredentialPrompt m_prompt;
|
||||
|
||||
[NonSerialized]
|
||||
private IVssCredentialStorage m_storage;
|
||||
}
|
||||
}
|
||||
@@ -1,545 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common.Diagnostics;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
internal interface ISupportSignOut
|
||||
{
|
||||
void SignOut(Uri serverUrl, Uri replyToUrl, string identityProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a common base class for providers of the token authentication model.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class IssuedTokenProvider
|
||||
{
|
||||
private const double c_slowTokenAcquisitionTimeInSeconds = 2.0;
|
||||
|
||||
protected IssuedTokenProvider(
|
||||
IssuedTokenCredential credential,
|
||||
Uri serverUrl,
|
||||
Uri signInUrl)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(credential, "credential");
|
||||
|
||||
this.SignInUrl = signInUrl;
|
||||
this.Credential = credential;
|
||||
this.ServerUrl = serverUrl;
|
||||
|
||||
m_thisLock = new object();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication scheme used to create this token provider.
|
||||
/// </summary>
|
||||
protected virtual String AuthenticationScheme
|
||||
{
|
||||
get
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication parameter or parameters used to create this token provider.
|
||||
/// </summary>
|
||||
protected virtual String AuthenticationParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the credential associated with the provider.
|
||||
/// </summary>
|
||||
protected internal IssuedTokenCredential Credential
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal VssCredentialsType CredentialType => this.Credential.CredentialType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current token.
|
||||
/// </summary>
|
||||
public IssuedToken CurrentToken
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not a call to get token will require interactivity.
|
||||
/// </summary>
|
||||
public abstract bool GetTokenIsInteractive
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not an ISynchronizeInvoke call is required.
|
||||
/// </summary>
|
||||
private Boolean InvokeRequired
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.GetTokenIsInteractive && this.Credential.Scheduler != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sign-in URL for the token provider.
|
||||
/// </summary>
|
||||
public Uri SignInUrl { get; private set; }
|
||||
|
||||
protected Uri ServerUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The base url for the vssconnection to be used in the token storage key.
|
||||
/// </summary>
|
||||
internal Uri TokenStorageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified web response is an authentication challenge.
|
||||
/// </summary>
|
||||
/// <param name="webResponse">The web response</param>
|
||||
/// <returns>True if the web response is a challenge for token authentication; otherwise, false</returns>
|
||||
protected internal virtual bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
return this.Credential.IsAuthenticationChallenge(webResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the authentication challenge string which this token provider handles.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the handled authentication challenge</returns>
|
||||
internal string GetAuthenticationParameters()
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.AuthenticationParameter))
|
||||
{
|
||||
return this.AuthenticationScheme;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, this.AuthenticationScheme, this.AuthenticationParameter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the current token if the provided reference is the current token and it
|
||||
/// has not been validated before.
|
||||
/// </summary>
|
||||
/// <param name="token">The token which should be validated</param>
|
||||
/// <param name="webResponse">The web response which used the token</param>
|
||||
internal void ValidateToken(
|
||||
IssuedToken token,
|
||||
IHttpResponse webResponse)
|
||||
{
|
||||
if (token == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (m_thisLock)
|
||||
{
|
||||
IssuedToken tokenToValidate = OnValidatingToken(token, webResponse);
|
||||
|
||||
if (tokenToValidate.IsAuthenticated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Perform validation which may include matching user information from the response
|
||||
// with that from the stored connection. If user information mismatch, an exception
|
||||
// will be thrown and the token will not be authenticated, which means if the same
|
||||
// token is ever used again in a different request it will be revalidated and fail.
|
||||
tokenToValidate.GetUserData(webResponse);
|
||||
OnTokenValidated(tokenToValidate);
|
||||
|
||||
// Set the token to be authenticated.
|
||||
tokenToValidate.Authenticated();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// When the token fails validation, we null its reference from the token provider so it
|
||||
// would not be used again by the consumers of both. Note that we only update the current
|
||||
// token of the provider if it is the original token being validated, because we do not
|
||||
// want to overwrite a different token.
|
||||
if (object.ReferenceEquals(this.CurrentToken, token))
|
||||
{
|
||||
this.CurrentToken = tokenToValidate.IsAuthenticated ? tokenToValidate : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the current token if the provided reference is the current token.
|
||||
/// </summary>
|
||||
/// <param name="token">The token reference which should be invalidated</param>
|
||||
internal void InvalidateToken(IssuedToken token)
|
||||
{
|
||||
bool invalidated = false;
|
||||
lock (m_thisLock)
|
||||
{
|
||||
if (token != null && object.ReferenceEquals(this.CurrentToken, token))
|
||||
{
|
||||
this.CurrentToken = null;
|
||||
invalidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidated)
|
||||
{
|
||||
OnTokenInvalidated(token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a token for the credentials.
|
||||
/// </summary>
|
||||
/// <param name="failedToken">The token which previously failed authentication, if available</param>
|
||||
/// <param name="cancellationToken">The <c>CancellationToken</c>that will be assigned to the new task</param>
|
||||
/// <returns>A security token for the current credentials</returns>
|
||||
public async Task<IssuedToken> GetTokenAsync(
|
||||
IssuedToken failedToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
IssuedToken currentToken = this.CurrentToken;
|
||||
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
||||
Stopwatch aadAuthTokenTimer = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
VssHttpEventSource.Log.AuthenticationStart(traceActivity);
|
||||
|
||||
if (currentToken != null)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenRetrievedFromCache(traceActivity, this, currentToken);
|
||||
return currentToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
GetTokenOperation operation = null;
|
||||
try
|
||||
{
|
||||
GetTokenOperation operationInProgress;
|
||||
operation = CreateOperation(traceActivity, failedToken, cancellationToken, out operationInProgress);
|
||||
if (operationInProgress == null)
|
||||
{
|
||||
return await operation.GetTokenAsync(traceActivity).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await operationInProgress.WaitForTokenAsync(traceActivity, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (m_thisLock)
|
||||
{
|
||||
m_operations.Remove(operation);
|
||||
}
|
||||
|
||||
operation?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
VssHttpEventSource.Log.AuthenticationStop(traceActivity);
|
||||
|
||||
aadAuthTokenTimer.Stop();
|
||||
TimeSpan getTokenTime = aadAuthTokenTimer.Elapsed;
|
||||
|
||||
if(getTokenTime.TotalSeconds >= c_slowTokenAcquisitionTimeInSeconds)
|
||||
{
|
||||
// It may seem strange to pass the string value of TotalSeconds into this method, but testing
|
||||
// showed that ETW is persnickety when you register a method in an EventSource that doesn't
|
||||
// use strings or integers as its parameters. It is easier to simply give the method a string
|
||||
// than figure out to get ETW to reliably accept a double or TimeSpan.
|
||||
VssHttpEventSource.Log.AuthorizationDelayed(getTokenTime.TotalSeconds.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a token for the credentials.
|
||||
/// </summary>
|
||||
/// <param name="failedToken">The token which previously failed authentication, if available</param>
|
||||
/// <param name="cancellationToken">The <c>CancellationToken</c>that will be assigned to the new task</param>
|
||||
/// <returns>A security token for the current credentials</returns>
|
||||
protected virtual Task<IssuedToken> OnGetTokenAsync(
|
||||
IssuedToken failedToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (this.Credential.Prompt != null)
|
||||
{
|
||||
return this.Credential.Prompt.GetTokenAsync(this, failedToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult<IssuedToken>(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the current token is being validated. When overriden in a derived class,
|
||||
/// validate and return the validated token.
|
||||
/// </summary>
|
||||
/// <remarks>Is called inside a lock in <c>ValidateToken</c></remarks>
|
||||
/// <param name="token">The token to validate</param>
|
||||
/// <param name="webResponse">The web response which used the token</param>
|
||||
/// <returns>The validated token</returns>
|
||||
protected virtual IssuedToken OnValidatingToken(
|
||||
IssuedToken token,
|
||||
IHttpResponse webResponse)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
protected virtual void OnTokenValidated(IssuedToken token)
|
||||
{
|
||||
// Store the validated token to the token storage if it is not originally from there.
|
||||
if (!token.FromStorage && TokenStorageUrl != null)
|
||||
{
|
||||
Credential.Storage?.StoreToken(TokenStorageUrl, token);
|
||||
}
|
||||
|
||||
VssHttpEventSource.Log.IssuedTokenValidated(VssTraceActivity.Current, this, token);
|
||||
}
|
||||
|
||||
protected virtual void OnTokenInvalidated(IssuedToken token)
|
||||
{
|
||||
if (Credential.Storage != null && TokenStorageUrl != null)
|
||||
{
|
||||
Credential.Storage.RemoveTokenValue(TokenStorageUrl, token);
|
||||
}
|
||||
|
||||
VssHttpEventSource.Log.IssuedTokenInvalidated(VssTraceActivity.Current, this, token);
|
||||
}
|
||||
|
||||
private GetTokenOperation CreateOperation(
|
||||
VssTraceActivity traceActivity,
|
||||
IssuedToken failedToken,
|
||||
CancellationToken cancellationToken,
|
||||
out GetTokenOperation operationInProgress)
|
||||
{
|
||||
operationInProgress = null;
|
||||
GetTokenOperation operation = null;
|
||||
lock (m_thisLock)
|
||||
{
|
||||
if (m_operations == null)
|
||||
{
|
||||
m_operations = new List<GetTokenOperation>();
|
||||
}
|
||||
|
||||
// Grab the main operation which is doing the work (if any)
|
||||
if (m_operations.Count > 0)
|
||||
{
|
||||
operationInProgress = m_operations[0];
|
||||
|
||||
// Use the existing completion source when creating the new operation
|
||||
operation = new GetTokenOperation(traceActivity, this, failedToken, cancellationToken, operationInProgress.CompletionSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
operation = new GetTokenOperation(traceActivity, this, failedToken, cancellationToken);
|
||||
}
|
||||
|
||||
m_operations.Add(operation);
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
private object m_thisLock;
|
||||
private List<GetTokenOperation> m_operations;
|
||||
|
||||
private class DisposableTaskCompletionSource<T> : TaskCompletionSource<T>, IDisposable
|
||||
{
|
||||
public DisposableTaskCompletionSource()
|
||||
{
|
||||
this.Task.ConfigureAwait(false).GetAwaiter().OnCompleted(() => { m_completed = true; });
|
||||
}
|
||||
|
||||
~DisposableTaskCompletionSource()
|
||||
{
|
||||
TraceErrorIfNotCompleted();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TraceErrorIfNotCompleted();
|
||||
|
||||
m_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void TraceErrorIfNotCompleted()
|
||||
{
|
||||
if (!m_completed)
|
||||
{
|
||||
VssHttpEventSource.Log.TokenSourceNotCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean m_disposed;
|
||||
private Boolean m_completed;
|
||||
}
|
||||
|
||||
private sealed class GetTokenOperation : IDisposable
|
||||
{
|
||||
public GetTokenOperation(
|
||||
VssTraceActivity activity,
|
||||
IssuedTokenProvider provider,
|
||||
IssuedToken failedToken,
|
||||
CancellationToken cancellationToken)
|
||||
: this(activity, provider, failedToken, cancellationToken, new DisposableTaskCompletionSource<IssuedToken>(), true)
|
||||
{
|
||||
}
|
||||
|
||||
public GetTokenOperation(
|
||||
VssTraceActivity activity,
|
||||
IssuedTokenProvider provider,
|
||||
IssuedToken failedToken,
|
||||
CancellationToken cancellationToken,
|
||||
DisposableTaskCompletionSource<IssuedToken> completionSource,
|
||||
Boolean ownsCompletionSource = false)
|
||||
{
|
||||
this.Provider = provider;
|
||||
this.ActivityId = activity?.Id ?? Guid.Empty;
|
||||
this.FailedToken = failedToken;
|
||||
this.CancellationToken = cancellationToken;
|
||||
this.CompletionSource = completionSource;
|
||||
this.OwnsCompletionSource = ownsCompletionSource;
|
||||
}
|
||||
|
||||
public Guid ActivityId { get; }
|
||||
|
||||
public CancellationToken CancellationToken { get; }
|
||||
|
||||
public DisposableTaskCompletionSource<IssuedToken> CompletionSource { get; }
|
||||
|
||||
public Boolean OwnsCompletionSource { get; }
|
||||
|
||||
private IssuedToken FailedToken { get; }
|
||||
|
||||
private IssuedTokenProvider Provider { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.OwnsCompletionSource)
|
||||
{
|
||||
this.CompletionSource?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IssuedToken> GetTokenAsync(VssTraceActivity traceActivity)
|
||||
{
|
||||
IssuedToken token = null;
|
||||
try
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenAcquiring(traceActivity, this.Provider);
|
||||
if (this.Provider.InvokeRequired)
|
||||
{
|
||||
// Post to the UI thread using the scheduler. This may return a new task object which needs
|
||||
// to be awaited, since once we get to the UI thread there may be nothing to do if someone else
|
||||
// preempts us.
|
||||
|
||||
// The cancellation token source is used to handle race conditions between scheduling and
|
||||
// waiting for the UI task to begin execution. The callback is responsible for disposing of
|
||||
// the token source, since the thought here is that the callback will run eventually as the
|
||||
// typical reason for not starting execution within the timeout is due to a deadlock with
|
||||
// the scheduler being used.
|
||||
var timerTask = new TaskCompletionSource<Object>();
|
||||
var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||
timeoutTokenSource.Token.Register(() => timerTask.SetResult(null), false);
|
||||
|
||||
var uiTask = Task.Factory.StartNew((state) => PostCallback(state, timeoutTokenSource),
|
||||
this,
|
||||
this.CancellationToken,
|
||||
TaskCreationOptions.None,
|
||||
this.Provider.Credential.Scheduler).Unwrap();
|
||||
|
||||
var completedTask = await Task.WhenAny(timerTask.Task, uiTask).ConfigureAwait(false);
|
||||
if (completedTask == uiTask)
|
||||
{
|
||||
token = uiTask.Result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token = await this.Provider.OnGetTokenAsync(this.FailedToken, this.CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
CompletionSource.TrySetResult(token);
|
||||
return token;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// Mark our completion source as failed so other waiters will get notified in all cases
|
||||
CompletionSource.TrySetException(exception);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.Provider.CurrentToken = token ?? this.FailedToken;
|
||||
VssHttpEventSource.Log.IssuedTokenAcquired(traceActivity, this.Provider, token);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IssuedToken> WaitForTokenAsync(
|
||||
VssTraceActivity traceActivity,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
IssuedToken token = null;
|
||||
try
|
||||
{
|
||||
|
||||
VssHttpEventSource.Log.IssuedTokenWaitStart(traceActivity, this.Provider, this.ActivityId);
|
||||
token = await Task.Factory.ContinueWhenAll<IssuedToken>(new Task[] { CompletionSource.Task }, (x) => CompletionSource.Task.Result, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenWaitStop(traceActivity, this.Provider, token);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private static Task<IssuedToken> PostCallback(
|
||||
Object state,
|
||||
CancellationTokenSource timeoutTokenSource)
|
||||
{
|
||||
// Make sure that we were not cancelled (timed out) before this callback is invoked.
|
||||
using (timeoutTokenSource)
|
||||
{
|
||||
timeoutTokenSource.CancelAfter(-1);
|
||||
if (timeoutTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromResult<IssuedToken>(null);
|
||||
}
|
||||
}
|
||||
|
||||
GetTokenOperation thisPtr = (GetTokenOperation)state;
|
||||
return thisPtr.Provider.OnGetTokenAsync(thisPtr.FailedToken, thisPtr.CancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a credential for basic authentication against a Visual Studio Service.
|
||||
/// </summary>
|
||||
public sealed class VssBasicCredential : FederatedCredential
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssBasicCredential</c> instance with no token specified.
|
||||
/// </summary>
|
||||
public VssBasicCredential()
|
||||
: this((VssBasicToken)null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssBasicCredential</c> instance with the specified user name and password.
|
||||
/// </summary>
|
||||
/// <param name="userName">The user name</param>
|
||||
/// <param name="password">The password</param>
|
||||
public VssBasicCredential(
|
||||
string userName,
|
||||
string password)
|
||||
: this(new VssBasicToken(new NetworkCredential(userName, password)))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssBasicCredential</c> instance with the specified token.
|
||||
/// </summary>
|
||||
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
|
||||
public VssBasicCredential(ICredentials initialToken)
|
||||
: this(new VssBasicToken(initialToken))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssBasicCredential</c> instance with the specified token.
|
||||
/// </summary>
|
||||
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
|
||||
public VssBasicCredential(VssBasicToken initialToken)
|
||||
: base(initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
public override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Basic;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (webResponse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (webResponse.StatusCode != HttpStatusCode.Found &&
|
||||
webResponse.StatusCode != HttpStatusCode.Redirect &&
|
||||
webResponse.StatusCode != HttpStatusCode.Unauthorized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return webResponse.Headers.GetValues(HttpHeaders.WwwAuthenticate).Any(x => x.StartsWith("Basic", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response)
|
||||
{
|
||||
if (serverUrl.Scheme != "https")
|
||||
{
|
||||
String unsafeBasicAuthEnv = Environment.GetEnvironmentVariable("VSS_ALLOW_UNSAFE_BASICAUTH") ?? "false";
|
||||
if (!Boolean.TryParse(unsafeBasicAuthEnv, out Boolean unsafeBasicAuth) || !unsafeBasicAuth)
|
||||
{
|
||||
throw new InvalidOperationException(CommonResources.BasicAuthenticationRequiresSsl());
|
||||
}
|
||||
}
|
||||
|
||||
return new BasicAuthTokenProvider(this, serverUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a token for basic authentication of internet identities.
|
||||
/// </summary>
|
||||
public sealed class VssBasicToken : IssuedToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>BasicAuthToken</c> instance with the specified token value.
|
||||
/// </summary>
|
||||
/// <param name="credentials">The credentials which should be used for authentication</param>
|
||||
public VssBasicToken(ICredentials credentials)
|
||||
{
|
||||
m_credentials = credentials;
|
||||
}
|
||||
|
||||
internal ICredentials Credentials
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_credentials;
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Basic;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ApplyTo(IHttpRequest request)
|
||||
{
|
||||
var basicCredential = m_credentials.GetCredential(request.RequestUri, "Basic");
|
||||
if (basicCredential != null)
|
||||
{
|
||||
request.Headers.SetValue(Internal.HttpHeaders.Authorization, "Basic " + FormatBasicAuthHeader(basicCredential));
|
||||
}
|
||||
}
|
||||
|
||||
private static String FormatBasicAuthHeader(NetworkCredential credential)
|
||||
{
|
||||
String authHeader = String.Empty;
|
||||
if (!String.IsNullOrEmpty(credential.Domain))
|
||||
{
|
||||
authHeader = String.Format(CultureInfo.InvariantCulture, "{0}\\{1}:{2}", credential.Domain, credential.UserName, credential.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
authHeader = String.Format(CultureInfo.InvariantCulture, "{0}:{1}", credential.UserName, credential.Password);
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(VssHttpRequestSettings.Encoding.GetBytes(authHeader));
|
||||
}
|
||||
|
||||
private readonly ICredentials m_credentials;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
internal sealed class BasicAuthTokenProvider : IssuedTokenProvider
|
||||
{
|
||||
public BasicAuthTokenProvider(
|
||||
VssBasicCredential credential,
|
||||
Uri serverUrl)
|
||||
: base(credential, serverUrl, serverUrl)
|
||||
{
|
||||
}
|
||||
|
||||
protected override String AuthenticationScheme
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Basic";
|
||||
}
|
||||
}
|
||||
|
||||
public new VssBasicCredential Credential
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VssBasicCredential)base.Credential;
|
||||
}
|
||||
}
|
||||
|
||||
public override Boolean GetTokenIsInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.CurrentToken == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,611 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common.Diagnostics;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of credentials supported natively by the framework
|
||||
/// </summary>
|
||||
public enum VssCredentialsType
|
||||
{
|
||||
Windows = 0,
|
||||
Federated = 1,
|
||||
Basic = 2,
|
||||
ServiceIdentity = 3,
|
||||
OAuth = 4,
|
||||
S2S = 5,
|
||||
Other = 6,
|
||||
Aad = 7,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the ability to control when to show or hide the credential prompt user interface.
|
||||
/// </summary>
|
||||
public enum CredentialPromptType
|
||||
{
|
||||
/// <summary>
|
||||
/// Show the UI only if necessary to obtain credentials.
|
||||
/// </summary>
|
||||
PromptIfNeeded = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Never show the UI, even if an error occurs.
|
||||
/// </summary>
|
||||
DoNotPrompt = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides credentials to use when connecting to a Visual Studio Service.
|
||||
/// </summary>
|
||||
public class VssCredentials
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with default credentials.
|
||||
/// </summary>
|
||||
public VssCredentials()
|
||||
: this(true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with default credentials if specified.
|
||||
/// </summary>
|
||||
/// <param name="useDefaultCredentials">True to use default windows credentials; otherwise, false</param>
|
||||
public VssCredentials(bool useDefaultCredentials)
|
||||
: this(new WindowsCredential(useDefaultCredentials))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with the specified windows credential.
|
||||
/// </summary>
|
||||
/// <param name="windowsCredential">The windows credential to use for authentication</param>
|
||||
public VssCredentials(WindowsCredential windowsCredential)
|
||||
: this(windowsCredential, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with the specified windows credential.
|
||||
/// </summary>
|
||||
/// <param name="windowsCredential">The windows credential to use for authentication</param>
|
||||
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed, otherwise CredentialProptType.DoNotPrompt</param>
|
||||
public VssCredentials(
|
||||
WindowsCredential windowsCredential,
|
||||
CredentialPromptType promptType)
|
||||
: this(windowsCredential, null, promptType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with the specified issued token credential and
|
||||
/// default windows credential.
|
||||
/// </summary>
|
||||
/// <param name="federatedCredential">The federated credential to use for authentication</param>
|
||||
public VssCredentials(FederatedCredential federatedCredential)
|
||||
: this(new WindowsCredential(), federatedCredential)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
|
||||
/// credential.
|
||||
/// </summary>
|
||||
/// <param name="windowsCredential">The windows credential to use for authentication</param>
|
||||
/// <param name="federatedCredential">The federated credential to use for authentication</param>
|
||||
public VssCredentials(
|
||||
WindowsCredential windowsCredential,
|
||||
FederatedCredential federatedCredential)
|
||||
: this(windowsCredential, federatedCredential, EnvironmentUserInteractive
|
||||
? CredentialPromptType.PromptIfNeeded : CredentialPromptType.DoNotPrompt)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
|
||||
/// credential.
|
||||
/// </summary>
|
||||
/// <param name="windowsCredential">The windows credential to use for authentication</param>
|
||||
/// <param name="federatedCredential">The federated credential to use for authentication</param>
|
||||
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed, otherwise CredentialProptType.DoNotPrompt</param>
|
||||
public VssCredentials(
|
||||
WindowsCredential windowsCredential,
|
||||
FederatedCredential federatedCredential,
|
||||
CredentialPromptType promptType)
|
||||
: this(windowsCredential, federatedCredential, promptType, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
|
||||
/// credential.
|
||||
/// </summary>
|
||||
/// <param name="windowsCredential">The windows credential to use for authentication</param>
|
||||
/// <param name="federatedCredential">The federated credential to use for authentication</param>
|
||||
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed; otherwise, CredentialProptType.DoNotPrompt</param>
|
||||
/// <param name="scheduler">An optional <c>TaskScheduler</c> to ensure credentials prompting occurs on the UI thread</param>
|
||||
public VssCredentials(
|
||||
WindowsCredential windowsCredential,
|
||||
FederatedCredential federatedCredential,
|
||||
CredentialPromptType promptType,
|
||||
TaskScheduler scheduler)
|
||||
: this(windowsCredential, federatedCredential, promptType, scheduler, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
|
||||
/// credential.
|
||||
/// </summary>
|
||||
/// <param name="windowsCredential">The windows credential to use for authentication</param>
|
||||
/// <param name="federatedCredential">The federated credential to use for authentication</param>
|
||||
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed; otherwise, CredentialProptType.DoNotPrompt</param>
|
||||
/// <param name="scheduler">An optional <c>TaskScheduler</c> to ensure credentials prompting occurs on the UI thread</param>
|
||||
/// <param name="credentialPrompt">An optional <c>IVssCredentialPrompt</c> to perform prompting for credentials</param>
|
||||
public VssCredentials(
|
||||
WindowsCredential windowsCredential,
|
||||
FederatedCredential federatedCredential,
|
||||
CredentialPromptType promptType,
|
||||
TaskScheduler scheduler,
|
||||
IVssCredentialPrompt credentialPrompt)
|
||||
{
|
||||
this.PromptType = promptType;
|
||||
|
||||
if (promptType == CredentialPromptType.PromptIfNeeded && scheduler == null)
|
||||
{
|
||||
// If we use TaskScheduler.FromCurrentSynchronizationContext() here and this is executing under the UI
|
||||
// thread, for example from an event handler in a WinForms applications, this TaskScheduler will capture
|
||||
// the UI SyncrhonizationContext whose MaximumConcurrencyLevel is 1 and only has a single thread to
|
||||
// execute queued work. Then, if the UI thread invokes one of our synchronous methods that are just
|
||||
// wrappers that block until the asynchronous overload returns, and if the async Task queues work to
|
||||
// this TaskScheduler, like GitHub.Services.CommonGetTokenOperation.GetTokenAsync does,
|
||||
// this will produce an immediate deadlock. It is a much safer choice to use TaskScheduler.Default here
|
||||
// as it uses the .NET Framework ThreadPool to execute queued work.
|
||||
scheduler = TaskScheduler.Default;
|
||||
}
|
||||
|
||||
if (windowsCredential != null)
|
||||
{
|
||||
m_windowsCredential = windowsCredential;
|
||||
m_windowsCredential.Scheduler = scheduler;
|
||||
m_windowsCredential.Prompt = credentialPrompt;
|
||||
}
|
||||
|
||||
if (federatedCredential != null)
|
||||
{
|
||||
m_federatedCredential = federatedCredential;
|
||||
m_federatedCredential.Scheduler = scheduler;
|
||||
m_federatedCredential.Prompt = credentialPrompt;
|
||||
}
|
||||
|
||||
m_thisLock = new object();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <c>FederatedCredential</c> instance into a <c>VssCredentials</c> instance.
|
||||
/// </summary>
|
||||
/// <param name="credential">The federated credential instance</param>
|
||||
/// <returns>A new <c>VssCredentials</c> instance which wraps the specified credential</returns>
|
||||
public static implicit operator VssCredentials(FederatedCredential credential)
|
||||
{
|
||||
return new VssCredentials(credential);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <c>WindowsCredential</c> instance into a <c>VssCredentials</c> instance.
|
||||
/// </summary>
|
||||
/// <param name="credential">The windows credential instance</param>
|
||||
/// <returns>A new <c>VssCredentials</c> instance which wraps the specified credential</returns>
|
||||
public static implicit operator VssCredentials(WindowsCredential credential)
|
||||
{
|
||||
return new VssCredentials(credential);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not interactive prompts are allowed.
|
||||
/// </summary>
|
||||
public CredentialPromptType PromptType
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_promptType;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == CredentialPromptType.PromptIfNeeded && !EnvironmentUserInteractive)
|
||||
{
|
||||
throw new ArgumentException(CommonResources.CannotPromptIfNonInteractive(), "PromptType");
|
||||
}
|
||||
|
||||
m_promptType = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the issued token credentials to use for authentication with the server.
|
||||
/// </summary>
|
||||
public FederatedCredential Federated
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_federatedCredential;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the windows credential to use for NTLM authentication with the server.
|
||||
/// </summary>
|
||||
public WindowsCredential Windows
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_windowsCredential;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A pluggable credential store.
|
||||
/// Simply assign a storage implementation to this property
|
||||
/// and the <c>VssCredentials</c> will use it to store and retrieve tokens
|
||||
/// during authentication.
|
||||
/// </summary>
|
||||
public IVssCredentialStorage Storage
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_credentialStorage;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_credentialStorage = value;
|
||||
|
||||
if (m_windowsCredential != null)
|
||||
{
|
||||
m_windowsCredential.Storage = value;
|
||||
}
|
||||
|
||||
if (m_federatedCredential != null)
|
||||
{
|
||||
m_federatedCredential.Storage = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///Attempts to find appropriate Access token for IDE user and add to prompt's parameter
|
||||
/// Actual implementation in override.
|
||||
/// </summary>
|
||||
internal virtual bool TryGetValidAdalToken(IVssCredentialPrompt prompt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a token provider for the configured issued token credentials.
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">The targeted server</param>
|
||||
/// <param name="webResponse">The failed web response</param>
|
||||
/// <param name="failedToken">The failed token</param>
|
||||
/// <returns>A provider for retrieving tokens for the configured credential</returns>
|
||||
internal IssuedTokenProvider CreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse webResponse,
|
||||
IssuedToken failedToken)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
|
||||
|
||||
IssuedTokenProvider tokenProvider = null;
|
||||
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
||||
lock (m_thisLock)
|
||||
{
|
||||
tokenProvider = m_currentProvider;
|
||||
if (tokenProvider == null || !tokenProvider.IsAuthenticationChallenge(webResponse))
|
||||
{
|
||||
// Prefer federated authentication over Windows authentication.
|
||||
if (m_federatedCredential != null && m_federatedCredential.IsAuthenticationChallenge(webResponse))
|
||||
{
|
||||
if (tokenProvider != null)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenProviderRemoved(traceActivity, tokenProvider);
|
||||
}
|
||||
|
||||
// TODO: This needs to be refactored or renamed to be more generic ...
|
||||
this.TryGetValidAdalToken(m_federatedCredential.Prompt);
|
||||
|
||||
tokenProvider = m_federatedCredential.CreateTokenProvider(serverUrl, webResponse, failedToken);
|
||||
|
||||
if (tokenProvider != null)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenProviderCreated(traceActivity, tokenProvider);
|
||||
}
|
||||
}
|
||||
else if (m_windowsCredential != null && m_windowsCredential.IsAuthenticationChallenge(webResponse))
|
||||
{
|
||||
if (tokenProvider != null)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenProviderRemoved(traceActivity, tokenProvider);
|
||||
}
|
||||
|
||||
tokenProvider = m_windowsCredential.CreateTokenProvider(serverUrl, webResponse, failedToken);
|
||||
|
||||
if (tokenProvider != null)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenProviderCreated(traceActivity, tokenProvider);
|
||||
}
|
||||
}
|
||||
|
||||
m_currentProvider = tokenProvider;
|
||||
}
|
||||
|
||||
return tokenProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the token provider for the provided server URL if one has been created.
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">The targeted server</param>
|
||||
/// <param name="provider">Stores the active token provider, if one exists</param>
|
||||
/// <returns>True if a token provider was found, false otherwise</returns>
|
||||
public bool TryGetTokenProvider(
|
||||
Uri serverUrl,
|
||||
out IssuedTokenProvider provider)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
|
||||
|
||||
lock (m_thisLock)
|
||||
{
|
||||
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
|
||||
if (m_currentProvider == null)
|
||||
{
|
||||
if (m_federatedCredential != null)
|
||||
{
|
||||
m_currentProvider = m_federatedCredential.CreateTokenProvider(serverUrl, null, null);
|
||||
}
|
||||
|
||||
if (m_currentProvider == null && m_windowsCredential != null)
|
||||
{
|
||||
m_currentProvider = m_windowsCredential.CreateTokenProvider(serverUrl, null, null);
|
||||
}
|
||||
|
||||
if (m_currentProvider != null)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenProviderCreated(VssTraceActivity.Current, m_currentProvider);
|
||||
}
|
||||
}
|
||||
|
||||
provider = m_currentProvider;
|
||||
}
|
||||
|
||||
return provider != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the web response is an authentication redirect for issued token providers.
|
||||
/// </summary>
|
||||
/// <param name="webResponse">The web response</param>
|
||||
/// <returns>True if this is an token authentication redirect, false otherwise</returns>
|
||||
internal bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (webResponse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isChallenge = false;
|
||||
if (m_windowsCredential != null)
|
||||
{
|
||||
isChallenge = m_windowsCredential.IsAuthenticationChallenge(webResponse);
|
||||
}
|
||||
|
||||
if (!isChallenge && m_federatedCredential != null)
|
||||
{
|
||||
isChallenge = m_federatedCredential.IsAuthenticationChallenge(webResponse);
|
||||
}
|
||||
|
||||
return isChallenge;
|
||||
}
|
||||
|
||||
internal void SignOut(
|
||||
Uri serverUrl,
|
||||
Uri serviceLocation,
|
||||
string identityProvider)
|
||||
{
|
||||
// Remove the token in the storage and the current token provider. Note that we don't
|
||||
// call InvalidateToken here because we want to remove the whole token not just its value
|
||||
if ((m_currentProvider != null) && (m_currentProvider.CurrentToken != null))
|
||||
{
|
||||
if (m_currentProvider.Credential.Storage != null && m_currentProvider.TokenStorageUrl != null)
|
||||
{
|
||||
m_currentProvider.Credential.Storage.RemoveToken(m_currentProvider.TokenStorageUrl, m_currentProvider.CurrentToken);
|
||||
}
|
||||
m_currentProvider.CurrentToken = null;
|
||||
}
|
||||
|
||||
// We need to make sure that the current provider actually supports the signout method
|
||||
ISupportSignOut tokenProviderWithSignOut = m_currentProvider as ISupportSignOut;
|
||||
if (tokenProviderWithSignOut == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace the parameters from the service location
|
||||
if (serviceLocation != null)
|
||||
{
|
||||
string serviceLocationUri = serviceLocation.AbsoluteUri;
|
||||
serviceLocationUri = serviceLocationUri.Replace("{mode}", "SignOut");
|
||||
serviceLocationUri = serviceLocationUri.Replace("{redirectUrl}", serverUrl.AbsoluteUri);
|
||||
serviceLocation = new Uri(serviceLocationUri);
|
||||
}
|
||||
|
||||
// Now actually signout of the token provider
|
||||
tokenProviderWithSignOut.SignOut(serviceLocation, serverUrl, identityProvider);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Loads stored credentials for the specified server if found. If no credentials are found in the windows
|
||||
/// credential store for the specified server and options then default credentials are returned.
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">The server location</param>
|
||||
/// <param name="requireExactMatch">A value indicating whether or not an exact or partial match of the server is required</param>
|
||||
/// <returns>A credentials object populated with stored credentials for the server if found</returns>
|
||||
public static VssCredentials LoadCachedCredentials(
|
||||
Uri serverUrl,
|
||||
bool requireExactMatch)
|
||||
{
|
||||
return LoadCachedCredentials(null, serverUrl, requireExactMatch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads stored credentials for the specified server if found. If no credentials are found for the specified server and options then default credentials are returned.
|
||||
/// This overload assumes that the credentials are to be stored under the TFS server's registry root
|
||||
/// </summary>
|
||||
/// <param name="featureRegistryKeyword">An optional application name for isolated credential storage in the registry</param>
|
||||
/// <param name="serverUrl">The server location</param>
|
||||
/// <param name="requireExactMatch">A value indicating whether or not an exact or partial match of the server is required</param>
|
||||
/// <returns>A credentials object populated with stored credentials for the server if found</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static VssCredentials LoadCachedCredentials(
|
||||
string featureRegistryKeyword,
|
||||
Uri serverUrl,
|
||||
bool requireExactMatch)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
|
||||
|
||||
bool uriKnownToCachedProvider = false;
|
||||
VssCredentials cred = LoadCachedCredentialsFromRegisteredProviders(serverUrl, out uriKnownToCachedProvider);
|
||||
|
||||
// If one of the registered credential providers had the target URI in its cache but failed to return a valid credential it means
|
||||
// we should have had a cred but something went wrong (user canceled, user failed, auth source unavailable, etc.). In that case
|
||||
// we Do Not want to carry on with the fallback to the VS registry/windows store credential caches. Even if that worked to get a
|
||||
// credential it would put the user in a bad state (having an active, authenticated connection with an unexpected credential type).
|
||||
if (cred == null && !uriKnownToCachedProvider)
|
||||
{
|
||||
WindowsCredential windowsCredential = null;
|
||||
FederatedCredential federatedCredential = null;
|
||||
CredentialsCacheManager credentialsCacheManager = new CredentialsCacheManager();
|
||||
TfsCredentialCacheEntry cacheEntry = credentialsCacheManager.GetCredentials(featureRegistryKeyword, serverUrl, requireExactMatch, null);
|
||||
if (cacheEntry != null)
|
||||
{
|
||||
if (cacheEntry.NonInteractive)
|
||||
{
|
||||
switch (cacheEntry.Type)
|
||||
{
|
||||
case CachedCredentialsType.ServiceIdentity:
|
||||
VssServiceIdentityToken initialToken = null;
|
||||
string initialTokenValue = ReadAuthorizationToken(cacheEntry.Attributes);
|
||||
if (!string.IsNullOrEmpty(initialTokenValue))
|
||||
{
|
||||
initialToken = new VssServiceIdentityToken(initialTokenValue);
|
||||
}
|
||||
|
||||
// Initialize the issued token credential using the stored token if it exists
|
||||
federatedCredential = new VssServiceIdentityCredential(cacheEntry.Credentials.UserName,
|
||||
cacheEntry.Credentials.Password,
|
||||
initialToken);
|
||||
break;
|
||||
|
||||
case CachedCredentialsType.Windows:
|
||||
windowsCredential = new WindowsCredential(cacheEntry.Credentials);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cred = new VssCredentials(windowsCredential ?? new WindowsCredential(true), federatedCredential, CredentialPromptType.DoNotPrompt);
|
||||
}
|
||||
|
||||
return cred ?? new VssCredentials(new WindowsCredential(true), null, CredentialPromptType.DoNotPrompt);
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static VssCredentials LoadCachedCredentialsFromRegisteredProviders(Uri serverUri, out bool knownUri)
|
||||
{
|
||||
LoadRegisteredCachedVssCredentialProviders();
|
||||
bool uriKnownByAnyProvider = false;
|
||||
VssCredentials cred = null;
|
||||
foreach (var pair in m_loadedCachedVssCredentialProviders)
|
||||
{
|
||||
bool uriKnownToProvider = false;
|
||||
cred = pair.Value?.GetCachedCredentials(serverUri, out uriKnownToProvider);
|
||||
if (cred != null || uriKnownToProvider)
|
||||
{
|
||||
uriKnownByAnyProvider |= uriKnownToProvider;
|
||||
break;
|
||||
}
|
||||
}
|
||||
knownUri = uriKnownByAnyProvider;
|
||||
return cred;
|
||||
}
|
||||
|
||||
private static void LoadRegisteredCachedVssCredentialProviders()
|
||||
{
|
||||
CredentialsProviderRegistryHelper.LoadCachedVssCredentialProviders(ref m_loadedCachedVssCredentialProviders);
|
||||
}
|
||||
private static ConcurrentDictionary<string, ICachedVssCredentialProvider> m_loadedCachedVssCredentialProviders = new ConcurrentDictionary<string, ICachedVssCredentialProvider>();
|
||||
#endif
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static void WriteAuthorizationToken(
|
||||
string token,
|
||||
IDictionary<string, string> attributes)
|
||||
{
|
||||
int i = 0;
|
||||
for (int j = 0; j < token.Length; i++, j += 128)
|
||||
{
|
||||
attributes["AuthTokenSegment" + i] = token.Substring(j, Math.Min(128, token.Length - j));
|
||||
}
|
||||
|
||||
attributes["AuthTokenSegmentCount"] = i.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
protected static string ReadAuthorizationToken(IDictionary<string, string> attributes)
|
||||
{
|
||||
string authTokenCountValue;
|
||||
if (attributes.TryGetValue("AuthTokenSegmentCount", out authTokenCountValue))
|
||||
{
|
||||
int authTokenCount = int.Parse(authTokenCountValue, CultureInfo.InvariantCulture);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < authTokenCount; i++)
|
||||
{
|
||||
string segmentName = "AuthTokenSegment" + i;
|
||||
|
||||
string segmentValue;
|
||||
if (attributes.TryGetValue(segmentName, out segmentValue))
|
||||
{
|
||||
sb.Append(segmentValue);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected static bool EnvironmentUserInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return Environment.UserInteractive;
|
||||
}
|
||||
}
|
||||
|
||||
private object m_thisLock;
|
||||
private CredentialPromptType m_promptType;
|
||||
private IssuedTokenProvider m_currentProvider;
|
||||
protected WindowsCredential m_windowsCredential;
|
||||
protected FederatedCredential m_federatedCredential;
|
||||
private IVssCredentialStorage m_credentialStorage;
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides federated authentication as a service identity with a Visual Studio Service.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssServiceIdentityCredential : FederatedCredential
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password.
|
||||
/// </summary>
|
||||
/// <param name="userName">The user name</param>
|
||||
/// <param name="password">The password</param>
|
||||
public VssServiceIdentityCredential(
|
||||
string userName,
|
||||
string password)
|
||||
: this(userName, password, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password. The
|
||||
/// provided token, if not null, will be used before attempting authentication with the credentials.
|
||||
/// </summary>
|
||||
/// <param name="userName">The user name</param>
|
||||
/// <param name="password">The password</param>
|
||||
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
|
||||
public VssServiceIdentityCredential(
|
||||
string userName,
|
||||
string password,
|
||||
VssServiceIdentityToken initialToken)
|
||||
: this(userName, password, initialToken, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified access token.
|
||||
/// </summary>
|
||||
/// <param name="token">A token which may be used for authorization as the desired service identity</param>
|
||||
public VssServiceIdentityCredential(VssServiceIdentityToken token)
|
||||
: this(null, null, token, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password. The
|
||||
/// provided token, if not null, will be used before attempting authentication with the credentials.
|
||||
/// </summary>
|
||||
/// <param name="userName">The user name</param>
|
||||
/// <param name="password">The password</param>
|
||||
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
|
||||
/// <param name="innerHandler">An optional HttpMessageHandler which if passed will be passed along to the TokenProvider when executing OnCreateTokenProvider </param>
|
||||
public VssServiceIdentityCredential(
|
||||
string userName,
|
||||
string password,
|
||||
VssServiceIdentityToken initialToken,
|
||||
DelegatingHandler innerHandler)
|
||||
: base(initialToken)
|
||||
{
|
||||
m_userName = userName;
|
||||
m_password = password;
|
||||
m_innerHandler = innerHandler;
|
||||
}
|
||||
|
||||
public override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.ServiceIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name.
|
||||
/// </summary>
|
||||
public String UserName
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_userName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the password.
|
||||
/// </summary>
|
||||
internal String Password
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_password;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (webResponse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (webResponse.StatusCode == HttpStatusCode.Found ||
|
||||
webResponse.StatusCode == HttpStatusCode.Redirect ||
|
||||
webResponse.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
var authRealm = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
|
||||
var authIssuer = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
|
||||
var wwwAuthenticate = webResponse.Headers.GetValues(Internal.HttpHeaders.WwwAuthenticate);
|
||||
if (!String.IsNullOrEmpty(authIssuer) && !String.IsNullOrEmpty(authRealm))
|
||||
{
|
||||
return webResponse.StatusCode != HttpStatusCode.Unauthorized || wwwAuthenticate.Any(x => x.StartsWith("TFS-Federated", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override string GetAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
var authRealm = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
|
||||
var authIssuer = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
|
||||
return string.Format(CultureInfo.InvariantCulture, "TFS-Federated realm={0}, issuer={1}", authRealm, authIssuer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a provider for retrieving security tokens for the provided credentials.
|
||||
/// </summary>
|
||||
/// <returns>An issued token provider for the current credential</returns>
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response)
|
||||
{
|
||||
// The response is only null when attempting to determine the most appropriate token provider to
|
||||
// use for the connection. The only way we should do anything here is if we have an initial token
|
||||
// since that means we can present something without making a server call.
|
||||
if (response == null && base.InitialToken == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri signInUrl = null;
|
||||
String realm = string.Empty;
|
||||
if (response != null)
|
||||
{
|
||||
realm = response.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
|
||||
signInUrl = new Uri(new Uri(response.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault()).GetLeftPart(UriPartial.Authority));
|
||||
}
|
||||
|
||||
return new VssServiceIdentityTokenProvider(this, serverUrl, signInUrl, realm, m_innerHandler);
|
||||
}
|
||||
|
||||
private readonly String m_userName;
|
||||
private readonly String m_password;
|
||||
|
||||
[NonSerialized]
|
||||
private readonly DelegatingHandler m_innerHandler = null;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides simple web token used for OAuth authentication.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssServiceIdentityToken : IssuedToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssServiceIdentityToken</c> instance with the specified token value.
|
||||
/// </summary>
|
||||
/// <param name="token">The token value as a string</param>
|
||||
public VssServiceIdentityToken(string token)
|
||||
{
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(token, "token");
|
||||
|
||||
m_token = token;
|
||||
//.ValidFrom = DateTime.UtcNow;
|
||||
|
||||
// Read out the expiration time for the ValidTo field if we can find it
|
||||
Dictionary<string, string> nameValues;
|
||||
if (TryGetNameValues(token, out nameValues))
|
||||
{
|
||||
string expiresOnValue;
|
||||
if (nameValues.TryGetValue(c_expiresName, out expiresOnValue))
|
||||
{
|
||||
// The time is represented as standard epoch
|
||||
// base.ValidTo = s_epoch.AddSeconds(Convert.ToUInt64(expiresOnValue, CultureInfo.CurrentCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String Token
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_token;
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.ServiceIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ApplyTo(IHttpRequest request)
|
||||
{
|
||||
request.Headers.SetValue(Internal.HttpHeaders.Authorization, "WRAP access_token=\"" + m_token + "\"");
|
||||
}
|
||||
|
||||
internal static VssServiceIdentityToken ExtractToken(string responseValue)
|
||||
{
|
||||
// Extract the actual token string
|
||||
string token = UriUtility.UrlDecode(responseValue
|
||||
.Split('&')
|
||||
.Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
|
||||
.Split('=')[1], VssHttpRequestSettings.Encoding);
|
||||
|
||||
return new VssServiceIdentityToken(token);
|
||||
}
|
||||
|
||||
internal static bool TryGetNameValues(
|
||||
string token,
|
||||
out Dictionary<string, string> tokenValues)
|
||||
{
|
||||
tokenValues = null;
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tokenValues =
|
||||
token
|
||||
.Split('&')
|
||||
.Aggregate(
|
||||
new Dictionary<string, string>(),
|
||||
(dict, rawNameValue) =>
|
||||
{
|
||||
if (rawNameValue == string.Empty)
|
||||
{
|
||||
return dict;
|
||||
}
|
||||
|
||||
string[] nameValue = rawNameValue.Split('=');
|
||||
|
||||
if (nameValue.Length != 2)
|
||||
{
|
||||
return dict;
|
||||
}
|
||||
|
||||
if (dict.ContainsKey(nameValue[0]) == true)
|
||||
{
|
||||
return dict;
|
||||
}
|
||||
|
||||
dict.Add(UriUtility.UrlDecode(nameValue[0]), UriUtility.UrlDecode(nameValue[1]));
|
||||
return dict;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private string m_token;
|
||||
private const string c_expiresName = "ExpiresOn";
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common.Diagnostics;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
internal sealed class VssServiceIdentityTokenProvider : IssuedTokenProvider
|
||||
{
|
||||
public VssServiceIdentityTokenProvider(
|
||||
VssServiceIdentityCredential credential,
|
||||
Uri serverUrl,
|
||||
Uri signInUrl,
|
||||
string realm,
|
||||
DelegatingHandler innerHandler)
|
||||
: this(credential, serverUrl, signInUrl, realm)
|
||||
{
|
||||
m_innerHandler = innerHandler;
|
||||
}
|
||||
|
||||
public VssServiceIdentityTokenProvider(
|
||||
VssServiceIdentityCredential credential,
|
||||
Uri serverUrl,
|
||||
Uri signInUrl,
|
||||
string realm)
|
||||
: base(credential, serverUrl, signInUrl)
|
||||
{
|
||||
Realm = realm;
|
||||
}
|
||||
|
||||
protected override string AuthenticationParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.Realm) && this.SignInUrl == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "issuer=\"{0}\", realm=\"{1}\"", this.SignInUrl, this.Realm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override String AuthenticationScheme
|
||||
{
|
||||
get
|
||||
{
|
||||
return "TFS-Federated";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the simple web token credential from which this provider was created.
|
||||
/// </summary>
|
||||
public new VssServiceIdentityCredential Credential
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VssServiceIdentityCredential)base.Credential;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not a call to get token will require interactivity.
|
||||
/// </summary>
|
||||
public override Boolean GetTokenIsInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the realm for the token provider.
|
||||
/// </summary>
|
||||
public String Realm
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
protected internal override bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (!base.IsAuthenticationChallenge(webResponse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This means we were proactively constructed without any connection information. In this case
|
||||
// we return false to ensure that a new provider is reconstructed with all appropriate configuration
|
||||
// to retrieve a new token.
|
||||
if (this.SignInUrl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string authRealm = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
|
||||
string authIssuer = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
|
||||
Uri signInUrl = new Uri(new Uri(authIssuer).GetLeftPart(UriPartial.Authority), UriKind.Absolute);
|
||||
|
||||
// Make sure that the values match our stored values. This way if the values change we will be thrown
|
||||
// away and a new instance with correct values will be constructed.
|
||||
return this.Realm.Equals(authRealm, StringComparison.OrdinalIgnoreCase) &&
|
||||
Uri.Compare(this.SignInUrl, signInUrl, UriComponents.AbsoluteUri, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a request to synchronously retrieve a token for the associated credential.
|
||||
/// </summary>
|
||||
/// <param name="failedToken"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<IssuedToken> OnGetTokenAsync(
|
||||
IssuedToken failedToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.Credential.UserName) ||
|
||||
string.IsNullOrEmpty(this.Credential.Password))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
||||
using (HttpClient client = new HttpClient(CreateMessageHandler(), false))
|
||||
{
|
||||
client.BaseAddress = this.SignInUrl;
|
||||
|
||||
KeyValuePair<string, string>[] values = new KeyValuePair<string, string>[]
|
||||
{
|
||||
new KeyValuePair<string, string>("wrap_name", this.Credential.UserName),
|
||||
new KeyValuePair<string, string>("wrap_password", this.Credential.Password),
|
||||
new KeyValuePair<string, string>("wrap_scope", this.Realm),
|
||||
};
|
||||
|
||||
Uri url = new Uri("WRAPv0.9/", UriKind.Relative);
|
||||
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
|
||||
using (HttpResponseMessage response = await client.PostAsync(url, content, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
string responseValue = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return VssServiceIdentityToken.ExtractToken(responseValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
VssHttpEventSource.Log.AuthenticationError(traceActivity, this, responseValue);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HttpMessageHandler CreateMessageHandler()
|
||||
{
|
||||
var retryOptions = new VssHttpRetryOptions()
|
||||
{
|
||||
RetryableStatusCodes =
|
||||
{
|
||||
VssNetworkHelper.TooManyRequests,
|
||||
HttpStatusCode.InternalServerError,
|
||||
},
|
||||
};
|
||||
|
||||
HttpMessageHandler innerHandler;
|
||||
|
||||
if (m_innerHandler != null)
|
||||
{
|
||||
if (m_innerHandler.InnerHandler == null)
|
||||
{
|
||||
m_innerHandler.InnerHandler = new HttpClientHandler();
|
||||
}
|
||||
|
||||
innerHandler = m_innerHandler;
|
||||
}
|
||||
else
|
||||
{
|
||||
innerHandler = new HttpClientHandler();
|
||||
}
|
||||
|
||||
// Inherit proxy setting from VssHttpMessageHandler
|
||||
var httpClientHandler = innerHandler as HttpClientHandler;
|
||||
if (httpClientHandler != null && VssHttpMessageHandler.DefaultWebProxy != null)
|
||||
{
|
||||
httpClientHandler.Proxy = VssHttpMessageHandler.DefaultWebProxy;
|
||||
httpClientHandler.UseProxy = true;
|
||||
}
|
||||
|
||||
return new VssHttpRetryMessageHandler(retryOptions, innerHandler);
|
||||
}
|
||||
|
||||
private DelegatingHandler m_innerHandler = null;
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a credential for windows authentication against a Visual Studio Service.
|
||||
/// </summary>
|
||||
public sealed class WindowsCredential : IssuedTokenCredential
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
|
||||
/// and the default network credentials.
|
||||
/// </summary>
|
||||
public WindowsCredential()
|
||||
: this(true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
|
||||
/// and the default network credentials, if specified.
|
||||
/// </summary>
|
||||
/// <param name="useDefaultCredentials">True if the default credentials should be used; otherwise, false</param>
|
||||
public WindowsCredential(bool useDefaultCredentials)
|
||||
: this(useDefaultCredentials ? CredentialCache.DefaultCredentials : null)
|
||||
{
|
||||
UseDefaultCredentials = useDefaultCredentials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
|
||||
/// and the specified network credentials.
|
||||
/// </summary>
|
||||
/// <param name="credentials">The windows credentials which should be used for authentication</param>
|
||||
public WindowsCredential(ICredentials credentials)
|
||||
: this(null)
|
||||
{
|
||||
m_credentials = credentials;
|
||||
UseDefaultCredentials = credentials == CredentialCache.DefaultCredentials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>WindowsCredential</c> instance using the specified initial token.
|
||||
/// </summary>
|
||||
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
|
||||
public WindowsCredential(WindowsToken initialToken)
|
||||
: base(initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the credentials associated with this windows credential.
|
||||
/// </summary>
|
||||
public ICredentials Credentials
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_credentials;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_credentials = value;
|
||||
UseDefaultCredentials = Credentials == CredentialCache.DefaultCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
public override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Windows;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating what value was passed to WindowsCredential(bool useDefaultCredentials) constructor
|
||||
/// </summary>
|
||||
public Boolean UseDefaultCredentials
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (webResponse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (webResponse.StatusCode == HttpStatusCode.Unauthorized &&
|
||||
webResponse.Headers.GetValues(Internal.HttpHeaders.WwwAuthenticate).Any(x => AuthenticationSchemeValid(x)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (webResponse.StatusCode == HttpStatusCode.ProxyAuthenticationRequired &&
|
||||
webResponse.Headers.GetValues(Internal.HttpHeaders.ProxyAuthenticate).Any(x => AuthenticationSchemeValid(x)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response)
|
||||
{
|
||||
// If we have no idea what kind of credentials we are supposed to be using, don't play a windows token on
|
||||
// the first request.
|
||||
if (response == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (m_credentials != null)
|
||||
{
|
||||
this.InitialToken = new WindowsToken(m_credentials);
|
||||
}
|
||||
|
||||
return new WindowsTokenProvider(this, serverUrl);
|
||||
}
|
||||
|
||||
private static Boolean AuthenticationSchemeValid(String authenticateHeader)
|
||||
{
|
||||
return authenticateHeader.StartsWith("Basic", StringComparison.OrdinalIgnoreCase) ||
|
||||
authenticateHeader.StartsWith("Digest", StringComparison.OrdinalIgnoreCase) ||
|
||||
authenticateHeader.StartsWith("Negotiate", StringComparison.OrdinalIgnoreCase) ||
|
||||
authenticateHeader.StartsWith("Ntlm", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private ICredentials m_credentials;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public sealed class WindowsToken : IssuedToken, ICredentials
|
||||
{
|
||||
internal WindowsToken(ICredentials credentials)
|
||||
{
|
||||
this.Credentials = credentials;
|
||||
}
|
||||
|
||||
public ICredentials Credentials
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
protected internal override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Windows;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ApplyTo(IHttpRequest request)
|
||||
{
|
||||
// Special-cased by the caller because we implement ICredentials
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
NetworkCredential ICredentials.GetCredential(
|
||||
Uri uri,
|
||||
String authType)
|
||||
{
|
||||
return this.Credentials?.GetCredential(uri, authType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
internal sealed class WindowsTokenProvider : IssuedTokenProvider
|
||||
{
|
||||
public WindowsTokenProvider(
|
||||
WindowsCredential credential,
|
||||
Uri serverUrl)
|
||||
: base(credential, serverUrl, serverUrl)
|
||||
{
|
||||
}
|
||||
|
||||
protected override String AuthenticationScheme
|
||||
{
|
||||
get
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", AuthenticationSchemes.Negotiate, AuthenticationSchemes.Ntlm, AuthenticationSchemes.Digest, AuthenticationSchemes.Basic);
|
||||
}
|
||||
}
|
||||
|
||||
public new WindowsCredential Credential
|
||||
{
|
||||
get
|
||||
{
|
||||
return (WindowsCredential)base.Credential;
|
||||
}
|
||||
}
|
||||
|
||||
public override Boolean GetTokenIsInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.CurrentToken == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GitHub.Services.Common.ClientStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for accessing client data stored locally.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)] // for internal use
|
||||
public interface IVssClientStorage : IVssClientStorageReader, IVssClientStorageWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Much like the System.IO.Path.Combine method, this method puts together path segments into a path using
|
||||
/// the appropriate path delimiter.
|
||||
/// </summary>
|
||||
/// <param name="paths"></param>
|
||||
/// <returns></returns>
|
||||
string PathKeyCombine(params string[] paths);
|
||||
|
||||
/// <summary>
|
||||
/// The path segment delimiter used by this storage mechanism.
|
||||
/// </summary>
|
||||
char PathSeparator { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface for reading from local data storage
|
||||
/// </summary>
|
||||
public interface IVssClientStorageReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads one entry from the storage.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to return.</typeparam>
|
||||
/// <param name="path">This is the path key for the data to retrieve.</param>
|
||||
/// <returns>Returns the value stored at the given path as type T</returns>
|
||||
T ReadEntry<T>(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Reads one entry from the storage. If the entry does not exist or can not be converted to type T, the default value provided will be returned.
|
||||
/// When T is not a simple type, and there is extra logic to determine the default value, the pattern: ReadEntry<T>(path) && GetDefault(); is
|
||||
/// preferred, so that method to retrieve the default is not evaluated unless the entry does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to return.</typeparam>
|
||||
/// <param name="path">This is the path key for the data to retrieve.</param>
|
||||
/// <param name="defaultValue">The value to return if the key does not exist or the value can not be converted to type T</param>
|
||||
/// <returns></returns>
|
||||
T ReadEntry<T>(string path, T defaultValue);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all entries under the path provided whose values can be converted to T. If path = "root\mydata", then this will return all entries where path begins with "root\mydata\".
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the entries to return.</typeparam>
|
||||
/// <param name="path">The path pointing to the branch of entries to return.</param>
|
||||
/// <returns></returns>
|
||||
IDictionary<string, T> ReadEntries<T>(string path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface for writing to local data storage
|
||||
/// </summary>
|
||||
public interface IVssClientStorageWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Write one entry into the local data storage.
|
||||
/// </summary>
|
||||
/// <param name="path">This is the key for the data to store. Providing a path allows data to be accessed hierarchicaly.</param>
|
||||
/// <param name="value">The value to store at the specified path. Setting his to NULL will remove the entry.</param>
|
||||
void WriteEntry(string path, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a set of entries to the writer, which provides efficiency benefits over writing each entry individually.
|
||||
/// It also ensures that the either all of the entries are written or in the case of an error, no entries are written.
|
||||
/// Setting a value to NULL, will remove the entry.
|
||||
/// </summary>
|
||||
/// <param name="entries"></param>
|
||||
void WriteEntries(IEnumerable<KeyValuePair<string, Object>> entries);
|
||||
}
|
||||
}
|
||||
@@ -1,623 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace GitHub.Services.Common.ClientStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Class providing access to local file storage, so data can persist across processes.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)] // for internal use
|
||||
public class VssFileStorage : IVssClientStorage, IDisposable
|
||||
{
|
||||
private readonly string m_filePath;
|
||||
private readonly VssFileStorageReader m_reader;
|
||||
private readonly IVssClientStorageWriter m_writer;
|
||||
|
||||
private const char c_defaultPathSeparator = '\\';
|
||||
private const bool c_defaultIgnoreCaseInPaths = false;
|
||||
|
||||
/// <summary>
|
||||
/// The separator to use between the path segments of the storage keys.
|
||||
/// </summary>
|
||||
public char PathSeparator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The StringComparer used to compare keys in the dictionary.
|
||||
/// </summary>
|
||||
public StringComparer PathComparer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This constructor should remain private. Use the factory method GetVssLocalFileStorage to ensure we only have one instance per file,
|
||||
/// which will reduce contention.
|
||||
/// </summary>
|
||||
/// <param name="filePath">This file path to store the settings.</param>
|
||||
/// <param name="pathSeparatorForKeys">The separator to use between the path segments of the storage keys.</param>
|
||||
/// <param name="ignoreCaseInPaths">If true the dictionary will use the OrdinalIgnoreCase StringComparer to compare keys.</param>
|
||||
private VssFileStorage(string filePath, char pathSeparatorForKeys = c_defaultPathSeparator, bool ignoreCaseInPaths = c_defaultIgnoreCaseInPaths) // This constructor should remain private.
|
||||
{
|
||||
PathSeparator = pathSeparatorForKeys;
|
||||
PathComparer = GetAppropriateStringComparer(ignoreCaseInPaths);
|
||||
m_filePath = filePath;
|
||||
m_reader = new VssFileStorageReader(m_filePath, pathSeparatorForKeys, PathComparer);
|
||||
m_writer = new VssFileStorageWriter(m_filePath, pathSeparatorForKeys, PathComparer);
|
||||
}
|
||||
|
||||
public T ReadEntry<T>(string path)
|
||||
{
|
||||
return m_reader.ReadEntry<T>(path);
|
||||
}
|
||||
|
||||
public T ReadEntry<T>(string path, T defaultValue)
|
||||
{
|
||||
return m_reader.ReadEntry<T>(path, defaultValue);
|
||||
}
|
||||
|
||||
public IDictionary<string, T> ReadEntries<T>(string pathPrefix)
|
||||
{
|
||||
return m_reader.ReadEntries<T>(pathPrefix);
|
||||
}
|
||||
|
||||
public void WriteEntries(IEnumerable<KeyValuePair<string, object>> entries)
|
||||
{
|
||||
m_writer.WriteEntries(entries);
|
||||
m_reader.NotifyChanged();
|
||||
}
|
||||
|
||||
public void WriteEntry(string key, object value)
|
||||
{
|
||||
m_writer.WriteEntry(key, value);
|
||||
m_reader.NotifyChanged();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_reader.Dispose();
|
||||
}
|
||||
|
||||
public string PathKeyCombine(params string[] paths)
|
||||
{
|
||||
StringBuilder combinedPath = new StringBuilder();
|
||||
foreach(string segment in paths)
|
||||
{
|
||||
if (segment != null)
|
||||
{
|
||||
string trimmedSegment = segment.TrimEnd(PathSeparator);
|
||||
if (trimmedSegment.Length > 0)
|
||||
{
|
||||
if (combinedPath.Length > 0)
|
||||
{
|
||||
combinedPath.Append(PathSeparator);
|
||||
}
|
||||
combinedPath.Append(trimmedSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinedPath.ToString();
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<string, VssFileStorage> s_storages = new ConcurrentDictionary<string, VssFileStorage>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Factory method to get a VssFileStorage instance ensuring that we don't have two instances for the same file.
|
||||
/// </summary>
|
||||
/// <param name="fullPath">The full path to the storage file. Ensure that the path used is in an appropriately secure location for the data you are storing.</param>
|
||||
/// <param name="pathSeparatorForKeys">The separator to use between the path segments of the storage keys.</param>
|
||||
/// <param name="ignoreCaseInPaths">If true the dictionary will use the OrdinalIgnoreCase StringComparer to compare keys.</param>
|
||||
/// <returns></returns>
|
||||
public static IVssClientStorage GetVssLocalFileStorage(string fullPath, char pathSeparatorForKeys = c_defaultPathSeparator, bool ignoreCaseInPaths = c_defaultIgnoreCaseInPaths)
|
||||
{
|
||||
string normalizedFullPath = Path.GetFullPath(fullPath);
|
||||
VssFileStorage storage = s_storages.GetOrAdd(normalizedFullPath, (key) => new VssFileStorage(key, pathSeparatorForKeys, ignoreCaseInPaths));
|
||||
|
||||
// we need to throw on mismatch if the cache contains a conflicting instance
|
||||
if (storage.PathSeparator != pathSeparatorForKeys)
|
||||
{
|
||||
throw new ArgumentException(CommonResources.ConflictingPathSeparatorForVssFileStorage(pathSeparatorForKeys, normalizedFullPath, storage.PathSeparator));
|
||||
}
|
||||
|
||||
StringComparer pathComparer = GetAppropriateStringComparer(ignoreCaseInPaths);
|
||||
{
|
||||
if (storage.PathComparer != pathComparer)
|
||||
{
|
||||
string caseSensitive = "Ordinal";
|
||||
string caseInsensitive = "OrdinalIgnoreCase";
|
||||
string requested = ignoreCaseInPaths ? caseInsensitive : caseSensitive;
|
||||
string previous = ignoreCaseInPaths ? caseSensitive : caseInsensitive;
|
||||
throw new ArgumentException(CommonResources.ConflictingStringComparerForVssFileStorage(requested, normalizedFullPath, previous));
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Debug.Assert(fullPath.Equals(storage.m_filePath), string.Format("The same storage file is being referenced with different casing. This will cause issues when running in cross patform environments where the file system may be case sensitive. {0} != {1}", storage.m_filePath, normalizedFullPath));
|
||||
#endif
|
||||
return storage;
|
||||
}
|
||||
|
||||
private static StringComparer GetAppropriateStringComparer(bool ignoreCase)
|
||||
{
|
||||
return ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of a VssLocalFileStorage under the current user directory.
|
||||
/// </summary>
|
||||
/// <param name="pathSuffix">This pathSuffix will be combined at the end of the current user data directory for VSS to make a full path. Something like: "%localappdata%\Microsoft\VisualStudio Services\[pathSuffix]"</param>
|
||||
/// <param name="storeByVssVersion">Adds the current product version as a path segment. ...\Microsoft\VisualStudio Services\v[GeneratedVersionInfo.ProductVersion]\[pathSuffix]"</param>
|
||||
/// <param name="pathSeparatorForKeys">The separator to use between the path segments of the storage keys.</param>
|
||||
/// <param name="ignoreCaseInPaths">If true the dictionary will use the OrdinalIgnoreCase StringComparer to compare keys.</param>
|
||||
/// <returns></returns>
|
||||
public static IVssClientStorage GetCurrentUserVssFileStorage(string pathSuffix, bool storeByVssVersion, char pathSeparatorForKeys = c_defaultPathSeparator, bool ignoreCaseInPaths = c_defaultIgnoreCaseInPaths)
|
||||
{
|
||||
return GetVssLocalFileStorage(Path.Combine(storeByVssVersion ? ClientSettingsDirectoryByVersion : ClientSettingsDirectory, pathSuffix), pathSeparatorForKeys, ignoreCaseInPaths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directory containing the client settings files.
|
||||
///
|
||||
/// This will look something like this:
|
||||
/// C:\Users\[user]\AppData\Local\Microsoft\VisualStudio Services\v[GeneratedVersionInfo.ProductVersion]
|
||||
/// </summary>
|
||||
internal static string ClientSettingsDirectoryByVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
// We purposely do not cache this value. This value needs to change if
|
||||
// Windows Impersonation is being used.
|
||||
return Path.Combine(ClientSettingsDirectory, "v" + GeneratedVersionInfo.ProductVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directory containing the client settings files.
|
||||
///
|
||||
/// This will look something like this:
|
||||
/// C:\Users\[user]\AppData\Local\Microsoft\VisualStudio Services
|
||||
/// </summary>
|
||||
internal static string ClientSettingsDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
// We purposely do not cache this value. This value needs to change if
|
||||
// Windows Impersonation is being used.
|
||||
|
||||
// Check to see if we can find the user's local application data directory.
|
||||
string subDir = "Microsoft\\VisualStudio Services";
|
||||
string path = Environment.GetEnvironmentVariable("localappdata");
|
||||
SafeGetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
// If the user has never logged onto this box they will not have a local application data directory.
|
||||
// Check to see if they have a roaming network directory that moves with them.
|
||||
path = SafeGetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
// The user does not have a roaming network directory either. Just place the cache in the
|
||||
// common area.
|
||||
// If we are using the common dir, we might not have access to create a folder under "Microsoft"
|
||||
// so we just create a top level folder.
|
||||
path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
|
||||
subDir = "Microsoft VisualStudio Services";
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(path != null, "folder path cannot be null");
|
||||
return Path.Combine(path, subDir);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets folder path and returns null in case the special folder in question doesn't exist (useful when the user has never logged on, which makes
|
||||
/// GetFolderPath throw)
|
||||
/// </summary>
|
||||
/// <param name="specialFolder">Folder to retrieve</param>
|
||||
/// <returns>Path if available, null othewise</returns>
|
||||
private static string SafeGetFolderPath(Environment.SpecialFolder specialFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Environment.GetFolderPath(specialFolder);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class VssFileStorageReader : VssLocalFile, IVssClientStorageReader, IDisposable
|
||||
{
|
||||
private readonly string m_path;
|
||||
private Dictionary<string, JRaw> m_settings;
|
||||
|
||||
private readonly FileSystemWatcher m_watcher;
|
||||
private readonly ReaderWriterLockSlim m_lock;
|
||||
private long m_completedRefreshId;
|
||||
private long m_outstandingRefreshId;
|
||||
|
||||
public VssFileStorageReader(string fullPath, char pathSeparator, StringComparer comparer)
|
||||
: base(fullPath, pathSeparator, comparer)
|
||||
{
|
||||
m_path = fullPath;
|
||||
m_lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
m_completedRefreshId = 0;
|
||||
m_outstandingRefreshId = 1;
|
||||
|
||||
// Set up the file system watcher
|
||||
{
|
||||
string directoryToWatch = Path.GetDirectoryName(m_path);
|
||||
|
||||
if (!Directory.Exists(directoryToWatch))
|
||||
{
|
||||
Directory.CreateDirectory(directoryToWatch);
|
||||
}
|
||||
|
||||
m_watcher = new FileSystemWatcher(directoryToWatch, Path.GetFileName(m_path));
|
||||
m_watcher.IncludeSubdirectories = false;
|
||||
m_watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
|
||||
m_watcher.Changed += OnCacheFileChanged;
|
||||
m_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
public T ReadEntry<T>(string path)
|
||||
{
|
||||
return ReadEntry<T>(path, default(T));
|
||||
}
|
||||
|
||||
public T ReadEntry<T>(string path, T defaultValue)
|
||||
{
|
||||
path = NormalizePath(path);
|
||||
RefreshIfNeeded();
|
||||
|
||||
Dictionary<string, JRaw> settings = m_settings; // use a pointer to m_settings, incase m_settings gets set to a new instance during the operation
|
||||
JRaw value;
|
||||
if (settings.TryGetValue(path, out value) && value != null)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(value.ToString());
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public IDictionary<string, T> ReadEntries<T>(string pathPrefix)
|
||||
{
|
||||
string prefix = NormalizePath(pathPrefix, true);
|
||||
RefreshIfNeeded();
|
||||
Dictionary<string, JRaw> settings = m_settings; // use a pointer to m_settings, incase m_settings gets set to a new instance during the operation
|
||||
Dictionary<string, T> matchingEntries = new Dictionary<string, T>();
|
||||
foreach (KeyValuePair<string, JRaw> kvp in settings.Where(kvp => kvp.Key == prefix || kvp.Key.StartsWith(prefix + PathSeparator)))
|
||||
{
|
||||
try
|
||||
{
|
||||
matchingEntries[kvp.Key] = JsonConvert.DeserializeObject<T>(kvp.Value.ToString());
|
||||
}
|
||||
catch (JsonSerializationException) { }
|
||||
catch (JsonReaderException) { }
|
||||
}
|
||||
return matchingEntries;
|
||||
}
|
||||
|
||||
private void OnCacheFileChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
NotifyChanged();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_watcher.Dispose();
|
||||
}
|
||||
|
||||
public void NotifyChanged()
|
||||
{
|
||||
using (new ReadLockScope(m_lock))
|
||||
{
|
||||
Interlocked.Increment(ref m_outstandingRefreshId);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshIfNeeded()
|
||||
{
|
||||
long requestedRefreshId;
|
||||
|
||||
using (new ReadLockScope(m_lock))
|
||||
{
|
||||
requestedRefreshId = Interlocked.Read(ref m_outstandingRefreshId);
|
||||
|
||||
if (m_completedRefreshId >= requestedRefreshId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, JRaw> newSettings;
|
||||
using (GetNewMutexScope())
|
||||
{
|
||||
if (m_completedRefreshId >= requestedRefreshId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
newSettings = LoadFile();
|
||||
}
|
||||
|
||||
using (new ReadLockScope(m_lock))
|
||||
{
|
||||
if (m_completedRefreshId >= requestedRefreshId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using (new WriteLockScope(m_lock))
|
||||
{
|
||||
if (m_completedRefreshId >= requestedRefreshId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_completedRefreshId = requestedRefreshId;
|
||||
m_settings = newSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private struct ReadLockScope : IDisposable
|
||||
{
|
||||
public ReadLockScope(ReaderWriterLockSlim @lock)
|
||||
{
|
||||
m_lock = @lock;
|
||||
|
||||
m_lock.EnterReadLock();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_lock.ExitReadLock();
|
||||
}
|
||||
|
||||
private readonly ReaderWriterLockSlim m_lock;
|
||||
}
|
||||
|
||||
private struct WriteLockScope : IDisposable
|
||||
{
|
||||
public WriteLockScope(ReaderWriterLockSlim @lock)
|
||||
{
|
||||
m_lock = @lock;
|
||||
m_lock.EnterWriteLock();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
private readonly ReaderWriterLockSlim m_lock;
|
||||
}
|
||||
}
|
||||
|
||||
private class VssFileStorageWriter : VssLocalFile, IVssClientStorageWriter
|
||||
{
|
||||
public VssFileStorageWriter(string fullPath, char pathSeparator, StringComparer comparer)
|
||||
: base(fullPath, pathSeparator, comparer)
|
||||
{
|
||||
}
|
||||
|
||||
public void WriteEntries(IEnumerable<KeyValuePair<string, object>> entries)
|
||||
{
|
||||
if (entries.Any())
|
||||
{
|
||||
using (GetNewMutexScope())
|
||||
{
|
||||
bool changesMade = false;
|
||||
Dictionary<string, JRaw> originalSettings = LoadFile();
|
||||
Dictionary<string, JRaw> newSettings = new Dictionary<string, JRaw>(PathComparer);
|
||||
if (originalSettings.Any())
|
||||
{
|
||||
originalSettings.Copy(newSettings);
|
||||
}
|
||||
foreach (KeyValuePair<string, object> kvp in entries)
|
||||
{
|
||||
string path = NormalizePath(kvp.Key);
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
JRaw jRawValue = new JRaw(JsonConvert.SerializeObject(kvp.Value));
|
||||
if (!newSettings.ContainsKey(path) || !newSettings[path].Equals(jRawValue))
|
||||
{
|
||||
newSettings[path] = jRawValue;
|
||||
changesMade = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newSettings.Remove(path))
|
||||
{
|
||||
changesMade = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changesMade)
|
||||
{
|
||||
SaveFile(originalSettings, newSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteEntry(string path, object value)
|
||||
{
|
||||
WriteEntries(new KeyValuePair<string, object>[] { new KeyValuePair<string, object>(path, value) });
|
||||
}
|
||||
}
|
||||
|
||||
private class VssLocalFile
|
||||
{
|
||||
private readonly string m_filePath;
|
||||
private readonly string m_bckUpFilePath;
|
||||
private readonly string m_emptyPathSegment;
|
||||
|
||||
public VssLocalFile(string filePath, char pathSeparator, StringComparer comparer)
|
||||
{
|
||||
m_filePath = filePath;
|
||||
PathComparer = comparer;
|
||||
PathSeparator = pathSeparator;
|
||||
m_emptyPathSegment = new string(pathSeparator, 2);
|
||||
FileInfo fileInfo = new FileInfo(m_filePath);
|
||||
m_bckUpFilePath = Path.Combine(fileInfo.Directory.FullName, "~" + fileInfo.Name);
|
||||
}
|
||||
|
||||
protected char PathSeparator { get; }
|
||||
|
||||
protected string NormalizePath(string path, bool allowRootPath = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || path[0] != PathSeparator || path.IndexOf(m_emptyPathSegment, StringComparison.Ordinal) >= 0 || (!allowRootPath && path.Length == 1))
|
||||
{
|
||||
throw new ArgumentException(CommonResources.InvalidClientStoragePath(path, PathSeparator), "path");
|
||||
}
|
||||
if (path[path.Length - 1] == PathSeparator)
|
||||
{
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
protected StringComparer PathComparer { get; }
|
||||
|
||||
protected Dictionary<string, JRaw> LoadFile()
|
||||
{
|
||||
Dictionary<string, JRaw> settings = null;
|
||||
if (File.Exists(m_filePath))
|
||||
{
|
||||
settings = LoadFile(m_filePath);
|
||||
}
|
||||
if ((settings == null || !settings.Any()) && File.Exists(m_bckUpFilePath))
|
||||
{
|
||||
settings = LoadFile(m_bckUpFilePath);
|
||||
}
|
||||
return settings ?? new Dictionary<string, JRaw>(PathComparer);
|
||||
}
|
||||
|
||||
private Dictionary<string, JRaw> LoadFile(string path)
|
||||
{
|
||||
Dictionary<string, JRaw> settings = new Dictionary<string, JRaw>(PathComparer);
|
||||
try
|
||||
{
|
||||
string fileContent;
|
||||
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
|
||||
{
|
||||
using (var sr = new StreamReader(fs, Encoding.UTF8))
|
||||
{
|
||||
fileContent = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
IReadOnlyDictionary<string, JRaw> loadedSettings = JsonConvert.DeserializeObject<IReadOnlyDictionary<string, JRaw>>(fileContent);
|
||||
if (loadedSettings != null)
|
||||
{
|
||||
// Replay the settings into our dictionary one by one so that our uniqueness constraint
|
||||
// isn't violated based on the StringComparer for this instance.
|
||||
foreach (KeyValuePair<string, JRaw> setting in loadedSettings)
|
||||
{
|
||||
settings[setting.Key] = setting.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (DirectoryNotFoundException) { }
|
||||
catch (FileNotFoundException) { }
|
||||
catch (JsonReaderException) { }
|
||||
catch (JsonSerializationException) { }
|
||||
catch (InvalidCastException) { }
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
protected void SaveFile(IDictionary<string, JRaw> originalSettings, IDictionary<string, JRaw> newSettings)
|
||||
{
|
||||
string newContent = JValue.Parse(JsonConvert.SerializeObject(newSettings)).ToString(Formatting.Indented);
|
||||
if (originalSettings.Any())
|
||||
{
|
||||
// during testing, creating this backup provided reliability in the event of aborted threads, and
|
||||
// crashed processes. With this, I was not able to simulate a case where corruption happens, but there is no
|
||||
// 100% gaurantee against corruption.
|
||||
string originalContent = JValue.Parse(JsonConvert.SerializeObject(originalSettings)).ToString(Formatting.Indented);
|
||||
SaveFile(m_bckUpFilePath, originalContent);
|
||||
}
|
||||
SaveFile(m_filePath, newContent);
|
||||
if (File.Exists(m_bckUpFilePath))
|
||||
{
|
||||
File.Delete(m_bckUpFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveFile(string path, string content)
|
||||
{
|
||||
bool success = false;
|
||||
int tries = 0;
|
||||
int retryDelayMilliseconds = 10;
|
||||
const int maxNumberOfRetries = 6;
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Delete))
|
||||
{
|
||||
using (var sw = new StreamWriter(fs, Encoding.UTF8))
|
||||
{
|
||||
sw.Write(content);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (++tries > maxNumberOfRetries)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
Task.Delay(retryDelayMilliseconds).Wait();
|
||||
retryDelayMilliseconds *= 2;
|
||||
}
|
||||
}
|
||||
while (!success);
|
||||
}
|
||||
|
||||
protected MutexScope GetNewMutexScope()
|
||||
{
|
||||
return new MutexScope(m_filePath.Replace(Path.DirectorySeparatorChar, '_'));
|
||||
}
|
||||
|
||||
protected struct MutexScope : IDisposable
|
||||
{
|
||||
public MutexScope(string name)
|
||||
{
|
||||
m_mutex = new Mutex(false, name);
|
||||
|
||||
try
|
||||
{
|
||||
if (!m_mutex.WaitOne(s_mutexTimeout))
|
||||
{
|
||||
throw new TimeoutException();
|
||||
}
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
// If this is thrown, then we hold the mutex.
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_mutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
private readonly Mutex m_mutex;
|
||||
private static readonly TimeSpan s_mutexTimeout = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace GitHub.Services.Common.Diagnostics
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class HttpRequestMessageExtensions
|
||||
{
|
||||
public static VssHttpMethod GetHttpMethod(this HttpRequestMessage message)
|
||||
{
|
||||
String methodName = message.Method.Method;
|
||||
VssHttpMethod httpMethod = VssHttpMethod.UNKNOWN;
|
||||
if (!Enum.TryParse<VssHttpMethod>(methodName, true, out httpMethod))
|
||||
{
|
||||
httpMethod = VssHttpMethod.UNKNOWN;
|
||||
}
|
||||
return httpMethod;
|
||||
}
|
||||
|
||||
public static VssTraceActivity GetActivity(this HttpRequestMessage message)
|
||||
{
|
||||
Object traceActivity;
|
||||
if (!message.Properties.TryGetValue(VssTraceActivity.PropertyName, out traceActivity))
|
||||
{
|
||||
return VssTraceActivity.Empty;
|
||||
}
|
||||
return (VssTraceActivity)traceActivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
|
||||
namespace GitHub.Services.Common.Diagnostics
|
||||
{
|
||||
internal enum VssHttpMethod
|
||||
{
|
||||
UNKNOWN,
|
||||
DELETE,
|
||||
HEAD,
|
||||
GET,
|
||||
OPTIONS,
|
||||
PATCH,
|
||||
POST,
|
||||
PUT,
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
#if !NETSTANDARD
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
#endif
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Services.Common.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a trace activity for correlating diagnostic traces together.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public sealed class VssTraceActivity
|
||||
{
|
||||
private VssTraceActivity()
|
||||
{
|
||||
}
|
||||
|
||||
private VssTraceActivity(Guid activityId)
|
||||
{
|
||||
this.Id = activityId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for the trace activity.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Guid Id
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current trace activity if one is set on the current thread; otherwise, null.
|
||||
/// </summary>
|
||||
public static VssTraceActivity Current
|
||||
{
|
||||
#if !NETSTANDARD
|
||||
get
|
||||
{
|
||||
return CallContext.LogicalGetData(VssTraceActivity.PropertyName) as VssTraceActivity;
|
||||
}
|
||||
private set
|
||||
{
|
||||
CallContext.LogicalSetData(VssTraceActivity.PropertyName, value);
|
||||
}
|
||||
#else
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
set { }
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the empty trace activity.
|
||||
/// </summary>
|
||||
public static VssTraceActivity Empty
|
||||
{
|
||||
get
|
||||
{
|
||||
return s_empty.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a disposable trace scope in which the current trace activity is activated for trace correlation.
|
||||
/// The call context state for <see cref="VssTraceActivity.Current"/> is updated within the scope to reference
|
||||
/// the activated activity.
|
||||
/// </summary>
|
||||
/// <returns>A trace scope for correlating multiple traces together</returns>
|
||||
public IDisposable EnterCorrelationScope()
|
||||
{
|
||||
return new CorrelationScope(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current activity or, if no activity is active on the current thread, creates a new activity for
|
||||
/// trace correlation.
|
||||
/// </summary>
|
||||
/// <returns>The current trace activity or a new trace activity</returns>
|
||||
public static VssTraceActivity GetOrCreate()
|
||||
{
|
||||
if (VssTraceActivity.Current != null)
|
||||
{
|
||||
return VssTraceActivity.Current;
|
||||
}
|
||||
else if (Trace.CorrelationManager.ActivityId == Guid.Empty)
|
||||
{
|
||||
return new VssTraceActivity(Guid.NewGuid());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new VssTraceActivity(Trace.CorrelationManager.ActivityId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new trace activity optionally using the provided identifier.
|
||||
/// </summary>
|
||||
/// <param name="activityId">The activity identifier or none to have one generated</param>
|
||||
/// <returns>A new trace activity instance</returns>
|
||||
public static VssTraceActivity New(Guid activityId = default(Guid))
|
||||
{
|
||||
return new VssTraceActivity(activityId == default(Guid) ? Guid.NewGuid() : activityId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property name used to cache this object on extensible objects.
|
||||
/// </summary>
|
||||
public const String PropertyName = "MS.VSS.Diagnostics.TraceActivity";
|
||||
private static Lazy<VssTraceActivity> s_empty = new Lazy<VssTraceActivity>(() => new VssTraceActivity(Guid.Empty));
|
||||
|
||||
private sealed class CorrelationScope : IDisposable
|
||||
{
|
||||
public CorrelationScope(VssTraceActivity activity)
|
||||
{
|
||||
m_previousActivity = VssTraceActivity.Current;
|
||||
if (m_previousActivity == null || m_previousActivity.Id != activity.Id)
|
||||
{
|
||||
m_swap = true;
|
||||
VssTraceActivity.Current = activity;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_swap)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_swap = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Perform in a finally block to ensure consistency between the two variables
|
||||
VssTraceActivity.Current = m_previousActivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean m_swap;
|
||||
private VssTraceActivity m_previousActivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches Exception Types to back compatible TypeName and TypeKey for the specified range
|
||||
/// of REST Api versions. This allows the current server to send back compatible typename
|
||||
/// and type key json when talking to older clients. It also allows current clients to translate
|
||||
/// exceptions returned from older servers to a current client's exception type.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class ExceptionMappingAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches Exception Types to back compatible TypeName and TypeKey for the specified range
|
||||
/// of REST Api versions. This allows the current server to send back compatible typename
|
||||
/// and type key json when talking to older clients. It also allows current clients to translate
|
||||
/// exceptions returned from older servers to a current client's exception type.
|
||||
/// </summary>
|
||||
/// <param name="minApiVersion">The inclusive minimum REST Api version for this mapping.</param>
|
||||
/// <param name="exclusiveMaxApiVersion">The exclusive maximum REST Api version for this mapping.</param>
|
||||
/// <param name="typeKey">The original typekey to be returned by the server when processing requests within the REST Api range specified.</param>
|
||||
/// <param name="typeName">The original typeName to be returned by the server when processing requests within the REST Api range specified.</param>
|
||||
public ExceptionMappingAttribute(string minApiVersion, string exclusiveMaxApiVersion, string typeKey, string typeName)
|
||||
{
|
||||
MinApiVersion = new Version(minApiVersion);
|
||||
ExclusiveMaxApiVersion = new Version(exclusiveMaxApiVersion);
|
||||
TypeKey = typeKey;
|
||||
TypeName = typeName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The inclusive minimum REST Api version for this mapping.
|
||||
/// </summary>
|
||||
public Version MinApiVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The exclusive maximum REST Api version for this mapping.
|
||||
/// </summary>
|
||||
public Version ExclusiveMaxApiVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The original typekey to be returned by the server when processing requests within the REST Api range specified.
|
||||
/// </summary>
|
||||
public string TypeKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The original typeName to be returned by the server when processing requests within the REST Api range specified.
|
||||
/// </summary>
|
||||
public string TypeName { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using GitHub.Services.Common.Internal;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
[Serializable]
|
||||
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
|
||||
[ExceptionMapping("0.0", "3.0", "VssAuthenticationException", "GitHub.Services.Common.VssAuthenticationException, GitHub.Services.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public class VssAuthenticationException : VssException
|
||||
{
|
||||
public VssAuthenticationException()
|
||||
{
|
||||
}
|
||||
|
||||
public VssAuthenticationException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public VssAuthenticationException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected VssAuthenticationException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
|
||||
[ExceptionMapping("0.0", "3.0", "VssUnauthorizedException", "GitHub.Services.Common.VssUnauthorizedException, GitHub.Services.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public class VssUnauthorizedException : VssException
|
||||
{
|
||||
public VssUnauthorizedException()
|
||||
: this(CommonResources.VssUnauthorizedUnknownServer())
|
||||
{
|
||||
}
|
||||
|
||||
public VssUnauthorizedException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public VssUnauthorizedException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected VssUnauthorizedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown when a config file fails to load
|
||||
/// </summary
|
||||
[Serializable]
|
||||
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
|
||||
[ExceptionMapping("0.0", "3.0", "ConfigFileException", "GitHub.Services.Common.ConfigFileException, GitHub.Services.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public class ConfigFileException : VssException
|
||||
{
|
||||
public ConfigFileException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ConfigFileException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected ConfigFileException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
|
||||
[ExceptionMapping("0.0", "3.0", "VssServiceException", "GitHub.Services.Common.VssServiceException, GitHub.Services.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public class VssServiceException : VssException
|
||||
{
|
||||
public VssServiceException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public VssServiceException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public VssServiceException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception from serialized data
|
||||
/// </summary>
|
||||
/// <param name="info">object holding the serialized data</param>
|
||||
/// <param name="context">context info about the source or destination</param>
|
||||
protected VssServiceException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type name and key for serialization of this exception.
|
||||
/// If not provided, the serializer will provide default values.
|
||||
/// </summary>
|
||||
public virtual void GetTypeNameAndKey(Version restApiVersion, out String typeName, out String typeKey)
|
||||
{
|
||||
GetTypeNameAndKeyForExceptionType(GetType(), restApiVersion, out typeName, out typeKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using GitHub.Services.Common.Internal;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown when validating user input. Similar to ArgumentException but doesn't require the property to be an input parameter.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
|
||||
[ExceptionMapping("0.0", "3.0", "VssPropertyValidationException", "GitHub.Services.Common.VssPropertyValidationException, GitHub.Services.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public class VssPropertyValidationException : VssServiceException
|
||||
{
|
||||
public VssPropertyValidationException(String propertyName, String message)
|
||||
: base(message)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
}
|
||||
|
||||
public VssPropertyValidationException(String propertyName, String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
}
|
||||
|
||||
protected VssPropertyValidationException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
PropertyName = info.GetString("PropertyName");
|
||||
}
|
||||
|
||||
public String PropertyName { get; set; }
|
||||
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue("PropertyName", PropertyName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyTypeNotSupportedException - this is thrown when a type is DBNull or an Object type other than a Byte array.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
|
||||
[ExceptionMapping("0.0", "3.0", "PropertyTypeNotSupportedException", "GitHub.Services.Common.PropertyTypeNotSupportedException, GitHub.Services.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public class PropertyTypeNotSupportedException : VssPropertyValidationException
|
||||
{
|
||||
public PropertyTypeNotSupportedException(String propertyName, Type type)
|
||||
: base(propertyName, CommonResources.VssUnsupportedPropertyValueType(propertyName, type.FullName))
|
||||
{
|
||||
}
|
||||
|
||||
protected PropertyTypeNotSupportedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
// Microsoft Confidential
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for constant generation. Allows types/fields to be generated
|
||||
/// with an alternate name.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class GenerateConstantAttributeBase : Attribute
|
||||
{
|
||||
protected GenerateConstantAttributeBase(string alternateName = null)
|
||||
{
|
||||
AlternateName = alternateName;
|
||||
}
|
||||
|
||||
public string AlternateName { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be applied to a const/readonly-static field of a class/enum/struct, but is
|
||||
/// only used when the containing type has the 'GenerateSpecificConstants' attribute applied.
|
||||
/// This allows the developer to specify exactly what constants to include out of the containing type.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class GenerateConstantAttribute : GenerateConstantAttributeBase
|
||||
{
|
||||
public GenerateConstantAttribute(string alternateName = null)
|
||||
: base(alternateName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applied to any enum/class/struct. Causes the constants generator to create javascript constants
|
||||
/// for all const/readonly-static fields contained by the type.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class GenerateAllConstantsAttribute : GenerateConstantAttribute
|
||||
{
|
||||
public GenerateAllConstantsAttribute(string alternateName = null)
|
||||
: base(alternateName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applied to any enum/class/struct. Causes the constants generator to create javascript constants at runtime
|
||||
/// for the type for any member constants/enumerated values that are tagged with the 'GenerateConstant' attribute.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class GenerateSpecificConstantsAttribute : GenerateConstantAttribute
|
||||
{
|
||||
public GenerateSpecificConstantsAttribute(string alternateName = null)
|
||||
: base(alternateName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applied to a class that represents a data model which is serialized to javascript.
|
||||
/// This attribute controls how TypeScript interfaces are generated for the class that
|
||||
/// this is applied to.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class GenerateInterfaceAttribute : GenerateConstantAttributeBase
|
||||
{
|
||||
public GenerateInterfaceAttribute()
|
||||
: this(true)
|
||||
{
|
||||
}
|
||||
|
||||
public GenerateInterfaceAttribute(string alternateName)
|
||||
: base(alternateName)
|
||||
{
|
||||
GenerateInterface = true;
|
||||
}
|
||||
|
||||
public GenerateInterfaceAttribute(bool generateInterface)
|
||||
: base()
|
||||
{
|
||||
GenerateInterface = generateInterface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to generate a typescript interface for this type
|
||||
/// </summary>
|
||||
public bool GenerateInterface { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface to allow custom implementations to
|
||||
/// gather client certificates when necessary.
|
||||
/// </summary>
|
||||
public interface IVssClientCertificateManager
|
||||
{
|
||||
X509Certificate2Collection ClientCertificates { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public interface IVssHttpRetryInfo
|
||||
{
|
||||
void InitialAttempt(HttpRequestMessage request);
|
||||
|
||||
void Retry(TimeSpan sleep);
|
||||
|
||||
void Reset();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class PerformanceTimerConstants
|
||||
{
|
||||
public const string Header = "X-VSS-PerfData";
|
||||
public const string PerfTimingKey = "PerformanceTimings";
|
||||
|
||||
[Obsolete]
|
||||
public const string Aad = "AAD"; // Previous timer, broken into Token and Graph below
|
||||
|
||||
public const string AadToken = "AadToken";
|
||||
public const string AadGraph = "AadGraph";
|
||||
public const string BlobStorage = "BlobStorage";
|
||||
public const string FinalSqlCommand = "FinalSQLCommand";
|
||||
public const string Redis = "Redis";
|
||||
public const string ServiceBus = "ServiceBus";
|
||||
public const string Sql = "SQL";
|
||||
public const string SqlReadOnly = "SQLReadOnly";
|
||||
public const string SqlRetries = "SQLRetries";
|
||||
public const string TableStorage = "TableStorage";
|
||||
public const string VssClient = "VssClient";
|
||||
public const string DocumentDB = "DocumentDB";
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of performance timings all keyed off of the same string
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class PerformanceTimingGroup
|
||||
{
|
||||
public PerformanceTimingGroup()
|
||||
{
|
||||
this.Timings = new List<PerformanceTimingEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overall duration of all entries in this group in ticks
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public long ElapsedTicks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of timing entries associated with this group
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of timing entries in this group. Only the first few entries in each group are collected.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public List<PerformanceTimingEntry> Timings { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single timing consisting of a duration and start time
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public struct PerformanceTimingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Duration of the entry in ticks
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public long ElapsedTicks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset from Server Request Context start time in microseconds
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public long StartOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties to distinguish timings within the same group or to provide data to send with telemetry
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public IDictionary<String, Object> Properties { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class TaskCancellationExtensions
|
||||
{
|
||||
private struct Void { }
|
||||
|
||||
/// <summary>
|
||||
/// Some APIs (e.g. HttpClient) don't honor cancellation tokens. This wrapper adds an extra layer of cancellation checking.
|
||||
/// </summary>
|
||||
public static Task EnforceCancellation(
|
||||
this Task task,
|
||||
CancellationToken cancellationToken,
|
||||
Func<string> makeMessage = null,
|
||||
[CallerFilePath] string file = "",
|
||||
[CallerMemberName] string member = "",
|
||||
[CallerLineNumber] int line = -1)
|
||||
{
|
||||
Func<Task<Void>> task2 = async () =>
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
return new Void();
|
||||
};
|
||||
|
||||
return task2().EnforceCancellation<Void>(cancellationToken, makeMessage, file, member, line);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some APIs (e.g. HttpClient) don't honor cancellation tokens. This wrapper adds an extra layer of cancellation checking.
|
||||
/// </summary>
|
||||
public static async Task<TResult> EnforceCancellation<TResult>(
|
||||
this Task<TResult> task,
|
||||
CancellationToken cancellationToken,
|
||||
Func<string> makeMessage = null,
|
||||
[CallerFilePath] string file = "",
|
||||
[CallerMemberName] string member = "",
|
||||
[CallerLineNumber] int line = -1)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(task, nameof(task));
|
||||
|
||||
// IsCompleted will return true when the task is in one of the three final states: RanToCompletion, Faulted, or Canceled.
|
||||
if (task.IsCompleted)
|
||||
{
|
||||
return await task;
|
||||
}
|
||||
|
||||
var cancellationTcs = new TaskCompletionSource<bool>(RUN_CONTINUATIONS_ASYNCHRONOUSLY);
|
||||
using (cancellationToken.Register(() => cancellationTcs.SetResult(false)))
|
||||
{
|
||||
var completedTask = await Task.WhenAny(task, cancellationTcs.Task).ConfigureAwait(false);
|
||||
if (completedTask == task)
|
||||
{
|
||||
return await task;
|
||||
}
|
||||
}
|
||||
|
||||
// Even if our actual task actually did honor the cancellation token, there's still a race that our WaitForCancellation
|
||||
// task may have handled the cancellation more quickly.
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new InvalidOperationException("Task ended but cancellation token is not marked for cancellation.");
|
||||
}
|
||||
|
||||
// However, we'd ideally like to throw the cancellation exception from the original task if we can.
|
||||
// Thus, we'll give that task a few seconds to coallesce (e.g. write to a log) before we give up on it.
|
||||
int seconds = 3;
|
||||
var lastChanceTcs = new TaskCompletionSource<bool>(RUN_CONTINUATIONS_ASYNCHRONOUSLY);
|
||||
using (var lastChanceTimer = new CancellationTokenSource(TimeSpan.FromSeconds(seconds)))
|
||||
using (lastChanceTimer.Token.Register(() => lastChanceTcs.SetResult(false)))
|
||||
{
|
||||
var completedTask = await Task.WhenAny(task, lastChanceTcs.Task).ConfigureAwait(false);
|
||||
if (completedTask == task)
|
||||
{
|
||||
return await task;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, we've given up on waiting for this task.
|
||||
ObserveExceptionIfNeeded(task);
|
||||
|
||||
string errorString = $"Task in function {member} at {file}:{line} was still active {seconds} seconds after operation was cancelled.";
|
||||
if (makeMessage != null)
|
||||
{
|
||||
errorString += $" {makeMessage()}";
|
||||
}
|
||||
|
||||
throw new OperationCanceledException(errorString, cancellationToken);
|
||||
}
|
||||
|
||||
private static void ObserveExceptionIfNeeded(Task task)
|
||||
{
|
||||
task.ContinueWith(t => t.Exception, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a flag exposed by TaskCreationOptions and TaskContinuationOptions but it's not in .Net 4.5
|
||||
/// In Azure we have latest .Net loaded which will consume this flag.
|
||||
/// Client environments using earlier .Net would ignore it.
|
||||
/// </summary>
|
||||
private const int RUN_CONTINUATIONS_ASYNCHRONOUSLY = 0x40;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,148 +0,0 @@
|
||||
//*************************************************************************************************
|
||||
// ArrayUtil.cs
|
||||
//
|
||||
// A class with random array processing helper routines.
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//*************************************************************************************************
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
//********************************************************************************************
|
||||
/// <summary>
|
||||
/// A class with random array processing helper routines.
|
||||
/// </summary>
|
||||
//********************************************************************************************
|
||||
public static class ArrayUtility
|
||||
{
|
||||
//****************************************************************************************
|
||||
/// <summary>
|
||||
/// Compare two byte arrays to determine if they contain the same data.
|
||||
/// </summary>
|
||||
/// <param name="a1">First array to compare.</param>
|
||||
/// <param name="a2">Second array to compare.</param>
|
||||
/// <returns>true if the arrays are equal and false if not.</returns>
|
||||
//****************************************************************************************
|
||||
public unsafe static bool Equals(byte[] a1, byte[] a2)
|
||||
{
|
||||
Debug.Assert(a1 != null, "a1 was null");
|
||||
Debug.Assert(a2 != null, "a2 was null");
|
||||
|
||||
// Check if the lengths are the same.
|
||||
if (a1.Length != a2.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (a1.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Equals(a1, a2, a1.Length);
|
||||
}
|
||||
|
||||
//****************************************************************************************
|
||||
/// <summary>
|
||||
/// Generate hash code for a byte array.
|
||||
/// </summary>
|
||||
/// <param name="array">array to generate hash code for.</param>
|
||||
/// <returns>hash generated from the array members.</returns>
|
||||
//****************************************************************************************
|
||||
public static int GetHashCode(byte[] array)
|
||||
{
|
||||
Debug.Assert(array != null, "array was null");
|
||||
|
||||
int hash = 0;
|
||||
// the C# compiler defaults to unchecked behavior, so this will
|
||||
// wrap silently. Since this is a hash code and not a count, this
|
||||
// is fine with us.
|
||||
foreach (byte item in array)
|
||||
{
|
||||
hash += item;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
//****************************************************************************************
|
||||
/// <summary>
|
||||
/// Compare two byte arrays to determine if they contain the same data.
|
||||
/// </summary>
|
||||
/// <param name="a1">First array to compare.</param>
|
||||
/// <param name="a2">Second array to compare.</param>
|
||||
/// <param name="length"># of bytes to compare.</param>
|
||||
/// <returns>true if the arrays are equal and false if not.</returns>
|
||||
//****************************************************************************************
|
||||
public unsafe static bool Equals(byte[] a1, byte[] a2, int length)
|
||||
{
|
||||
// Pin the arrays so that we can use unsafe pointers to compare an int at a time.
|
||||
fixed (byte* p1 = &a1[0])
|
||||
{
|
||||
fixed (byte* p2 = &a2[0])
|
||||
{
|
||||
// Get temps for the pointers because you can't change fixed pointers.
|
||||
byte* q1 = p1, q2 = p2;
|
||||
|
||||
// Compare an int at a time for as long as we can. We divide by four because an int
|
||||
// is always 32 bits in C# regardless of platform.
|
||||
int i;
|
||||
for (i = length >> 2; i > 0; --i)
|
||||
{
|
||||
if (*((int*) q1) != *((int*) q2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
q1 += sizeof(int);
|
||||
q2 += sizeof(int);
|
||||
}
|
||||
|
||||
// Compare a byte at a time for the remaining bytes (0 - 3 of them). This also
|
||||
// depends on ints being 32 bits.
|
||||
for (i = length & 0x3; i > 0; --i)
|
||||
{
|
||||
if (*q1 != *q2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++q1;
|
||||
++q2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//****************************************************************************************
|
||||
/// <summary>
|
||||
/// Convert the byte array to a lower case hex string.
|
||||
/// </summary>
|
||||
/// <param name="bytes">byte array to be converted.</param>
|
||||
/// <returns>hex string converted from byte array.</returns>
|
||||
//****************************************************************************************
|
||||
public static String StringFromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(bytes.Length * 2);
|
||||
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
byte b = bytes[i];
|
||||
|
||||
char first = (char)(((b >> 4) & 0x0F) + 0x30);
|
||||
char second = (char)((b & 0x0F) + 0x30);
|
||||
|
||||
sb.Append(first >= 0x3A ? (char)(first + 0x27) : first);
|
||||
sb.Append(second >= 0x3A ? (char)(second + 0x27) : second);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class BackoffTimerHelper
|
||||
{
|
||||
public static TimeSpan GetRandomBackoff(
|
||||
TimeSpan minBackoff,
|
||||
TimeSpan maxBackoff,
|
||||
TimeSpan? previousBackoff = null)
|
||||
{
|
||||
Random random = null;
|
||||
if (previousBackoff.HasValue)
|
||||
{
|
||||
random = new Random((Int32)previousBackoff.Value.TotalMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
random = new Random();
|
||||
}
|
||||
|
||||
return TimeSpan.FromMilliseconds(random.Next((Int32)minBackoff.TotalMilliseconds, (Int32)maxBackoff.TotalMilliseconds));
|
||||
}
|
||||
|
||||
public static TimeSpan GetExponentialBackoff(
|
||||
Int32 attempt,
|
||||
TimeSpan minBackoff,
|
||||
TimeSpan maxBackoff,
|
||||
TimeSpan deltaBackoff)
|
||||
{
|
||||
Double randomBackoff = (Double)new Random().Next((Int32)(deltaBackoff.TotalMilliseconds * 0.8), (Int32)(deltaBackoff.TotalMilliseconds * 1.2));
|
||||
Double additionalBackoff = attempt < 0 ? (Math.Pow(2.0, (Double)attempt)) * randomBackoff : (Math.Pow(2.0, (Double)attempt) - 1.0) * randomBackoff;
|
||||
return TimeSpan.FromMilliseconds(Math.Min(minBackoff.TotalMilliseconds + additionalBackoff, maxBackoff.TotalMilliseconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class CollectionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds all of the given values to this collection.
|
||||
/// Can be used with dictionaries, which implement <see cref="ICollection{T}"/> and <see cref="IEnumerable{T}"/> where T is <see cref="KeyValuePair{TKey, TValue}"/>.
|
||||
/// For dictionaries, also see <see cref="DictionaryExtensions.SetRange{K, V, TDictionary}(TDictionary, IEnumerable{KeyValuePair{K, V}})"/>
|
||||
/// </summary>
|
||||
public static TCollection AddRange<T, TCollection>(this TCollection collection, IEnumerable<T> values)
|
||||
where TCollection : ICollection<T>
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
collection.Add(value);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the given values to this collection if and only if the values object is not null.
|
||||
/// See <see cref="AddRange{T, TCollection}(TCollection, IEnumerable{T})"/> for more details.
|
||||
/// </summary>
|
||||
public static TCollection AddRangeIfRangeNotNull<T, TCollection>(this TCollection collection, IEnumerable<T> values)
|
||||
where TCollection : ICollection<T>
|
||||
{
|
||||
if (values != null)
|
||||
{
|
||||
collection.AddRange(values);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for wrapping Convert.ChangeType to handle nullable values.
|
||||
/// </summary>
|
||||
public class ConvertUtility
|
||||
{
|
||||
public static object ChangeType(object value, Type type)
|
||||
{
|
||||
return ChangeType(value, type, CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
public static object ChangeType(object value, Type type, IFormatProvider provider)
|
||||
{
|
||||
if (type.IsOfType(typeof(Nullable<>)))
|
||||
{
|
||||
var nullableConverter = new NullableConverter(type);
|
||||
return nullableConverter.ConvertTo(value, nullableConverter.UnderlyingType);
|
||||
}
|
||||
|
||||
return Convert.ChangeType(value, type, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,655 +0,0 @@
|
||||
using GitHub.Services.Common.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new value to the dictionary or updates the value if the entry already exists.
|
||||
/// Returns the updated value inserted into the dictionary.
|
||||
/// </summary>
|
||||
public static V AddOrUpdate<K, V>(this IDictionary<K, V> dictionary,
|
||||
K key, V addValue, Func<V, V, V> updateValueFactory)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out V returnValue))
|
||||
{
|
||||
addValue = updateValueFactory(returnValue, addValue);
|
||||
}
|
||||
|
||||
dictionary[key] = addValue;
|
||||
return addValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IDictionary at the given key, or the default
|
||||
/// value for that type if it is not present.
|
||||
/// </summary>
|
||||
public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dictionary, K key, V @default = default(V))
|
||||
{
|
||||
V value;
|
||||
return dictionary.TryGetValue(key, out value) ? value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IReadOnlyDictionary at the given key, or the default
|
||||
/// value for that type if it is not present.
|
||||
/// </summary>
|
||||
public static V GetValueOrDefault<K, V>(this IReadOnlyDictionary<K, V> dictionary, K key, V @default = default(V))
|
||||
{
|
||||
V value;
|
||||
return dictionary.TryGetValue(key, out value) ? value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in a Dictionary at the given key, or the default
|
||||
/// value for that type if it is not present.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This overload is necessary to prevent Ambiguous Match issues, as Dictionary implements both
|
||||
/// IDictionary and IReadonlyDictionary, but neither interface implements the other
|
||||
/// </remarks>
|
||||
public static V GetValueOrDefault<K, V>(this Dictionary<K, V> dictionary, K key, V @default = default(V))
|
||||
{
|
||||
V value;
|
||||
return dictionary.TryGetValue(key, out value) ? value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IDictionary at the given key, or the default
|
||||
/// nullable value for that type if it is not present.
|
||||
/// </summary>
|
||||
public static V? GetNullableValueOrDefault<K, V>(this IDictionary<K, V> dictionary, K key, V? @default = default(V?)) where V : struct
|
||||
{
|
||||
V value;
|
||||
return dictionary.TryGetValue(key, out value) ? value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IReadOnlyDictionary at the given key, or the default
|
||||
/// nullable value for that type if it is not present.
|
||||
/// </summary>
|
||||
public static V? GetNullableValueOrDefault<K, V>(this IReadOnlyDictionary<K, V> dictionary, K key, V? @default = default(V?)) where V : struct
|
||||
{
|
||||
V value;
|
||||
return dictionary.TryGetValue(key, out value) ? value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in a Dictionary at the given key, or the default
|
||||
/// nullable value for that type if it is not present.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This overload is necessary to prevent Ambiguous Match issues, as Dictionary implements both
|
||||
/// IDictionary and IReadonlyDictionary, but neither interface implements the other
|
||||
/// </remarks>
|
||||
public static V? GetNullableValueOrDefault<K, V>(this Dictionary<K, V> dictionary, K key, V? @default = default(V?)) where V : struct
|
||||
{
|
||||
V value;
|
||||
return dictionary.TryGetValue(key, out value) ? value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IReadonlyDictionary with values of type <see cref="object"/>
|
||||
/// casted as values of requested type, or the defualt if the key is not found or
|
||||
/// if the value was found but not compatabile with the requested type.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">The key type</typeparam>
|
||||
/// <typeparam name="V">The requested type of the stored value</typeparam>
|
||||
/// <param name="dictionary">the dictionary to perform the lookup on</param>
|
||||
/// <param name="key">The key to lookup</param>
|
||||
/// <param name="default">Optional: the default value to return if not found</param>
|
||||
/// <returns>The value at the key, or the default if it is not found or of the wrong type</returns>
|
||||
public static V GetCastedValueOrDefault<K, V>(this IReadOnlyDictionary<K, object> dictionary, K key, V @default = default(V))
|
||||
{
|
||||
object value;
|
||||
return dictionary.TryGetValue(key, out value) && value is V ? (V)value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IDictionary with values of type <see cref="object"/>
|
||||
/// casted as values of requested type, or the defualt if the key is not found or
|
||||
/// if the value was found but not compatabile with the requested type.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">The key type</typeparam>
|
||||
/// <typeparam name="V">The requested type of the stored value</typeparam>
|
||||
/// <param name="dictionary">the dictionary to perform the lookup on</param>
|
||||
/// <param name="key">The key to lookup</param>
|
||||
/// <param name="default">Optional: the default value to return if not found</param>
|
||||
/// <returns>The value at the key, or the default if it is not found or of the wrong type</returns>
|
||||
public static V GetCastedValueOrDefault<K, V>(this IDictionary<K, object> dictionary, K key, V @default = default(V))
|
||||
{
|
||||
object value;
|
||||
return dictionary.TryGetValue(key, out value) && value is V ? (V)value : @default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in a Dictionary with values of type <see cref="object"/>
|
||||
/// casted as values of requested type, or the defualt if the key is not found or
|
||||
/// if the value was found but not compatabile with the requested type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This overload is necessary to prevent Ambiguous Match issues, as Dictionary implements both
|
||||
/// IDictionary and IReadonlyDictionary, but neither interface implements the other
|
||||
/// </remarks>
|
||||
/// <typeparam name="K">The key type</typeparam>
|
||||
/// <typeparam name="V">The requested type of the stored value</typeparam>
|
||||
/// <param name="dictionary">the dictionary to perform the lookup on</param>
|
||||
/// <param name="key">The key to lookup</param>
|
||||
/// <param name="default">Optional: the default value to return if not found</param>
|
||||
/// <returns>The value at the key, or the default if it is not found or of the wrong type</returns>
|
||||
public static V GetCastedValueOrDefault<K, V>(this Dictionary<K, object> dictionary, K key, V @default = default(V))
|
||||
{
|
||||
return ((IReadOnlyDictionary<K, object>)dictionary).GetCastedValueOrDefault(key, @default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IDictionary at the given key, or creates a new value using the default constructor, adds it at the given key, and returns the new value.
|
||||
/// </summary>
|
||||
public static V GetOrAddValue<K, V>(this IDictionary<K, V> dictionary, K key) where V : new()
|
||||
{
|
||||
V value = default(V);
|
||||
|
||||
if (!dictionary.TryGetValue(key, out value))
|
||||
{
|
||||
value = new V();
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value in an IDictionary at the given key, or creates a new value using the given delegate, adds it at the given key, and returns the new value.
|
||||
/// </summary>
|
||||
public static V GetOrAddValue<K, V>(this IDictionary<K, V> dictionary, K key, Func<V> createValueToAdd)
|
||||
{
|
||||
V value = default(V);
|
||||
|
||||
if (!dictionary.TryGetValue(key, out value))
|
||||
{
|
||||
value = createValueToAdd();
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the given key-value pairs (such as from another dictionary, since IDictionary implements IEnumerable<KeyValuePair>) to this dictionary.
|
||||
/// Overwrites preexisting values of the same key.
|
||||
/// To avoid overwriting values, use <see cref="CollectionsExtensions.AddRange{T, TCollection}(TCollection, IEnumerable{T})"/>.
|
||||
/// </summary>
|
||||
/// <returns>this dictionary</returns>
|
||||
public static TDictionary SetRange<K, V, TDictionary>(this TDictionary dictionary, IEnumerable<KeyValuePair<K, V>> keyValuePairs)
|
||||
where TDictionary : IDictionary<K, V>
|
||||
{
|
||||
foreach (var keyValuePair in keyValuePairs)
|
||||
{
|
||||
dictionary[keyValuePair.Key] = keyValuePair.Value;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the given key-value pairs if and only if the key-value pairs object is not null.
|
||||
/// See <see cref="SetRange{K, V, TDictionary}(TDictionary, IEnumerable{KeyValuePair{K, V}})"/> for more details.
|
||||
/// </summary>
|
||||
/// <returns>this dictionary</returns>
|
||||
public static TDictionary SetRangeIfRangeNotNull<K, V, TDictionary>(this TDictionary dictionary, IEnumerable<KeyValuePair<K, V>> keyValuePairs)
|
||||
where TDictionary : IDictionary<K, V>
|
||||
{
|
||||
if (keyValuePairs != null)
|
||||
{
|
||||
dictionary.SetRange(keyValuePairs);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the given key-value pairs to this lazily initialized dictionary if and only if the key-value pairs object is not null or empty.
|
||||
/// Does not initialize the dictionary otherwise.
|
||||
/// See <see cref="SetRange{K, V, TDictionary}(TDictionary, IEnumerable{KeyValuePair{K, V}})"/> for more details.
|
||||
/// </summary>
|
||||
/// <returns>this dictionary</returns>
|
||||
public static Lazy<TDictionary> SetRangeIfRangeNotNullOrEmpty<K, V, TDictionary>(this Lazy<TDictionary> lazyDictionary, IEnumerable<KeyValuePair<K, V>> keyValuePairs)
|
||||
where TDictionary : IDictionary<K, V>
|
||||
{
|
||||
if (keyValuePairs != null && keyValuePairs.Any())
|
||||
{
|
||||
lazyDictionary.Value.SetRange(keyValuePairs);
|
||||
}
|
||||
|
||||
return lazyDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a key to the dictionary, if it does not already exist.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The <see cref="IDictionary{TKey,TValue}"/> instance where <c>TValue</c> is <c>object</c></param>
|
||||
/// <param name="key">The key to add</param>
|
||||
/// <param name="value">The value to add</param>
|
||||
/// <returns><c>true</c> if the key was added with the specified value. If the key already exists, the method returns <c>false</c> without updating the value.</returns>
|
||||
public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
|
||||
{
|
||||
if (dictionary.ContainsKey(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary.Add(key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add all of the given key-values pairs to the dictionary, if they do not already exist.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The <see cref="IDictionary{TKey,TValue}"/> instance where <c>TValue</c> is <c>object</c></param>
|
||||
/// <param name="keyValuePairs">The values to try and add to the dictionary</param>
|
||||
/// <returns><c>true</c> if the all of the values were added. If any of the keys exists, the method returns <c>false</c> without updating the value.</returns>
|
||||
public static bool TryAddRange<TKey, TValue, TDictionary>(this TDictionary dictionary, IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs) where TDictionary : IDictionary<TKey, TValue>
|
||||
{
|
||||
bool rangeAdded = true;
|
||||
foreach (var keyValuePair in keyValuePairs)
|
||||
{
|
||||
rangeAdded &= dictionary.TryAdd(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
return rangeAdded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of <typeparamref name="T"/> associated with the specified key or <c>default</c> value if
|
||||
/// either the key is not present or the value is not of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value associated with the specified key.</typeparam>
|
||||
/// <param name="dictionary">The <see cref="IDictionary{TKey,TValue}"/> instance where <c>TValue</c> is <c>object</c>.</param>
|
||||
/// <param name="key">The key whose value to get.</param>
|
||||
/// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter.</param>
|
||||
/// <returns><c>true</c> if key was found, value is non-null, and value is of type <typeparamref name="T"/>; otherwise false.</returns>
|
||||
public static bool TryGetValue<T>(this IDictionary<string, object> dictionary, string key, out T value)
|
||||
{
|
||||
object valueObj;
|
||||
if (dictionary.TryGetValue(key, out valueObj))
|
||||
{
|
||||
//Handle Guids specially
|
||||
if (typeof(T) == typeof(Guid))
|
||||
{
|
||||
Guid guidVal;
|
||||
if (dictionary.TryGetGuid(key, out guidVal))
|
||||
{
|
||||
value = (T)(object)guidVal;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Handle Enums specially
|
||||
if (typeof(T).GetTypeInfo().IsEnum)
|
||||
{
|
||||
if (dictionary.TryGetEnum(key, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (valueObj is T)
|
||||
{
|
||||
value = (T)valueObj;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of T associated with the specified key if the value can be converted to T according to <see cref="PropertyValidation"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type of the value associated with the specified key</typeparam>
|
||||
/// <param name="dictionary">the dictionary from which we should retrieve the value</param>
|
||||
/// <param name="key">the key of the value to retrieve</param>
|
||||
/// <param name="value">when this method returns, the value associated with the specified key, if the key is found and the value is convertible to T,
|
||||
/// or default of T, if not</param>
|
||||
/// <returns>true if the value was retrieved successfully, otherwise false</returns>
|
||||
public static bool TryGetValidatedValue<T>(this IDictionary<string, object> dictionary, string key, out T value, bool allowNull = true)
|
||||
{
|
||||
value = default(T);
|
||||
//try to convert to T. T *must* be something with
|
||||
//TypeCode != TypeCode.object (and not DBNull) OR
|
||||
//byte[] or guid or object.
|
||||
if (!PropertyValidation.IsValidConvertibleType(typeof(T)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//special case guid...
|
||||
if (typeof(T) == typeof(Guid))
|
||||
{
|
||||
Guid guidVal;
|
||||
if (dictionary.TryGetGuid(key, out guidVal))
|
||||
{
|
||||
value = (T)(object)guidVal;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
object objValue = null;
|
||||
if (dictionary.TryGetValue(key, out objValue))
|
||||
{
|
||||
if (objValue == null)
|
||||
{
|
||||
//we found it and it is
|
||||
//null, which may be okay depending on the allowNull flag
|
||||
//value is already = default(T)
|
||||
return allowNull;
|
||||
}
|
||||
|
||||
if (typeof(T).GetTypeInfo().IsAssignableFrom(objValue.GetType().GetTypeInfo()))
|
||||
{
|
||||
value = (T)objValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof(T).GetTypeInfo().IsEnum)
|
||||
{
|
||||
if (dictionary.TryGetEnum(key, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (objValue is string)
|
||||
{
|
||||
TypeCode typeCode = Type.GetTypeCode(typeof(T));
|
||||
|
||||
try
|
||||
{
|
||||
value = (T)Convert.ChangeType(objValue, typeCode, CultureInfo.CurrentCulture);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Enum value associated with the specified key if the value can be converted to an Enum.
|
||||
/// </summary>
|
||||
public static bool TryGetEnum<T>(this IDictionary<string, object> dictionary, string key, out T value)
|
||||
{
|
||||
value = default(T);
|
||||
|
||||
object objValue = null;
|
||||
|
||||
if (dictionary.TryGetValue(key, out objValue))
|
||||
{
|
||||
if (objValue is string)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = (T)Enum.Parse(typeof(T), (string)objValue, true);
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Provided string is not a member of enumeration
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
value = (T)objValue;
|
||||
return true;
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
// Value cannot be cast to the enum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Guid value associated with the specified key if the value can be converted to a Guid.
|
||||
/// </summary>
|
||||
public static bool TryGetGuid(this IDictionary<string, object> dictionary, string key, out Guid value)
|
||||
{
|
||||
value = Guid.Empty;
|
||||
|
||||
object objValue = null;
|
||||
|
||||
if (dictionary.TryGetValue(key, out objValue))
|
||||
{
|
||||
if (objValue is Guid)
|
||||
{
|
||||
value = (Guid)objValue;
|
||||
return true;
|
||||
}
|
||||
else if (objValue is string)
|
||||
{
|
||||
return Guid.TryParse((string)objValue, out value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the values from this <see cref="IDictionary{TKey, TValue}"/> into a destination <see cref="IDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <param name="source">The source dictionary from which to from.</param>
|
||||
/// <param name="dest">The destination dictionary to which to copy to.</param>
|
||||
/// <param name="filter">Optional filtering predicate.</param>
|
||||
/// <returns>The destination dictionary.</returns>
|
||||
/// <remarks>
|
||||
/// If <paramref name="dest"/> is <c>null</c>, no changes are made.
|
||||
/// </remarks>
|
||||
public static IDictionary<TKey, TValue> Copy<TKey, TValue>(this IDictionary<TKey, TValue> source, IDictionary<TKey, TValue> dest, Predicate<TKey> filter)
|
||||
{
|
||||
if (dest == null)
|
||||
{
|
||||
return dest;
|
||||
}
|
||||
|
||||
foreach (var key in source.Keys)
|
||||
{
|
||||
if (filter == null || filter(key))
|
||||
{
|
||||
dest[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the values from this <see cref="IDictionary{TKey, TValue}"/> into a destination <see cref="IDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <param name="source">The source dictionary from which to from.</param>
|
||||
/// <param name="dest">The destination dictionary to which to copy to.</param>
|
||||
/// <returns>The destination dictionary.</returns>
|
||||
/// <remarks>
|
||||
/// If <paramref name="dest"/> is <c>null</c>, no changes are made.
|
||||
/// </remarks>
|
||||
public static IDictionary<TKey, TValue> Copy<TKey, TValue>(this IDictionary<TKey, TValue> source, IDictionary<TKey, TValue> dest)
|
||||
{
|
||||
return source.Copy(dest, filter: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the given key-value pair if and only if the value is not null.
|
||||
/// </summary>
|
||||
public static IDictionary<TKey, TValue> SetIfNotNull<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> dictionary,
|
||||
TKey key,
|
||||
TValue value)
|
||||
where TValue : class
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
dictionary[key] = value;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the given key-value pair on this lazily initialized dictionary if and only if the value is not null.
|
||||
/// Does not initialize the dictionary otherwise.
|
||||
/// </summary>
|
||||
public static Lazy<IDictionary<TKey, TValue>> SetIfNotNull<TKey, TValue>(
|
||||
this Lazy<IDictionary<TKey, TValue>> dictionary,
|
||||
TKey key,
|
||||
TValue value)
|
||||
where TValue : class
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
dictionary.Value[key] = value;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given key-value pair to this dictionary if the value is nonnull
|
||||
/// and does not conflict with a preexisting value for the same key.
|
||||
/// No-ops if the value is null.
|
||||
/// No-ops if the preexisting value for the same key is equal to the given value.
|
||||
/// Throws <see cref="ArgumentException"/> if the preexisting value for the same key is not equal to the given value.
|
||||
/// </summary>
|
||||
public static IDictionary<TKey, TValue> SetIfNotNullAndNotConflicting<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> dictionary,
|
||||
TKey key,
|
||||
TValue value,
|
||||
string valuePropertyName = "value",
|
||||
string dictionaryName = "dictionary")
|
||||
where TValue : class
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
dictionary.CheckForConflict(key, value, valuePropertyName, dictionaryName, ignoreDefaultValue: true);
|
||||
|
||||
dictionary[key] = value;
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given key-value pair to this dictionary if the value does not conflict with a preexisting value for the same key.
|
||||
/// No-ops if the preexisting value for the same key is equal to the given value.
|
||||
/// Throws <see cref="ArgumentException"/> if the preexisting value for the same key is not equal to the given value.
|
||||
/// </summary>
|
||||
public static IDictionary<TKey, TValue> SetIfNotConflicting<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> dictionary,
|
||||
TKey key,
|
||||
TValue value,
|
||||
string valuePropertyName = "value",
|
||||
string dictionaryName = "dictionary")
|
||||
{
|
||||
dictionary.CheckForConflict(key, value, valuePropertyName, dictionaryName, ignoreDefaultValue: false);
|
||||
|
||||
dictionary[key] = value;
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws <see cref="ArgumentException"/> if this IDictionary contains a preexisting value for the same key which is not equal to the given key.
|
||||
/// </summary>
|
||||
public static void CheckForConflict<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> dictionary,
|
||||
TKey key,
|
||||
TValue value,
|
||||
string valuePropertyName = "value",
|
||||
string dictionaryName = "dictionary",
|
||||
bool ignoreDefaultValue = true)
|
||||
{
|
||||
if (Equals(value, default(TValue)) && ignoreDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TValue previousValue = default(TValue);
|
||||
|
||||
if (!dictionary.TryGetValue(key, out previousValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Equals(previousValue, default(TValue)) && ignoreDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Equals(value, previousValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ArgumentException(
|
||||
String.Format(CultureInfo.CurrentCulture,
|
||||
"Parameter {0} = '{1}' inconsistent with {2}['{3}'] => '{4}'",
|
||||
valuePropertyName, value, dictionaryName, key, previousValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws <see cref="ArgumentException"/> if this IReadOnlyDictionary contains a preexisting value for the same key which is not equal to the given key.
|
||||
/// </summary>
|
||||
public static void CheckForConflict<TKey, TValue>(
|
||||
this IReadOnlyDictionary<TKey, TValue> dictionary,
|
||||
TKey key,
|
||||
TValue value,
|
||||
string valuePropertyName = "value",
|
||||
string dictionaryName = "dictionary",
|
||||
bool ignoreDefaultValue = true)
|
||||
{
|
||||
if (Equals(value, default(TValue)) && ignoreDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TValue previousValue = default(TValue);
|
||||
|
||||
if (!dictionary.TryGetValue(key, out previousValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Equals(previousValue, default(TValue)) && ignoreDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Equals(value, previousValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ArgumentException(
|
||||
String.Format(CultureInfo.CurrentCulture,
|
||||
"Parameter {0} = \"{1}\" is inconsistent with {2}[\"{3}\"] => \"{4}\"",
|
||||
valuePropertyName, value, dictionaryName, key, previousValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an empty <see cref="IEnumerable{T}"/> if the supplied source is null.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements of source.</typeparam>
|
||||
/// <param name="source">A sequence of values to return when not null.</param>
|
||||
/// <returns>The source sequence, or a new empty one if source was null.</returns>
|
||||
public static IEnumerable<T> AsEmptyIfNull<T>(this IEnumerable<T> source)
|
||||
=> source ?? Enumerable.Empty<T>();
|
||||
|
||||
/// <summary>
|
||||
/// If an enumerable is null, and it has a default constructor, return an empty collection by calling the
|
||||
/// default constructor.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnumerable">The type of the Enumerable</typeparam>
|
||||
/// <param name="source">A sequence of values to return when not null</param>
|
||||
/// <returns>The source sequence, or a new empty one if source was null.</returns>
|
||||
public static TEnumerable AsEmptyIfNull<TEnumerable>(this TEnumerable source) where TEnumerable : class, IEnumerable, new()
|
||||
=> source ?? new TEnumerable();
|
||||
|
||||
/// <summary>
|
||||
/// Splits a source <see cref="IEnumerable{T}"/> into several <see cref="IList{T}"/>s
|
||||
/// with a max size of batchSize.
|
||||
/// <remarks>Note that batchSize must be one or larger.</remarks>
|
||||
/// </summary>
|
||||
/// <param name="source">A sequence of values to split into smaller batches.</param>
|
||||
/// <param name="batchSize">The number of elements to place in each batch.</param>
|
||||
/// <returns>The original collection, split into batches.</returns>
|
||||
public static IEnumerable<IList<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(source, nameof(source));
|
||||
ArgumentUtility.CheckBoundsInclusive(batchSize, 1, int.MaxValue, nameof(batchSize));
|
||||
|
||||
var nextBatch = new List<T>(batchSize);
|
||||
foreach (T item in source)
|
||||
{
|
||||
nextBatch.Add(item);
|
||||
if (nextBatch.Count == batchSize)
|
||||
{
|
||||
yield return nextBatch;
|
||||
nextBatch = new List<T>(batchSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextBatch.Count > 0)
|
||||
{
|
||||
yield return nextBatch;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits an <see cref="IEnumerable{T}"/> into two partitions, determined by the supplied predicate. Those
|
||||
/// that follow the predicate are returned in the first, with the remaining elements in the second.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements of source.</typeparam>
|
||||
/// <param name="source">The source enumerable to partition.</param>
|
||||
/// <param name="predicate">The predicate applied to filter the items into their partitions.</param>
|
||||
/// <returns>An object containing the matching and nonmatching results.</returns>
|
||||
public static PartitionResults<T> Partition<T>(this IEnumerable<T> source, Predicate<T> predicate)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(source, nameof(source));
|
||||
ArgumentUtility.CheckForNull(predicate, nameof(predicate));
|
||||
|
||||
var results = new PartitionResults<T>();
|
||||
|
||||
foreach (var item in source)
|
||||
{
|
||||
if (predicate(item))
|
||||
{
|
||||
results.MatchingPartition.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.NonMatchingPartition.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Partitions items from a source IEnumerable into N+1 lists, where the first N lists are determened
|
||||
/// by the sequential check of the provided predicates, with the N+1 list containing those items
|
||||
/// which matched none of the provided predicates.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in source.</typeparam>
|
||||
/// <param name="source">The source containing the elements to partition</param>
|
||||
/// <param name="predicates">The predicates to determine which list the results end up in</param>
|
||||
/// <returns>An item containing the matching collections and a collection containing the non-matching items.</returns>
|
||||
public static MultiPartitionResults<T> Partition<T>(this IEnumerable<T> source, params Predicate<T>[] predicates)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(source, nameof(source));
|
||||
ArgumentUtility.CheckForNull(predicates, nameof(predicates));
|
||||
|
||||
var range = Enumerable.Range(0, predicates.Length).ToList();
|
||||
|
||||
var results = new MultiPartitionResults<T>();
|
||||
results.MatchingPartitions.AddRange(range.Select(_ => new List<T>()));
|
||||
|
||||
foreach (var item in source)
|
||||
{
|
||||
bool added = false;
|
||||
|
||||
foreach (var predicateIndex in range.Where(predicateIndex => predicates[predicateIndex](item)))
|
||||
{
|
||||
results.MatchingPartitions[predicateIndex].Add(item);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!added)
|
||||
{
|
||||
results.NonMatchingPartition.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges two sorted IEnumerables using the given comparison function which
|
||||
/// defines a total ordering of the data.
|
||||
/// </summary>
|
||||
public static IEnumerable<T> Merge<T>(
|
||||
this IEnumerable<T> first,
|
||||
IEnumerable<T> second,
|
||||
IComparer<T> comparer)
|
||||
{
|
||||
return Merge(first, second, comparer == null ? (Func<T, T, int>)null : comparer.Compare);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges two sorted IEnumerables using the given comparison function which
|
||||
/// defines a total ordering of the data.
|
||||
/// </summary>
|
||||
public static IEnumerable<T> Merge<T>(
|
||||
this IEnumerable<T> first,
|
||||
IEnumerable<T> second,
|
||||
Func<T, T, int> comparer)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(first, nameof(first));
|
||||
ArgumentUtility.CheckForNull(second, nameof(second));
|
||||
ArgumentUtility.CheckForNull(comparer, nameof(comparer));
|
||||
|
||||
using (IEnumerator<T> e1 = first.GetEnumerator())
|
||||
using (IEnumerator<T> e2 = second.GetEnumerator())
|
||||
{
|
||||
bool e1Valid = e1.MoveNext();
|
||||
bool e2Valid = e2.MoveNext();
|
||||
|
||||
while (e1Valid && e2Valid)
|
||||
{
|
||||
if (comparer(e1.Current, e2.Current) <= 0)
|
||||
{
|
||||
yield return e1.Current;
|
||||
|
||||
e1Valid = e1.MoveNext();
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return e2.Current;
|
||||
|
||||
e2Valid = e2.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
while (e1Valid)
|
||||
{
|
||||
yield return e1.Current;
|
||||
|
||||
e1Valid = e1.MoveNext();
|
||||
}
|
||||
|
||||
while (e2Valid)
|
||||
{
|
||||
yield return e2.Current;
|
||||
|
||||
e2Valid = e2.MoveNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges two sorted IEnumerables using the given comparison function which defines a total ordering of the data. Unlike Merge, this method requires that
|
||||
/// both IEnumerables contain distinct elements. Likewise, the returned IEnumerable will only contain distinct elements. If the same element appears in both inputs,
|
||||
/// it will appear only once in the output.
|
||||
///
|
||||
/// Example:
|
||||
/// first: [1, 3, 5]
|
||||
/// second: [4, 5, 7]
|
||||
/// result: [1, 3, 4, 5, 7]
|
||||
/// </summary>
|
||||
public static IEnumerable<T> MergeDistinct<T>(
|
||||
this IEnumerable<T> first,
|
||||
IEnumerable<T> second,
|
||||
IComparer<T> comparer)
|
||||
{
|
||||
return MergeDistinct(first, second, comparer == null ? (Func<T, T, int>)null : comparer.Compare);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges two sorted IEnumerables using the given comparison function which defines a total ordering of the data. Unlike Merge, this method requires that
|
||||
/// both IEnumerables contain distinct elements. Likewise, the returned IEnumerable will only contain distinct elements. If the same element appears in both inputs,
|
||||
/// it will appear only once in the output.
|
||||
///
|
||||
/// Example:
|
||||
/// first: [1, 3, 5]
|
||||
/// second: [4, 5, 7]
|
||||
/// result: [1, 3, 4, 5, 7]
|
||||
/// </summary>
|
||||
public static IEnumerable<T> MergeDistinct<T>(
|
||||
this IEnumerable<T> first,
|
||||
IEnumerable<T> second,
|
||||
Func<T, T, int> comparer)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(first, nameof(first));
|
||||
ArgumentUtility.CheckForNull(second, nameof(second));
|
||||
ArgumentUtility.CheckForNull(comparer, nameof(comparer));
|
||||
|
||||
using (IEnumerator<T> e1 = first.GetEnumerator())
|
||||
using (IEnumerator<T> e2 = second.GetEnumerator())
|
||||
{
|
||||
bool e1Valid = e1.MoveNext();
|
||||
bool e2Valid = e2.MoveNext();
|
||||
|
||||
while (e1Valid && e2Valid)
|
||||
{
|
||||
if (comparer(e1.Current, e2.Current) < 0)
|
||||
{
|
||||
yield return e1.Current;
|
||||
|
||||
e1Valid = e1.MoveNext();
|
||||
}
|
||||
else if (comparer(e1.Current, e2.Current) > 0)
|
||||
{
|
||||
yield return e2.Current;
|
||||
|
||||
e2Valid = e2.MoveNext();
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return e1.Current;
|
||||
|
||||
e1Valid = e1.MoveNext();
|
||||
e2Valid = e2.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
while (e1Valid)
|
||||
{
|
||||
yield return e1.Current;
|
||||
|
||||
e1Valid = e1.MoveNext();
|
||||
}
|
||||
|
||||
while (e2Valid)
|
||||
{
|
||||
yield return e2.Current;
|
||||
|
||||
e2Valid = e2.MoveNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a HashSet based on the elements in <paramref name="source"/>.
|
||||
/// </summary>
|
||||
public static HashSet<T> ToHashSet<T>(
|
||||
IEnumerable<T> source)
|
||||
{
|
||||
return new HashSet<T>(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a HashSet with equality comparer <paramref name="comparer"/> based on the elements
|
||||
/// in <paramref name="source"/>.
|
||||
/// </summary>
|
||||
public static HashSet<T> ToHashSet<T>(
|
||||
IEnumerable<T> source,
|
||||
IEqualityComparer<T> comparer)
|
||||
{
|
||||
return new HashSet<T>(source, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a HashSet based on the elements in <paramref name="source"/>, using transformation
|
||||
/// function <paramref name="selector"/>.
|
||||
/// </summary>
|
||||
public static HashSet<TOut> ToHashSet<TIn, TOut>(
|
||||
this IEnumerable<TIn> source,
|
||||
Func<TIn, TOut> selector)
|
||||
{
|
||||
return new HashSet<TOut>(source.Select(selector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a HashSet with equality comparer <paramref name="comparer"/> based on the elements
|
||||
/// in <paramref name="source"/>, using transformation function <paramref name="selector"/>.
|
||||
/// </summary>
|
||||
public static HashSet<TOut> ToHashSet<TIn, TOut>(
|
||||
this IEnumerable<TIn> source,
|
||||
Func<TIn, TOut> selector,
|
||||
IEqualityComparer<TOut> comparer)
|
||||
{
|
||||
return new HashSet<TOut>(source.Select(selector), comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified action to each of the items in the collection
|
||||
/// <typeparam name="T">The type of the elements in the collection.</typeparam>
|
||||
/// <param name="collection">The collection on which the action will be performed</param>
|
||||
/// <param name="action">The action to be performed</param>
|
||||
/// </summary>
|
||||
public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(action, nameof(action));
|
||||
ArgumentUtility.CheckForNull(collection, nameof(collection));
|
||||
|
||||
foreach (T item in collection)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the item to the List if the condition is satisfied
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the collection.</typeparam>
|
||||
/// <param name="list">The collection on which the action will be performed</param>
|
||||
/// <param name="condition">The Condition under which the item will be added</param>
|
||||
/// <param name="element">The element to be added</param>
|
||||
public static void AddIf<T>(this List<T> list, bool condition, T element)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
list.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a collection of key-value string pairs to a NameValueCollection.
|
||||
/// </summary>
|
||||
/// <param name="pairs">The key-value string pairs.</param>
|
||||
/// <returns>The NameValueCollection.</returns>
|
||||
public static NameValueCollection ToNameValueCollection(this IEnumerable<KeyValuePair<string, string>> pairs)
|
||||
{
|
||||
NameValueCollection collection = new NameValueCollection();
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in pairs)
|
||||
{
|
||||
collection.Add(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public static IList<P> PartitionSolveAndMergeBack<T, P>(this IList<T> source, Predicate<T> predicate, Func<IList<T>, IList<P>> matchingPartitionSolver, Func<IList<T>, IList<P>> nonMatchingPartitionSolver)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(source, nameof(source));
|
||||
ArgumentUtility.CheckForNull(predicate, nameof(predicate));
|
||||
ArgumentUtility.CheckForNull(matchingPartitionSolver, nameof(matchingPartitionSolver));
|
||||
ArgumentUtility.CheckForNull(nonMatchingPartitionSolver, nameof(nonMatchingPartitionSolver));
|
||||
|
||||
var partitionedSource = new PartitionResults<Tuple<int, T>>();
|
||||
|
||||
for (int sourceCnt = 0; sourceCnt < source.Count; sourceCnt++)
|
||||
{
|
||||
var item = source[sourceCnt];
|
||||
|
||||
if (predicate(item))
|
||||
{
|
||||
partitionedSource.MatchingPartition.Add(new Tuple<int, T>(sourceCnt, item));
|
||||
}
|
||||
else
|
||||
{
|
||||
partitionedSource.NonMatchingPartition.Add(new Tuple<int, T>(sourceCnt, item));
|
||||
}
|
||||
}
|
||||
|
||||
var solvedResult = new List<P>(source.Count);
|
||||
if (partitionedSource.MatchingPartition.Any())
|
||||
{
|
||||
solvedResult.AddRange(matchingPartitionSolver(partitionedSource.MatchingPartition.Select(x => x.Item2).ToList()));
|
||||
}
|
||||
|
||||
if (partitionedSource.NonMatchingPartition.Any())
|
||||
{
|
||||
solvedResult.AddRange(nonMatchingPartitionSolver(partitionedSource.NonMatchingPartition.Select(x => x.Item2).ToList()));
|
||||
}
|
||||
|
||||
var result = Enumerable.Repeat(default(P), source.Count).ToList();
|
||||
|
||||
if (solvedResult.Count != source.Count)
|
||||
{
|
||||
return solvedResult; // either we can throw here or just return solvedResult and ignore!
|
||||
}
|
||||
|
||||
for (int resultCnt = 0; resultCnt < source.Count; resultCnt++)
|
||||
{
|
||||
if (resultCnt < partitionedSource.MatchingPartition.Count)
|
||||
{
|
||||
result[partitionedSource.MatchingPartition[resultCnt].Item1] = solvedResult[resultCnt];
|
||||
}
|
||||
else
|
||||
{
|
||||
result[partitionedSource.NonMatchingPartition[resultCnt - partitionedSource.MatchingPartition.Count].Item1] = solvedResult[resultCnt];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class ExpectedExceptionExtensions
|
||||
{
|
||||
private const string c_expectedKey = "isExpected";
|
||||
|
||||
/// <summary>
|
||||
/// Mark the exception as expected when caused by user input in the provided area.
|
||||
/// If the exception thrower is the same area as the caller, the exception will be treated as expected.
|
||||
/// However, in the case of a service to service call, then the exception will be treated as unexpected.
|
||||
/// ex: GitRefsController throws ArgumentException called directly by a user then the exception will be expected
|
||||
/// GitRefsController throws ArgumentException called by BuildDefinitionController then the exception will not be expected.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows for the use case "throw new ArgumentException().Expected(c_area)"
|
||||
/// This will overwrite the expected area if called a second time.
|
||||
/// This should not throw any exceptions as to avoid hiding the exception that was already caught.
|
||||
/// See https://vsowiki.com/index.php?title=Whitelisting_Expected_Commands_and_Exceptions
|
||||
/// </remarks>
|
||||
/// <param name="area">The area name where the exception is expected. This will be compared against IVssRequestContext.ServiceName. Area should be non-empty</param>
|
||||
/// <returns><paramref name="ex"/> after setting the area</returns>
|
||||
public static Exception Expected(this Exception ex, string area)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(area))
|
||||
{
|
||||
ex.Data[c_expectedKey] = area;
|
||||
}
|
||||
|
||||
return ex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to "expect" an exception within the exception filtering syntax.
|
||||
/// ex:
|
||||
/// catch(ArgumentException ex) when (ex.ExpectedExceptionFilter(c_area))
|
||||
/// See <seealso cref="Expected(Exception, string)"/>
|
||||
/// </summary>
|
||||
/// <returns>false always</returns>
|
||||
public static bool ExpectedExceptionFilter(this Exception ex, string area)
|
||||
{
|
||||
ex.Expected(area);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the exception is expected in the specified area.
|
||||
/// Case is ignored for the area comparison.
|
||||
/// </summary>
|
||||
public static bool IsExpected(this Exception ex, string area)
|
||||
{
|
||||
if (string.IsNullOrEmpty(area))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// An exception's Data property is an IDictionary, which returns null for keys that do not exist.
|
||||
return area.Equals(ex.Data[c_expectedKey] as string, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace GitHub.Services.Common.Internal
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class HttpHeaders
|
||||
{
|
||||
public const String ActivityId = "ActivityId";
|
||||
public const String ETag = "ETag";
|
||||
public const String TfsVersion = "X-TFS-Version";
|
||||
public const String TfsRedirect = "X-TFS-Redirect";
|
||||
public const String TfsException = "X-TFS-Exception";
|
||||
public const String TfsServiceError = "X-TFS-ServiceError";
|
||||
public const String TfsSessionHeader = "X-TFS-Session";
|
||||
public const String TfsSoapException = "X-TFS-SoapException";
|
||||
public const String TfsFedAuthRealm = "X-TFS-FedAuthRealm";
|
||||
public const String TfsFedAuthIssuer = "X-TFS-FedAuthIssuer";
|
||||
public const String TfsFedAuthRedirect = "X-TFS-FedAuthRedirect";
|
||||
public const String VssAuthorizationEndpoint = "X-VSS-AuthorizationEndpoint";
|
||||
public const String VssPageHandlers = "X-VSS-PageHandlers";
|
||||
public const String VssE2EID = "X-VSS-E2EID";
|
||||
public const String VssOrchestrationId = "X-VSS-OrchestrationId";
|
||||
public const String AuditCorrelationId = "X-VSS-Audit-CorrelationId";
|
||||
public const String VssOriginUserAgent = "X-VSS-OriginUserAgent";
|
||||
|
||||
// Internal Headers that we use in our client.
|
||||
public const string TfsInstanceHeader = "X-TFS-Instance";
|
||||
public const string TfsVersionOneHeader = "X-VersionControl-Instance";
|
||||
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tfs")]
|
||||
public const string TfsImpersonate = "X-TFS-Impersonate";
|
||||
public const string TfsSubjectDescriptorImpersonate = "X-TFS-SubjectDescriptorImpersonate";
|
||||
|
||||
public const string MsContinuationToken = "X-MS-ContinuationToken";
|
||||
public const String VssUserData = "X-VSS-UserData";
|
||||
public const String VssAgentHeader = "X-VSS-Agent";
|
||||
public const String VssAuthenticateError = "X-VSS-AuthenticateError";
|
||||
public const string VssReauthenticationAction = "X-VSS-ReauthenticationAction";
|
||||
public const string RequestedWith = "X-Requested-With";
|
||||
|
||||
public const String VssRateLimitResource = "X-RateLimit-Resource";
|
||||
public const String VssRateLimitDelay = "X-RateLimit-Delay";
|
||||
public const String VssRateLimitLimit = "X-RateLimit-Limit";
|
||||
public const String VssRateLimitRemaining = "X-RateLimit-Remaining";
|
||||
public const String VssRateLimitReset = "X-RateLimit-Reset";
|
||||
public const String RetryAfter = "Retry-After";
|
||||
|
||||
public const String VssGlobalMessage = "X-VSS-GlobalMessage";
|
||||
|
||||
public const String VssRequestRouted = "X-VSS-RequestRouted";
|
||||
public const String VssUseRequestRouting = "X-VSS-UseRequestRouting";
|
||||
|
||||
public const string VssResourceTenant = "X-VSS-ResourceTenant";
|
||||
public const String VssOverridePrompt = "X-VSS-OverridePrompt";
|
||||
|
||||
public const String VssOAuthS2STargetService = "X-VSS-S2STargetService";
|
||||
public const String VssHostOfflineError = "X-VSS-HostOfflineError";
|
||||
|
||||
public const string VssForceMsaPassThrough = "X-VSS-ForceMsaPassThrough";
|
||||
public const string VssRequestPriority = "X-VSS-RequestPriority";
|
||||
|
||||
// This header represents set of ';' delimited mappings (usually one) that are considered by DetermineAccessMapping API
|
||||
public const string VssClientAccessMapping = "X-VSS-ClientAccessMapping";
|
||||
|
||||
// This header is used to download artifacts anonymously.
|
||||
// N.B. Some resources secured with download tickets (e.g. TFVC files) are still retrieved with the download
|
||||
// ticket in the query string.
|
||||
public const string VssDownloadTicket = "X-VSS-DownloadTicket";
|
||||
|
||||
public const string IfModifiedSince = "If-Modified-Since";
|
||||
public const string Authorization = "Authorization";
|
||||
public const string Location = "Location";
|
||||
public const string ProxyAuthenticate = "Proxy-Authenticate";
|
||||
public const string WwwAuthenticate = "WWW-Authenticate";
|
||||
|
||||
public const string AfdIncomingRouteKey = "X-FD-RouteKey";
|
||||
public const string AfdOutgoingRouteKey = "X-AS-RouteKey";
|
||||
public const string AfdIncomingEndpointList = "X-FD-RouteKeyApplicationEndpointList";
|
||||
public const string AfdOutgoingEndpointList = "X-AS-RouteKeyApplicationEndpointList";
|
||||
public const string AfdResponseRef = "X-MSEdge-Ref";
|
||||
public const string AfdIncomingClientIp = "X-FD-ClientIP";
|
||||
public const string AfdIncomingSocketIp = "X-FD-SocketIP";
|
||||
public const string AfdIncomingRef = "X-FD-Ref";
|
||||
public const string AfdIncomingEventId = "X-FD-EventId";
|
||||
public const string AfdIncomingEdgeEnvironment = "X-FD-EdgeEnvironment";
|
||||
public const string AfdOutgoingQualityOfResponse = "X-AS-QualityOfResponse";
|
||||
public const string AfdOutgoingClientIp = "X-MSEdge-ClientIP";
|
||||
}
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides path normalization/expansion for absolute, relative and UNC-style paths
|
||||
/// and supports paths that contain more than 248 characters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This utility class can be used in place of the .NET Path and Directory classes
|
||||
/// that throw System.IO.PathTooLongException when paths are longer than 248 characters
|
||||
/// </remarks>
|
||||
public static class LongPathUtility
|
||||
{
|
||||
private static Regex AbsolutePathRegEx = new Regex(@"^([a-zA-Z]:\\|\\\\)", RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
private const int ERROR_FILE_NOT_FOUND = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of directory names under the path specified, and optionally all subdirectories
|
||||
/// </summary>
|
||||
/// <param name="path">The directory to search</param>
|
||||
/// <param name="recursiveSearch">Specifies whether the search operation should include only the currect directory or all subdirectories</param>
|
||||
/// <returns>A list of all subdirectories</returns>
|
||||
public static IEnumerable<string> EnumerateDirectories(string path, bool recursiveSearch)
|
||||
{
|
||||
var directoryPaths = new List<string>();
|
||||
EnumerateDirectoriesInternal(directoryPaths, path, recursiveSearch);
|
||||
return directoryPaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of file names under the path specified, and optionally within all subdirectories.
|
||||
/// </summary>
|
||||
/// <param name="path">The directory to search</param>
|
||||
/// <param name="recursiveSearch">Specifies whether the search operation should include only the current directory or all subdirectories</param>
|
||||
/// <returns>
|
||||
/// A list of full file names(including path) contained in the directory specified that match the specified search pattern.</returns>
|
||||
public static IEnumerable<string> EnumerateFiles(string path, bool recursiveSearch)
|
||||
{
|
||||
return EnumerateFiles(path, "*", recursiveSearch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable collection of file names that match a search pattern in a specified path,
|
||||
/// and optionally searches subdirectories.
|
||||
/// </summary>
|
||||
/// <param name="path">The directory to search</param>
|
||||
/// <param name="matchPattern">The search string to match against the names of the files</param>
|
||||
/// <param name="recursiveSearch">Specifies whether the search operation should include only the current directory or all subdirectories</param>
|
||||
/// <returns>
|
||||
/// A list of full file names(including path) contained in the directory specified (and subdirectories optionally) that match the specified pattern.
|
||||
/// </returns>
|
||||
public static IEnumerable<string> EnumerateFiles(string path, string matchPattern, bool recursiveSearch)
|
||||
{
|
||||
if (!DirectoryExists(path))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"The path '{path}' is not a valid directory.");
|
||||
}
|
||||
|
||||
var filePaths = new List<string>();
|
||||
EnumerateFilesInternal(filePaths, path, matchPattern, recursiveSearch);
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true/false whether the file exists. This method inspects the
|
||||
/// file system attributes and supports files without extensions (ex: DIRS, Sources). This method
|
||||
/// supports file paths that are longer than 260 characters.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path to inspect</param>
|
||||
/// <returns>
|
||||
/// True if the file exists or false if not
|
||||
/// </returns>
|
||||
public static bool FileExists(string filePath)
|
||||
{
|
||||
return FileOrDirectoryExists(filePath, isDirectory: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true/false whether the directory exists. This method inspects the
|
||||
/// file system attributes and supports files without extensions (ex: DIRS, Sources). This method
|
||||
/// supports file paths that are longer than 260 characters.
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">The file path to inspect</param>
|
||||
/// <returns>
|
||||
/// True if the directory exists or false if not
|
||||
/// </returns>
|
||||
public static bool DirectoryExists(string directoryPath)
|
||||
{
|
||||
return FileOrDirectoryExists(directoryPath, isDirectory: true);
|
||||
}
|
||||
|
||||
private static bool FileOrDirectoryExists(string filePath, bool isDirectory)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
throw new ArgumentException("A path to the file is required and cannot be null, empty or whitespace", "filePath");
|
||||
}
|
||||
|
||||
bool pathExists = false;
|
||||
|
||||
// File names may or may not include an extension (ex: DIRS, Sources). We have to look at the attributes
|
||||
// on the file system object in order to distinguish a directory from a file
|
||||
var attributes = (FlagsAndAttributes)NativeMethods.GetFileAttributes(filePath);
|
||||
|
||||
if (attributes != FlagsAndAttributes.InvalidFileAttributes)
|
||||
{
|
||||
bool pathIsDirectory = (attributes & FlagsAndAttributes.Directory) == FlagsAndAttributes.Directory;
|
||||
|
||||
if (pathIsDirectory == isDirectory)
|
||||
{
|
||||
pathExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
return pathExists;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the fully expanded/normalized path. This method supports paths that are
|
||||
/// longer than 248 characters.
|
||||
/// </summary>
|
||||
/// <param name="path">The file or directory path</param>
|
||||
/// <returns></returns>
|
||||
public static string GetFullNormalizedPath(string path)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new ArgumentException("A path is required and cannot be null, empty or whitespace", "path");
|
||||
}
|
||||
|
||||
string outPath = path;
|
||||
|
||||
// We need the length of the absolute path in order to prepare a buffer of
|
||||
// the correct size
|
||||
uint bufferSize = NativeMethods.GetFullPathName(path, 0, null, null);
|
||||
int lastWin32Error = Marshal.GetLastWin32Error();
|
||||
|
||||
if (bufferSize > 0)
|
||||
{
|
||||
var absolutePath = new StringBuilder((int)bufferSize);
|
||||
uint length = NativeMethods.GetFullPathName(path, bufferSize, absolutePath, null);
|
||||
lastWin32Error = Marshal.GetLastWin32Error();
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
outPath = absolutePath.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Path resolution failed
|
||||
throw new Win32Exception(
|
||||
lastWin32Error,
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Path normalization/expansion failed. The path length was not returned by the Kernel32 subsystem for '{0}'.",
|
||||
path
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Path resolution failed and the path length could not
|
||||
// be determined
|
||||
throw new Win32Exception(
|
||||
lastWin32Error,
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Path normalization/expansion failed. A full path was not returned by the Kernel32 subsystem for '{0}'.",
|
||||
path
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return outPath != null ? outPath.TrimEnd('\\') : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified path is an absolute path or not.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to be tested.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the path is absolute; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsAbsolutePath(string path)
|
||||
{
|
||||
return LongPathUtility.AbsolutePathRegEx.Match(path).Success;
|
||||
}
|
||||
|
||||
public static string RemoveExtendedLengthPathPrefix(string inPath)
|
||||
{
|
||||
string outPath = inPath;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(inPath))
|
||||
{
|
||||
if (inPath.StartsWith("\\", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// ex: \\?\UNC\server\share to \\server\share
|
||||
outPath = inPath.Replace(@"\\?\UNC", @"\");
|
||||
|
||||
// ex: \\?\c:\windows to c:\windows
|
||||
outPath = outPath.Replace(@"\\?\", String.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
return outPath;
|
||||
}
|
||||
|
||||
private static string CombinePaths(string pathA, string pathB)
|
||||
{
|
||||
if (pathA == null)
|
||||
{
|
||||
throw new ArgumentNullException("pathA");
|
||||
}
|
||||
|
||||
if (pathB == null)
|
||||
{
|
||||
throw new ArgumentNullException("pathB");
|
||||
}
|
||||
|
||||
// The Path class does not suffer from the 248/260 character limitation
|
||||
// that the File and Directory classes do.
|
||||
return Path.Combine(
|
||||
pathA.TrimEnd('\\'),
|
||||
pathB.TrimStart('\\')
|
||||
);
|
||||
}
|
||||
|
||||
private static string ConvertToExtendedLengthPath(string path)
|
||||
{
|
||||
string extendedLengthPath = GetFullNormalizedPath(path);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(extendedLengthPath))
|
||||
{
|
||||
//no need to modify- it's already unicode
|
||||
if (!extendedLengthPath.StartsWith(@"\\?", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// ex: \\server\share
|
||||
if (extendedLengthPath.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// make it \\?\UNC\server\share
|
||||
extendedLengthPath = String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
@"\\?\UNC{0}",
|
||||
extendedLengthPath.Substring(1)
|
||||
);
|
||||
}
|
||||
else //not unicode already, and not UNC
|
||||
{
|
||||
extendedLengthPath = String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
@"\\?\{0}",
|
||||
extendedLengthPath
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extendedLengthPath;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateDirectoriesInPath(string path)
|
||||
{
|
||||
SafeFindHandle handle = null;
|
||||
var findData = new FindData();
|
||||
var childDirectories = new List<string>();
|
||||
|
||||
using (handle = NativeMethods.FindFirstFile(CombinePaths(ConvertToExtendedLengthPath(path), "*"), findData))
|
||||
{
|
||||
if (!handle.IsInvalid)
|
||||
{
|
||||
bool searchComplete = false;
|
||||
|
||||
do
|
||||
{
|
||||
// skip the dot directories
|
||||
if (!findData.fileName.Equals(@".") && !findData.fileName.Equals(@".."))
|
||||
{
|
||||
if ((findData.fileAttributes & (int)FileAttributes.Directory) != 0)
|
||||
{
|
||||
childDirectories.Add(RemoveExtendedLengthPathPrefix(CombinePaths(path, findData.fileName)));
|
||||
}
|
||||
}
|
||||
|
||||
if (NativeMethods.FindNextFile(handle, findData))
|
||||
{
|
||||
if (handle.IsInvalid)
|
||||
{
|
||||
throw new Win32Exception(
|
||||
Marshal.GetLastWin32Error(),
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Enumerating subdirectories for path '{0}' failed.",
|
||||
path
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
searchComplete = true;
|
||||
}
|
||||
|
||||
} while (!searchComplete);
|
||||
}
|
||||
}
|
||||
|
||||
return childDirectories;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateFilesInPath(string path, string matchPattern)
|
||||
{
|
||||
SafeFindHandle handle = null;
|
||||
var findData = new FindData();
|
||||
var fullFilePaths = new List<string>();
|
||||
|
||||
using (handle = NativeMethods.FindFirstFile(CombinePaths(ConvertToExtendedLengthPath(path), matchPattern), findData))
|
||||
{
|
||||
int lastWin32Error = Marshal.GetLastWin32Error();
|
||||
|
||||
if (handle.IsInvalid)
|
||||
{
|
||||
if (lastWin32Error != ERROR_FILE_NOT_FOUND)
|
||||
{
|
||||
throw new Win32Exception(
|
||||
lastWin32Error,
|
||||
String.Format(CultureInfo.InvariantCulture, "Enumerating files for path '{0}' failed.", path)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool searchComplete = false;
|
||||
|
||||
do
|
||||
{
|
||||
// skip the dot directories
|
||||
if (!findData.fileName.Equals(@".") && !findData.fileName.Equals(@".."))
|
||||
{
|
||||
if ((findData.fileAttributes & (int)FileAttributes.Directory) == 0)
|
||||
{
|
||||
fullFilePaths.Add(RemoveExtendedLengthPathPrefix(CombinePaths(path, findData.fileName)));
|
||||
}
|
||||
}
|
||||
|
||||
if (NativeMethods.FindNextFile(handle, findData))
|
||||
{
|
||||
lastWin32Error = Marshal.GetLastWin32Error();
|
||||
|
||||
if (handle.IsInvalid)
|
||||
{
|
||||
throw new Win32Exception(
|
||||
lastWin32Error,
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Enumerating subdirectories for path '{0}' failed.",
|
||||
path
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
searchComplete = true;
|
||||
}
|
||||
|
||||
} while (!searchComplete);
|
||||
}
|
||||
}
|
||||
|
||||
return fullFilePaths;
|
||||
}
|
||||
|
||||
private static void EnumerateFilesInternal(List<string> filePaths, string path, string matchPattern, bool recursiveSearch)
|
||||
{
|
||||
var fullFilePaths = EnumerateFilesInPath(path, matchPattern);
|
||||
if (fullFilePaths.Any())
|
||||
{
|
||||
lock (filePaths)
|
||||
{
|
||||
filePaths.AddRange(fullFilePaths);
|
||||
}
|
||||
}
|
||||
|
||||
if (recursiveSearch)
|
||||
{
|
||||
var directorySearchPaths = EnumerateDirectoriesInPath(path);
|
||||
if (directorySearchPaths.Any())
|
||||
{
|
||||
Parallel.ForEach(
|
||||
directorySearchPaths,
|
||||
(searchPath) =>
|
||||
{
|
||||
EnumerateFilesInternal(filePaths, searchPath, matchPattern, recursiveSearch);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnumerateDirectoriesInternal(List<string> directoryPaths, string path, bool recursiveSearch)
|
||||
{
|
||||
var directorySearchPaths = EnumerateDirectoriesInPath(path);
|
||||
if (directorySearchPaths.Any())
|
||||
{
|
||||
lock (directoryPaths)
|
||||
{
|
||||
directoryPaths.AddRange(directorySearchPaths);
|
||||
}
|
||||
|
||||
if (recursiveSearch)
|
||||
{
|
||||
// This will not ensure that the directory paths are added to the list
|
||||
// in alphabetical order but does provide performance 2 - 4 times better than the
|
||||
// canonical Directory.GetDirectories() method.
|
||||
Parallel.ForEach(
|
||||
directorySearchPaths,
|
||||
(searchPath) =>
|
||||
{
|
||||
EnumerateDirectoriesInternal(directoryPaths, searchPath, recursiveSearch);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kernel32.dll native interop methods for use with utility file/path parsing
|
||||
/// operations
|
||||
/// </summary>
|
||||
private static class NativeMethods
|
||||
{
|
||||
private const string Kernel32Dll = "kernel32.dll";
|
||||
|
||||
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool FindClose(IntPtr hFindFile);
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.alternateFileName")]
|
||||
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.fileName")]
|
||||
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
|
||||
public static extern SafeFindHandle FindFirstFile(
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
string fileName,
|
||||
[In, Out] FindData findFileData
|
||||
);
|
||||
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.alternateFileName")]
|
||||
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.fileName")]
|
||||
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData);
|
||||
|
||||
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
|
||||
public static extern int GetFileAttributes(string lpFileName);
|
||||
|
||||
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
|
||||
public static extern uint GetFullPathName(
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
string lpFileName,
|
||||
uint nBufferLength,
|
||||
[Out]
|
||||
StringBuilder lpBuffer,
|
||||
StringBuilder lpFilePart
|
||||
);
|
||||
}
|
||||
|
||||
//for mapping to the WIN32_FIND_DATA native structure
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed.")]
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed.")]
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private sealed class FindData
|
||||
{
|
||||
// NOTE:
|
||||
// Although it may seem correct to Marshal the string members of this class as UnmanagedType.LPWStr, they
|
||||
// must explicitly remain UnmanagedType.ByValTStr with the size constraints noted. Otherwise we end up with
|
||||
// COM Interop exceptions while trying to marshal the data across the PInvoke boundaries. We thus require the StyleCop
|
||||
// suppressions on the NativeMethods.FindNextFile() method above.
|
||||
public int fileAttributes;
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME creationTime;
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME lastAccessTime;
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime;
|
||||
public int nFileSizeHigh;
|
||||
public int nFileSizeLow;
|
||||
public int dwReserved0;
|
||||
public int dwReserved1;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string fileName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
|
||||
public string alternateFileName;
|
||||
}
|
||||
|
||||
//A Win32 safe find handle in which a return value of -1 indicates it's invalid
|
||||
private sealed class SafeFindHandle : Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid
|
||||
{
|
||||
public SafeFindHandle()
|
||||
: base(true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return NativeMethods.FindClose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum FlagsAndAttributes : uint
|
||||
{
|
||||
None = 0x00000000,
|
||||
Readonly = 0x00000001,
|
||||
Hidden = 0x00000002,
|
||||
System = 0x00000004,
|
||||
Directory = 0x00000010,
|
||||
Archive = 0x00000020,
|
||||
Device = 0x00000040,
|
||||
Normal = 0x00000080,
|
||||
Temporary = 0x00000100,
|
||||
SparseFile = 0x00000200,
|
||||
ReparsePoint = 0x00000400,
|
||||
Compressed = 0x00000800,
|
||||
Offline = 0x00001000,
|
||||
NotContentIndexed = 0x00002000,
|
||||
Encrypted = 0x00004000,
|
||||
Write_Through = 0x80000000,
|
||||
Overlapped = 0x40000000,
|
||||
NoBuffering = 0x20000000,
|
||||
RandomAccess = 0x10000000,
|
||||
SequentialScan = 0x08000000,
|
||||
DeleteOnClose = 0x04000000,
|
||||
BackupSemantics = 0x02000000,
|
||||
PosixSemantics = 0x01000000,
|
||||
OpenReparsePoint = 0x00200000,
|
||||
OpenNoRecall = 0x00100000,
|
||||
FirstPipeInstance = 0x00080000,
|
||||
|
||||
InvalidFileAttributes = 0xFFFFFFFF // Returned by GetFileAttributes on Non existant path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains results from two-way variant of EnuemrableExtensions.Partition()
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the contained lists.</typeparam>
|
||||
public sealed class PartitionResults<T>
|
||||
{
|
||||
public List<T> MatchingPartition { get; } = new List<T>();
|
||||
|
||||
public List<T> NonMatchingPartition { get; } = new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains results from multi-partitioning variant of EnuemrableExtensions.Partition()
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the contained lists.</typeparam>
|
||||
public sealed class MultiPartitionResults<T>
|
||||
{
|
||||
public List<List<T>> MatchingPartitions { get; } = new List<List<T>>();
|
||||
|
||||
public List<T> NonMatchingPartition { get; } = new List<T>();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class PathUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Replacement for Path.Combine.
|
||||
/// For URL please use UrlUtility.CombineUrl
|
||||
/// </summary>
|
||||
/// <param name="path1">The first half of the path.</param>
|
||||
/// <param name="path2">The second half of the path.</param>
|
||||
/// <returns>The concatenated string with and leading slashes or
|
||||
/// tildes removed from the second string.</returns>
|
||||
public static String Combine(String path1, String path2)
|
||||
{
|
||||
if (String.IsNullOrEmpty(path1))
|
||||
{
|
||||
return path2;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(path2))
|
||||
{
|
||||
return path1;
|
||||
}
|
||||
|
||||
Char separator = path1.Contains("/") ? '/' : '\\';
|
||||
|
||||
Char[] trimChars = new Char[] { '\\', '/' };
|
||||
|
||||
return path1.TrimEnd(trimChars) + separator.ToString() + path2.TrimStart(trimChars);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using GitHub.Services.Common.Internal;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class PrimitiveExtensions
|
||||
{
|
||||
public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly long maxSecondsSinceUnixEpoch = (long)DateTime.MaxValue.Subtract(UnixEpoch).TotalSeconds;
|
||||
|
||||
//extension methods to convert to and from a Unix Epoch time to a DateTime
|
||||
public static Int64 ToUnixEpochTime(this DateTime dateTime)
|
||||
{
|
||||
return Convert.ToInt64((dateTime.ToUniversalTime() - UnixEpoch).TotalSeconds);
|
||||
}
|
||||
|
||||
public static DateTime FromUnixEpochTime(this Int64 unixTime)
|
||||
{
|
||||
if (unixTime >= maxSecondsSinceUnixEpoch)
|
||||
{
|
||||
return DateTime.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return UnixEpoch + TimeSpan.FromSeconds(unixTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToBase64StringNoPaddingFromString(string utf8String)
|
||||
{
|
||||
return ToBase64StringNoPadding(Encoding.UTF8.GetBytes(utf8String));
|
||||
}
|
||||
|
||||
public static string FromBase64StringNoPaddingToString(string base64String)
|
||||
{
|
||||
byte[] result = FromBase64StringNoPadding(base64String);
|
||||
|
||||
if (result == null || result.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(result, 0, result.Length);
|
||||
}
|
||||
|
||||
//These methods convert To and From base64 strings without padding
|
||||
//for JWT scenarios
|
||||
//code taken from the JWS spec here:
|
||||
//http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08#appendix-C
|
||||
public static String ToBase64StringNoPadding(this byte[] bytes)
|
||||
{
|
||||
ArgumentUtility.CheckEnumerableForNullOrEmpty(bytes, "bytes");
|
||||
|
||||
string s = Convert.ToBase64String(bytes); // Regular base64 encoder
|
||||
s = s.Split('=')[0]; // Remove any trailing '='s
|
||||
s = s.Replace('+', '-'); // 62nd char of encoding
|
||||
s = s.Replace('/', '_'); // 63rd char of encoding
|
||||
return s;
|
||||
}
|
||||
|
||||
public static byte[] FromBase64StringNoPadding(this String base64String)
|
||||
{
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(base64String, "base64String");
|
||||
|
||||
string s = base64String;
|
||||
s = s.Replace('-', '+'); // 62nd char of encoding
|
||||
s = s.Replace('_', '/'); // 63rd char of encoding
|
||||
switch (s.Length % 4) // Pad with trailing '='s
|
||||
{
|
||||
case 0: break; // No pad chars in this case
|
||||
case 2: s += "=="; break; // Two pad chars
|
||||
case 3: s += "="; break; // One pad char
|
||||
default:
|
||||
throw new ArgumentException(CommonResources.IllegalBase64String(), "base64String");
|
||||
}
|
||||
return Convert.FromBase64String(s); // Standard base64 decoder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts base64 represented value into hex string representation.
|
||||
/// </summary>
|
||||
public static String ConvertToHex(String base64String)
|
||||
{
|
||||
var bytes = FromBase64StringNoPadding(base64String);
|
||||
return BitConverter.ToString(bytes).Replace("-", String.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace GitHub.Services.Common.Internal
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class PropertyValidation
|
||||
{
|
||||
public static void ValidateDictionary(IDictionary<String, Object> source)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(source, "source");
|
||||
|
||||
foreach (var entry in source)
|
||||
{
|
||||
ValidatePropertyName(entry.Key);
|
||||
ValidatePropertyValue(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean IsValidConvertibleType(Type type)
|
||||
{
|
||||
return type != null && (type.GetTypeInfo().IsEnum ||
|
||||
type == typeof(Object) ||
|
||||
type == typeof(Byte[]) ||
|
||||
type == typeof(Guid) ||
|
||||
type == typeof(Boolean) ||
|
||||
type == typeof(Char) ||
|
||||
type == typeof(SByte) ||
|
||||
type == typeof(Byte) ||
|
||||
type == typeof(Int16) ||
|
||||
type == typeof(UInt16) ||
|
||||
type == typeof(Int32) ||
|
||||
type == typeof(UInt32) ||
|
||||
type == typeof(Int64) ||
|
||||
type == typeof(UInt64) ||
|
||||
type == typeof(Single) ||
|
||||
type == typeof(Double) ||
|
||||
type == typeof(Decimal) ||
|
||||
type == typeof(DateTime) ||
|
||||
type == typeof(String)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for deserialization checks. Makes sure that
|
||||
/// the type string presented is in the inclusion list
|
||||
/// of valid types for the property service
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static Boolean IsValidTypeString(String type)
|
||||
{
|
||||
return s_validPropertyTypeStrings.ContainsKey(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for deserialization checks. Looks up the
|
||||
/// type string presented in the inclusion list
|
||||
/// of valid types for the property service and returns the Type object
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="result">Resulting type that maps to the type string</param>
|
||||
/// <returns></returns>
|
||||
public static Boolean TryGetValidType(String type, out Type result)
|
||||
{
|
||||
return s_validPropertyTypeStrings.TryGetValue(type, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the property name conforms to the requirements for a
|
||||
/// property name.
|
||||
/// </summary>
|
||||
/// <param name="propertyName"></param>
|
||||
public static void ValidatePropertyName(String propertyName)
|
||||
{
|
||||
ValidatePropertyString(propertyName, c_maxPropertyNameLengthInChars, "propertyName");
|
||||
|
||||
// Key must not start or end in whitespace. ValidatePropertyString() checks for null and empty strings,
|
||||
// which is why indexing on length without re-checking String.IsNullOrEmpty() is ok.
|
||||
if (Char.IsWhiteSpace(propertyName[0]) || Char.IsWhiteSpace(propertyName[propertyName.Length - 1]))
|
||||
{
|
||||
throw new VssPropertyValidationException(propertyName, CommonResources.InvalidPropertyName(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the property value is within the supported range of values
|
||||
/// for the type of the property specified.
|
||||
/// </summary>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void ValidatePropertyValue(String propertyName, Object value)
|
||||
{
|
||||
// Keep this consistent with XmlPropertyWriter.Write.
|
||||
if (null != value)
|
||||
{
|
||||
Type type = value.GetType();
|
||||
TypeCode typeCode = Type.GetTypeCode(type);
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
ValidateStringValue(propertyName, ((Enum)value).ToString("D"));
|
||||
}
|
||||
else if (typeCode == TypeCode.Object && value is byte[])
|
||||
{
|
||||
ValidateByteArray(propertyName, (byte[])value);
|
||||
}
|
||||
else if (typeCode == TypeCode.Object && value is Guid)
|
||||
{
|
||||
//treat Guid like the other valid primitive types that
|
||||
//don't have explicit columns, e.g. it gets stored as a string
|
||||
ValidateStringValue(propertyName, ((Guid)value).ToString("N"));
|
||||
}
|
||||
else if (typeCode == TypeCode.Object)
|
||||
{
|
||||
throw new PropertyTypeNotSupportedException(propertyName, type);
|
||||
}
|
||||
else if (typeCode == TypeCode.DBNull)
|
||||
{
|
||||
throw new PropertyTypeNotSupportedException(propertyName, type);
|
||||
}
|
||||
else if (typeCode == TypeCode.Empty)
|
||||
{
|
||||
// should be impossible with null check above, but just in case.
|
||||
throw new PropertyTypeNotSupportedException(propertyName, type);
|
||||
}
|
||||
else if (typeCode == TypeCode.Int32)
|
||||
{
|
||||
ValidateInt32(propertyName, (int)value);
|
||||
}
|
||||
else if (typeCode == TypeCode.Double)
|
||||
{
|
||||
ValidateDouble(propertyName, (double)value);
|
||||
}
|
||||
else if (typeCode == TypeCode.DateTime)
|
||||
{
|
||||
ValidateDateTime(propertyName, (DateTime)value);
|
||||
}
|
||||
else if (typeCode == TypeCode.String)
|
||||
{
|
||||
ValidateStringValue(propertyName, (String)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here are the remaining types. All are supported over in DbArtifactPropertyValueColumns.
|
||||
// With a property definition they'll be strongly-typed when they're read back.
|
||||
// Otherwise they read back as strings.
|
||||
// Boolean
|
||||
// Char
|
||||
// SByte
|
||||
// Byte
|
||||
// Int16
|
||||
// UInt16
|
||||
// UInt32
|
||||
// Int64
|
||||
// UInt64
|
||||
// Single
|
||||
// Decimal
|
||||
ValidateStringValue(propertyName, value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateStringValue(String propertyName, String propertyValue)
|
||||
{
|
||||
if (propertyValue.Length > c_maxStringValueLength)
|
||||
{
|
||||
throw new VssPropertyValidationException("value", CommonResources.InvalidPropertyValueSize(propertyName, typeof(String).FullName, c_maxStringValueLength));
|
||||
}
|
||||
ArgumentUtility.CheckStringForInvalidCharacters(propertyValue, "value", true);
|
||||
}
|
||||
|
||||
private static void ValidateByteArray(String propertyName, Byte[] propertyValue)
|
||||
{
|
||||
if (propertyValue.Length > c_maxByteValueSize)
|
||||
{
|
||||
throw new VssPropertyValidationException("value", CommonResources.InvalidPropertyValueSize(propertyName, typeof(Byte[]).FullName, c_maxByteValueSize));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateDateTime(String propertyName, DateTime propertyValue)
|
||||
{
|
||||
// Let users get an out of range error for MinValue and MaxValue, not a DateTimeKind unspecified error.
|
||||
if (propertyValue != DateTime.MinValue
|
||||
&& propertyValue != DateTime.MaxValue)
|
||||
{
|
||||
if (propertyValue.Kind == DateTimeKind.Unspecified)
|
||||
{
|
||||
throw new VssPropertyValidationException("value", CommonResources.DateTimeKindMustBeSpecified());
|
||||
}
|
||||
|
||||
// Make sure the property value is in Universal time.
|
||||
if (propertyValue.Kind != DateTimeKind.Utc)
|
||||
{
|
||||
propertyValue = propertyValue.ToUniversalTime();
|
||||
}
|
||||
}
|
||||
|
||||
CheckRange(propertyValue, s_minAllowedDateTime, s_maxAllowedDateTime, propertyName, "value");
|
||||
}
|
||||
|
||||
private static void ValidateDouble(String propertyName, Double propertyValue)
|
||||
{
|
||||
if (Double.IsInfinity(propertyValue) || Double.IsNaN(propertyValue))
|
||||
{
|
||||
throw new VssPropertyValidationException("value", CommonResources.DoubleValueOutOfRange(propertyName, propertyValue));
|
||||
}
|
||||
|
||||
// SQL Server support: - 1.79E+308 to -2.23E-308, 0 and 2.23E-308 to 1.79E+308
|
||||
if (propertyValue < s_minNegative ||
|
||||
(propertyValue < 0 && propertyValue > s_maxNegative) ||
|
||||
propertyValue > s_maxPositive ||
|
||||
(propertyValue > 0 && propertyValue < s_minPositive))
|
||||
{
|
||||
throw new VssPropertyValidationException("value", CommonResources.DoubleValueOutOfRange(propertyName, propertyValue));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateInt32(String propertyName, Int32 propertyValue)
|
||||
{
|
||||
// All values allowed.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation helper for validating all property strings.
|
||||
/// </summary>
|
||||
/// <param name="propertyString"></param>
|
||||
/// <param name="maxSize"></param>
|
||||
/// <param name="argumentName"></param>
|
||||
private static void ValidatePropertyString(String propertyString, Int32 maxSize, String argumentName)
|
||||
{
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(propertyString, argumentName);
|
||||
if (propertyString.Length > maxSize)
|
||||
{
|
||||
throw new VssPropertyValidationException(argumentName, CommonResources.PropertyArgumentExceededMaximumSizeAllowed(argumentName, maxSize));
|
||||
}
|
||||
ArgumentUtility.CheckStringForInvalidCharacters(propertyString, argumentName, true);
|
||||
}
|
||||
|
||||
public static void CheckPropertyLength(String propertyValue, Boolean allowNull, Int32 minLength, Int32 maxLength, String propertyName, Type containerType, String topLevelParamName)
|
||||
{
|
||||
Boolean valueIsInvalid = false;
|
||||
|
||||
if (propertyValue == null)
|
||||
{
|
||||
if (!allowNull)
|
||||
{
|
||||
valueIsInvalid = true;
|
||||
}
|
||||
}
|
||||
else if ((propertyValue.Length < minLength) || (propertyValue.Length > maxLength))
|
||||
{
|
||||
valueIsInvalid = true;
|
||||
}
|
||||
|
||||
// throw exception if the value is invalid.
|
||||
if (valueIsInvalid)
|
||||
{
|
||||
// If the propertyValue is null, just print it like an empty string.
|
||||
if (propertyValue == null)
|
||||
{
|
||||
propertyValue = String.Empty;
|
||||
}
|
||||
|
||||
if (allowNull)
|
||||
{
|
||||
// paramName comes second for ArgumentException.
|
||||
throw new ArgumentException(CommonResources.InvalidStringPropertyValueNullAllowed(propertyValue, propertyName, containerType.Name, minLength, maxLength), topLevelParamName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(CommonResources.InvalidStringPropertyValueNullForbidden(propertyValue, propertyName, containerType.Name, minLength, maxLength), topLevelParamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that a propery is within the bounds of the specified range.
|
||||
/// </summary>
|
||||
/// <param name="propertyValue">The property value</param>
|
||||
/// <param name="minValue">The minimum value allowed</param>
|
||||
/// <param name="maxValue">The maximum value allowed</param>
|
||||
/// <param name="propertyName">The name of the property</param>
|
||||
/// <param name="containerType">The container of the property</param>
|
||||
/// <param name="topLevelParamName">The top level parameter name</param>
|
||||
public static void CheckRange<T>(T propertyValue, T minValue, T maxValue, String propertyName, Type containerType, String topLevelParamName)
|
||||
where T : IComparable<T>
|
||||
{
|
||||
if (propertyValue.CompareTo(minValue) < 0 || propertyValue.CompareTo(maxValue) > 0)
|
||||
{
|
||||
// paramName comes first for ArgumentOutOfRangeException.
|
||||
throw new ArgumentOutOfRangeException(topLevelParamName, CommonResources.ValueTypeOutOfRange(propertyValue, propertyName, containerType.Name, minValue, maxValue));
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckRange<T>(T propertyValue, T minValue, T maxValue, String propertyName, String topLevelParamName)
|
||||
where T : IComparable<T>
|
||||
{
|
||||
if (propertyValue.CompareTo(minValue) < 0 || propertyValue.CompareTo(maxValue) > 0)
|
||||
{
|
||||
// paramName comes first for ArgumentOutOfRangeException.
|
||||
throw new ArgumentOutOfRangeException(topLevelParamName, CommonResources.VssPropertyValueOutOfRange(propertyName, propertyValue, minValue, maxValue));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the property filter conforms to the requirements for a
|
||||
/// property filter.
|
||||
/// </summary>
|
||||
/// <param name="propertyNameFilter"></param>
|
||||
public static void ValidatePropertyFilter(String propertyNameFilter)
|
||||
{
|
||||
PropertyValidation.ValidatePropertyString(propertyNameFilter, c_maxPropertyNameLengthInChars, "propertyNameFilter");
|
||||
}
|
||||
|
||||
// Limits on the sizes of property values
|
||||
private const Int32 c_maxPropertyNameLengthInChars = 400;
|
||||
private const Int32 c_maxByteValueSize = 8 * 1024 * 1024;
|
||||
private const Int32 c_maxStringValueLength = 4 * 1024 * 1024;
|
||||
|
||||
// Minium date time allowed for a property value.
|
||||
private static readonly DateTime s_minAllowedDateTime = new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
// Maximum date time allowed for a property value.
|
||||
// We can't preserve DateTime.MaxValue faithfully because SQL's cut-off is 3 milliseconds lower. Also to handle UTC to Local shifts, we give ourselves a buffer of one day.
|
||||
private static readonly DateTime s_maxAllowedDateTime = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc).AddDays(-1);
|
||||
|
||||
private static Double s_minNegative = Double.Parse("-1.79E+308", CultureInfo.InvariantCulture);
|
||||
private static Double s_maxNegative = Double.Parse("-2.23E-308", CultureInfo.InvariantCulture);
|
||||
private static Double s_minPositive = Double.Parse("2.23E-308", CultureInfo.InvariantCulture);
|
||||
private static Double s_maxPositive = Double.Parse("1.79E+308", CultureInfo.InvariantCulture);
|
||||
|
||||
private static readonly Dictionary<String, Type> s_validPropertyTypeStrings = new Dictionary<String, Type>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
//primitive types:
|
||||
//(NO DBNull or Empty)
|
||||
{ "System.Boolean", typeof(Boolean) },
|
||||
{ "System.Byte", typeof(Byte) },
|
||||
{ "System.Char", typeof(Char) },
|
||||
{ "System.DateTime", typeof(DateTime) },
|
||||
{ "System.Decimal", typeof(Decimal) },
|
||||
{ "System.Double", typeof(Double) },
|
||||
{ "System.Int16", typeof(Int16) },
|
||||
{ "System.Int32", typeof(Int32) },
|
||||
{ "System.Int64", typeof(Int64) },
|
||||
{ "System.SByte", typeof(SByte) },
|
||||
{ "System.Single", typeof(Single) },
|
||||
{ "System.String", typeof(String) },
|
||||
{ "System.UInt16", typeof(UInt16) },
|
||||
{ "System.UInt32", typeof(UInt32) },
|
||||
{ "System.UInt64", typeof(UInt64) },
|
||||
|
||||
//other valid types
|
||||
{ "System.Byte[]", typeof(Byte[]) },
|
||||
{ "System.Guid", typeof(Guid) }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility for masking common secret patterns
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class SecretUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// The string to use to replace secrets when throwing exceptions, logging
|
||||
/// or otherwise risking exposure
|
||||
/// </summary>
|
||||
internal const string PasswordMask = "******";
|
||||
|
||||
/// <summary>
|
||||
/// The string used to mask newer secrets
|
||||
/// </summary>
|
||||
internal const string SecretMask = "<secret removed>";
|
||||
|
||||
//We use a different mask per token, to help track down suspicious mask sequences in error
|
||||
// strings that shouldn't obviously be masked
|
||||
// Internal for testing, please don't reuse
|
||||
internal const string PasswordRemovedMask = "**password-removed**";
|
||||
internal const string PwdRemovedMask = "**pwd-removed**";
|
||||
internal const string PasswordSpaceRemovedMask = "**password-space-removed**";
|
||||
internal const string PwdSpaceRemovedMask = "**pwd-space-removed**";
|
||||
internal const string AccountKeyRemovedMask = "**account-key-removed**";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether this string contains an unmasked secret
|
||||
/// </summary>
|
||||
/// <param name="message">The message to check</param>
|
||||
/// <returns>True if a secret this class supports was found</returns>
|
||||
/// <remarks>This implementation is as least as expensive as a ScrubSecrets call</remarks>
|
||||
public static bool ContainsUnmaskedSecret(string message)
|
||||
{
|
||||
return !String.Equals(message, ScrubSecrets(message, false), StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether this string contains an unmasked secret
|
||||
/// </summary>
|
||||
/// <param name="message">The message to check</param>
|
||||
/// <param name="onlyJwtsFound">True if a secret was found and only jwts were found</param>
|
||||
/// <returns>True if a message this class supports was found</returns>
|
||||
/// <remarks>This method is a temporary workaround and should be removed in M136
|
||||
/// This implementation is as least as expensive as a ScrubSecrets call</remarks>
|
||||
public static bool ContainsUnmaskedSecret(string message, out bool onlyJwtsFound)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
onlyJwtsFound = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
string scrubbedMessage = ScrubJwts(message, assertOnDetection: false);
|
||||
bool jwtsFound = !String.Equals(message, scrubbedMessage, StringComparison.Ordinal);
|
||||
scrubbedMessage = ScrubTraditionalSecrets(message, assertOnDetection: false);
|
||||
bool secretsFound = !String.Equals(message, scrubbedMessage, StringComparison.Ordinal);
|
||||
onlyJwtsFound = !secretsFound && jwtsFound;
|
||||
return secretsFound || jwtsFound;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrub a message for any secrets(passwords, tokens) in known formats
|
||||
/// This method is called to scrub exception messages and traces to prevent any secrets
|
||||
/// from being leaked.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to verify for secret data.</param>
|
||||
/// <param name="assertOnDetection">When true, if a message contains a
|
||||
/// secret in a known format the method will debug assert. Default = true.</param>
|
||||
/// <returns>The message with any detected secrets masked</returns>
|
||||
/// <remarks>This only does best effort pattern matching for a set of known patterns</remarks>
|
||||
public static string ScrubSecrets(string message, bool assertOnDetection = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
message = ScrubTraditionalSecrets(message, assertOnDetection);
|
||||
message = ScrubJwts(message, assertOnDetection);
|
||||
return message;
|
||||
}
|
||||
|
||||
private static string ScrubTraditionalSecrets(string message, bool assertOnDetection)
|
||||
{
|
||||
message = ScrubSecret(message, c_passwordToken, PasswordRemovedMask, assertOnDetection);
|
||||
message = ScrubSecret(message, c_pwdToken, PwdRemovedMask, assertOnDetection);
|
||||
message = ScrubSecret(message, c_passwordTokenSpaced, PasswordSpaceRemovedMask, assertOnDetection);
|
||||
message = ScrubSecret(message, c_pwdTokenSpaced, PwdSpaceRemovedMask, assertOnDetection);
|
||||
message = ScrubSecret(message, c_accountKeyToken, AccountKeyRemovedMask, assertOnDetection);
|
||||
|
||||
message = ScrubSecret(message, c_authBearerToken, SecretMask, assertOnDetection);
|
||||
return message;
|
||||
}
|
||||
|
||||
private static string ScrubJwts(string message, bool assertOnDetection)
|
||||
{
|
||||
//JWTs are sensitive and we need to scrub them, so this is a best effort attempt to
|
||||
// scrub them based on typical patterns we see
|
||||
message = ScrubSecret(message, c_jwtTypToken, SecretMask, assertOnDetection,
|
||||
maskToken: true);
|
||||
message = ScrubSecret(message, c_jwtAlgToken, SecretMask, assertOnDetection,
|
||||
maskToken: true);
|
||||
message = ScrubSecret(message, c_jwtX5tToken, SecretMask, assertOnDetection,
|
||||
maskToken: true);
|
||||
message = ScrubSecret(message, c_jwtKidToken, SecretMask, assertOnDetection,
|
||||
maskToken: true);
|
||||
return message;
|
||||
}
|
||||
|
||||
private static string ScrubSecret(string message, string token, string mask, bool assertOnDetection, bool maskToken=false)
|
||||
{
|
||||
int startIndex = -1;
|
||||
|
||||
do
|
||||
{
|
||||
startIndex = message.IndexOf(token, (startIndex < 0) ? 0 : startIndex, StringComparison.OrdinalIgnoreCase);
|
||||
if (startIndex < 0)
|
||||
{
|
||||
// Common case, there is not a password.
|
||||
break;
|
||||
}
|
||||
|
||||
//Explicitly check for original password mask so code that uses the orignal doesn't assert
|
||||
if (!maskToken && (
|
||||
message.IndexOf(token + mask, StringComparison.OrdinalIgnoreCase) == startIndex
|
||||
|| (message.IndexOf(token + PasswordMask, StringComparison.OrdinalIgnoreCase) == startIndex)))
|
||||
{
|
||||
// The password is already masked, move past this string.
|
||||
startIndex += token.Length + mask.Length;
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point we detected a password that is not masked, remove it!
|
||||
try
|
||||
{
|
||||
if (!maskToken)
|
||||
{
|
||||
startIndex += token.Length;
|
||||
}
|
||||
// Find the end of the password.
|
||||
int endIndex = message.Length - 1;
|
||||
|
||||
if (message[startIndex] == '"' || message[startIndex] == '\'')
|
||||
{
|
||||
// The password is wrapped in quotes. The end of the string will be the next unpaired quote.
|
||||
// Unless the message itself wrapped the connection string in quotes, in which case we may mask out the rest of the message. Better to be safe than leak the connection string.
|
||||
// Intentionally going to "i < message.Length - 1". If the quote isn't the second to last character, it is the last character, and we delete to the end of the string anyway.
|
||||
for (int i = startIndex + 1; i < message.Length - 1; i++)
|
||||
{
|
||||
if (message[startIndex] == message[i])
|
||||
{
|
||||
if (message[startIndex] == message[i + 1])
|
||||
{
|
||||
// we found a pair of quotes. Skip over the pair and continue.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is a single quote, and the end of the password.
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The password is not wrapped in quotes.
|
||||
// The end is any whitespace, semi-colon, single, or double quote character.
|
||||
for (int i = startIndex + 1; i < message.Length; i++)
|
||||
{
|
||||
if (Char.IsWhiteSpace(message[i]) || ((IList<Char>)s_validPasswordEnding).Contains(message[i]))
|
||||
{
|
||||
endIndex = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message = message.Substring(0, startIndex) + mask + message.Substring(endIndex + 1);
|
||||
|
||||
// Bug 94478: We need to scrub the message before Assert, otherwise we will fall into
|
||||
// a recursive assert where the TeamFoundationServerException contains same message
|
||||
if (assertOnDetection)
|
||||
{
|
||||
Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, "Message contains an unmasked secret. Message: {0}", message));
|
||||
}
|
||||
|
||||
// Trace raw that we have scrubbed a message.
|
||||
//FUTURE: We need a work item to add Tracing to the VSS Client assembly.
|
||||
//TraceLevel traceLevel = assertOnDetection ? TraceLevel.Error : TraceLevel.Info;
|
||||
//TeamFoundationTracingService.TraceRaw(99230, traceLevel, s_area, s_layer, "An unmasked password was detected in a message. MESSAGE: {0}. STACK TRACE: {1}", message, Environment.StackTrace);
|
||||
}
|
||||
catch (Exception /*exception*/)
|
||||
{
|
||||
// With an exception here the message may still contain an unmasked password.
|
||||
// We also do not want to interupt the current thread with this exception, because it may be constucting a message
|
||||
// for a different exception. Trace this exception and continue on using a generic exception message.
|
||||
//TeamFoundationTracingService.TraceExceptionRaw(99231, s_area, s_layer, exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Iterate to the next password (if it exists)
|
||||
startIndex += mask.Length;
|
||||
}
|
||||
} while (startIndex < message.Length);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private const string c_passwordToken = "Password=";
|
||||
private const string c_passwordTokenSpaced = "-Password ";
|
||||
private const string c_pwdToken = "Pwd=";
|
||||
private const string c_pwdTokenSpaced = "-Pwd ";
|
||||
private const string c_accountKeyToken = "AccountKey=";
|
||||
private const string c_authBearerToken = "Bearer ";
|
||||
/// <remarks>
|
||||
/// {"typ":" // eyJ0eXAiOi
|
||||
/// </remarks>
|
||||
private const string c_jwtTypToken = "eyJ0eXAiOi";
|
||||
/// <remarks>
|
||||
/// {"alg":" // eyJhbGciOi
|
||||
/// </remarks>
|
||||
private const string c_jwtAlgToken = "eyJhbGciOi";
|
||||
/// <remarks>
|
||||
/// {"x5t":" // eyJ4NXQiOi
|
||||
/// </remarks>
|
||||
private const string c_jwtX5tToken = "eyJ4NXQiOi";
|
||||
/// <remarks>
|
||||
/// {"kid":" // eyJraWQiOi
|
||||
/// </remarks>
|
||||
private const string c_jwtKidToken = "eyJraWQiOi";
|
||||
|
||||
|
||||
|
||||
private static readonly char[] s_validPasswordEnding = new char[] { ';', '\'', '"' };
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class SecureCompare
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two byte arrays for byte-by-byte equality.
|
||||
/// If both arrays are the same length, the running time of this routine will not vary with the number of equal bytes between the two.
|
||||
/// </summary>
|
||||
/// <param name="lhs">A byte array (non-null)</param>
|
||||
/// <param name="rhs">A byte array (non-null)</param>
|
||||
/// <remarks>
|
||||
/// Checking secret values using built-in equality operators is insecure.
|
||||
/// Operations like `==` on strings will stop the comparison when the first unmatched character is encountered.
|
||||
/// When checking secret values from an untrusted source that we use for authentication, we must be careful
|
||||
/// not to stop the comparison early for incorrect values.
|
||||
/// If we do, an attacker can send a large volume of requests and use statistical methods to infer the secret value byte-by-byte.
|
||||
///
|
||||
/// This method is intended to be used with arrays of the same length -- for example, two hashes from the same SHA algorithm.
|
||||
/// Comparing strings of unequal length can leak length information to an attacker.
|
||||
/// </remarks>
|
||||
public static bool TimeInvariantEquals(byte[] lhs, byte[] rhs)
|
||||
{
|
||||
if (lhs.Length != rhs.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must use bitwise operations
|
||||
// Conditional branching or short-circuiting Boolean operators would change the running time depending on the result
|
||||
int result = 0;
|
||||
for (int i = 0; i < lhs.Length; i++)
|
||||
{
|
||||
result |= lhs[i] ^ rhs[i];
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
// Hide the `Equals` method inherited from `object`
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public new static bool Equals(object lhs, object rhs)
|
||||
{
|
||||
throw new NotImplementedException($"This is not the secure equals method! Use `{nameof(SecureCompare.TimeInvariantEquals)}` instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple helper class used to break up a stream into smaller streams
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class StreamParser
|
||||
{
|
||||
public StreamParser(Stream fileStream, int chunkSize)
|
||||
{
|
||||
m_stream = fileStream;
|
||||
m_chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns total length of file.
|
||||
/// </summary>
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_stream.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the next substream
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public SubStream GetNextStream()
|
||||
{
|
||||
return new SubStream(m_stream, m_chunkSize);
|
||||
}
|
||||
|
||||
Stream m_stream;
|
||||
int m_chunkSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Streams a subsection of a larger stream
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class SubStream : Stream
|
||||
{
|
||||
public SubStream(Stream stream, int maxStreamSize)
|
||||
{
|
||||
m_startingPosition = stream.Position;
|
||||
long remainingStream = stream.Length - m_startingPosition;
|
||||
m_length = Math.Min(maxStreamSize, remainingStream);
|
||||
m_stream = stream;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_stream.CanRead && m_stream.Position <= this.EndingPostionOnOuterStream;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return m_stream.CanSeek; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_stream.Position - m_startingPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value >= m_length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
m_stream.Position = m_startingPosition + value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Postion in larger stream where this substream starts
|
||||
/// </summary>
|
||||
public long StartingPostionOnOuterStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_startingPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Postion in larger stream where this substream ends
|
||||
/// </summary>
|
||||
public long EndingPostionOnOuterStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_startingPosition + m_length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
// check that the read is only in our substream
|
||||
count = EnsureLessThanOrEqualToRemainingBytes(count);
|
||||
|
||||
return m_stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// check that the read is only in our substream
|
||||
count = EnsureLessThanOrEqualToRemainingBytes(count);
|
||||
|
||||
return m_stream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
if (origin == SeekOrigin.Begin && 0 <= offset && offset < m_length)
|
||||
{
|
||||
return m_stream.Seek(offset + m_startingPosition, origin);
|
||||
}
|
||||
else if (origin == SeekOrigin.End && 0 >= offset && offset > -m_length)
|
||||
{
|
||||
return m_stream.Seek(offset - ((m_stream.Length-1) - this.EndingPostionOnOuterStream), origin);
|
||||
}
|
||||
else if (origin == SeekOrigin.Current && (offset + m_stream.Position) >= this.StartingPostionOnOuterStream && (offset + m_stream.Position) < this.EndingPostionOnOuterStream)
|
||||
{
|
||||
return m_stream.Seek(offset, origin);
|
||||
}
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private int EnsureLessThanOrEqualToRemainingBytes(int numBytes)
|
||||
{
|
||||
long remainingBytesInStream = m_length - this.Position;
|
||||
if (numBytes > remainingBytesInStream)
|
||||
{
|
||||
numBytes = Convert.ToInt32(remainingBytesInStream);
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
private long m_length;
|
||||
private long m_startingPosition;
|
||||
private Stream m_stream;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class TypeExtensionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Determins if a value is assignable to the requested type. It goes
|
||||
/// the extra step beyond IsAssignableFrom in that it also checks for
|
||||
/// IConvertible and attempts to convert the value.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsAssignableOrConvertibleFrom(this Type type, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!type.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo()))
|
||||
{
|
||||
if (value is IConvertible)
|
||||
{
|
||||
// Try and convert to the requested type, if successful
|
||||
// assign value to the result so we don't have to do again.
|
||||
try
|
||||
{
|
||||
ConvertUtility.ChangeType(value, type, CultureInfo.CurrentCulture);
|
||||
return true;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the type is of the type t.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <param name="t">The type to compare to.</param>
|
||||
/// <returns>True if of the same type, otherwise false.</returns>
|
||||
public static bool IsOfType(this Type type, Type t)
|
||||
{
|
||||
if (t.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (type.GetTypeInfo().IsGenericType &&
|
||||
type.GetGenericTypeDefinition() == t)
|
||||
{
|
||||
//generic type
|
||||
return true;
|
||||
}
|
||||
else if (type.GetTypeInfo().ImplementedInterfaces.Any(
|
||||
i => i.GetTypeInfo().IsGenericType &&
|
||||
i.GetGenericTypeDefinition() == t))
|
||||
{
|
||||
//implements generic type
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the type is a Dictionary.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <returns>True if a dictionary, otherwise false.</returns>
|
||||
public static bool IsDictionary(this Type type)
|
||||
{
|
||||
if (typeof(IDictionary).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
|
||||
{
|
||||
//non-generic dictionary
|
||||
return true;
|
||||
}
|
||||
else if (type.GetTypeInfo().IsGenericType &&
|
||||
type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
|
||||
{
|
||||
//generic dictionary interface
|
||||
return true;
|
||||
}
|
||||
else if (type.GetTypeInfo().ImplementedInterfaces.Any(
|
||||
i => i.GetTypeInfo().IsGenericType &&
|
||||
i.GetGenericTypeDefinition() == typeof(IDictionary<,>)))
|
||||
{
|
||||
//implements generic dictionary
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the type is a List.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <returns>True if a list, otherwise false.</returns>
|
||||
public static bool IsList(this Type type)
|
||||
{
|
||||
if (typeof(IList).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
|
||||
{
|
||||
//non-generic list
|
||||
return true;
|
||||
}
|
||||
else if (type.GetTypeInfo().IsGenericType &&
|
||||
type.GetGenericTypeDefinition() == typeof(IList<>))
|
||||
{
|
||||
//generic list interface
|
||||
return true;
|
||||
}
|
||||
else if (type.GetTypeInfo().ImplementedInterfaces.Any(
|
||||
i => i.GetTypeInfo().IsGenericType &&
|
||||
i.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
{
|
||||
//implements generic list
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's the type of the field/property specified.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get the field/property from.</param>
|
||||
/// <param name="name">The name of the field/property.</param>
|
||||
/// <returns>The type of the field/property or null if no match found.</returns>
|
||||
public static Type GetMemberType(this Type type, string name)
|
||||
{
|
||||
TypeInfo typeInfo = type.GetTypeInfo();
|
||||
PropertyInfo propertyInfo = GetPublicInstancePropertyInfo(type, name);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
return propertyInfo.PropertyType;
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldInfo fieldInfo = GetPublicInstanceFieldInfo(type, name);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
return fieldInfo.FieldType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's the value of the field/property specified.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get the field/property from.</param>
|
||||
/// <param name="name">The name of the field/property.</param>
|
||||
/// <param name="obj">The object to get the value from.</param>
|
||||
/// <returns>The value of the field/property or null if no match found.</returns>
|
||||
public static object GetMemberValue(this Type type, string name, object obj)
|
||||
{
|
||||
PropertyInfo propertyInfo = GetPublicInstancePropertyInfo(type, name);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
return propertyInfo.GetValue(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldInfo fieldInfo = GetPublicInstanceFieldInfo(type, name);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
return fieldInfo.GetValue(obj);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set's the value of the field/property specified.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get the field/property from.</param>
|
||||
/// <param name="name">The name of the field/property.</param>
|
||||
/// <param name="obj">The object to set the value to.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
public static void SetMemberValue(this Type type, string name, object obj, object value)
|
||||
{
|
||||
PropertyInfo propertyInfo = GetPublicInstancePropertyInfo(type, name);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
if (!propertyInfo.SetMethod.IsPublic)
|
||||
{
|
||||
// this is here to match original behaviour before we switched to PCL version of code.
|
||||
throw new ArgumentException("Property set method not public.");
|
||||
}
|
||||
propertyInfo.SetValue(obj, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldInfo fieldInfo = GetPublicInstanceFieldInfo(type, name);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
fieldInfo.SetValue(obj, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD
|
||||
/// <summary>
|
||||
/// Portable compliant way to get a constructor with specified arguments. This will return a constructor that is public or private as long as the arguments match. NULL will be returned if there is no match.
|
||||
/// Note that it will pick the first one it finds that matches, which is not necesarily the best match.
|
||||
/// </summary>
|
||||
/// <param name="type">The Type that has the constructor</param>
|
||||
/// <param name="parameterTypes">The type of the arguments for the constructor.</param>
|
||||
/// <returns></returns>
|
||||
public static ConstructorInfo GetFirstMatchingConstructor(this Type type, params Type[] parameterTypes)
|
||||
{
|
||||
return type.GetTypeInfo().DeclaredConstructors.GetFirstMatchingConstructor(parameterTypes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Portable compliant way to get a constructor with specified arguments from a prefiltered list. This will return a constructor that is public or private as long as the arguments match. NULL will be returned if there is no match.
|
||||
/// Note that it will pick the first one it finds that matches, which is not necesarily the best match.
|
||||
/// </summary>
|
||||
/// <param name="constructors">Prefiltered list of constructors</param>
|
||||
/// <param name="parameterTypes">The type of the arguments for the constructor.</param>
|
||||
/// <returns></returns>
|
||||
public static ConstructorInfo GetFirstMatchingConstructor(this IEnumerable<ConstructorInfo> constructors, params Type[] parameterTypes)
|
||||
{
|
||||
foreach (ConstructorInfo constructorInfo in constructors)
|
||||
{
|
||||
ParameterInfo[] parameters = constructorInfo.GetParameters();
|
||||
if (parameters.Length == parameterTypes.Length)
|
||||
{
|
||||
int i;
|
||||
bool matches = true;
|
||||
for (i = 0; i < parameterTypes.Length; i++)
|
||||
{
|
||||
if (parameters[i].ParameterType != parameterTypes[i] && !parameters[i].ParameterType.GetTypeInfo().IsAssignableFrom(parameterTypes[i].GetTypeInfo()))
|
||||
{
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matches)
|
||||
{
|
||||
return constructorInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
|
||||
private static PropertyInfo GetPublicInstancePropertyInfo(Type type, string name)
|
||||
{
|
||||
Type typeToCheck = type;
|
||||
PropertyInfo propertyInfo = null;
|
||||
while (propertyInfo == null && typeToCheck != null)
|
||||
{
|
||||
TypeInfo typeInfo = typeToCheck.GetTypeInfo();
|
||||
propertyInfo = typeInfo.DeclaredProperties.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && p.GetMethod.Attributes.HasFlag(MethodAttributes.Public) && !p.GetMethod.Attributes.HasFlag(MethodAttributes.Static));
|
||||
typeToCheck = typeInfo.BaseType;
|
||||
}
|
||||
return propertyInfo;
|
||||
}
|
||||
|
||||
private static FieldInfo GetPublicInstanceFieldInfo(Type type, string name)
|
||||
{
|
||||
Type typeToCheck = type;
|
||||
FieldInfo fieldInfo = null;
|
||||
while (fieldInfo == null && typeToCheck != null)
|
||||
{
|
||||
TypeInfo typeInfo = typeToCheck.GetTypeInfo();
|
||||
fieldInfo = typeInfo.DeclaredFields.FirstOrDefault(f => f.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && f.IsPublic && !f.IsStatic);
|
||||
typeToCheck = typeInfo.BaseType;
|
||||
}
|
||||
return fieldInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
public static class UriExtensions
|
||||
{
|
||||
public static Uri AppendQuery(this Uri uri, String name, String value)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(uri, "uri");
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(name, "name");
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(value, "value");
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder(uri.Query.TrimStart('?'));
|
||||
|
||||
AppendSingleQueryValue(stringBuilder, name, value);
|
||||
|
||||
UriBuilder uriBuilder = new UriBuilder(uri);
|
||||
|
||||
uriBuilder.Query = stringBuilder.ToString();
|
||||
|
||||
return uriBuilder.Uri;
|
||||
}
|
||||
|
||||
public static Uri AppendQuery(this Uri uri, IEnumerable<KeyValuePair<String, String>> queryValues)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(uri, "uri");
|
||||
ArgumentUtility.CheckForNull(queryValues, "queryValues");
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder(uri.Query.TrimStart('?'));
|
||||
|
||||
foreach (KeyValuePair<String, String> kvp in queryValues)
|
||||
{
|
||||
AppendSingleQueryValue(stringBuilder, kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
UriBuilder uriBuilder = new UriBuilder(uri);
|
||||
uriBuilder.Query = stringBuilder.ToString();
|
||||
return uriBuilder.Uri;
|
||||
}
|
||||
|
||||
public static Uri AppendQuery(this Uri uri, NameValueCollection queryValues)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(uri, "uri");
|
||||
ArgumentUtility.CheckForNull(queryValues, "queryValues");
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder(uri.Query.TrimStart('?'));
|
||||
|
||||
foreach (String name in queryValues)
|
||||
{
|
||||
AppendSingleQueryValue(stringBuilder, name, queryValues[name]);
|
||||
}
|
||||
|
||||
UriBuilder uriBuilder = new UriBuilder(uri);
|
||||
|
||||
uriBuilder.Query = stringBuilder.ToString();
|
||||
|
||||
return uriBuilder.Uri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an Add similar to the NameValuCollection 'Add' method where the value gets added as an item in a comma delimited list if the key is already present.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="collection"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="convert"></param>
|
||||
public static void Add<T>(this IList<KeyValuePair<String, String>> collection, String key, T value, Func<T, String> convert = null)
|
||||
{
|
||||
collection.AddMultiple<T>(key, new List<T> { value }, convert);
|
||||
}
|
||||
|
||||
public static void AddMultiple<T>(this IList<KeyValuePair<String, String>> collection, String key, IEnumerable<T> values, Func<T, String> convert)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(collection, "collection");
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(key, "name");
|
||||
|
||||
if (convert == null) convert = (val) => val.ToString();
|
||||
|
||||
if (values != null && values.Any())
|
||||
{
|
||||
StringBuilder newValue = new StringBuilder();
|
||||
KeyValuePair<String, String> matchingKvp = collection.FirstOrDefault(kvp => kvp.Key.Equals(key));
|
||||
if (matchingKvp.Key == key)
|
||||
{
|
||||
collection.Remove(matchingKvp);
|
||||
newValue.Append(matchingKvp.Value);
|
||||
}
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (newValue.Length > 0)
|
||||
{
|
||||
newValue.Append(",");
|
||||
}
|
||||
newValue.Append(convert(value));
|
||||
}
|
||||
|
||||
collection.Add(new KeyValuePair<String, String>(key, newValue.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(this IList<KeyValuePair<String, String>> collection, String key, String value)
|
||||
{
|
||||
collection.AddMultiple(key, new[] { value });
|
||||
}
|
||||
|
||||
public static void AddMultiple(this IList<KeyValuePair<String, String>> collection, String key, IEnumerable<String> values)
|
||||
{
|
||||
collection.AddMultiple(key, values, (val) => val);
|
||||
}
|
||||
|
||||
public static void AddMultiple<T>(this NameValueCollection collection, String name, IEnumerable<T> values, Func<T, String> convert)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(collection, "collection");
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(name, "name");
|
||||
|
||||
if (convert == null) convert = (val) => val.ToString();
|
||||
|
||||
if (values != null)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
collection.Add(name, convert(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddMultiple(this NameValueCollection collection, String name, IEnumerable<String> values)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(collection, "collection");
|
||||
collection.AddMultiple(name, values, (val) => val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the absolute path of the given Uri, if it is absolute.
|
||||
/// </summary>
|
||||
/// <returns>If the URI is absolute, the string form of it is returned; otherwise,
|
||||
/// the URI's string representation.</returns>
|
||||
public static string AbsoluteUri(this Uri uri)
|
||||
{
|
||||
return uri.IsAbsoluteUri ? uri.AbsoluteUri : uri.ToString();
|
||||
}
|
||||
|
||||
private static void AppendSingleQueryValue(StringBuilder builder, String name, String value)
|
||||
{
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append("&");
|
||||
}
|
||||
builder.Append(Uri.EscapeDataString(name));
|
||||
builder.Append("=");
|
||||
builder.Append(Uri.EscapeDataString(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,286 +0,0 @@
|
||||
// ************************************************************************************************
|
||||
// Microsoft Team Foundation
|
||||
//
|
||||
// Microsoft Confidential
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// File: VssStringComparer.cs
|
||||
// Area: Team Foundation
|
||||
// Classes: VssStringComparer
|
||||
// Contents: The Team Foundation string comparison class provides inner classes
|
||||
// that are used to provide semantic-specific Equals and Compare methods
|
||||
// and a semantic-specific StringComparer instance. New semantics should
|
||||
// be added on an as-needed basis.
|
||||
// ************************************************************************************************
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
|
||||
// NOTE: Since the recommendations are for Ordinal and OrdinalIgnoreCase, no need to explain those, but
|
||||
// please explain any instances using non-Ordinal comparisons (CurrentCulture, InvariantCulture)
|
||||
// so that developers following you can understand the choices and verify they are correct.
|
||||
|
||||
// NOTE: please try to keep the semantic-named properties in alphabetical order to ease merges
|
||||
|
||||
// NOTE: do NOT add xml doc comments - everything in here should be a very thin wrapper around String
|
||||
// or StringComparer. The usage of the methods and properties in this class should be intuitively
|
||||
// obvious, so please don't add xml doc comments to this class since it should be wholly internal
|
||||
// by the time we ship.
|
||||
|
||||
// NOTE: Current guidelines from the CLR team (Dave Fetterman) is to stick with the same operation for both
|
||||
// Compare and Equals for a given semantic inner class. This has the nice side effect that you don't
|
||||
// get different behavior between calling Equals or calling Compare == 0. This may seem odd given the
|
||||
// recommendations about using CurrentCulture for UI operations and Compare being used for sorting
|
||||
// items for user display in many cases, but we need to have the type of string data determine the
|
||||
// string comparison enum to use instead of the consumer of the comparison operation so that we're
|
||||
// consistent in how we treat a given semantic.
|
||||
|
||||
// VssStringComparer should act like StringComparer with a few additional methods for usefulness (Contains,
|
||||
// StartsWith, EndsWith, etc.) so that it can be a "one-stop shop" for string comparisons.
|
||||
public class VssStringComparer : StringComparer
|
||||
{
|
||||
private StringComparison m_stringComparison;
|
||||
private StringComparer m_stringComparer;
|
||||
|
||||
protected VssStringComparer(StringComparison stringComparison)
|
||||
: base()
|
||||
{
|
||||
m_stringComparison = stringComparison;
|
||||
}
|
||||
|
||||
// pass-through implementations based on our current StringComparison setting
|
||||
public override int Compare(string x, string y) { return String.Compare(x, y, m_stringComparison); }
|
||||
public override bool Equals(string x, string y) { return String.Equals(x, y, m_stringComparison); }
|
||||
public override int GetHashCode(string x) { return MatchingStringComparer.GetHashCode(x); }
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x")]
|
||||
public int Compare(string x, int indexX, string y, int indexY, int length) { return String.Compare(x, indexX, y, indexY, length, m_stringComparison); }
|
||||
|
||||
// add new useful methods here
|
||||
public bool Contains(string main, string pattern)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(main, "main");
|
||||
ArgumentUtility.CheckForNull(pattern, "pattern");
|
||||
|
||||
return main.IndexOf(pattern, m_stringComparison) >= 0;
|
||||
}
|
||||
|
||||
public int IndexOf(string main, string pattern)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(main, "main");
|
||||
ArgumentUtility.CheckForNull(pattern, "pattern");
|
||||
|
||||
return main.IndexOf(pattern, m_stringComparison);
|
||||
}
|
||||
|
||||
public bool StartsWith(string main, string pattern)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(main, "main");
|
||||
ArgumentUtility.CheckForNull(pattern, "pattern");
|
||||
|
||||
return main.StartsWith(pattern, m_stringComparison);
|
||||
}
|
||||
|
||||
public bool EndsWith(string main, string pattern)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(main, "main");
|
||||
ArgumentUtility.CheckForNull(pattern, "pattern");
|
||||
|
||||
return main.EndsWith(pattern, m_stringComparison);
|
||||
}
|
||||
|
||||
private StringComparer MatchingStringComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_stringComparer == null)
|
||||
{
|
||||
switch (m_stringComparison)
|
||||
{
|
||||
case StringComparison.CurrentCulture:
|
||||
m_stringComparer = StringComparer.CurrentCulture;
|
||||
break;
|
||||
|
||||
case StringComparison.CurrentCultureIgnoreCase:
|
||||
m_stringComparer = StringComparer.CurrentCultureIgnoreCase;
|
||||
break;
|
||||
|
||||
case StringComparison.Ordinal:
|
||||
m_stringComparer = StringComparer.Ordinal;
|
||||
break;
|
||||
|
||||
case StringComparison.OrdinalIgnoreCase:
|
||||
m_stringComparer = StringComparer.OrdinalIgnoreCase;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Assert(false, "Unknown StringComparison value");
|
||||
m_stringComparer = StringComparer.Ordinal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m_stringComparer;
|
||||
}
|
||||
}
|
||||
|
||||
protected static VssStringComparer s_ordinal = new VssStringComparer(StringComparison.Ordinal);
|
||||
protected static VssStringComparer s_ordinalIgnoreCase = new VssStringComparer(StringComparison.OrdinalIgnoreCase);
|
||||
protected static VssStringComparer s_currentCulture = new VssStringComparer(StringComparison.CurrentCulture);
|
||||
protected static VssStringComparer s_currentCultureIgnoreCase = new VssStringComparer(StringComparison.CurrentCultureIgnoreCase);
|
||||
private static VssStringComparer s_dataSourceIgnoreProtocol = new DataSourceIgnoreProtocolComparer();
|
||||
|
||||
|
||||
public static VssStringComparer ActiveDirectoryEntityIdComparer { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ArtifactType { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ArtifactTool { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer AssemblyName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ContentType { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DomainName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DomainNameUI { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer DatabaseCategory { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DatabaseName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DataSource { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DataSourceIgnoreProtocol { get { return s_dataSourceIgnoreProtocol; } }
|
||||
public static VssStringComparer DirectoryName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DirectoryEntityIdentifierConstants { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DirectoryEntityPropertyComparer { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DirectoryEntityTypeComparer { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DirectoryEntryNameComparer { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer DirectoryKeyStringComparer { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer EncodingName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer EnvVar { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ExceptionSource { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer FilePath { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer FilePathUI { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer Guid { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer Hostname { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer HostnameUI { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer HttpRequestMethod { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer IdentityDescriptor { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer IdentityDomain { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer IdentityOriginId { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer IdentityType { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer LinkName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer MachineName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer MailAddress { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer PropertyName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer RegistrationAttributeName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ReservedGroupName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer WMDSchemaClassName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer SamAccountName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer AccountName { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer SocialType { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer ServerUrl { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ServerUrlUI { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer ServiceInterface { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ServicingOperation { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ToolId { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer Url { get { return s_ordinal; } }
|
||||
public static VssStringComparer UrlPath { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer UriScheme { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer UriAuthority { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer UserId { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer UserName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer UserNameUI { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer XmlAttributeName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer XmlNodeName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer XmlElement { get { return s_ordinal; } }
|
||||
public static VssStringComparer XmlAttributeValue { get { return s_ordinalIgnoreCase; } }
|
||||
|
||||
//Framework comparers.
|
||||
public static VssStringComparer RegistryPath { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ServiceType { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer AccessMappingMoniker { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer CatalogNodePath { get { return s_ordinal; } }
|
||||
public static VssStringComparer CatalogServiceReference { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer CatalogNodeDependency { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ServicingTokenName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer IdentityPropertyName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer Collation { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer FeatureAvailabilityName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer TagName { get { return s_currentCultureIgnoreCase; } }
|
||||
|
||||
//Framework Hosting comparers.
|
||||
public static VssStringComparer HostingAccountPropertyName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer MessageBusName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer MessageBusSubscriptionName { get { return s_ordinalIgnoreCase; } }
|
||||
|
||||
public static VssStringComparer SID { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer FieldName { get { return s_ordinal; } }
|
||||
public static VssStringComparer FieldNameUI { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer FieldType { get { return s_ordinal; } }
|
||||
public static VssStringComparer EventType { get { return s_ordinal; } }
|
||||
public static VssStringComparer EventTypeIgnoreCase { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer RegistrationEntryName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ServerName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer GroupName { get { return s_currentCultureIgnoreCase; } }
|
||||
public static VssStringComparer RegistrationUtilities { get { return s_ordinal; } }
|
||||
public static VssStringComparer RegistrationUtilitiesCaseInsensitive { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer IdentityName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer IdentityNameOrdinal { get { return s_ordinal; } }
|
||||
public static VssStringComparer PlugInId { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ExtensionName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer ExtensionType { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer DomainUrl { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer AccountInfoAccount { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer AccountInfoPassword { get { return s_ordinal; } }
|
||||
public static VssStringComparer AttributesDescriptor { get { return s_ordinalIgnoreCase; } }
|
||||
|
||||
// Converters comparer
|
||||
public static VssStringComparer VSSServerPath { get { return s_ordinalIgnoreCase; } }
|
||||
|
||||
// Item rename in VSS is case sensitive.
|
||||
public static VssStringComparer VSSItemName { get { return s_ordinal; } }
|
||||
// Web Access Comparers
|
||||
public static VssStringComparer HtmlElementName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer HtmlAttributeName { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer HtmlAttributeValue { get { return s_ordinalIgnoreCase; } }
|
||||
|
||||
public static VssStringComparer StringFieldConditionEquality { get { return s_ordinalIgnoreCase; } }
|
||||
public static VssStringComparer StringFieldConditionOrdinal { get { return s_ordinal; } }
|
||||
|
||||
// Service Endpoint Comparer
|
||||
public static VssStringComparer ServiceEndpointTypeCompararer { get { return s_ordinalIgnoreCase; } }
|
||||
|
||||
private class DataSourceIgnoreProtocolComparer : VssStringComparer
|
||||
{
|
||||
public DataSourceIgnoreProtocolComparer()
|
||||
: base(StringComparison.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public override int Compare(string x, string y)
|
||||
{
|
||||
return base.Compare(RemoveProtocolPrefix(x), RemoveProtocolPrefix(y));
|
||||
}
|
||||
|
||||
public override bool Equals(string x, string y)
|
||||
{
|
||||
return base.Equals(RemoveProtocolPrefix(x), RemoveProtocolPrefix(y));
|
||||
}
|
||||
|
||||
private static string RemoveProtocolPrefix(string x)
|
||||
{
|
||||
if (x != null)
|
||||
{
|
||||
if (x.StartsWith(c_tcpPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
x = x.Substring(c_tcpPrefix.Length);
|
||||
}
|
||||
else if (x.StartsWith(c_npPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
x = x.Substring(c_npPrefix.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
private const string c_tcpPrefix = "tcp:";
|
||||
private const string c_npPrefix = "np:";
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,291 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all custom exceptions thrown from Vss and Tfs code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All Exceptions in the VSS space -- any exception that flows across
|
||||
/// a REST API boudary -- should derive from VssServiceException. This is likely
|
||||
/// almost ALL new exceptions. Legacy TFS exceptions that do not flow through rest
|
||||
/// derive from TeamFoundationServerException or TeamFoundationServiceException
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")]
|
||||
[ExceptionMapping("0.0", "3.0", "VssException", "GitHub.Services.Common.VssException, GitHub.Services.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public abstract class VssException : ApplicationException
|
||||
{
|
||||
/// <summary>
|
||||
/// No-arg constructor that sumply defers to the base class.
|
||||
/// </summary>
|
||||
public VssException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message.
|
||||
/// </summary>
|
||||
/// <param name="errorCode">Application-defined error code for this exception</param>
|
||||
public VssException(int errorCode) : this(errorCode, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message.
|
||||
/// </summary>
|
||||
/// <param name="errorCode">Application-defined error code for this exception</param>
|
||||
/// <param name="logException">Indicate whether this exception should be logged</param>
|
||||
public VssException(int errorCode, bool logException)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
LogException = logException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">A human readable message that describes the error</param>
|
||||
public VssException(string message) : base(SecretUtility.ScrubSecrets(message))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message and an inner exception that caused this exception to be raised.
|
||||
/// </summary>
|
||||
/// <param name="message">A human readable message that describes the error</param>
|
||||
/// <param name="innerException"></param>
|
||||
public VssException(string message, Exception innerException) : base(SecretUtility.ScrubSecrets(message), innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message and an inner exception that caused this exception to be raised.
|
||||
/// </summary>
|
||||
/// <param name="message">A human readable message that describes the error</param>
|
||||
/// <param name="errorCode">Application defined error code</param>
|
||||
/// <param name="innerException"></param>
|
||||
public VssException(string message, int errorCode, Exception innerException) : this(message, innerException)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
LogException = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message and an inner exception that caused this exception to be raised.
|
||||
/// </summary>
|
||||
/// <param name="message">A human readable message that describes the error</param>
|
||||
/// <param name="errorCode">Application defined error code</param>
|
||||
public VssException(string message, int errorCode) : this(message, errorCode, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message and an inner exception that caused this exception to be raised.
|
||||
/// </summary>
|
||||
/// <param name="message">A human readable message that describes the error</param>
|
||||
/// <param name="errorCode">Application defined error code</param>
|
||||
/// <param name="logException">Indicate whether this exception should be logged</param>
|
||||
public VssException(string message, int errorCode, bool logException) : this(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
LogException = logException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception with the specified error message and an inner exception that caused this exception to be raised.
|
||||
/// </summary>
|
||||
/// <param name="message">A human readable message that describes the error</param>
|
||||
/// <param name="errorCode">Application defined error code</param>
|
||||
/// <param name="logException"></param>
|
||||
/// <param name="innerException"></param>
|
||||
public VssException(string message, int errorCode, bool logException, Exception innerException) : this(message, innerException)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
LogException = logException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception from serialized data
|
||||
/// </summary>
|
||||
/// <param name="info">object holding the serialized data</param>
|
||||
/// <param name="context">context info about the source or destination</param>
|
||||
protected VssException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
LogException = (bool)info.GetValue("m_logException", typeof(bool));
|
||||
ReportException = (bool)info.GetValue("m_reportException", typeof(bool));
|
||||
ErrorCode = (int)info.GetValue("m_errorCode", typeof(int));
|
||||
#if !NETSTANDARD
|
||||
LogLevel = (EventLogEntryType)info.GetValue("m_logLevel", typeof(EventLogEntryType));
|
||||
#endif
|
||||
EventId = (int)info.GetValue("m_eventId", typeof(int));
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue("m_logException", LogException);
|
||||
info.AddValue("m_reportException", ReportException);
|
||||
info.AddValue("m_errorCode", ErrorCode);
|
||||
#if !NETSTANDARD
|
||||
info.AddValue("m_logLevel", LogLevel);
|
||||
#endif
|
||||
info.AddValue("m_eventId", EventId);
|
||||
}
|
||||
|
||||
/// <summary>Indicate whether this exception instance should be logged</summary>
|
||||
/// <value>True (false) if the exception should (should not) be logged</value>
|
||||
public bool LogException
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_logException;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_logException = value;
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>The event log entry type to use when logging the exception</summary>
|
||||
/// <value>One of the event log entry types: </value>
|
||||
public EventLogEntryType LogLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_logLevel;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_logLevel = value;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>A user-defined error code.</summary>
|
||||
public int ErrorCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_errorCode;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_errorCode = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The event ID to report if the exception is marked for the event log</summary>
|
||||
/// <value>The event ID used in the entry added to the event log</value>
|
||||
public int EventId
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_eventId;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_eventId = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Indicate whether the exception should be reported through Dr. Watson</summary>
|
||||
/// <value>True (false) if the exception should (should not) be reported</value>
|
||||
public bool ReportException
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_reportException;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_reportException = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default serialized type name and type key for the given exception type.
|
||||
/// </summary>
|
||||
internal static void GetTypeNameAndKeyForExceptionType(Type exceptionType, Version restApiVersion, out String typeName, out String typeKey)
|
||||
{
|
||||
typeName = null;
|
||||
typeKey = exceptionType.Name;
|
||||
if (restApiVersion != null)
|
||||
{
|
||||
IEnumerable<ExceptionMappingAttribute> exceptionAttributes = exceptionType.GetTypeInfo().GetCustomAttributes<ExceptionMappingAttribute>().Where(ea => ea.MinApiVersion <= restApiVersion && ea.ExclusiveMaxApiVersion > restApiVersion);
|
||||
if (exceptionAttributes.Any())
|
||||
{
|
||||
ExceptionMappingAttribute exceptionAttribute = exceptionAttributes.First();
|
||||
typeName = exceptionAttribute.TypeName;
|
||||
typeKey = exceptionAttribute.TypeKey;
|
||||
}
|
||||
else if (restApiVersion < s_backCompatExclusiveMaxVersion) //if restApiVersion < 3 we send the assembly qualified name with the current binary version switched out to 14
|
||||
{
|
||||
typeName = GetBackCompatAssemblyQualifiedName(exceptionType);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeName == null)
|
||||
{
|
||||
|
||||
AssemblyName asmName = exceptionType.GetTypeInfo().Assembly.GetName();
|
||||
if (asmName != null)
|
||||
{
|
||||
//going forward we send "FullName" and simple assembly name which includes no version.
|
||||
typeName = exceptionType.FullName + ", " + asmName.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
String assemblyString = exceptionType.GetTypeInfo().Assembly.FullName;
|
||||
assemblyString = assemblyString.Substring(0, assemblyString.IndexOf(','));
|
||||
typeName = exceptionType.FullName + ", " + assemblyString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static String GetBackCompatAssemblyQualifiedName(Type type)
|
||||
{
|
||||
AssemblyName current = type.GetTypeInfo().Assembly.GetName();
|
||||
if (current != null)
|
||||
{
|
||||
AssemblyName old = current;
|
||||
old.Version = new Version(c_backCompatVersion, 0, 0, 0);
|
||||
return Assembly.CreateQualifiedName(old.ToString(), type.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
//this is probably not necessary...
|
||||
return type.AssemblyQualifiedName.Replace(c_currentAssemblyMajorVersionString, c_backCompatVersionString);
|
||||
}
|
||||
}
|
||||
|
||||
private const String c_currentAssemblyMajorVersionString = "Version=" + GeneratedVersionInfo.AssemblyMajorVersion;
|
||||
private const String c_backCompatVersionString = "Version=14";
|
||||
private const int c_backCompatVersion = 14;
|
||||
|
||||
private static Version s_backCompatExclusiveMaxVersion = new Version(3, 0);
|
||||
private bool m_logException;
|
||||
private bool m_reportException;
|
||||
private int m_errorCode;
|
||||
|
||||
#if !NETSTANDARD
|
||||
private EventLogEntryType m_logLevel = EventLogEntryType.Warning;
|
||||
#endif
|
||||
|
||||
private int m_eventId = DefaultExceptionEventId;
|
||||
|
||||
//From EventLog.cs in Framework.
|
||||
public const int DefaultExceptionEventId = 3000;
|
||||
}
|
||||
}
|
||||
@@ -1,682 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common.Diagnostics;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides authentication for Visual Studio Services.
|
||||
/// </summary>
|
||||
public class VssHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with default credentials and request
|
||||
/// settings.
|
||||
/// </summary>
|
||||
public VssHttpMessageHandler()
|
||||
: this(new VssCredentials(), new VssHttpRequestSettings())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with the specified credentials and request
|
||||
/// settings.
|
||||
/// </summary>
|
||||
/// <param name="credentials">The credentials which should be used</param>
|
||||
/// <param name="settings">The request settings which should be used</param>
|
||||
public VssHttpMessageHandler(
|
||||
VssCredentials credentials,
|
||||
VssHttpRequestSettings settings)
|
||||
: this(credentials, settings,
|
||||
#if !NETSTANDARD
|
||||
new WebRequestHandler()
|
||||
#else
|
||||
new HttpClientHandler()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with the specified credentials and request
|
||||
/// settings.
|
||||
/// </summary>
|
||||
/// <param name="credentials">The credentials which should be used</param>
|
||||
/// <param name="settings">The request settings which should be used</param>
|
||||
/// <param name="innerHandler"></param>
|
||||
public VssHttpMessageHandler(
|
||||
VssCredentials credentials,
|
||||
VssHttpRequestSettings settings,
|
||||
HttpMessageHandler innerHandler)
|
||||
{
|
||||
this.Credentials = credentials;
|
||||
this.Settings = settings;
|
||||
this.ExpectContinue = settings.ExpectContinue;
|
||||
|
||||
m_credentialWrapper = new CredentialWrapper();
|
||||
m_messageInvoker = new HttpMessageInvoker(innerHandler);
|
||||
|
||||
// If we were given a pipeline make sure we find the inner-most handler to apply our settings as this
|
||||
// will be the actual outgoing transport.
|
||||
{
|
||||
HttpMessageHandler transportHandler = innerHandler;
|
||||
DelegatingHandler delegatingHandler = transportHandler as DelegatingHandler;
|
||||
while (delegatingHandler != null)
|
||||
{
|
||||
transportHandler = delegatingHandler.InnerHandler;
|
||||
delegatingHandler = transportHandler as DelegatingHandler;
|
||||
}
|
||||
|
||||
m_transportHandler = transportHandler;
|
||||
}
|
||||
|
||||
#if NETSTANDARD
|
||||
//.Net Core does not recognize CredentialCache.DefaultCredentials if we wrap them with CredentialWrapper
|
||||
bool isDefaultCredentials = credentials != null && credentials.Windows != null && credentials.Windows.UseDefaultCredentials;
|
||||
ApplySettings(m_transportHandler, isDefaultCredentials ? CredentialCache.DefaultCredentials : m_credentialWrapper, this.Settings);
|
||||
#else
|
||||
ApplySettings(m_transportHandler, m_credentialWrapper, this.Settings);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the credentials associated with this handler.
|
||||
/// </summary>
|
||||
public VssCredentials Credentials
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the settings associated with this handler.
|
||||
/// </summary>
|
||||
public VssHttpRequestSettings Settings
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Boolean ExpectContinue
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
protected override void Dispose(Boolean disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (m_messageInvoker != null)
|
||||
{
|
||||
m_messageInvoker.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly String PropertyName = "MS.VS.MessageHandler";
|
||||
|
||||
/// <summary>
|
||||
/// Handles the authentication hand-shake for a Visual Studio service.
|
||||
/// </summary>
|
||||
/// <param name="request">The HTTP request message</param>
|
||||
/// <param name="cancellationToken">The cancellation token used for cooperative cancellation</param>
|
||||
/// <returns>A new <c>Task<HttpResponseMessage></c> which wraps the response from the remote service</returns>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
||||
|
||||
var traceInfo = VssHttpMessageHandlerTraceInfo.GetTraceInfo(request);
|
||||
traceInfo?.TraceHandlerStartTime();
|
||||
|
||||
#if !NETSTANDARD
|
||||
// This action is deferred from ApplySettings because we want don't want to do it if we aren't
|
||||
// talking to an HTTPS endpoint.
|
||||
if (!m_appliedClientCertificatesToTransportHandler &&
|
||||
request.RequestUri.Scheme == "https")
|
||||
{
|
||||
WebRequestHandler webRequestHandler = m_transportHandler as WebRequestHandler;
|
||||
if (webRequestHandler != null &&
|
||||
this.Settings.ClientCertificateManager != null &&
|
||||
this.Settings.ClientCertificateManager.ClientCertificates != null &&
|
||||
this.Settings.ClientCertificateManager.ClientCertificates.Count > 0)
|
||||
{
|
||||
webRequestHandler.ClientCertificates.AddRange(this.Settings.ClientCertificateManager.ClientCertificates);
|
||||
}
|
||||
m_appliedClientCertificatesToTransportHandler = true;
|
||||
}
|
||||
|
||||
if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
|
||||
request.RequestUri.Scheme == "https")
|
||||
{
|
||||
WebRequestHandler webRequestHandler = m_transportHandler as WebRequestHandler;
|
||||
if (webRequestHandler != null &&
|
||||
this.Settings.ServerCertificateValidationCallback != null)
|
||||
{
|
||||
webRequestHandler.ServerCertificateValidationCallback = this.Settings.ServerCertificateValidationCallback;
|
||||
}
|
||||
m_appliedServerCertificateValidationCallbackToTransportHandler = true;
|
||||
}
|
||||
#else
|
||||
if (!m_appliedClientCertificatesToTransportHandler &&
|
||||
request.RequestUri.Scheme == "https")
|
||||
{
|
||||
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
|
||||
if (httpClientHandler != null &&
|
||||
this.Settings.ClientCertificateManager != null &&
|
||||
this.Settings.ClientCertificateManager.ClientCertificates != null &&
|
||||
this.Settings.ClientCertificateManager.ClientCertificates.Count > 0)
|
||||
{
|
||||
httpClientHandler.ClientCertificates.AddRange(this.Settings.ClientCertificateManager.ClientCertificates);
|
||||
}
|
||||
m_appliedClientCertificatesToTransportHandler = true;
|
||||
}
|
||||
|
||||
if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
|
||||
request.RequestUri.Scheme == "https")
|
||||
{
|
||||
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
|
||||
if (httpClientHandler != null &&
|
||||
this.Settings.ServerCertificateValidationCallback != null)
|
||||
{
|
||||
httpClientHandler.ServerCertificateCustomValidationCallback = this.Settings.ServerCertificateValidationCallback;
|
||||
}
|
||||
m_appliedServerCertificateValidationCallbackToTransportHandler = true;
|
||||
}
|
||||
|
||||
// The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
|
||||
// This causes problems with some versions of the Curl handler on Linux.
|
||||
// See GitHub issue https://github.com/dotnet/corefx/issues/32376
|
||||
if (Settings.UseHttp11)
|
||||
{
|
||||
request.Version = HttpVersion.Version11;
|
||||
}
|
||||
#endif
|
||||
|
||||
IssuedToken token = null;
|
||||
IssuedTokenProvider provider;
|
||||
if (this.Credentials.TryGetTokenProvider(request.RequestUri, out provider))
|
||||
{
|
||||
token = provider.CurrentToken;
|
||||
}
|
||||
|
||||
// Add ourselves to the message so the underlying token issuers may use it if necessary
|
||||
request.Properties[VssHttpMessageHandler.PropertyName] = this;
|
||||
|
||||
Boolean succeeded = false;
|
||||
Boolean lastResponseDemandedProxyAuth = false;
|
||||
Int32 retries = m_maxAuthRetries;
|
||||
HttpResponseMessage response = null;
|
||||
HttpResponseMessageWrapper responseWrapper;
|
||||
CancellationTokenSource tokenSource = null;
|
||||
|
||||
try
|
||||
{
|
||||
tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
if (this.Settings.SendTimeout > TimeSpan.Zero)
|
||||
{
|
||||
tokenSource.CancelAfter(this.Settings.SendTimeout);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
response.Dispose();
|
||||
}
|
||||
|
||||
ApplyHeaders(request);
|
||||
|
||||
// In the case of a Windows token, only apply it to the web proxy if it
|
||||
// returned a 407 Proxy Authentication Required. If we didn't get this
|
||||
// status code back, then the proxy (if there is one) is clearly working fine,
|
||||
// so we shouldn't mess with its credentials.
|
||||
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
|
||||
lastResponseDemandedProxyAuth = false;
|
||||
|
||||
// The WinHttpHandler will chunk any content that does not have a computed length which is
|
||||
// not what we want. By loading into a buffer up-front we bypass this behavior and there is
|
||||
// no difference in the normal HttpClientHandler behavior here since this is what they were
|
||||
// already doing.
|
||||
await BufferRequestContentAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
traceInfo?.TraceBufferedRequestTime();
|
||||
|
||||
// ConfigureAwait(false) enables the continuation to be run outside any captured
|
||||
// SyncronizationContext (such as ASP.NET's) which keeps things from deadlocking...
|
||||
response = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
traceInfo?.TraceRequestSendTime();
|
||||
|
||||
// Now buffer the response content if configured to do so. In general we will be buffering
|
||||
// the response content in this location, except in the few cases where the caller has
|
||||
// specified HttpCompletionOption.ResponseHeadersRead.
|
||||
// Trace content type in case of error
|
||||
await BufferResponseContentAsync(request, response, () => $"[ContentType: {response.Content.GetType().Name}]", tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
traceInfo?.TraceResponseContentTime();
|
||||
|
||||
responseWrapper = new HttpResponseMessageWrapper(response);
|
||||
|
||||
if (!this.Credentials.IsAuthenticationChallenge(responseWrapper))
|
||||
{
|
||||
// Validate the token after it has been successfully authenticated with the server.
|
||||
if (provider != null)
|
||||
{
|
||||
provider.ValidateToken(token, responseWrapper);
|
||||
}
|
||||
|
||||
// Make sure that once we can authenticate with the service that we turn off the
|
||||
// Expect100Continue behavior to increase performance.
|
||||
this.ExpectContinue = false;
|
||||
succeeded = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the case of a Windows token, only apply it to the web proxy if it
|
||||
// returned a 407 Proxy Authentication Required. If we didn't get this
|
||||
// status code back, then the proxy (if there is one) is clearly working fine,
|
||||
// so we shouldn't mess with its credentials.
|
||||
lastResponseDemandedProxyAuth = responseWrapper.StatusCode == HttpStatusCode.ProxyAuthenticationRequired;
|
||||
|
||||
// Invalidate the token and ensure that we have the correct token provider for the challenge
|
||||
// which we just received
|
||||
VssHttpEventSource.Log.AuthenticationFailed(traceActivity, response);
|
||||
|
||||
if (provider != null)
|
||||
{
|
||||
provider.InvalidateToken(token);
|
||||
}
|
||||
|
||||
// Ensure we have an appropriate token provider for the current challenge
|
||||
provider = this.Credentials.CreateTokenProvider(request.RequestUri, responseWrapper, token);
|
||||
|
||||
// Make sure we don't invoke the provider in an invalid state
|
||||
if (provider == null)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenProviderNotFound(traceActivity);
|
||||
break;
|
||||
}
|
||||
else if (provider.GetTokenIsInteractive && this.Credentials.PromptType == CredentialPromptType.DoNotPrompt)
|
||||
{
|
||||
VssHttpEventSource.Log.IssuedTokenProviderPromptRequired(traceActivity, provider);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the user has already tried once but still unauthorized, stop retrying. The main scenario for this condition
|
||||
// is a user typed in a valid credentials for a hosted account but the associated identity does not have
|
||||
// access. We do not want to continually prompt 3 times without telling them the failure reason. In the
|
||||
// next release we should rethink about presenting user the failure and options between retries.
|
||||
IEnumerable<String> headerValues;
|
||||
Boolean hasAuthenticateError =
|
||||
response.Headers.TryGetValues(HttpHeaders.VssAuthenticateError, out headerValues) &&
|
||||
!String.IsNullOrEmpty(headerValues.FirstOrDefault());
|
||||
|
||||
if (retries == 0 || (retries < m_maxAuthRetries && hasAuthenticateError))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Now invoke the provider and await the result
|
||||
token = await provider.GetTokenAsync(token, tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
// I always see 0 here, but the method above could take more time so keep for now
|
||||
traceInfo?.TraceGetTokenTime();
|
||||
|
||||
// If we just received a token, lets ask the server for the VSID
|
||||
request.Headers.Add(HttpHeaders.VssUserData, String.Empty);
|
||||
|
||||
retries--;
|
||||
}
|
||||
}
|
||||
while (retries >= 0);
|
||||
|
||||
if (traceInfo != null)
|
||||
{
|
||||
traceInfo.TokenRetries = m_maxAuthRetries - retries;
|
||||
}
|
||||
|
||||
// We're out of retries and the response was an auth challenge -- then the request was unauthorized
|
||||
// and we will throw a strongly-typed exception with a friendly error message.
|
||||
if (!succeeded && response != null && this.Credentials.IsAuthenticationChallenge(responseWrapper))
|
||||
{
|
||||
String message = null;
|
||||
IEnumerable<String> serviceError;
|
||||
|
||||
if (response.Headers.TryGetValues(HttpHeaders.TfsServiceError, out serviceError))
|
||||
{
|
||||
message = UriUtility.UrlDecode(serviceError.FirstOrDefault());
|
||||
}
|
||||
else
|
||||
{
|
||||
message = CommonResources.VssUnauthorized(request.RequestUri.GetLeftPart(UriPartial.Authority));
|
||||
}
|
||||
|
||||
// Make sure we do not leak the response object when raising an exception
|
||||
if (response != null)
|
||||
{
|
||||
response.Dispose();
|
||||
}
|
||||
|
||||
VssHttpEventSource.Log.HttpRequestUnauthorized(traceActivity, request, message);
|
||||
VssUnauthorizedException unauthorizedException = new VssUnauthorizedException(message);
|
||||
|
||||
if (provider != null)
|
||||
{
|
||||
unauthorizedException.Data.Add(CredentialsType, provider.CredentialType);
|
||||
}
|
||||
|
||||
throw unauthorizedException;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
VssHttpEventSource.Log.HttpRequestCancelled(traceActivity, request);
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
VssHttpEventSource.Log.HttpRequestTimedOut(traceActivity, request, this.Settings.SendTimeout);
|
||||
throw new TimeoutException(CommonResources.HttpRequestTimeout(this.Settings.SendTimeout), ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// We always dispose of the token source since otherwise we leak resources if there is a timer pending
|
||||
if (tokenSource != null)
|
||||
{
|
||||
tokenSource.Dispose();
|
||||
}
|
||||
|
||||
traceInfo?.TraceTrailingTime();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task BufferRequestContentAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.Content != null &&
|
||||
request.Headers.TransferEncodingChunked != true)
|
||||
{
|
||||
Int64? contentLength = request.Content.Headers.ContentLength;
|
||||
if (contentLength == null)
|
||||
{
|
||||
await request.Content.LoadIntoBufferAsync().EnforceCancellation(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Explicitly turn off chunked encoding since we have computed the request content size
|
||||
request.Headers.TransferEncodingChunked = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task BufferResponseContentAsync(
|
||||
HttpRequestMessage request,
|
||||
HttpResponseMessage response,
|
||||
Func<string> makeErrorMessage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Determine whether or not we should go ahead and buffer the output under our timeout scope. If
|
||||
// we do not perform this action here there is a potential network stack hang since we override
|
||||
// the HttpClient.SendTimeout value and the cancellation token for monitoring request timeout does
|
||||
// not survive beyond this scope.
|
||||
if (response == null || response.StatusCode == HttpStatusCode.NoContent || response.Content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not try to buffer with a size of 0. This forces all calls to effectively use the behavior of
|
||||
// HttpCompletionOption.ResponseHeadersRead if that is desired.
|
||||
if (this.Settings.MaxContentBufferSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the completion option provided by the caller. If we don't find the property then we
|
||||
// assume it is OK to buffer by default.
|
||||
HttpCompletionOption completionOption;
|
||||
if (!request.Properties.TryGetValue(VssHttpRequestSettings.HttpCompletionOptionPropertyName, out completionOption))
|
||||
{
|
||||
completionOption = HttpCompletionOption.ResponseContentRead;
|
||||
}
|
||||
|
||||
// If the caller specified that response content should be read then we need to go ahead and
|
||||
// buffer it all up to the maximum buffer size specified by the settings. Anything larger than
|
||||
// the maximum will trigger an error in the underlying stack.
|
||||
if (completionOption == HttpCompletionOption.ResponseContentRead)
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync(this.Settings.MaxContentBufferSize).EnforceCancellation(cancellationToken, makeErrorMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyHeaders(HttpRequestMessage request)
|
||||
{
|
||||
if (this.Settings.ApplyTo(request))
|
||||
{
|
||||
VssTraceActivity activity = request.GetActivity();
|
||||
if (activity != null &&
|
||||
activity != VssTraceActivity.Empty &&
|
||||
!request.Headers.Contains(HttpHeaders.TfsSessionHeader))
|
||||
{
|
||||
request.Headers.Add(HttpHeaders.TfsSessionHeader, activity.Id.ToString("D"));
|
||||
}
|
||||
|
||||
request.Headers.ExpectContinue = this.ExpectContinue;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyToken(
|
||||
HttpRequestMessage request,
|
||||
IssuedToken token,
|
||||
bool applyICredentialsToWebProxy = false)
|
||||
{
|
||||
if (token == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ICredentials credentialsToken = token as ICredentials;
|
||||
if (credentialsToken != null)
|
||||
{
|
||||
if (applyICredentialsToWebProxy)
|
||||
{
|
||||
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
|
||||
|
||||
if (httpClientHandler != null &&
|
||||
httpClientHandler.Proxy != null)
|
||||
{
|
||||
httpClientHandler.Proxy.Credentials = credentialsToken;
|
||||
}
|
||||
}
|
||||
|
||||
m_credentialWrapper.InnerCredentials = credentialsToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
token.ApplyTo(new HttpRequestMessageWrapper(request));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplySettings(
|
||||
HttpMessageHandler handler,
|
||||
ICredentials defaultCredentials,
|
||||
VssHttpRequestSettings settings)
|
||||
{
|
||||
HttpClientHandler httpClientHandler = handler as HttpClientHandler;
|
||||
if (httpClientHandler != null)
|
||||
{
|
||||
httpClientHandler.AllowAutoRedirect = settings.AllowAutoRedirect;
|
||||
httpClientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
|
||||
//Setting httpClientHandler.UseDefaultCredentials to false in .Net Core, clears httpClientHandler.Credentials if
|
||||
//credentials is already set to defaultcredentials. Therefore httpClientHandler.Credentials must be
|
||||
//set after httpClientHandler.UseDefaultCredentials.
|
||||
httpClientHandler.UseDefaultCredentials = false;
|
||||
httpClientHandler.Credentials = defaultCredentials;
|
||||
httpClientHandler.PreAuthenticate = false;
|
||||
httpClientHandler.Proxy = DefaultWebProxy;
|
||||
httpClientHandler.UseCookies = false;
|
||||
httpClientHandler.UseProxy = true;
|
||||
|
||||
if (settings.CompressionEnabled)
|
||||
{
|
||||
httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IWebProxy s_defaultWebProxy =
|
||||
#if !NETSTANDARD
|
||||
WebRequest.DefaultWebProxy;
|
||||
#else
|
||||
// setting this to WebRequest.DefaultWebProxy in NETSTANDARD is causing a System.PlatformNotSupportedException
|
||||
//.in System.Net.SystemWebProxy.IsBypassed. Comment in IsBypassed method indicates ".NET Core and .NET Native
|
||||
// code will handle this exception and call into WinInet/WinHttp as appropriate to use the system proxy."
|
||||
// This needs to be investigated further.
|
||||
null;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to set a proxy to be used by all VssHttpMessageHandler requests without affecting the global WebRequest.DefaultWebProxy. If not set it returns the WebRequest.DefaultWebProxy.
|
||||
/// </summary>
|
||||
public static IWebProxy DefaultWebProxy
|
||||
{
|
||||
get
|
||||
{
|
||||
var toReturn = WebProxyWrapper.Wrap(s_defaultWebProxy);
|
||||
|
||||
if (null != toReturn &&
|
||||
toReturn.Credentials == null)
|
||||
{
|
||||
toReturn.Credentials = CredentialCache.DefaultCredentials;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
set
|
||||
{
|
||||
// requested by Insights team to be able to set a default Proxy that only affects this handler.
|
||||
// see following bug for details: https://mseng.visualstudio.com/DefaultCollection/VSOnline/_workitems#_a=edit&id=425575&triage=true
|
||||
s_defaultWebProxy = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal const String CredentialsType = nameof(CredentialsType);
|
||||
|
||||
private const Int32 m_maxAuthRetries = 3;
|
||||
private HttpMessageInvoker m_messageInvoker;
|
||||
private CredentialWrapper m_credentialWrapper;
|
||||
private bool m_appliedClientCertificatesToTransportHandler;
|
||||
private bool m_appliedServerCertificateValidationCallbackToTransportHandler;
|
||||
private readonly HttpMessageHandler m_transportHandler;
|
||||
|
||||
#if NETSTANDARD
|
||||
//.Net Core does not attempt NTLM schema on Linux, unless ICredentials is a CredentialCache instance
|
||||
//This workaround may not be needed after this corefx fix is consumed: https://github.com/dotnet/corefx/pull/7923
|
||||
private sealed class CredentialWrapper : CredentialCache, ICredentials
|
||||
#else
|
||||
private sealed class CredentialWrapper : ICredentials
|
||||
#endif
|
||||
{
|
||||
public ICredentials InnerCredentials
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
NetworkCredential ICredentials.GetCredential(
|
||||
Uri uri,
|
||||
String authType)
|
||||
{
|
||||
return InnerCredentials != null ? InnerCredentials.GetCredential(uri, authType) : null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WebProxyWrapper : IWebProxy
|
||||
{
|
||||
private WebProxyWrapper(IWebProxy toWrap)
|
||||
{
|
||||
m_wrapped = toWrap;
|
||||
m_credentials = null;
|
||||
}
|
||||
|
||||
public static WebProxyWrapper Wrap(IWebProxy toWrap)
|
||||
{
|
||||
if (null == toWrap)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new WebProxyWrapper(toWrap);
|
||||
}
|
||||
|
||||
public ICredentials Credentials
|
||||
{
|
||||
get
|
||||
{
|
||||
ICredentials credentials = m_credentials;
|
||||
|
||||
if (null == credentials)
|
||||
{
|
||||
// This means to fall back to the Credentials from the wrapped
|
||||
// IWebProxy.
|
||||
credentials = m_wrapped.Credentials;
|
||||
}
|
||||
else if (Object.ReferenceEquals(credentials, m_nullCredentials))
|
||||
{
|
||||
// This sentinel value means we have explicitly had our credentials
|
||||
// set to null.
|
||||
credentials = null;
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (null == value)
|
||||
{
|
||||
// Use this as a sentinel value to distinguish the case when someone has
|
||||
// explicitly set our credentials to null. We don't want to fall back to
|
||||
// m_wrapped.Credentials when we have credentials that are explicitly null.
|
||||
m_credentials = m_nullCredentials;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_credentials = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Uri GetProxy(Uri destination)
|
||||
{
|
||||
return m_wrapped.GetProxy(destination);
|
||||
}
|
||||
|
||||
public bool IsBypassed(Uri host)
|
||||
{
|
||||
return m_wrapped.IsBypassed(host);
|
||||
}
|
||||
|
||||
private readonly IWebProxy m_wrapped;
|
||||
private ICredentials m_credentials;
|
||||
|
||||
private static readonly ICredentials m_nullCredentials = new CredentialWrapper();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used by the message handler, if injected as a request property, to trace additional
|
||||
/// timing details for outgoing requests. This information is added to the HttpOutgoingRequest logs
|
||||
/// </summary>
|
||||
public class VssHttpMessageHandlerTraceInfo
|
||||
{
|
||||
DateTime _lastTime;
|
||||
|
||||
static readonly String TfsTraceInfoKey = "TFS_TraceInfo";
|
||||
|
||||
public int TokenRetries { get; internal set; }
|
||||
|
||||
public TimeSpan HandlerStartTime { get; private set; }
|
||||
public TimeSpan BufferedRequestTime { get; private set; }
|
||||
public TimeSpan RequestSendTime { get; private set; }
|
||||
public TimeSpan ResponseContentTime { get; private set; }
|
||||
public TimeSpan GetTokenTime { get; private set; }
|
||||
public TimeSpan TrailingTime { get; private set; }
|
||||
|
||||
public VssHttpMessageHandlerTraceInfo()
|
||||
{
|
||||
_lastTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
internal void TraceHandlerStartTime()
|
||||
{
|
||||
var previous = _lastTime;
|
||||
_lastTime = DateTime.UtcNow;
|
||||
HandlerStartTime += (_lastTime - previous);
|
||||
}
|
||||
|
||||
internal void TraceBufferedRequestTime()
|
||||
{
|
||||
var previous = _lastTime;
|
||||
_lastTime = DateTime.UtcNow;
|
||||
BufferedRequestTime += (_lastTime - previous);
|
||||
}
|
||||
|
||||
internal void TraceRequestSendTime()
|
||||
{
|
||||
var previous = _lastTime;
|
||||
_lastTime = DateTime.UtcNow;
|
||||
RequestSendTime += (_lastTime - previous);
|
||||
}
|
||||
|
||||
internal void TraceResponseContentTime()
|
||||
{
|
||||
var previous = _lastTime;
|
||||
_lastTime = DateTime.UtcNow;
|
||||
ResponseContentTime += (_lastTime - previous);
|
||||
}
|
||||
|
||||
internal void TraceGetTokenTime()
|
||||
{
|
||||
var previous = _lastTime;
|
||||
_lastTime = DateTime.UtcNow;
|
||||
GetTokenTime += (_lastTime - previous);
|
||||
}
|
||||
|
||||
internal void TraceTrailingTime()
|
||||
{
|
||||
var previous = _lastTime;
|
||||
_lastTime = DateTime.UtcNow;
|
||||
TrailingTime += (_lastTime - previous);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the provided traceInfo as a property on a request message (if not already set)
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="traceInfo"></param>
|
||||
public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo)
|
||||
{
|
||||
object existingTraceInfo;
|
||||
if (!message.Properties.TryGetValue(TfsTraceInfoKey, out existingTraceInfo))
|
||||
{
|
||||
message.Properties.Add(TfsTraceInfoKey, traceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get VssHttpMessageHandlerTraceInfo from request message, or return null if none found
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public static VssHttpMessageHandlerTraceInfo GetTraceInfo(HttpRequestMessage message)
|
||||
{
|
||||
VssHttpMessageHandlerTraceInfo traceInfo = null;
|
||||
|
||||
if (message.Properties.TryGetValue(TfsTraceInfoKey, out object traceInfoObject))
|
||||
{
|
||||
traceInfo = traceInfoObject as VssHttpMessageHandlerTraceInfo;
|
||||
}
|
||||
|
||||
return traceInfo;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"R:{TokenRetries}, HS:{HandlerStartTime.Ticks}, BR:{BufferedRequestTime.Ticks}, RS:{RequestSendTime.Ticks}, RC:{ResponseContentTime.Ticks}, GT:{GetTokenTime.Ticks}, TT={TrailingTime.Ticks}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides common settings for a <c>VssHttpMessageHandler</c> instance.
|
||||
/// </summary>
|
||||
public class VssHttpRequestSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpRequestSettings</c> instance with compression enabled.
|
||||
/// </summary>
|
||||
public VssHttpRequestSettings()
|
||||
: this(Guid.NewGuid())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpRequestSettings</c> instance with compression enabled.
|
||||
/// </summary>
|
||||
public VssHttpRequestSettings(Guid sessionId)
|
||||
{
|
||||
this.AllowAutoRedirect = false;
|
||||
this.CompressionEnabled = true;
|
||||
this.ExpectContinue = true;
|
||||
this.BypassProxyOnLocal = true;
|
||||
this.MaxContentBufferSize = c_defaultContentBufferSize;
|
||||
this.SendTimeout = s_defaultTimeout;
|
||||
if (!String.IsNullOrEmpty(CultureInfo.CurrentUICulture.Name)) // InvariantCulture for example has an empty name.
|
||||
{
|
||||
this.AcceptLanguages.Add(CultureInfo.CurrentUICulture);
|
||||
}
|
||||
this.SessionId = sessionId;
|
||||
this.SuppressFedAuthRedirects = true;
|
||||
this.ClientCertificateManager = null;
|
||||
this.ServerCertificateValidationCallback = null;
|
||||
#if NETSTANDARD
|
||||
this.UseHttp11 = false;
|
||||
#endif
|
||||
|
||||
// If different, we'll also add CurrentCulture to the request headers,
|
||||
// but UICulture was added first, so it gets first preference
|
||||
if (!CultureInfo.CurrentCulture.Equals(CultureInfo.CurrentUICulture) && !String.IsNullOrEmpty(CultureInfo.CurrentCulture.Name))
|
||||
{
|
||||
this.AcceptLanguages.Add(CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
this.MaxRetryRequest = c_defaultMaxRetry;
|
||||
|
||||
#if DEBUG
|
||||
string customClientRequestTimeout = Environment.GetEnvironmentVariable("VSS_Client_Request_Timeout");
|
||||
if (!string.IsNullOrEmpty(customClientRequestTimeout) && int.TryParse(customClientRequestTimeout, out int customTimeout))
|
||||
{
|
||||
// avoid disrupting a debug session due to the request timing out by setting a custom timeout.
|
||||
this.SendTimeout = TimeSpan.FromSeconds(customTimeout);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpRequestSettings</c> instance with compression enabled.
|
||||
/// </summary>
|
||||
/// <remarks>The e2eId argument is not used.</remarks>
|
||||
[Obsolete]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public VssHttpRequestSettings(Guid sessionId, Guid e2eId)
|
||||
: this(sessionId)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy Constructor
|
||||
/// </summary>
|
||||
/// <param name="copy"></param>
|
||||
protected VssHttpRequestSettings(VssHttpRequestSettings copy)
|
||||
{
|
||||
this.AllowAutoRedirect = copy.AllowAutoRedirect;
|
||||
this.CompressionEnabled = copy.CompressionEnabled;
|
||||
this.ExpectContinue = copy.ExpectContinue;
|
||||
this.BypassProxyOnLocal = copy.BypassProxyOnLocal;
|
||||
this.MaxContentBufferSize = copy.MaxContentBufferSize;
|
||||
this.SendTimeout = copy.SendTimeout;
|
||||
this.m_acceptLanguages = new List<CultureInfo>(copy.AcceptLanguages);
|
||||
this.SessionId = copy.SessionId;
|
||||
this.AgentId = copy.AgentId;
|
||||
this.SuppressFedAuthRedirects = copy.SuppressFedAuthRedirects;
|
||||
this.UserAgent = new List<ProductInfoHeaderValue>(copy.UserAgent);
|
||||
this.OperationName = copy.OperationName;
|
||||
this.ClientCertificateManager = copy.ClientCertificateManager;
|
||||
this.ServerCertificateValidationCallback = copy.ServerCertificateValidationCallback;
|
||||
this.MaxRetryRequest = copy.MaxRetryRequest;
|
||||
#if NETSTANDARD
|
||||
this.UseHttp11 = copy.UseHttp11;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not HttpClientHandler should follow redirect on outgoing requests.
|
||||
/// </summary>
|
||||
public Boolean AllowAutoRedirect
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not compression should be used on outgoing requests.
|
||||
/// The default value is true.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
public Boolean CompressionEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the Expect: 100-continue header should be sent on
|
||||
/// outgoing requess. The default value is true.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
public Boolean ExpectContinue
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether to bypass web proxies if the call is local
|
||||
/// </summary>
|
||||
public Boolean BypassProxyOnLocal
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
#if NETSTANDARD
|
||||
/// <summary>
|
||||
/// The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
|
||||
/// This causes problems with some versions of the Curl handler on Linux.
|
||||
/// See GitHub issue https://github.com/dotnet/corefx/issues/32376
|
||||
/// If true, requests generated by this client will use HTTP 1.1.
|
||||
/// </summary>
|
||||
public Boolean UseHttp11
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum size allowed for response content buffering.
|
||||
/// </summary>
|
||||
[DefaultValue(c_defaultContentBufferSize)]
|
||||
public Int32 MaxContentBufferSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_maxContentBufferSize;
|
||||
}
|
||||
set
|
||||
{
|
||||
ArgumentUtility.CheckForOutOfRange(value, nameof(value), 0, c_maxAllowedContentBufferSize);
|
||||
m_maxContentBufferSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timespan to wait before timing out a request. Defaults to 100 seconds
|
||||
/// </summary>
|
||||
public TimeSpan SendTimeout
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a hint to the server requesting that rather than getting 302 redirects as part of FedAuth flows 401 and 403 are passed through.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
public Boolean SuppressFedAuthRedirects
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User-Agent header passed along in the request,
|
||||
/// For multiple values, the order in the list is the order
|
||||
/// in which they will appear in the header
|
||||
/// </summary>
|
||||
public List<ProductInfoHeaderValue> UserAgent
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the culture is passed in the Accept-Language header
|
||||
/// </summary>
|
||||
public ICollection<CultureInfo> AcceptLanguages
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_acceptLanguages;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A unique identifier for the user session
|
||||
/// </summary>
|
||||
public Guid SessionId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End to End ID which gets propagated everywhere unchanged
|
||||
/// </summary>
|
||||
public Guid E2EId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a kind of combination between SessionId and UserAgent.
|
||||
/// If supplied, the value should be a string that uniquely identifies
|
||||
/// this application running on this particular machine.
|
||||
/// The server will then use this value
|
||||
/// to correlate user requests, even if the process restarts.
|
||||
/// </summary>
|
||||
public String AgentId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional string that is sent in the SessionId header used to group a set of operations together
|
||||
/// </summary>
|
||||
public String OperationName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional implementation used to gather client certificates
|
||||
/// for connections that require them
|
||||
/// </summary>
|
||||
public IVssClientCertificateManager ClientCertificateManager
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Optional implementation used to validate server certificate validation
|
||||
/// </summary>
|
||||
public RemoteCertificateValidationCallback ServerCertificateValidationCallback
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Optional implementation used to validate server certificate validation
|
||||
/// </summary>
|
||||
public Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> ServerCertificateValidationCallback
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Number of times to retry a request that has an ambient failure
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is only used by VssConnection, so only relevant on the client
|
||||
/// </remarks>
|
||||
[DefaultValue(c_defaultMaxRetry)]
|
||||
public Int32 MaxRetryRequest
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
protected internal virtual Boolean IsHostLocal(String hostName)
|
||||
{
|
||||
//base class always returns false. See VssClientHttpRequestSettings for override
|
||||
return false;
|
||||
}
|
||||
|
||||
protected internal virtual Boolean ApplyTo(HttpRequestMessage request)
|
||||
{
|
||||
// Make sure we only apply the settings to the request once
|
||||
if (request.Properties.ContainsKey(PropertyName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
request.Properties.Add(PropertyName, this);
|
||||
|
||||
if (this.AcceptLanguages != null && this.AcceptLanguages.Count > 0)
|
||||
{
|
||||
// An empty or null CultureInfo name will cause an ArgumentNullException in the
|
||||
// StringWithQualityHeaderValue constructor. CultureInfo.InvariantCulture is an example of
|
||||
// a CultureInfo that has an empty name.
|
||||
foreach (CultureInfo culture in this.AcceptLanguages.Where(a => !String.IsNullOrEmpty(a.Name)))
|
||||
{
|
||||
request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(culture.Name));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.UserAgent != null)
|
||||
{
|
||||
foreach (var headerVal in this.UserAgent)
|
||||
{
|
||||
if (!request.Headers.UserAgent.Contains(headerVal))
|
||||
{
|
||||
request.Headers.UserAgent.Add(headerVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.SuppressFedAuthRedirects)
|
||||
{
|
||||
request.Headers.Add(Internal.HttpHeaders.TfsFedAuthRedirect, "Suppress");
|
||||
}
|
||||
|
||||
// Record the command, if we have it. Otherwise, just record the session ID.
|
||||
if (!request.Headers.Contains(Internal.HttpHeaders.TfsSessionHeader))
|
||||
{
|
||||
if (!String.IsNullOrEmpty(this.OperationName))
|
||||
{
|
||||
request.Headers.Add(Internal.HttpHeaders.TfsSessionHeader, String.Concat(this.SessionId.ToString("D"), ", ", this.OperationName));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Add(Internal.HttpHeaders.TfsSessionHeader, this.SessionId.ToString("D"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.AgentId))
|
||||
{
|
||||
request.Headers.Add(Internal.HttpHeaders.VssAgentHeader, this.AgentId);
|
||||
}
|
||||
|
||||
#if NETSTANDARD
|
||||
// Content is being sent as chunked by default in dotnet5.4, which differs than the .net 4.5 behaviour.
|
||||
if (request.Content != null && !request.Content.Headers.ContentLength.HasValue && !request.Headers.TransferEncodingChunked.HasValue)
|
||||
{
|
||||
request.Content.Headers.ContentLength = request.Content.ReadAsByteArrayAsync().Result.Length;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the encoding used for outgoing requests.
|
||||
/// </summary>
|
||||
public static Encoding Encoding
|
||||
{
|
||||
get
|
||||
{
|
||||
return s_encoding.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property name used to reference this object.
|
||||
/// </summary>
|
||||
public const String PropertyName = "MS.VS.RequestSettings";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property name used to reference the completion option for a specific request.
|
||||
/// </summary>
|
||||
public const String HttpCompletionOptionPropertyName = "MS.VS.HttpCompletionOption";
|
||||
|
||||
/// <summary>
|
||||
/// Header to include the light weight response client option.
|
||||
/// </summary>
|
||||
public const string LightweightHeader = "lightweight";
|
||||
|
||||
/// <summary>
|
||||
/// Header to include the exclude urls client option.
|
||||
/// </summary>
|
||||
public const string ExcludeUrlsHeader = "excludeUrls";
|
||||
|
||||
private Int32 m_maxContentBufferSize;
|
||||
private ICollection<CultureInfo> m_acceptLanguages = new List<CultureInfo>();
|
||||
private static Lazy<Encoding> s_encoding = new Lazy<Encoding>(() => new UTF8Encoding(false), LazyThreadSafetyMode.PublicationOnly);
|
||||
private static readonly TimeSpan s_defaultTimeout = TimeSpan.FromSeconds(100); //default WebAPI timeout
|
||||
private const Int32 c_defaultMaxRetry = 3;
|
||||
|
||||
// We will buffer a maximum of 1024MB in the message handler
|
||||
private const Int32 c_maxAllowedContentBufferSize = 1024 * 1024 * 1024;
|
||||
|
||||
// We will buffer, by default, up to 512MB in the message handler
|
||||
private const Int32 c_defaultContentBufferSize = 1024 * 1024 * 512;
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
using GitHub.Services.Common.Diagnostics;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles automatic replay of HTTP requests when errors are encountered based on a configurable set of options.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class VssHttpRetryMessageHandler : DelegatingHandler
|
||||
{
|
||||
public VssHttpRetryMessageHandler(Int32 maxRetries)
|
||||
: this(new VssHttpRetryOptions { MaxRetries = maxRetries })
|
||||
{
|
||||
}
|
||||
|
||||
public VssHttpRetryMessageHandler(Int32 maxRetries, string clientName)
|
||||
: this(new VssHttpRetryOptions { MaxRetries = maxRetries })
|
||||
{
|
||||
m_clientName = clientName;
|
||||
}
|
||||
|
||||
public VssHttpRetryMessageHandler(VssHttpRetryOptions options)
|
||||
{
|
||||
m_retryOptions = options;
|
||||
}
|
||||
|
||||
public VssHttpRetryMessageHandler(
|
||||
VssHttpRetryOptions options,
|
||||
HttpMessageHandler innerHandler)
|
||||
: base(innerHandler)
|
||||
{
|
||||
m_retryOptions = options;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Int32 attempt = 1;
|
||||
HttpResponseMessage response = null;
|
||||
HttpRequestException exception = null;
|
||||
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
||||
|
||||
// Allow overriding default retry options per request
|
||||
VssHttpRetryOptions retryOptions = m_retryOptions;
|
||||
object retryOptionsObject;
|
||||
if (request.Properties.TryGetValue(HttpRetryOptionsKey, out retryOptionsObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
||||
{
|
||||
// Fallback to default options if object of unexpected type was passed
|
||||
retryOptions = retryOptionsObject as VssHttpRetryOptions ?? m_retryOptions;
|
||||
}
|
||||
|
||||
TimeSpan minBackoff = retryOptions.MinBackoff;
|
||||
Int32 maxAttempts = retryOptions.MaxRetries + 1;
|
||||
|
||||
IVssHttpRetryInfo retryInfo = null;
|
||||
object retryInfoObject;
|
||||
if (request.Properties.TryGetValue(HttpRetryInfoKey, out retryInfoObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
||||
{
|
||||
retryInfo = retryInfoObject as IVssHttpRetryInfo;
|
||||
}
|
||||
|
||||
if (IsLowPriority(request))
|
||||
{
|
||||
// Increase the backoff and retry count, low priority requests can be retried many times if the server is busy.
|
||||
minBackoff = TimeSpan.FromSeconds(minBackoff.TotalSeconds * 2);
|
||||
maxAttempts = maxAttempts * 10;
|
||||
}
|
||||
|
||||
TimeSpan backoff = minBackoff;
|
||||
|
||||
while (attempt <= maxAttempts)
|
||||
{
|
||||
// Reset the exception so we don't have a lingering variable
|
||||
exception = null;
|
||||
|
||||
Boolean canRetry = false;
|
||||
SocketError? socketError = null;
|
||||
HttpStatusCode? statusCode = null;
|
||||
WebExceptionStatus? webExceptionStatus = null;
|
||||
WinHttpErrorCode? winHttpErrorCode = null;
|
||||
CurlErrorCode? curlErrorCode = null;
|
||||
string afdRefInfo = null;
|
||||
try
|
||||
{
|
||||
if (attempt == 1)
|
||||
{
|
||||
retryInfo?.InitialAttempt(request);
|
||||
}
|
||||
|
||||
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (attempt > 1)
|
||||
{
|
||||
TraceHttpRequestSucceededWithRetry(traceActivity, response, attempt);
|
||||
}
|
||||
|
||||
// Verify the response is successful or the status code is one that may be retried.
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
statusCode = response.StatusCode;
|
||||
afdRefInfo = response.Headers.TryGetValues(HttpHeaders.AfdResponseRef, out var headers) ? headers.First() : null;
|
||||
canRetry = m_retryOptions.IsRetryableResponse(response);
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
exception = ex;
|
||||
canRetry = VssNetworkHelper.IsTransientNetworkException(exception, m_retryOptions, out statusCode, out webExceptionStatus, out socketError, out winHttpErrorCode, out curlErrorCode);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts && canRetry)
|
||||
{
|
||||
backoff = BackoffTimerHelper.GetExponentialBackoff(attempt, minBackoff, m_retryOptions.MaxBackoff, m_retryOptions.BackoffCoefficient);
|
||||
retryInfo?.Retry(backoff);
|
||||
TraceHttpRequestRetrying(traceActivity, request, attempt, backoff, statusCode, webExceptionStatus, socketError, winHttpErrorCode, curlErrorCode, afdRefInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attempt < maxAttempts)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
TraceHttpRequestFailed(traceActivity, request, statusCode != null ? statusCode.Value : (HttpStatusCode)0, afdRefInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceHttpRequestFailed(traceActivity, request, exception);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceHttpRequestFailedMaxAttempts(traceActivity, request, attempt, statusCode, webExceptionStatus, socketError, winHttpErrorCode, curlErrorCode, afdRefInfo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Make sure to dispose of this so we don't keep the connection open
|
||||
if (response != null)
|
||||
{
|
||||
response.Dispose();
|
||||
}
|
||||
|
||||
attempt++;
|
||||
TraceRaw(request, 100011, TraceLevel.Error,
|
||||
"{{ \"Client\":\"{0}\", \"Endpoint\":\"{1}\", \"Attempt\":{2}, \"MaxAttempts\":{3}, \"Backoff\":{4} }}",
|
||||
m_clientName,
|
||||
request.RequestUri.Host,
|
||||
attempt,
|
||||
maxAttempts,
|
||||
backoff.TotalMilliseconds);
|
||||
await Task.Delay(backoff, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected virtual void TraceRaw(HttpRequestMessage request, int tracepoint, TraceLevel level, string message, params object[] args)
|
||||
{
|
||||
// implement in Server so retries are recorded in ProductTrace
|
||||
}
|
||||
|
||||
protected virtual void TraceHttpRequestFailed(VssTraceActivity activity, HttpRequestMessage request, HttpStatusCode statusCode, string afdRefInfo)
|
||||
{
|
||||
VssHttpEventSource.Log.HttpRequestFailed(activity, request, statusCode, afdRefInfo);
|
||||
}
|
||||
|
||||
protected virtual void TraceHttpRequestFailed(VssTraceActivity activity, HttpRequestMessage request, Exception exception)
|
||||
{
|
||||
VssHttpEventSource.Log.HttpRequestFailed(activity, request, exception);
|
||||
}
|
||||
|
||||
protected virtual void TraceHttpRequestFailedMaxAttempts(VssTraceActivity activity, HttpRequestMessage request, Int32 attempt, HttpStatusCode? httpStatusCode, WebExceptionStatus? webExceptionStatus, SocketError? socketErrorCode, WinHttpErrorCode? winHttpErrorCode, CurlErrorCode? curlErrorCode, string afdRefInfo)
|
||||
{
|
||||
VssHttpEventSource.Log.HttpRequestFailedMaxAttempts(activity, request, attempt, httpStatusCode, webExceptionStatus, socketErrorCode, winHttpErrorCode, curlErrorCode, afdRefInfo);
|
||||
}
|
||||
|
||||
protected virtual void TraceHttpRequestSucceededWithRetry(VssTraceActivity activity, HttpResponseMessage response, Int32 attempt)
|
||||
{
|
||||
VssHttpEventSource.Log.HttpRequestSucceededWithRetry(activity, response, attempt);
|
||||
}
|
||||
|
||||
protected virtual void TraceHttpRequestRetrying(VssTraceActivity activity, HttpRequestMessage request, Int32 attempt, TimeSpan backoffDuration, HttpStatusCode? httpStatusCode, WebExceptionStatus? webExceptionStatus, SocketError? socketErrorCode, WinHttpErrorCode? winHttpErrorCode, CurlErrorCode? curlErrorCode, string afdRefInfo)
|
||||
{
|
||||
VssHttpEventSource.Log.HttpRequestRetrying(activity, request, attempt, backoffDuration, httpStatusCode, webExceptionStatus, socketErrorCode, winHttpErrorCode, curlErrorCode, afdRefInfo);
|
||||
}
|
||||
|
||||
private static bool IsLowPriority(HttpRequestMessage request)
|
||||
{
|
||||
bool isLowPriority = false;
|
||||
|
||||
IEnumerable<string> headers;
|
||||
|
||||
if (request.Headers.TryGetValues(HttpHeaders.VssRequestPriority, out headers) && headers != null)
|
||||
{
|
||||
string header = headers.FirstOrDefault();
|
||||
isLowPriority = string.Equals(header, "Low", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return isLowPriority;
|
||||
}
|
||||
|
||||
private VssHttpRetryOptions m_retryOptions;
|
||||
public const string HttpRetryInfoKey = "HttpRetryInfo";
|
||||
public const string HttpRetryOptionsKey = "VssHttpRetryOptions";
|
||||
private string m_clientName = "";
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
using GitHub.Services.Common.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the options used for configuring the retry policy.
|
||||
/// </summary>
|
||||
public class VssHttpRetryOptions
|
||||
{
|
||||
public VssHttpRetryOptions()
|
||||
: this (new VssHttpRetryableStatusCodeFilter[] { s_hostShutdownFilter } )
|
||||
{
|
||||
}
|
||||
|
||||
public VssHttpRetryOptions(IEnumerable<VssHttpRetryableStatusCodeFilter> filters)
|
||||
{
|
||||
this.BackoffCoefficient = s_backoffCoefficient;
|
||||
this.MinBackoff = s_minBackoff;
|
||||
this.MaxBackoff = s_maxBackoff;
|
||||
this.MaxRetries = 5;
|
||||
this.RetryableStatusCodes = new HashSet<HttpStatusCode>
|
||||
{
|
||||
HttpStatusCode.BadGateway,
|
||||
HttpStatusCode.GatewayTimeout,
|
||||
HttpStatusCode.ServiceUnavailable,
|
||||
};
|
||||
|
||||
this.m_retryFilters = new HashSet<VssHttpRetryableStatusCodeFilter>(filters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a singleton read-only instance of the default settings.
|
||||
/// </summary>
|
||||
public static VssHttpRetryOptions Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return s_defaultOptions.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the coefficient which exponentially increases the backoff starting at <see cref="MinBackoff" />.
|
||||
/// </summary>
|
||||
public TimeSpan BackoffCoefficient
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_backoffCoefficient;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfReadonly();
|
||||
m_backoffCoefficient = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum backoff interval to be used.
|
||||
/// </summary>
|
||||
public TimeSpan MinBackoff
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_minBackoff;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfReadonly();
|
||||
m_minBackoff = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum backoff interval to be used.
|
||||
/// </summary>
|
||||
public TimeSpan MaxBackoff
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_maxBackoff;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfReadonly();
|
||||
m_maxBackoff = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of retries allowed.
|
||||
/// </summary>
|
||||
public Int32 MaxRetries
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_maxRetries;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfReadonly();
|
||||
m_maxRetries = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a set of HTTP status codes which should be retried.
|
||||
/// </summary>
|
||||
public ICollection<HttpStatusCode> RetryableStatusCodes
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_retryableStatusCodes;
|
||||
}
|
||||
private set
|
||||
{
|
||||
ThrowIfReadonly();
|
||||
m_retryableStatusCodes = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How to verify that the response can be retried.
|
||||
/// </summary>
|
||||
/// <param name="response">Response message from a request</param>
|
||||
/// <returns>True if the request can be retried, false otherwise.</returns>
|
||||
public Boolean IsRetryableResponse(HttpResponseMessage response)
|
||||
{
|
||||
if (m_retryableStatusCodes.Contains(response.StatusCode))
|
||||
{
|
||||
foreach (VssHttpRetryableStatusCodeFilter filter in m_retryFilters)
|
||||
{
|
||||
if (filter(response))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that no further modifications may be made to the retry options.
|
||||
/// </summary>
|
||||
/// <returns>A read-only instance of the retry options</returns>
|
||||
public VssHttpRetryOptions MakeReadonly()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref m_isReadOnly, 1, 0) == 0)
|
||||
{
|
||||
m_retryableStatusCodes = new ReadOnlyCollection<HttpStatusCode>(m_retryableStatusCodes.ToList());
|
||||
m_retryFilters = new ReadOnlyCollection<VssHttpRetryableStatusCodeFilter>(m_retryFilters.ToList());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Throws an InvalidOperationException if this is marked as ReadOnly.
|
||||
/// </summary>
|
||||
private void ThrowIfReadonly()
|
||||
{
|
||||
if (m_isReadOnly > 0)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns false if we should continue retrying based on the response, and true if we should not, even though
|
||||
/// this is technically a retryable status code.
|
||||
/// </summary>
|
||||
/// <param name="response">The response to check if we should retry the request.</param>
|
||||
/// <returns>False if we should retry, true if we should not based on the response.</returns>
|
||||
public delegate Boolean VssHttpRetryableStatusCodeFilter(HttpResponseMessage response);
|
||||
|
||||
private Int32 m_isReadOnly;
|
||||
private Int32 m_maxRetries;
|
||||
private TimeSpan m_minBackoff;
|
||||
private TimeSpan m_maxBackoff;
|
||||
private TimeSpan m_backoffCoefficient;
|
||||
private ICollection<HttpStatusCode> m_retryableStatusCodes;
|
||||
private ICollection<VssHttpRetryableStatusCodeFilter> m_retryFilters;
|
||||
private static TimeSpan s_minBackoff = TimeSpan.FromSeconds(10);
|
||||
private static TimeSpan s_maxBackoff = TimeSpan.FromMinutes(10);
|
||||
private static TimeSpan s_backoffCoefficient = TimeSpan.FromSeconds(1);
|
||||
private static Lazy<VssHttpRetryOptions> s_defaultOptions = new Lazy<VssHttpRetryOptions>(() => new VssHttpRetryOptions().MakeReadonly());
|
||||
private static VssHttpRetryableStatusCodeFilter s_hostShutdownFilter = new VssHttpRetryableStatusCodeFilter(response => response.Headers.Contains(HttpHeaders.VssHostOfflineError));
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class VssNetworkHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Heuristic used to determine whether an exception is a transient network
|
||||
/// failure that should be retried.
|
||||
/// </summary>
|
||||
public static bool IsTransientNetworkException(Exception ex)
|
||||
{
|
||||
return IsTransientNetworkException(ex, new VssHttpRetryOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Heuristic used to determine whether an exception is a transient network
|
||||
/// failure that should be retried.
|
||||
/// </summary>
|
||||
public static bool IsTransientNetworkException(
|
||||
Exception ex,
|
||||
VssHttpRetryOptions options)
|
||||
{
|
||||
HttpStatusCode? httpStatusCode;
|
||||
WebExceptionStatus? webExceptionStatus;
|
||||
SocketError? socketErrorCode;
|
||||
WinHttpErrorCode? winHttpErrorCode;
|
||||
CurlErrorCode? curlErrorCode;
|
||||
return IsTransientNetworkException(ex, options, out httpStatusCode, out webExceptionStatus, out socketErrorCode, out winHttpErrorCode, out curlErrorCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Heuristic used to determine whether an exception is a transient network
|
||||
/// failure that should be retried.
|
||||
/// </summary>
|
||||
public static bool IsTransientNetworkException(
|
||||
Exception ex,
|
||||
out HttpStatusCode? httpStatusCode,
|
||||
out WebExceptionStatus? webExceptionStatus,
|
||||
out SocketError? socketErrorCode,
|
||||
out WinHttpErrorCode? winHttpErrorCode,
|
||||
out CurlErrorCode? curlErrorCode)
|
||||
{
|
||||
return IsTransientNetworkException(ex, VssHttpRetryOptions.Default, out httpStatusCode, out webExceptionStatus, out socketErrorCode, out winHttpErrorCode, out curlErrorCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Heuristic used to determine whether an exception is a transient network
|
||||
/// failure that should be retried.
|
||||
/// </summary>
|
||||
public static bool IsTransientNetworkException(
|
||||
Exception ex,
|
||||
VssHttpRetryOptions options,
|
||||
out HttpStatusCode? httpStatusCode,
|
||||
out WebExceptionStatus? webExceptionStatus,
|
||||
out SocketError? socketErrorCode,
|
||||
out WinHttpErrorCode? winHttpErrorCode,
|
||||
out CurlErrorCode? curlErrorCode)
|
||||
{
|
||||
httpStatusCode = null;
|
||||
webExceptionStatus = null;
|
||||
socketErrorCode = null;
|
||||
winHttpErrorCode = null;
|
||||
curlErrorCode = null;
|
||||
|
||||
while (ex != null)
|
||||
{
|
||||
if (IsTransientNetworkExceptionHelper(ex, options, out httpStatusCode, out webExceptionStatus, out socketErrorCode, out winHttpErrorCode, out curlErrorCode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ex = ex.InnerException;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper which checks a particular Exception instance (non-recursive).
|
||||
/// </summary>
|
||||
private static bool IsTransientNetworkExceptionHelper(
|
||||
Exception ex,
|
||||
VssHttpRetryOptions options,
|
||||
out HttpStatusCode? httpStatusCode,
|
||||
out WebExceptionStatus? webExceptionStatus,
|
||||
out SocketError? socketErrorCode,
|
||||
out WinHttpErrorCode? winHttpErrorCode,
|
||||
out CurlErrorCode? curlErrorCode)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(ex, "ex");
|
||||
|
||||
httpStatusCode = null;
|
||||
webExceptionStatus = null;
|
||||
socketErrorCode = null;
|
||||
winHttpErrorCode = null;
|
||||
curlErrorCode = null;
|
||||
|
||||
if (ex is WebException)
|
||||
{
|
||||
WebException webEx = (WebException)ex;
|
||||
|
||||
if (webEx.Response != null && webEx.Response is HttpWebResponse)
|
||||
{
|
||||
var httpResponse = (HttpWebResponse)webEx.Response;
|
||||
httpStatusCode = httpResponse.StatusCode;
|
||||
|
||||
// If the options include this status code as a retryable error then we report the exception
|
||||
// as transient to the caller
|
||||
if (options.RetryableStatusCodes.Contains(httpResponse.StatusCode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
webExceptionStatus = webEx.Status;
|
||||
|
||||
if (webEx.Status == WebExceptionStatus.ConnectFailure ||
|
||||
webEx.Status == WebExceptionStatus.ConnectionClosed ||
|
||||
webEx.Status == WebExceptionStatus.KeepAliveFailure ||
|
||||
webEx.Status == WebExceptionStatus.NameResolutionFailure ||
|
||||
webEx.Status == WebExceptionStatus.ReceiveFailure ||
|
||||
webEx.Status == WebExceptionStatus.SendFailure ||
|
||||
webEx.Status == WebExceptionStatus.Timeout)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (ex is SocketException)
|
||||
{
|
||||
SocketException sockEx = (SocketException)ex;
|
||||
|
||||
socketErrorCode = sockEx.SocketErrorCode;
|
||||
|
||||
if (sockEx.SocketErrorCode == SocketError.Interrupted ||
|
||||
sockEx.SocketErrorCode == SocketError.NetworkDown ||
|
||||
sockEx.SocketErrorCode == SocketError.NetworkUnreachable ||
|
||||
sockEx.SocketErrorCode == SocketError.NetworkReset ||
|
||||
sockEx.SocketErrorCode == SocketError.ConnectionAborted ||
|
||||
sockEx.SocketErrorCode == SocketError.ConnectionReset ||
|
||||
sockEx.SocketErrorCode == SocketError.TimedOut ||
|
||||
sockEx.SocketErrorCode == SocketError.HostDown ||
|
||||
sockEx.SocketErrorCode == SocketError.HostUnreachable ||
|
||||
sockEx.SocketErrorCode == SocketError.TryAgain)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (ex is Win32Exception) // WinHttpException when use WinHttp (dotnet core)
|
||||
{
|
||||
Win32Exception winHttpEx = (Win32Exception)ex;
|
||||
|
||||
Int32 errorCode = winHttpEx.NativeErrorCode;
|
||||
if (errorCode > (Int32)WinHttpErrorCode.WINHTTP_ERROR_BASE &&
|
||||
errorCode <= (Int32)WinHttpErrorCode.WINHTTP_ERROR_LAST)
|
||||
{
|
||||
winHttpErrorCode = (WinHttpErrorCode)errorCode;
|
||||
|
||||
if (winHttpErrorCode == WinHttpErrorCode.ERROR_WINHTTP_CANNOT_CONNECT ||
|
||||
winHttpErrorCode == WinHttpErrorCode.ERROR_WINHTTP_CONNECTION_ERROR ||
|
||||
winHttpErrorCode == WinHttpErrorCode.ERROR_WINHTTP_INTERNAL_ERROR ||
|
||||
winHttpErrorCode == WinHttpErrorCode.ERROR_WINHTTP_NAME_NOT_RESOLVED ||
|
||||
winHttpErrorCode == WinHttpErrorCode.ERROR_WINHTTP_TIMEOUT)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ex is IOException)
|
||||
{
|
||||
if (null != ex.InnerException &&
|
||||
ex.InnerException is Win32Exception)
|
||||
{
|
||||
String stackTrace = ex.StackTrace;
|
||||
|
||||
if (null != stackTrace &&
|
||||
stackTrace.IndexOf("System.Net.Security._SslStream.StartWriting(", StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
// HACK: There is an underlying HRESULT code for this error which is not set on the exception which
|
||||
// bubbles from the underlying stack. The top of the stack trace will be in the _SslStream class
|
||||
// and will have an exception chain of HttpRequestException -> IOException -> Win32Exception.
|
||||
|
||||
// Check for SEC_E_CONTEXT_EXPIRED as this occurs at random in the underlying stack. Retrying the
|
||||
// request should get a new connection and work correctly, so we ignore this particular error.
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ex.GetType().Name == "CurlException") // CurlException when use libcurl (dotnet core)
|
||||
{
|
||||
// Valid curl error code should in range (0, 93]
|
||||
if (ex.HResult > 0 && ex.HResult < 94)
|
||||
{
|
||||
curlErrorCode = (CurlErrorCode)ex.HResult;
|
||||
if (curlErrorCode == CurlErrorCode.CURLE_COULDNT_RESOLVE_PROXY ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_COULDNT_RESOLVE_HOST ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_COULDNT_CONNECT ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_HTTP2 ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_PARTIAL_FILE ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_WRITE_ERROR ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_UPLOAD_FAILED ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_READ_ERROR ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_OPERATION_TIMEDOUT ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_INTERFACE_FAILED ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_GOT_NOTHING ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_SEND_ERROR ||
|
||||
curlErrorCode == CurlErrorCode.CURLE_RECV_ERROR)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if !NETSTANDARD
|
||||
else if (ex is System.Data.Services.Client.DataServiceRequestException ||
|
||||
ex is System.Data.Services.Client.DataServiceClientException)
|
||||
{
|
||||
// WCF exceptions
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HttpStatusCode which represents a throttling error.
|
||||
/// </summary>
|
||||
public const HttpStatusCode TooManyRequests = (HttpStatusCode)429;
|
||||
}
|
||||
}
|
||||
@@ -1,498 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Note: This is our perfview event source which is used for performance troubleshooting
|
||||
/// Sadly, EventSource has few overloads so anything that isn't in http://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.writeevent.aspx
|
||||
/// will cause a bunch of allocations - so we use manual interop for anything non trivial.
|
||||
///
|
||||
/// </summary>
|
||||
public sealed class VssPerformanceEventSource : EventSource
|
||||
{
|
||||
public static VssPerformanceEventSource Log = new VssPerformanceEventSource();
|
||||
|
||||
#region WriteEvent PInvoke Overrides
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u1, Guid u2, string st)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st = st ?? String.Empty;
|
||||
const int parameters = 3;
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[0].DataPointer = (IntPtr)(&u1);
|
||||
dataDesc[1].Size = sizeof(Guid);
|
||||
dataDesc[1].DataPointer = (IntPtr)(&u2);
|
||||
dataDesc[2].Size = (st.Length + 1) * sizeof(char);
|
||||
|
||||
fixed (char* pcst = st)
|
||||
{
|
||||
dataDesc[2].DataPointer = (IntPtr)pcst;
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u1, Guid u2, string st, long duration)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st = st ?? String.Empty;
|
||||
const int parameters = 4;
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[0].DataPointer = (IntPtr)(&u1);
|
||||
dataDesc[1].Size = sizeof(Guid);
|
||||
dataDesc[1].DataPointer = (IntPtr)(&u2);
|
||||
dataDesc[2].Size = (st.Length + 1) * sizeof(char);
|
||||
dataDesc[3].Size = sizeof(long);
|
||||
dataDesc[3].DataPointer = (IntPtr)(&duration);
|
||||
|
||||
fixed (char* pcst = st)
|
||||
{
|
||||
dataDesc[2].DataPointer = (IntPtr)pcst;
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u, string st)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st = st ?? String.Empty;
|
||||
const int parameters = 2;
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].DataPointer = (IntPtr)(&u);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].Size = (st.Length + 1) * sizeof(char);
|
||||
|
||||
fixed (char* pcSt = st)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcSt);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u, string st, long duration)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st = st ?? String.Empty;
|
||||
const int parameters = 3;
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].DataPointer = (IntPtr)(&u);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].Size = (st.Length + 1) * sizeof(char);
|
||||
dataDesc[2].Size = sizeof(long);
|
||||
dataDesc[2].DataPointer = (IntPtr)(&duration);
|
||||
|
||||
fixed (char* pcSt = st)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcSt);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
EventData dataDesc = new EventData(); // this is a struct so no allocation here
|
||||
|
||||
dataDesc.DataPointer = (IntPtr)(&u);
|
||||
dataDesc.Size = sizeof(Guid);
|
||||
WriteEventCore(eventId, 1, &dataDesc);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u, long duration)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
const int parameters = 2;
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].DataPointer = (IntPtr)(&u);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].DataPointer = (IntPtr)(&duration);
|
||||
dataDesc[1].Size = sizeof(long);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u1, string st1, DateTime dt1, DateTime dt2, Guid u2) // Guid uniqueIdentifier, string name, string validFrom, string validTo, Guid contextId
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st1 = st1 ?? String.Empty;
|
||||
long ft1 = dt1.ToFileTimeUtc();
|
||||
long ft2 = dt2.ToFileTimeUtc();
|
||||
|
||||
const int parameters = 5;
|
||||
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
dataDesc[0].DataPointer = (IntPtr)(&u1);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].Size = (st1.Length + 1) * sizeof(char);
|
||||
dataDesc[2].DataPointer = (IntPtr)(&ft1);
|
||||
dataDesc[2].Size = sizeof(long);
|
||||
dataDesc[3].DataPointer = (IntPtr)(&ft2);
|
||||
dataDesc[3].Size = sizeof(long);
|
||||
dataDesc[4].DataPointer = (IntPtr)(&u2);
|
||||
dataDesc[4].Size = sizeof(Guid);
|
||||
|
||||
fixed (char* pcst1 = st1)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcst1);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid u1, string st1, string st2, string st3, Guid u2, long duration) // Guid uniqueIdentifier, string name, string validFrom, string validTo, Guid contextId
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st1 = st1 ?? String.Empty;
|
||||
st2 = st2 ?? String.Empty;
|
||||
st3 = st3 ?? String.Empty;
|
||||
|
||||
const int parameters = 6;
|
||||
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
dataDesc[0].DataPointer = (IntPtr)(&u1);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].Size = (st1.Length + 1) * sizeof(char);
|
||||
dataDesc[2].Size = (st2.Length + 1) * sizeof(char);
|
||||
dataDesc[3].Size = (st3.Length + 1) * sizeof(char);
|
||||
dataDesc[4].DataPointer = (IntPtr)(&u2);
|
||||
dataDesc[4].Size = sizeof(Guid);
|
||||
dataDesc[5].DataPointer = (IntPtr)(&duration);
|
||||
dataDesc[5].Size = sizeof(long);
|
||||
|
||||
fixed (char* pcst1 = st1, pcst2 = st2, pcst3 = st3)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcst1);
|
||||
dataDesc[2].DataPointer = (IntPtr)(pcst2);
|
||||
dataDesc[3].DataPointer = (IntPtr)(pcst3);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid uniqueIdentifier, string st1, string st2, string st3)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st1 = st1 ?? String.Empty;
|
||||
st2 = st2 ?? String.Empty;
|
||||
st3 = st3 ?? String.Empty;
|
||||
|
||||
const int parameters = 4;
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].DataPointer = (IntPtr)(&uniqueIdentifier);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].Size = (st1.Length + 1) * sizeof(char);
|
||||
dataDesc[2].Size = (st2.Length + 1) * sizeof(char);
|
||||
dataDesc[3].Size = (st3.Length + 1) * sizeof(char);
|
||||
|
||||
fixed (char* pcst1 = st1, pcst2 = st2, pcst3 = st3)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcst1);
|
||||
dataDesc[2].DataPointer = (IntPtr)(pcst2);
|
||||
dataDesc[3].DataPointer = (IntPtr)(pcst3);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid uniqueIdentifier, string st1, string st2, string st3, long duration)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st1 = st1 ?? String.Empty;
|
||||
st2 = st2 ?? String.Empty;
|
||||
st3 = st3 ?? String.Empty;
|
||||
|
||||
const int parameters = 5;
|
||||
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].DataPointer = (IntPtr)(&uniqueIdentifier);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
|
||||
dataDesc[1].Size = (st1.Length + 1) * sizeof(char);
|
||||
dataDesc[2].Size = (st2.Length + 1) * sizeof(char);
|
||||
dataDesc[3].Size = (st3.Length + 1) * sizeof(char);
|
||||
|
||||
dataDesc[4].DataPointer = (IntPtr)(&duration);
|
||||
dataDesc[4].Size = sizeof(long);
|
||||
|
||||
fixed (char* pcst1 = st1, pcst2 = st2, pcst3 = st3)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcst1);
|
||||
dataDesc[2].DataPointer = (IntPtr)(pcst2);
|
||||
dataDesc[3].DataPointer = (IntPtr)(pcst3);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid uniqueIdentifier, string st1, string st2)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st1 = st1 ?? String.Empty;
|
||||
st2 = st2 ?? String.Empty;
|
||||
|
||||
const int parameters = 3;
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].DataPointer = (IntPtr)(&uniqueIdentifier);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].Size = (st1.Length + 1) * sizeof(char);
|
||||
dataDesc[2].Size = (st2.Length + 1) * sizeof(char);
|
||||
|
||||
fixed (char* pcst1 = st1, pcst2 = st2)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcst1);
|
||||
dataDesc[2].DataPointer = (IntPtr)(pcst2);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, Guid uniqueIdentifier, string st1, string st2, long duration)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
st1 = st1 ?? String.Empty;
|
||||
st2 = st2 ?? String.Empty;
|
||||
|
||||
const int parameters = 4;
|
||||
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].DataPointer = (IntPtr)(&uniqueIdentifier);
|
||||
dataDesc[0].Size = sizeof(Guid);
|
||||
dataDesc[1].Size = (st1.Length + 1) * sizeof(char);
|
||||
dataDesc[2].Size = (st2.Length + 1) * sizeof(char);
|
||||
dataDesc[3].DataPointer = (IntPtr)(&duration);
|
||||
dataDesc[3].Size = sizeof(long);
|
||||
|
||||
fixed (char* pcst1 = st1, pcst2 = st2)
|
||||
{
|
||||
dataDesc[1].DataPointer = (IntPtr)(pcst1);
|
||||
dataDesc[2].DataPointer = (IntPtr)(pcst2);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public unsafe void WriteEvent(int eventId, string st, int i1, long duration)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
const int parameters = 3;
|
||||
st = st ?? String.Empty;
|
||||
|
||||
EventData* dataDesc = stackalloc EventData[parameters];
|
||||
|
||||
dataDesc[0].Size = (st.Length + 1) * sizeof(char);
|
||||
dataDesc[1].DataPointer = (IntPtr)(&i1);
|
||||
dataDesc[1].Size = sizeof(Int32);
|
||||
dataDesc[2].DataPointer = (IntPtr)(&duration);
|
||||
dataDesc[2].Size = sizeof(int);
|
||||
|
||||
fixed (char* pcst = st)
|
||||
{
|
||||
dataDesc[0].DataPointer = (IntPtr)(pcst);
|
||||
|
||||
WriteEventCore(eventId, parameters, dataDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void MethodStart(Guid uniqueIdentifier, Guid hostId, string methodName)
|
||||
{
|
||||
WriteEvent(1, uniqueIdentifier, hostId, methodName);
|
||||
}
|
||||
|
||||
public void MethodStop(Guid uniqueIdentifier, Guid hostId, string methodName, long duration)
|
||||
{
|
||||
WriteEvent(2, uniqueIdentifier, hostId, methodName, duration);
|
||||
}
|
||||
|
||||
public void NotificationCallbackStart(Guid hostId, string callback)
|
||||
{
|
||||
WriteEvent(3, hostId, callback);
|
||||
}
|
||||
|
||||
public void NotificationCallbackStop(Guid hostId, string callback, long duration)
|
||||
{
|
||||
WriteEvent(4, hostId, callback, duration);
|
||||
}
|
||||
|
||||
public void TaskCallbackStart(Guid hostId, string callback)
|
||||
{
|
||||
WriteEvent(5, hostId, callback);
|
||||
}
|
||||
|
||||
public void TaskCallbackStop(Guid hostId, string callback, long duration)
|
||||
{
|
||||
WriteEvent(6, hostId, callback, duration);
|
||||
}
|
||||
|
||||
public void StopHostTaskStart(Guid hostId)
|
||||
{
|
||||
WriteEvent(7, hostId);
|
||||
}
|
||||
|
||||
public void StopHostTaskStop(Guid hostId, long duration)
|
||||
{
|
||||
WriteEvent(8, hostId, duration);
|
||||
}
|
||||
|
||||
public void RefreshSecurityTokenStart(Guid uniqueIdentifier, string name)
|
||||
{
|
||||
WriteEvent(9, uniqueIdentifier, name);
|
||||
}
|
||||
|
||||
public void RefreshSecurityTokenStop(Guid uniqueIdentifier, string name, DateTime validFrom, DateTime validTo, Guid contextId, long duration)
|
||||
{
|
||||
WriteEvent(10, uniqueIdentifier, name, validFrom, validTo, contextId, duration);
|
||||
}
|
||||
|
||||
public void SQLStart(Guid uniqueIdentifier, string query, string server, string databaseName)
|
||||
{
|
||||
WriteEvent(11, uniqueIdentifier, query, server, databaseName);
|
||||
}
|
||||
|
||||
public void SQLStop(Guid uniqueIdentifier, string query, string server, string databaseName, long duration)
|
||||
{
|
||||
WriteEvent(12, uniqueIdentifier, query, server, databaseName, duration);
|
||||
}
|
||||
|
||||
public void RESTStart(Guid uniqueIdentifier, string message)
|
||||
{
|
||||
WriteEvent(13, uniqueIdentifier, message);
|
||||
}
|
||||
|
||||
public void RESTStop(Guid uniqueIdentifier, Guid originalActivityId, string message, long duration)
|
||||
{
|
||||
WriteEvent(14, uniqueIdentifier, originalActivityId, message, duration);
|
||||
}
|
||||
|
||||
public void WindowsAzureStorageStart(Guid uniqueIdentifier, string accountName, string methodName)
|
||||
{
|
||||
WriteEvent(15, uniqueIdentifier, accountName, methodName);
|
||||
}
|
||||
|
||||
public void WindowsAzureStorageStop(Guid uniqueIdentifier, string accountName, string methodName, long duration)
|
||||
{
|
||||
WriteEvent(16, uniqueIdentifier, accountName, methodName, duration);
|
||||
}
|
||||
|
||||
public void LoadHostStart(Guid hostId)
|
||||
{
|
||||
WriteEvent(17, hostId);
|
||||
}
|
||||
|
||||
public void LoadHostStop(Guid hostId, long duration)
|
||||
{
|
||||
WriteEvent(18, hostId, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is intentionally called Begin, not Start(), since it's a recursive event
|
||||
/// Service Profiler cannot deal with recursive events unless you have the
|
||||
/// [Event(EventActivityOptions.Recursive)] however that is not supported in 4.5 currently
|
||||
/// </summary>
|
||||
/// <param name="uniqueIdentifier"></param>
|
||||
/// <param name="hostId"></param>
|
||||
/// <param name="serviceType"></param>
|
||||
public void CreateServiceInstanceBegin(Guid uniqueIdentifier, Guid hostId, string serviceType)
|
||||
{
|
||||
WriteEvent(19, uniqueIdentifier, hostId, serviceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is intentionally called Begin, not Start(), since it's a recursive event
|
||||
/// Service Profiler cannot deal with recursive events unless you have the
|
||||
/// [Event(EventActivityOptions.Recursive)] however that is not supported in 4.5 currently
|
||||
/// </summary>
|
||||
/// <param name="uniqueIdentifier"></param>
|
||||
/// <param name="hostId"></param>
|
||||
/// <param name="serviceType"></param>
|
||||
/// <param name="duration"></param>
|
||||
public void CreateServiceInstanceEnd(Guid uniqueIdentifier, Guid hostId, string serviceType, long duration)
|
||||
{
|
||||
WriteEvent(20, uniqueIdentifier, hostId, serviceType, duration);
|
||||
}
|
||||
|
||||
public void DetectedLockReentryViolation(string lockName)
|
||||
{
|
||||
WriteEvent(21, lockName);
|
||||
}
|
||||
|
||||
public void DetectedLockUsageViolation(string lockName, string locksHeld)
|
||||
{
|
||||
WriteEvent(22, lockName, locksHeld);
|
||||
}
|
||||
|
||||
public void RedisStart(Guid uniqueIdentifier, string operation, string ciArea, string cacheArea)
|
||||
{
|
||||
WriteEvent(23, uniqueIdentifier, operation, ciArea, cacheArea);
|
||||
}
|
||||
|
||||
public void RedisStop(Guid uniqueIdentifier, string operation, string ciArea, string cacheArea, long duration)
|
||||
{
|
||||
WriteEvent(24, uniqueIdentifier, operation, ciArea, cacheArea, duration);
|
||||
}
|
||||
|
||||
public void MessageBusSendBatchStart(Guid uniqueIdentifier, string messageBusName, int numberOfMessages)
|
||||
{
|
||||
WriteEvent(25, uniqueIdentifier, messageBusName, numberOfMessages);
|
||||
}
|
||||
|
||||
public void MessageBusSendBatchStop(Guid uniqueIdentifier, string messageBusName, int numberOfMessages, long duration)
|
||||
{
|
||||
WriteEvent(26, uniqueIdentifier, messageBusName, numberOfMessages, duration);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
[CompilerGenerated]
|
||||
internal static class GeneratedVersionInfo
|
||||
{
|
||||
// Legacy values which preserve semantics from prior to the Assembly / File version split.
|
||||
// See Toolsets\Version\Version.props for more details.
|
||||
public const String MajorVersion = "16";
|
||||
public const String MinorVersion = "0";
|
||||
public const String BuildVersion = "65000";
|
||||
public const String PatchVersion = "0";
|
||||
public const String ProductVersion = MajorVersion + "." + MinorVersion;
|
||||
|
||||
// Assembly version (i.e. strong name)
|
||||
public const String AssemblyMajorVersion = "16";
|
||||
public const String AssemblyMinorVersion = "0";
|
||||
public const String AssemblyBuildVersion = "0";
|
||||
public const String AssemblyPatchVersion = "0";
|
||||
public const String AssemblyVersion = AssemblyMajorVersion + "." + AssemblyMinorVersion + "." + AssemblyBuildVersion + "." + AssemblyPatchVersion;
|
||||
|
||||
// File version
|
||||
public const String FileMajorVersion = "16";
|
||||
public const String FileMinorVersion = "255";
|
||||
public const String FileBuildVersion = "65000";
|
||||
public const String FilePatchVersion = "0";
|
||||
public const String FileVersion = FileMajorVersion + "." + FileMinorVersion + "." + FileBuildVersion + "." + FilePatchVersion;
|
||||
|
||||
// Derived versions
|
||||
public const String TfsMajorVersion = "8";
|
||||
public const String TfsMinorVersion = "0";
|
||||
public const String TfsProductVersion = TfsMajorVersion + "." + TfsMinorVersion;
|
||||
|
||||
// On-premises TFS install folder
|
||||
public const String TfsInstallDirectory = "Azure DevOps Server 2019";
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Common.Internal;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.WebApi.Patch;
|
||||
using GitHub.Services.WebApi.Patch.Json;
|
||||
|
||||
namespace GitHub.Core.WebApi
|
||||
{
|
||||
[GenerateAllConstants]
|
||||
public enum ProjectState
|
||||
{
|
||||
/// <summary>
|
||||
/// Project is in the process of being deleted.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Deleting = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Project is in the process of being created.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
New = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Project is completely created and ready to use.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
WellFormed = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Project has been queued for creation, but the process has not yet started.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
CreatePending = 3,
|
||||
|
||||
/// <summary>
|
||||
/// All projects regardless of state.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
All = -1, // Used for filtering.
|
||||
|
||||
/// <summary>
|
||||
/// Project has not been changed.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Unchanged = -2, // Used for updating projects.
|
||||
|
||||
/// <summary>
|
||||
/// Project has been deleted.
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Deleted = 4, // Used for the project history.
|
||||
}
|
||||
|
||||
public enum ProjectVisibility // Stored as a TINYINT
|
||||
{
|
||||
[ClientInternalUseOnly]
|
||||
Unchanged = -1, // Used for updating projects.
|
||||
/// <summary>
|
||||
/// The project is only visible to users with explicit access.
|
||||
/// </summary>
|
||||
Private = 0,
|
||||
/// <summary>
|
||||
/// Enterprise level project visibility
|
||||
/// </summary>
|
||||
[ClientInternalUseOnly(omitFromTypeScriptDeclareFile: false)]
|
||||
Organization = 1,
|
||||
/// <summary>
|
||||
/// The project is visible to all.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
Public = 2,
|
||||
[ClientInternalUseOnly]
|
||||
SystemPrivate = 3 // Soft-deleted projects
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Core.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a shallow reference to a TeamProject.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class TeamProjectReference : ISecuredObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Default constructor to ensure we set up the project state correctly for serialization.
|
||||
/// </summary>
|
||||
public TeamProjectReference()
|
||||
{
|
||||
State = ProjectState.Unchanged;
|
||||
Visibility = ProjectVisibility.Unchanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Project identifier.
|
||||
/// </summary>
|
||||
[DataMember(Order = 0, EmitDefaultValue = false)]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Project abbreviation.
|
||||
/// </summary>
|
||||
[DataMember(Order = 1, EmitDefaultValue = false)]
|
||||
public string Abbreviation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Project name.
|
||||
/// </summary>
|
||||
[DataMember(Order = 2, EmitDefaultValue = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The project's description (if any).
|
||||
/// </summary>
|
||||
[DataMember(Order = 3, EmitDefaultValue = false)]
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Url to the full version of the object.
|
||||
/// </summary>
|
||||
[DataMember(Order = 4, EmitDefaultValue = false)]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Project state.
|
||||
/// </summary>
|
||||
[DataMember(Order = 5)]
|
||||
public ProjectState State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Project revision.
|
||||
/// </summary>
|
||||
[DataMember(Order = 6, EmitDefaultValue = false)]
|
||||
public Int64 Revision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Project visibility.
|
||||
/// </summary>
|
||||
[DataMember(Order = 7)]
|
||||
public ProjectVisibility Visibility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Url to default team identity image.
|
||||
/// </summary>
|
||||
[DataMember(Order = 8, EmitDefaultValue = false)]
|
||||
public String DefaultTeamImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Project last update time.
|
||||
/// </summary>
|
||||
[DataMember(Order = 9)]
|
||||
public DateTime LastUpdateTime { get; set; }
|
||||
|
||||
#region ISecuredObject
|
||||
Guid ISecuredObject.NamespaceId => NamespaceId;
|
||||
|
||||
int ISecuredObject.RequiredPermissions => RequiredPermissions;
|
||||
|
||||
string ISecuredObject.GetToken()
|
||||
{
|
||||
return GetToken();
|
||||
}
|
||||
|
||||
protected virtual Guid NamespaceId => TeamProjectSecurityConstants.NamespaceId;
|
||||
|
||||
protected virtual int RequiredPermissions => TeamProjectSecurityConstants.GenericRead;
|
||||
|
||||
protected virtual string GetToken()
|
||||
{
|
||||
// WE DON'T CARE THIS FOR NOW
|
||||
return TeamProjectSecurityConstants.GetToken(Id.ToString("D"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.DistributedTask.Common.Contracts
|
||||
{
|
||||
[DataContract]
|
||||
public class AuthorizationHeader : BaseSecuredObject
|
||||
{
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Name { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.DistributedTask.Common.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents binding of data source for the service endpoint request.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DataSourceBindingBase : BaseSecuredObject
|
||||
{
|
||||
public DataSourceBindingBase()
|
||||
{
|
||||
}
|
||||
|
||||
protected DataSourceBindingBase(DataSourceBindingBase inputDefinitionToClone)
|
||||
: this(inputDefinitionToClone, null)
|
||||
{
|
||||
}
|
||||
|
||||
protected DataSourceBindingBase(DataSourceBindingBase inputDefinitionToClone, ISecuredObject securedObject)
|
||||
: base(securedObject)
|
||||
{
|
||||
this.DataSourceName = inputDefinitionToClone.DataSourceName;
|
||||
this.EndpointId = inputDefinitionToClone.EndpointId;
|
||||
this.Target = inputDefinitionToClone.Target;
|
||||
this.ResultTemplate = inputDefinitionToClone.ResultTemplate;
|
||||
this.EndpointUrl = inputDefinitionToClone.EndpointUrl;
|
||||
this.ResultSelector = inputDefinitionToClone.ResultSelector;
|
||||
this.RequestVerb = inputDefinitionToClone.RequestVerb;
|
||||
this.RequestContent = inputDefinitionToClone.RequestContent;
|
||||
this.CallbackContextTemplate = inputDefinitionToClone.CallbackContextTemplate;
|
||||
this.CallbackRequiredTemplate = inputDefinitionToClone.CallbackRequiredTemplate;
|
||||
this.InitialContextTemplate = inputDefinitionToClone.InitialContextTemplate;
|
||||
inputDefinitionToClone.Parameters.Copy(this.Parameters);
|
||||
this.CloneHeaders(inputDefinitionToClone.Headers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the data source.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string DataSourceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameters for the data source.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public Dictionary<string, string> Parameters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_parameters == null)
|
||||
{
|
||||
m_parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return m_parameters;
|
||||
}
|
||||
}
|
||||
|
||||
public DataSourceBindingBase Clone(ISecuredObject securedObject)
|
||||
{
|
||||
return new DataSourceBindingBase(this, securedObject);
|
||||
}
|
||||
|
||||
private void CloneHeaders(List<AuthorizationHeader> headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Headers = headers.Select(header => new AuthorizationHeader { Name = header.Name, Value = header.Value }).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the endpoint Id.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String EndpointId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target of the data source.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the result template.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String ResultTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets http request verb
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String RequestVerb { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets http request body
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String RequestContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the url of the service endpoint.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String EndpointUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the result selector.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String ResultSelector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pagination format supported by this data source(ContinuationToken/SkipTop).
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String CallbackContextTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Subsequent calls needed?
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String CallbackRequiredTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the initial value of the query params
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String InitialContextTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authorization headers.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public List<AuthorizationHeader> Headers { get; set; }
|
||||
|
||||
private Dictionary<String, String> m_parameters;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.DistributedTask.Common.Contracts
|
||||
{
|
||||
[DataContract]
|
||||
public class ProcessParameters : BaseSecuredObject
|
||||
{
|
||||
public ProcessParameters()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public ProcessParameters(ISecuredObject securedObject)
|
||||
: this(null, securedObject)
|
||||
{
|
||||
}
|
||||
|
||||
private ProcessParameters(ProcessParameters toClone, ISecuredObject securedObject)
|
||||
: base(securedObject)
|
||||
{
|
||||
if (toClone != null)
|
||||
{
|
||||
if (toClone.Inputs.Count > 0)
|
||||
{
|
||||
Inputs.AddRange(toClone.Inputs.Select(i => i.Clone(securedObject)));
|
||||
}
|
||||
|
||||
if (toClone.SourceDefinitions.Count > 0)
|
||||
{
|
||||
SourceDefinitions.AddRange(toClone.SourceDefinitions.Select(sd => sd.Clone(securedObject)));
|
||||
}
|
||||
|
||||
if (toClone.DataSourceBindings.Count > 0)
|
||||
{
|
||||
DataSourceBindings.AddRange(toClone.DataSourceBindings.Select(dsb => dsb.Clone(securedObject)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IList<TaskInputDefinitionBase> Inputs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_inputs == null)
|
||||
{
|
||||
m_inputs = new List<TaskInputDefinitionBase>();
|
||||
}
|
||||
return m_inputs;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<TaskSourceDefinitionBase> SourceDefinitions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_sourceDefinitions == null)
|
||||
{
|
||||
m_sourceDefinitions = new List<TaskSourceDefinitionBase>();
|
||||
}
|
||||
return m_sourceDefinitions;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<DataSourceBindingBase> DataSourceBindings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_dataSourceBindings == null)
|
||||
{
|
||||
m_dataSourceBindings = new List<DataSourceBindingBase>();
|
||||
}
|
||||
return m_dataSourceBindings;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var processParameters2 = obj as ProcessParameters;
|
||||
if (processParameters2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.Inputs == null && processParameters2.Inputs == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((this.Inputs != null && processParameters2.Inputs == null)
|
||||
|| (this.Inputs == null && processParameters2.Inputs != null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.Inputs.Count != processParameters2.Inputs.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var orderedProcessParameters1 = this.Inputs.Where(i => i != null).OrderBy(i => i.Name);
|
||||
var orderedProcessParameters2 = processParameters2.Inputs.Where(i => i != null).OrderBy(i => i.Name);
|
||||
|
||||
if (!orderedProcessParameters1.OrderBy(i => i.Name).SequenceEqual(orderedProcessParameters2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ProcessParameters Clone(ISecuredObject securedObject = null)
|
||||
{
|
||||
return new ProcessParameters(this, securedObject);
|
||||
}
|
||||
|
||||
[OnDeserialized]
|
||||
private void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
SerializationHelper.Copy(ref m_serializedInputs, ref m_inputs, true);
|
||||
SerializationHelper.Copy(ref m_serializedSourceDefinitions, ref m_sourceDefinitions, true);
|
||||
SerializationHelper.Copy(ref m_serializedDataSourceBindings, ref m_dataSourceBindings, true);
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
SerializationHelper.Copy(ref m_inputs, ref m_serializedInputs);
|
||||
SerializationHelper.Copy(ref m_sourceDefinitions, ref m_serializedSourceDefinitions);
|
||||
SerializationHelper.Copy(ref m_dataSourceBindings, ref m_serializedDataSourceBindings);
|
||||
}
|
||||
|
||||
[OnSerialized]
|
||||
private void OnSerialized(StreamingContext context)
|
||||
{
|
||||
m_serializedInputs = null;
|
||||
m_serializedSourceDefinitions = null;
|
||||
m_serializedDataSourceBindings = null;
|
||||
}
|
||||
|
||||
[DataMember(Name = "Inputs", EmitDefaultValue = false)]
|
||||
private List<TaskInputDefinitionBase> m_serializedInputs;
|
||||
|
||||
[DataMember(Name = "SourceDefinitions", EmitDefaultValue = false)]
|
||||
private List<TaskSourceDefinitionBase> m_serializedSourceDefinitions;
|
||||
|
||||
[DataMember(Name = "DataSourceBindings", EmitDefaultValue = false)]
|
||||
private List<DataSourceBindingBase> m_serializedDataSourceBindings;
|
||||
|
||||
private List<TaskInputDefinitionBase> m_inputs;
|
||||
private List<TaskSourceDefinitionBase> m_sourceDefinitions;
|
||||
private List<DataSourceBindingBase> m_dataSourceBindings;
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.DistributedTask.Common.Contracts
|
||||
{
|
||||
[DataContract]
|
||||
public class TaskInputDefinitionBase : BaseSecuredObject
|
||||
{
|
||||
public TaskInputDefinitionBase()
|
||||
{
|
||||
InputType = TaskInputType.String;
|
||||
DefaultValue = String.Empty;
|
||||
Required = false;
|
||||
HelpMarkDown = String.Empty;
|
||||
}
|
||||
|
||||
protected TaskInputDefinitionBase(TaskInputDefinitionBase inputDefinitionToClone)
|
||||
: this(inputDefinitionToClone, null)
|
||||
{
|
||||
}
|
||||
|
||||
protected TaskInputDefinitionBase(TaskInputDefinitionBase inputDefinitionToClone, ISecuredObject securedObject)
|
||||
: base(securedObject)
|
||||
{
|
||||
this.DefaultValue = inputDefinitionToClone.DefaultValue;
|
||||
this.InputType = inputDefinitionToClone.InputType;
|
||||
this.Label = inputDefinitionToClone.Label;
|
||||
this.Name = inputDefinitionToClone.Name;
|
||||
this.Required = inputDefinitionToClone.Required;
|
||||
this.HelpMarkDown = inputDefinitionToClone.HelpMarkDown;
|
||||
this.VisibleRule = inputDefinitionToClone.VisibleRule;
|
||||
this.GroupName = inputDefinitionToClone.GroupName;
|
||||
|
||||
if (inputDefinitionToClone.Validation != null)
|
||||
{
|
||||
this.Validation = inputDefinitionToClone.Validation.Clone(securedObject);
|
||||
}
|
||||
|
||||
if (inputDefinitionToClone.m_aliases != null)
|
||||
{
|
||||
this.m_aliases = new List<String>(inputDefinitionToClone.m_aliases);
|
||||
}
|
||||
|
||||
if (inputDefinitionToClone.m_options != null)
|
||||
{
|
||||
this.m_options = new Dictionary<String, String>(inputDefinitionToClone.m_options);
|
||||
}
|
||||
if (inputDefinitionToClone.m_properties != null)
|
||||
{
|
||||
this.m_properties = new Dictionary<String, String>(inputDefinitionToClone.m_properties);
|
||||
}
|
||||
}
|
||||
|
||||
public IList<String> Aliases
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_aliases == null)
|
||||
{
|
||||
m_aliases = new List<String>();
|
||||
}
|
||||
return m_aliases;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Label
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String DefaultValue
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public Boolean Required
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(Name = "Type")]
|
||||
public String InputType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String HelpMarkDown
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// VisibleRule should specify the condition at which this input is to be shown/displayed
|
||||
// Typical format is "NAME OF THE DEPENDENT INPUT = VALUE TOBE BOUND"
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string VisibleRule
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string GroupName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public TaskInputValidation Validation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Dictionary<String, String> Options
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_options == null)
|
||||
{
|
||||
m_options = new Dictionary<String, String>();
|
||||
}
|
||||
return m_options;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<String, String> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_properties == null)
|
||||
{
|
||||
m_properties = new Dictionary<String, String>();
|
||||
}
|
||||
return m_properties;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual TaskInputDefinitionBase Clone(
|
||||
ISecuredObject securedObject)
|
||||
{
|
||||
return new TaskInputDefinitionBase(this, securedObject);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Name.GetHashCode() ^ this.DefaultValue.GetHashCode() ^ this.Label.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var taskInput2 = obj as TaskInputDefinitionBase;
|
||||
if (taskInput2 == null
|
||||
|| !string.Equals(InputType, taskInput2.InputType, StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(Label, taskInput2.Label, StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(Name, taskInput2.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(GroupName, taskInput2.GroupName, StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(DefaultValue, taskInput2.DefaultValue, StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(HelpMarkDown, taskInput2.HelpMarkDown, StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(VisibleRule, taskInput2.VisibleRule, StringComparison.OrdinalIgnoreCase)
|
||||
|| !this.Required.Equals(taskInput2.Required))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AreListsEqual(Aliases, taskInput2.Aliases)
|
||||
|| !AreDictionariesEqual(Properties, taskInput2.Properties)
|
||||
|| !AreDictionariesEqual(Options, taskInput2.Options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((Validation != null && taskInput2.Validation == null)
|
||||
|| (Validation == null && taskInput2.Validation != null)
|
||||
|| ((Validation != null && taskInput2.Validation != null)
|
||||
&& !Validation.Equals(taskInput2.Validation)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AreDictionariesEqual(Dictionary<String, String> input1, Dictionary<String, String> input2)
|
||||
{
|
||||
if (input1 == null && input2 == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((input1 == null && input2 != null)
|
||||
|| (input1 != null && input2 == null)
|
||||
|| (input1.Count != input2.Count))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var key in input1.Keys)
|
||||
{
|
||||
if (!(input2.ContainsKey(key) && String.Equals(input1[key], input2[key], StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Boolean AreListsEqual(IList<String> list1, IList<String> list2)
|
||||
{
|
||||
if (list1.Count != list2.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Int32 i = 0; i < list1.Count; i++)
|
||||
{
|
||||
if (!String.Equals(list1[i], list2[i], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[DataMember(Name = "Aliases", EmitDefaultValue = false)]
|
||||
private List<String> m_aliases;
|
||||
|
||||
[DataMember(Name = "Options", EmitDefaultValue = false)]
|
||||
private Dictionary<String, String> m_options;
|
||||
|
||||
[DataMember(Name = "Properties", EmitDefaultValue = false)]
|
||||
private Dictionary<String, String> m_properties;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.Common.Contracts
|
||||
{
|
||||
public static class TaskInputType
|
||||
{
|
||||
public const String String = "string";
|
||||
public const String Repository = "repository";
|
||||
public const String Boolean = "boolean";
|
||||
public const String KeyValue = "keyvalue";
|
||||
public const String FilePath = "filepath";
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.DistributedTask.Common.Contracts
|
||||
{
|
||||
[DataContract]
|
||||
public class TaskInputValidation : BaseSecuredObject
|
||||
{
|
||||
public TaskInputValidation()
|
||||
{
|
||||
}
|
||||
|
||||
private TaskInputValidation(TaskInputValidation toClone, ISecuredObject securedObject)
|
||||
: base(securedObject)
|
||||
{
|
||||
if (toClone != null)
|
||||
{
|
||||
this.Expression = toClone.Expression;
|
||||
this.Message = toClone.Message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conditional expression
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Expression
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Message explaining how user can correct if validation fails
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Expression.GetHashCode() ^ Message.GetHashCode();
|
||||
}
|
||||
|
||||
public TaskInputValidation Clone()
|
||||
{
|
||||
return this.Clone(null);
|
||||
}
|
||||
|
||||
public TaskInputValidation Clone(ISecuredObject securedObject)
|
||||
{
|
||||
return new TaskInputValidation(this, securedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.DistributedTask.Common.Contracts
|
||||
{
|
||||
[DataContract]
|
||||
public class TaskSourceDefinitionBase : BaseSecuredObject
|
||||
{
|
||||
public TaskSourceDefinitionBase()
|
||||
{
|
||||
AuthKey = String.Empty;
|
||||
Endpoint = String.Empty;
|
||||
Selector = String.Empty;
|
||||
Target = String.Empty;
|
||||
KeySelector = String.Empty;
|
||||
}
|
||||
|
||||
protected TaskSourceDefinitionBase(TaskSourceDefinitionBase inputDefinitionToClone)
|
||||
: this(inputDefinitionToClone, null)
|
||||
{
|
||||
}
|
||||
|
||||
protected TaskSourceDefinitionBase(TaskSourceDefinitionBase inputDefinitionToClone, ISecuredObject securedObject)
|
||||
: base(securedObject)
|
||||
{
|
||||
this.Endpoint = inputDefinitionToClone.Endpoint;
|
||||
this.Target = inputDefinitionToClone.Target;
|
||||
this.AuthKey = inputDefinitionToClone.AuthKey;
|
||||
this.Selector = inputDefinitionToClone.Selector;
|
||||
this.KeySelector = inputDefinitionToClone.KeySelector;
|
||||
}
|
||||
|
||||
public virtual TaskSourceDefinitionBase Clone(ISecuredObject securedObject)
|
||||
{
|
||||
return new TaskSourceDefinitionBase(this, securedObject);
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Endpoint
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Target
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String AuthKey
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Selector
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String KeySelector
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.Expressions
|
||||
{
|
||||
internal sealed class AndNode : FunctionNode
|
||||
{
|
||||
protected sealed override Boolean TraceFullyRealized => false;
|
||||
|
||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
||||
{
|
||||
foreach (ExpressionNode parameter in Parameters)
|
||||
{
|
||||
if (!parameter.EvaluateBoolean(context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user