< Summary

Class:Azure.Identity.VisualStudioCredential
Assembly:Azure.Identity
File(s):C:\Git\azure-sdk-for-net\sdk\identity\Azure.Identity\src\VisualStudioCredential.cs
Covered lines:98
Uncovered lines:6
Coverable lines:104
Total lines:256
Line coverage:94.2% (98 of 104)
Covered branches:31
Total branches:36
Branch coverage:86.1% (31 of 36)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor()-100%100%
.ctor(...)-100%50%
.ctor(...)-100%100%
GetTokenAsync()-100%100%
GetToken(...)-100%100%
GetTokenImplAsync()-100%100%
GetTokenProviderPath()-66.67%50%
RunProcessesAsync()-85.19%87.5%
GetProcessStartInfos(...)-95.24%83.33%
GetTokenProviders(...)-100%100%
GetTokenProviderContent(...)-100%100%
GetStringArrayPropertyValue(...)-100%100%
get_Path()-100%100%
get_Arguments()-100%100%
.ctor(...)-100%100%
CompareTo(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\identity\Azure.Identity\src\VisualStudioCredential.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.IO;
 8using System.Runtime.ExceptionServices;
 9using System.Runtime.InteropServices;
 10using System.Text;
 11using System.Text.Json;
 12using System.Threading;
 13using System.Threading.Tasks;
 14using Azure.Core;
 15using Azure.Core.Pipeline;
 16
 17
 18namespace Azure.Identity
 19{
 20    /// <summary>
 21    /// Enables authentication to Azure Active Directory using data from Visual Studio
 22    /// </summary>
 23    public class VisualStudioCredential : TokenCredential
 24    {
 25        private const string TokenProviderFilePath = @".IdentityService\AzureServiceAuth\tokenprovider.json";
 26        private const string ResourceArgumentName = "--resource";
 27        private const string TenantArgumentName = "--tenant";
 28
 29        private readonly CredentialPipeline _pipeline;
 30        private readonly string _tenantId;
 31        private readonly IFileSystemService _fileSystem;
 32        private readonly IProcessService _processService;
 33
 34        /// <summary>
 35        /// Creates a new instance of the <see cref="VisualStudioCredential"/>.
 36        /// </summary>
 9637        public VisualStudioCredential() : this(null) { }
 38
 39        /// <summary>
 40        /// Creates a new instance of the <see cref="VisualStudioCredential"/> with the specified options.
 41        /// </summary>
 42        /// <param name="options">Options for configuring the credential.</param>
 9643        public VisualStudioCredential(VisualStudioCredentialOptions options) : this(options?.TenantId, CredentialPipelin
 44
 13045        internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IFileSystemService fileSystem, IPr
 46        {
 13047            _tenantId = tenantId;
 13048            _pipeline = pipeline ?? CredentialPipeline.GetInstance(null);
 13049            _fileSystem = fileSystem ?? FileSystemService.Default;
 13050            _processService = processService ?? ProcessService.Default;
 13051        }
 52
 53        /// <inheritdoc />
 54        public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken
 3855            => await GetTokenImplAsync(requestContext, true, cancellationToken).ConfigureAwait(false);
 56
 57        /// <inheritdoc />
 58        public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
 4059            => GetTokenImplAsync(requestContext, false, cancellationToken).EnsureCompleted();
 60
 61        private async ValueTask<AccessToken> GetTokenImplAsync(TokenRequestContext requestContext, bool async, Cancellat
 62        {
 7863            using CredentialDiagnosticScope scope = _pipeline.StartGetTokenScope("VisualStudioCredential.GetToken", requ
 64
 65            try
 66            {
 7867                var tokenProviderPath = GetTokenProviderPath();
 7868                var tokenProviders = GetTokenProviders(tokenProviderPath);
 69
 4070                var resource = ScopeUtilities.ScopesToResource(requestContext.Scopes);
 4071                var processStartInfos = GetProcessStartInfos(tokenProviders, resource, cancellationToken);
 72
 3673                if (processStartInfos.Count == 0)
 74                {
 475                    throw new CredentialUnavailableException("No installed instance of Visual Studio was found");
 76                }
 77
 3278                var accessToken = await RunProcessesAsync(processStartInfos, async, cancellationToken).ConfigureAwait(fa
 1679                return scope.Succeeded(accessToken);
 80            }
 6281            catch (Exception e)
 82            {
 6283                throw scope.FailWrapAndThrow(e);
 84            }
 1685        }
 86
 87        private static string GetTokenProviderPath()
 88        {
 7889            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
 90            {
 7891                return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), TokenProv
 92            }
 93
 094            throw new CredentialUnavailableException($"Operating system {RuntimeInformation.OSDescription} isn't support
 95        }
 96
 97        private async Task<AccessToken> RunProcessesAsync(List<ProcessStartInfo> processStartInfos, bool async, Cancella
 98        {
 3299            var exceptions = new List<Exception>();
 136100            foreach (ProcessStartInfo processStartInfo in processStartInfos)
 101            {
 44102                string output = string.Empty;
 103                try
 104                {
 44105                    var processRunner = new ProcessRunner(_processService.Create(processStartInfo), TimeSpan.FromSeconds
 44106                    output = async
 44107                        ? await processRunner.RunAsync().ConfigureAwait(false)
 44108                        : processRunner.Run();
 109
 24110                    JsonElement root = JsonDocument.Parse(output).RootElement;
 16111                    string accessToken = root.GetProperty("access_token").GetString();
 16112                    DateTimeOffset expiresOn = root.GetProperty("expires_on").GetDateTimeOffset();
 16113                    return new AccessToken(accessToken, expiresOn);
 114                }
 4115                catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
 116                {
 0117                    exceptions.Add(new CredentialUnavailableException($"Process \"{processStartInfo.FileName}\" has fail
 0118                }
 8119                catch (JsonException exception)
 120                {
 8121                    exceptions.Add(new CredentialUnavailableException($"Process \"{processStartInfo.FileName}\" has non-
 8122                }
 20123                catch (Exception exception)
 124                {
 20125                    exceptions.Add(exception);
 20126                }
 28127            }
 128
 16129            switch (exceptions.Count) {
 130                case 0:
 0131                    throw new CredentialUnavailableException("No installed instance of Visual Studio was able to get cre
 132                case 1:
 12133                    ExceptionDispatchInfo.Capture(exceptions[0]).Throw();
 0134                    return default;
 135                default:
 4136                    throw new AggregateException(exceptions);
 137            }
 16138        }
 139
 140        private List<ProcessStartInfo> GetProcessStartInfos(VisualStudioTokenProvider[] visualStudioTokenProviders, stri
 141        {
 40142            List<ProcessStartInfo> processStartInfos = new List<ProcessStartInfo>();
 40143            StringBuilder arguments = new StringBuilder();
 144
 196145            foreach (VisualStudioTokenProvider tokenProvider in visualStudioTokenProviders)
 146            {
 60147                cancellationToken.ThrowIfCancellationRequested();
 148
 149                // If file does not exist, the version of Visual Studio that set the token provider may be uninstalled.
 56150                if (!_fileSystem.FileExists(tokenProvider.Path))
 151                {
 152                    continue;
 153                }
 154
 48155                arguments.Clear();
 48156                arguments.Append(ResourceArgumentName).Append(' ').Append(resource);
 157
 48158                if (_tenantId != default)
 159                {
 0160                    arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(_tenantId);
 161                }
 162
 163                // Add the arguments set in the token provider file.
 48164                if (tokenProvider.Arguments?.Length > 0)
 165                {
 192166                    foreach (var argument in tokenProvider.Arguments)
 167                    {
 48168                        arguments.Append(' ').Append(argument);
 169                    }
 170                }
 171
 48172                var startInfo = new ProcessStartInfo
 48173                {
 48174                    FileName = tokenProvider.Path,
 48175                    Arguments = arguments.ToString(),
 48176                    ErrorDialog = false,
 48177                    CreateNoWindow = true,
 48178                };
 179
 48180                processStartInfos.Add(startInfo);
 181            }
 182
 36183            return processStartInfos;
 184        }
 185
 186        private VisualStudioTokenProvider[] GetTokenProviders(string tokenProviderPath)
 187        {
 78188            var content = GetTokenProviderContent(tokenProviderPath);
 189
 62190            using JsonDocument document = JsonDocument.Parse(content);
 191
 62192            JsonElement providersElement = document.RootElement.GetProperty("TokenProviders");
 193
 40194            var providers = new VisualStudioTokenProvider[providersElement.GetArrayLength()];
 200195            for (int i = 0; i < providers.Length; i++)
 196            {
 60197                JsonElement providerElement = providersElement[i];
 198
 60199                var path = providerElement.GetProperty("Path").GetString();
 60200                var preference = providerElement.GetProperty("Preference").GetInt32();
 60201                var arguments = GetStringArrayPropertyValue(providerElement, "Arguments");
 202
 60203                providers[i] = new VisualStudioTokenProvider(path, arguments, preference);
 204            }
 205
 40206            Array.Sort(providers);
 40207            return providers;
 40208        }
 209
 210        private string GetTokenProviderContent(string tokenProviderPath)
 211        {
 212            try
 213            {
 78214                return _fileSystem.ReadAllText(tokenProviderPath);
 215            }
 12216            catch (FileNotFoundException exception)
 217            {
 12218                throw new CredentialUnavailableException($"Visual Studio Token provider file not found at {tokenProvider
 219            }
 4220            catch (IOException exception)
 221            {
 4222                throw new CredentialUnavailableException($"Visual Studio Token provider can't be accessed at {tokenProvi
 223            }
 62224        }
 225
 226        private static string[] GetStringArrayPropertyValue(JsonElement element, string name)
 227        {
 60228            JsonElement arrayElement = element.GetProperty(name);
 60229            var array = new string[arrayElement.GetArrayLength()];
 230
 240231            for (int i = 0; i < array.Length; i++)
 232            {
 60233                array[i] = arrayElement[i].GetString();
 234            }
 235
 60236            return array;
 237        }
 238
 239        private readonly struct VisualStudioTokenProvider : IComparable<VisualStudioTokenProvider>
 240        {
 241            private readonly int _preference;
 242
 104243            public string Path { get; }
 96244            public string[] Arguments { get; }
 245
 246            public VisualStudioTokenProvider(string path, string[] arguments, int preference)
 247            {
 60248                Path = path;
 60249                Arguments = arguments;
 60250                _preference = preference;
 60251            }
 252
 24253            public int CompareTo(VisualStudioTokenProvider other) => _preference.CompareTo(other._preference);
 254        }
 255    }
 256}