Add cloud symweb support (#4848)
authorMike McLaughlin <mikem@microsoft.com>
Tue, 3 Sep 2024 19:56:19 +0000 (12:56 -0700)
committerGitHub <noreply@github.com>
Tue, 3 Sep 2024 19:56:19 +0000 (12:56 -0700)
Add back -mi (add symweb symbol server), -nocache and -interface options
to setsymbolserver command.

Add back --internal-server support to dotnet-symbol.

Clean up the HttpSymbolStore.cs - fix HttpResponse not being properly
disposed. Removed the separate authenticated client instance/var.

Fix dotnet-symbol for some heap dumps. Some didn't have coreclr.dll's
debug directory contents. Catch error and continue downloading files.

eng/Versions.props
src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj
src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs
src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs
src/Microsoft.SymbolStore/KeyGenerators/PEFileKeyGenerator.cs
src/Microsoft.SymbolStore/Microsoft.SymbolStore.csproj
src/Microsoft.SymbolStore/SymbolStores/HttpSymbolStore.cs
src/Tools/dotnet-symbol/Program.cs
src/Tools/dotnet-symbol/dotnet-symbol.csproj

index dcc109693891c4a9d5c9c3f084d3d3740f3eb44e..6b6e047002eaa37ca8b4e20e463abb6573eb423e 100644 (file)
@@ -27,6 +27,7 @@
   <PropertyGroup>
     <!-- Opt-in/out repo features -->
     <UsingToolXliff>false</UsingToolXliff>
+    <AzureIdentityVersion>1.12.0</AzureIdentityVersion>
     <!-- Uncomment this line to use the custom version of roslyn as needed. -->
     <!-- <UsingToolMicrosoftNetCompilers Condition="'$(DotNetBuildSourceOnly)' != 'true'">true</UsingToolMicrosoftNetCompilers> -->
     <!-- CoreFX -->
@@ -48,6 +49,7 @@
     <SystemBuffersVersion>4.5.1</SystemBuffersVersion>
     <SystemMemoryVersion>4.5.5</SystemMemoryVersion>
     <SystemRuntimeLoaderVersion>4.3.0</SystemRuntimeLoaderVersion>
+    <SystemThreadingTasksExtensionsVersion>4.5.4</SystemThreadingTasksExtensionsVersion>
     <SystemTextEncodingsWebVersion>8.0.0</SystemTextEncodingsWebVersion>
     <SystemTextJsonVersion>8.0.4</SystemTextJsonVersion>
     <XUnitAbstractionsVersion>2.0.3</XUnitAbstractionsVersion>
index 5f17fb0a49f4544eef7cef8e74d738244c476a8c..cbb95d87eef33f4a23403dcf4f7cd08cbe7f4f9f 100644 (file)
@@ -21,6 +21,7 @@
     <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
     <PackageReference Include="System.Runtime.Loader" Version="$(SystemRuntimeLoaderVersion)" />
     <PackageReference Include="System.Text.Json" Version="$(SystemTextJsonVersion)" />
+    <PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
   </ItemGroup>
   
   <ItemGroup>
index 4ed23f80620f82d04ee76c3f9acb44b46b6590ae..1247620b743d49247b87d2714cac57f8961faa41 100644 (file)
@@ -7,11 +7,15 @@ using System.Collections.Immutable;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
+using System.Net.Http.Headers;
 using System.Reflection.Metadata;
 using System.Reflection.PortableExecutable;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading;
+using System.Threading.Tasks;
+using Azure.Core;
+using Azure.Identity;
 using Microsoft.FileFormats;
 using Microsoft.FileFormats.ELF;
 using Microsoft.FileFormats.MachO;
@@ -32,6 +36,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// Symbol server URLs
         /// </summary>
         public const string MsdlSymbolServer = "https://msdl.microsoft.com/download/symbols/";
+        public const string SymwebSymbolServer = "https://symweb.azurefd.net/";
+
+        private static string _symwebHost = new Uri(SymwebSymbolServer).Host;
 
         private readonly IHost _host;
         private string _defaultSymbolCache;
@@ -207,9 +214,20 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     }
                     if (symbolServerPath != null)
                     {
-                        if (!AddSymbolServer(symbolServerPath: symbolServerPath.Trim()))
+                        symbolServerPath = symbolServerPath.Trim();
+                        if (IsSymweb(symbolServerPath))
                         {
-                            return false;
+                            if (!AddSymwebSymbolServer(includeInteractiveCredentials: false))
+                            {
+                                return false;
+                            }
+                        }
+                        else
+                        {
+                            if (!AddSymbolServer(symbolServerPath))
+                            {
+                                return false;
+                            }
                         }
                     }
                     foreach (string symbolCachePath in symbolCachePaths.Reverse<string>())
@@ -226,19 +244,89 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return true;
         }
 
+        /// <summary>
+        /// Add the cloud symweb symbol server with authentication.
+        /// </summary>
+        /// <param name="includeInteractiveCredentials">specifies whether credentials requiring user interaction will be included in the default authentication flow</param>
+        /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional uses <see cref="DefaultTimeout"/> if null)</param>
+        /// <param name="retryCount">number of retries (optional uses <see cref="DefaultRetryCount"/> if null)</param>
+        /// <returns>if false, failure</returns>
+        public bool AddSymwebSymbolServer(
+            bool includeInteractiveCredentials = false,
+            int? timeoutInMinutes = null,
+            int? retryCount = null)
+        {
+            TokenCredential tokenCredential = new DefaultAzureCredential(includeInteractiveCredentials);
+            AccessToken accessToken;
+            async ValueTask<AuthenticationHeaderValue> authenticationFunc(CancellationToken token)
+            {
+                try
+                {
+                    if (accessToken.ExpiresOn <= DateTimeOffset.UtcNow.AddMinutes(2))
+                    {
+                        accessToken = await tokenCredential.GetTokenAsync(new TokenRequestContext(["api://af9e1c69-e5e9-4331-8cc5-cdf93d57bafa/.default"]), token).ConfigureAwait(false);
+                    }
+                    return new AuthenticationHeaderValue("Bearer", accessToken.Token);
+                }
+                catch (Exception ex) when (ex is CredentialUnavailableException or AuthenticationFailedException)
+                {
+                    Trace.TraceError($"AddSymwebSymbolServer: {ex}");
+                    return null;
+                }
+            }
+            return AddSymbolServer(SymwebSymbolServer, timeoutInMinutes, retryCount, authenticationFunc);
+        }
+
+        /// <summary>
+        /// Add symbol server to search path. The server URL can be the cloud symweb.
+        /// </summary>
+        /// <param name="accessToken">PAT or access token</param>
+        /// <param name="symbolServerPath">symbol server url (optional, uses <see cref="DefaultSymbolPath"/> if null)</param>
+        /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional uses <see cref="DefaultTimeout"/> if null)</param>
+        /// <param name="retryCount">number of retries (optional uses <see cref="DefaultRetryCount"/> if null)</param>
+        /// <returns>if false, failure</returns>
+        public bool AddAuthenticatedSymbolServer(
+            string accessToken,
+            string symbolServerPath = null,
+            int? timeoutInMinutes = null,
+            int? retryCount = null)
+        {
+            if (accessToken == null)
+            {
+                throw new ArgumentNullException(nameof(accessToken));
+            }
+            AuthenticationHeaderValue authenticationValue = new("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($":{accessToken}")));
+            return AddSymbolServer(symbolServerPath, timeoutInMinutes, retryCount, (_) => new ValueTask<AuthenticationHeaderValue>(authenticationValue));
+        }
+
         /// <summary>
         /// Add symbol server to search path.
         /// </summary>
         /// <param name="symbolServerPath">symbol server url (optional, uses <see cref="DefaultSymbolPath"/> if null)</param>
-        /// <param name="authToken">PAT for secure symbol server (optional)</param>
         /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional uses <see cref="DefaultTimeout"/> if null)</param>
         /// <param name="retryCount">number of retries (optional uses <see cref="DefaultRetryCount"/> if null)</param>
         /// <returns>if false, failure</returns>
         public bool AddSymbolServer(
             string symbolServerPath = null,
-            string authToken = null,
             int? timeoutInMinutes = null,
             int? retryCount = null)
+        {
+            return AddSymbolServer(symbolServerPath, timeoutInMinutes, retryCount, authenticationFunc: null);
+        }
+
+        /// <summary>
+        /// Add symbol server to search path.
+        /// </summary>
+        /// <param name="symbolServerPath">symbol server url (optional, uses <see cref="DefaultSymbolPath"/> if null)</param>
+        /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional uses <see cref="DefaultTimeout"/> if null)</param>
+        /// <param name="retryCount">number of retries (optional uses <see cref="DefaultRetryCount"/> if null)</param>
+        /// <param name="authenticationFunc">function that returns the authentication value for a request</param>
+        /// <returns>if false, failure</returns>
+        public bool AddSymbolServer(
+            string symbolServerPath,
+            int? timeoutInMinutes,
+            int? retryCount,
+            Func<CancellationToken, ValueTask<AuthenticationHeaderValue>> authenticationFunc)
         {
             // Add symbol server URL if exists
             symbolServerPath ??= DefaultSymbolPath;
@@ -260,9 +348,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 if (!IsDuplicateSymbolStore<HttpSymbolStore>(store, (httpSymbolStore) => uri.Equals(httpSymbolStore.Uri)))
                 {
                     // Create http symbol server store
-                    HttpSymbolStore httpSymbolStore = new(Tracer.Instance, store, uri, personalAccessToken: authToken);
-                    httpSymbolStore.Timeout = TimeSpan.FromMinutes(timeoutInMinutes.GetValueOrDefault(DefaultTimeout));
-                    httpSymbolStore.RetryCount = retryCount.GetValueOrDefault(DefaultRetryCount);
+                    HttpSymbolStore httpSymbolStore = new(Tracer.Instance, store, uri, authenticationFunc)
+                    {
+                        Timeout = TimeSpan.FromMinutes(timeoutInMinutes.GetValueOrDefault(DefaultTimeout)),
+                        RetryCount = retryCount.GetValueOrDefault(DefaultRetryCount)
+                    };
                     SetSymbolStore(httpSymbolStore);
                 }
             }
@@ -953,6 +1043,23 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return sb.ToString();
         }
 
+        /// <summary>
+        /// Returns true if cloud symweb server
+        /// </summary>
+        /// <param name="server"></param>
+        private static bool IsSymweb(string server)
+        {
+            try
+            {
+                Uri uri = new(server);
+                return uri.Host.Equals(_symwebHost, StringComparison.OrdinalIgnoreCase);
+            }
+            catch (Exception ex) when (ex is UriFormatException or InvalidOperationException)
+            {
+                return false;
+            }
+        }
+
         /// <summary>
         /// Attempts to download/retrieve from cache the key.
         /// </summary>
index b32b19cde0ee8215a86200d01f7d7e5e36c28459..fb3c23bdc4a30c9218c1cab21381f5313239ff56 100644 (file)
@@ -53,15 +53,43 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <returns>if false, error parsing symbol path</returns>
         bool ParseSymbolPath(string symbolPath);
 
+        /// <summary>
+        /// Add the cloud symweb symbol server with authentication.
+        /// </summary>
+        /// <param name="includeInteractiveCredentials">specifies whether credentials requiring user interaction will be included in the default authentication flow</param>
+        /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional uses <see cref="DefaultTimeout"/> if null)</param>
+        /// <param name="retryCount">number of retries (optional uses <see cref="DefaultRetryCount"/> if null)</param>
+        /// <returns>if false, failure</returns>
+        public bool AddSymwebSymbolServer(
+            bool includeInteractiveCredentials = false,
+            int? timeoutInMinutes = null,
+            int? retryCount = null);
+
+        /// <summary>
+        /// Add symbol server to search path with a PAT.
+        /// </summary>
+        /// <param name="accessToken">PAT or access token</param>
+        /// <param name="symbolServerPath">symbol server url (optional, uses <see cref="DefaultSymbolPath"/> if null)</param>
+        /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional uses <see cref="DefaultTimeout"/> if null)</param>
+        /// <param name="retryCount">number of retries (optional uses <see cref="DefaultRetryCount"/> if null)</param>
+        /// <returns>if false, failure</returns>
+        public bool AddAuthenticatedSymbolServer(
+            string accessToken,
+            string symbolServerPath = null,
+            int? timeoutInMinutes = null,
+            int? retryCount = null);
+
         /// <summary>
         /// Add symbol server to search path.
         /// </summary>
         /// <param name="symbolServerPath">symbol server url (optional, uses <see cref="DefaultSymbolPath"/> if null)</param>
-        /// <param name="authToken">PAT for secure symbol server (optional)</param>
-        /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional, uses <see cref="DefaultTimeout"/> if null)</param>
-        /// <param name="retryCount">number of retries (optional, uses <see cref="DefaultRetryCount"/> if null)</param>
+        /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional uses <see cref="DefaultTimeout"/> if null)</param>
+        /// <param name="retryCount">number of retries (optional uses <see cref="DefaultRetryCount"/> if null)</param>
         /// <returns>if false, failure</returns>
-        bool AddSymbolServer(string symbolServerPath = null, string authToken = null, int? timeoutInMinutes = null, int? retryCount = null);
+        public bool AddSymbolServer(
+            string symbolServerPath = null,
+            int? timeoutInMinutes = null,
+            int? retryCount = null);
 
         /// <summary>
         /// Add cache path to symbol search path
index 3d165d44eb895a1b1c16a7029245c14961f8f7e6..9c6ca4f4da0e092e919caf29f080debf3304cb54 100644 (file)
@@ -1,6 +1,9 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System;
+using System.Net.Http.Headers;
+using System.Text;
 using Microsoft.Diagnostics.DebugServices;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
@@ -18,7 +21,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = "--ms", Aliases = new string[] { "-ms" }, Help = "Use the public Microsoft symbol server.")]
         public bool MicrosoftSymbolServer { get; set; }
 
-        [Option(Name = "--disable", Aliases = new string[] { "-disable" }, Help = "Clear or disable symbol download support.")]
+        [Option(Name = "--mi", Aliases = new string[] { "-mi" }, Help = "Use the internal symweb symbol server.")]
+        public bool InternalSymbolServer { get; set; }
+
+        [Option(Name = "--interactive", Aliases = new string[] { "-interactive" }, Help = "Allows user interaction will be included in the authentication flow.")]
+        public bool Interactive { get; set; }
+
+        [Option(Name = "--disable", Aliases = new string[] { "-disable", "-clear" }, Help = "Clear or disable symbol download support.")]
         public bool Disable { get; set; }
 
         [Option(Name = "--reset", Aliases = new string[] { "-reset" }, Help = "Reset the HTTP symbol servers clearing any cached failures.")]
@@ -27,6 +36,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = "--cache", Aliases = new string[] { "-cache" }, Help = "Specify a symbol cache directory.")]
         public string Cache { get; set; }
 
+        [Option(Name = "--nocache", Aliases = new string[] { "-nocache" }, Help = "Do not automatically add the default cache before a server.")]
+        public bool NoCache { get; set; }
+
         [Option(Name = "--directory", Aliases = new string[] { "-directory" }, Help = "Specify a directory to search for symbols.")]
         public string Directory { get; set; }
 
@@ -47,9 +59,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands
 
         public override void Invoke()
         {
-            if (MicrosoftSymbolServer && !string.IsNullOrEmpty(SymbolServerUrl))
+            if (MicrosoftSymbolServer && InternalSymbolServer)
             {
-                throw new DiagnosticsException("Cannot have -ms option and a symbol server path");
+                throw new DiagnosticsException("Cannot have both -ms and -mi options");
+            }
+            if ((MicrosoftSymbolServer || InternalSymbolServer) && !string.IsNullOrEmpty(SymbolServerUrl))
+            {
+                throw new DiagnosticsException("Cannot have -ms or -mi option and a symbol server path");
             }
             if (Disable)
             {
@@ -59,13 +75,24 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             {
                 SymbolService.Reset();
             }
-            if (MicrosoftSymbolServer || !string.IsNullOrEmpty(SymbolServerUrl))
+            if (MicrosoftSymbolServer || InternalSymbolServer || !string.IsNullOrEmpty(SymbolServerUrl))
             {
-                if (string.IsNullOrEmpty(Cache))
+                if (string.IsNullOrEmpty(Cache) && !NoCache)
                 {
                     Cache = SymbolService.DefaultSymbolCache;
                 }
-                SymbolService.AddSymbolServer(SymbolServerUrl, AccessToken, Timeout, RetryCount);
+                if (InternalSymbolServer)
+                {
+                    SymbolService.AddSymwebSymbolServer(includeInteractiveCredentials: Interactive, Timeout, RetryCount);
+                }
+                else if (AccessToken is not null)
+                {
+                    SymbolService.AddAuthenticatedSymbolServer(AccessToken, SymbolServerUrl, Timeout, RetryCount);
+                }
+                else
+                {
+                    SymbolService.AddSymbolServer(SymbolServerUrl, Timeout, RetryCount);
+                }
             }
             if (!string.IsNullOrEmpty(Cache))
             {
index 55c95c13d9d79c72e6fef6e5398a3e6a7d295c0f..ed8b2190e6f6b88a31b6577cad8a465c23b020a2 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
@@ -61,7 +62,7 @@ namespace Microsoft.SymbolStore.KeyGenerators
                     {
                         pdbs = _peFile.Pdbs.ToArray();
                     }
-                    catch (InvalidVirtualAddressException ex)
+                    catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException)
                     {
                         Tracer.Error("Reading PDB records for {0}: {1}", _path, ex.Message);
                     }
index 2f13376c6dc46a65b5f1e1ca42d9234e8c462d92..cd891449f7c227ca6b747fa76d8d27500000ce9c 100644 (file)
 
   <ItemGroup>
     <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
-<!--
-    <PackageReference Condition="'$(TargetFramework)' != 'net462'" Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
-    <PackageReference Condition="'$(TargetFramework)' == 'net462'" Include="System.Reflection.Metadata" Version="1.6.0" />
--->
+    <PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
   </ItemGroup>
 
   <ItemGroup>
index 9409babc98594bb3dc5c801bf514236685b7f776..5887261547843e13f7752269c3038f5d2a96c31a 100644 (file)
@@ -16,12 +16,12 @@ using System.Threading.Tasks;
 namespace Microsoft.SymbolStore.SymbolStores
 {
     /// <summary>
-    /// Basic http symbol store. The request can be authentication with a PAT for VSTS symbol stores.
+    /// Basic http symbol store. The request can be authentication with a PAT or bearer token.
     /// </summary>
     public class HttpSymbolStore : SymbolStore
     {
         private readonly HttpClient _client;
-        private readonly HttpClient _authenticatedClient;
+        private readonly Func<CancellationToken, ValueTask<AuthenticationHeaderValue>> _authenticationFunc;
         private bool _clientFailure;
 
         /// <summary>
@@ -41,10 +41,6 @@ namespace Microsoft.SymbolStore.SymbolStores
             set
             {
                 _client.Timeout = value;
-                if (_authenticatedClient != null)
-                {
-                    _authenticatedClient.Timeout = value;
-                }
             }
         }
 
@@ -59,36 +55,25 @@ namespace Microsoft.SymbolStore.SymbolStores
         /// <param name="tracer">logger</param>
         /// <param name="backingStore">next symbol store or null</param>
         /// <param name="symbolServerUri">symbol server url</param>
-        /// <param name="hasPAT">flag to indicate to create an authenticatedClient if there is a PAT</param>
-        private HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, bool hasPAT)
+        /// <param name="authenticationFunc">function that returns the authentication value for a request</param>
+        public HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, Func<CancellationToken, ValueTask<AuthenticationHeaderValue>> authenticationFunc)
             : base(tracer, backingStore)
         {
             Uri = symbolServerUri ?? throw new ArgumentNullException(nameof(symbolServerUri));
             if (!symbolServerUri.IsAbsoluteUri || symbolServerUri.IsFile)
             {
-                throw new ArgumentException(nameof(symbolServerUri));
+                throw new ArgumentException(null, nameof(symbolServerUri));
             }
+            _authenticationFunc = authenticationFunc;
 
-            // Normal unauthenticated client
+            // Create client
             _client = new HttpClient
             {
                 Timeout = TimeSpan.FromMinutes(4)
             };
 
-            if (hasPAT)
-            {
-                HttpClientHandler handler = new()
-                {
-                    AllowAutoRedirect = false
-                };
-                HttpClient client = new(handler)
-                {
-                    Timeout = TimeSpan.FromMinutes(4)
-                };
-                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
-                // Authorization is set in associated constructors.
-                _authenticatedClient = client;
-            }
+            // Force redirect logins to fail.
+            _client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
         }
 
         /// <summary>
@@ -97,15 +82,10 @@ namespace Microsoft.SymbolStore.SymbolStores
         /// <param name="tracer">logger</param>
         /// <param name="backingStore">next symbol store or null</param>
         /// <param name="symbolServerUri">symbol server url</param>
-        /// <param name="personalAccessToken">optional Basic Auth PAT or null if no authentication</param>
-        public HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, string personalAccessToken = null)
-            : this(tracer, backingStore, symbolServerUri, !string.IsNullOrEmpty(personalAccessToken))
+        /// <param name="accessToken">optional Basic Auth PAT or null if no authentication</param>
+        public HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, string accessToken = null)
+            : this(tracer, backingStore, symbolServerUri, string.IsNullOrEmpty(accessToken) ? null : GetAuthenticationFunc("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($":{accessToken}"))))
         {
-            // If PAT, create authenticated client with Basic Auth
-            if (!string.IsNullOrEmpty(personalAccessToken))
-            {
-                _authenticatedClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalAccessToken))));
-            }
         }
 
         /// <summary>
@@ -117,22 +97,22 @@ namespace Microsoft.SymbolStore.SymbolStores
         /// <param name="scheme">The scheme information to use for the AuthenticationHeaderValue</param>
         /// <param name="parameter">The parameter information to use for the AuthenticationHeaderValue</param>
         public HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, string scheme, string parameter)
-            : this(tracer, backingStore, symbolServerUri, true)
+            : this(tracer, backingStore, symbolServerUri, GetAuthenticationFunc(scheme, parameter))
         {
             if (string.IsNullOrEmpty(scheme))
             {
                 throw new ArgumentNullException(nameof(scheme));
             }
-
             if (string.IsNullOrEmpty(parameter))
             {
                 throw new ArgumentNullException(nameof(parameter));
             }
+        }
 
-            // Create authenticated header with given SymbolAuthHeader
-            _authenticatedClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, parameter);
-            // Force redirect logins to fail.
-            _authenticatedClient.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
+        private static Func<CancellationToken, ValueTask<AuthenticationHeaderValue>> GetAuthenticationFunc(string scheme, string parameter)
+        {
+            AuthenticationHeaderValue authenticationValue = new(scheme, parameter);
+            return (_) => new ValueTask<AuthenticationHeaderValue>(authenticationValue);
         }
 
         /// <summary>
@@ -149,13 +129,11 @@ namespace Microsoft.SymbolStore.SymbolStores
             Uri uri = GetRequestUri(key.Index);
 
             bool needsChecksumMatch = key.PdbChecksums.Any();
-
             if (needsChecksumMatch)
             {
                 string checksumHeader = string.Join(";", key.PdbChecksums);
-                HttpClient client = _authenticatedClient ?? _client;
                 Tracer.Information($"SymbolChecksum: {checksumHeader}");
-                client.DefaultRequestHeaders.Add("SymbolChecksum", checksumHeader);
+                _client.DefaultRequestHeaders.Add("SymbolChecksum", checksumHeader);
             }
 
             Stream stream = await GetFileStream(key.FullPathName, uri, token).ConfigureAwait(false);
@@ -193,7 +171,6 @@ namespace Microsoft.SymbolStore.SymbolStores
                 return null;
             }
             string fileName = Path.GetFileName(path);
-            HttpClient client = _authenticatedClient ?? _client;
             int retries = 0;
             while (true)
             {
@@ -203,25 +180,19 @@ namespace Microsoft.SymbolStore.SymbolStores
                 {
                     // Can not dispose the response (like via using) on success because then the content stream
                     // is disposed and it is returned by this function.
-                    HttpResponseMessage response = await client.GetAsync(requestUri, token).ConfigureAwait(false);
-                    if (response.StatusCode == HttpStatusCode.OK)
+                    using HttpRequestMessage request = new(HttpMethod.Get, requestUri);
+                    if (_authenticationFunc is not null)
                     {
-                        return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+                        request.Headers.Authorization = await _authenticationFunc(token).ConfigureAwait(false);
                     }
-                    if (response.StatusCode == HttpStatusCode.Found)
+                    using HttpResponseMessage response = await _client.SendAsync(request, token).ConfigureAwait(false);
+                    if (response.StatusCode == HttpStatusCode.OK)
                     {
-                        Uri location = response.Headers.Location;
-                        response.Dispose();
-
-                        response = await _client.GetAsync(location, token).ConfigureAwait(false);
-                        if (response.StatusCode == HttpStatusCode.OK)
-                        {
-                            return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-                        }
+                        byte[] buffer = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
+                        return new MemoryStream(buffer);
                     }
                     HttpStatusCode statusCode = response.StatusCode;
                     string reasonPhrase = response.ReasonPhrase;
-                    response.Dispose();
 
                     // The GET failed
 
@@ -287,7 +258,6 @@ namespace Microsoft.SymbolStore.SymbolStores
         public override void Dispose()
         {
             _client.Dispose();
-            _authenticatedClient?.Dispose();
             base.Dispose();
         }
 
index d7f7e0a21b38c81740257d793659acfdd5d3fd8c..8aa07ddeb70f02b5c6ff4b27de43c9907c79c1ca 100644 (file)
@@ -5,9 +5,12 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Net.Http.Headers;
 using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
+using Azure.Core;
+using Azure.Identity;
 using Microsoft.Diagnostic.Tools.Symbol.Properties;
 using Microsoft.FileFormats;
 using Microsoft.FileFormats.ELF;
@@ -25,11 +28,13 @@ namespace Microsoft.Diagnostics.Tools.Symbol
         {
             public Uri Uri;
             public string PersonalAccessToken;
+            public bool InternalSymwebServer;
         }
 
         private readonly List<string> InputFilePaths = new();
         private readonly List<string> CacheDirectories = new();
         private readonly List<ServerInfo> SymbolServers = new();
+        private TokenCredential TokenCredential = new DefaultAzureCredential(includeInteractiveCredentials: true);
         private string OutputDirectory;
         private TimeSpan? Timeout;
         private bool Overwrite;
@@ -63,6 +68,11 @@ namespace Microsoft.Diagnostics.Tools.Symbol
                         program.SymbolServers.Add(new ServerInfo { Uri = uri, PersonalAccessToken = null });
                         break;
 
+                     case "--internal-server":
+                        Uri.TryCreate("https://symweb.azurefd.net/", UriKind.Absolute, out uri);
+                        program.SymbolServers.Add(new ServerInfo { Uri = uri, PersonalAccessToken = null, InternalSymwebServer = true });
+                        break;
+
                     case "--authenticated-server-path":
                         if (++i < args.Length)
                         {
@@ -256,7 +266,14 @@ namespace Microsoft.Diagnostics.Tools.Symbol
 
             foreach (ServerInfo server in ((IEnumerable<ServerInfo>)SymbolServers).Reverse())
             {
-                store = new HttpSymbolStore(Tracer, store, server.Uri, server.PersonalAccessToken);
+                if (server.InternalSymwebServer)
+                {
+                    store = new HttpSymbolStore(Tracer, store, server.Uri, SymwebAuthenticationFunc);
+                }
+                else
+                {
+                    store = new HttpSymbolStore(Tracer, store, server.Uri, server.PersonalAccessToken);
+                }
                 if (Timeout.HasValue && store is HttpSymbolStore http)
                 {
                     http.Timeout = Timeout.Value;
@@ -277,6 +294,19 @@ namespace Microsoft.Diagnostics.Tools.Symbol
             return store;
         }
 
+        private async ValueTask<AuthenticationHeaderValue> SymwebAuthenticationFunc(CancellationToken token)
+        {
+            try
+            {
+                AccessToken accessToken = await TokenCredential.GetTokenAsync(new TokenRequestContext(["api://af9e1c69-e5e9-4331-8cc5-cdf93d57bafa/.default"]), token).ConfigureAwait(false);
+                return new AuthenticationHeaderValue("Bearer", accessToken.Token);
+            }
+            catch (Exception ex) when (ex is CredentialUnavailableException or AuthenticationFailedException)
+            {
+                return null;
+            }
+        }
+
         private sealed class SymbolStoreKeyWrapper
         {
             public readonly SymbolStoreKey Key;
index 86275cc922356b7f14513458e31d5bf8eef112d5..bf363f1a99b0c988528fd2c763fec8b344034823 100644 (file)
       <PrivateAssets>All</PrivateAssets>
     </ProjectReference>
   </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Azure.Identity" Version="$(AzureIdentityVersion)" />
+  </ItemGroup>
   
   <ItemGroup>
     <Compile Update="Properties\Resources.Designer.cs">