| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | using System; |
| | 5 | | using System.IO; |
| | 6 | | using System.Linq; |
| | 7 | | using System.Runtime.InteropServices; |
| | 8 | | using System.Text.Json; |
| | 9 | | using System.Threading; |
| | 10 | | using System.Threading.Tasks; |
| | 11 | | using Azure.Core; |
| | 12 | | using Azure.Core.Pipeline; |
| | 13 | | using Microsoft.Identity.Client; |
| | 14 | |
|
| | 15 | | namespace Azure.Identity |
| | 16 | | { |
| | 17 | | /// <summary> |
| | 18 | | /// Enables authentication to Azure Active Directory using data from Visual Studio Code. |
| | 19 | | /// </summary> |
| | 20 | | public class VisualStudioCodeCredential : TokenCredential |
| | 21 | | { |
| | 22 | | private const string CredentialsSection = "VS Code Azure"; |
| | 23 | | private const string ClientId = "aebc6443-996d-45c2-90f0-388ff96faa56"; |
| | 24 | | private readonly IVisualStudioCodeAdapter _vscAdapter; |
| | 25 | | private readonly IFileSystemService _fileSystem; |
| | 26 | | private readonly CredentialPipeline _pipeline; |
| | 27 | | private readonly string _tenantId; |
| | 28 | | private readonly MsalPublicClient _client; |
| | 29 | |
|
| | 30 | | /// <summary> |
| | 31 | | /// Creates a new instance of the <see cref="VisualStudioCodeCredential"/>. |
| | 32 | | /// </summary> |
| 64 | 33 | | public VisualStudioCodeCredential() : this(default, default, default, default, default) { } |
| | 34 | |
|
| | 35 | | /// <summary> |
| | 36 | | /// Creates a new instance of the <see cref="VisualStudioCodeCredential"/> with the specified options. |
| | 37 | | /// </summary> |
| | 38 | | /// <param name="options">Options for configuring the credential.</param> |
| 0 | 39 | | public VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options) : this(options, default, default, d |
| | 40 | |
|
| 98 | 41 | | internal VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options, CredentialPipeline pipeline, Msal |
| | 42 | | { |
| 98 | 43 | | _tenantId = options?.TenantId ?? "common"; |
| 98 | 44 | | _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); |
| 98 | 45 | | _client = client ?? new MsalPublicClient(_pipeline, options?.TenantId, ClientId, null, null); |
| 98 | 46 | | _fileSystem = fileSystem ?? FileSystemService.Default; |
| 98 | 47 | | _vscAdapter = vscAdapter ?? GetVscAdapter(); |
| 98 | 48 | | } |
| | 49 | |
|
| | 50 | | /// <inheritdoc /> |
| | 51 | | public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken |
| 46 | 52 | | => await GetTokenImplAsync(requestContext, true, cancellationToken).ConfigureAwait(false); |
| | 53 | |
|
| | 54 | | /// <inheritdoc /> |
| | 55 | | public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) |
| 48 | 56 | | => GetTokenImplAsync(requestContext, false, cancellationToken).EnsureCompleted(); |
| | 57 | |
|
| | 58 | | private async ValueTask<AccessToken> GetTokenImplAsync(TokenRequestContext requestContext, bool async, Cancellat |
| | 59 | | { |
| 94 | 60 | | using CredentialDiagnosticScope scope = _pipeline.StartGetTokenScope("VisualStudioCodeCredential.GetToken", |
| | 61 | |
|
| | 62 | | try |
| | 63 | | { |
| 90 | 64 | | GetUserSettings(out var tenant, out var environmentName); |
| | 65 | |
|
| 90 | 66 | | var cloudInstance = GetAzureCloudInstance(environmentName); |
| 90 | 67 | | var storedCredentials = _vscAdapter.GetCredentials(CredentialsSection, environmentName); |
| | 68 | |
|
| 72 | 69 | | if (!IsRefreshTokenString(storedCredentials)) |
| | 70 | | { |
| 8 | 71 | | throw new CredentialUnavailableException("Need to re-authenticate user in VSCode Azure Account."); |
| | 72 | | } |
| | 73 | |
|
| 64 | 74 | | var result = await _client.AcquireTokenByRefreshToken(requestContext.Scopes, storedCredentials, cloudIns |
| 60 | 75 | | return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); |
| | 76 | | } |
| 4 | 77 | | catch (MsalUiRequiredException e) |
| | 78 | | { |
| 4 | 79 | | throw scope.FailWrapAndThrow(new CredentialUnavailableException($"{nameof(VisualStudioCodeCredential)} a |
| | 80 | | } |
| 26 | 81 | | catch (Exception e) |
| | 82 | | { |
| 26 | 83 | | throw scope.FailWrapAndThrow(e); |
| | 84 | | } |
| 60 | 85 | | } |
| | 86 | |
|
| | 87 | | private static bool IsRefreshTokenString(string str) |
| | 88 | | { |
| 4752 | 89 | | for (var index = 0; index < str.Length; index++) |
| | 90 | | { |
| 2312 | 91 | | var ch = (uint)str[index]; |
| 2312 | 92 | | if ((ch < '0' || ch > '9') && (ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && ch != '_' && ch != '-' |
| | 93 | | { |
| 8 | 94 | | return false; |
| | 95 | | } |
| | 96 | | } |
| | 97 | |
|
| 64 | 98 | | return true; |
| | 99 | | } |
| | 100 | |
|
| | 101 | | private void GetUserSettings(out string tenant, out string environmentName) |
| | 102 | | { |
| 90 | 103 | | var path = _vscAdapter.GetUserSettingsPath(); |
| 90 | 104 | | tenant = _tenantId; |
| 90 | 105 | | environmentName = "Azure"; |
| | 106 | |
|
| | 107 | | try |
| | 108 | | { |
| 90 | 109 | | var content = _fileSystem.ReadAllText(path); |
| 78 | 110 | | var root = JsonDocument.Parse(content).RootElement; |
| | 111 | |
|
| 74 | 112 | | if (root.TryGetProperty("azure.tenant", out JsonElement tenantProperty)) |
| | 113 | | { |
| 74 | 114 | | tenant = tenantProperty.GetString(); |
| | 115 | | } |
| | 116 | |
|
| 74 | 117 | | if (root.TryGetProperty("azure.cloud", out JsonElement environmentProperty)) |
| | 118 | | { |
| 48 | 119 | | environmentName = environmentProperty.GetString(); |
| | 120 | | } |
| 74 | 121 | | } |
| 24 | 122 | | catch (IOException) { } |
| 8 | 123 | | catch (JsonException) { } |
| 90 | 124 | | } |
| | 125 | |
|
| | 126 | | private static IVisualStudioCodeAdapter GetVscAdapter() |
| | 127 | | { |
| 58 | 128 | | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| | 129 | | { |
| 58 | 130 | | return new WindowsVisualStudioCodeAdapter(); |
| | 131 | | } |
| | 132 | |
|
| 0 | 133 | | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
| | 134 | | { |
| 0 | 135 | | return new MacosVisualStudioCodeAdapter(); |
| | 136 | | } |
| | 137 | |
|
| 0 | 138 | | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
| | 139 | | { |
| 0 | 140 | | return new LinuxVisualStudioCodeAdapter(); |
| | 141 | | } |
| | 142 | |
|
| 0 | 143 | | throw new PlatformNotSupportedException(); |
| | 144 | | } |
| | 145 | |
|
| | 146 | | private static AzureCloudInstance GetAzureCloudInstance(string name) => |
| 90 | 147 | | name switch |
| 90 | 148 | | { |
| 132 | 149 | | "Azure" => AzureCloudInstance.AzurePublic, |
| 0 | 150 | | "AzureChina" => AzureCloudInstance.AzureChina, |
| 0 | 151 | | "AzureGermanCloud" => AzureCloudInstance.AzureGermany, |
| 0 | 152 | | "AzureUSGovernment" => AzureCloudInstance.AzureUsGovernment, |
| 138 | 153 | | _ => AzureCloudInstance.AzurePublic |
| 90 | 154 | | }; |
| | 155 | | } |
| | 156 | | } |