From 98b1fa5c87675631085065710bd4674d9397dcb7 Mon Sep 17 00:00:00 2001 From: Yan Xu <yanxu1@microsoft.com> Date: Mon, 5 Aug 2024 15:45:32 +0800 Subject: [PATCH] [Az.Accounts] Remove duplicated code copied from Azure.Identity (#25733) * use Azure.Identity * update README.md * update ChangeLog.md * move TaskExtensions.cs to src/Accounts/Authentication/KeyStore * Update ChangeLog.md Polish change log --------- Co-authored-by: Jin Lei <54836179+msJinLei@users.noreply.github.com> --- src/Accounts/Accounts/ChangeLog.md | 3 + ...tAcquireTokenParameterBuilderExtensions.cs | 36 - .../Identity/AuthenticationAccount.cs | 36 - .../Identity/AuthenticationRecord.cs | 237 ---- .../Identity/AzureAuthorityHosts.cs | 81 -- .../Identity/AzureEventSource.cs | 101 -- .../Identity/AzureIdentityEventSource.cs | 360 ------ .../Authentication/Identity/Constants.cs | 61 - .../Identity/Core/AppContextSwitchHelper.cs | 52 - .../Authentication/Identity/Core/Argument.cs | 257 ---- .../Identity/Core/AsyncLockWithValue.cs | 267 ----- .../Identity/Core/ClientDiagnostics.cs | 80 -- .../Identity/Core/DiagnosticScope.cs | 1062 ----------------- .../Identity/Core/DiagnosticScopeFactory.cs | 124 -- .../Identity/Core/HttpMessageSanitizer.cs | 151 --- .../Core/HttpPipelineMessageHandler.cs | 118 -- .../Identity/Core/LightweightPkcs8Decoder.cs | 370 ------ .../Authentication/Identity/Core/PemReader.cs | 310 ----- .../Identity/CredentialDiagnosticScope.cs | 103 -- .../Identity/CredentialPipeline.cs | 93 -- .../Credentials/ClientAssertionCredential.cs | 134 --- .../ClientAssertionCredentialOptions.cs | 39 - .../ISupportsDisableInstanceDiscovery.cs | 29 - .../ISupportsTokenCachePersistenceOptions.cs | 27 - .../Identity/EnvironmentVariables.cs | 61 - .../Identity/HttpPipelineClientFactory.cs | 41 - .../Authentication/Identity/IScopeHandler.cs | 28 - .../ISupportsAdditionallyAllowedTenants.cs | 29 - .../Identity/IX509Certificate2Provider.cs | 29 - .../Identity/IdentityCompatSwitches.cs | 37 - ...nagedIdentityRequestFailedDetailsParser.cs | 53 - .../Identity/ManagedIdentitySource.cs | 190 --- .../Identity/MsalCacheHelperWrapper.cs | 100 -- .../Authentication/Identity/MsalClientBase.cs | 128 -- .../Identity/MsalConfidentialClient.cs | 306 ----- .../Authentication/Identity/README.md | 17 - .../Identity/ScopeGroupHandler.cs | 146 --- .../Identity/TenantIdResolver.cs | 84 -- .../Authentication/Identity/TokenCache.cs | 304 ----- .../Authentication/Identity/TokenCacheData.cs | 39 - .../Identity/TokenCacheRefreshArgs.cs | 53 - .../Identity/TokenCacheUpdatedArgs.cs | 44 - .../Authentication/Identity/TokenHelper.cs | 89 -- .../Identity/UnsafeTokenCacheOptions.cs | 50 - .../Authentication/Identity/Validations.cs | 92 -- .../X509Certificate2FromFileProvider.cs | 156 --- .../X509Certificate2FromObjectProvider.cs | 40 - .../KeyStore/AsyncLockWithValue.cs | 1 - .../Core => KeyStore}/TaskExtensions.cs | 2 +- .../ClientAssertionAuthenticator.cs | 2 +- 50 files changed, 5 insertions(+), 6247 deletions(-) delete mode 100644 src/Accounts/Authentication/Identity/AbstractAcquireTokenParameterBuilderExtensions.cs delete mode 100644 src/Accounts/Authentication/Identity/AuthenticationAccount.cs delete mode 100644 src/Accounts/Authentication/Identity/AuthenticationRecord.cs delete mode 100644 src/Accounts/Authentication/Identity/AzureAuthorityHosts.cs delete mode 100644 src/Accounts/Authentication/Identity/AzureEventSource.cs delete mode 100644 src/Accounts/Authentication/Identity/AzureIdentityEventSource.cs delete mode 100644 src/Accounts/Authentication/Identity/Constants.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/AppContextSwitchHelper.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/Argument.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/AsyncLockWithValue.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/ClientDiagnostics.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/DiagnosticScope.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/DiagnosticScopeFactory.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/HttpMessageSanitizer.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/HttpPipelineMessageHandler.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/LightweightPkcs8Decoder.cs delete mode 100644 src/Accounts/Authentication/Identity/Core/PemReader.cs delete mode 100644 src/Accounts/Authentication/Identity/CredentialDiagnosticScope.cs delete mode 100644 src/Accounts/Authentication/Identity/CredentialPipeline.cs delete mode 100644 src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredential.cs delete mode 100644 src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredentialOptions.cs delete mode 100644 src/Accounts/Authentication/Identity/Credentials/ISupportsDisableInstanceDiscovery.cs delete mode 100644 src/Accounts/Authentication/Identity/Credentials/ISupportsTokenCachePersistenceOptions.cs delete mode 100644 src/Accounts/Authentication/Identity/EnvironmentVariables.cs delete mode 100644 src/Accounts/Authentication/Identity/HttpPipelineClientFactory.cs delete mode 100644 src/Accounts/Authentication/Identity/IScopeHandler.cs delete mode 100644 src/Accounts/Authentication/Identity/ISupportsAdditionallyAllowedTenants.cs delete mode 100644 src/Accounts/Authentication/Identity/IX509Certificate2Provider.cs delete mode 100644 src/Accounts/Authentication/Identity/IdentityCompatSwitches.cs delete mode 100644 src/Accounts/Authentication/Identity/ManagedIdentityRequestFailedDetailsParser.cs delete mode 100644 src/Accounts/Authentication/Identity/ManagedIdentitySource.cs delete mode 100644 src/Accounts/Authentication/Identity/MsalCacheHelperWrapper.cs delete mode 100644 src/Accounts/Authentication/Identity/MsalClientBase.cs delete mode 100644 src/Accounts/Authentication/Identity/MsalConfidentialClient.cs delete mode 100644 src/Accounts/Authentication/Identity/README.md delete mode 100644 src/Accounts/Authentication/Identity/ScopeGroupHandler.cs delete mode 100644 src/Accounts/Authentication/Identity/TenantIdResolver.cs delete mode 100644 src/Accounts/Authentication/Identity/TokenCache.cs delete mode 100644 src/Accounts/Authentication/Identity/TokenCacheData.cs delete mode 100644 src/Accounts/Authentication/Identity/TokenCacheRefreshArgs.cs delete mode 100644 src/Accounts/Authentication/Identity/TokenCacheUpdatedArgs.cs delete mode 100644 src/Accounts/Authentication/Identity/TokenHelper.cs delete mode 100644 src/Accounts/Authentication/Identity/UnsafeTokenCacheOptions.cs delete mode 100644 src/Accounts/Authentication/Identity/Validations.cs delete mode 100644 src/Accounts/Authentication/Identity/X509Certificate2FromFileProvider.cs delete mode 100644 src/Accounts/Authentication/Identity/X509Certificate2FromObjectProvider.cs rename src/Accounts/Authentication/{Identity/Core => KeyStore}/TaskExtensions.cs (99%) diff --git a/src/Accounts/Accounts/ChangeLog.md b/src/Accounts/Accounts/ChangeLog.md index 03dc846833d7..c6ffbaf170bf 100644 --- a/src/Accounts/Accounts/ChangeLog.md +++ b/src/Accounts/Accounts/ChangeLog.md @@ -19,6 +19,9 @@ --> ## Upcoming Release +* Used Azure.Identity and Azure.Core directly for client assertion [#22628]. + +## Version 3.0.3 * Reduced the frequency of displaying sign-in announcement messages. * Upgraded Azure.Core to 1.41.0 to include the fix for `BearerTokenAuthenticationPolicy` * Removed the informational table about selected context to avoid duplication with output table. diff --git a/src/Accounts/Authentication/Identity/AbstractAcquireTokenParameterBuilderExtensions.cs b/src/Accounts/Authentication/Identity/AbstractAcquireTokenParameterBuilderExtensions.cs deleted file mode 100644 index bce3028ed49b..000000000000 --- a/src/Accounts/Authentication/Identity/AbstractAcquireTokenParameterBuilderExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Identity.Client; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal static class AbstractAcquireTokenParameterBuilderExtensions - { - public static async ValueTask<AuthenticationResult> ExecuteAsync<T>(this AbstractAcquireTokenParameterBuilder<T> builder, bool async, CancellationToken cancellationToken) - where T : AbstractAcquireTokenParameterBuilder<T> - { - Microsoft.Identity.Client.AuthenticationResult result = async - ? await builder.ExecuteAsync(cancellationToken).ConfigureAwait(false) -#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead. - : builder.ExecuteAsync(cancellationToken).GetAwaiter().GetResult(); -#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead. - - return result; - } - } -} diff --git a/src/Accounts/Authentication/Identity/AuthenticationAccount.cs b/src/Accounts/Authentication/Identity/AuthenticationAccount.cs deleted file mode 100644 index 7210e19d7d36..000000000000 --- a/src/Accounts/Authentication/Identity/AuthenticationAccount.cs +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Microsoft.Identity.Client; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class AuthenticationAccount : IAccount - { - private AuthenticationRecord _profile; - - internal AuthenticationAccount(AuthenticationRecord profile) - { - _profile = profile; - } - - string IAccount.Username => _profile.Username; - - string IAccount.Environment => _profile.Authority; - - AccountId IAccount.HomeAccountId => _profile.AccountId; - - public static explicit operator AuthenticationAccount(AuthenticationRecord profile) => new AuthenticationAccount(profile); - public static explicit operator AuthenticationRecord(AuthenticationAccount account) => account._profile; - } -} diff --git a/src/Accounts/Authentication/Identity/AuthenticationRecord.cs b/src/Accounts/Authentication/Identity/AuthenticationRecord.cs deleted file mode 100644 index 90d713808914..000000000000 --- a/src/Accounts/Authentication/Identity/AuthenticationRecord.cs +++ /dev/null @@ -1,237 +0,0 @@ -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure.Core.Pipeline; -using Azure.Identity; -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; -using Microsoft.Identity.Client; - -using System; -using System.IO; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Account information relating to an authentication request. - /// </summary> - /// <seealso cref="TokenCachePersistenceOptions"/>. - public class AuthenticationRecord - { - internal const string CurrentVersion = "1.0"; - private const string UsernamePropertyName = "username"; - private const string AuthorityPropertyName = "authority"; - private const string HomeAccountIdPropertyName = "homeAccountId"; - private const string TenantIdPropertyName = "tenantId"; - private const string ClientIdPropertyName = "clientId"; - private const string VersionPropertyName = "version"; - - private static readonly JsonEncodedText s_usernamePropertyNameBytes = JsonEncodedText.Encode(UsernamePropertyName); - private static readonly JsonEncodedText s_authorityPropertyNameBytes = JsonEncodedText.Encode(AuthorityPropertyName); - private static readonly JsonEncodedText s_homeAccountIdPropertyNameBytes = JsonEncodedText.Encode(HomeAccountIdPropertyName); - private static readonly JsonEncodedText s_tenantIdPropertyNameBytes = JsonEncodedText.Encode(TenantIdPropertyName); - private static readonly JsonEncodedText s_clientIdPropertyNameBytes = JsonEncodedText.Encode(ClientIdPropertyName); - private static readonly JsonEncodedText s_versionPropertyNameBytes = JsonEncodedText.Encode(VersionPropertyName); - - internal AuthenticationRecord() - { - } - - internal AuthenticationRecord(AuthenticationResult authResult, string clientId) - { - Username = authResult.Account.Username; - Authority = authResult.Account.Environment; - AccountId = authResult.Account.HomeAccountId; - TenantId = authResult.TenantId; - ClientId = clientId; - } - - internal AuthenticationRecord(string username, string authority, string homeAccountId, string tenantId, string clientId) - { - Username = username; - Authority = authority; - AccountId = BuildAccountIdFromString(homeAccountId); - TenantId = tenantId; - ClientId = clientId; - } - - /// <summary> - /// The user principal or service principal name of the account. - /// </summary> - public string Username { get; private set; } - - /// <summary> - /// The authority host used to authenticate the account. - /// </summary> - public string Authority { get; private set; } - - /// <summary> - /// A unique identifier of the account. - /// </summary> - public string HomeAccountId { get => AccountId.Identifier; } - - /// <summary> - /// The tenant the account should authenticate in. - /// </summary> - public string TenantId { get; private set; } - - /// <summary> - /// The client id of the application which performed the original authentication - /// </summary> - public string ClientId { get; private set; } - - internal AccountId AccountId { get; private set; } - internal string Version { get; private set; } = CurrentVersion; - - /// <summary> - /// Serializes the <see cref="AuthenticationRecord"/> to the specified <see cref="Stream"/>. - /// </summary> - /// <param name="stream">The <see cref="Stream"/> which the serialized <see cref="AuthenticationRecord"/> will be written to.</param> - /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param> - public void Serialize(Stream stream, CancellationToken cancellationToken = default) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); - - SerializeAsync(stream, false, cancellationToken).EnsureCompleted(); - } - - /// <summary> - /// Serializes the <see cref="AuthenticationRecord"/> to the specified <see cref="Stream"/>. - /// </summary> - /// <param name="stream">The <see cref="Stream"/> to which the serialized <see cref="AuthenticationRecord"/> will be written.</param> - /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param> - public async Task SerializeAsync(Stream stream, CancellationToken cancellationToken = default) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); - - await SerializeAsync(stream, true, cancellationToken).ConfigureAwait(false); - } - - /// <summary> - /// Deserializes the <see cref="AuthenticationRecord"/> from the specified <see cref="Stream"/>. - /// </summary> - /// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="AuthenticationRecord"/> will be read.</param> - /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param> - public static AuthenticationRecord Deserialize(Stream stream, CancellationToken cancellationToken = default) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); - - return DeserializeAsync(stream, false, cancellationToken).EnsureCompleted(); - } - - /// <summary> - /// Deserializes the <see cref="AuthenticationRecord"/> from the specified <see cref="Stream"/>. - /// </summary> - /// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="AuthenticationRecord"/> will be read.</param> - /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param> - public static async Task<AuthenticationRecord> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); - - return await DeserializeAsync(stream, true, cancellationToken).ConfigureAwait(false); - } - - private async Task SerializeAsync(Stream stream, bool async, CancellationToken cancellationToken) - { - using (var json = new Utf8JsonWriter(stream)) - { - json.WriteStartObject(); - - json.WriteString(s_usernamePropertyNameBytes, Username); - - json.WriteString(s_authorityPropertyNameBytes, Authority); - - json.WriteString(s_homeAccountIdPropertyNameBytes, HomeAccountId); - - json.WriteString(s_tenantIdPropertyNameBytes, TenantId); - - json.WriteString(s_clientIdPropertyNameBytes, ClientId); - - json.WriteString(s_versionPropertyNameBytes, Version); - - json.WriteEndObject(); - - if (async) - { - await json.FlushAsync(cancellationToken).ConfigureAwait(false); - } - else - { - json.Flush(); - } - } - } - - private static async Task<AuthenticationRecord> DeserializeAsync(Stream stream, bool async, CancellationToken cancellationToken) - { - var authProfile = new AuthenticationRecord(); - - using (JsonDocument doc = async ? await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false) : JsonDocument.Parse(stream)) - { - foreach (JsonProperty prop in doc.RootElement.EnumerateObject()) - { - switch (prop.Name) - { - case UsernamePropertyName: - authProfile.Username = prop.Value.GetString(); - break; - case AuthorityPropertyName: - authProfile.Authority = prop.Value.GetString(); - break; - case HomeAccountIdPropertyName: - authProfile.AccountId = BuildAccountIdFromString(prop.Value.GetString()); - break; - case TenantIdPropertyName: - authProfile.TenantId = prop.Value.GetString(); - break; - case ClientIdPropertyName: - authProfile.ClientId = prop.Value.GetString(); - break; - case VersionPropertyName: - authProfile.Version = prop.Value.GetString(); - if (authProfile.Version != CurrentVersion) - { - throw new InvalidOperationException($"Attempted to deserialize an {nameof(AuthenticationRecord)} with a version that is not the current version. Expected: '{CurrentVersion}', Actual: '{authProfile.Version}'"); - } - break; - } - } - } - return authProfile; - } - - private static AccountId BuildAccountIdFromString(string homeAccountId) - { - //For the Microsoft identity platform (formerly named Azure AD v2.0), the identifier is the concatenation of - // Microsoft.Identity.Client.AccountId.ObjectId and Microsoft.Identity.Client.AccountId.TenantId separated by a dot. - var homeAccountSegments = homeAccountId.Split('.'); - AccountId accountId; - if (homeAccountSegments.Length == 2) - { - accountId = new AccountId(homeAccountId, homeAccountSegments[0], homeAccountSegments[1]); - } - else - { - accountId = new AccountId(homeAccountId); - } - return accountId; - } - } -} diff --git a/src/Accounts/Authentication/Identity/AzureAuthorityHosts.cs b/src/Accounts/Authentication/Identity/AzureAuthorityHosts.cs deleted file mode 100644 index 93036932f159..000000000000 --- a/src/Accounts/Authentication/Identity/AzureAuthorityHosts.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Defines fields exposing the well known authority hosts for the Azure Public Cloud and sovereign clouds. - /// </summary> - public static class AzureAuthorityHosts - { - private const string AzurePublicCloudHostUrl = "https://login.microsoftonline.com/"; - private const string AzureChinaHostUrl = "https://login.chinacloudapi.cn/"; - private const string AzureGermanyHostUrl = "https://login.microsoftonline.de/"; - private const string AzureGovernmentHostUrl = "https://login.microsoftonline.us/"; - /// <summary> - /// The host of the Azure Active Directory authority for tenants in the Azure Public Cloud. - /// </summary> - public static Uri AzurePublicCloud { get; } = new Uri(AzurePublicCloudHostUrl); - - /// <summary> - /// The host of the Azure Active Directory authority for tenants in the Azure China Cloud. - /// </summary> - public static Uri AzureChina { get; } = new Uri(AzureChinaHostUrl); - - /// <summary> - /// The host of the Azure Active Directory authority for tenants in the Azure German Cloud. - /// </summary> - public static Uri AzureGermany { get; } = new Uri(AzureGermanyHostUrl); - - /// <summary> - /// The host of the Azure Active Directory authority for tenants in the Azure US Government Cloud. - /// </summary> - public static Uri AzureGovernment { get; } = new Uri(AzureGovernmentHostUrl); - - internal static Uri GetDefault() - { - if (EnvironmentVariables.AuthorityHost != null) - { - return new Uri(EnvironmentVariables.AuthorityHost); - } - - return AzurePublicCloud; - } - - internal static string GetDefaultScope(Uri authorityHost) - { - switch (authorityHost.AbsoluteUri) - { - case AzurePublicCloudHostUrl: - // The double slash is intentional for public cloud. - return "https://management.azure.com//.default"; - case AzureChinaHostUrl: - return "https://management.chinacloudapi.cn/.default"; - case AzureGermanyHostUrl: - return "https://management.microsoftazure.de/.default"; - case AzureGovernmentHostUrl: - return "https://management.usgovcloudapi.net/.default"; - default: - return null; - } - } - - internal static Uri GetDeviceCodeRedirectUri(Uri authorityHost) - { - return new Uri(authorityHost, "/common/oauth2/nativeclient"); - } - } -} diff --git a/src/Accounts/Authentication/Identity/AzureEventSource.cs b/src/Accounts/Authentication/Identity/AzureEventSource.cs deleted file mode 100644 index 2231eee322aa..000000000000 --- a/src/Accounts/Authentication/Identity/AzureEventSource.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure.Core.Diagnostics; - -using System; -using System.Collections.Generic; -using System.Diagnostics.Tracing; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal abstract class AzureEventSource: EventSource - { - private const string SharedDataKey = "_AzureEventSourceNamesInUse"; - private static readonly HashSet<string> NamesInUse; - -#pragma warning disable CA1810 // Use static initializer - static AzureEventSource() -#pragma warning restore CA1810 - { - // It's important for this code to run in a static constructor because runtime guarantees that - // a single instance is executed at a time - // This gives us a chance to store a shared hashset in the global dictionary without a race - var namesInUse = AppDomain.CurrentDomain.GetData(SharedDataKey) as HashSet<string>; - if (namesInUse == null) - { - namesInUse = new HashSet<string>(); - AppDomain.CurrentDomain.SetData(SharedDataKey, namesInUse); - } - - NamesInUse = namesInUse; - } - - private static readonly string[] MainEventSourceTraits = - { - AzureEventSourceListener.TraitName, - AzureEventSourceListener.TraitValue - }; - - protected AzureEventSource(string eventSourceName): base( - DeduplicateName(eventSourceName), - EventSourceSettings.Default, - MainEventSourceTraits - ) - { - } - - // The name de-duplication is required for the case where multiple versions of the same assembly are loaded - // in different assembly load contexts - private static string DeduplicateName(string eventSourceName) - { - try - { - lock (NamesInUse) - { - // pick up existing EventSources that might not participate in this logic - foreach (var source in GetSources()) - { - NamesInUse.Add(source.Name); - } - - if (!NamesInUse.Contains(eventSourceName)) - { - NamesInUse.Add(eventSourceName); - return eventSourceName; - } - - int i = 1; - while (true) - { - var candidate = $"{eventSourceName}-{i}"; - if (!NamesInUse.Contains(candidate)) - { - NamesInUse.Add(candidate); - return candidate; - } - - i++; - } - } - } - // GetSources() is not supported on some platforms - catch (NotImplementedException) { } - - return eventSourceName; - } - } -} \ No newline at end of file diff --git a/src/Accounts/Authentication/Identity/AzureIdentityEventSource.cs b/src/Accounts/Authentication/Identity/AzureIdentityEventSource.cs deleted file mode 100644 index 657690c155d8..000000000000 --- a/src/Accounts/Authentication/Identity/AzureIdentityEventSource.cs +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure.Core; - -using System; -using System.Diagnostics.Tracing; -using System.Globalization; -using System.Text; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - [EventSource(Name = EventSourceName)] - internal sealed class AzureIdentityEventSource : AzureEventSource - { - private const string EventSourceName = "Azure-Identity"; - - private const int GetTokenEvent = 1; - private const int GetTokenSucceededEvent = 2; - private const int GetTokenFailedEvent = 3; - private const int ProbeImdsEndpointEvent = 4; - private const int ImdsEndpointFoundEvent = 5; - private const int ImdsEndpointUnavailableEvent = 6; - private const int MsalLogVerboseEvent = 7; - private const int MsalLogInfoEvent = 8; - private const int MsalLogWarningEvent = 9; - private const int MsalLogErrorEvent = 10; - private const int InteractiveAuthenticationThreadPoolExecutionEvent = 11; - private const int InteractiveAuthenticationInlineExecutionEvent = 12; - private const int DefaultAzureCredentialCredentialSelectedEvent = 13; - private const int ProcessRunnerErrorEvent = 14; - private const int ProcessRunnerInfoEvent = 15; - private const int UsernamePasswordCredentialAcquireTokenSilentFailedEvent = 16; - private const int TenantIdDiscoveredAndNotUsedEvent = 17; - private const int TenantIdDiscoveredAndUsedEvent = 18; - internal const int AuthenticatedAccountDetailsEvent = 19; - internal const int UnableToParseAccountDetailsFromTokenEvent = 20; - private const int UserAssignedManagedIdentityNotSupportedEvent = 21; - private const int ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedEvent = 22; - internal const string TenantIdDiscoveredAndNotUsedEventMessage = "A token was request for a different tenant than was configured on the credential, but the configured value was used since multi tenant authentication has been disabled. Configured TenantId: {0}, Requested TenantId {1}"; - internal const string TenantIdDiscoveredAndUsedEventMessage = "A token was requested for a different tenant than was configured on the credential, and the requested tenant id was used to authenticate. Configured TenantId: {0}, Requested TenantId {1}"; - internal const string AuthenticatedAccountDetailsMessage = "Client ID: {0}. Tenant ID: {1}. User Principal Name: {2} Object ID: {3}"; - internal const string Unavailable = "<not available>"; - internal const string UnableToParseAccountDetailsFromTokenMessage = "Unable to parse account details from the Access Token"; - internal const string UserAssignedManagedIdentityNotSupportedMessage = "User assigned managed identities are not supported in the {0} environment."; - internal const string ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedMessage = "Service Fabric user assigned managed identity ClientId or ResourceId is not configurable at runtime."; - - private AzureIdentityEventSource() : base(EventSourceName) { } - - public static AzureIdentityEventSource Singleton { get; } = new AzureIdentityEventSource(); - - [NonEvent] - public void GetToken(string method, TokenRequestContext context) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - GetToken(method, FormatStringArray(context.Scopes), context.ParentRequestId); - } - } - - [Event(GetTokenEvent, Level = EventLevel.Informational, Message = "{0} invoked. Scopes: {1} ParentRequestId: {2}")] - public void GetToken(string method, string scopes, string parentRequestId) - { - WriteEvent(GetTokenEvent, method, scopes, parentRequestId); - } - - [NonEvent] - public void GetTokenSucceeded(string method, TokenRequestContext context, DateTimeOffset expiresOn) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - GetTokenSucceeded(method, FormatStringArray(context.Scopes), context.ParentRequestId, expiresOn.ToString("O", CultureInfo.InvariantCulture)); - } - } - - [Event(GetTokenSucceededEvent, Level = EventLevel.Informational, Message = "{0} succeeded. Scopes: {1} ParentRequestId: {2} ExpiresOn: {3}")] - public void GetTokenSucceeded(string method, string scopes, string parentRequestId, string expiresOn) - { - WriteEvent(GetTokenSucceededEvent, method, scopes, parentRequestId, expiresOn); - } - - [NonEvent] - public void GetTokenFailed(string method, TokenRequestContext context, Exception ex) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - GetTokenFailed(method, FormatStringArray(context.Scopes), context.ParentRequestId, FormatException(ex)); - } - } - - [Event(GetTokenFailedEvent, Level = EventLevel.Informational, Message = "{0} was unable to retrieve an access token. Scopes: {1} ParentRequestId: {2} Exception: {3}")] - public void GetTokenFailed(string method, string scopes, string parentRequestId, string exception) - { - WriteEvent(GetTokenFailedEvent, method, scopes, parentRequestId, exception); - } - - [NonEvent] - public void ProbeImdsEndpoint(Uri uri) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - ProbeImdsEndpoint(uri.AbsoluteUri); - } - } - - [Event(ProbeImdsEndpointEvent, Level = EventLevel.Informational, Message = "Probing IMDS endpoint for availability. Endpoint: {0}")] - public void ProbeImdsEndpoint(string uri) - { - WriteEvent(ProbeImdsEndpointEvent, uri); - } - - [NonEvent] - public void ImdsEndpointFound(Uri uri) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - ImdsEndpointFound(uri.AbsoluteUri); - } - } - - [Event(ImdsEndpointFoundEvent, Level = EventLevel.Informational, Message = "IMDS endpoint is available. Endpoint: {0}")] - public void ImdsEndpointFound(string uri) - { - WriteEvent(ImdsEndpointFoundEvent, uri); - } - - [NonEvent] - public void ImdsEndpointUnavailable(Uri uri, string error) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - ImdsEndpointUnavailable(uri.AbsoluteUri, error); - } - } - - [NonEvent] - public void ImdsEndpointUnavailable(Uri uri, Exception e) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - ImdsEndpointUnavailable(uri.AbsoluteUri, FormatException(e)); - } - } - - [Event(ImdsEndpointUnavailableEvent, Level = EventLevel.Informational, Message = "IMDS endpoint is not available. Endpoint: {0}. Error: {1}")] - public void ImdsEndpointUnavailable(string uri, string error) - { - WriteEvent(ImdsEndpointUnavailableEvent, uri, error); - } - - [NonEvent] - private static string FormatException(Exception ex) - { - StringBuilder sb = new StringBuilder(); - bool nest = false; - do - { - if (nest) - { - // Format how Exception.ToString() would. - sb.AppendLine() - .Append(" ---> "); - } - // Do not include StackTrace, but do include HResult (often useful for CryptographicExceptions or IOExceptions). - sb.Append(ex.GetType().FullName) - .Append(" (0x") - .Append(ex.HResult.ToString("x", CultureInfo.InvariantCulture)) - .Append("): ") - .Append(ex.Message); - ex = ex.InnerException; - nest = true; - } - while (ex != null); - return sb.ToString(); - } - - [NonEvent] - public void LogMsal(Microsoft.Identity.Client.LogLevel level, string message) - { - switch (level) - { - case Microsoft.Identity.Client.LogLevel.Error when IsEnabled(EventLevel.Error, EventKeywords.All): - LogMsalError(message); - break; - case Microsoft.Identity.Client.LogLevel.Warning when IsEnabled(EventLevel.Warning, EventKeywords.All): - LogMsalWarning(message); - break; - case Microsoft.Identity.Client.LogLevel.Info when IsEnabled(EventLevel.Informational, EventKeywords.All): - LogMsalInformational(message); - break; - case Microsoft.Identity.Client.LogLevel.Verbose when IsEnabled(EventLevel.Verbose, EventKeywords.All): - LogMsalVerbose(message); - break; - } - } - - [Event(MsalLogErrorEvent, Level = EventLevel.Error, Message = "{0}")] - public void LogMsalError(string message) - { - WriteEvent(MsalLogErrorEvent, message); - } - - [Event(MsalLogWarningEvent, Level = EventLevel.Warning, Message = "{0}")] - public void LogMsalWarning(string message) - { - WriteEvent(MsalLogWarningEvent, message); - } - - [Event(MsalLogInfoEvent, Level = EventLevel.Informational, Message = "{0}")] - public void LogMsalInformational(string message) - { - WriteEvent(MsalLogInfoEvent, message); - } - - [Event(MsalLogVerboseEvent, Level = EventLevel.Verbose, Message = "{0}")] - public void LogMsalVerbose(string message) - { - WriteEvent(MsalLogVerboseEvent, message); - } - - [NonEvent] - private static string FormatStringArray(string[] array) - { - return new StringBuilder("[ ").Append(string.Join(", ", array)).Append(" ]").ToString(); - } - - [Event(InteractiveAuthenticationThreadPoolExecutionEvent, Level = EventLevel.Informational, Message = "Executing interactive authentication workflow via Task.Run.")] - public void InteractiveAuthenticationExecutingOnThreadPool() - { - WriteEvent(InteractiveAuthenticationThreadPoolExecutionEvent); - } - - [Event(InteractiveAuthenticationInlineExecutionEvent, Level = EventLevel.Informational, Message = "Executing interactive authentication workflow inline.")] - public void InteractiveAuthenticationExecutingInline() - { - WriteEvent(InteractiveAuthenticationInlineExecutionEvent); - } - - [Event(DefaultAzureCredentialCredentialSelectedEvent, Level = EventLevel.Informational, Message = "DefaultAzureCredential credential selected: {0}")] - public void DefaultAzureCredentialCredentialSelected(string credentialType) - { - WriteEvent(DefaultAzureCredentialCredentialSelectedEvent, credentialType); - } - - [NonEvent] - public void ProcessRunnerError(string message) - { - if (IsEnabled(EventLevel.Error, EventKeywords.All)) - { - LogProcessRunnerError(message); - } - } - - [Event(ProcessRunnerErrorEvent, Level = EventLevel.Error, Message = "{0}")] - public void LogProcessRunnerError(string message) - { - WriteEvent(ProcessRunnerErrorEvent, message); - } - - [NonEvent] - public void ProcessRunnerInformational(string message) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - LogProcessRunnerInformational(message); - } - } - - [Event(ProcessRunnerInfoEvent, Level = EventLevel.Informational, Message = "{0}")] - public void LogProcessRunnerInformational(string message) - { - WriteEvent(ProcessRunnerInfoEvent, message); - } - - [NonEvent] - public void UsernamePasswordCredentialAcquireTokenSilentFailed(Exception e) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - UsernamePasswordCredentialAcquireTokenSilentFailed(FormatException(e)); - } - } - - [Event( - UsernamePasswordCredentialAcquireTokenSilentFailedEvent, - Level = EventLevel.Informational, - Message = "UsernamePasswordCredential failed to acquire token silently. Error: {1}")] - public void UsernamePasswordCredentialAcquireTokenSilentFailed(string error) - { - WriteEvent(UsernamePasswordCredentialAcquireTokenSilentFailedEvent, error); - } - - [Event(TenantIdDiscoveredAndNotUsedEvent, Level = EventLevel.Informational, Message = TenantIdDiscoveredAndNotUsedEventMessage)] - public void TenantIdDiscoveredAndNotUsed(string explicitTenantId, string contextTenantId) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - WriteEvent(TenantIdDiscoveredAndNotUsedEvent, explicitTenantId, contextTenantId); - } - } - - [Event(TenantIdDiscoveredAndUsedEvent, Level = EventLevel.Informational, Message = TenantIdDiscoveredAndUsedEventMessage)] - public void TenantIdDiscoveredAndUsed(string explicitTenantId, string contextTenantId) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - WriteEvent(TenantIdDiscoveredAndUsedEvent, explicitTenantId, contextTenantId); - } - } - - [Event(AuthenticatedAccountDetailsEvent, Level = EventLevel.Informational, Message = AuthenticatedAccountDetailsMessage)] - public void AuthenticatedAccountDetails(string clientId, string tenantId, string upn, string objectId) - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - WriteEvent(AuthenticatedAccountDetailsEvent, clientId ?? Unavailable, tenantId ?? Unavailable, upn ?? Unavailable, objectId ?? Unavailable); - } - } - - [Event(UnableToParseAccountDetailsFromTokenEvent, Level = EventLevel.Informational, Message = UnableToParseAccountDetailsFromTokenMessage)] - internal void UnableToParseAccountDetailsFromToken() - { - if (IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - WriteEvent(UnableToParseAccountDetailsFromTokenEvent); - } - } - - [Event(UserAssignedManagedIdentityNotSupportedEvent, Level = EventLevel.Warning, Message = UserAssignedManagedIdentityNotSupportedMessage)] - public void UserAssignedManagedIdentityNotSupported(string environment) - { - if (IsEnabled(EventLevel.Warning, EventKeywords.All)) - { - WriteEvent(UserAssignedManagedIdentityNotSupportedEvent, environment); - } - } - - [Event(ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedEvent, Level = EventLevel.Warning, Message =ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedMessage)] - public void ServiceFabricManagedIdentityRuntimeConfigurationNotSupported() - { - if (IsEnabled(EventLevel.Warning, EventKeywords.All)) - { - WriteEvent(ServiceFabricManagedIdentityRuntimeConfigurationNotSupportedEvent); - } - } - } -} diff --git a/src/Accounts/Authentication/Identity/Constants.cs b/src/Accounts/Authentication/Identity/Constants.cs deleted file mode 100644 index 1befe7d0f72f..000000000000 --- a/src/Accounts/Authentication/Identity/Constants.cs +++ /dev/null @@ -1,61 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Collections.Generic; -using System.IO; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class Constants - { - public const string OrganizationsTenantId = "organizations"; - - public const string AdfsTenantId = "adfs"; - - // TODO: Currently this is piggybacking off the Azure CLI client ID, but needs to be switched once the Developer Sign On application is available - public const string DeveloperSignOnClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; - - public static string SharedTokenCacheFilePath { get { return Path.Combine(DefaultMsalTokenCacheDirectory, DefaultMsalTokenCacheName); } } - - public const int SharedTokenCacheAccessRetryCount = 100; - - public static readonly TimeSpan SharedTokenCacheAccessRetryDelay = TimeSpan.FromMilliseconds(600); - - public const string DefaultRedirectUrl = "http://localhost"; - - public static readonly string DefaultMsalTokenCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ".IdentityService"); - - public const string DefaultMsalTokenCacheKeychainService = "Microsoft.Developer.IdentityService"; - - public const string DefaultMsalTokenCacheKeychainAccount = "MSALCache"; - - public const string DefaultMsalTokenCacheKeyringLabel = "MSALCache"; - - public const string DefaultMsalTokenCacheKeyringSchema = "msal.cache"; - - public const string DefaultMsalTokenCacheKeyringCollection = "default"; - - public static readonly KeyValuePair<string, string> DefaultMsaltokenCacheKeyringAttribute1 = new KeyValuePair<string, string>("MsalClientID", "Microsoft.Developer.IdentityService"); - - public static readonly KeyValuePair<string, string> DefaultMsaltokenCacheKeyringAttribute2 = new KeyValuePair<string, string>("Microsoft.Developer.IdentityService", "1.0.0.0"); - - public const string DefaultMsalTokenCacheName = "msal.cache"; - public const string CaeEnabledCacheSuffix = ".cae"; - public const string CaeDisabledCacheSuffix = ".nocae"; - - public const string ManagedIdentityClientId = "client_id"; - public const string ManagedIdentityResourceId = "mi_res_id"; - } -} diff --git a/src/Accounts/Authentication/Identity/Core/AppContextSwitchHelper.cs b/src/Accounts/Authentication/Identity/Core/AppContextSwitchHelper.cs deleted file mode 100644 index 341f64ce9d88..000000000000 --- a/src/Accounts/Authentication/Identity/Core/AppContextSwitchHelper.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - /// <summary> - /// Helper for interacting with AppConfig settings and their related Environment variable settings. - /// </summary> - internal static class AppContextSwitchHelper - { - /// <summary> - /// Determines if either an AppContext switch or its corresponding Environment Variable is set - /// </summary> - /// <param name="appContexSwitchName">Name of the AppContext switch.</param> - /// <param name="environmentVariableName">Name of the Environment variable.</param> - /// <returns>If the AppContext switch has been set, returns the value of the switch. - /// If the AppContext switch has not been set, returns the value of the environment variable. - /// False if neither is set. - /// </returns> - public static bool GetConfigValue(string appContexSwitchName, string environmentVariableName) - { - // First check for the AppContext switch, giving it priority over the environment variable. - if (AppContext.TryGetSwitch(appContexSwitchName, out bool value)) - { - return value; - } - // AppContext switch wasn't used. Check the environment variable. - string envVar = Environment.GetEnvironmentVariable(environmentVariableName); - if (envVar != null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1"))) - { - return true; - } - - // Default to false. - return false; - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/Argument.cs b/src/Accounts/Authentication/Identity/Core/Argument.cs deleted file mode 100644 index c7783c0b661b..000000000000 --- a/src/Accounts/Authentication/Identity/Core/Argument.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - /// <summary> - /// Argument validation. - /// </summary> - /// <remarks> - /// <para>This class should be shared via source using Azure.Core.props and contain only common argument validation. - /// It is declared partial so that you can use the same familiar class name but extend it with project-specific validation. - /// To extend the functionality of this class, just declare your own partial <see cref="Argument"/> class with project-specific methods. - /// </para> - /// <para> - /// Be sure to document exceptions thrown by these methods on your public methods. - /// </para> - /// </remarks> - internal static partial class Argument - { - // TODO: Add nullability attributes as needed when possible. - - /// <summary> - /// Throws if <paramref name="value"/> is null. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> -#if AZURE_NULLABLE - public static void AssertNotNull<T>([AllowNull, NotNull] T value, string name) -#else - public static void AssertNotNull<T>(T value, string name) -#endif - { - if (value == null) - { - throw new ArgumentNullException(name); - } - } - - /// <summary> - /// Throws if <paramref name="value"/> has not been initialized. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentNullException"><paramref name="value"/> has not been initialized.</exception> - public static void AssertNotNull<T>(T? value, string name) where T : struct - { - if (!value.HasValue) - { - throw new ArgumentNullException(name); - } - } - - /// <summary> - /// Throws if <paramref name="value"/> is null or an empty collection. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentException"><paramref name="value"/> is an empty collection.</exception> - /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> -#if AZURE_NULLABLE - public static void AssertNotNullOrEmpty<T>([AllowNull, NotNull] IEnumerable<T> value, string name) -#else - public static void AssertNotNullOrEmpty<T>(IEnumerable<T> value, string name) -#endif - { - if (value is null) - { - throw new ArgumentNullException(name); - } - - // .NET Framework's Enumerable.Any() always allocates an enumerator, so we optimize for collections here. - if (value is ICollection<T> collectionOfT && collectionOfT.Count == 0) - { - throw new ArgumentException("Value cannot be an empty collection.", name); - } - - if (value is ICollection collection && collection.Count == 0) - { - throw new ArgumentException("Value cannot be an empty collection.", name); - } - - using (IEnumerator<T> e = value.GetEnumerator()) - { - if (!e.MoveNext()) - { - throw new ArgumentException("Value cannot be an empty collection.", name); - } - } - } - - /// <summary> - /// Throws if <paramref name="value"/> is null or an empty string. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentException"><paramref name="value"/> is an empty string.</exception> - /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> -#if AZURE_NULLABLE - public static void AssertNotNullOrEmpty([AllowNull, NotNull] string value, string name) -#else - public static void AssertNotNullOrEmpty(string value, string name) -#endif - { - if (value is null) - { - throw new ArgumentNullException(name); - } - - if (value.Length == 0) - { - throw new ArgumentException("Value cannot be an empty string.", name); - } - } - - /// <summary> - /// Throws if <paramref name="value"/> is null, an empty string, or consists only of white-space characters. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentException"><paramref name="value"/> is an empty string or consists only of white-space characters.</exception> - /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> -#if AZURE_NULLABLE - public static void AssertNotNullOrWhiteSpace([AllowNull, NotNull] string value, string name) -#else - public static void AssertNotNullOrWhiteSpace(string value, string name) -#endif - { - if (value is null) - { - throw new ArgumentNullException(name); - } - - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentException("Value cannot be empty or contain only white-space characters.", name); - } - } - - /// <summary> - /// Throws if <paramref name="value"/> is the default value for type <typeparamref name="T"/>. - /// </summary> - /// <typeparam name="T">The type of structure to validate which implements <see cref="IEquatable{T}"/>.</typeparam> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentException"><paramref name="value"/> is the default value for type <typeparamref name="T"/>.</exception> - public static void AssertNotDefault<T>(ref T value, string name) where T : struct, IEquatable<T> - { - if (value.Equals(default)) - { - throw new ArgumentException("Value cannot be empty.", name); - } - } - - /// <summary> - /// Throws if <paramref name="value"/> is less than the <paramref name="minimum"/> or greater than the <paramref name="maximum"/>. - /// </summary> - /// <typeparam name="T">The type of to validate which implements <see cref="IComparable{T}"/>.</typeparam> - /// <param name="value">The value to validate.</param> - /// <param name="minimum">The minimum value to compare.</param> - /// <param name="maximum">The maximum value to compare.</param> - /// <param name="name">The name of the parameter.</param> - public static void AssertInRange<T>(T value, T minimum, T maximum, string name) where T : IComparable<T> - { - if (minimum.CompareTo(value) > 0) - { - throw new ArgumentOutOfRangeException(name, "Value is less than the minimum allowed."); - } - - if (maximum.CompareTo(value) < 0) - { - throw new ArgumentOutOfRangeException(name, "Value is greater than the maximum allowed."); - } - } - - /// <summary> - /// Throws if <paramref name="value"/> is not defined for <paramref name="enumType"/>. - /// </summary> - /// <param name="enumType">The type to validate against.</param> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentException"><paramref name="value"/> is not defined for <paramref name="enumType"/>.</exception> - public static void AssertEnumDefined(Type enumType, object value, string name) - { - if (!Enum.IsDefined(enumType, value)) - { - throw new ArgumentException($"Value not defined for {enumType.FullName}.", name); - } - } - - /// <summary> - /// Throws if <paramref name="value"/> has not been initialized; otherwise, returns <paramref name="value"/>. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentNullException"><paramref name="value"/> has not been initialized.</exception> -#if AZURE_NULLABLE - public static T CheckNotNull<T>([AllowNull, NotNull] T value, string name) where T : class -#else - public static T CheckNotNull<T>(T value, string name) where T : class -#endif - { - AssertNotNull(value, name); - return value; - } - - /// <summary> - /// Throws if <paramref name="value"/> is null or an empty string; otherwise, returns <paramref name="value"/>. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <exception cref="ArgumentException"><paramref name="value"/> is an empty string.</exception> - /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> -#if AZURE_NULLABLE - public static string CheckNotNullOrEmpty([AllowNull, NotNull] string value, string name) -#else - public static string CheckNotNullOrEmpty(string value, string name) -#endif - { - AssertNotNullOrEmpty(value, name); - return value; - } - - /// <summary> - /// Throws if <paramref name="value"/> is not null. - /// </summary> - /// <param name="value">The value to validate.</param> - /// <param name="name">The name of the parameter.</param> - /// <param name="message">The error message.</param> - /// <exception cref="ArgumentException"><paramref name="value"/> is not null.</exception> - - public static void AssertNull<T>(T value, string name, string message = null) - { - if (value != null) - { - throw new ArgumentException(message ?? "Value must be null.", name); - } - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/AsyncLockWithValue.cs b/src/Accounts/Authentication/Identity/Core/AsyncLockWithValue.cs deleted file mode 100644 index dadce620b9b7..000000000000 --- a/src/Accounts/Authentication/Identity/Core/AsyncLockWithValue.cs +++ /dev/null @@ -1,267 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - /// <summary> - /// Primitive that combines async lock and value cache - /// </summary> - /// <typeparam name="T"></typeparam> - internal sealed class AsyncLockWithValue<T> - { - private readonly object _syncObj = new object(); - private Queue<TaskCompletionSource<LockOrValue>> _waiters; - private bool _isLocked; - private bool _hasValue; - private long _index; - private T _value; - - public bool HasValue - { - get - { - lock (_syncObj) - { - return _hasValue; - } - } - } - - public AsyncLockWithValue() { } - - public AsyncLockWithValue(T value) - { - _hasValue = true; - _value = value; - } - - public bool TryGetValue(out T value) - { - lock (_syncObj) - { - if (_hasValue) - { - value = _value; - return true; - } - } - - value = default; - return false; - } - - /// <summary> - /// Method that either returns cached value or acquire a lock. - /// If one caller has acquired a lock, other callers will be waiting for the lock to be released. - /// If value is set, lock is released and all waiters get that value. - /// If value isn't set, the next waiter in the queue will get the lock. - /// </summary> - /// <param name="async"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> - public async ValueTask<LockOrValue> GetLockOrValueAsync(bool async, CancellationToken cancellationToken = default) - { - TaskCompletionSource<LockOrValue> valueTcs; - lock (_syncObj) - { - // If there is a value, just return it - if (_hasValue) - { - return new LockOrValue(_value); - } - - // If lock isn't acquire yet, acquire it and return to the caller - if (!_isLocked) - { - _isLocked = true; - _index = unchecked(_index + 1); - return new LockOrValue(this, _index); - } - - // Check cancellationToken before instantiating waiter - cancellationToken.ThrowIfCancellationRequested(); - - // If lock is already taken, create a waiter and wait either until value is set or lock can be acquired by this waiter - _waiters = new Queue<TaskCompletionSource<LockOrValue>>() ?? _waiters; - // if async == false, valueTcs will be waited only in this thread and only synchronously, so RunContinuationsAsynchronously isn't needed. - valueTcs = new TaskCompletionSource<LockOrValue>(async ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None); - _waiters.Enqueue(valueTcs); - } - - try - { - if (async) - { - return await valueTcs.Task.AwaitWithCancellation(cancellationToken); - } - -#pragma warning disable AZC0104 // Use EnsureCompleted() directly on asynchronous method return value. -#pragma warning disable AZC0111 // DO NOT use EnsureCompleted in possibly asynchronous scope. - valueTcs.Task.Wait(cancellationToken); - return valueTcs.Task.EnsureCompleted(); -#pragma warning restore AZC0111 // DO NOT use EnsureCompleted in possibly asynchronous scope. -#pragma warning restore AZC0104 // Use EnsureCompleted() directly on asynchronous method return value. - } - catch (OperationCanceledException) - { - // Throw OperationCanceledException only if another thread hasn't set a value to this waiter - // by calling either Reset or SetValue - if (valueTcs.TrySetCanceled(cancellationToken)) - { - throw; - } - - return valueTcs.Task.Result; - } - } - - /// <summary> - /// Set value to the cache and to all the waiters - /// </summary> - /// <param name="value"></param> - /// <param name="lockIndex"></param> - private void SetValue(T value, in long lockIndex) - { - Queue<TaskCompletionSource<LockOrValue>> waiters; - lock (_syncObj) - { - if (lockIndex != _index) - { - throw new InvalidOperationException($"Disposed {nameof(LockOrValue)} tries to set value. Current index: {_index}, {nameof(LockOrValue)} index: {lockIndex}"); - } - - _value = value; - _hasValue = true; - _index = 0; - _isLocked = false; - if (_waiters == default) - { - return; - } - - waiters = _waiters; - _waiters = default; - } - - while (waiters.Count > 0) - { - waiters.Dequeue().TrySetResult(new LockOrValue(value)); - } - } - - /// <summary> - /// Release the lock and allow next waiter acquire it - /// </summary> - private void Reset(in long lockIndex) - { - UnlockOrGetNextWaiter(lockIndex, out var nextWaiter); - while (nextWaiter != default && !nextWaiter.TrySetResult(new LockOrValue(this, unchecked(lockIndex + 1)))) - { - UnlockOrGetNextWaiter(lockIndex, out nextWaiter); - } - } - - private void UnlockOrGetNextWaiter(in long lockIndex, out TaskCompletionSource<LockOrValue> nextWaiter) - { - lock (_syncObj) - { - nextWaiter = default; - // If lock isn't acquired, just return - if (!_isLocked || lockIndex != _index) - { - return; - } - - _index = unchecked(lockIndex + 1); - - // If lock was acquired, but there are no waiters, unlock and return - if (_waiters == default) - { - _isLocked = false; - return; - } - - // Find the next waiter - while (_waiters.Count > 0) - { - nextWaiter = _waiters.Dequeue(); - if (!nextWaiter.Task.IsCompleted) - { - // Return the waiter only if it wasn't canceled already - return; - } - } - - // If no next waiter has been found, unlock and return - _isLocked = false; - } - } - - public readonly struct LockOrValue : IDisposable - { - private readonly AsyncLockWithValue<T> _owner; - private readonly T _value; - private readonly long _index; - - /// <summary> - /// Returns true if lock contains the cached value. Otherwise false. - /// </summary> - public bool HasValue => _owner == default; - - /// <summary> - /// Returns cached value if it was set when lock has been created. Throws exception otherwise. - /// </summary> - /// <exception cref="InvalidOperationException">Value isn't set.</exception> - public T Value => HasValue ? _value : throw new InvalidOperationException("Value isn't set"); - - public LockOrValue(T value) - { - _owner = default; - _value = value; - _index = 0; - } - - public LockOrValue(AsyncLockWithValue<T> owner, long index) - { - _owner = owner; - _index = index; - _value = default; - } - - /// <summary> - /// Set value to the cache and to all the waiters. - /// </summary> - /// <param name="value"></param> - /// <exception cref="InvalidOperationException">Value is set already.</exception> - public void SetValue(T value) - { - if (_owner != null) - { - _owner.SetValue(value, _index); - } - else - { - throw new InvalidOperationException("Value for the lock is set already"); - } - } - - public void Dispose() => _owner?.Reset(_index); - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/ClientDiagnostics.cs b/src/Accounts/Authentication/Identity/Core/ClientDiagnostics.cs deleted file mode 100644 index c43a8c7c81e4..000000000000 --- a/src/Accounts/Authentication/Identity/Core/ClientDiagnostics.cs +++ /dev/null @@ -1,80 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Core; -using System.Linq; -using System.Reflection; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - internal class ClientDiagnostics : DiagnosticScopeFactory - { - /// <summary> - /// Initializes a new instance of the <see cref="ClientDiagnostics"/> class. - /// </summary> - /// <param name="options">The customer provided client options object.</param> - /// <param name="suppressNestedClientActivities">Flag controlling if <see cref="System.Diagnostics.Activity"/> - /// created by this <see cref="ClientDiagnostics"/> for client method calls should be suppressed when called - /// by other Azure SDK client methods. It's recommended to set it to true for new clients; use default (null) - /// for backward compatibility reasons, or set it to false to explicitly disable suppression for specific cases. - /// The default value could change in the future, the flag should be only set to false if suppression for the client - /// should never be enabled.</param> - public ClientDiagnostics(ClientOptions options, bool suppressNestedClientActivities = default) - : this(options.GetType().Namespace, - GetResourceProviderNamespace(options.GetType().Assembly), - options.Diagnostics, - suppressNestedClientActivities) - { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ClientDiagnostics"/> class. - /// </summary> - /// <param name="optionsNamespace">Namespace of the client class, such as Azure.Storage or Azure.AppConfiguration.</param> - /// <param name="providerNamespace">Azure Resource Provider namespace of the Azure service SDK is primarily used for.</param> - /// <param name="diagnosticsOptions">The customer provided client diagnostics options.</param> - /// <param name="suppressNestedClientActivities">Flag controlling if <see cref="System.Diagnostics.Activity"/> - /// created by this <see cref="ClientDiagnostics"/> for client method calls should be suppressed when called - /// by other Azure SDK client methods. It's recommended to set it to true for new clients, use default (null) for old clients - /// for backward compatibility reasons, or set it to false to explicitly disable suppression for specific cases. - /// The default value could change in the future, the flag should be only set to false if suppression for the client - /// should never be enabled.</param> - public ClientDiagnostics(string optionsNamespace, string providerNamespace, DiagnosticsOptions diagnosticsOptions, bool suppressNestedClientActivities = default) - : base(optionsNamespace, providerNamespace, diagnosticsOptions.IsDistributedTracingEnabled, suppressNestedClientActivities) - { - } - - internal static HttpMessageSanitizer CreateMessageSanitizer(DiagnosticsOptions diagnostics) - { - return new HttpMessageSanitizer( - diagnostics.LoggedQueryParameters.ToArray(), - diagnostics.LoggedHeaderNames.ToArray()); - } - - internal static string GetResourceProviderNamespace(Assembly assembly) - { - foreach (var customAttribute in assembly.GetCustomAttributes(true)) - { - // Weak bind internal shared type - var attributeType = customAttribute.GetType(); - if (attributeType.Name == "AzureResourceProviderNamespaceAttribute") - { - return attributeType.GetProperty("ResourceProviderNamespace")?.GetValue(customAttribute) as string; - } - } - - return null; - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/DiagnosticScope.cs b/src/Accounts/Authentication/Identity/Core/DiagnosticScope.cs deleted file mode 100644 index 9a79998a1359..000000000000 --- a/src/Accounts/Authentication/Identity/Core/DiagnosticScope.cs +++ /dev/null @@ -1,1062 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - internal readonly struct DiagnosticScope : IDisposable - { - private const string AzureSdkScopeLabel = "az.sdk.scope"; - internal const string OpenTelemetrySchemaAttribute = "az.schema_url"; - internal const string OpenTelemetrySchemaVersion = "https://opentelemetry.io/schemas/1.17.0"; - private static readonly object AzureSdkScopeValue = bool.TrueString; - - private readonly ActivityAdapter _activityAdapter; - private readonly bool _suppressNestedClientActivities; - -#if NETCOREAPP2_1 - internal DiagnosticScope(string scopeName, DiagnosticListener source, object? diagnosticSourceArgs, object? activitySource, ActivityKind kind, bool suppressNestedClientActivities) -#else - internal DiagnosticScope(string scopeName, DiagnosticListener source, object diagnosticSourceArgs, ActivitySource activitySource, ActivityKind kind, bool suppressNestedClientActivities) -#endif - { - // ActivityKind.Internal and Client both can represent public API calls depending on the SDK -#if NETCOREAPP2_1 - _suppressNestedClientActivities = (kind == ActivityKind.Client || kind == ActivityKind.Internal) ? suppressNestedClientActivities : false; -#else - _suppressNestedClientActivities = kind == ActivityKind.Client || kind == ActivityKind.Internal ? suppressNestedClientActivities : false; -#endif - - // outer scope presence is enough to suppress any inner scope, regardless of inner scope configuation. - bool hasListeners; -#if NETCOREAPP2_1 - hasListeners = ActivityExtensions.ActivitySourceHasListeners(activitySource); -#else - hasListeners = activitySource?.HasListeners() ?? false; -#endif - IsEnabled = source.IsEnabled() || hasListeners; - - if (_suppressNestedClientActivities) - { - IsEnabled &= !AzureSdkScopeValue.Equals(Activity.Current?.GetCustomProperty(AzureSdkScopeLabel)); - } - - _activityAdapter = IsEnabled ? new ActivityAdapter( - activitySource: activitySource, - diagnosticSource: source, - activityName: scopeName, - kind: kind, - diagnosticSourceArgs: diagnosticSourceArgs) : null; - } - - public bool IsEnabled { get; } - - public void AddAttribute(string name, string value) - { - _activityAdapter?.AddTag(name, value); - } - - public void AddIntegerAttribute(string name, int value) - { - _activityAdapter?.AddTag(name, value); - } - - public void AddAttribute<T>(string name, -#if AZURE_NULLABLE - [AllowNull] -#endif - T value) - { - AddAttribute(name, value, v => Convert.ToString(v, CultureInfo.InvariantCulture) ?? string.Empty); - } - - public void AddAttribute<T>(string name, T value, Func<T, string> format) - { - if (_activityAdapter != null) - { - var formattedValue = format(value); - _activityAdapter.AddTag(name, formattedValue); - } - } - - /// <summary> - /// Adds a link to the scope. This must be called before <see cref="Start"/> has been called for the DiagnosticScope. - /// </summary> - /// <param name="traceparent">The traceparent for the link.</param> - /// <param name="tracestate">The tracestate for the link.</param> - /// <param name="attributes">Optional attributes to associate with the link.</param> - public void AddLink(string traceparent, string tracestate, IDictionary<string, string> attributes = null) - { - _activityAdapter?.AddLink(traceparent, tracestate, attributes); - } - - public void Start() - { - Activity started = _activityAdapter?.Start(); - started?.SetCustomProperty(AzureSdkScopeLabel, AzureSdkScopeValue); - } - - public void SetDisplayName(string displayName) - { - _activityAdapter?.SetDisplayName(displayName); - } - - public void SetStartTime(DateTime dateTime) - { - _activityAdapter?.SetStartTime(dateTime); - } - - /// <summary> - /// Sets the trace context for the current scope. - /// </summary> - /// <param name="traceparent">The trace parent to set for the current scope.</param> - /// <param name="tracestate">The trace state to set for the current scope.</param> - public void SetTraceContext(string traceparent, string tracestate = default) - { - _activityAdapter?.SetTraceContext(traceparent, tracestate); - } - - public void Dispose() - { - // Reverse the Start order - _activityAdapter?.Dispose(); - } - - /// <summary> - /// Marks the scope as failed. - /// </summary> - /// <param name="exception">The exception to associate with the failed scope.</param> - public void Failed(Exception exception = default) - { - _activityAdapter?.MarkFailed(exception); - } - -#if NETCOREAPP2_1 - /// <summary> - /// Kind describes the relationship between the Activity, its parents, and its children in a Trace. - /// </summary> - public enum ActivityKind - { - /// <summary> - /// Default value. - /// Indicates that the Activity represents an internal operation within an application, as opposed to an operations with remote parents or children. - /// </summary> - Internal = 0, - - /// <summary> - /// Server activity represents request incoming from external component. - /// </summary> - Server = 1, - - /// <summary> - /// Client activity represents outgoing request to the external component. - /// </summary> - Client = 2, - - /// <summary> - /// Producer activity represents output provided to external components. - /// </summary> - Producer = 3, - - /// <summary> - /// Consumer activity represents output received from an external component. - /// </summary> - Consumer = 4, - } -#endif - - private class DiagnosticActivity : Activity - { -#pragma warning disable 109 // extra new modifier - public new IEnumerable<Activity> Links { get; set; } = Array.Empty<Activity>(); -#pragma warning restore 109 - - public DiagnosticActivity(string operationName) : base(operationName) - { - } - } - - private class ActivityAdapter : IDisposable - { -#if NETCOREAPP2_1 - private readonly object? _activitySource; -#else - private readonly ActivitySource _activitySource; -#endif - private readonly DiagnosticSource _diagnosticSource; - private readonly string _activityName; -#if NETCOREAPP2_1 - private readonly ActivityKind _kind; -#else - private readonly ActivityKind _kind; -#endif - private readonly object _diagnosticSourceArgs; - - private Activity _currentActivity; - private Activity _sampleOutActivity; - -#if NETCOREAPP2_1 - private ICollection<KeyValuePair<string,object>>? _tagCollection; -#else - private ActivityTagsCollection _tagCollection; -#endif - private DateTimeOffset _startTime; - private List<Activity> _links; - private string _traceparent; - private string _tracestate; - private string _displayName; - -#if NETCOREAPP2_1 - public ActivityAdapter(object? activitySource, DiagnosticSource diagnosticSource, string activityName, ActivityKind kind, object? diagnosticSourceArgs) -#else - public ActivityAdapter(ActivitySource activitySource, DiagnosticSource diagnosticSource, string activityName, ActivityKind kind, object diagnosticSourceArgs) -#endif - { - _activitySource = activitySource; - _diagnosticSource = diagnosticSource; - _activityName = activityName; - _kind = kind; - _diagnosticSourceArgs = diagnosticSourceArgs; - } - - public void AddTag(string name, object value) - { - if (_currentActivity == null) - { - // Activity is not started yet, add the value to the collection - // that is going to be passed to StartActivity -#if NETCOREAPP2_1 - _tagCollection ??= ActivityExtensions.CreateTagsCollection() ?? new List<KeyValuePair<string, object>>(); - _tagCollection?.Add(new KeyValuePair<string, object>(name, value!)); -#else - _tagCollection = new ActivityTagsCollection() ?? _tagCollection; - _tagCollection.Add(name, value); -#endif - } - else - { -#if NETCOREAPP2_1 - _currentActivity?.AddObjectTag(name, value); -#else - _currentActivity?.AddTag(name, value); -#endif - } - } - -#if NETCOREAPP2_1 - private IList? GetActivitySourceLinkCollection() -#else - private List<ActivityLink> GetActivitySourceLinkCollection() -#endif - { - if (_links == null) - { - return null; - } - -#if NETCOREAPP2_1 - var linkCollection = ActivityExtensions.CreateLinkCollection(); - if (linkCollection == null) - { - return null; - } -#else - var linkCollection = new List<ActivityLink>(); -#endif - - foreach (var activity in _links) - { -#if NETCOREAPP2_1 - ICollection<KeyValuePair<string,object>>? linkTagsCollection = ActivityExtensions.CreateTagsCollection(); - if (linkTagsCollection != null) - { - foreach (var tag in activity.Tags) - { - linkTagsCollection.Add(new KeyValuePair<string, object>(tag.Key, tag.Value!)); - } - } - - var link = ActivityExtensions.CreateActivityLink(activity.ParentId!, activity.GetTraceState(), linkTagsCollection); - if (link != null) - { - linkCollection.Add(link); - } -#else - ActivityTagsCollection linkTagsCollection = new ActivityTagsCollection(); - foreach (var tag in activity.Tags) - { - linkTagsCollection.Add(tag.Key, tag.Value); - } - - var contextWasParsed = ActivityContext.TryParse(activity.ParentId, activity.TraceStateString, out var context); - if (contextWasParsed) - { - var link = new ActivityLink(context, linkTagsCollection); - linkCollection.Add(link); - } -#endif - } - - return linkCollection; - } - - public void AddLink(string traceparent, string tracestate, IDictionary<string, string> attributes) - { - var linkedActivity = new Activity("LinkedActivity"); - linkedActivity.SetParentId(traceparent); -#if NETCOREAPP2_1 - linkedActivity.SetW3CFormat(); - linkedActivity.SetTraceState(tracestate); -#else - linkedActivity.SetIdFormat(ActivityIdFormat.W3C); - linkedActivity.TraceStateString = tracestate; -#endif - - if (attributes != null) - { - foreach (var kvp in attributes) - { - linkedActivity.AddTag(kvp.Key, kvp.Value); - } - } - - _links = new List<Activity>() ?? _links; - _links.Add(linkedActivity); - } - - public Activity Start() - { - _currentActivity = StartActivitySourceActivity(); - if (_currentActivity != null) - { -#if NETCOREAPP2_1 - if (!_currentActivity.GetIsAllDataRequested()) -#else - if (!_currentActivity.IsAllDataRequested) -#endif - { - _sampleOutActivity = _currentActivity; - _currentActivity = null; - - return null; - } - - _currentActivity.AddTag(OpenTelemetrySchemaAttribute, OpenTelemetrySchemaVersion); - } - else - { - if (!_diagnosticSource.IsEnabled(_activityName, _diagnosticSourceArgs)) - { - return null; - } - - switch (_kind) - { - case ActivityKind.Internal: - AddTag("kind", "internal"); - break; - case ActivityKind.Server: - AddTag("kind", "server"); - break; - case ActivityKind.Client: - AddTag("kind", "client"); - break; - case ActivityKind.Producer: - AddTag("kind", "producer"); - break; - case ActivityKind.Consumer: - AddTag("kind", "consumer"); - break; - } - - _currentActivity = new DiagnosticActivity(_activityName) - { - Links = (IEnumerable<Activity>)_links ?? Array.Empty<Activity>(), - }; -#if NETCOREAPP2_1 - _currentActivity.SetW3CFormat(); -#else - _currentActivity.SetIdFormat(ActivityIdFormat.W3C); -#endif - - if (_startTime != default) - { - _currentActivity.SetStartTime(_startTime.UtcDateTime); - } - - if (_tagCollection != null) - { - foreach (var tag in _tagCollection) - { -#if NETCOREAPP2_1 - _currentActivity.AddObjectTag(tag.Key, tag.Value); -#else - _currentActivity.AddTag(tag.Key, tag.Value); -#endif - } - } - - if (_traceparent != null) - { - _currentActivity.SetParentId(_traceparent); - } - - if (_tracestate != null) - { -#if NETCOREAPP2_1 - _currentActivity.SetTraceState(_tracestate); -#else - _currentActivity.TraceStateString = _tracestate; -#endif - } - - _currentActivity.Start(); - } - - _diagnosticSource.Write(_activityName + ".Start", _diagnosticSourceArgs ?? _currentActivity); - - if (_displayName != null) - { -#if NETCOREAPP2_1 - _currentActivity?.SetDisplayName(_displayName); -#else - _currentActivity.DisplayName = _displayName; -#endif - } - - return _currentActivity; - } - - public void SetDisplayName(string displayName) - { - _displayName = displayName; - if (_currentActivity != null) - { -#if NETCOREAPP2_1 - _currentActivity.SetDisplayName(_displayName); -#else - _currentActivity.DisplayName = _displayName; -#endif - } - } - - private Activity StartActivitySourceActivity() - { -#if NETCOREAPP2_1 - return ActivityExtensions.ActivitySourceStartActivity( - _activitySource, - _activityName, - (int)_kind, - startTime: _startTime, - tags: _tagCollection, - links: GetActivitySourceLinkCollection(), - traceparent: _traceparent, - tracestate: _tracestate); -#else - if (_activitySource == null) - { - return null; - } - ActivityContext context; - if (_traceparent != null) - { - context = ActivityContext.Parse(_traceparent, _tracestate); - } - else - { - context = new ActivityContext(); - } - var activity = _activitySource.StartActivity(_activityName, _kind, context, _tagCollection, GetActivitySourceLinkCollection(), _startTime); - return activity; -#endif - } - - public void SetStartTime(DateTime startTime) - { - _startTime = startTime; - _currentActivity?.SetStartTime(startTime); - } - - public void MarkFailed(Exception exception) - { - if (exception != null) - { - _diagnosticSource?.Write(_activityName + ".Exception", exception); - } - -#if NETCOREAPP2_1 - if (ActivityExtensions.SupportsActivitySource()) - { - _currentActivity?.SetErrorStatus(exception?.ToString()); - } -#endif -#if NET6_0_OR_GREATER - _currentActivity?.SetStatus(ActivityStatusCode.Error, exception?.ToString()); -#endif - } - - public void SetTraceContext(string traceparent, string tracestate) - { - if (_currentActivity != null) - { - throw new InvalidOperationException("Traceparent can not be set after the activity is started."); - } - _traceparent = traceparent; - _tracestate = tracestate; - } - - public void Dispose() - { - var activity = _currentActivity ?? _sampleOutActivity; - if (activity == null) - { - return; - } - - if (activity.Duration == TimeSpan.Zero) - activity.SetEndTime(DateTime.UtcNow); - - _diagnosticSource.Write(_activityName + ".Stop", _diagnosticSourceArgs); - -#if NETCOREAPP2_1 - if (!activity.TryDispose()) - { - activity.Stop(); - } -#else - activity.Dispose(); -#endif - } - } - } - -#if NETCOREAPP2_1 -#pragma warning disable SA1507 // File can not contain multiple types - /// <summary> - /// Until we can reference the 5.0 of System.Diagnostics.DiagnosticSource - /// </summary> - internal static class ActivityExtensions - { - static ActivityExtensions() - { - ResetFeatureSwitch(); - } - - private static bool SupportsActivitySourceSwitch; - - private static readonly Type? ActivitySourceType = Type.GetType("System.Diagnostics.ActivitySource, System.Diagnostics.DiagnosticSource"); - private static readonly Type? ActivityKindType = Type.GetType("System.Diagnostics.ActivityKind, System.Diagnostics.DiagnosticSource"); - private static readonly Type? ActivityTagsCollectionType = Type.GetType("System.Diagnostics.ActivityTagsCollection, System.Diagnostics.DiagnosticSource"); - private static readonly Type? ActivityLinkType = Type.GetType("System.Diagnostics.ActivityLink, System.Diagnostics.DiagnosticSource"); - private static readonly Type? ActivityContextType = Type.GetType("System.Diagnostics.ActivityContext, System.Diagnostics.DiagnosticSource"); - private static readonly Type? ActivityStatusCodeType = Type.GetType("System.Diagnostics.ActivityStatusCode, System.Diagnostics.DiagnosticSource"); - - private static Action<Activity, int>? SetIdFormatMethod; - private static Func<Activity, string?>? GetTraceStateStringMethod; - private static Action<Activity, string?>? SetTraceStateStringMethod; - private static Action<Activity, int, string?>? SetErrorStatusMethod; - private static Func<Activity, int>? GetIdFormatMethod; - private static Func<Activity, bool>? GetAllDataRequestedMethod; - private static Action<Activity, string, object?>? ActivityAddTagMethod; - private static Func<object, string, int, object?, ICollection<KeyValuePair<string, object>>?, IList?, DateTimeOffset, Activity?>? ActivitySourceStartActivityMethod; - private static Func<object, bool>? ActivitySourceHasListenersMethod; - private static Func<string, string?, ICollection<KeyValuePair<string, object>>?, object?>? CreateActivityLinkMethod; - private static Func<ICollection<KeyValuePair<string,object>>?>? CreateTagsCollectionMethod; - private static Func<Activity, string, object?>? GetCustomPropertyMethod; - private static Action<Activity, string, object>? SetCustomPropertyMethod; - private static readonly ParameterExpression ActivityParameter = Expression.Parameter(typeof(Activity)); - private static MethodInfo? ParseActivityContextMethod; - private static Action<Activity, string>? SetDisplayNameMethod; - - public static object? GetCustomProperty(this Activity activity, string propertyName) - { - if (GetCustomPropertyMethod == null) - { - var method = typeof(Activity).GetMethod("GetCustomProperty"); - if (method == null) - { - GetCustomPropertyMethod = (_, _) => null; - } - else - { - var nameParameter = Expression.Parameter(typeof(string)); - - GetCustomPropertyMethod = Expression.Lambda<Func<Activity, string, object>>( - Expression.Convert(Expression.Call(ActivityParameter, method, nameParameter), typeof(object)), - ActivityParameter, nameParameter).Compile(); - } - } - - return GetCustomPropertyMethod(activity, propertyName); - } - - public static void SetCustomProperty(this Activity activity, string propertyName, object propertyValue) - { - if (SetCustomPropertyMethod == null) - { - var method = typeof(Activity).GetMethod("SetCustomProperty"); - if (method == null) - { - SetCustomPropertyMethod = (_, _, _) => { }; - } - else - { - var nameParameter = Expression.Parameter(typeof(string)); - var valueParameter = Expression.Parameter(typeof(object)); - - SetCustomPropertyMethod = Expression.Lambda<Action<Activity, string, object>>( - Expression.Call(ActivityParameter, method, nameParameter, valueParameter), - ActivityParameter, nameParameter, valueParameter).Compile(); - } - } - - SetCustomPropertyMethod(activity, propertyName, propertyValue); - } - - public static void SetW3CFormat(this Activity activity) - { - if (SetIdFormatMethod == null) - { - var method = typeof(Activity).GetMethod("SetIdFormat"); - if (method == null) - { - SetIdFormatMethod = (_, _) => { }; - } - else - { - var idParameter = Expression.Parameter(typeof(int)); - var convertedId = Expression.Convert(idParameter, method.GetParameters()[0].ParameterType); - - SetIdFormatMethod = Expression.Lambda<Action<Activity, int>>( - Expression.Call(ActivityParameter, method, convertedId), - ActivityParameter, idParameter).Compile(); - } - } - - SetIdFormatMethod(activity, 2 /* ActivityIdFormat.W3C */); - } - - public static bool IsW3CFormat(this Activity activity) - { - if (GetIdFormatMethod == null) - { - var method = typeof(Activity).GetProperty("IdFormat")?.GetMethod; - if (method == null) - { - GetIdFormatMethod = _ => -1; - } - else - { - GetIdFormatMethod = Expression.Lambda<Func<Activity, int>>( - Expression.Convert(Expression.Call(ActivityParameter, method), typeof(int)), - ActivityParameter).Compile(); - } - } - - - int result = GetIdFormatMethod(activity); - - return result == 2 /* ActivityIdFormat.W3C */; - } - - public static string? GetTraceState(this Activity activity) - { - if (GetTraceStateStringMethod == null) - { - var method = typeof(Activity).GetProperty("TraceStateString")?.GetMethod; - if (method == null) - { - GetTraceStateStringMethod = _ => null; - } - else - { - GetTraceStateStringMethod = Expression.Lambda<Func<Activity, string?>>( - Expression.Call(ActivityParameter, method), - ActivityParameter).Compile(); - } - } - - return GetTraceStateStringMethod(activity); - } - - public static bool GetIsAllDataRequested(this Activity activity) - { - if (GetAllDataRequestedMethod == null) - { - var method = typeof(Activity).GetProperty("IsAllDataRequested")?.GetMethod; - if (method == null) - { - GetAllDataRequestedMethod = _ => true; - } - else - { - GetAllDataRequestedMethod = Expression.Lambda<Func<Activity, bool>>( - Expression.Call(ActivityParameter, method), - ActivityParameter).Compile(); - } - } - return GetAllDataRequestedMethod(activity); - } - - public static void SetDisplayName(this Activity activity, string displayName) - { - if (displayName != null) - { - if (SetDisplayNameMethod == null) - { - var method = typeof(Activity).GetProperty("DisplayName")?.SetMethod; - if (method == null) - { - SetDisplayNameMethod = (_, _) => { }; - } - else - { - var displayNameParameter = Expression.Parameter(typeof(string)); - var convertedParameter = Expression.Convert(displayNameParameter, method.GetParameters()[0].ParameterType); - SetDisplayNameMethod = Expression.Lambda<Action<Activity, string>>( - Expression.Call(ActivityParameter, method, convertedParameter), - ActivityParameter, displayNameParameter).Compile(); - } - } - SetDisplayNameMethod(activity, displayName); - } - } - - public static void SetTraceState(this Activity activity, string? tracestate) - { - if (SetTraceStateStringMethod == null) - { - var method = typeof(Activity).GetProperty("TraceStateString")?.SetMethod; - if (method == null) - { - SetTraceStateStringMethod = (_, _) => { }; - } - else - { - var tracestateParameter = Expression.Parameter(typeof(string)); - var convertedParameter = Expression.Convert(tracestateParameter, method.GetParameters()[0].ParameterType); - SetTraceStateStringMethod = Expression.Lambda<Action<Activity, string?>>( - Expression.Call(ActivityParameter, method, convertedParameter), - ActivityParameter, tracestateParameter).Compile(); - } - } - - SetTraceStateStringMethod(activity, tracestate); - } - - public static void SetErrorStatus(this Activity activity, string? errorDescription) - { - if (SetErrorStatusMethod == null) - { - if (ActivityStatusCodeType == null) - { - SetErrorStatusMethod = (_, _, _) => { }; - } - else - { - var method = typeof(Activity).GetMethod("SetStatus", BindingFlags.Instance | BindingFlags.Public, null, new Type[] - { - ActivityStatusCodeType, - typeof(string) - }, null); - if (method == null) - { - SetErrorStatusMethod = (_, _, _) => { }; - } - else - { - var methodParameters = method.GetParameters(); - - var statusParameter = Expression.Parameter(typeof(int)); - var descriptionParameter = Expression.Parameter(typeof(string)); - SetErrorStatusMethod = Expression.Lambda<Action<Activity, int, string?>>( - Expression.Call(ActivityParameter, method, Expression.Convert(statusParameter, methodParameters[0].ParameterType), descriptionParameter), - ActivityParameter, statusParameter, descriptionParameter).Compile(); - } - } - } - SetErrorStatusMethod(activity, 2 /* Error */, errorDescription); - } - - public static void AddObjectTag(this Activity activity, string name, object value) - { - if (ActivityAddTagMethod == null) - { - var method = typeof(Activity).GetMethod("AddTag", BindingFlags.Instance | BindingFlags.Public, null, new Type[] - { - typeof(string), - typeof(object) - }, null); - - if (method == null) - { - // If the object overload is not available, fall back to the string overload. The assumption is that the object overload - // not being available means that we cannot be using activity source, so the string cast should never fail because we will always - // be passing a string value. - ActivityAddTagMethod = (activityParameter, nameParameter, valueParameter) => activityParameter.AddTag( - nameParameter, - // null check is required to keep nullable reference compilation happy - valueParameter == null ? null : (string)valueParameter); - } - else - { - var nameParameter = Expression.Parameter(typeof(string)); - var valueParameter = Expression.Parameter(typeof(object)); - - ActivityAddTagMethod = Expression.Lambda<Action<Activity, string, object?>>( - Expression.Call(ActivityParameter, method, nameParameter, valueParameter), - ActivityParameter, nameParameter, valueParameter).Compile(); - } - } - - ActivityAddTagMethod(activity, name, value); - } - - public static bool SupportsActivitySource() - { - return SupportsActivitySourceSwitch && ActivitySourceType != null; - } - - public static ICollection<KeyValuePair<string,object>>? CreateTagsCollection() - { - if (CreateTagsCollectionMethod == null) - { - var ctor = ActivityTagsCollectionType?.GetConstructor(Array.Empty<Type>()); - if (ctor == null) - { - CreateTagsCollectionMethod = () => null; - } - else - { - CreateTagsCollectionMethod = Expression.Lambda<Func<ICollection<KeyValuePair<string,object>>?>>( - Expression.New(ctor)).Compile(); - } - } - - return CreateTagsCollectionMethod(); - } - - public static object? CreateActivityLink(string traceparent, string? tracestate, ICollection<KeyValuePair<string,object>>? tags) - { - if (ActivityLinkType == null) - { - return null; - } - - if (CreateActivityLinkMethod == null) - { - var parseMethod = ActivityContextType?.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public); - var ctor = ActivityLinkType?.GetConstructor(new[] { ActivityContextType!, ActivityTagsCollectionType! }); - - if (parseMethod == null || - ctor == null || - ActivityTagsCollectionType == null || - ActivityContextType == null) - { - CreateActivityLinkMethod = (_, _, _) => null; - } - else - { - var traceparentParameter = Expression.Parameter(typeof(string)); - var tracestateParameter = Expression.Parameter(typeof(string)); - var tagsParameter = Expression.Parameter(typeof(ICollection<KeyValuePair<string,object>>)); - - CreateActivityLinkMethod = Expression.Lambda<Func<string, string?, ICollection<KeyValuePair<string, object>>?, object?>>( - Expression.TryCatch( - Expression.Convert(Expression.New(ctor, - Expression.Call(parseMethod, traceparentParameter, tracestateParameter), - Expression.Convert(tagsParameter, ActivityTagsCollectionType)), typeof(object)), - Expression.Catch(typeof(Exception), Expression.Default(typeof(object)))), - traceparentParameter, tracestateParameter, tagsParameter).Compile(); - } - } - - return CreateActivityLinkMethod(traceparent, tracestate, tags); - } - - public static bool ActivitySourceHasListeners(object? activitySource) - { - if (!SupportsActivitySource()) - { - return false; - } - - if (activitySource == null) - { - return false; - } - - if (ActivitySourceHasListenersMethod == null) - { - var method = ActivitySourceType?.GetMethod("HasListeners", BindingFlags.Instance | BindingFlags.Public); - if (method == null || - ActivitySourceType == null) - { - ActivitySourceHasListenersMethod = _ => false; - } - else - { - var sourceParameter = Expression.Parameter(typeof(object)); - ActivitySourceHasListenersMethod = Expression.Lambda<Func<object, bool>>( - Expression.Call(Expression.Convert(sourceParameter, ActivitySourceType), method), - sourceParameter).Compile(); - } - } - - return ActivitySourceHasListenersMethod.Invoke(activitySource); - } - - public static Activity? ActivitySourceStartActivity( - object? activitySource, - string activityName, - int kind, - DateTimeOffset startTime, - ICollection<KeyValuePair<string, object>>? tags, - IList? links, - string? traceparent, - string? tracestate) - { - if (activitySource == null) - { - return null; - } - - object? activityContext = default; - - if (ActivitySourceStartActivityMethod == null) - { - if (ActivityLinkType == null || - ActivitySourceType == null || - ActivityContextType == null || - ActivityKindType == null) - { - ActivitySourceStartActivityMethod = (_, _, _, _, _, _, _) => null; - } - else - { - var method = ActivitySourceType?.GetMethod("StartActivity", BindingFlags.Instance | BindingFlags.Public, null, new[] - { - typeof(string), - ActivityKindType, - ActivityContextType, - typeof(IEnumerable<KeyValuePair<string, object>>), - typeof(IEnumerable<>).MakeGenericType(ActivityLinkType), - typeof(DateTimeOffset) - }, null); - - if (method == null) - { - ActivitySourceStartActivityMethod = (_, _, _, _, _, _, _) => null; - } - else - { - var sourceParameter = Expression.Parameter(typeof(object)); - var nameParameter = Expression.Parameter(typeof(string)); - var kindParameter = Expression.Parameter(typeof(int)); - var contextParameter = Expression.Parameter(typeof(object)); - var startTimeParameter = Expression.Parameter(typeof(DateTimeOffset)); - var tagsParameter = Expression.Parameter(typeof(ICollection<KeyValuePair<string, object>>)); - var linksParameter = Expression.Parameter(typeof(IList)); - var methodParameter = method.GetParameters(); - ParseActivityContextMethod = ActivityContextType.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public); - - ActivitySourceStartActivityMethod = Expression.Lambda<Func<object, string, int, object?, ICollection<KeyValuePair<string, object>>?, IList?, DateTimeOffset, Activity?>>( - Expression.Call( - Expression.Convert(sourceParameter, method.DeclaringType!), - method, - nameParameter, - Expression.Convert(kindParameter, methodParameter[1].ParameterType), - Expression.Convert(contextParameter, methodParameter[2].ParameterType), - Expression.Convert(tagsParameter, methodParameter[3].ParameterType), - Expression.Convert(linksParameter, methodParameter[4].ParameterType), - Expression.Convert(startTimeParameter, methodParameter[5].ParameterType)), - sourceParameter, nameParameter, kindParameter, contextParameter, tagsParameter, linksParameter, startTimeParameter).Compile(); - } - } - } - - if (ActivityContextType != null && ParseActivityContextMethod != null) - { - if (traceparent != null) - activityContext = ParseActivityContextMethod.Invoke(null, new[] {traceparent, tracestate})!; - else - // because ActivityContext is a struct, we need to create a default instance rather than allowing the argument to be null - activityContext = Activator.CreateInstance(ActivityContextType); - } - - return ActivitySourceStartActivityMethod.Invoke(activitySource, activityName, kind, activityContext, tags, links, startTime); - } - - public static object? CreateActivitySource(string name) - { - if (ActivitySourceType == null) - { - return null; - } - return Activator.CreateInstance(ActivitySourceType, - name, // name - null // version - ); - } - - public static IList? CreateLinkCollection() - { - if (ActivityLinkType == null) - { - return null; - } - return Activator.CreateInstance(typeof(List<>).MakeGenericType(ActivityLinkType)) as IList; - } - - public static bool TryDispose(this Activity activity) - { - if (activity is IDisposable disposable) - { - disposable.Dispose(); - return true; - } - - return false; - } - - public static void ResetFeatureSwitch() - { - SupportsActivitySourceSwitch = AppContextSwitchHelper.GetConfigValue( - "Azure.Experimental.EnableActivitySource", - "AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE"); - } - } -#else -#pragma warning disable SA1507 // File can not contain multiple types - /// <summary> - /// Until Activity Source is no longer considered experimental. - /// </summary> - internal static class ActivityExtensions - { - public static bool SupportsActivitySource { get; private set; } - - public static void ResetFeatureSwitch() - { - SupportsActivitySource = AppContextSwitchHelper.GetConfigValue( - "Azure.Experimental.EnableActivitySource", - "AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE"); - } - } -#endif -} diff --git a/src/Accounts/Authentication/Identity/Core/DiagnosticScopeFactory.cs b/src/Accounts/Authentication/Identity/Core/DiagnosticScopeFactory.cs deleted file mode 100644 index 353a362bfd3f..000000000000 --- a/src/Accounts/Authentication/Identity/Core/DiagnosticScopeFactory.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ -#pragma warning disable CA1001 // Types that own disposable fields should be disposable - internal class DiagnosticScopeFactory -#pragma warning restore CA1001 // Types that own disposable fields should be disposable - { - private static Dictionary<string, DiagnosticListener> _listeners; - private readonly string _resourceProviderNamespace; - private readonly DiagnosticListener _source; - private readonly bool _suppressNestedClientActivities; - -#if NETCOREAPP2_1 - private static readonly ConcurrentDictionary<string, object?> ActivitySources = new(); -#else - private static readonly ConcurrentDictionary<string, ActivitySource> ActivitySources = new ConcurrentDictionary<string, ActivitySource>(); -#endif - - public DiagnosticScopeFactory(string clientNamespace, string resourceProviderNamespace, bool isActivityEnabled, bool suppressNestedClientActivities) - { - _resourceProviderNamespace = resourceProviderNamespace; - IsActivityEnabled = isActivityEnabled; - _suppressNestedClientActivities = suppressNestedClientActivities; - - if (IsActivityEnabled) - { - var listeners = LazyInitializer.EnsureInitialized(ref _listeners); - - lock (listeners) - { - if (!listeners.TryGetValue(clientNamespace, out _source)) - { - _source = new DiagnosticListener(clientNamespace); - listeners[clientNamespace] = _source; - } - } - } - } - - public bool IsActivityEnabled { get; } - -#if NETCOREAPP2_1 - public DiagnosticScope CreateScope(string name, DiagnosticScope.ActivityKind kind = DiagnosticScope.ActivityKind.Internal) -#else - public DiagnosticScope CreateScope(string name, ActivityKind kind = ActivityKind.Internal) -#endif - { - if (_source == null) - { - return default; - } - - var scope = new DiagnosticScope( - scopeName: name, - source: _source, - diagnosticSourceArgs: null, - activitySource: GetActivitySource(_source.Name, name), - kind: kind, - suppressNestedClientActivities: _suppressNestedClientActivities); - - if (_resourceProviderNamespace != null) - { - scope.AddAttribute("az.namespace", _resourceProviderNamespace); - } - return scope; - } - - /// <summary> - /// This method combines client namespace and operation name into an ActivitySource name and creates the activity source. - /// For example: - /// ns: Azure.Storage.Blobs - /// name: BlobClient.DownloadTo - /// result Azure.Storage.Blobs.BlobClient - /// </summary> -#if NETCOREAPP2_1 - private static object? GetActivitySource(string ns, string name) -#else - private static ActivitySource GetActivitySource(string ns, string name) -#endif - { -#if NETCOREAPP2_1 - if (!ActivityExtensions.SupportsActivitySource()) -#else - if (!ActivityExtensions.SupportsActivitySource) -#endif - { - return null; - } - - string clientName = ns; - int indexOfDot = name.IndexOf(".", StringComparison.OrdinalIgnoreCase); - if (indexOfDot != -1) - { - clientName += "." + name.Substring(0, indexOfDot); - } -#if NETCOREAPP2_1 - return ActivitySources.GetOrAdd(clientName, static n => ActivityExtensions.CreateActivitySource(n)); -#else - return ActivitySources.GetOrAdd(clientName, n => new ActivitySource(n)); -#endif - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/HttpMessageSanitizer.cs b/src/Accounts/Authentication/Identity/Core/HttpMessageSanitizer.cs deleted file mode 100644 index bb6d0d74ade3..000000000000 --- a/src/Accounts/Authentication/Identity/Core/HttpMessageSanitizer.cs +++ /dev/null @@ -1,151 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - internal class HttpMessageSanitizer - { - private const string LogAllValue = "*"; - private readonly bool _logAllHeaders; - private readonly bool _logFullQueries; - private readonly string[] _allowedQueryParameters; - private readonly string _redactedPlaceholder; - private readonly HashSet<string> _allowedHeaders; - - internal static HttpMessageSanitizer Default = new HttpMessageSanitizer(Array.Empty<string>(), Array.Empty<string>()); - - public HttpMessageSanitizer(string[] allowedQueryParameters, string[] allowedHeaders, string redactedPlaceholder = "REDACTED") - { - _logAllHeaders = allowedHeaders.Contains(LogAllValue); - _logFullQueries = allowedQueryParameters.Contains(LogAllValue); - - _allowedQueryParameters = allowedQueryParameters; - _redactedPlaceholder = redactedPlaceholder; - _allowedHeaders = new HashSet<string>(allowedHeaders, StringComparer.InvariantCultureIgnoreCase); - } - - public string SanitizeHeader(string name, string value) - { - if (_logAllHeaders || _allowedHeaders.Contains(name)) - { - return value; - } - - return _redactedPlaceholder; - } - - public string SanitizeUrl(string url) - { - if (_logFullQueries) - { - return url; - } - -#if NET5_0_OR_GREATER - int indexOfQuerySeparator = url.IndexOf('?', StringComparison.Ordinal); -#else - int indexOfQuerySeparator = url.IndexOf('?'); -#endif - - if (indexOfQuerySeparator == -1) - { - return url; - } - - StringBuilder stringBuilder = new StringBuilder(url.Length); - stringBuilder.Append(url, 0, indexOfQuerySeparator); - - string query = url.Substring(indexOfQuerySeparator); - - int queryIndex = 1; - stringBuilder.Append('?'); - - do - { - int endOfParameterValue = query.IndexOf('&', queryIndex); - int endOfParameterName = query.IndexOf('=', queryIndex); - bool noValue = false; - - // Check if we have parameter without value - if (endOfParameterValue == -1 && endOfParameterName == -1 || - endOfParameterValue != -1 && (endOfParameterName == -1 || endOfParameterName > endOfParameterValue)) - { - endOfParameterName = endOfParameterValue; - noValue = true; - } - - if (endOfParameterName == -1) - { - endOfParameterName = query.Length; - } - - if (endOfParameterValue == -1) - { - endOfParameterValue = query.Length; - } - else - { - // include the separator - endOfParameterValue++; - } - - ReadOnlySpan<char> parameterName = query.AsSpan(queryIndex, endOfParameterName - queryIndex); - - bool isAllowed = false; - foreach (string name in _allowedQueryParameters) - { - if (parameterName.Equals(name.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - isAllowed = true; - break; - } - } - - int valueLength = endOfParameterValue - queryIndex; - int nameLength = endOfParameterName - queryIndex; - - if (isAllowed) - { - stringBuilder.Append(query, queryIndex, valueLength); - } - else - { - if (noValue) - { - stringBuilder.Append(query, queryIndex, valueLength); - } - else - { - stringBuilder.Append(query, queryIndex, nameLength); - stringBuilder.Append('='); - stringBuilder.Append(_redactedPlaceholder); - if (query[endOfParameterValue - 1] == '&') - { - stringBuilder.Append('&'); - } - } - } - - queryIndex += valueLength; - } while (queryIndex < query.Length); - - return stringBuilder.ToString(); - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/HttpPipelineMessageHandler.cs b/src/Accounts/Authentication/Identity/Core/HttpPipelineMessageHandler.cs deleted file mode 100644 index aae8019c6b39..000000000000 --- a/src/Accounts/Authentication/Identity/Core/HttpPipelineMessageHandler.cs +++ /dev/null @@ -1,118 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Core.Pipeline; -using Azure; -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using System.Net; -using Azure.Core; -using System.Collections.Generic; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - /// <summary> - /// An HttpMessageHandler which delegates SendAsync to a specified HttpPipeline. - /// </summary> - internal class HttpPipelineMessageHandler : HttpMessageHandler - { - private readonly HttpPipeline _pipeline; - - public HttpPipelineMessageHandler(HttpPipeline pipeline) - { - _pipeline = pipeline; - } - - protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - Request pipelineRequest = await ToPipelineRequestAsync(request).ConfigureAwait(false); - - Response pipelineResponse = await _pipeline.SendRequestAsync(pipelineRequest, cancellationToken).ConfigureAwait(false); - - return ToHttpResponseMessage(pipelineResponse); - } - - private async Task<Request> ToPipelineRequestAsync(HttpRequestMessage request) - { - Request pipelineRequest = _pipeline.CreateRequest(); - - pipelineRequest.Method = RequestMethod.Parse(request.Method.Method); - - pipelineRequest.Uri.Reset(request.RequestUri); - - pipelineRequest.Content = await ToPipelineRequestContentAsync(request.Content).ConfigureAwait(false); - - foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers) - { - foreach (var value in header.Value) - { - pipelineRequest.Headers.Add(header.Key, value); - } - } - - if (request.Content != null) - { - foreach (KeyValuePair<string, IEnumerable<string>> header in request.Content.Headers) - { - foreach (var value in header.Value) - { - pipelineRequest.Headers.Add(header.Key, value); - } - } - } - - return pipelineRequest; - } - - private static HttpResponseMessage ToHttpResponseMessage(Response response) - { - HttpResponseMessage responseMessage = new HttpResponseMessage - { - StatusCode = (HttpStatusCode)response.Status - }; - if (response.ContentStream != null) - { - responseMessage.Content = new StreamContent(response.ContentStream); - } - - foreach (HttpHeader header in response.Headers) - { - if (response.Headers.TryGetValues(header.Name, out IEnumerable<string> values)) - { - if (!responseMessage.Headers.TryAddWithoutValidation(header.Name, values)) - { - if (responseMessage.Content == null || !responseMessage.Content.Headers.TryAddWithoutValidation(header.Name, values)) - { - throw new InvalidOperationException("Unable to add header to response or content"); - } - } - } - } - - return responseMessage; - } - - private static async Task<RequestContent> ToPipelineRequestContentAsync(HttpContent content) - { - if (content != null) - { - return RequestContent.Create(await content.ReadAsStreamAsync().ConfigureAwait(false)); - } - - return null; - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/LightweightPkcs8Decoder.cs b/src/Accounts/Authentication/Identity/Core/LightweightPkcs8Decoder.cs deleted file mode 100644 index 61b3e48e87dd..000000000000 --- a/src/Accounts/Authentication/Identity/Core/LightweightPkcs8Decoder.cs +++ /dev/null @@ -1,370 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - /// <summary> - /// This is a very targeted PKCS#8 decoder for use when reading a PKCS# encoded RSA private key from an - /// DER encoded ASN.1 blob. In an ideal world, we would be able to call AsymmetricAlgorithm.ImportPkcs8PrivateKey - /// off an RSA object to import the private key from a byte array, which we got from the PEM file. There - /// are a few issues with this however: - /// - /// 1. ImportPkcs8PrivateKey does not exist in the Desktop .NET Framework as of today. - /// 2. ImportPkcs8PrivateKey was added to .NET Core in 3.0, and we'd love to be able to support this - /// on older versions of .NET Core. - /// - /// This code is able to decode RSA keys (without any attributes) from well formed PKCS#8 blobs. - /// </summary> - internal static partial class LightweightPkcs8Decoder - { - private static readonly byte[] s_derIntegerZero = { 0x02, 0x01, 0x00 }; - - private static readonly byte[] s_rsaAlgorithmId = - { - 0x30, 0x0D, - 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, - 0x05, 0x00, - }; - - internal static byte[] ReadBitString(byte[] data, ref int offset) - { - // Adapted from https://github.com/dotnet/runtime/blob/be74b4bd/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.BitString.cs#L156 - - if (data[offset++] != 0x03) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - int length = ReadLength(data, ref offset); - if (length == 0) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - int unusedBitCount = data[offset++]; - if (unusedBitCount > 7) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - Span<byte> span = data.AsSpan(offset, length - 1); - - // Build a mask for the bits that are used so the normalized value can be computed - // - // If 3 bits are "unused" then build a mask for them to check for 0. - // -1 << 3 => 0b1111_1111 << 3 => 0b1111_1000 - int mask = -1 << unusedBitCount; - byte lastByte = span[span.Length - 1]; - byte maskedByte = (byte)(lastByte & mask); - - byte[] ret = new byte[span.Length]; - - Buffer.BlockCopy(data, offset, ret, 0, span.Length); - ret[span.Length - 1] = maskedByte; - - offset += span.Length; - - return ret; - } - - internal static string ReadObjectIdentifier(byte[] data, ref int offset) - { - // Adapted from https://github.com/dotnet/runtime/blob/be74b4bd/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Oid.cs#L175 - - if (data[offset++] != 0x06) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - int length = ReadLength(data, ref offset); - - StringBuilder ret = new StringBuilder(); - for (int i = offset; i < offset + length; i++) - { - byte val = data[i]; - - if (i == offset) - { - byte first; - if (val < 40) - { - first = 0; - } - else if (val < 80) - { - first = 1; - val -= 40; - } - else - { - throw new InvalidDataException("Unsupported PKCS#8 Data"); - } - - ret.Append(first).Append('.').Append(val); - } - else - { - if (val < 128) - { - ret.Append('.').Append(val); - } - else - { - ret.Append('.'); - - if (val == 0x80) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - // See how long the segment is. - int end = -1; - int idx; - - for (idx = i; idx < offset + length; idx++) - { - if ((data[idx] & 0x80) == 0) - { - end = idx; - break; - } - } - - if (end < 0) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - // 4 or fewer bytes fits into a signed integer. - int max = end + 1; - if (max <= i + 4) - { - // cspell:ignore accum - int accum = 0; - for (idx = i; idx < max; idx++) - { - val = data[idx]; - accum <<= 7; - accum |= (byte)(val & 0x7f); - } - - ret.Append(accum); - i = end; - } - else - { - throw new InvalidDataException("Unsupported PKCS#8 Data"); - } - } - } - } - - offset += length; - return ret.ToString(); - } - - internal static byte[] ReadOctetString(byte[] data, ref int offset) - { - if (data[offset++] != 0x04) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - int length = ReadLength(data, ref offset); - - byte[] ret = new byte[length]; - - Buffer.BlockCopy(data, offset, ret, 0, length); - offset += length; - - return ret; - } - - private static int ReadLength(byte[] data, ref int offset) - { - byte lengthOrLengthLength = data[offset++]; - - if (lengthOrLengthLength < 0x80) - { - return lengthOrLengthLength; - } - - int lengthLength = lengthOrLengthLength & 0x7F; - int length = 0; - - for (int i = 0; i < lengthLength; i++) - { - length <<= 8; - length |= data[offset++]; - - if (length > ushort.MaxValue) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - } - - return length; - } - - private static byte[] ReadUnsignedInteger(byte[] data, ref int offset, int targetSize = 0) - { - if (data[offset++] != 0x02) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - int length = ReadLength(data, ref offset); - - // Encoding rules say 0 is encoded as the one byte value 0x00. - // Since we expect unsigned, throw if the high bit is set. - if (length < 1 || data[offset] >= 0x80) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - byte[] ret; - - if (length == 1) - { - ret = new byte[length]; - ret[0] = data[offset++]; - return ret; - } - - if (data[offset] == 0) - { - offset++; - length--; - } - - if (targetSize != 0) - { - if (length > targetSize) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - ret = new byte[targetSize]; - } - else - { - ret = new byte[length]; - } - - Buffer.BlockCopy(data, offset, ret, ret.Length - length, length); - offset += length; - return ret; - } - - private static int ReadPayloadTagLength(byte[] data, ref int offset, byte tagValue) - { - if (data[offset++] != tagValue) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - return ReadLength(data, ref offset); - } - - private static void ConsumeFullPayloadTag(byte[] data, ref int offset, byte tagValue) - { - if (data[offset++] != tagValue) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - int length = ReadLength(data, ref offset); - - if (data.Length - offset != length) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - } - - private static void ConsumeMatch(byte[] data, ref int offset, byte[] toMatch) - { - if (data.Length - offset > toMatch.Length) - { - if (data.Skip(offset).Take(toMatch.Length).SequenceEqual(toMatch)) - { - offset += toMatch.Length; - return; - } - } - - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - public static RSA DecodeRSAPkcs8(byte[] pkcs8Bytes) - { - int offset = 0; - - // PrivateKeyInfo SEQUENCE - ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x30); - // PKCS#8 PrivateKeyInfo.version == 0 - ConsumeMatch(pkcs8Bytes, ref offset, s_derIntegerZero); - // rsaEncryption AlgorithmIdentifier value - ConsumeMatch(pkcs8Bytes, ref offset, s_rsaAlgorithmId); - // PrivateKeyInfo.privateKey OCTET STRING - ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x04); - // RSAPrivateKey SEQUENCE - ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x30); - // RSAPrivateKey.version == 0 - ConsumeMatch(pkcs8Bytes, ref offset, s_derIntegerZero); - - RSAParameters rsaParameters = new RSAParameters(); - rsaParameters.Modulus = ReadUnsignedInteger(pkcs8Bytes, ref offset); - rsaParameters.Exponent = ReadUnsignedInteger(pkcs8Bytes, ref offset); - rsaParameters.D = ReadUnsignedInteger(pkcs8Bytes, ref offset, rsaParameters.Modulus.Length); - int halfModulus = (rsaParameters.Modulus.Length + 1) / 2; - rsaParameters.P = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); - rsaParameters.Q = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); - rsaParameters.DP = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); - rsaParameters.DQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); - rsaParameters.InverseQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); - - if (offset != pkcs8Bytes.Length) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - - RSA rsa = RSA.Create(); - rsa.ImportParameters(rsaParameters); - return rsa; - } - - public static string DecodePrivateKeyOid(byte[] pkcs8Bytes) - { - int offset = 0; - - // PrivateKeyInfo SEQUENCE - ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x30); - - // PKCS#8 PrivateKeyInfo.version == 0 - ConsumeMatch(pkcs8Bytes, ref offset, s_derIntegerZero); - - // PKCS#8 PrivateKeyInfo.sequence - ReadPayloadTagLength(pkcs8Bytes, ref offset, 0x30); - - // Return the AlgorithmIdentifier value - return ReadObjectIdentifier(pkcs8Bytes, ref offset); - } - } -} diff --git a/src/Accounts/Authentication/Identity/Core/PemReader.cs b/src/Accounts/Authentication/Identity/Core/PemReader.cs deleted file mode 100644 index a1a8231b001e..000000000000 --- a/src/Accounts/Authentication/Identity/Core/PemReader.cs +++ /dev/null @@ -1,310 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System; -using System.IO; -using System.Reflection; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core -{ - /// <summary> - /// Reads PEM streams to parse PEM fields or load certificates. - /// </summary> - /// <remarks> - /// This class provides a downlevel PEM decoder since <c>PemEncoding</c> wasn't added until net5.0. - /// The <c>PemEncoding</c> class takes advantage of other implementation changes in net5.0 and, - /// based on conversations with the .NET team, runtime changes. - /// </remarks> - internal static partial class PemReader - { - // The following implementation was based on PemEncoding and reviewed by @bartonjs on the .NET / cryptography team. - private delegate void ImportPrivateKeyDelegate(ReadOnlySpan<byte> blob, out int bytesRead); - - private const string Prolog = "-----BEGIN "; - private const string Epilog = "-----END "; - private const string LabelEnd = "-----"; - - private const string RSAAlgorithmId = "1.2.840.113549.1.1.1"; - private const string ECDsaAlgorithmId = "1.2.840.10045.2.1"; - - private static bool s_rsaInitializedImportPkcs8PrivateKeyMethod; - private static MethodInfo s_rsaImportPkcs8PrivateKeyMethod; - private static MethodInfo s_rsaCopyWithPrivateKeyMethod; - - /// <summary> - /// Loads an <see cref="X509Certificate2"/> from PEM data. - /// </summary> - /// <param name="data">The PEM data to parse.</param> - /// <param name="cer">Optional public certificate data if not defined within the PEM data.</param> - /// <param name="keyType"> - /// Optional <see cref="KeyType"/> of the certificate private key. The default is <see cref="KeyType.Auto"/> to automatically detect. - /// Only support for <see cref="KeyType.RSA"/> is implemented by shared code. - /// </param> - /// <param name="allowCertificateOnly">Whether to create an <see cref="X509Certificate2"/> if no private key is read.</param> - /// <param name="keyStorageFlags">A combination of the enumeration values that control where and how to import the certificate.</param> - /// <returns>An <see cref="X509Certificate2"/> loaded from the PEM data.</returns> - /// <exception cref="CryptographicException">A cryptographic exception occurred when trying to create the <see cref="X509Certificate2"/>.</exception> - /// <exception cref="InvalidDataException"><paramref name="cer"/> is null and no CERTIFICATE field is defined in PEM, or no PRIVATE KEY is defined in PEM.</exception> - /// <exception cref="NotSupportedException">The <paramref name="keyType"/> is not supported.</exception> - /// <exception cref="PlatformNotSupportedException">Creating a <see cref="X509Certificate2"/> from PEM data is not supported on the current platform.</exception> - public static X509Certificate2 LoadCertificate(ReadOnlySpan<char> data, byte[] cer = null, KeyType keyType = KeyType.Auto, bool allowCertificateOnly = false, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) - { - byte[] priv = null; - - while (TryRead(data, out PemField field)) - { - // TODO: Consider building up a chain to determine the leaf certificate: https://github.com/Azure/azure-sdk-for-net/issues/19043 - if (field.Label.Equals("CERTIFICATE".AsSpan(), StringComparison.Ordinal)) - { - cer = field.FromBase64Data(); - } - else if (field.Label.Equals("PRIVATE KEY".AsSpan(), StringComparison.Ordinal)) - { - priv = field.FromBase64Data(); - } - - int offset = field.Start + field.Length; - if (offset >= data.Length) - { - break; - } - - data = data.Slice(offset); - } - - if (cer is null) - { - throw new InvalidDataException("The certificate is missing the public key"); - } - - if (priv is null) - { - if (allowCertificateOnly) - { - return new X509Certificate2(cer, (string)null, keyStorageFlags); - } - - throw new InvalidDataException("The certificate is missing the private key"); - } - - if (keyType == KeyType.Auto) - { - string oid = LightweightPkcs8Decoder.DecodePrivateKeyOid(priv); - - switch (oid) - { - case RSAAlgorithmId: - keyType = KeyType.RSA; - break; - case ECDsaAlgorithmId: - keyType = KeyType.ECDsa; - break; - default: - throw new NotSupportedException($"The private key algorithm ID {oid} is not supported"); - } - } - - if (keyType == KeyType.ECDsa) - { - X509Certificate2 certificate = null; - CreateECDsaCertificate(cer, priv, keyStorageFlags, ref certificate); - - return certificate ?? throw new NotSupportedException("Reading an ECDsa certificate from a PEM file is not supported"); - } - - return CreateRsaCertificate(cer, priv, keyStorageFlags); - } - - static partial void CreateECDsaCertificate(byte[] cer, byte[] key, X509KeyStorageFlags keyStorageFlags, ref X509Certificate2 certificate); - - private static X509Certificate2 CreateRsaCertificate(byte[] cer, byte[] key, X509KeyStorageFlags keyStorageFlags) - { - if (!s_rsaInitializedImportPkcs8PrivateKeyMethod) - { - // ImportPkcs8PrivateKey was added in .NET Core 3.0 and is only present on Core. We will fall back to a lightweight decoder if this method is missing from the current runtime. - s_rsaImportPkcs8PrivateKeyMethod = typeof(RSA).GetMethod("ImportPkcs8PrivateKey", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(ReadOnlySpan<byte>), typeof(int).MakeByRefType() }, null); - s_rsaInitializedImportPkcs8PrivateKeyMethod = true; - } - - if (s_rsaCopyWithPrivateKeyMethod is null) - { - s_rsaCopyWithPrivateKeyMethod = typeof(RSACertificateExtensions).GetMethod("CopyWithPrivateKey", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(X509Certificate2), typeof(RSA) }, null) - ?? throw new PlatformNotSupportedException("The current platform does not support reading a private key from a PEM file"); - } - - RSA privateKey = null; - try - { - if (s_rsaImportPkcs8PrivateKeyMethod != null) - { - privateKey = RSA.Create(); - - // Because ImportPkcs8PrivateKey declares an out parameter we cannot call it directly using MethodInfo.Invoke since all arguments are passed as an object array. - // Instead we create a delegate with the correct signature and invoke it. - ImportPrivateKeyDelegate importPkcs8PrivateKey = (ImportPrivateKeyDelegate)s_rsaImportPkcs8PrivateKeyMethod.CreateDelegate(typeof(ImportPrivateKeyDelegate), privateKey); - importPkcs8PrivateKey.Invoke(key, out int bytesRead); - - if (key.Length != bytesRead) - { - throw new InvalidDataException("Invalid PKCS#8 Data"); - } - } - else - { - privateKey = LightweightPkcs8Decoder.DecodeRSAPkcs8(key); - } - - using (X509Certificate2 certificateWithoutPrivateKey = new X509Certificate2(cer, (string)null, keyStorageFlags)) - { - X509Certificate2 certificate = (X509Certificate2)s_rsaCopyWithPrivateKeyMethod.Invoke(null, new object[] { certificateWithoutPrivateKey, privateKey }); - // On .NET Framework the PrivateKey member is not initialized after calling CopyWithPrivateKey. - - // This class only compiles against NET6.0 in tests and never in SDK libraries suppress the warning -#pragma warning disable SYSLIB0028 // Use CopyWithPrivateKey instead - if (certificate.PrivateKey is null) - { - certificate.PrivateKey = privateKey; - } -#pragma warning restore SYSLIB0028 - - // Make sure the private key doesn't get disposed now that it's used. - privateKey = null; - - return certificate; - } - } - finally - { - // If we created and did not use the RSA private key, make sure it's disposed. - privateKey?.Dispose(); - } - } - - /// <summary> - /// Attempts to read the next PEM field from the given data. - /// </summary> - /// <param name="data">The PEM data to parse.</param> - /// <param name="field">The PEM first complete PEM field that was found.</param> - /// <returns>True if a valid PEM field was parsed; otherwise, false.</returns> - /// <remarks> - /// To find subsequent fields, pass a slice of <paramref name="data"/> past the found <see cref="PemField.Length"/>. - /// </remarks> - public static bool TryRead(ReadOnlySpan<char> data, out PemField field) - { - field = default; - - int start = data.IndexOf(Prolog.AsSpan()); - if (start < 0) - { - return false; - } - - ReadOnlySpan<char> label = data.Slice(start + Prolog.Length); - int end = label.IndexOf(LabelEnd.AsSpan()); - if (end < 0) - { - return false; - } - - // Slice the label. - label = label.Slice(0, end); - - // Slice the remaining data after the label. - int dataOffset = start + Prolog.Length + end + LabelEnd.Length; - data = data.Slice(dataOffset); - - // Find the label end. - string labelEpilog = Epilog + label.ToString() + LabelEnd; - end = data.IndexOf(labelEpilog.AsSpan()); - if (end < 0) - { - return false; - } - - int fieldLength = dataOffset + end + labelEpilog.Length - start; - field = new PemField(start, label, data.Slice(0, end), fieldLength); - - return true; - } - - /// <summary> - /// Key type of the certificate private key. - /// </summary> - public enum KeyType - { - /// <summary> - /// The key type is unknown. - /// </summary> - Unknown = -1, - - /// <summary> - /// Attempt to detect the key type. - /// </summary> - Auto, - - /// <summary> - /// RSA key type. - /// </summary> - RSA, - - /// <summary> - /// ECDsa key type. - /// </summary> - ECDsa, - } - - /// <summary> - /// A PEM field including its section header and encoded data. - /// </summary> - public ref struct PemField - { - internal PemField(int start, ReadOnlySpan<char> label, ReadOnlySpan<char> data, int length) - { - Start = start; - Label = label; - Data = data; - Length = length; - } - - /// <summary> - /// The offset of the section from the start of the input PEM stream. - /// </summary> - public int Start { get; } - - /// <summary> - /// A span of the section label from within the PEM stream. - /// </summary> - public ReadOnlySpan<char> Label { get; } - - /// <summary> - /// A span of the section data from within the PEM stream. - /// </summary> - public ReadOnlySpan<char> Data { get; } - - /// <summary> - /// The length of the section from the <see cref="Start"/>. - /// </summary> - public int Length { get; } - - /// <summary> - /// Decodes the base64-encoded <see cref="Data"/> - /// </summary> - /// <returns></returns> - public byte[] FromBase64Data() => Convert.FromBase64String(Data.ToString()); - } - } -} diff --git a/src/Accounts/Authentication/Identity/CredentialDiagnosticScope.cs b/src/Accounts/Authentication/Identity/CredentialDiagnosticScope.cs deleted file mode 100644 index df2496baa17f..000000000000 --- a/src/Accounts/Authentication/Identity/CredentialDiagnosticScope.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure.Core; -using Azure.Identity; - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; - -using System; -using System.Linq; -using System.Runtime.ExceptionServices; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal readonly struct CredentialDiagnosticScope : IDisposable - { - private readonly string _name; - private readonly DiagnosticScope _scope; - private readonly TokenRequestContext _context; - private readonly IScopeHandler _scopeHandler; - - public CredentialDiagnosticScope(ClientDiagnostics diagnostics, string name, TokenRequestContext context, IScopeHandler scopeHandler) - { - _name = name; - _scope = scopeHandler.CreateScope(diagnostics, name); - _context = context; - _scopeHandler = scopeHandler; - } - - public void Start() - { - AzureIdentityEventSource.Singleton.GetToken(_name, _context); - _scopeHandler.Start(_name, _scope); - } - - public AccessToken Succeeded(AccessToken token) - { - AzureIdentityEventSource.Singleton.GetTokenSucceeded(_name, _context, token.ExpiresOn); - return token; - } - - public Exception FailWrapAndThrow(Exception ex, string additionalMessage = null, bool isCredentialUnavailable = false) - { - var wrapped = TryWrapException(ref ex, additionalMessage); - RegisterFailed(ex); - - if (!wrapped) - { - ExceptionDispatchInfo.Capture(ex).Throw(); - } - - throw ex; - } - - private void RegisterFailed(Exception ex) - { - AzureIdentityEventSource.Singleton.GetTokenFailed(_name, _context, ex); - _scopeHandler.Fail(_name, _scope, ex); - } - - private bool TryWrapException(ref Exception exception, string additionalMessageText = null, bool isCredentialUnavailable = false) - { - if (exception is OperationCanceledException || exception is AuthenticationFailedException) - { - return false; - } - - if (exception is AggregateException aex) - { - CredentialUnavailableException firstCredentialUnavailable = aex.Flatten().InnerExceptions.OfType<CredentialUnavailableException>().FirstOrDefault(); - if (firstCredentialUnavailable != default) - { - exception = new CredentialUnavailableException(firstCredentialUnavailable.Message, aex); - return true; - } - } - string exceptionMessage = $"{_name.Substring(0, _name.IndexOf('.'))} authentication failed: {exception.Message}"; - if (additionalMessageText != null) - { - exceptionMessage = exceptionMessage + $"\n{additionalMessageText}"; - } - exception = isCredentialUnavailable ? - new CredentialUnavailableException(exceptionMessage, exception) : - new AuthenticationFailedException(exceptionMessage, exception); - return true; - } - - public void Dispose() => _scopeHandler.Dispose(_name, _scope); - } -} diff --git a/src/Accounts/Authentication/Identity/CredentialPipeline.cs b/src/Accounts/Authentication/Identity/CredentialPipeline.cs deleted file mode 100644 index 22124a7f9c36..000000000000 --- a/src/Accounts/Authentication/Identity/CredentialPipeline.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Core; -using Azure.Core.Pipeline; -using Azure.Identity; - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; -using Microsoft.Identity.Client; - -using System; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class CredentialPipeline - { - private static readonly Lazy<CredentialPipeline> s_singleton = new Lazy<CredentialPipeline>(() => new CredentialPipeline(new TokenCredentialOptions())); - - private static readonly IScopeHandler _defaultScopeHandler = new ScopeHandler(); - - private CredentialPipeline(TokenCredentialOptions options) - { - HttpPipeline = HttpPipelineBuilder.Build(new HttpPipelineOptions(options) { RequestFailedDetailsParser = new ManagedIdentityRequestFailedDetailsParser() }); - Diagnostics = new ClientDiagnostics(options); - } - - public CredentialPipeline(HttpPipeline httpPipeline, ClientDiagnostics diagnostics) - { - HttpPipeline = httpPipeline; - Diagnostics = diagnostics; - } - - public static CredentialPipeline GetInstance(TokenCredentialOptions options) - { - return options is null ? s_singleton.Value : new CredentialPipeline(options); - } - - public HttpPipeline HttpPipeline { get; } - - public ClientDiagnostics Diagnostics { get; } - - public IConfidentialClientApplication CreateMsalConfidentialClient(string tenantId, string clientId, string clientSecret) - { - return ConfidentialClientApplicationBuilder.Create(clientId).WithHttpClientFactory(new HttpPipelineClientFactory(HttpPipeline)).WithTenantId(tenantId).WithClientSecret(clientSecret).Build(); - } - - public CredentialDiagnosticScope StartGetTokenScope(string fullyQualifiedMethod, TokenRequestContext context) - { - IScopeHandler scopeHandler = ScopeGroupHandler.Current ?? _defaultScopeHandler; - - CredentialDiagnosticScope scope = new CredentialDiagnosticScope(Diagnostics, fullyQualifiedMethod, context, scopeHandler); - scope.Start(); - return scope; - } - - public CredentialDiagnosticScope StartGetTokenScopeGroup(string fullyQualifiedMethod, TokenRequestContext context) - { - var scopeHandler = new ScopeGroupHandler(fullyQualifiedMethod); - - CredentialDiagnosticScope scope = new CredentialDiagnosticScope(Diagnostics, fullyQualifiedMethod, context, scopeHandler); - scope.Start(); - return scope; - } - - private class CredentialResponseClassifier : ResponseClassifier - { - public override bool IsRetriableResponse(HttpMessage message) - { - return base.IsRetriableResponse(message) || message.Response.Status == 404; - } - } - - private class ScopeHandler : IScopeHandler - { - public DiagnosticScope CreateScope(ClientDiagnostics diagnostics, string name) => diagnostics.CreateScope(name); - public void Start(string name, in DiagnosticScope scope) => scope.Start(); - public void Dispose(string name, in DiagnosticScope scope) => scope.Dispose(); - public void Fail(string name, in DiagnosticScope scope, Exception exception) => scope.Failed(exception); - } - } -} diff --git a/src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredential.cs b/src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredential.cs deleted file mode 100644 index efa902d209e1..000000000000 --- a/src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredential.cs +++ /dev/null @@ -1,134 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core; -using Azure.Core.Pipeline; -using Microsoft.Identity.Client; -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Enables authentication of an AAD service principal using a signed client assertion. - /// </summary> - public class ClientAssertionCredential : TokenCredential - { - internal readonly string[] AdditionallyAllowedTenantIds; - - internal string TenantId { get; } - internal string ClientId { get; } - internal MsalConfidentialClient Client { get; } - internal CredentialPipeline Pipeline { get; } - internal bool AllowMultiTenantAuthentication { get; } - - /// <summary> - /// Protected constructor for mocking. - /// </summary> - protected ClientAssertionCredential() - { } - - /// <summary> - /// Creates an instance of the ClientCertificateCredential with an asynchronous callback that provides a signed client assertion to authenticate against Azure Active Directory. - /// </summary> - /// <param name="tenantId">The Azure Active Directory tenant (directory) Id of the service principal.</param> - /// <param name="clientId">The client (application) ID of the service principal</param> - /// <param name="assertionCallback">An asynchronous callback returning a valid client assertion used to authenticate the service principal.</param> - /// <param name="options">Options that allow to configure the management of the requests sent to the Azure Active Directory service.</param> - public ClientAssertionCredential(string tenantId, string clientId, Func<CancellationToken, Task<string>> assertionCallback, ClientAssertionCredentialOptions options = default) - { - Argument.AssertNotNull(clientId, nameof(clientId)); - - TenantId = Validations.ValidateTenantId(tenantId, nameof(tenantId)); - ClientId = clientId; - - Client = options?.MsalClient ?? new MsalConfidentialClient(options?.Pipeline ?? CredentialPipeline.GetInstance(options), tenantId, clientId, assertionCallback, options); - Pipeline = options?.Pipeline ?? Client.Pipeline; - AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds((options as ISupportsAdditionallyAllowedTenants)?.AdditionallyAllowedTenants); - } - - /// <summary> - /// Creates an instance of the ClientCertificateCredential with a synchronous callback that provides a signed client assertion to authenticate against Azure Active Directory. - /// </summary> - /// <param name="tenantId">The Azure Active Directory tenant (directory) Id of the service principal.</param> - /// <param name="clientId">The client (application) ID of the service principal</param> - /// <param name="assertionCallback">A synchronous callback returning a valid client assertion used to authenticate the service principal.</param> - /// <param name="options">Options that allow to configure the management of the requests sent to the Azure Active Directory service.</param> - public ClientAssertionCredential(string tenantId, string clientId, Func<string> assertionCallback, ClientAssertionCredentialOptions options = default) - { - Argument.AssertNotNull(clientId, nameof(clientId)); - - TenantId = Validations.ValidateTenantId(tenantId, nameof(tenantId)); - ClientId = clientId; - - Client = options?.MsalClient ?? new MsalConfidentialClient(options?.Pipeline ?? CredentialPipeline.GetInstance(options), tenantId, clientId, assertionCallback, options); - Pipeline = options?.Pipeline ?? Client.Pipeline; - AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds((options as ISupportsAdditionallyAllowedTenants)?.AdditionallyAllowedTenants); - } - - /// <summary> - /// Obtains a token from the Azure Active Directory service, by calling the assertionCallback specified when constructing the credential to obtain a client assertion for authentication. - /// </summary> - /// <param name="requestContext">The details of the authentication request.</param> - /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param> - /// <returns>An <see cref="AccessToken"/> which can be used to authenticate service client calls.</returns> - public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken = default) - { - using (CredentialDiagnosticScope scope = Pipeline.StartGetTokenScope("ClientAssertionCredential.GetToken", requestContext)) - { - try - { - var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, AdditionallyAllowedTenantIds); - - AuthenticationResult result = Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, requestContext.IsCaeEnabled, false, cancellationToken).EnsureCompleted(); - - return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); - } - catch (Exception e) - { - throw scope.FailWrapAndThrow(e); - } - } - } - - /// <summary> - /// Obtains a token from the Azure Active Directory service, by calling the assertionCallback specified when constructing the credential to obtain a client assertion for authentication. - /// </summary> - /// <param name="requestContext">The details of the authentication request.</param> - /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param> - /// <returns>An <see cref="AccessToken"/> which can be used to authenticate service client calls.</returns> - public async override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken = default) - { - using (CredentialDiagnosticScope scope = Pipeline.StartGetTokenScope("ClientAssertionCredential.GetToken", requestContext)) - { - try - { - var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, AdditionallyAllowedTenantIds); - - AuthenticationResult result = await Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, requestContext.IsCaeEnabled, true, cancellationToken).ConfigureAwait(false); - - return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); - } - catch (Exception e) - { - throw scope.FailWrapAndThrow(e); - } - } - } - } -} diff --git a/src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredentialOptions.cs b/src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredentialOptions.cs deleted file mode 100644 index a6cc9b427505..000000000000 --- a/src/Accounts/Authentication/Identity/Credentials/ClientAssertionCredentialOptions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System.Collections.Generic; - -using Azure.Identity; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Options used to configure the <see cref="ClientAssertionCredential"/>. - /// </summary> - public class ClientAssertionCredentialOptions : TokenCredentialOptions, ISupportsDisableInstanceDiscovery, ISupportsAdditionallyAllowedTenants, ISupportsTokenCachePersistenceOptions - { - internal CredentialPipeline Pipeline { get; set; } - - internal MsalConfidentialClient MsalClient { get; set; } - - /// <summary> - /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed. - /// </summary> - public IList<string> AdditionallyAllowedTenants { get; internal set; } = new List<string>(); - - /// <inheritdoc/> - public bool DisableInstanceDiscovery { get; set; } - public TokenCachePersistenceOptions TokenCachePersistenceOptions { get; set; } - } -} diff --git a/src/Accounts/Authentication/Identity/Credentials/ISupportsDisableInstanceDiscovery.cs b/src/Accounts/Authentication/Identity/Credentials/ISupportsDisableInstanceDiscovery.cs deleted file mode 100644 index 7b85c02b4cc3..000000000000 --- a/src/Accounts/Authentication/Identity/Credentials/ISupportsDisableInstanceDiscovery.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal interface ISupportsDisableInstanceDiscovery - { - /// <summary> - /// Gets or sets the setting which determines whether or not instance discovery is performed when attempting to authenticate. - /// Setting this to true will completely disable both instance discovery and authority validation. - /// This functionality is intended for use in scenarios where the metadata endpoint cannot be reached, such as in private clouds or Azure Stack. - /// The process of instance discovery entails retrieving authority metadata from https://login.microsoft.com/ to validate the authority. - /// By setting this to <c>true</c>, the validation of the authority is disabled. - /// As a result, it is crucial to ensure that the configured authority host is valid and trustworthy." - /// </summary> - bool DisableInstanceDiscovery { get; set; } - } -} diff --git a/src/Accounts/Authentication/Identity/Credentials/ISupportsTokenCachePersistenceOptions.cs b/src/Accounts/Authentication/Identity/Credentials/ISupportsTokenCachePersistenceOptions.cs deleted file mode 100644 index 77ed7af3bd1a..000000000000 --- a/src/Accounts/Authentication/Identity/Credentials/ISupportsTokenCachePersistenceOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Collections.Generic; -using System.Text; - -using Azure.Identity; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal interface ISupportsTokenCachePersistenceOptions - { - TokenCachePersistenceOptions TokenCachePersistenceOptions { get; set; } - } -} diff --git a/src/Accounts/Authentication/Identity/EnvironmentVariables.cs b/src/Accounts/Authentication/Identity/EnvironmentVariables.cs deleted file mode 100644 index 3b6a14b3cfd9..000000000000 --- a/src/Accounts/Authentication/Identity/EnvironmentVariables.cs +++ /dev/null @@ -1,61 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class EnvironmentVariables - { - public static string Username => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_USERNAME")); - public static string Password => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_PASSWORD")); - public static string TenantId => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_TENANT_ID")); - public static List<string> AdditionallyAllowedTenants => (Environment.GetEnvironmentVariable("AZURE_ADDITIONALLY_ALLOWED_TENANTS") ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - public static string ClientId => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")); - public static string ClientSecret => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET")); - public static string ClientCertificatePath => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_CLIENT_CERTIFICATE_PATH")); - public static string ClientCertificatePassword => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_CLIENT_CERTIFICATE_PASSWORD")); - public static bool ClientSendCertificateChain => EnvironmentVariableToBool(Environment.GetEnvironmentVariable("AZURE_CLIENT_SEND_CERTIFICATE_CHAIN")); - - public static string IdentityEndpoint => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT")); - public static string IdentityHeader => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("IDENTITY_HEADER")); - public static string MsiEndpoint => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("MSI_ENDPOINT")); - public static string MsiSecret => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("MSI_SECRET")); - public static string ImdsEndpoint => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("IMDS_ENDPOINT")); - public static string IdentityServerThumbprint => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT")); - public static string PodIdentityEndpoint => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_POD_IDENTITY_AUTHORITY_HOST")); - - public static string Path => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("PATH")); - - public static string ProgramFilesX86 => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("ProgramFiles(x86)")); - public static string ProgramFiles => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("ProgramFiles")); - public static string AuthorityHost => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_AUTHORITY_HOST")); - - public static string AzureRegionalAuthorityName => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_REGIONAL_AUTHORITY_NAME")); - - public static string AzureFederatedTokenFile => GetNonEmptyStringOrNull(Environment.GetEnvironmentVariable("AZURE_FEDERATED_TOKEN_FILE")); - - private static string GetNonEmptyStringOrNull(string str) - { - return !string.IsNullOrEmpty(str) ? str : null; - } - - private static bool EnvironmentVariableToBool(string str) - { - return (string.Equals(bool.TrueString, str, StringComparison.OrdinalIgnoreCase) || string.Equals("1", str, StringComparison.OrdinalIgnoreCase)); - } - } -} diff --git a/src/Accounts/Authentication/Identity/HttpPipelineClientFactory.cs b/src/Accounts/Authentication/Identity/HttpPipelineClientFactory.cs deleted file mode 100644 index 09a0272d4e82..000000000000 --- a/src/Accounts/Authentication/Identity/HttpPipelineClientFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Core.Pipeline; - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; -using Microsoft.Identity.Client; - -using System.Net.Http; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// This class is an HttpClient factory which creates an HttpClient which delegates it's transport to an HttpPipeline, to enable MSAL to send requests through an Azure.Core HttpPipeline. - /// </summary> - internal class HttpPipelineClientFactory : IMsalHttpClientFactory - { - private readonly HttpPipeline _pipeline; - - public HttpPipelineClientFactory(HttpPipeline pipeline) - { - _pipeline = pipeline; - } - - public HttpClient GetHttpClient() - { - return new HttpClient(new HttpPipelineMessageHandler(_pipeline)); - } - } -} diff --git a/src/Accounts/Authentication/Identity/IScopeHandler.cs b/src/Accounts/Authentication/Identity/IScopeHandler.cs deleted file mode 100644 index bac9f37e1469..000000000000 --- a/src/Accounts/Authentication/Identity/IScopeHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; - -using System; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal interface IScopeHandler - { - DiagnosticScope CreateScope(ClientDiagnostics diagnostics, string name); - void Start(string name, in DiagnosticScope scope); - void Dispose(string name, in DiagnosticScope scope); - void Fail(string name, in DiagnosticScope scope, Exception exception); - } -} diff --git a/src/Accounts/Authentication/Identity/ISupportsAdditionallyAllowedTenants.cs b/src/Accounts/Authentication/Identity/ISupportsAdditionallyAllowedTenants.cs deleted file mode 100644 index e3a70c239008..000000000000 --- a/src/Accounts/Authentication/Identity/ISupportsAdditionallyAllowedTenants.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System.Collections.Generic; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal interface ISupportsAdditionallyAllowedTenants - { - /// <summary> - /// Specifies tenants in addition to the configured tenant for which the credential may acquire tokens. - /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access. - /// If no specific tenant was configured this option will have no effect, and the credential will acquire tokens for any requested tenant. - /// </summary> - IList<string> AdditionallyAllowedTenants { get; } - } -} diff --git a/src/Accounts/Authentication/Identity/IX509Certificate2Provider.cs b/src/Accounts/Authentication/Identity/IX509Certificate2Provider.cs deleted file mode 100644 index e077f009af6a..000000000000 --- a/src/Accounts/Authentication/Identity/IX509Certificate2Provider.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// IX509Certificate2Provider provides a way to control how the X509Certificate2 object is fetched. - /// </summary> - internal interface IX509Certificate2Provider - { - ValueTask<X509Certificate2> GetCertificateAsync(bool async, CancellationToken cancellationToken); - } -} diff --git a/src/Accounts/Authentication/Identity/IdentityCompatSwitches.cs b/src/Accounts/Authentication/Identity/IdentityCompatSwitches.cs deleted file mode 100644 index 6cefcf627a6d..000000000000 --- a/src/Accounts/Authentication/Identity/IdentityCompatSwitches.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class IdentityCompatSwitches - { - internal const string DisableInteractiveThreadpoolExecutionSwitchName = "Azure.Identity.DisableInteractiveBrowserThreadpoolExecution"; - internal const string DisableInteractiveThreadpoolExecutionEnvVar = "AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION"; - internal const string DisableCP1ExecutionSwitchName = "Azure.Identity.DisableCP1"; - internal const string DisableCP1ExecutionEnvVar = "AZURE_IDENTITY_DISABLE_CP1"; - internal const string DisableMultiTenantAuthSwitchName = "Azure.Identity.DisableMultiTenantAuth"; - internal const string DisableMultiTenantAuthEnvVar = "AZURE_IDENTITY_DISABLE_MULTITENANTAUTH"; - - public static bool DisableInteractiveBrowserThreadpoolExecution - => AppContextSwitchHelper.GetConfigValue(DisableInteractiveThreadpoolExecutionSwitchName, DisableInteractiveThreadpoolExecutionEnvVar); - - public static bool DisableCP1 - => AppContextSwitchHelper.GetConfigValue(DisableCP1ExecutionSwitchName, DisableCP1ExecutionEnvVar); - - public static bool DisableTenantDiscovery - => AppContextSwitchHelper.GetConfigValue(DisableMultiTenantAuthSwitchName, DisableMultiTenantAuthEnvVar); - } -} diff --git a/src/Accounts/Authentication/Identity/ManagedIdentityRequestFailedDetailsParser.cs b/src/Accounts/Authentication/Identity/ManagedIdentityRequestFailedDetailsParser.cs deleted file mode 100644 index 0dcde8b76bc8..000000000000 --- a/src/Accounts/Authentication/Identity/ManagedIdentityRequestFailedDetailsParser.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure; -using Azure.Core; -using Azure.Core.Pipeline; -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class ManagedIdentityRequestFailedDetailsParser : RequestFailedDetailsParser - { - public override bool TryParse(Response response, out ResponseError error, out IDictionary<string, string> data) - { - data = new Dictionary<string, string>(); - error = null; - try - { - // The response content is buffered at this point. - string content = response.Content.ToString(); - - // Optimistic check for JSON object we expect - if (content == null || !content.StartsWith("{", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - var message = ManagedIdentitySource.GetMessageFromResponse(response, false, CancellationToken.None).EnsureCompleted(); - error = new ResponseError(null, message); - return true; - } - catch - { - error = new ResponseError(null, ManagedIdentitySource.UnexpectedResponse); - return true; - } - } - } -} diff --git a/src/Accounts/Authentication/Identity/ManagedIdentitySource.cs b/src/Accounts/Authentication/Identity/ManagedIdentitySource.cs deleted file mode 100644 index 647bf0849f4e..000000000000 --- a/src/Accounts/Authentication/Identity/ManagedIdentitySource.cs +++ /dev/null @@ -1,190 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure; -using Azure.Core; -using Azure.Identity; - -using System; -using System.Globalization; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal abstract class ManagedIdentitySource - { - internal const string AuthenticationResponseInvalidFormatError = "Invalid response, the authentication response was not in the expected format."; - internal const string UnexpectedResponse = "Managed Identity response was not in the expected format. See the inner exception for details."; - private ManagedIdentityResponseClassifier _responseClassifier; - - protected ManagedIdentitySource(CredentialPipeline pipeline) - { - Pipeline = pipeline; - _responseClassifier = new ManagedIdentityResponseClassifier(); - } - - protected internal CredentialPipeline Pipeline { get; } - protected internal string ClientId { get; } - - public virtual async ValueTask<AccessToken> AuthenticateAsync(bool async, TokenRequestContext context, CancellationToken cancellationToken) - { - using (HttpMessage message = CreateHttpMessage(CreateRequest(context.Scopes))) - { - if (async) - { - await Pipeline.HttpPipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); - } - else - { - Pipeline.HttpPipeline.Send(message, cancellationToken); - } - - return await HandleResponseAsync(async, context, message.Response, cancellationToken).ConfigureAwait(false); - } - } - - protected virtual async ValueTask<AccessToken> HandleResponseAsync( - bool async, - TokenRequestContext context, - Response response, - CancellationToken cancellationToken) - { - Exception exception = null; - try - { - if (response.Status == 200) - { - using (JsonDocument json = async - ? await JsonDocument.ParseAsync(response.ContentStream, default, cancellationToken).ConfigureAwait(false) - : JsonDocument.Parse(response.ContentStream)) - { - return GetTokenFromResponse(json.RootElement); - } - } - } - catch (JsonException jex) - { - throw new CredentialUnavailableException(UnexpectedResponse, jex); - } - catch (Exception e) - { - exception = e; - } - - throw new RequestFailedException(response, exception); - } - - protected abstract Request CreateRequest(string[] scopes); - - protected virtual HttpMessage CreateHttpMessage(Request request) - { - return new HttpMessage(request, _responseClassifier); - } - - internal static async Task<string> GetMessageFromResponse(Response response, bool async, CancellationToken cancellationToken) - { - if (response?.ContentStream == null || !response.ContentStream.CanRead || response.ContentStream.Length == 0) - { - return null; - } - try - { - response.ContentStream.Position = 0; - using (JsonDocument json = async - ? await JsonDocument.ParseAsync(response.ContentStream, default, cancellationToken).ConfigureAwait(false) - : JsonDocument.Parse(response.ContentStream)) - { - - return GetMessageFromResponse(json.RootElement); - } } - catch // parsing failed - { - return "Response was not in a valid json format."; - } - } - - protected static string GetMessageFromResponse(in JsonElement root) - { - // Parse the error, if possible - foreach (var prop in root.EnumerateObject()) - { - if (prop.Name == "Message") - { - return prop.Value.GetString(); - } - } - return null; - } - - private static AccessToken GetTokenFromResponse(in JsonElement root) - { - string accessToken = null; - DateTimeOffset? expiresOn = null; - - foreach (JsonProperty prop in root.EnumerateObject()) - { - switch (prop.Name) - { - case "access_token": - accessToken = prop.Value.GetString(); - break; - - case "expires_on": - expiresOn = TryParseExpiresOn(prop.Value); - break; - } - } - - return accessToken != null && expiresOn.HasValue - ? new AccessToken(accessToken, expiresOn.Value) - : throw new AuthenticationFailedException(AuthenticationResponseInvalidFormatError); - } - - private static DateTimeOffset? TryParseExpiresOn(JsonElement jsonExpiresOn) - { - // first test if expiresOn is a unix timestamp either as a number or string - if (jsonExpiresOn.ValueKind == JsonValueKind.Number && jsonExpiresOn.TryGetInt64(out long expiresOnSec) || - jsonExpiresOn.ValueKind == JsonValueKind.String && long.TryParse(jsonExpiresOn.GetString(), out expiresOnSec)) - { - return DateTimeOffset.FromUnixTimeSeconds(expiresOnSec); - } - // otherwise if it is a json string try to parse as a datetime offset - else if (jsonExpiresOn.ValueKind == JsonValueKind.String && DateTimeOffset.TryParse(jsonExpiresOn.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset expiresOn)) - { - return expiresOn; - } - - return null; - } - - private class ManagedIdentityResponseClassifier : ResponseClassifier - { - public override bool IsRetriableResponse(HttpMessage message) - { - switch (message.Response.Status) - { - case 404: - return true; - case 502: - return false; - default: - return base.IsRetriableResponse(message); - } - } - } - } -} diff --git a/src/Accounts/Authentication/Identity/MsalCacheHelperWrapper.cs b/src/Accounts/Authentication/Identity/MsalCacheHelperWrapper.cs deleted file mode 100644 index d5e0ffb9df6f..000000000000 --- a/src/Accounts/Authentication/Identity/MsalCacheHelperWrapper.cs +++ /dev/null @@ -1,100 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensions.Msal; - -using System.Diagnostics; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class MsalCacheHelperWrapper - { - private MsalCacheHelper _helper; - - /// <summary> - /// Default Constructor. - /// </summary> - public MsalCacheHelperWrapper() - { - } - - /// <summary> - /// Creates a new instance of Microsoft.Identity.Client.Extensions.Msal.MsalCacheHelper. - /// To configure MSAL to use this cache persistence, call Microsoft.Identity.Client.Extensions.Msal.MsalCacheHelper.RegisterCache(Microsoft.Identity.Client.ITokenCache) - /// </summary> - /// <param name="storageCreationProperties"></param> - /// <param name="logger">Passing null uses a default logger</param> - /// <returns>A new instance of Microsoft.Identity.Client.Extensions.Msal.MsalCacheHelper.</returns> - public virtual async Task InitializeAsync(StorageCreationProperties storageCreationProperties, TraceSource logger = null) - { - _helper = await MsalCacheHelper.CreateAsync(storageCreationProperties, logger).ConfigureAwait(false); - } - - /// <summary> - /// Performs a write -> read -> clear using the underlying persistence mechanism - /// and throws an Microsoft.Identity.Client.Extensions.Msal.MsalCachePersistenceException - /// if something goes wrong. - /// </summary> - /// <remarks> - /// Does not overwrite the token cache. Should never fail on Windows and Mac where - /// the cache accessors are guaranteed to exist by the OS. - /// </remarks> - public virtual void VerifyPersistence() - { - _helper.VerifyPersistence(); - } - - /// <summary> - /// Registers a token cache to synchronize with on disk storage. - /// </summary> - /// <param name="tokenCache"></param> - public virtual void RegisterCache(ITokenCache tokenCache) - { - _helper.RegisterCache(tokenCache); - } - - /// <summary> - /// Unregisters a token cache so it no longer synchronizes with on disk storage. - /// </summary> - /// <param name="tokenCache"></param> - public virtual void UnregisterCache(ITokenCache tokenCache) - { - _helper.UnregisterCache(tokenCache); - } - - /// <summary> - /// Extracts the token cache data from the persistent store - /// </summary> - /// <remarks> - /// This method should be used with care. The data returned is unencrypted. - /// </remarks> - /// <returns>UTF-8 byte array of the unencrypted token cache</returns> - public virtual byte[] LoadUnencryptedTokenCache() - { - return _helper.LoadUnencryptedTokenCache(); - } - - /// <summary> - /// Saves an unencrypted, UTF-8 encoded byte array representing an MSAL token cache. - /// The save operation will persist the data in a secure location, as configured - /// in Microsoft.Identity.Client.Extensions.Msal.StorageCreationProperties - /// </summary> - public virtual void SaveUnencryptedTokenCache(byte[] tokenCache) - { - _helper.SaveUnencryptedTokenCache(tokenCache); - } - } -} diff --git a/src/Accounts/Authentication/Identity/MsalClientBase.cs b/src/Accounts/Authentication/Identity/MsalClientBase.cs deleted file mode 100644 index cdf86e4d3613..000000000000 --- a/src/Accounts/Authentication/Identity/MsalClientBase.cs +++ /dev/null @@ -1,128 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Identity; - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; -using Microsoft.Identity.Client; - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal abstract class MsalClientBase<TClient> - where TClient : IClientApplicationBase - { - private readonly AsyncLockWithValue<(TClient Client, TokenCache Cache)> _clientAsyncLock; - private readonly AsyncLockWithValue<(TClient Client, TokenCache Cache)> _clientWithCaeAsyncLock; - private readonly bool _logAccountDetails; - private readonly TokenCachePersistenceOptions _tokenCachePersistenceOptions; - protected internal bool IsSupportLoggingEnabled { get; } - protected internal bool DisableInstanceDiscovery { get; } - protected string[] cp1Capabilities = new[] { "CP1" }; - protected internal CredentialPipeline Pipeline { get; } - internal string TenantId { get; } - internal string ClientId { get; } - internal Uri AuthorityHost { get; } - - /// <summary> - /// For mocking purposes only. - /// </summary> - protected MsalClientBase() - { - } - - protected MsalClientBase(CredentialPipeline pipeline, string tenantId, string clientId, TokenCredentialOptions options) - { - // This validation is performed as a backstop. Validation in TokenCredentialOptions.AuthorityHost prevents users from explicitly - // setting AuthorityHost to a non TLS endpoint. However, the AuthorityHost can also be set by the AZURE_AUTHORITY_HOST environment - // variable rather than in code. In this case we need to validate the endpoint before we use it. However, we can't validate in - // CredentialPipeline as this is also used by the ManagedIdentityCredential which allows non TLS endpoints. For this reason - // we validate here as all other credentials will create an MSAL client. - Validations.ValidateAuthorityHost(options?.AuthorityHost); - AuthorityHost = options?.AuthorityHost ?? AzureAuthorityHosts.GetDefault(); - _logAccountDetails = options?.Diagnostics?.IsAccountIdentifierLoggingEnabled ?? false; - DisableInstanceDiscovery = options is ISupportsDisableInstanceDiscovery supportsDisableInstanceDiscovery && supportsDisableInstanceDiscovery.DisableInstanceDiscovery; - ISupportsTokenCachePersistenceOptions cacheOptions = options as ISupportsTokenCachePersistenceOptions; - _tokenCachePersistenceOptions = cacheOptions?.TokenCachePersistenceOptions; - IsSupportLoggingEnabled = options?.IsUnsafeSupportLoggingEnabled ?? false; - Pipeline = pipeline; - TenantId = tenantId; - ClientId = clientId; - _clientAsyncLock = new AsyncLockWithValue<(TClient Client, TokenCache Cache)>(); - _clientWithCaeAsyncLock = new AsyncLockWithValue<(TClient Client, TokenCache Cache)>(); - } - - protected abstract ValueTask<TClient> CreateClientAsync(bool enableCae, bool async, CancellationToken cancellationToken); - - protected async ValueTask<TClient> GetClientAsync(bool enableCae, bool async, CancellationToken cancellationToken) - { - using (var asyncLock = enableCae ? - await _clientWithCaeAsyncLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait(false) : - await _clientAsyncLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait(false)) - { - if (asyncLock.HasValue) - { - return asyncLock.Value.Client; - } - - var client = await CreateClientAsync(enableCae, async, cancellationToken).ConfigureAwait(false); - - TokenCache tokenCache = null; - if (_tokenCachePersistenceOptions != null) - { - tokenCache = new TokenCache(_tokenCachePersistenceOptions, enableCae); - await tokenCache.RegisterCache(async, client.UserTokenCache, cancellationToken).ConfigureAwait(false); - - if (client is IConfidentialClientApplication cca) - { - await tokenCache.RegisterCache(async, cca.AppTokenCache, cancellationToken).ConfigureAwait(false); - } - } - - asyncLock.SetValue((Client: client, Cache: tokenCache)); - return client; - } - } - - protected void LogMsal(LogLevel level, string message, bool isPii) - { - if (!isPii || IsSupportLoggingEnabled) - { - AzureIdentityEventSource.Singleton.LogMsal(level, message); - } - } - - protected void LogAccountDetails(AuthenticationResult result) - { - if (_logAccountDetails) - { - var accountDetails = TokenHelper.ParseAccountInfoFromToken(result.AccessToken); - AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(accountDetails.ClientId, accountDetails.TenantId ?? result.TenantId, accountDetails.Upn ?? result.Account?.Username, accountDetails.ObjectId ?? result.UniqueId); - } - } - - internal async ValueTask<TokenCache> GetTokenCache(bool enableCae) - { - using (var asyncLock = enableCae ? - await _clientWithCaeAsyncLock.GetLockOrValueAsync(true, default).ConfigureAwait(false) : - await _clientAsyncLock.GetLockOrValueAsync(true, default).ConfigureAwait(false)) - { - return asyncLock.HasValue ? asyncLock.Value.Cache : null; - } - } - } -} diff --git a/src/Accounts/Authentication/Identity/MsalConfidentialClient.cs b/src/Accounts/Authentication/Identity/MsalConfidentialClient.cs deleted file mode 100644 index 5aa0d8c206bb..000000000000 --- a/src/Accounts/Authentication/Identity/MsalConfidentialClient.cs +++ /dev/null @@ -1,306 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Identity; - -using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensibility; - -using System; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal class MsalConfidentialClient : MsalClientBase<IConfidentialClientApplication> - { - internal readonly string _clientSecret; - internal readonly bool _includeX5CClaimHeader; - internal readonly IX509Certificate2Provider _certificateProvider; - private readonly Func<string> _assertionCallback; - private readonly Func<CancellationToken, Task<string>> _asyncAssertionCallback; - private readonly Func<AppTokenProviderParameters, Task<AppTokenProviderResult>> _appTokenProviderCallback; - - internal string RedirectUrl { get; } - - /// <summary> - /// For mocking purposes only. - /// </summary> - protected MsalConfidentialClient() - { } - - public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, string clientSecret, string redirectUrl, TokenCredentialOptions options) - : base(pipeline, tenantId, clientId, options) - { - _clientSecret = clientSecret; - RedirectUrl = redirectUrl; - } - - public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, IX509Certificate2Provider certificateProvider, bool includeX5CClaimHeader, TokenCredentialOptions options) - : base(pipeline, tenantId, clientId, options) - { - _includeX5CClaimHeader = includeX5CClaimHeader; - _certificateProvider = certificateProvider; - } - - public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, Func<string> assertionCallback, TokenCredentialOptions options) - : base(pipeline, tenantId, clientId, options) - { - _assertionCallback = assertionCallback; - } - - public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, Func<CancellationToken, Task<string>> assertionCallback, TokenCredentialOptions options) - : base(pipeline, tenantId, clientId, options) - { - _asyncAssertionCallback = assertionCallback; - } - - public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, Func<AppTokenProviderParameters, Task<AppTokenProviderResult>> appTokenProviderCallback, TokenCredentialOptions options) - : base(pipeline, tenantId, clientId, options) - { - _appTokenProviderCallback = appTokenProviderCallback; - } - - internal string RegionalAuthority { get; } = EnvironmentVariables.AzureRegionalAuthorityName; - - protected override ValueTask<IConfidentialClientApplication> CreateClientAsync(bool enableCae, bool async, CancellationToken cancellationToken) - { - return CreateClientCoreAsync(enableCae, async, cancellationToken); - } - - protected virtual async ValueTask<IConfidentialClientApplication> CreateClientCoreAsync(bool enableCae, bool async, CancellationToken cancellationToken) - { - string[] clientCapabilities = - enableCae ? cp1Capabilities : Array.Empty<string>(); - - ConfidentialClientApplicationBuilder confClientBuilder = ConfidentialClientApplicationBuilder.Create(ClientId) - .WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline)) - .WithLogging(LogMsal, enablePiiLogging: IsSupportLoggingEnabled); - - // Special case for using appTokenProviderCallback, authority validation and instance metadata discovery should be disabled since we're not calling the STS - // The authority matches the one configured in the CredentialOptions. - if (_appTokenProviderCallback != null) - { - confClientBuilder.WithAppTokenProvider(_appTokenProviderCallback) - .WithAuthority(AuthorityHost.AbsoluteUri, TenantId, false) - .WithInstanceDiscovery(false); - } - else - { - confClientBuilder.WithAuthority(AuthorityHost.AbsoluteUri, TenantId); - if (DisableInstanceDiscovery) - { - confClientBuilder.WithInstanceDiscovery(false); - } - } - - if (clientCapabilities.Length > 0) - { - confClientBuilder.WithClientCapabilities(clientCapabilities); - } - - if (_clientSecret != null) - { - confClientBuilder.WithClientSecret(_clientSecret); - } - - if (_assertionCallback != null) - { - confClientBuilder.WithClientAssertion(_assertionCallback); - } - - if (_asyncAssertionCallback != null) - { - confClientBuilder.WithClientAssertion(_asyncAssertionCallback); - } - - if (_certificateProvider != null) - { - X509Certificate2 clientCertificate = await _certificateProvider.GetCertificateAsync(async, cancellationToken).ConfigureAwait(false); - confClientBuilder.WithCertificate(clientCertificate); - } - - // When the appTokenProviderCallback is set, meaning this is for managed identity, the regional authority is not relevant. - if (_appTokenProviderCallback == null && !string.IsNullOrEmpty(RegionalAuthority)) - { - confClientBuilder.WithAzureRegion(RegionalAuthority); - } - - if (!string.IsNullOrEmpty(RedirectUrl)) - { - confClientBuilder.WithRedirectUri(RedirectUrl); - } - - return confClientBuilder.Build(); - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenForClientAsync( - string[] scopes, - string tenantId, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - var result = await AcquireTokenForClientCoreAsync(scopes, tenantId, enableCae, async, cancellationToken).ConfigureAwait(false); - LogAccountDetails(result); - return result; - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenForClientCoreAsync( - string[] scopes, - string tenantId, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - IConfidentialClientApplication client = await GetClientAsync(enableCae, async, cancellationToken).ConfigureAwait(false); - - var builder = client - .AcquireTokenForClient(scopes) - .WithSendX5C(_includeX5CClaimHeader); - - if (!string.IsNullOrEmpty(tenantId)) - { - var uriBuilder = new UriBuilder(AuthorityHost.AbsoluteUri) - { - Path = tenantId - }; - builder.WithTenantIdFromAuthority(uriBuilder.Uri); - } - return await builder - .ExecuteAsync(async, cancellationToken) - .ConfigureAwait(false); - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenSilentAsync( - string[] scopes, - AuthenticationAccount account, - string tenantId, - string redirectUri, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - var result = await AcquireTokenSilentCoreAsync(scopes, account, tenantId, redirectUri, enableCae, async, cancellationToken).ConfigureAwait(false); - LogAccountDetails(result); - return result; - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenSilentCoreAsync( - string[] scopes, - AuthenticationAccount account, - string tenantId, - string redirectUri, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - IConfidentialClientApplication client = await GetClientAsync(enableCae, async, cancellationToken).ConfigureAwait(false); - - var builder = client.AcquireTokenSilent(scopes, account); - if (!string.IsNullOrEmpty(tenantId)) - { - var uriBuilder = new UriBuilder(AuthorityHost.AbsoluteUri) - { - Path = tenantId - }; - builder.WithTenantIdFromAuthority(uriBuilder.Uri); - } - return await builder - .ExecuteAsync(async, cancellationToken) - .ConfigureAwait(false); - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenByAuthorizationCodeAsync( - string[] scopes, - string code, - string tenantId, - string redirectUri, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - var result = await AcquireTokenByAuthorizationCodeCoreAsync(scopes, code, tenantId, redirectUri, enableCae, async, cancellationToken).ConfigureAwait(false); - LogAccountDetails(result); - return result; - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenByAuthorizationCodeCoreAsync( - string[] scopes, - string code, - string tenantId, - string redirectUri, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - IConfidentialClientApplication client = await GetClientAsync(enableCae, async, cancellationToken).ConfigureAwait(false); - - var builder = client.AcquireTokenByAuthorizationCode(scopes, code); - - if (!string.IsNullOrEmpty(tenantId)) - { - var uriBuilder = new UriBuilder(AuthorityHost.AbsoluteUri) - { - Path = tenantId - }; - builder.WithTenantIdFromAuthority(uriBuilder.Uri); - } - return await builder - .ExecuteAsync(async, cancellationToken) - .ConfigureAwait(false); - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenOnBehalfOfAsync( - string[] scopes, - string tenantId, - UserAssertion userAssertionValue, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - var result = await AcquireTokenOnBehalfOfCoreAsync(scopes, tenantId, userAssertionValue, enableCae, async, cancellationToken).ConfigureAwait(false); - LogAccountDetails(result); - return result; - } - - public virtual async ValueTask<AuthenticationResult> AcquireTokenOnBehalfOfCoreAsync( - string[] scopes, - string tenantId, - UserAssertion userAssertionValue, - bool enableCae, - bool async, - CancellationToken cancellationToken) - { - IConfidentialClientApplication client = await GetClientAsync(enableCae, async, cancellationToken).ConfigureAwait(false); - - var builder = client - .AcquireTokenOnBehalfOf(scopes, userAssertionValue) - .WithSendX5C(_includeX5CClaimHeader); - - if (!string.IsNullOrEmpty(tenantId)) - { - var uriBuilder = new UriBuilder(AuthorityHost.AbsoluteUri) - { - Path = tenantId - }; - builder.WithTenantIdFromAuthority(uriBuilder.Uri); - } - return await builder - .ExecuteAsync(async, cancellationToken) - .ConfigureAwait(false); - } - } -} diff --git a/src/Accounts/Authentication/Identity/README.md b/src/Accounts/Authentication/Identity/README.md deleted file mode 100644 index f28a5109ed43..000000000000 --- a/src/Accounts/Authentication/Identity/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Context -Due to the gaps between exposed API by Azure.Identity and urgent requirements from service, we have forked Azure.Identity code to this folder. - -# Changelog -[2024-1-16, Lei Jin] Replace the client assertion related codes by the forks from Azure.Identity 1.10.3 and Azure.Core 1.35.0 -* The codes with namespace `Microsoft.Azure.PowerShell.Authenticators.Identity` is from Azure.Identity and with namespace `Microsoft.Azure.PowerShell.Authenticators.Identity.Core` is from Azure.Core -* Modify codes whose syntax requires C# 8 support. -* Add interface `ISupportsTokenCachePersistenceOptions` to the new version `ClientAssertionCredentialOptions`. - -[2022-11-28, Yeming Liu] To support token cache of client assertion, I made `ClientAssertionCredentialOptions` implement `ITokenCacheOptions` interface. -* ClientAssertionCredentialOptions.cs - * Implement `ITokenCacheOptions` and added `TokenCachePersistenceOptions` property. - -[2022-11-23, dixue] To support token cache of client assertion, it forks client assertion related code from Azure.Identity 1.6.1 -* MsalClientBase.cs - * Modify using clause because its syntax requires C# 8 support. - * Remove all log related method because Azure.Core and Azure.Identity do not expose those API to external. diff --git a/src/Accounts/Authentication/Identity/ScopeGroupHandler.cs b/src/Accounts/Authentication/Identity/ScopeGroupHandler.cs deleted file mode 100644 index a6f3dfa1303c..000000000000 --- a/src/Accounts/Authentication/Identity/ScopeGroupHandler.cs +++ /dev/null @@ -1,146 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal sealed class ScopeGroupHandler : IScopeHandler - { - private static readonly AsyncLocal<IScopeHandler> _currentAsyncLocal = new AsyncLocal<IScopeHandler>(); - private readonly string _groupName; - private Dictionary<string, ChildScopeInfo> _childScopes; - - public static IScopeHandler Current => _currentAsyncLocal.Value; - - public ScopeGroupHandler(string groupName) - { - _groupName = groupName; - _currentAsyncLocal.Value = this; - } - - public DiagnosticScope CreateScope(ClientDiagnostics diagnostics, string name) - { - if (IsGroup(name)) - { - return diagnostics.CreateScope(name); - } - - _childScopes = new Dictionary<string, ChildScopeInfo>() ?? _childScopes; - _childScopes[name] = new ChildScopeInfo(diagnostics, name); - return default; - } - - public void Start(string name, in DiagnosticScope scope) - { - if (IsGroup(name)) - { - scope.Start(); - } - else - { - _childScopes[name].StartDateTime = DateTime.UtcNow; - } - } - - public void Dispose(string name, in DiagnosticScope scope) - { - if (!IsGroup(name)) - { - return; - } - - var succeededScope = _childScopes?.Values.LastOrDefault(i => i.Exception == default); - if (succeededScope != null) - { - SucceedChildScope(succeededScope); - } - - scope.Dispose(); - _currentAsyncLocal.Value = default; - } - - public void Fail(string name, in DiagnosticScope scope, Exception exception) - { - if (_childScopes == default) - { - scope.Failed(exception); - return; - } - - if (IsGroup(name)) - { - if (exception is OperationCanceledException) - { - var canceledScope = _childScopes.Values.Last(i => i.Exception == exception); - FailChildScope(canceledScope); - } - else - { - foreach (var childScope in _childScopes.Values) - { - FailChildScope(childScope); - } - } - - scope.Failed(exception); - } - else - { - _childScopes[name].Exception = exception; - } - } - - private static void SucceedChildScope(ChildScopeInfo scopeInfo) - { - using (DiagnosticScope scope = scopeInfo.Diagnostics.CreateScope(scopeInfo.Name)) - { - scope.SetStartTime(scopeInfo.StartDateTime); - scope.Start(); - } - } - - private static void FailChildScope(ChildScopeInfo scopeInfo) - { - using (DiagnosticScope scope = scopeInfo.Diagnostics.CreateScope(scopeInfo.Name)) - { - scope.SetStartTime(scopeInfo.StartDateTime); - scope.Start(); - scope.Failed(scopeInfo.Exception); - } - } - - private bool IsGroup(string name) => string.Equals(name, _groupName, StringComparison.Ordinal); - - private class ChildScopeInfo - { - public ClientDiagnostics Diagnostics { get; } - public string Name { get; } - public DateTime StartDateTime { get; set; } - public Exception Exception { get; set; } - - public ChildScopeInfo(ClientDiagnostics diagnostics, string name) - { - Diagnostics = diagnostics; - Name = name; - } - } - } -} diff --git a/src/Accounts/Authentication/Identity/TenantIdResolver.cs b/src/Accounts/Authentication/Identity/TenantIdResolver.cs deleted file mode 100644 index e04320875c40..000000000000 --- a/src/Accounts/Authentication/Identity/TenantIdResolver.cs +++ /dev/null @@ -1,84 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Core; -using Azure.Identity; - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal static class TenantIdResolver - { - public static readonly string[] AllTenants = new string[] { "*" }; - - /// <summary> - /// Resolves the tenantId based on the supplied configuration values. - /// </summary> - /// <param name="explicitTenantId">The tenantId passed to the ctor of the Credential.</param> - /// <param name="context">The <see cref="TokenRequestContext"/>.</param> - /// <param name="additionallyAllowedTenantIds">Additional tenants the credential is configured to acquire tokens for.</param> - /// <returns>The tenantId to be used for authorization.</returns> - public static string Resolve(string explicitTenantId, TokenRequestContext context, string[] additionallyAllowedTenantIds) - { - bool disableMultiTenantAuth = IdentityCompatSwitches.DisableTenantDiscovery; - - if (context.TenantId != explicitTenantId && context.TenantId != null && explicitTenantId != null) - { - if (disableMultiTenantAuth || explicitTenantId == Constants.AdfsTenantId) - { - AzureIdentityEventSource.Singleton.TenantIdDiscoveredAndNotUsed(explicitTenantId, context.TenantId); - } - else - { - AzureIdentityEventSource.Singleton.TenantIdDiscoveredAndUsed(explicitTenantId, context.TenantId); - } - } - - string resolvedTenantId = string.Empty; - if (disableMultiTenantAuth || explicitTenantId == Constants.AdfsTenantId) - { - resolvedTenantId = explicitTenantId; - } - else - { - resolvedTenantId = context.TenantId ?? explicitTenantId; - } - - if (explicitTenantId != null && resolvedTenantId != explicitTenantId && additionallyAllowedTenantIds != AllTenants && Array.BinarySearch(additionallyAllowedTenantIds, resolvedTenantId, StringComparer.OrdinalIgnoreCase) < 0) - { - throw new AuthenticationFailedException($"The current credential is not configured to acquire tokens for tenant {resolvedTenantId}. To enable acquiring tokens for this tenant add it to the AdditionallyAllowedTenants on the credential options, or add \"*\" to AdditionallyAllowedTenants to allow acquiring tokens for any tenant. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/multitenant/troubleshoot"); - } - - return resolvedTenantId; - } - - public static string[] ResolveAddionallyAllowedTenantIds(IList<string> additionallyAllowedTenants) - { - if (additionallyAllowedTenants == null || additionallyAllowedTenants.Count == 0) - { - return Array.Empty<string>(); - } - - if (additionallyAllowedTenants.Contains("*")) - { - return AllTenants; - } - - return additionallyAllowedTenants.OrderBy(s => s).ToArray(); - } - } -} diff --git a/src/Accounts/Authentication/Identity/TokenCache.cs b/src/Accounts/Authentication/Identity/TokenCache.cs deleted file mode 100644 index 66f0c736c06f..000000000000 --- a/src/Accounts/Authentication/Identity/TokenCache.cs +++ /dev/null @@ -1,304 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using Azure.Identity; - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; -using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensions.Msal; - -using System; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// A cache for Tokens. - /// </summary> -#pragma warning disable CA1001 // Types that own disposable fields should be disposable - // SemaphoreSlim only needs to be disposed when AvailableWaitHandle is called. - internal class TokenCache -#pragma warning restore CA1001 // Types that own disposable fields should be disposable - { - private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); - private DateTimeOffset _lastUpdated; - private ConditionalWeakTable<object, CacheTimestamp> _cacheAccessMap; - internal Func<IPublicClientApplication> _publicClientApplicationFactory; - private readonly bool _allowUnencryptedStorage; - private readonly string _name; - private readonly bool _persistToDisk; - private AsyncLockWithValue<MsalCacheHelperWrapper> cacheHelperLock = new AsyncLockWithValue<MsalCacheHelperWrapper>(); - private readonly MsalCacheHelperWrapper _cacheHelperWrapper; - - /// <summary> - /// The internal state of the cache. - /// </summary> - internal byte[] Data { get; private set; } - - /// <summary> - /// Determines whether the token cache will be associated with CAE enabled requests. - /// </summary> - /// <value>If true, this cache services only CAE enabled requests.Otherwise, this cache services non-CAE enabled requests.</value> - internal bool IsCaeEnabled { get;} - - private class CacheTimestamp - { - private DateTimeOffset _timestamp; - - public CacheTimestamp() - { - Update(); - } - - public DateTimeOffset Update() - { - _timestamp = DateTimeOffset.UtcNow; - return _timestamp; - } - - public DateTimeOffset Value { get { return _timestamp; } } - } - - /// <summary> - /// Creates a new instance of <see cref="TokenCache"/> with the specified options. - /// </summary> - /// <param name="options">Options controlling the storage of the <see cref="TokenCache"/>.</param> - /// <param name="enableCae">Controls whether this cache will be associated with CAE requests or non-CAE requests.</param> - public TokenCache(TokenCachePersistenceOptions options = null, bool enableCae = false) - : this(options, default, default, enableCae) - { } - - internal TokenCache(TokenCachePersistenceOptions options, MsalCacheHelperWrapper cacheHelperWrapper, Func<IPublicClientApplication> publicApplicationFactory = null, bool enableCae = false) - { - _cacheHelperWrapper = cacheHelperWrapper ?? new MsalCacheHelperWrapper(); - _publicClientApplicationFactory = publicApplicationFactory ?? new Func<IPublicClientApplication>(() => PublicClientApplicationBuilder.Create(Guid.NewGuid().ToString()).Build()); - IsCaeEnabled = enableCae; - if (options is UnsafeTokenCacheOptions inMemoryOptions) - { - TokenCacheUpdatedAsync = inMemoryOptions.TokenCacheUpdatedAsync; - RefreshCacheFromOptionsAsync = inMemoryOptions.RefreshCacheAsync; - _lastUpdated = DateTimeOffset.UtcNow; - _cacheAccessMap = new ConditionalWeakTable<object, CacheTimestamp>(); - } - else - { - _allowUnencryptedStorage = options?.UnsafeAllowUnencryptedStorage ?? false; - _name = (options?.Name ?? Constants.DefaultMsalTokenCacheName) + (enableCae ? Constants.CaeEnabledCacheSuffix : Constants.CaeDisabledCacheSuffix); - _persistToDisk = true; - } - } - - /// <summary> - /// A delegate that is called with the cache contents when the underlying <see cref="TokenCache"/> has been updated. - /// </summary> - internal Func<TokenCacheUpdatedArgs, Task> TokenCacheUpdatedAsync; - - /// <summary> - /// A delegate that will be called before the cache is accessed. The data returned will be used to set the current state of the cache. - /// </summary> - internal Func<TokenCacheRefreshArgs, CancellationToken, Task<TokenCacheData>> RefreshCacheFromOptionsAsync; - - internal virtual async Task RegisterCache(bool async, ITokenCache tokenCache, CancellationToken cancellationToken) - { - if (_persistToDisk) - { - MsalCacheHelperWrapper cacheHelper = await GetCacheHelperAsync(async, cancellationToken).ConfigureAwait(false); - cacheHelper.RegisterCache(tokenCache); - } - else - { - if (async) - { - await _lock.WaitAsync(cancellationToken).ConfigureAwait(false); - } - else - { - _lock.Wait(cancellationToken); - } - - try - { - if (!_cacheAccessMap.TryGetValue(tokenCache, out _)) - { - tokenCache.SetBeforeAccessAsync(OnBeforeCacheAccessAsync); - - tokenCache.SetAfterAccessAsync(OnAfterCacheAccessAsync); - - _cacheAccessMap.Add(tokenCache, new CacheTimestamp()); - } - } - finally - { - _lock.Release(); - } - } - } - - private async Task OnBeforeCacheAccessAsync(TokenCacheNotificationArgs args) - { - await _lock.WaitAsync().ConfigureAwait(false); - - try - { - if (RefreshCacheFromOptionsAsync != null) - { - Data = (await RefreshCacheFromOptionsAsync(new TokenCacheRefreshArgs(args, IsCaeEnabled), default).ConfigureAwait(false)) - .CacheBytes.ToArray(); - } - args.TokenCache.DeserializeMsalV3(Data, true); - - _cacheAccessMap.GetOrCreateValue(args.TokenCache).Update(); - } - finally - { - _lock.Release(); - } - } - - private async Task OnAfterCacheAccessAsync(TokenCacheNotificationArgs args) - { - if (args.HasStateChanged) - { - await UpdateCacheDataAsync(args.TokenCache).ConfigureAwait(false); - } - } - - private async Task UpdateCacheDataAsync(ITokenCacheSerializer tokenCache) - { - await _lock.WaitAsync().ConfigureAwait(false); - - try - { - if (!_cacheAccessMap.TryGetValue(tokenCache, out CacheTimestamp lastRead) || lastRead.Value < _lastUpdated) - { - Data = await MergeCacheData(Data, tokenCache.SerializeMsalV3()).ConfigureAwait(false); - } - else - { - Data = tokenCache.SerializeMsalV3(); - } - - if (TokenCacheUpdatedAsync != null) - { - var eventBytes = Data.ToArray(); - await TokenCacheUpdatedAsync(new TokenCacheUpdatedArgs(eventBytes, IsCaeEnabled)).ConfigureAwait(false); - } - - _lastUpdated = _cacheAccessMap.GetOrCreateValue(tokenCache).Update(); - } - finally - { - _lock.Release(); - } - } - - private async Task<byte[]> MergeCacheData(byte[] cacheA, byte[] cacheB) - { - byte[] merged = null; - - IPublicClientApplication client = _publicClientApplicationFactory(); - - client.UserTokenCache.SetBeforeAccess(args => args.TokenCache.DeserializeMsalV3(cacheA)); - - await client.GetAccountsAsync().ConfigureAwait(false); - - client.UserTokenCache.SetBeforeAccess(args => args.TokenCache.DeserializeMsalV3(cacheB, shouldClearExistingCache: false)); - - client.UserTokenCache.SetAfterAccess(args => merged = args.TokenCache.SerializeMsalV3()); - - await client.GetAccountsAsync().ConfigureAwait(false); - - return merged; - } - - private async Task<MsalCacheHelperWrapper> GetCacheHelperAsync(bool async, CancellationToken cancellationToken) - { - using (var asyncLock = await cacheHelperLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait(false)) - { - if (asyncLock.HasValue) - { - return asyncLock.Value; - } - - MsalCacheHelperWrapper cacheHelper; - - try - { - cacheHelper = await GetProtectedCacheHelperAsync(async, _name).ConfigureAwait(false); - - cacheHelper.VerifyPersistence(); - } - catch (MsalCachePersistenceException) - { - if (_allowUnencryptedStorage) - { - cacheHelper = await GetFallbackCacheHelperAsync(async, _name).ConfigureAwait(false); - - cacheHelper.VerifyPersistence(); - } - else - { - throw; - } - } - - asyncLock.SetValue(cacheHelper); - - return cacheHelper; - } - } - - private async Task<MsalCacheHelperWrapper> GetProtectedCacheHelperAsync(bool async, string name) - { - StorageCreationProperties storageProperties = new StorageCreationPropertiesBuilder(name, Constants.DefaultMsalTokenCacheDirectory) - .WithMacKeyChain(Constants.DefaultMsalTokenCacheKeychainService, name) - .WithLinuxKeyring(Constants.DefaultMsalTokenCacheKeyringSchema, Constants.DefaultMsalTokenCacheKeyringCollection, name, Constants.DefaultMsaltokenCacheKeyringAttribute1, Constants.DefaultMsaltokenCacheKeyringAttribute2) - .Build(); - - MsalCacheHelperWrapper cacheHelper = await InitializeCacheHelper(async, storageProperties).ConfigureAwait(false); - - return cacheHelper; - } - - private async Task<MsalCacheHelperWrapper> GetFallbackCacheHelperAsync(bool async, string name = Constants.DefaultMsalTokenCacheName) - { - StorageCreationProperties storageProperties = new StorageCreationPropertiesBuilder(name, Constants.DefaultMsalTokenCacheDirectory) - .WithMacKeyChain(Constants.DefaultMsalTokenCacheKeychainService, name) - .WithLinuxUnprotectedFile() - .Build(); - - MsalCacheHelperWrapper cacheHelper = await InitializeCacheHelper(async, storageProperties).ConfigureAwait(false); - - return cacheHelper; - } - - private async Task<MsalCacheHelperWrapper> InitializeCacheHelper(bool async, StorageCreationProperties storageProperties) - { - if (async) - { - await _cacheHelperWrapper.InitializeAsync(storageProperties).ConfigureAwait(false); - } - else - { -#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead. - _cacheHelperWrapper.InitializeAsync(storageProperties).GetAwaiter().GetResult(); -#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead. - } - return _cacheHelperWrapper; - } - } -} diff --git a/src/Accounts/Authentication/Identity/TokenCacheData.cs b/src/Accounts/Authentication/Identity/TokenCacheData.cs deleted file mode 100644 index ae3513121eb9..000000000000 --- a/src/Accounts/Authentication/Identity/TokenCacheData.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Details related to a <see cref="UnsafeTokenCacheOptions"/> cache delegate. - /// </summary> - public struct TokenCacheData - { - /// <summary> - /// Constructs a new <see cref="TokenCacheData"/> instance with the specified cache bytes. - /// </summary> - /// <param name="cacheBytes">The serialized content of the token cache.</param> - public TokenCacheData(ReadOnlyMemory<byte> cacheBytes) - { - CacheBytes = cacheBytes; - } - - /// <summary> - /// The bytes representing the state of the token cache. - /// </summary> - public ReadOnlyMemory<byte> CacheBytes { get; } - } -} diff --git a/src/Accounts/Authentication/Identity/TokenCacheRefreshArgs.cs b/src/Accounts/Authentication/Identity/TokenCacheRefreshArgs.cs deleted file mode 100644 index b0920c206c52..000000000000 --- a/src/Accounts/Authentication/Identity/TokenCacheRefreshArgs.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Microsoft.Identity.Client; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Args sent to TokenCache OnBefore and OnAfter events. - /// </summary> - public class TokenCacheRefreshArgs - { - /// <summary> - /// A suggested token cache key, which can be used with general purpose storage mechanisms that allow - /// storing key-value pairs and key based retrieval. Useful in applications that store one token cache per user, - /// the recommended pattern for web apps. - /// - /// The value is: - /// - /// <list type="bullet"> - /// <item><description><c>homeAccountId</c> for <c>AcquireTokenSilent</c>, <c>GetAccount(homeAccountId)</c>, <c>RemoveAccount</c> and when writing tokens on confidential client calls</description></item> - /// <item><description><c>"{clientId}__AppTokenCache"</c> for <c>AcquireTokenForClient</c></description></item> - /// <item><description><c>"{clientId}_{tenantId}_AppTokenCache"</c> for <c>AcquireTokenForClient</c> when using a tenant specific authority</description></item> - /// <item><description>the hash of the original token for <c>AcquireTokenOnBehalfOf</c></description></item> - /// </list> - /// </summary> - public string SuggestedCacheKey { get; } - - /// <summary> - /// Whether or not the cache is enabled for CAE. Note that this value should be used as an indicator for how the cache will be partitioned. - /// Token cache refresh events with this value set to `true` will originate from a different cache instance than those with this value set to `false`. - /// </summary> - public bool IsCaeEnabled { get; } - - internal TokenCacheRefreshArgs(TokenCacheNotificationArgs args, bool enableCae) - { - SuggestedCacheKey = args.SuggestedCacheKey; - IsCaeEnabled = enableCae; - } - } -} diff --git a/src/Accounts/Authentication/Identity/TokenCacheUpdatedArgs.cs b/src/Accounts/Authentication/Identity/TokenCacheUpdatedArgs.cs deleted file mode 100644 index b1c1843c4f86..000000000000 --- a/src/Accounts/Authentication/Identity/TokenCacheUpdatedArgs.cs +++ /dev/null @@ -1,44 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure.Identity; - -using System; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Data regarding an update of a token cache. - /// </summary> - public class TokenCacheUpdatedArgs - { - internal TokenCacheUpdatedArgs(ReadOnlyMemory<byte> cacheData, bool enableCae) - { - UnsafeCacheData = cacheData; - IsCaeEnabled = enableCae; - } - - /// <summary> - /// The <see cref="TokenCachePersistenceOptions"/> instance which was updated. - /// </summary> - public ReadOnlyMemory<byte> UnsafeCacheData { get; } - - /// <summary> - /// Whether or not the cache is enabled for CAE. Note that this value should be used as an indicator for how the cache will be partitioned. - /// Token cache refresh events with this value set to `true` will originate from a different cache instance than those with this value set to `false`. - /// </summary> - public bool IsCaeEnabled { get; } - } -} diff --git a/src/Accounts/Authentication/Identity/TokenHelper.cs b/src/Accounts/Authentication/Identity/TokenHelper.cs deleted file mode 100644 index a5c2067c1912..000000000000 --- a/src/Accounts/Authentication/Identity/TokenHelper.cs +++ /dev/null @@ -1,89 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; - -using System; -using System.Text.Json; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal static class TokenHelper - { - public static (string ClientId, string TenantId, string Upn, string ObjectId) ParseAccountInfoFromToken(string token) - { - Argument.AssertNotNullOrEmpty(token, nameof(token)); - var parts = token.Split('.'); - if (parts.Length != 3) - { - throw new ArgumentException("Invalid token", nameof(token)); - } - - (string ClientId, string TenantId, string Upn, string ObjectId) result = default; - - try - { - string convertedToken = parts[1].Replace('_', '/').Replace('-', '+'); - switch (parts[1].Length % 4) - { - case 2: - convertedToken += "=="; - break; - case 3: - convertedToken += "="; - break; - } - Utf8JsonReader reader = new Utf8JsonReader(Convert.FromBase64String(convertedToken)); - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.PropertyName) - { - switch (reader.GetString()) - { - case "appid": - reader.Read(); - result.ClientId = reader.GetString(); - break; - - case "tid": - reader.Read(); - result.TenantId = reader.GetString(); - break; - - case "upn": - reader.Read(); - result.Upn = reader.GetString(); - break; - - case "oid": - reader.Read(); - result.ObjectId = reader.GetString(); - break; - default: - reader.Read(); - break; - } - } - } - } - catch - { - AzureIdentityEventSource.Singleton.UnableToParseAccountDetailsFromToken(); - } - - return result; - } - } -} diff --git a/src/Accounts/Authentication/Identity/UnsafeTokenCacheOptions.cs b/src/Accounts/Authentication/Identity/UnsafeTokenCacheOptions.cs deleted file mode 100644 index c713a821f8d1..000000000000 --- a/src/Accounts/Authentication/Identity/UnsafeTokenCacheOptions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure.Identity; - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// Options controlling the storage of the token cache. - /// </summary> - public abstract class UnsafeTokenCacheOptions : TokenCachePersistenceOptions - { - /// <summary> - /// The delegate to be called when the Updated event fires. - /// </summary> - protected internal abstract Task TokenCacheUpdatedAsync(TokenCacheUpdatedArgs tokenCacheUpdatedArgs); - - /// <summary> - /// Returns the bytes used to initialize the token cache. This would most likely have come from the <see cref="TokenCacheUpdatedArgs"/>. - /// This implementation will get called by the default implementation of <see cref="RefreshCacheAsync(TokenCacheRefreshArgs, CancellationToken)"/>. - /// It is recommended to provide an implementation for <see cref="RefreshCacheAsync(TokenCacheRefreshArgs, CancellationToken)"/> rather than this method. - /// </summary> - protected internal abstract Task<ReadOnlyMemory<byte>> RefreshCacheAsync(); - - /// <summary> - /// Returns the bytes used to initialize the token cache. This would most likely have come from the <see cref="TokenCacheUpdatedArgs"/>. - /// It is recommended that if this method is overriden, there is no need to provide a duplicate implementation for the parameterless <see cref="RefreshCacheAsync()"/>. - /// </summary> - /// <param name="args">The <see cref="TokenCacheRefreshArgs"/> containing information about the current state of the cache.</param> - /// <param name="cancellationToken">The <see cref="CancellationToken"/> controlling the lifetime of this operation.</param> - protected internal virtual async Task<TokenCacheData> RefreshCacheAsync(TokenCacheRefreshArgs args, CancellationToken cancellationToken = default) => - new TokenCacheData(await RefreshCacheAsync().ConfigureAwait(false)); - } -} diff --git a/src/Accounts/Authentication/Identity/Validations.cs b/src/Accounts/Authentication/Identity/Validations.cs deleted file mode 100644 index 60d47a4f0d2a..000000000000 --- a/src/Accounts/Authentication/Identity/Validations.cs +++ /dev/null @@ -1,92 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// -using System; -using System.Runtime.InteropServices; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - internal static class Validations - { - private const string InvalidTenantIdErrorMessage = "Invalid tenant id provided. You can locate your tenant id by following the instructions listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names"; - - private const string NullTenantIdErrorMessage = "Tenant id cannot be null. You can locate your tenant id by following the instructions listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names"; - - private const string NonTlsAuthorityHostErrorMessage = "Authority host must be a TLS protected (https) endpoint."; - - internal const string NoWindowsPowerShellLegacyErrorMessage = "PowerShell Legacy is only supported in Windows."; - - /// <summary> - /// As tenant id is used in constructing authority endpoints and in command line invocation we validate the character set of the tenant id matches allowed characters. - /// </summary> - public static string ValidateTenantId(string tenantId, string argumentName = default, bool allowNull = false) - { - if (tenantId != null) - { - if (tenantId.Length == 0) - { - throw (argumentName != null) ? new ArgumentException(InvalidTenantIdErrorMessage, argumentName) : new ArgumentException(InvalidTenantIdErrorMessage); - } - - foreach (char c in tenantId) - { - if (!IsValidTenantCharacter(c)) - { - throw (argumentName != null) ? new ArgumentException(InvalidTenantIdErrorMessage, argumentName) : new ArgumentException(InvalidTenantIdErrorMessage); - } - } - } - else if (!allowNull) - { - throw (argumentName != null) ? new ArgumentNullException(argumentName, NullTenantIdErrorMessage) : new ArgumentNullException(InvalidTenantIdErrorMessage, (Exception)null); - } - - return tenantId; - } - - public static Uri ValidateAuthorityHost(Uri authorityHost) - { - if (authorityHost != null) - { - if (string.Compare(authorityHost.Scheme, "https", StringComparison.OrdinalIgnoreCase) != 0) - { - throw new ArgumentException(NonTlsAuthorityHostErrorMessage); - } - } - - return authorityHost; - } - - /// <summary> - /// PowerShell Legacy can only be used on Windows OS systems. - /// </summary> - /// <param name="useLegacyPowerShell"></param> - /// <returns></returns> - public static bool CanUseLegacyPowerShell(bool useLegacyPowerShell) - { - //If the OS is not Windows, PowerShell Legacy cannot be used - if (useLegacyPowerShell && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - throw new ArgumentException(NoWindowsPowerShellLegacyErrorMessage); - } - - return useLegacyPowerShell; - } - - private static bool IsValidTenantCharacter(char c) - { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '.') || (c == '-'); - } - } -} diff --git a/src/Accounts/Authentication/Identity/X509Certificate2FromFileProvider.cs b/src/Accounts/Authentication/Identity/X509Certificate2FromFileProvider.cs deleted file mode 100644 index 2d4ec340b02f..000000000000 --- a/src/Accounts/Authentication/Identity/X509Certificate2FromFileProvider.cs +++ /dev/null @@ -1,156 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using Azure.Identity; - -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// X509Certificate2FromFileProvider provides an X509Certificate2 from a file on disk. It supports both - /// "pfx" and "pem" encoded certificates. - /// </summary> - internal class X509Certificate2FromFileProvider : IX509Certificate2Provider - { - // Lazy initialized on the first call to GetCertificateAsync, based on CertificatePath. - private X509Certificate2 Certificate { get; set; } - internal string CertificatePath { get; } - internal string CertificatePassword { get; } - - public X509Certificate2FromFileProvider(string clientCertificatePath, string certificatePassword) - { - Argument.AssertNotNull(clientCertificatePath, nameof(clientCertificatePath)); - CertificatePath = clientCertificatePath ?? throw new ArgumentNullException(nameof(clientCertificatePath)); - CertificatePassword = certificatePassword; - } - - public ValueTask<X509Certificate2> GetCertificateAsync(bool async, CancellationToken cancellationToken) - { - if (!(Certificate is null)) - { - return new ValueTask<X509Certificate2>(Certificate); - } - - string fileType = Path.GetExtension(CertificatePath); - - switch (fileType.ToLowerInvariant()) - { - case ".pfx": - return LoadCertificateFromPfxFileAsync(async, CertificatePath, CertificatePassword, cancellationToken); - case ".pem": - if (CertificatePassword != null) - { - throw new CredentialUnavailableException("Password protection for PEM encoded certificates is not supported."); - } - return LoadCertificateFromPemFileAsync(async, CertificatePath, cancellationToken); - default: - throw new CredentialUnavailableException("Only .pfx and .pem files are supported."); - } - } - - private async ValueTask<X509Certificate2> LoadCertificateFromPfxFileAsync(bool async, string clientCertificatePath, string certificatePassword, CancellationToken cancellationToken) - { - const int BufferSize = 4 * 1024; - - if (!(Certificate is null)) - { - return Certificate; - } - - try - { - if (!async) - { - Certificate = new X509Certificate2(clientCertificatePath, certificatePassword); - } - else - { - List<byte> certContents = new List<byte>(); - byte[] buf = new byte[BufferSize]; - int offset = 0; - using (Stream s = File.OpenRead(clientCertificatePath)) - { - while (true) - { - int read = await s.ReadAsync(buf, offset, buf.Length, cancellationToken).ConfigureAwait(false); - for (int i = 0; i < read; i++) - { - certContents.Add(buf[i]); - } - - if (read == 0) - { - break; - } - } - } - - Certificate = new X509Certificate2(certContents.ToArray(), certificatePassword); - } - - return Certificate; - } - catch (Exception e) when (!(e is OperationCanceledException)) - { - throw new CredentialUnavailableException("Could not load certificate file", e); - } - } - - private async ValueTask<X509Certificate2> LoadCertificateFromPemFileAsync(bool async, string clientCertificatePath, CancellationToken cancellationToken) - { - if (!(Certificate is null)) - { - return Certificate; - } - - string certficateText; - - try - { - if (!async) - { - certficateText = File.ReadAllText(clientCertificatePath); - } - else - { - cancellationToken.ThrowIfCancellationRequested(); - - using (StreamReader sr = new StreamReader(clientCertificatePath)) - { - certficateText = await sr.ReadToEndAsync().ConfigureAwait(false); - } - } - - Certificate = PemReader.LoadCertificate(certficateText.AsSpan(), keyType: PemReader.KeyType.RSA); - - return Certificate; - } - catch (Exception e) when (!(e is OperationCanceledException)) - { - throw new CredentialUnavailableException("Could not load certificate file", e); - } - } - - private delegate void ImportPkcs8PrivateKeyDelegate(ReadOnlySpan<byte> blob, out int bytesRead); - } -} diff --git a/src/Accounts/Authentication/Identity/X509Certificate2FromObjectProvider.cs b/src/Accounts/Authentication/Identity/X509Certificate2FromObjectProvider.cs deleted file mode 100644 index c3f932557c6f..000000000000 --- a/src/Accounts/Authentication/Identity/X509Certificate2FromObjectProvider.cs +++ /dev/null @@ -1,40 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- -// - -using System; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.PowerShell.Authenticators.Identity -{ - /// <summary> - /// X509Certificate2FromObjectProvider provides an X509Certificate2 from an existing instance. - /// </summary> - internal class X509Certificate2FromObjectProvider : IX509Certificate2Provider - { - private X509Certificate2 Certificate { get; } - - public X509Certificate2FromObjectProvider(X509Certificate2 clientCertificate) - { - Certificate = clientCertificate ?? throw new ArgumentNullException(nameof(clientCertificate)); - } - - public ValueTask<X509Certificate2> GetCertificateAsync(bool async, CancellationToken cancellationToken) - { - return new ValueTask<X509Certificate2>(Certificate); - } - } -} diff --git a/src/Accounts/Authentication/KeyStore/AsyncLockWithValue.cs b/src/Accounts/Authentication/KeyStore/AsyncLockWithValue.cs index 05852f1e947a..bf8aa882750d 100644 --- a/src/Accounts/Authentication/KeyStore/AsyncLockWithValue.cs +++ b/src/Accounts/Authentication/KeyStore/AsyncLockWithValue.cs @@ -12,7 +12,6 @@ // limitations under the License. // ---------------------------------------------------------------------------------- // -using Microsoft.Azure.PowerShell.Authenticators.Identity.Core; using System; using System.Collections.Generic; using System.Threading; diff --git a/src/Accounts/Authentication/Identity/Core/TaskExtensions.cs b/src/Accounts/Authentication/KeyStore/TaskExtensions.cs similarity index 99% rename from src/Accounts/Authentication/Identity/Core/TaskExtensions.cs rename to src/Accounts/Authentication/KeyStore/TaskExtensions.cs index d26047531f77..74d63094128e 100644 --- a/src/Accounts/Authentication/Identity/Core/TaskExtensions.cs +++ b/src/Accounts/Authentication/KeyStore/TaskExtensions.cs @@ -20,7 +20,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Azure.PowerShell.Authenticators.Identity.Core +namespace Microsoft.Azure.Commands.ResourceManager.Common { internal static class TaskExtensions { diff --git a/src/Accounts/Authenticators/ClientAssertionAuthenticator.cs b/src/Accounts/Authenticators/ClientAssertionAuthenticator.cs index 94af4a71ca6f..25a7c2c35533 100644 --- a/src/Accounts/Authenticators/ClientAssertionAuthenticator.cs +++ b/src/Accounts/Authenticators/ClientAssertionAuthenticator.cs @@ -13,6 +13,7 @@ // ---------------------------------------------------------------------------------- using Azure.Core; +using Azure.Identity; using Microsoft.Azure.Commands.Common.Authentication; using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Azure.PowerShell.Authenticators.Factories; @@ -20,7 +21,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Azure.PowerShell.Authenticators.Identity; namespace Microsoft.Azure.PowerShell.Authenticators {