diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 52abd247a..3c63e9634 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -45,6 +45,7 @@
0.3.1
4.60.1
4.60.1
+ 7.1.2
7.1.2
7.1.2
8.0.0
@@ -58,6 +59,8 @@
4.7.0
6.0.0
+ 4.3.1
+ 4.7.0
8.0.0
8.0.1
4.5.4
diff --git a/src/PerfView/Authentication.cs b/src/PerfView/Authentication.cs
index 2ab6da7aa..a6f764423 100644
--- a/src/PerfView/Authentication.cs
+++ b/src/PerfView/Authentication.cs
@@ -214,6 +214,9 @@ public static void Configure(this SymbolReaderHttpHandler handler, Authenticatio
handler.ClearHandlers();
+ // Always add Symweb authentication.
+ handler.AddSymwebAuthentication(log);
+
// The order isn't critical, but we chose to put GCM last
// because the user might want to use GCM for GitHub and
// Developer identity for Azure DevOps. If GCM were first,
diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj
index f6b77f73a..eaa9732e4 100644
--- a/src/PerfView/PerfView.csproj
+++ b/src/PerfView/PerfView.csproj
@@ -37,7 +37,6 @@
en
true
-
PerfView.nuspec
$(GenerateNuspecDependsOn);SetNuspecProperties
@@ -87,11 +86,13 @@
+
+
@@ -510,6 +511,13 @@
Microsoft.Identity.Client.Extensions.Msal.dll
False
+
+ Non-Resx
+ false
+ .\Microsoft.IdentityModel.Abstractions.dll
+ Microsoft.IdentityModel.Abstractions.dll
+ False
+
Non-Resx
false
@@ -552,6 +560,13 @@
System.Numerics.Vectors.dll
False
+
+ Non-Resx
+ false
+ .\System.Security.Cryptography.ProtectedData.dll
+ System.Security.Cryptography.ProtectedData.dll
+ False
+
Non-Resx
false
diff --git a/src/PerfView/SymbolReaderHttpHandler.cs b/src/PerfView/SymbolReaderHttpHandler.cs
index 05789cb08..fe9b499a6 100644
--- a/src/PerfView/SymbolReaderHttpHandler.cs
+++ b/src/PerfView/SymbolReaderHttpHandler.cs
@@ -162,6 +162,26 @@ public SymbolReaderHttpHandler AddAzureDevOpsAuthentication(TextWriter log, bool
return AddHandler(new AzureDevOpsHandler(log, new DefaultAzureCredential(options)));
}
+ ///
+ /// Add a handler for Symweb authentication using local credentials.
+ /// It will try to use cached credentials from Visual Studio, VS Code,
+ /// Azure Powershell and Azure CLI.
+ ///
+ /// A logger.
+ /// If no local credentials can be found, then a browser window will
+ /// be opened to prompt the user. Set this to true to if you don't want that.
+ /// This instance for fluent chaining.
+ public SymbolReaderHttpHandler AddSymwebAuthentication(TextWriter log, bool silent = false)
+ {
+ DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions
+ {
+ ExcludeInteractiveBrowserCredential = silent,
+ ExcludeManagedIdentityCredential = true // This is not designed to be used in a service.
+ };
+
+ return AddHandler(new SymwebHandler(log, new DefaultAzureCredential(options)));
+ }
+
///
/// Add a handler for GitHub device flow authentication.
///
@@ -1572,6 +1592,101 @@ private static bool TryGetNextChallengeParameter(ref ReadOnlySpan paramete
}
}
+ ///
+ /// A handler that adds authorization for Symweb.
+ ///
+ internal sealed class SymwebHandler : SymbolReaderAuthHandlerBase
+ {
+ ///
+ /// The value of stored in a single element
+ /// array suitable for passing to
+ /// .
+ ///
+ private static readonly string[] s_scopes = new[] { Symweb.Scope };
+
+ ///
+ /// Prefix to put in front of logging messages.
+ ///
+ private const string LogPrefix = "SymwebAuth: ";
+
+ ///
+ /// A provider of access tokens.
+ ///
+ private readonly TokenCredential _tokenCredential;
+
+ ///
+ /// Protect against concurrent access.
+ ///
+ private readonly SemaphoreSlim _tokenCredentialGate = new SemaphoreSlim(initialCount: 1);
+
+ ///
+ /// An HTTP client used to discover the authority (login endpoint and tenant) for Symweb.
+ ///
+ private readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler() { CheckCertificateRevocationList = true });
+
+ ///
+ /// Construct a new instance.
+ ///
+ /// A provider of access tokens.
+ public SymwebHandler(TextWriter log, TokenCredential tokenCredential) : base(log, LogPrefix)
+ {
+ _tokenCredential = tokenCredential ?? throw new ArgumentNullException(nameof(tokenCredential));
+ }
+
+ ///
+ /// Try to find the authority endpoint for Symweb
+ /// given a full URI.
+ ///
+ /// The request URI.
+ /// The authority, if found.
+ /// True if represents a path to a
+ /// resource in Symweb.
+ protected override bool TryGetAuthority(Uri requestUri, out Uri authority) => Symweb.TryGetAuthority(requestUri, out authority);
+
+ ///
+ /// Get a token to access Symweb.
+ ///
+ /// The request context.
+ /// The next handler.
+ /// The Symweb instance.
+ /// A cancellation token.
+ /// An access token, or null if one could not be obtained.
+ protected override async Task GetAuthTokenAsync(RequestContext context, SymbolReaderHandlerDelegate next, Uri authority, CancellationToken cancellationToken)
+ {
+ // Get a new access token from the credential provider.
+ WriteLog("Asking for authorization to access {0}", authority);
+ AuthToken token = await GetTokenAsync(cancellationToken).ConfigureAwait(false);
+
+ return token;
+ }
+
+ ///
+ /// Get a new access token for Symweb from the .
+ ///
+ /// A cancellation token.
+ /// The access token.
+ private async Task GetTokenAsync(CancellationToken cancellationToken)
+ {
+ await _tokenCredentialGate.WaitAsync(cancellationToken).ConfigureAwait(false);
+ try
+ {
+ // Use the token credential provider to acquire a new token.
+ TokenRequestContext requestContext = new TokenRequestContext(s_scopes);
+ AccessToken accessToken = await _tokenCredential.GetTokenAsync(requestContext, cancellationToken).ConfigureAwait(false);
+ return AuthToken.FromAzureCoreAccessToken(accessToken);
+ }
+ catch (Exception ex)
+ {
+ WriteStatusLog("Exception getting token. {0}", ex);
+ throw;
+ }
+ finally
+ {
+ _tokenCredentialGate.Release();
+ }
+ }
+ }
+
///
/// A handler that handles GitHub device flow authorization.
///
@@ -1861,6 +1976,64 @@ private sealed class AccessTokenResponse
}
}
+ ///
+ /// Contains constants, static properties and helper methods pertinent to Symweb.
+ ///
+ internal static class Symweb
+ {
+ ///
+ /// The OAuth scope to use when requesting tokens for Symweb.
+ ///
+ public const string Scope = "af9e1c69-e5e9-4331-8cc5-cdf93d57bafa/.default";
+
+ ///
+ /// The url host for Symweb.
+ ///
+ public const string SymwebHost = "symweb.azurefd.net";
+
+ ///
+ /// Try to find the authority endpoint for Symweb given a full URI.
+ ///
+ /// The request URI.
+ /// The authority, if found.
+ /// True if represents a path to a
+ /// resource in Symweb.
+ public static bool TryGetAuthority(Uri requestUri, out Uri authority)
+ {
+ if (!requestUri.IsAbsoluteUri)
+ {
+ authority = null;
+ return false;
+ }
+
+ UriBuilder builder = null;
+ string host = requestUri.DnsSafeHost;
+ if (host.Equals(SymwebHost, StringComparison.OrdinalIgnoreCase))
+ {
+ builder = new UriBuilder
+ {
+ Host = SymwebHost
+ };
+ }
+
+ if (builder is null)
+ {
+ // Not a Symweb URI.
+ authority = null;
+ return false;
+ }
+
+ builder.Scheme = requestUri.Scheme;
+ if (!requestUri.IsDefaultPort)
+ {
+ builder.Port = requestUri.Port;
+ }
+
+ authority = builder.Uri;
+ return true;
+ }
+ }
+
///
/// Contains constants, static properties and helper methods pertinent to Azure DevOps.
///
diff --git a/src/TraceEvent/Symbols/SymbolPath.cs b/src/TraceEvent/Symbols/SymbolPath.cs
index 54ed139cf..81685aa56 100644
--- a/src/TraceEvent/Symbols/SymbolPath.cs
+++ b/src/TraceEvent/Symbols/SymbolPath.cs
@@ -70,9 +70,7 @@ public static string MicrosoftSymbolServerPath
if (s_MicrosoftSymbolServerPath == null)
{
s_MicrosoftSymbolServerPath = s_MicrosoftSymbolServerPath +
- ";" + @"SRV*https://msdl.microsoft.com/download/symbols" + // Operatig system Symbols
- ";" + @"SRV*https://nuget.smbsrc.net" + // Nuget symbols
- ";" + @"SRV*https://referencesource.microsoft.com/symbols"; // .NET Runtime desktop symbols
+ ";" + @"SRV*https://msdl.microsoft.com/download/symbols";
}
return s_MicrosoftSymbolServerPath;
}
diff --git a/src/TraceEvent/Symbols/SymbolReader.cs b/src/TraceEvent/Symbols/SymbolReader.cs
index 5d3c1a102..67fcfc59c 100644
--- a/src/TraceEvent/Symbols/SymbolReader.cs
+++ b/src/TraceEvent/Symbols/SymbolReader.cs
@@ -1072,7 +1072,7 @@ internal bool GetPhysicalFileFromServer(string serverPath, string pdbIndexPath,
{
m_log.WriteLine("FindSymbolFilePath: In task, sending HTTP request {0}", fullUri);
- var responseTask = HttpClient.GetAsync(fullUri);
+ var responseTask = HttpClient.GetAsync(fullUri, HttpCompletionOption.ResponseHeadersRead);
responseTask.Wait();
var response = responseTask.Result.EnsureSuccessStatusCode();
@@ -1140,8 +1140,8 @@ internal bool GetPhysicalFileFromServer(string serverPath, string pdbIndexPath,
}
});
- // Wait 25 seconds allowing for interruptions.
- var limit = 250;
+ // Wait 60 seconds allowing for interruptions.
+ var limit = 600;
for (int i = 0; i < limit; i++)
{