diff --git a/eng/Versions.props b/eng/Versions.props index a82002ed44..460a868732 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -27,6 +27,7 @@ false + 1.12.0 @@ -48,6 +49,7 @@ 4.5.1 4.5.5 4.3.0 + 4.5.4 8.0.0 8.0.4 2.0.3 diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj index 5f17fb0a49..cbb95d87ee 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs index 4ed23f8062..1247620b74 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs @@ -7,11 +7,15 @@ 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 @@ public class SymbolService : ISymbolService /// Symbol server URLs /// 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 @@ void ParseServer(int start) } 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()) @@ -226,19 +244,89 @@ void ParseServer(int start) return true; } + /// + /// Add the cloud symweb symbol server with authentication. + /// + /// specifies whether credentials requiring user interaction will be included in the default authentication flow + /// symbol server timeout in minutes (optional uses if null) + /// number of retries (optional uses if null) + /// if false, failure + public bool AddSymwebSymbolServer( + bool includeInteractiveCredentials = false, + int? timeoutInMinutes = null, + int? retryCount = null) + { + TokenCredential tokenCredential = new DefaultAzureCredential(includeInteractiveCredentials); + AccessToken accessToken; + async ValueTask 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); + } + + /// + /// Add symbol server to search path. The server URL can be the cloud symweb. + /// + /// PAT or access token + /// symbol server url (optional, uses if null) + /// symbol server timeout in minutes (optional uses if null) + /// number of retries (optional uses if null) + /// if false, failure + 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(authenticationValue)); + } + /// /// Add symbol server to search path. /// /// symbol server url (optional, uses if null) - /// PAT for secure symbol server (optional) /// symbol server timeout in minutes (optional uses if null) /// number of retries (optional uses if null) /// if false, failure public bool AddSymbolServer( string symbolServerPath = null, - string authToken = null, int? timeoutInMinutes = null, int? retryCount = null) + { + return AddSymbolServer(symbolServerPath, timeoutInMinutes, retryCount, authenticationFunc: null); + } + + /// + /// Add symbol server to search path. + /// + /// symbol server url (optional, uses if null) + /// symbol server timeout in minutes (optional uses if null) + /// number of retries (optional uses if null) + /// function that returns the authentication value for a request + /// if false, failure + public bool AddSymbolServer( + string symbolServerPath, + int? timeoutInMinutes, + int? retryCount, + Func> authenticationFunc) { // Add symbol server URL if exists symbolServerPath ??= DefaultSymbolPath; @@ -260,9 +348,11 @@ public bool AddSymbolServer( if (!IsDuplicateSymbolStore(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 @@ public override string ToString() return sb.ToString(); } + /// + /// Returns true if cloud symweb server + /// + /// + 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; + } + } + /// /// Attempts to download/retrieve from cache the key. /// diff --git a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs index b32b19cde0..fb3c23bdc4 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs @@ -53,15 +53,43 @@ public interface ISymbolService /// if false, error parsing symbol path bool ParseSymbolPath(string symbolPath); + /// + /// Add the cloud symweb symbol server with authentication. + /// + /// specifies whether credentials requiring user interaction will be included in the default authentication flow + /// symbol server timeout in minutes (optional uses if null) + /// number of retries (optional uses if null) + /// if false, failure + public bool AddSymwebSymbolServer( + bool includeInteractiveCredentials = false, + int? timeoutInMinutes = null, + int? retryCount = null); + + /// + /// Add symbol server to search path with a PAT. + /// + /// PAT or access token + /// symbol server url (optional, uses if null) + /// symbol server timeout in minutes (optional uses if null) + /// number of retries (optional uses if null) + /// if false, failure + public bool AddAuthenticatedSymbolServer( + string accessToken, + string symbolServerPath = null, + int? timeoutInMinutes = null, + int? retryCount = null); + /// /// Add symbol server to search path. /// /// symbol server url (optional, uses if null) - /// PAT for secure symbol server (optional) - /// symbol server timeout in minutes (optional, uses if null) - /// number of retries (optional, uses if null) + /// symbol server timeout in minutes (optional uses if null) + /// number of retries (optional uses if null) /// if false, failure - 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); /// /// Add cache path to symbol search path diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs index 3d165d44eb..9c6ca4f4da 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs @@ -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 @@ public class SetSymbolServerCommand : CommandBase [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 @@ public class SetSymbolServerCommand : CommandBase [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 @@ public class SetSymbolServerCommand : CommandBase 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 @@ public override void Invoke() { 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)) { diff --git a/src/Microsoft.SymbolStore/KeyGenerators/PEFileKeyGenerator.cs b/src/Microsoft.SymbolStore/KeyGenerators/PEFileKeyGenerator.cs index 55c95c13d9..ed8b2190e6 100644 --- a/src/Microsoft.SymbolStore/KeyGenerators/PEFileKeyGenerator.cs +++ b/src/Microsoft.SymbolStore/KeyGenerators/PEFileKeyGenerator.cs @@ -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 @@ public override IEnumerable GetKeys(KeyTypeFlags flags) { 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); } diff --git a/src/Microsoft.SymbolStore/Microsoft.SymbolStore.csproj b/src/Microsoft.SymbolStore/Microsoft.SymbolStore.csproj index 2f13376c6d..cd891449f7 100644 --- a/src/Microsoft.SymbolStore/Microsoft.SymbolStore.csproj +++ b/src/Microsoft.SymbolStore/Microsoft.SymbolStore.csproj @@ -15,10 +15,7 @@ - + diff --git a/src/Microsoft.SymbolStore/SymbolStores/HttpSymbolStore.cs b/src/Microsoft.SymbolStore/SymbolStores/HttpSymbolStore.cs index 9409babc98..5887261547 100644 --- a/src/Microsoft.SymbolStore/SymbolStores/HttpSymbolStore.cs +++ b/src/Microsoft.SymbolStore/SymbolStores/HttpSymbolStore.cs @@ -16,12 +16,12 @@ namespace Microsoft.SymbolStore.SymbolStores { /// - /// 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. /// public class HttpSymbolStore : SymbolStore { private readonly HttpClient _client; - private readonly HttpClient _authenticatedClient; + private readonly Func> _authenticationFunc; private bool _clientFailure; /// @@ -41,10 +41,6 @@ public TimeSpan Timeout set { _client.Timeout = value; - if (_authenticatedClient != null) - { - _authenticatedClient.Timeout = value; - } } } @@ -59,36 +55,25 @@ public TimeSpan Timeout /// logger /// next symbol store or null /// symbol server url - /// flag to indicate to create an authenticatedClient if there is a PAT - private HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, bool hasPAT) + /// function that returns the authentication value for a request + public HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, Func> 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"); } /// @@ -97,15 +82,10 @@ private HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServ /// logger /// next symbol store or null /// symbol server url - /// optional Basic Auth PAT or null if no authentication - public HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServerUri, string personalAccessToken = null) - : this(tracer, backingStore, symbolServerUri, !string.IsNullOrEmpty(personalAccessToken)) + /// optional Basic Auth PAT or null if no authentication + 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)))); - } } /// @@ -117,22 +97,22 @@ public HttpSymbolStore(ITracer tracer, SymbolStore backingStore, Uri symbolServe /// The scheme information to use for the AuthenticationHeaderValue /// The parameter information to use for the AuthenticationHeaderValue 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> GetAuthenticationFunc(string scheme, string parameter) + { + AuthenticationHeaderValue authenticationValue = new(scheme, parameter); + return (_) => new ValueTask(authenticationValue); } /// @@ -149,13 +129,11 @@ protected override async Task GetFileInner(SymbolStoreKey key, 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 @@ protected async Task GetFileStream(string path, Uri requestUri, Cancella return null; } string fileName = Path.GetFileName(path); - HttpClient client = _authenticatedClient ?? _client; int retries = 0; while (true) { @@ -203,25 +180,19 @@ protected async Task GetFileStream(string path, Uri requestUri, Cancella { // 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 @@ protected async Task GetFileStream(string path, Uri requestUri, Cancella public override void Dispose() { _client.Dispose(); - _authenticatedClient?.Dispose(); base.Dispose(); } diff --git a/src/Tools/dotnet-symbol/Program.cs b/src/Tools/dotnet-symbol/Program.cs index d7f7e0a21b..8aa07ddeb7 100644 --- a/src/Tools/dotnet-symbol/Program.cs +++ b/src/Tools/dotnet-symbol/Program.cs @@ -5,9 +5,12 @@ 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 @@ private struct ServerInfo { public Uri Uri; public string PersonalAccessToken; + public bool InternalSymwebServer; } private readonly List InputFilePaths = new(); private readonly List CacheDirectories = new(); private readonly List SymbolServers = new(); + private TokenCredential TokenCredential = new DefaultAzureCredential(includeInteractiveCredentials: true); private string OutputDirectory; private TimeSpan? Timeout; private bool Overwrite; @@ -63,6 +68,11 @@ public static void Main(string[] args) 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 @@ private Microsoft.SymbolStore.SymbolStores.SymbolStore BuildSymbolStore() foreach (ServerInfo server in ((IEnumerable)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 @@ private Microsoft.SymbolStore.SymbolStores.SymbolStore BuildSymbolStore() return store; } + private async ValueTask 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; diff --git a/src/Tools/dotnet-symbol/dotnet-symbol.csproj b/src/Tools/dotnet-symbol/dotnet-symbol.csproj index 86275cc922..bf363f1a99 100644 --- a/src/Tools/dotnet-symbol/dotnet-symbol.csproj +++ b/src/Tools/dotnet-symbol/dotnet-symbol.csproj @@ -14,6 +14,10 @@ All + + + +