From c6f77b730c9de890a9e94eef56902361e1137098 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 28 Apr 2021 12:43:07 -0500 Subject: [PATCH 01/39] wip --- ...earerTokenChallengeAuthenticationPolicy.cs | 2 +- .../Azure.Core/src/TokenRequestContext.cs | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/sdk/core/Azure.Core/src/Shared/BearerTokenChallengeAuthenticationPolicy.cs b/sdk/core/Azure.Core/src/Shared/BearerTokenChallengeAuthenticationPolicy.cs index 921f9aef43077..58061c0c99c71 100644 --- a/sdk/core/Azure.Core/src/Shared/BearerTokenChallengeAuthenticationPolicy.cs +++ b/sdk/core/Azure.Core/src/Shared/BearerTokenChallengeAuthenticationPolicy.cs @@ -157,7 +157,7 @@ public AccessTokenCache(TokenCredential credential, TimeSpan tokenRefreshOffset, _credential = credential; _tokenRefreshOffset = tokenRefreshOffset; _tokenRefreshRetryDelay = tokenRefreshRetryDelay; - _currentContext = new TokenRequestContext(initialScopes); + _currentContext = new TokenRequestContext(initialScopes, null, null, null); } public async ValueTask GetHeaderValueAsync(HttpMessage message, TokenRequestContext context, bool async) diff --git a/sdk/core/Azure.Core/src/TokenRequestContext.cs b/sdk/core/Azure.Core/src/TokenRequestContext.cs index 0346a06a8c783..8bb2822e9a6f4 100644 --- a/sdk/core/Azure.Core/src/TokenRequestContext.cs +++ b/sdk/core/Azure.Core/src/TokenRequestContext.cs @@ -18,6 +18,7 @@ public TokenRequestContext(string[] scopes, string? parentRequestId) Scopes = scopes; ParentRequestId = parentRequestId; Claims = default; + TenantIdHint = default; } /// @@ -31,6 +32,22 @@ public TokenRequestContext(string[] scopes, string? parentRequestId = default, s Scopes = scopes; ParentRequestId = parentRequestId; Claims = claims; + TenantIdHint = default; + } + + /// + /// Creates a new TokenRequest with the specified scopes. + /// + /// The scopes required for the token. + /// The of the request requiring a token for authentication, if applicable. + /// Additional claims to be included in the token. + /// The tenantId to prefer when requesting a token. + public TokenRequestContext(string[] scopes, string? parentRequestId = default, string? claims = default, string? tenantId = default) + { + Scopes = scopes; + ParentRequestId = parentRequestId; + Claims = claims; + TenantIdHint = tenantId; } /// @@ -47,5 +64,10 @@ public TokenRequestContext(string[] scopes, string? parentRequestId = default, s /// Additional claims to be included in the token. See https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter for more information on format and content. /// public string? Claims { get; } + + /// + /// A hint to indicate which tenantId is preferred. + /// + public string? TenantIdHint { get; } } } From 08265aac8ec0d42eea62ec305435fb684eb79ad7 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 12 May 2021 12:53:15 -0500 Subject: [PATCH 02/39] wip --- .../BearerTokenAuthenticationPolicy.cs | 2 +- .../Azure.Core/src/TokenRequestContext.cs | 15 ++- .../src/TokenRequestContextOptions.cs | 31 ++++++ .../Azure.Identity/src/Azure.Identity.csproj | 5 + .../src/VisualStudioCodeCredential.cs | 10 +- .../src/VisualStudioCredential.cs | 10 +- .../src/Azure.Storage.Common.csproj | 10 +- ...BearerTokenChallengeAuthorizationPolicy.cs | 97 +++++++++++++++++++ .../src/Shared/StorageClientOptions.cs | 3 +- 9 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 sdk/core/Azure.Core/src/TokenRequestContextOptions.cs create mode 100644 sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs diff --git a/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs index 775bd3d1a774f..649211bf51afc 100644 --- a/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs @@ -136,7 +136,7 @@ private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory /// Creates a new TokenRequest with the specified scopes. /// - /// The scopes required for the token. - /// The of the request requiring a token for authentication, if applicable. - /// Additional claims to be included in the token. - /// The tenantId to prefer when requesting a token. - public TokenRequestContext(string[] scopes, string? parentRequestId = default, string? claims = default, string? tenantId = default) + /// The options to initialized the + public TokenRequestContext(TokenRequestContextOptions options) { - Scopes = scopes; - ParentRequestId = parentRequestId; - Claims = claims; - TenantIdHint = tenantId; + Scopes = options.Scopes; + ParentRequestId = options.ParentRequestId; + Claims = options.Claims; + TenantIdHint = options.TenantIdHint; } /// diff --git a/sdk/core/Azure.Core/src/TokenRequestContextOptions.cs b/sdk/core/Azure.Core/src/TokenRequestContextOptions.cs new file mode 100644 index 0000000000000..22341b68b5a28 --- /dev/null +++ b/sdk/core/Azure.Core/src/TokenRequestContextOptions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Core +{ + /// + /// Options for initialization. + /// + public struct TokenRequestContextOptions + { + /// + /// The scopes required for the token. + /// + public string[] Scopes { get; set; } + + /// + /// The of the request requiring a token for authentication, if applicable. + /// + public string? ParentRequestId { get; set; } + + /// + /// Additional claims to be included in the token. See https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter for more information on format and content. + /// + public string? Claims { get; set; } + + /// + /// A hint to indicate which tenantId is preferred. + /// + public string? TenantIdHint { get; set; } + } +} diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj index 0833412fc88aa..aa7f88f6fdd40 100644 --- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj +++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj @@ -10,7 +10,12 @@ true + + + + diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs index b051e86ec5eca..f729c02a9a406 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs @@ -26,6 +26,8 @@ public class VisualStudioCodeCredential : TokenCredential private readonly CredentialPipeline _pipeline; private readonly string _tenantId; private readonly MsalPublicClient _client; + private const string _commonTenant = "common"; + private bool tenantIdOptionProvided; /// /// Creates a new instance of the . @@ -41,7 +43,8 @@ public VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options) : t internal VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client, IFileSystemService fileSystem, IVisualStudioCodeAdapter vscAdapter) { - _tenantId = options?.TenantId ?? "common"; + tenantIdOptionProvided = options?.TenantId != null; + _tenantId = options?.TenantId ?? _commonTenant; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _client = client ?? new MsalPublicClient(_pipeline, options?.TenantId, ClientId, null, null); _fileSystem = fileSystem ?? FileSystemService.Default; @@ -63,6 +66,10 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque try { GetUserSettings(out var tenant, out var environmentName); + if (!tenantIdOptionProvided && requestContext.TenantIdHint != default ) + { + tenant = requestContext.TenantIdHint; + } if (string.Equals(tenant, Constants.AdfsTenantId, StringComparison.Ordinal)) { @@ -135,6 +142,7 @@ private void GetUserSettings(out string tenant, out string environmentName) if (root.TryGetProperty("azure.tenant", out JsonElement tenantProperty)) { tenant = tenantProperty.GetString(); + tenantIdOptionProvided = true; } if (root.TryGetProperty("azure.cloud", out JsonElement environmentProperty)) diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs index a8ce6db9c3f82..34229d1b9f9c1 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs @@ -29,6 +29,7 @@ public class VisualStudioCredential : TokenCredential private readonly string _tenantId; private readonly IFileSystemService _fileSystem; private readonly IProcessService _processService; + private bool tenantIdOptionProvided; /// /// Creates a new instance of the . @@ -43,6 +44,7 @@ public VisualStudioCredential(VisualStudioCredentialOptions options) : this(opti internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IFileSystemService fileSystem, IProcessService processService) { + tenantIdOptionProvided = tenantId != null; _tenantId = tenantId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(null); _fileSystem = fileSystem ?? FileSystemService.Default; @@ -72,7 +74,7 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque var tokenProviders = GetTokenProviders(tokenProviderPath); var resource = ScopeUtilities.ScopesToResource(requestContext.Scopes); - var processStartInfos = GetProcessStartInfos(tokenProviders, resource, cancellationToken); + var processStartInfos = GetProcessStartInfos(tokenProviders, resource, requestContext, cancellationToken); if (processStartInfos.Count == 0) { @@ -141,7 +143,7 @@ private async Task RunProcessesAsync(List process } } - private List GetProcessStartInfos(VisualStudioTokenProvider[] visualStudioTokenProviders, string resource, CancellationToken cancellationToken) + private List GetProcessStartInfos(VisualStudioTokenProvider[] visualStudioTokenProviders, string resource, TokenRequestContext requestContext, CancellationToken cancellationToken) { List processStartInfos = new List(); StringBuilder arguments = new StringBuilder(); @@ -159,9 +161,9 @@ private List GetProcessStartInfos(VisualStudioTokenProvider[] arguments.Clear(); arguments.Append(ResourceArgumentName).Append(' ').Append(resource); - if (_tenantId != default) + if (_tenantId != default || requestContext.TenantIdHint != default) { - arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(_tenantId); + arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(_tenantId ?? requestContext.TenantIdHint); } // Add the arguments set in the token provider file. diff --git a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj index cd0d64d7354d2..7bb0edfe9d618 100644 --- a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj +++ b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj @@ -18,9 +18,16 @@ Azure.Storage + + + + + + @@ -29,11 +36,12 @@ - + + diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs new file mode 100644 index 0000000000000..481bc469ff99f --- /dev/null +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.Pipeline; + +namespace Azure.Storage.Shared +{ + /// + /// + /// + public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy + { + private readonly bool _disableTenantDiscovery; + private readonly string[] _scopes; + private volatile string tenantId; + + /// + /// + /// + /// + /// + public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, string scope, bool disableTenantDiscovery) : base(credential, scope) + { + _disableTenantDiscovery = disableTenantDiscovery; + Argument.AssertNotNullOrEmpty(scope, nameof(scope)); + _scopes = new[] { scope }; + } + + /// + /// + /// + /// + /// + public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, IEnumerable scopes, bool disableTenantDiscovery) : base(credential, scopes) + { + Argument.AssertNotNull(scopes, nameof(scopes)); + _scopes = scopes.ToArray(); + } + + /// + protected override void AuthorizeRequest(HttpMessage message) + { + if (tenantId != null || disab) + { + base.AuthorizeRequest(message); + } + } + + /// + protected override ValueTask AuthorizeRequestAsync(HttpMessage message) + { + return tenantId switch + { + null => default, + _ => base.AuthorizeRequestAsync(message) + }; + } + + /// + protected override bool AuthorizeRequestOnChallenge(HttpMessage message) => AuthorizeRequestOnChallengeInternalAsync(message, false).EnsureCompleted(); + + /// + protected override ValueTask AuthorizeRequestOnChallengeAsync(HttpMessage message) => AuthorizeRequestOnChallengeInternalAsync(message, true); + + private async ValueTask AuthorizeRequestOnChallengeInternalAsync(HttpMessage message, bool async) + { + try + { + var authUri = AuthorizationChallengeParser.GetChallengeParameterFromResponse(message.Response, "Bearer", "authorization_uri"); + + // tenantId should be the guid as seen in this example: https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize + tenantId = new Uri(authUri).Segments[1].Trim('/'); + + var context = new TokenRequestContext( + new TokenRequestContextOptions { Scopes = _scopes, TenantIdHint = tenantId, ParentRequestId = message.Request.ClientRequestId }); + if (async) + { + await AuthenticateAndAuthorizeRequestAsync(message, context).ConfigureAwait(false); + } + else + { + AuthenticateAndAuthorizeRequest(message, context); + } + return true; + } + catch + { + return default; + } + } + } +} diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs index e86cb2de5c983..a0846fbf7370f 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Azure.Core; using Azure.Core.Pipeline; +using Azure.Storage.Shared; namespace Azure.Storage { @@ -67,7 +68,7 @@ public static HttpPipelinePolicy AsPolicy(this AzureSasCredential c /// Credential to use. /// An authentication policy. public static HttpPipelinePolicy AsPolicy(this TokenCredential credential) => - new BearerTokenAuthenticationPolicy( + new StorageBearerTokenChallengeAuthorizationPolicy( credential ?? throw Errors.ArgumentNull(nameof(credential)), StorageScope); From a06206a589d130532e1ed93f54a3a82a01c9fce2 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 12 May 2021 17:16:19 -0500 Subject: [PATCH 03/39] Storage POC --- .../Azure.Storage.Blobs/src/BlobBaseClient.cs | 4 ++++ .../src/BlobClientOptions.cs | 5 ++++ .../src/BlobContainerClient.cs | 5 ++++ ...BearerTokenChallengeAuthorizationPolicy.cs | 23 +++++++++++-------- .../src/DataLakeClientOptions.cs | 5 ++++ .../src/DataLakePathClient.cs | 4 ++++ .../Azure.Storage.Queues/src/QueueClient.cs | 5 ++++ .../src/QueueClientOptions.cs | 5 ++++ 8 files changed, 46 insertions(+), 10 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs index 19d207dddafd8..d48c84f29ca97 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs @@ -366,6 +366,10 @@ internal BlobBaseClient( _blobVersionId = System.Web.HttpUtility.ParseQueryString(blobUri.Query).Get(Constants.VersionIdParameterName); } } + if (authentication is StorageBearerTokenChallengeAuthorizationPolicy policy) + { + policy.DisableTenantDiscovery = options.DisableTenantDiscovery; + } _clientConfiguration = new BlobClientConfiguration( pipeline: options.Build(authentication), diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs index b644f4d7904ce..01b36f10e6475 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs @@ -87,6 +87,11 @@ public enum ServiceVersion /// public string EncryptionScope { get; set; } + /// + /// Disables discovery of the tenant Id related to the storage service. + /// + public bool DisableTenantDiscovery { get; set; } + /// /// Gets or sets the secondary storage that can be read from for the storage account if the /// account is enabled for RA-GRS. diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs index ccfd1c0ab132e..814be72beab9d 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs @@ -14,6 +14,7 @@ using Azure.Storage.Blobs.Specialized; using Azure.Storage.Cryptography; using Azure.Storage.Sas; +using Azure.Storage.Shared; using Metadata = System.Collections.Generic.IDictionary; #pragma warning disable SA1402 // File may only contain a single type @@ -199,6 +200,10 @@ public BlobContainerClient(string connectionString, string blobContainerName, Bl encryptionScope: options.EncryptionScope); _authenticationPolicy = StorageClientOptions.GetAuthenticationPolicy(conn.Credentials); + if (_authenticationPolicy is StorageBearerTokenChallengeAuthorizationPolicy policy) + { + policy.DisableTenantDiscovery = options.DisableTenantDiscovery; + } _containerRestClient = BuildContainerRestClient(_uri); BlobErrors.VerifyHttpsCustomerProvidedKey(_uri, _clientConfiguration.CustomerProvidedKey); diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index 481bc469ff99f..18b93209466b4 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -15,7 +15,11 @@ namespace Azure.Storage.Shared /// public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy { - private readonly bool _disableTenantDiscovery; + /// + /// Disables tenant discovery through challenge responses. + /// + public bool DisableTenantDiscovery { get; set; } + private readonly string[] _scopes; private volatile string tenantId; @@ -24,9 +28,8 @@ public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthent /// /// /// - public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, string scope, bool disableTenantDiscovery) : base(credential, scope) + public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, string scope) : base(credential, scope) { - _disableTenantDiscovery = disableTenantDiscovery; Argument.AssertNotNullOrEmpty(scope, nameof(scope)); _scopes = new[] { scope }; } @@ -36,7 +39,7 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential /// /// /// - public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, IEnumerable scopes, bool disableTenantDiscovery) : base(credential, scopes) + public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, IEnumerable scopes) : base(credential, scopes) { Argument.AssertNotNull(scopes, nameof(scopes)); _scopes = scopes.ToArray(); @@ -45,7 +48,7 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential /// protected override void AuthorizeRequest(HttpMessage message) { - if (tenantId != null || disab) + if (tenantId != null || DisableTenantDiscovery) { base.AuthorizeRequest(message); } @@ -54,11 +57,11 @@ protected override void AuthorizeRequest(HttpMessage message) /// protected override ValueTask AuthorizeRequestAsync(HttpMessage message) { - return tenantId switch + if (tenantId != null || DisableTenantDiscovery) { - null => default, - _ => base.AuthorizeRequestAsync(message) - }; + return base.AuthorizeRequestAsync(message); + } + return default; } /// @@ -74,7 +77,7 @@ private async ValueTask AuthorizeRequestOnChallengeInternalAsync(HttpMessa var authUri = AuthorizationChallengeParser.GetChallengeParameterFromResponse(message.Response, "Bearer", "authorization_uri"); // tenantId should be the guid as seen in this example: https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize - tenantId = new Uri(authUri).Segments[1].Trim('/'); + tenantId = new Uri(authUri).Segments[1].Trim('/'); var context = new TokenRequestContext( new TokenRequestContextOptions { Scopes = _scopes, TenantIdHint = tenantId, ParentRequestId = message.Request.ClientRequestId }); diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs index b2a143687b4dc..d8402d8204057 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs @@ -113,6 +113,11 @@ public DataLakeClientOptions(ServiceVersion version = LatestVersion) /// public Uri GeoRedundantSecondaryUri { get; set; } + /// + /// Disables tenant discovery for the storage service. + /// + public bool DisableTenantDiscovery { get; set; } + /// /// Add headers and query parameters in and /// diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs index 938a5b33f1069..6f4a13f1e24e6 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs @@ -471,6 +471,10 @@ internal DataLakePathClient( _uri = pathUri; _blobUri = uriBuilder.ToBlobUri(); _dfsUri = uriBuilder.ToDfsUri(); + if (authentication is StorageBearerTokenChallengeAuthorizationPolicy policy) + { + policy.DisableTenantDiscovery = options.DisableTenantDiscovery; + } _clientConfiguration = new DataLakeClientConfiguration( pipeline: options.Build(authentication), diff --git a/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs b/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs index 17674b98d3cb0..56b3fc2cb1806 100644 --- a/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs +++ b/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs @@ -13,6 +13,7 @@ using Azure.Storage.Queues.Models; using Azure.Storage.Queues.Specialized; using Azure.Storage.Sas; +using Azure.Storage.Shared; using Metadata = System.Collections.Generic.IDictionary; #pragma warning disable SA1402 // File may only contain a single type @@ -338,6 +339,10 @@ internal QueueClient( _uri = queueUri; _messagesUri = queueUri.AppendToPath(Constants.Queue.MessagesUri); options ??= new QueueClientOptions(); + if (authentication is StorageBearerTokenChallengeAuthorizationPolicy policy) + { + policy.DisableTenantDiscovery = options.DisableTenantDiscovery; + } _clientConfiguration = new QueueClientConfiguration( pipeline: options.Build(authentication), sharedKeyCredential: storageSharedKeyCredential, diff --git a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs index da0088d2f7f7e..656c2d37b59b3 100644 --- a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs +++ b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs @@ -123,6 +123,11 @@ public QueueClientOptions(ServiceVersion version = LatestVersion) /// public QueueMessageEncoding MessageEncoding { get; set; } = QueueMessageEncoding.None; + /// + /// Disables tenant discovery for the storage service. + /// + public bool DisableTenantDiscovery { get; set; } + /// /// Optional. Performs the tasks needed when a message is received or peaked from the queue but cannot be decoded. /// From c7ce340c71066687b86f6095af9617e0f18c1be3 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Fri, 14 May 2021 10:48:32 -0500 Subject: [PATCH 04/39] introduce IBearerTokenChallengeOptions --- .../src/Shared/IBearerTokenChallengeOptions.cs | 17 +++++++++++++++++ .../src/Azure.Storage.Blobs.csproj | 1 + .../src/BlobClientOptions.cs | 10 ++++------ .../src/Azure.Storage.Common.csproj | 1 + ...geBearerTokenChallengeAuthorizationPolicy.cs | 10 ++++------ .../src/Azure.Storage.Files.DataLake.csproj | 1 + .../src/DataLakeClientOptions.cs | 6 ++---- .../src/Azure.Storage.Queues.csproj | 1 + .../src/QueueClientOptions.cs | 6 ++---- 9 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs diff --git a/sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs b/sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs new file mode 100644 index 0000000000000..5cd5d7859e25d --- /dev/null +++ b/sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Core +{ + /// + /// Options to be exposed in client options classes related to bearer token authorization challenge scenarios. + /// + internal interface IBearerTokenChallengeOptions + { + /// + /// Disables tenant discovery through the authorization challenge when the client is configured to use a . + /// When disabled, the client will not attempt an initial un-authroized request to prompt a challenge in order to discover the correct tenant for the resource. + /// + public bool DisableTenantDiscovery { get; set; } + } +} diff --git a/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj b/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj index 9fc5c52c3cd2a..97c4cbe1ef4e3 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj +++ b/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj @@ -39,6 +39,7 @@ + diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs index 01b36f10e6475..bbbf9b61b9ee5 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs @@ -12,7 +12,7 @@ namespace Azure.Storage.Blobs /// Provides the client configuration options for connecting to Azure Blob /// Storage. /// - public class BlobClientOptions : ClientOptions + public class BlobClientOptions : ClientOptions, IBearerTokenChallengeOptions { /// /// The Latest service version supported by this client library. @@ -87,11 +87,6 @@ public enum ServiceVersion /// public string EncryptionScope { get; set; } - /// - /// Disables discovery of the tenant Id related to the storage service. - /// - public bool DisableTenantDiscovery { get; set; } - /// /// Gets or sets the secondary storage that can be read from for the storage account if the /// account is enabled for RA-GRS. @@ -267,5 +262,8 @@ internal HttpPipeline Build(object credentials) { return this.Build(credentials, GeoRedundantSecondaryUri); } + + /// + public bool DisableTenantDiscovery { get; set; } } } diff --git a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj index 7bb0edfe9d618..b1fdc0e8a7339 100644 --- a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj +++ b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj @@ -28,6 +28,7 @@ + diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index 18b93209466b4..ac386949788b4 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -13,16 +13,14 @@ namespace Azure.Storage.Shared /// /// /// - public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy + public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy, IBearerTokenChallengeOptions { - /// - /// Disables tenant discovery through challenge responses. - /// - public bool DisableTenantDiscovery { get; set; } - private readonly string[] _scopes; private volatile string tenantId; + /// + public bool DisableTenantDiscovery { get; set; } + /// /// /// diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj b/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj index 7e033427a6a3e..41061532148aa 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj @@ -37,6 +37,7 @@ + diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs index d8402d8204057..f44a3cba084ad 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs @@ -12,7 +12,7 @@ namespace Azure.Storage.Files.DataLake /// /// Provides the client configuration options for connecting to Azure Data Lake service. /// - public class DataLakeClientOptions : ClientOptions + public class DataLakeClientOptions : ClientOptions, IBearerTokenChallengeOptions { /// /// The Latest service version supported by this client library. @@ -113,9 +113,7 @@ public DataLakeClientOptions(ServiceVersion version = LatestVersion) /// public Uri GeoRedundantSecondaryUri { get; set; } - /// - /// Disables tenant discovery for the storage service. - /// + /// public bool DisableTenantDiscovery { get; set; } /// diff --git a/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj b/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj index 667c63db61cb9..2992b05504d78 100644 --- a/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj +++ b/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj @@ -39,6 +39,7 @@ + diff --git a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs index 656c2d37b59b3..81ed95cb4b02f 100644 --- a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs +++ b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs @@ -13,7 +13,7 @@ namespace Azure.Storage.Queues /// Provides the client configuration options for connecting to Azure Queue /// Storage /// - public class QueueClientOptions : ClientOptions + public class QueueClientOptions : ClientOptions, IBearerTokenChallengeOptions { /// /// The Latest service version supported by this client library. @@ -123,9 +123,7 @@ public QueueClientOptions(ServiceVersion version = LatestVersion) /// public QueueMessageEncoding MessageEncoding { get; set; } = QueueMessageEncoding.None; - /// - /// Disables tenant discovery for the storage service. - /// + /// public bool DisableTenantDiscovery { get; set; } /// From 4f01bb6a13870f459daa7a4d3b523283b666f49f Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 18 May 2021 18:49:18 -0500 Subject: [PATCH 05/39] refactor StorageBearerTokenChallengeAuthorizationPolicy ctor --- .../src/Shared/IBearerTokenChallengeOptions.cs | 2 +- .../api/Azure.Storage.Blobs.netstandard2.0.cs | 1 + .../Azure.Storage.Blobs/src/BlobBaseClient.cs | 6 +----- .../Azure.Storage.Blobs/src/BlobContainerClient.cs | 6 +----- .../Azure.Storage.Blobs/src/BlobServiceClient.cs | 2 +- .../api/Azure.Storage.Common.netstandard2.0.cs | 13 +++++++++++++ ...torageBearerTokenChallengeAuthorizationPolicy.cs | 10 +++++++--- .../src/Shared/StorageClientOptions.cs | 11 +++++++---- .../Azure.Storage.Files.DataLake.netstandard2.0.cs | 1 + .../src/DataLakeDirectoryClient.cs | 4 ++-- .../src/DataLakeFileClient.cs | 4 ++-- .../src/DataLakeFileSystemClient.cs | 4 ++-- .../src/DataLakePathClient.cs | 8 ++------ .../src/DataLakeServiceClient.cs | 4 ++-- .../src/Azure.Storage.Files.Shares.csproj | 1 + .../api/Azure.Storage.Queues.netstandard2.0.cs | 1 + sdk/storage/Azure.Storage.Queues/src/QueueClient.cs | 6 +----- .../Azure.Storage.Queues/src/QueueServiceClient.cs | 2 +- 18 files changed, 47 insertions(+), 39 deletions(-) diff --git a/sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs b/sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs index 5cd5d7859e25d..1f4eedf518c08 100644 --- a/sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs +++ b/sdk/core/Azure.Core/src/Shared/IBearerTokenChallengeOptions.cs @@ -12,6 +12,6 @@ internal interface IBearerTokenChallengeOptions /// Disables tenant discovery through the authorization challenge when the client is configured to use a . /// When disabled, the client will not attempt an initial un-authroized request to prompt a challenge in order to discover the correct tenant for the resource. /// - public bool DisableTenantDiscovery { get; set; } + public bool DisableTenantDiscovery { get; } } } diff --git a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs index d8d9d778b33b2..2292bd9271932 100644 --- a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs @@ -51,6 +51,7 @@ public partial class BlobClientOptions : Azure.Core.ClientOptions { public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion version = Azure.Storage.Blobs.BlobClientOptions.ServiceVersion.V2020_08_04) { } public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } } + public bool DisableTenantDiscovery { get { throw null; } set { } } public string EncryptionScope { get { throw null; } set { } } public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } } public Azure.Storage.Blobs.BlobClientOptions.ServiceVersion Version { get { throw null; } } diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs index d48c84f29ca97..8889ae33bc70e 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs @@ -318,7 +318,7 @@ public BlobBaseClient(Uri blobUri, AzureSasCredential credential, BlobClientOpti /// every request. /// public BlobBaseClient(Uri blobUri, TokenCredential credential, BlobClientOptions options = default) - : this(blobUri, credential.AsPolicy(), options, null) + : this(blobUri, credential.AsPolicy(options), options, null) { _blobRestClient = BuildBlobRestClient(blobUri); Errors.VerifyHttpsTokenAuth(blobUri); @@ -366,10 +366,6 @@ internal BlobBaseClient( _blobVersionId = System.Web.HttpUtility.ParseQueryString(blobUri.Query).Get(Constants.VersionIdParameterName); } } - if (authentication is StorageBearerTokenChallengeAuthorizationPolicy policy) - { - policy.DisableTenantDiscovery = options.DisableTenantDiscovery; - } _clientConfiguration = new BlobClientConfiguration( pipeline: options.Build(authentication), diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs index 814be72beab9d..12f39653d2710 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs @@ -200,10 +200,6 @@ public BlobContainerClient(string connectionString, string blobContainerName, Bl encryptionScope: options.EncryptionScope); _authenticationPolicy = StorageClientOptions.GetAuthenticationPolicy(conn.Credentials); - if (_authenticationPolicy is StorageBearerTokenChallengeAuthorizationPolicy policy) - { - policy.DisableTenantDiscovery = options.DisableTenantDiscovery; - } _containerRestClient = BuildContainerRestClient(_uri); BlobErrors.VerifyHttpsCustomerProvidedKey(_uri, _clientConfiguration.CustomerProvidedKey); @@ -313,7 +309,7 @@ public BlobContainerClient(Uri blobContainerUri, AzureSasCredential credential, /// every request. /// public BlobContainerClient(Uri blobContainerUri, TokenCredential credential, BlobClientOptions options = default) - : this(blobContainerUri, credential.AsPolicy(), options) + : this(blobContainerUri, credential.AsPolicy(options), options) { Errors.VerifyHttpsTokenAuth(blobContainerUri); } diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobServiceClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobServiceClient.cs index ae904bdb54e75..cd59f604f1fc5 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobServiceClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobServiceClient.cs @@ -244,7 +244,7 @@ public BlobServiceClient(Uri serviceUri, AzureSasCredential credential, BlobClie /// every request. /// public BlobServiceClient(Uri serviceUri, TokenCredential credential, BlobClientOptions options = default) - : this(serviceUri, credential.AsPolicy(), options ?? new BlobClientOptions()) + : this(serviceUri, credential.AsPolicy(options), options ?? new BlobClientOptions()) { Errors.VerifyHttpsTokenAuth(serviceUri); } diff --git a/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs b/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs index 88bc1ef7dfa14..64c0f558ad7da 100644 --- a/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs @@ -164,3 +164,16 @@ protected internal void AppendProperties(System.Text.StringBuilder stringBuilder public override string ToString() { throw null; } } } +namespace Azure.Storage.Shared +{ + public partial class StorageBearerTokenChallengeAuthorizationPolicy : Azure.Core.Pipeline.BearerTokenAuthenticationPolicy + { + public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, System.Collections.Generic.IEnumerable scopes, bool disableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } + public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, string scope, bool disableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } + public bool DisableTenantDiscovery { get { throw null; } } + protected override void AuthorizeRequest(Azure.Core.HttpMessage message) { } + protected override System.Threading.Tasks.ValueTask AuthorizeRequestAsync(Azure.Core.HttpMessage message) { throw null; } + protected override bool AuthorizeRequestOnChallenge(Azure.Core.HttpMessage message) { throw null; } + protected override System.Threading.Tasks.ValueTask AuthorizeRequestOnChallengeAsync(Azure.Core.HttpMessage message) { throw null; } + } +} diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index ac386949788b4..b1336016835f8 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -19,17 +19,19 @@ public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthent private volatile string tenantId; /// - public bool DisableTenantDiscovery { get; set; } + public bool DisableTenantDiscovery { get; } /// /// /// /// /// - public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, string scope) : base(credential, scope) + /// + public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, string scope, bool disableTenantDiscovery) : base(credential, scope) { Argument.AssertNotNullOrEmpty(scope, nameof(scope)); _scopes = new[] { scope }; + DisableTenantDiscovery = disableTenantDiscovery; } /// @@ -37,10 +39,12 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential /// /// /// - public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, IEnumerable scopes) : base(credential, scopes) + /// + public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential, IEnumerable scopes, bool disableTenantDiscovery) : base(credential, scopes) { Argument.AssertNotNull(scopes, nameof(scopes)); _scopes = scopes.ToArray(); + DisableTenantDiscovery = disableTenantDiscovery; } /// diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs index a0846fbf7370f..0cc9af8a95e03 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs @@ -66,18 +66,21 @@ public static HttpPipelinePolicy AsPolicy(this AzureSasCredential c /// Get an authentication policy to sign Storage requests. /// /// Credential to use. + /// The to apply to the credential. /// An authentication policy. - public static HttpPipelinePolicy AsPolicy(this TokenCredential credential) => + public static HttpPipelinePolicy AsPolicy(this TokenCredential credential, ClientOptions options) => new StorageBearerTokenChallengeAuthorizationPolicy( credential ?? throw Errors.ArgumentNull(nameof(credential)), - StorageScope); + StorageScope, + options is not IBearerTokenChallengeOptions btcp || btcp.DisableTenantDiscovery); /// /// Get an optional authentication policy to sign Storage requests. /// /// Optional credentials to use. + /// The /// An optional authentication policy. - public static HttpPipelinePolicy GetAuthenticationPolicy(object credentials = null) + public static HttpPipelinePolicy GetAuthenticationPolicy(object credentials = null, ClientOptions options = null) { // Use the credentials to decide on the authentication policy switch (credentials) @@ -88,7 +91,7 @@ public static HttpPipelinePolicy GetAuthenticationPolicy(object credentials = nu case StorageSharedKeyCredential sharedKey: return sharedKey.AsPolicy(); case TokenCredential token: - return token.AsPolicy(); + return token.AsPolicy(options); default: throw Errors.InvalidCredentials(credentials.GetType().FullName); } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs b/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs index cd060643c9e5e..8f76c4abda8e6 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs @@ -3,6 +3,7 @@ namespace Azure.Storage.Files.DataLake public partial class DataLakeClientOptions : Azure.Core.ClientOptions { public DataLakeClientOptions(Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion version = Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion.V2020_08_04) { } + public bool DisableTenantDiscovery { get { throw null; } set { } } public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } } public Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion Version { get { throw null; } } public enum ServiceVersion diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeDirectoryClient.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeDirectoryClient.cs index 704213c677519..8ea46fe23ec17 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeDirectoryClient.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeDirectoryClient.cs @@ -218,7 +218,7 @@ public DataLakeDirectoryClient(Uri directoryUri, AzureSasCredential credential, /// The token credential used to sign requests. /// public DataLakeDirectoryClient(Uri directoryUri, TokenCredential credential) - : this(directoryUri, credential.AsPolicy(), null, null) + : this(directoryUri, credential.AsPolicy(new DataLakeClientOptions()), null, null) { Errors.VerifyHttpsTokenAuth(directoryUri); } @@ -241,7 +241,7 @@ public DataLakeDirectoryClient(Uri directoryUri, TokenCredential credential) /// every request. /// public DataLakeDirectoryClient(Uri directoryUri, TokenCredential credential, DataLakeClientOptions options) - : this(directoryUri, credential.AsPolicy(), options, null) + : this(directoryUri, credential.AsPolicy(options), options, null) { Errors.VerifyHttpsTokenAuth(directoryUri); } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileClient.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileClient.cs index 1c5658509f2b3..15743d613a883 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileClient.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileClient.cs @@ -236,7 +236,7 @@ public DataLakeFileClient(Uri fileUri, AzureSasCredential credential, DataLakeCl /// The token credential used to sign requests. /// public DataLakeFileClient(Uri fileUri, TokenCredential credential) - : this(fileUri, credential.AsPolicy(), null, null) + : this(fileUri, credential.AsPolicy(new DataLakeClientOptions()), null, null) { Errors.VerifyHttpsTokenAuth(fileUri); } @@ -258,7 +258,7 @@ public DataLakeFileClient(Uri fileUri, TokenCredential credential) /// applied to every request. /// public DataLakeFileClient(Uri fileUri, TokenCredential credential, DataLakeClientOptions options) - : this(fileUri, credential.AsPolicy(), options, null) + : this(fileUri, credential.AsPolicy(options), options, null) { Errors.VerifyHttpsTokenAuth(fileUri); } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileSystemClient.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileSystemClient.cs index 7a9312c262e0c..de80f520dc2ab 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileSystemClient.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeFileSystemClient.cs @@ -330,7 +330,7 @@ public DataLakeFileSystemClient(Uri fileSystemUri, AzureSasCredential credential /// The token credential used to sign requests. /// public DataLakeFileSystemClient(Uri fileSystemUri, TokenCredential credential) - : this(fileSystemUri, credential.AsPolicy(), null, null) + : this(fileSystemUri, credential.AsPolicy(new DataLakeClientOptions()), null, null) { Errors.VerifyHttpsTokenAuth(fileSystemUri); } @@ -352,7 +352,7 @@ public DataLakeFileSystemClient(Uri fileSystemUri, TokenCredential credential) /// every request. /// public DataLakeFileSystemClient(Uri fileSystemUri, TokenCredential credential, DataLakeClientOptions options) - : this(fileSystemUri, credential.AsPolicy(), options, null) + : this(fileSystemUri, credential.AsPolicy(options), options, null) { Errors.VerifyHttpsTokenAuth(fileSystemUri); } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs index 6f4a13f1e24e6..7031214ce7538 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakePathClient.cs @@ -399,7 +399,7 @@ public DataLakePathClient(Uri pathUri, AzureSasCredential credential, DataLakeCl /// The token credential used to sign requests. /// public DataLakePathClient(Uri pathUri, TokenCredential credential) - : this(pathUri, credential.AsPolicy(), null, null) + : this(pathUri, credential.AsPolicy(new DataLakeClientOptions()), null, null) { Errors.VerifyHttpsTokenAuth(pathUri); } @@ -422,7 +422,7 @@ public DataLakePathClient(Uri pathUri, TokenCredential credential) /// every request. /// public DataLakePathClient(Uri pathUri, TokenCredential credential, DataLakeClientOptions options) - : this(pathUri, credential.AsPolicy(), options, null) + : this(pathUri, credential.AsPolicy(options), options, null) { Errors.VerifyHttpsTokenAuth(pathUri); } @@ -471,10 +471,6 @@ internal DataLakePathClient( _uri = pathUri; _blobUri = uriBuilder.ToBlobUri(); _dfsUri = uriBuilder.ToDfsUri(); - if (authentication is StorageBearerTokenChallengeAuthorizationPolicy policy) - { - policy.DisableTenantDiscovery = options.DisableTenantDiscovery; - } _clientConfiguration = new DataLakeClientConfiguration( pipeline: options.Build(authentication), diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeServiceClient.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeServiceClient.cs index fa8c1f8832942..90f9dd2ce5402 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeServiceClient.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeServiceClient.cs @@ -262,7 +262,7 @@ public DataLakeServiceClient(Uri serviceUri, AzureSasCredential credential, Data /// The token credential used to sign requests. /// public DataLakeServiceClient(Uri serviceUri, TokenCredential credential) - : this(serviceUri, credential.AsPolicy(), null, null) + : this(serviceUri, credential.AsPolicy(new DataLakeClientOptions()), null, null) { Errors.VerifyHttpsTokenAuth(serviceUri); } @@ -283,7 +283,7 @@ public DataLakeServiceClient(Uri serviceUri, TokenCredential credential) /// every request. /// public DataLakeServiceClient(Uri serviceUri, TokenCredential credential, DataLakeClientOptions options) - : this(serviceUri, credential.AsPolicy(), options, null) + : this(serviceUri, credential.AsPolicy(options), options, null) { Errors.VerifyHttpsTokenAuth(serviceUri); } diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj b/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj index 653842e93df96..d4d5f0071029b 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj +++ b/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj @@ -35,6 +35,7 @@ + diff --git a/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs b/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs index b817aef8a7946..98852b107a499 100644 --- a/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs @@ -71,6 +71,7 @@ public QueueClient(System.Uri queueUri, Azure.Storage.StorageSharedKeyCredential public partial class QueueClientOptions : Azure.Core.ClientOptions { public QueueClientOptions(Azure.Storage.Queues.QueueClientOptions.ServiceVersion version = Azure.Storage.Queues.QueueClientOptions.ServiceVersion.V2020_08_04) { } + public bool DisableTenantDiscovery { get { throw null; } set { } } public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } } public Azure.Storage.Queues.QueueMessageEncoding MessageEncoding { get { throw null; } set { } } public Azure.Storage.Queues.QueueClientOptions.ServiceVersion Version { get { throw null; } } diff --git a/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs b/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs index 56b3fc2cb1806..a142334760a72 100644 --- a/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs +++ b/sdk/storage/Azure.Storage.Queues/src/QueueClient.cs @@ -304,7 +304,7 @@ public QueueClient(Uri queueUri, AzureSasCredential credential, QueueClientOptio /// every request. /// public QueueClient(Uri queueUri, TokenCredential credential, QueueClientOptions options = default) - : this(queueUri, credential.AsPolicy(), options, null) + : this(queueUri, credential.AsPolicy(options), options, null) { Errors.VerifyHttpsTokenAuth(queueUri); } @@ -339,10 +339,6 @@ internal QueueClient( _uri = queueUri; _messagesUri = queueUri.AppendToPath(Constants.Queue.MessagesUri); options ??= new QueueClientOptions(); - if (authentication is StorageBearerTokenChallengeAuthorizationPolicy policy) - { - policy.DisableTenantDiscovery = options.DisableTenantDiscovery; - } _clientConfiguration = new QueueClientConfiguration( pipeline: options.Build(authentication), sharedKeyCredential: storageSharedKeyCredential, diff --git a/sdk/storage/Azure.Storage.Queues/src/QueueServiceClient.cs b/sdk/storage/Azure.Storage.Queues/src/QueueServiceClient.cs index 12f29ac8f8265..06609600299c3 100644 --- a/sdk/storage/Azure.Storage.Queues/src/QueueServiceClient.cs +++ b/sdk/storage/Azure.Storage.Queues/src/QueueServiceClient.cs @@ -220,7 +220,7 @@ public QueueServiceClient(Uri serviceUri, AzureSasCredential credential, QueueCl /// every request. /// public QueueServiceClient(Uri serviceUri, TokenCredential credential, QueueClientOptions options = default) - : this(serviceUri, credential.AsPolicy(), options, null) + : this(serviceUri, credential.AsPolicy(options), options, null) { Errors.VerifyHttpsTokenAuth(serviceUri); } From 437237b3e7c019a8aee3052e3b3f3f95a3cdb82f Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 24 May 2021 18:09:50 -0500 Subject: [PATCH 06/39] needs more tests --- .../src/AuthorizationCodeCredential.cs | 52 +++-- .../Azure.Identity/src/AzureCliCredential.cs | 17 +- .../src/AzurePowerShellCredential.cs | 20 +- .../src/ClientCertificateCredential.cs | 10 +- .../src/ClientSecretCredential.cs | 16 +- .../src/DeviceCodeCredential.cs | 23 +- .../src/EnvironmentCredential.cs | 8 +- .../src/InteractiveBrowserCredential.cs | 18 +- .../src/MsalConfidentialClient.cs | 74 ++++++- .../Azure.Identity/src/MsalPublicClient.cs | 23 +- .../Azure.Identity/src/TenantIdResolver.cs | 26 +++ .../src/TokenCredentialOptions.cs | 6 + .../src/UsernamePasswordCredential.cs | 30 +-- .../src/VisualStudioCodeCredential.cs | 15 +- .../src/VisualStudioCredential.cs | 9 +- .../tests/AuthorizationCodeCredentialTests.cs | 201 +++--------------- .../tests/AzureIdentityEventSourceTests.cs | 12 +- .../InteractiveBrowserCredentialTests.cs | 14 +- .../tests/Mock/MockMsalConfidentialClient.cs | 48 ++++- .../tests/Mock/MockMsalPublicClient.cs | 34 +-- .../tests/SharedTokenCacheCredentialTests.cs | 34 +-- .../tests/UsernamePasswordCredentialTests.cs | 2 +- 22 files changed, 363 insertions(+), 329 deletions(-) create mode 100644 sdk/identity/Azure.Identity/src/TenantIdResolver.cs diff --git a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs index ee68e7ee77611..42e1525f68fd2 100644 --- a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Diagnostics; using Azure.Core.Pipeline; using Microsoft.Identity.Client; @@ -18,12 +17,13 @@ namespace Azure.Identity /// public class AuthorizationCodeCredential : TokenCredential { - private readonly IConfidentialClientApplication _confidentialClient; - private readonly ClientDiagnostics _clientDiagnostics; private readonly string _authCode; private readonly string _clientId; private readonly CredentialPipeline _pipeline; private AuthenticationRecord _record; + private readonly string _tenantId; + private readonly TokenCredentialOptions _options; + private readonly MsalConfidentialClient _client; /// /// Protected constructor for mocking. @@ -55,26 +55,26 @@ public AuthorizationCodeCredential(string tenantId, string clientId, string clie /// See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow for more information. /// Options that allow to configure the management of the requests sent to the Azure Active Directory service. public AuthorizationCodeCredential(string tenantId, string clientId, string clientSecret, string authorizationCode, TokenCredentialOptions options) - { - Validations.ValidateTenantId(tenantId, nameof(tenantId)); - - if (clientSecret is null) throw new ArgumentNullException(nameof(clientSecret)); - - _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - - _authCode = authorizationCode ?? throw new ArgumentNullException(nameof(authorizationCode)); + : this(tenantId, clientId, clientSecret, authorizationCode, options, null) + {} - options ??= new TokenCredentialOptions(); - - _pipeline = CredentialPipeline.GetInstance(options); - - _confidentialClient = ConfidentialClientApplicationBuilder.Create(clientId).WithHttpClientFactory(new HttpPipelineClientFactory(_pipeline.HttpPipeline)).WithTenantId(tenantId).WithClientSecret(clientSecret).Build(); - - _clientDiagnostics = new ClientDiagnostics(options); + internal AuthorizationCodeCredential(string tenantId, string clientId, string clientSecret, string authorizationCode, TokenCredentialOptions options, MsalConfidentialClient client) + { + Argument.AssertNotNull(clientSecret, nameof(clientSecret)); + Argument.AssertNotNull(clientId, nameof(clientId)); + Argument.AssertNotNull(authorizationCode, nameof(authorizationCode)); + _tenantId = Validations.ValidateTenantId(tenantId, nameof(tenantId)); + _clientId = clientId; + _authCode = authorizationCode ; + _options = options ?? new TokenCredentialOptions(); + _pipeline = CredentialPipeline.GetInstance(_options); + + _client = client ?? new MsalConfidentialClient(_pipeline, tenantId, clientId, clientSecret, options as ITokenCacheOptions); } /// - /// Obtains a token from the Azure Active Directory service, using the specified authorization code authenticate. This method is called automatically by Azure SDK client libraries. You may call this method directly, but you must also handle token caching and token refreshing. + /// Obtains a token from the Azure Active Directory service, using the specified authorization code authenticate. This method is called automatically by + /// Azure SDK client libraries. You may call this method directly, but you must also handle token caching and token refreshing. /// /// The details of the authentication request. /// A controlling the request lifetime. @@ -85,7 +85,8 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell } /// - /// Obtains a token from the Azure Active Directory service, using the specified authorization code authenticate. This method is called automatically by Azure SDK client libraries. You may call this method directly, but you must also handle token caching and token refreshing. + /// Obtains a token from the Azure Active Directory service, using the specified authorization code authenticate. This method is called automatically by + /// Azure SDK client libraries. You may call this method directly, but you must also handle token caching and token refreshing. /// /// The details of the authentication request. /// A controlling the request lifetime. @@ -101,11 +102,14 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC try { - AccessToken token = default; + AccessToken token; + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); if (_record is null) { - AuthenticationResult result = await _confidentialClient.AcquireTokenByAuthorizationCode(requestContext.Scopes, _authCode).ExecuteAsync(async, cancellationToken).ConfigureAwait(false); + AuthenticationResult result = await _client + .AcquireTokenByAuthorizationCodeAsync(requestContext.Scopes, tenantId, _authCode, async, cancellationToken) + .ConfigureAwait(false); _record = new AuthenticationRecord(result, _clientId); @@ -113,7 +117,9 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC } else { - AuthenticationResult result = await _confidentialClient.AcquireTokenSilent(requestContext.Scopes, (AuthenticationAccount)_record).ExecuteAsync(async, cancellationToken).ConfigureAwait(false); + AuthenticationResult result = await _client + .AcquireTokenSilentAsync(requestContext.Scopes, (AuthenticationAccount)_record, tenantId, async, cancellationToken) + .ConfigureAwait(false); token = new AccessToken(result.AccessToken, result.ExpiresOn); } diff --git a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs index adbe2daf44108..1faef65f4df43 100644 --- a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs @@ -86,7 +86,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC try { - AccessToken token = await RequestCliAccessTokenAsync(async, requestContext.Scopes, cancellationToken).ConfigureAwait(false); + AccessToken token = await RequestCliAccessTokenAsync(async, requestContext, cancellationToken).ConfigureAwait(false); return scope.Succeeded(token); } catch (Exception e) @@ -95,13 +95,14 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC } } - private async ValueTask RequestCliAccessTokenAsync(bool async, string[] scopes, CancellationToken cancellationToken) + private async ValueTask RequestCliAccessTokenAsync(bool async, TokenRequestContext context, CancellationToken cancellationToken) { - string resource = ScopeUtilities.ScopesToResource(scopes); + string resource = ScopeUtilities.ScopesToResource(context.Scopes); + string tenantId = TenantIdResolver.Resolve(null, context, null); ScopeUtilities.ValidateScope(resource); - GetFileNameAndArguments(resource, out string fileName, out string argument); + GetFileNameAndArguments(resource, tenantId, out string fileName, out string argument); ProcessStartInfo processStartInfo = GetAzureCliProcessStartInfo(fileName, argument); using var processRunner = new ProcessRunner(_processService.Create(processStartInfo), TimeSpan.FromMilliseconds(CliProcessTimeoutMs), cancellationToken); @@ -157,9 +158,13 @@ private ProcessStartInfo GetAzureCliProcessStartInfo(string fileName, string arg Environment = { { "PATH", _path } } }; - private static void GetFileNameAndArguments(string resource, out string fileName, out string argument) + private static void GetFileNameAndArguments(string resource, string tenantId, out string fileName, out string argument) { - string command = $"az account get-access-token --output json --resource {resource}"; + string command = tenantId switch + { + null => $"az account get-access-token --output json --resource {resource}", + _ => $"az account get-access-token --output json --resource {resource} -tenant {tenantId}" + }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { diff --git a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs index b58ded6a662ae..fd677c66dbc7a 100644 --- a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs @@ -40,6 +40,8 @@ public class AzurePowerShellCredential : TokenCredential private static readonly string DefaultWorkingDir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DefaultWorkingDirWindows : DefaultWorkingDirNonWindows; + private readonly AzurePowerShellCredentialOptions _options; + private const int ERROR_FILE_NOT_FOUND = 2; /// @@ -59,6 +61,7 @@ public AzurePowerShellCredential(AzurePowerShellCredentialOptions options) : thi internal AzurePowerShellCredential(AzurePowerShellCredentialOptions options, CredentialPipeline pipeline, IProcessService processService) { UseLegacyPowerShell = false; + _options = options; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _processService = processService ?? ProcessService.Default; } @@ -91,7 +94,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC try { - AccessToken token = await RequestAzurePowerShellAccessTokenAsync(async, requestContext.Scopes, cancellationToken).ConfigureAwait(false); + AccessToken token = await RequestAzurePowerShellAccessTokenAsync(async, requestContext, cancellationToken).ConfigureAwait(false); return scope.Succeeded(token); } catch (Win32Exception ex) when (ex.NativeErrorCode == ERROR_FILE_NOT_FOUND && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -99,7 +102,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC UseLegacyPowerShell = true; try { - AccessToken token = await RequestAzurePowerShellAccessTokenAsync(async, requestContext.Scopes, cancellationToken).ConfigureAwait(false); + AccessToken token = await RequestAzurePowerShellAccessTokenAsync(async, requestContext, cancellationToken).ConfigureAwait(false); return scope.Succeeded(token); } catch (Exception e) @@ -113,13 +116,14 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC } } - private async ValueTask RequestAzurePowerShellAccessTokenAsync(bool async, string[] scopes, CancellationToken cancellationToken) + private async ValueTask RequestAzurePowerShellAccessTokenAsync(bool async, TokenRequestContext context, CancellationToken cancellationToken) { - string resource = ScopeUtilities.ScopesToResource(scopes); + string resource = ScopeUtilities.ScopesToResource(context.Scopes); ScopeUtilities.ValidateScope(resource); + var tenantId = TenantIdResolver.Resolve(null, context, _options); - GetFileNameAndArguments(resource, out string fileName, out string argument); + GetFileNameAndArguments(resource, tenantId, out string fileName, out string argument); ProcessStartInfo processStartInfo = GetAzurePowerShellProcessStartInfo(fileName, argument); using var processRunner = new ProcessRunner( _processService.Create(processStartInfo), @@ -185,7 +189,7 @@ private static ProcessStartInfo GetAzurePowerShellProcessStartInfo(string fileNa WorkingDirectory = DefaultWorkingDir }; - private void GetFileNameAndArguments(string resource, out string fileName, out string argument) + private void GetFileNameAndArguments(string resource, string tenantId, out string fileName, out string argument) { string powershellExe = "pwsh -NonInteractive -EncodedCommand"; @@ -194,6 +198,8 @@ private void GetFileNameAndArguments(string resource, out string fileName, out s powershellExe = "powershell -NonInteractive -EncodedCommand"; } + var tenantIdArg = tenantId == null ? string.Empty : $" -TenantId {tenantId}"; + string command = @$" $ErrorActionPreference = 'Stop' [version]$minimumVersion = '2.2.0' @@ -205,7 +211,7 @@ private void GetFileNameAndArguments(string resource, out string fileName, out s exit }} -$token = Get-AzAccessToken -ResourceUrl '{resource}' +$token = Get-AzAccessToken -ResourceUrl '{resource}'{tenantIdArg} $x = $token | ConvertTo-Xml return $x.Objects.FirstChild.OuterXml diff --git a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs index decc53e6409a8..b39e9bcac75e8 100644 --- a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs @@ -34,6 +34,7 @@ public class ClientCertificateCredential : TokenCredential private readonly MsalConfidentialClient _client; private readonly CredentialPipeline _pipeline; + private readonly TokenCredentialOptions _options; /// /// Protected constructor for mocking. @@ -126,6 +127,7 @@ internal ClientCertificateCredential(string tenantId, string clientId, IX509Cert ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); ClientCertificateProvider = certificateProvider; + _options = options; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); @@ -144,7 +146,8 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell try { - AuthenticationResult result = _client.AcquireTokenForClientAsync(requestContext.Scopes, false, cancellationToken).EnsureCompleted(); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + AuthenticationResult result = _client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, false, cancellationToken).EnsureCompleted(); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } @@ -166,7 +169,10 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r try { - AuthenticationResult result = await _client.AcquireTokenForClientAsync(requestContext.Scopes, true, cancellationToken).ConfigureAwait(false); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + AuthenticationResult result = await _client + .AcquireTokenForClientAsync(requestContext.Scopes, tenantId, true, cancellationToken) + .ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } diff --git a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs index 05d342f7dfefc..3cf6dcd2486ed 100644 --- a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs @@ -5,6 +5,7 @@ using Azure.Core.Pipeline; using Microsoft.Identity.Client; using System; +using System.Runtime.InteropServices.ComTypes; using System.Threading; using System.Threading.Tasks; @@ -19,6 +20,7 @@ public class ClientSecretCredential : TokenCredential { private readonly MsalConfidentialClient _client; private readonly CredentialPipeline _pipeline; + private readonly TokenCredentialOptions _options; /// /// Gets the Azure Active Directory tenant (directory) Id of the service principal @@ -79,14 +81,14 @@ public ClientSecretCredential(string tenantId, string clientId, string clientSec internal ClientSecretCredential(string tenantId, string clientId, string clientSecret, TokenCredentialOptions options, CredentialPipeline pipeline, MsalConfidentialClient client) { + Argument.AssertNotNull(clientId, nameof(clientId)); + Argument.AssertNotNull(clientSecret, nameof(clientSecret)); TenantId = Validations.ValidateTenantId(tenantId, nameof(tenantId)); - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - ClientSecret = clientSecret ?? throw new ArgumentNullException(nameof(clientSecret)); - + ClientSecret = clientSecret; + _options = options; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - _client = client ?? new MsalConfidentialClient(_pipeline, tenantId, clientId, clientSecret, options as ITokenCacheOptions); } @@ -102,7 +104,8 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r try { - AuthenticationResult result = await _client.AcquireTokenForClientAsync(requestContext.Scopes, true, cancellationToken).ConfigureAwait(false); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + AuthenticationResult result = await _client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, true, cancellationToken).ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } @@ -124,7 +127,8 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell try { - AuthenticationResult result = _client.AcquireTokenForClientAsync(requestContext.Scopes, false, cancellationToken).EnsureCompleted(); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + AuthenticationResult result = _client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, false, cancellationToken).EnsureCompleted(); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } diff --git a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs index c2d94a43053ee..582b1b463863e 100644 --- a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs @@ -17,6 +17,8 @@ namespace Azure.Identity /// public class DeviceCodeCredential : TokenCredential { + private readonly string _tenantId; + private readonly TokenCredentialOptions _options; internal MsalPublicClient Client { get; } internal string ClientId { get; } internal bool DisableAutomaticAuthentication { get; } @@ -76,16 +78,16 @@ internal DeviceCodeCredential(Func devi internal DeviceCodeCredential(Func deviceCodeCallback, string tenantId, string clientId, TokenCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client) { - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - - DeviceCodeCallback = deviceCodeCallback ?? throw new ArgumentNullException(nameof(deviceCodeCallback)); + Argument.AssertNotNull(clientId, nameof(clientId)); + Argument.AssertNotNull(deviceCodeCallback, nameof(deviceCodeCallback)); + _tenantId = tenantId; + ClientId = clientId; + DeviceCodeCallback = deviceCodeCallback; DisableAutomaticAuthentication = (options as DeviceCodeCredentialOptions)?.DisableAutomaticAuthentication ?? false; - Record = (options as DeviceCodeCredentialOptions)?.AuthenticationRecord; - + _options = options; Pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - Client = client ?? new MsalPublicClient(Pipeline, tenantId, ClientId, AzureAuthorityHosts.GetDeviceCodeRedirectUri(Pipeline.AuthorityHost).ToString(), options as ITokenCacheOptions); } @@ -196,7 +198,10 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC { try { - AuthenticationResult result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, async, cancellationToken).ConfigureAwait(false); + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + AuthenticationResult result = await Client + .AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, async, cancellationToken) + .ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } @@ -221,7 +226,9 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC private async Task GetTokenViaDeviceCodeAsync(TokenRequestContext context, bool async, CancellationToken cancellationToken) { - AuthenticationResult result = await Client.AcquireTokenWithDeviceCodeAsync(context.Scopes, context.Claims, code => DeviceCodeCallbackImpl(code, cancellationToken), async, cancellationToken).ConfigureAwait(false); + AuthenticationResult result = await Client + .AcquireTokenWithDeviceCodeAsync(context.Scopes, context.Claims, code => DeviceCodeCallbackImpl(code, cancellationToken), async, cancellationToken) + .ConfigureAwait(false); Record = new AuthenticationRecord(result, ClientId); diff --git a/sdk/identity/Azure.Identity/src/EnvironmentCredential.cs b/sdk/identity/Azure.Identity/src/EnvironmentCredential.cs index 5810df9138e36..0745141f961c5 100644 --- a/sdk/identity/Azure.Identity/src/EnvironmentCredential.cs +++ b/sdk/identity/Azure.Identity/src/EnvironmentCredential.cs @@ -30,6 +30,7 @@ public class EnvironmentCredential : TokenCredential { private const string UnavailableErrorMessage = "EnvironmentCredential authentication unavailable. Environment variables are not fully configured."; private readonly CredentialPipeline _pipeline; + private readonly TokenCredentialOptions _options; internal TokenCredential Credential { get; } @@ -50,6 +51,7 @@ public EnvironmentCredential() public EnvironmentCredential(TokenCredentialOptions options) : this(CredentialPipeline.GetInstance(options)) { + _options = options; } internal EnvironmentCredential(CredentialPipeline pipeline) @@ -67,15 +69,15 @@ internal EnvironmentCredential(CredentialPipeline pipeline) { if (!string.IsNullOrEmpty(clientSecret)) { - Credential = new ClientSecretCredential(tenantId, clientId, clientSecret, null, _pipeline, null); + Credential = new ClientSecretCredential(tenantId, clientId, clientSecret, _options, _pipeline, null); } else if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) { - Credential = new UsernamePasswordCredential(username, password, tenantId, clientId, null, _pipeline, null); + Credential = new UsernamePasswordCredential(username, password, tenantId, clientId, _options, _pipeline, null); } else if (!string.IsNullOrEmpty(clientCertificatePath)) { - Credential = new ClientCertificateCredential(tenantId, clientId, clientCertificatePath, null, _pipeline, null); + Credential = new ClientCertificateCredential(tenantId, clientId, clientCertificatePath, _options, _pipeline, null); } } } diff --git a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs index b6bc5bee16831..5b0ce4ba5952c 100644 --- a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs +++ b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs @@ -17,6 +17,8 @@ namespace Azure.Identity /// public class InteractiveBrowserCredential : TokenCredential { + private readonly string _tenantId; + private readonly TokenCredentialOptions _options; internal string ClientId { get; } internal MsalPublicClient Client {get;} internal CredentialPipeline Pipeline { get; } @@ -75,12 +77,13 @@ internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCre internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client) { - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + Argument.AssertNotNull(clientId, nameof(clientId)); + ClientId = clientId; + _tenantId = tenantId; + _options = options; Pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - var redirectUrl = (options as InteractiveBrowserCredentialOptions)?.RedirectUri?.AbsoluteUri ?? Constants.DefaultRedirectUrl; - Client = client ?? new MsalPublicClient(Pipeline, tenantId, clientId, redirectUrl, options as ITokenCacheOptions); } @@ -182,7 +185,10 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC { try { - AuthenticationResult result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, async, cancellationToken).ConfigureAwait(false); + var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record.TenantId, requestContext, _options); + AuthenticationResult result = await Client + .AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, async, cancellationToken) + .ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } @@ -207,7 +213,9 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC private async Task GetTokenViaBrowserLoginAsync(TokenRequestContext context, bool async, CancellationToken cancellationToken) { - AuthenticationResult result = await Client.AcquireTokenInteractiveAsync(context.Scopes, context.Claims, Prompt.SelectAccount, async, cancellationToken).ConfigureAwait(false); + AuthenticationResult result = await Client + .AcquireTokenInteractiveAsync(context.Scopes, context.Claims, Prompt.SelectAccount, async, cancellationToken) + .ConfigureAwait(false); Record = new AuthenticationRecord(result, ClientId); diff --git a/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs b/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs index ba070d65f90cb..9dbb5eeabc9b4 100644 --- a/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs +++ b/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensions.Msal; namespace Azure.Identity { @@ -20,9 +19,7 @@ internal class MsalConfidentialClient : MsalClientBase protected MsalConfidentialClient() - : base() - { - } + { } public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, string clientSecret, ITokenCacheOptions cacheOptions) : base(pipeline, tenantId, clientId, cacheOptions) @@ -30,7 +27,13 @@ public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, stri _clientSecret = clientSecret; } - public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, ClientCertificateCredential.IX509Certificate2Provider certificateProvider, bool includeX5CClaimHeader, ITokenCacheOptions cacheOptions) + public MsalConfidentialClient( + CredentialPipeline pipeline, + string tenantId, + string clientId, + ClientCertificateCredential.IX509Certificate2Provider certificateProvider, + bool includeX5CClaimHeader, + ITokenCacheOptions cacheOptions) : base(pipeline, tenantId, clientId, cacheOptions) { _includeX5CClaimHeader = includeX5CClaimHeader; @@ -39,7 +42,9 @@ public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, stri protected override async ValueTask CreateClientAsync(bool async, CancellationToken cancellationToken) { - ConfidentialClientApplicationBuilder confClientBuilder = ConfidentialClientApplicationBuilder.Create(ClientId).WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, TenantId).WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline)); + ConfidentialClientApplicationBuilder confClientBuilder = ConfidentialClientApplicationBuilder.Create(ClientId) + .WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, TenantId) + .WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline)); if (_clientSecret != null) { @@ -55,11 +60,64 @@ protected override async ValueTask CreateClientA return confClientBuilder.Build(); } - public virtual async ValueTask AcquireTokenForClientAsync(string[] scopes, bool async, CancellationToken cancellationToken) + public virtual async ValueTask AcquireTokenForClientAsync( + string[] scopes, + string tenantId, + bool async, + CancellationToken cancellationToken) + { + IConfidentialClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); + + var builder = client + .AcquireTokenForClient(scopes) + .WithSendX5C(_includeX5CClaimHeader); + + if (!string.IsNullOrEmpty(tenantId)) + { + builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId); + } + return await builder + .ExecuteAsync(async, cancellationToken) + .ConfigureAwait(false); + } + + public virtual async ValueTask AcquireTokenSilentAsync( + string[] scopes, + AuthenticationAccount account, + string tenantId, + bool async, + CancellationToken cancellationToken) { IConfidentialClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); - return await client.AcquireTokenForClient(scopes).WithSendX5C(_includeX5CClaimHeader).ExecuteAsync(async, cancellationToken).ConfigureAwait(false); + var builder = client.AcquireTokenSilent(scopes, account); + if (!string.IsNullOrEmpty(tenantId)) + { + builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId); + } + return await builder + .ExecuteAsync(async, cancellationToken) + .ConfigureAwait(false); + } + + public virtual async ValueTask AcquireTokenByAuthorizationCodeAsync( + string[] scopes, + string code, + string tenantId, + bool async, + CancellationToken cancellationToken) + { + IConfidentialClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); + + var builder = client.AcquireTokenByAuthorizationCode(scopes, code); + + if (!string.IsNullOrEmpty(tenantId)) + { + builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId); + } + return await builder + .ExecuteAsync(async, cancellationToken) + .ConfigureAwait(false); } } } diff --git a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs index 157defe8394d8..6d094e572d8b8 100644 --- a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs @@ -81,12 +81,12 @@ protected virtual async ValueTask AcquireTokenSilentCoreAs .ConfigureAwait(false); } - public async ValueTask AcquireTokenSilentAsync(string[] scopes, string claims, AuthenticationRecord record, bool async, CancellationToken cancellationToken) + public async ValueTask AcquireTokenSilentAsync(string[] scopes, string claims, AuthenticationRecord record, string tenantId, bool async, CancellationToken cancellationToken) { - return await AcquireTokenSilentCoreAsync(scopes, claims, record, async, cancellationToken).ConfigureAwait(false); + return await AcquireTokenSilentCoreAsync(scopes, claims, record, tenantId, async, cancellationToken).ConfigureAwait(false); } - protected virtual async ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, AuthenticationRecord record, bool async, CancellationToken cancellationToken) + protected virtual async ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, AuthenticationRecord record, string tenantId, bool async, CancellationToken cancellationToken) { IPublicClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); @@ -134,17 +134,22 @@ protected virtual async ValueTask AcquireTokenInteractiveC .ConfigureAwait(false); } - public async ValueTask AcquireTokenByUsernamePasswordAsync(string[] scopes, string claims, string username, SecureString password, bool async, CancellationToken cancellationToken) + public async ValueTask AcquireTokenByUsernamePasswordAsync(string[] scopes, string claims, string username, SecureString password, string tenantId, bool async, CancellationToken cancellationToken) { - return await AcquireTokenByUsernamePasswordCoreAsync(scopes, claims, username, password, async, cancellationToken).ConfigureAwait(false); + return await AcquireTokenByUsernamePasswordCoreAsync(scopes, claims, username, password, tenantId, async, cancellationToken).ConfigureAwait(false); } - protected virtual async ValueTask AcquireTokenByUsernamePasswordCoreAsync(string[] scopes, string claims, string username, SecureString password, bool async, CancellationToken cancellationToken) + protected virtual async ValueTask AcquireTokenByUsernamePasswordCoreAsync(string[] scopes, string claims, string username, SecureString password, string tenantId, bool async, CancellationToken cancellationToken) { IPublicClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); - return await client.AcquireTokenByUsernamePassword(scopes, username, password) - .WithClaims(claims) - .ExecuteAsync(async, cancellationToken) + var builder = client + .AcquireTokenByUsernamePassword(scopes, username, password) + .WithClaims(claims); + if (!string.IsNullOrEmpty(tenantId)) + { + builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId); + } + return await builder.ExecuteAsync(async, cancellationToken) .ConfigureAwait(false); } diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs new file mode 100644 index 0000000000000..061108dea7dd5 --- /dev/null +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; + +namespace Azure.Identity +{ + internal static class TenantIdResolver + { + /// + /// Resolves the tenantId based on the supplied configuration values. + /// + /// The tenantId passed to the ctor of the Credential. + /// The . + /// The . + /// + public static string Resolve(string explicitTenantId, TokenRequestContext context, TokenCredentialOptions options) + { + return options?.PreferTenantIdChallengeHint switch + { + true => context.TenantIdHint ?? explicitTenantId, + _ => explicitTenantId ?? context.TenantIdHint + }; + } + } +} diff --git a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs index da79cb4e5e145..9a98f2be91db4 100644 --- a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs @@ -12,6 +12,7 @@ namespace Azure.Identity public class TokenCredentialOptions : ClientOptions { private Uri _authorityHost; + /// /// The host of the Azure Active Directory authority. The default is https://login.microsoftonline.com/. For well known authority hosts for Azure cloud instances see . /// @@ -20,5 +21,10 @@ public Uri AuthorityHost get { return _authorityHost ?? AzureAuthorityHosts.GetDefault(); } set { _authorityHost = Validations.ValidateAuthorityHost(value); } } + + /// + /// If true, the tenant Id hint provided by a service authorization challenge will override a tenantId configured via the credential options. + /// + public bool PreferTenantIdChallengeHint { get; set; } } } diff --git a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs index 4e83b6a59ff76..5cb07322872f0 100644 --- a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs +++ b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs @@ -26,6 +26,8 @@ public class UsernamePasswordCredential : TokenCredential private readonly string _username; private readonly SecureString _password; private AuthenticationRecord _record; + private readonly string _tenantId; + private readonly TokenCredentialOptions _options; /// /// Protected constructor for mocking @@ -77,16 +79,16 @@ public UsernamePasswordCredential(string username, string password, string tenan internal UsernamePasswordCredential(string username, string password, string tenantId, string clientId, TokenCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client) { - _username = username ?? throw new ArgumentNullException(nameof(username)); - - _password = (password != null) ? password.ToSecureString() : throw new ArgumentNullException(nameof(password)); - - _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - - Validations.ValidateTenantId(tenantId, nameof(tenantId)); - + Argument.AssertNotNull(username, nameof(username)); + Argument.AssertNotNull(password, nameof(password)); + Argument.AssertNotNull(clientId, nameof(clientId)); + _tenantId = Validations.ValidateTenantId(tenantId, nameof(tenantId)); + _options = options; + + _username = username; + _password = password.ToSecureString() ; + _clientId = clientId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - _client = client ?? new MsalPublicClient(_pipeline, tenantId, clientId, null, options as ITokenCacheOptions); } @@ -140,7 +142,8 @@ public virtual async Task AuthenticateAsync(TokenRequestCo /// /// Obtains a token for a user account, authenticating them using the given username and password. Note: This will fail with - /// an if the specified user account has MFA enabled. This method is called automatically by Azure SDK client libraries. You may call this method directly, but you must also handle token caching and token refreshing. + /// an if the specified user account has MFA enabled. This method is called automatically by Azure SDK client libraries. + /// You may call this method directly, but you must also handle token caching and token refreshing. /// /// The details of the authentication request. /// A controlling the request lifetime. @@ -152,7 +155,8 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell /// /// Obtains a token for a user account, authenticating them using the given username and password. Note: This will fail with - /// an if the specified user account has MFA enabled. This method is called automatically by Azure SDK client libraries. You may call this method directly, but you must also handle token caching and token refreshing. + /// an if the specified user account has MFA enabled. This method is called automatically by Azure SDK client libraries. + /// You may call this method directly, but you must also handle token caching and token refreshing. /// /// The details of the authentication request. /// A controlling the request lifetime. @@ -184,8 +188,10 @@ private async Task GetTokenImplAsync(bool async, TokenRequestContex try { + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + AuthenticationResult result = await _client - .AcquireTokenByUsernamePasswordAsync(requestContext.Scopes, requestContext.Claims, _username, _password, async, cancellationToken) + .AcquireTokenByUsernamePasswordAsync(requestContext.Scopes, requestContext.Claims, _username, _password, tenantId, async, cancellationToken) .ConfigureAwait(false); _record = new AuthenticationRecord(result, _clientId); diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs index f729c02a9a406..1bde6652673f7 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs @@ -27,7 +27,7 @@ public class VisualStudioCodeCredential : TokenCredential private readonly string _tenantId; private readonly MsalPublicClient _client; private const string _commonTenant = "common"; - private bool tenantIdOptionProvided; + private readonly TokenCredentialOptions _options; /// /// Creates a new instance of the . @@ -43,12 +43,12 @@ public VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options) : t internal VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client, IFileSystemService fileSystem, IVisualStudioCodeAdapter vscAdapter) { - tenantIdOptionProvided = options?.TenantId != null; _tenantId = options?.TenantId ?? _commonTenant; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _client = client ?? new MsalPublicClient(_pipeline, options?.TenantId, ClientId, null, null); _fileSystem = fileSystem ?? FileSystemService.Default; _vscAdapter = vscAdapter ?? GetVscAdapter(); + _options = options; } /// @@ -66,12 +66,9 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque try { GetUserSettings(out var tenant, out var environmentName); - if (!tenantIdOptionProvided && requestContext.TenantIdHint != default ) - { - tenant = requestContext.TenantIdHint; - } + var tenantId = TenantIdResolver.Resolve(tenant, requestContext, _options); - if (string.Equals(tenant, Constants.AdfsTenantId, StringComparison.Ordinal)) + if (string.Equals(tenantId, Constants.AdfsTenantId, StringComparison.Ordinal)) { throw new CredentialUnavailableException("VisualStudioCodeCredential authentication unavailable. ADFS tenant / authorities are not supported."); } @@ -79,7 +76,8 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque var cloudInstance = GetAzureCloudInstance(environmentName); string storedCredentials = GetStoredCredentials(environmentName); - var result = await _client.AcquireTokenByRefreshTokenAsync(requestContext.Scopes, requestContext.Claims, storedCredentials, cloudInstance, tenant, async, cancellationToken) + var result = await _client + .AcquireTokenByRefreshTokenAsync(requestContext.Scopes, requestContext.Claims, storedCredentials, cloudInstance, tenantId, async, cancellationToken) .ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } @@ -142,7 +140,6 @@ private void GetUserSettings(out string tenant, out string environmentName) if (root.TryGetProperty("azure.tenant", out JsonElement tenantProperty)) { tenant = tenantProperty.GetString(); - tenantIdOptionProvided = true; } if (root.TryGetProperty("azure.cloud", out JsonElement environmentProperty)) diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs index 34229d1b9f9c1..19fe488888958 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs @@ -29,6 +29,7 @@ public class VisualStudioCredential : TokenCredential private readonly string _tenantId; private readonly IFileSystemService _fileSystem; private readonly IProcessService _processService; + private readonly TokenCredentialOptions _options; private bool tenantIdOptionProvided; /// @@ -40,7 +41,10 @@ public VisualStudioCredential() : this(null) { } /// Creates a new instance of the with the specified options. /// /// Options for configuring the credential. - public VisualStudioCredential(VisualStudioCredentialOptions options) : this(options?.TenantId, CredentialPipeline.GetInstance(options), default, default) { } + public VisualStudioCredential(VisualStudioCredentialOptions options) : this(options?.TenantId, CredentialPipeline.GetInstance(options), default, default) + { + _options = options; + } internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IFileSystemService fileSystem, IProcessService processService) { @@ -161,7 +165,8 @@ private List GetProcessStartInfos(VisualStudioTokenProvider[] arguments.Clear(); arguments.Append(ResourceArgumentName).Append(' ').Append(resource); - if (_tenantId != default || requestContext.TenantIdHint != default) + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + if (tenantId != default) { arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(_tenantId ?? requestContext.TenantIdHint); } diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 73d50c2270407..237cadb3c28e0 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -5,6 +5,8 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Core.TestFramework; +using Azure.Identity.Tests.Mock; +using Microsoft.Identity.Client; using NUnit.Framework; namespace Azure.Identity.Tests @@ -12,8 +14,7 @@ namespace Azure.Identity.Tests public class AuthorizationCodeCredentialTests : ClientTestBase { public AuthorizationCodeCredentialTests(bool isAsync) : base(isAsync) - { - } + { } [Test] public async Task AuthenticateWithAuthCodeMockAsync() @@ -23,184 +24,34 @@ public async Task AuthenticateWithAuthCodeMockAsync() var clientId = Guid.NewGuid().ToString(); var tenantId = Guid.NewGuid().ToString(); var clientSecret = Guid.NewGuid().ToString(); - - MockResponse response = CreateAuthorizationResponse(expectedToken); - - var mockTransport = new MockTransport(request => ProcessMockRequest(request, tenantId, expectedToken)); - - var options = new TokenCredentialOptions() { Transport = mockTransport }; - - AuthorizationCodeCredential cred = InstrumentClient(new AuthorizationCodeCredential(tenantId, clientId, clientSecret, authCode, options)); - - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); + string[] scopes = { "https://vault.azure.net/.default" }; + var account = new MockAccount("username", tenantId); + var options = new TokenCredentialOptions(); + var authResult = new AuthenticationResult( + expectedToken, + false, + "", + DateTimeOffset.Now.AddHours(1), + default, + tenantId, + account, + null, + scopes, + Guid.NewGuid(), + null, + "Bearer"); + var mockMsalClient = new MockMsalConfidentialClient(authResult); + + AuthorizationCodeCredential cred = InstrumentClient( + new AuthorizationCodeCredential(tenantId, clientId, clientSecret, authCode, options, mockMsalClient)); + + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(scopes)); Assert.AreEqual(token.Token, expectedToken); - AccessToken token2 = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://managemnt.azure.com/.default" })); + AccessToken token2 = await cred.GetTokenAsync(new TokenRequestContext(scopes)); Assert.AreEqual(token.Token, expectedToken); } - - private MockResponse ProcessMockRequest(MockRequest mockRequest, string tenantId, string token) - { - string requestUrl = mockRequest.Uri.ToUri().AbsoluteUri; - - if (requestUrl.StartsWith("https://login.microsoftonline.com/common/discovery/instance")) - { - return DiscoveryInstanceResponse; - } - - if (requestUrl.StartsWith($"https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration")) - { - return CreateOpenIdConfigurationResponse(tenantId); - } - - if (requestUrl.StartsWith($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token")) - { - return CreateAuthorizationResponse(token); - } - - throw new InvalidOperationException(); - } - - private MockResponse CreateAuthorizationResponse(string accessToken) - { - MockResponse response = new MockResponse(200).WithContent(@$"{{ - ""token_type"": ""Bearer"", - ""scope"": ""https://vault.azure.net/user_impersonation https://vault.azure.net/.default"", - ""expires_in"": 3600, - ""ext_expires_in"": 3600, - ""access_token"": ""{accessToken}"", - ""refresh_token"": ""eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9-eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ-SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"", - ""foci"": ""1"", - ""id_token"": ""eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InU0T2ZORlBId0VCb3NIanRyYXVPYlY4NExuWSJ9.eyJhdWQiOiJFMDFCNUY2NC03OEY1LTRGODgtQjI4Mi03QUUzOUI4QUM0QkQiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vRDEwOUI0NkUtM0E5Ri00NDQwLTg2MjItMjVEQjQxOTg1MDUxL3YyLjAiLCJpYXQiOjE1NjM5OTA0MDEsIm5iZiI6MTU2Mzk5MDQwMSwiZXhwIjoxNTYzOTk0MzAxLCJhaW8iOiJRMVV3TlV4YVNFeG9aak5uUWpSd00zcFRNamRrV2pSTFNVcEdMMVV3TWt0a2FrZDFTRkJVVlZwMmVFODRNMFZ0VXk4Mlp6TjJLM1JrVVVzeVQyVXhNamxJWTNKQ1p6MGlMQ0p1WVcxbElqb2lVMk52ZEhRZ1UyTiIsIm5hbWUiOiJTb21lIFVzZXIiLCJvaWQiOiIyQ0M5QzNBOC0yNTA5LTQyMEYtQjAwQi02RTczQkM1MURDQjUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzb21ldXNlckBtaWNyb3NvZnQuY29tIiwic3ViIjoiQ0p6ZFdJaU9pSkdXakY0UVVOS1JFRnBRWFp6IiwidGlkIjoiMjRGRTMxMUYtN0E3MS00RjgzLTkxNkEtOTQ3OEQ0NUMwNDI3IiwidXRpIjoidFFqSTRNaTAzUVVVek9VSTRRVU0wUWtRaUxDSnBjM01pT2lKb2RIUiIsInZlciI6IjIuMCJ9.eVyG1AL8jwnTo3m9mGsV4EDHa_8PN6rRPEN9E3cQzxNoPU9HZTFt1SgOnLB7n1a4J_E3iVoZ3VB5I-NdDBESRdlg1k4XlrWqtisxl3I7pvWVFZKEhwHYYQ_nZITNeCb48LfZNz-Mr4EZeX6oyUymha5tOomikBLLxP78LOTlbGQiFn9AjtV0LtMeoiDf-K9t-kgU-XwsVjCyFKFBQhcyv7zaBEpeA-Kzh3-HG7wZ-geteM5y-JF97nD_rJ8ow1FmvtDYy6MVcwuNTv2YYT8dn8s-SGB4vpNNignlL0QgYh2P2cIrPdhZVc2iQqYTn_FK_UFPqyb_MZSjl1QkXVhgJA"", - ""client_info"": ""eyJ1aWQiOiIyQ0M5QzNBOC0yNTA5LTQyMEYtQjAwQi02RTczQkM1MURDQjUiLCJ1dGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3In0"" -}}"); - - return response; - } - - private static MockResponse DiscoveryInstanceResponse - { - get - { - return new MockResponse(200).WithContent(@" -{ - ""tenant_discovery_endpoint"": ""https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"", - ""api-version"": ""1.1"", - ""metadata"": [ - { - ""preferred_network"": ""login.microsoftonline.com"", - ""preferred_cache"": ""login.windows.net"", - ""aliases"": [ - ""login.microsoftonline.com"", - ""login.windows.net"", - ""login.microsoft.com"", - ""sts.windows.net"" - ] -}, - { - ""preferred_network"": ""login.partner.microsoftonline.cn"", - ""preferred_cache"": ""login.partner.microsoftonline.cn"", - ""aliases"": [ - ""login.partner.microsoftonline.cn"", - ""login.chinacloudapi.cn"" - ] - }, - { - ""preferred_network"": ""login.microsoftonline.de"", - ""preferred_cache"": ""login.microsoftonline.de"", - ""aliases"": [ - ""login.microsoftonline.de"" - ] - }, - { - ""preferred_network"": ""login.microsoftonline.us"", - ""preferred_cache"": ""login.microsoftonline.us"", - ""aliases"": [ - ""login.microsoftonline.us"", - ""login.usgovcloudapi.net"" - ] - }, - { - ""preferred_network"": ""login-us.microsoftonline.com"", - ""preferred_cache"": ""login-us.microsoftonline.com"", - ""aliases"": [ - ""login-us.microsoftonline.com"" - ] - } - ] -}"); - } - } - - private MockResponse CreateOpenIdConfigurationResponse(string tenantId) - { - return new MockResponse(200).WithContent(@$"{{ - ""authorization_endpoint"": ""https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize"", - ""token_endpoint"": ""https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"", - ""token_endpoint_auth_methods_supported"": [ - ""client_secret_post"", - ""private_key_jwt"", - ""client_secret_basic"" - ], - ""jwks_uri"": ""https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys"", - ""response_modes_supported"": [ - ""query"", - ""fragment"", - ""form_post"" - ], - ""subject_types_supported"": [ - ""pairwise"" - ], - ""id_token_signing_alg_values_supported"": [ - ""RS256"" - ], - ""http_logout_supported"": true, - ""frontchannel_logout_supported"": true, - ""end_session_endpoint"": ""https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/logout"", - ""response_types_supported"": [ - ""code"", - ""id_token"", - ""code id_token"", - ""id_token token"" - ], - ""scopes_supported"": [ - ""openid"", - ""profile"", - ""email"", - ""offline_access"" - ], - ""issuer"": ""https://login.microsoftonline.com/{tenantId}/v2.0"", - ""claims_supported"": [ - ""sub"", - ""iss"", - ""cloud_instance_name"", - ""cloud_instance_host_name"", - ""cloud_graph_host_name"", - ""msgraph_host"", - ""aud"", - ""exp"", - ""iat"", - ""auth_time"", - ""acr"", - ""nonce"", - ""preferred_username"", - ""name"", - ""tid"", - ""ver"", - ""at_hash"", - ""c_hash"", - ""email"" - ], - ""request_uri_parameter_supported"": false, - ""userinfo_endpoint"": ""https://graph.microsoft.com/oidc/userinfo"", - ""tenant_region_scope"": null, - ""cloud_instance_name"": ""microsoftonline.com"", - ""cloud_graph_host_name"": ""graph.windows.net"", - ""msgraph_host"": ""graph.microsoft.com"", - ""rbac_url"": ""https://pas.windows.net"" -}}"); - } } } diff --git a/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs b/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs index ffc89323ccc1c..bf9505b78786f 100644 --- a/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs @@ -75,7 +75,7 @@ public async Task ValidateClientCertificateCredentialSucceededEvents() [Test] public async Task ValidateDeviceCodeCredentialSucceededEvents() { - var mockMsalClient = new MockMsalPublicClient() { DeviceCodeAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; + var mockMsalClient = new MockMsalPublicClient() { DeviceCodeAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; var credential = InstrumentClient(new DeviceCodeCredential((_, __) => { return Task.CompletedTask; }, default, Guid.NewGuid().ToString(), default, default, mockMsalClient)); @@ -87,7 +87,7 @@ public async Task ValidateDeviceCodeCredentialSucceededEvents() [Test] public async Task ValidateInteractiveBrowserCredentialSucceededEvents() { - var mockMsalClient = new MockMsalPublicClient() { InteractiveAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; + var mockMsalClient = new MockMsalPublicClient() { InteractiveAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, Guid.NewGuid().ToString(), default, default, mockMsalClient)); @@ -102,7 +102,7 @@ public async Task ValidateSharedTokenCacheCredentialSucceededEvents() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("mockuser@mockdomain.com") }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, "mockuser@mockdomain.com", default, default, mockMsalClient)); @@ -145,7 +145,7 @@ public async Task ValidateDeviceCodeCredentialFailedEvents() { var expExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient() { DeviceCodeAuthFactory = (_) => throw new MockClientException(expExMessage) }; + var mockMsalClient = new MockMsalPublicClient() { DeviceCodeAuthFactory = (_,_) => throw new MockClientException(expExMessage) }; var credential = InstrumentClient(new DeviceCodeCredential((_, __) => { return Task.CompletedTask; }, default, Guid.NewGuid().ToString(), default, default, mockMsalClient)); @@ -159,7 +159,7 @@ public async Task ValidateInteractiveBrowserCredentialFailedEvents() { var expExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient() { InteractiveAuthFactory = (_) => throw new MockClientException(expExMessage) }; + var mockMsalClient = new MockMsalPublicClient() { InteractiveAuthFactory = (_,_) => throw new MockClientException(expExMessage) }; var credential = InstrumentClient(new InteractiveBrowserCredential(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), default, default, mockMsalClient)); @@ -176,7 +176,7 @@ public async Task ValidateSharedTokenCacheCredentialFailedEvents() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("mockuser@mockdomain.com") }, - SilentAuthFactory = (_) => throw new MockClientException(expExMessage) + SilentAuthFactory = (_,_) => throw new MockClientException(expExMessage) }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, "mockuser@mockdomain.com", default, default, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index 027ba227c9d2b..60df4a318159e 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -23,7 +23,7 @@ public async Task InteractiveBrowserAcquireTokenInteractiveException() { string expInnerExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient() { InteractiveAuthFactory = (_) => { throw new MockClientException(expInnerExMessage); } }; + var mockMsalClient = new MockMsalPublicClient { InteractiveAuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); @@ -47,8 +47,8 @@ public async Task InteractiveBrowserAcquireTokenSilentException() var mockMsalClient = new MockMsalPublicClient { - InteractiveAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, - SilentAuthFactory = (_) => { throw new MockClientException(expInnerExMessage); } + InteractiveAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, + SilentAuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); @@ -79,8 +79,8 @@ public async Task InteractiveBrowserRefreshException() var mockMsalClient = new MockMsalPublicClient { - InteractiveAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, - SilentAuthFactory = (_) => { throw new MsalUiRequiredException("errorCode", "message"); } + InteractiveAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, + SilentAuthFactory = (_,_) => { throw new MsalUiRequiredException("errorCode", "message"); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); @@ -91,7 +91,7 @@ public async Task InteractiveBrowserRefreshException() Assert.AreEqual(expExpiresOn, token.ExpiresOn); - mockMsalClient.InteractiveAuthFactory = (_) => { throw new MockClientException(expInnerExMessage); }; + mockMsalClient.InteractiveAuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); }; var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); @@ -162,7 +162,7 @@ private async Task ValidateSyncWorkaroundCompatSwitch(bool expectedThreadPoolExe var mockMsalClient = new MockMsalPublicClient { - InteractiveAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(5)); } + InteractiveAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(5)); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs index 9d531850f8230..373c852b4406d 100644 --- a/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs +++ b/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs @@ -10,26 +10,62 @@ namespace Azure.Identity.Tests.Mock { internal class MockMsalConfidentialClient : MsalConfidentialClient { - private Func _factory = null; + private Func _factory = null; + private Func _silentFactory = null; + private Func _authcodeFactory = null; public MockMsalConfidentialClient(AuthenticationResult result) { - _factory = (_) => result; + _factory = (_, _) => result; + _silentFactory = (_, _, _) => result; + _authcodeFactory = (_, _, _) => result; } public MockMsalConfidentialClient(Exception exception) { - _factory = (_) => throw exception; + _factory = (_, _) => throw exception; + _silentFactory = (_, _, _) => throw exception; + _authcodeFactory = (_, _, _) => throw exception; } - public MockMsalConfidentialClient(Func factory) + public MockMsalConfidentialClient(Func factory) { _factory = factory; } - public override ValueTask AcquireTokenForClientAsync(string[] scopes, bool async, CancellationToken cancellationToken) + public MockMsalConfidentialClient(Func factory) { - return new ValueTask(_factory(scopes)); + _silentFactory = factory; + } + + public MockMsalConfidentialClient(Func factory) + { + _authcodeFactory = factory; + } + + public override ValueTask AcquireTokenForClientAsync(string[] scopes, string tenantId, bool async, CancellationToken cancellationToken) + { + return new(_factory(scopes, tenantId)); + } + + public override ValueTask AcquireTokenSilentAsync( + string[] scopes, + AuthenticationAccount account, + string tenantId, + bool async, + CancellationToken cancellationToken) + { + return new(_silentFactory(scopes, tenantId, account)); + } + + public override ValueTask AcquireTokenByAuthorizationCodeAsync( + string[] scopes, + string code, + string tenantId, + bool async, + CancellationToken cancellationToken) + { + return new(_authcodeFactory(scopes, tenantId, code)); } } } diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs index b73fedd47b068..48779c587a777 100644 --- a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs @@ -14,17 +14,17 @@ internal class MockMsalPublicClient : MsalPublicClient { public List Accounts { get; set; } - public Func AuthFactory { get; set; } + public Func AuthFactory { get; set; } - public Func UserPassAuthFactory { get; set; } + public Func UserPassAuthFactory { get; set; } - public Func InteractiveAuthFactory { get; set; } + public Func InteractiveAuthFactory { get; set; } - public Func SilentAuthFactory { get; set; } + public Func SilentAuthFactory { get; set; } public Func ExtendedSilentAuthFactory { get; set; } - public Func DeviceCodeAuthFactory { get; set; } + public Func DeviceCodeAuthFactory { get; set; } public Func PubClientAppFactory { get; set; } @@ -33,13 +33,13 @@ protected override ValueTask> GetAccountsCoreAsync(bool async, Ca return new(Accounts); } - protected override ValueTask AcquireTokenByUsernamePasswordCoreAsync(string[] scopes, string claims, string username, SecureString password, bool async, CancellationToken cancellationToken) + protected override ValueTask AcquireTokenByUsernamePasswordCoreAsync(string[] scopes, string claims, string username, SecureString password, string tenantId, bool async, CancellationToken cancellationToken) { - Func factory = UserPassAuthFactory ?? AuthFactory; + Func factory = UserPassAuthFactory ?? AuthFactory; if (factory != null) { - return new ValueTask(factory(scopes)); + return new ValueTask(factory(scopes, tenantId)); } throw new NotImplementedException(); @@ -47,11 +47,11 @@ protected override ValueTask AcquireTokenByUsernamePasswor protected override ValueTask AcquireTokenInteractiveCoreAsync(string[] scopes, string claims, Prompt prompt, bool async, CancellationToken cancellationToken) { - Func factory = InteractiveAuthFactory ?? AuthFactory; + Func factory = InteractiveAuthFactory ?? AuthFactory; if (factory != null) { - return new ValueTask(factory(scopes)); + return new ValueTask(factory(scopes, null)); } throw new NotImplementedException(); @@ -64,23 +64,23 @@ protected override ValueTask AcquireTokenSilentCoreAsync(s return new ValueTask(ExtendedSilentAuthFactory(scopes, account, async, cancellationToken)); } - Func factory = SilentAuthFactory ?? AuthFactory; + Func factory = SilentAuthFactory ?? AuthFactory; if (factory != null) { - return new ValueTask(factory(scopes)); + return new ValueTask(factory(scopes, null)); } throw new NotImplementedException(); } - protected override ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, AuthenticationRecord record, bool async, CancellationToken cancellationToken) + protected override ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, AuthenticationRecord record, string tenantId, bool async, CancellationToken cancellationToken) { - Func factory = SilentAuthFactory ?? AuthFactory; + Func factory = SilentAuthFactory ?? AuthFactory; if (factory != null) { - return new ValueTask(factory(scopes)); + return new ValueTask(factory(scopes, tenantId)); } throw new NotImplementedException(); @@ -88,11 +88,11 @@ protected override ValueTask AcquireTokenSilentCoreAsync(s protected override ValueTask AcquireTokenWithDeviceCodeCoreAsync(string[] scopes, string claims, Func deviceCodeCallback, bool async, CancellationToken cancellationToken) { - Func factory = DeviceCodeAuthFactory ?? AuthFactory; + Func factory = DeviceCodeAuthFactory ?? AuthFactory; if (factory != null) { - return new ValueTask(factory(scopes)); + return new ValueTask(factory(scopes, null)); } throw new NotImplementedException(); diff --git a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs index 9c57a1c6ad5b8..73f0689e52894 100644 --- a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs @@ -67,7 +67,7 @@ public async Task OneAccountNoTentantNoUsername() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, null, null, null, mockMsalClient)); @@ -88,7 +88,7 @@ public async Task OneMatchingAccountUsernameOnly() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, expectedUsername, null, null, mockMsalClient)); @@ -109,7 +109,7 @@ public async Task OneMatchingAccountUsernameDifferentCasing() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, expectedUsername, null, null, mockMsalClient)); @@ -131,7 +131,7 @@ public async Task OneMatchingAccountTenantIdOnly() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername, nonMatchedTenantId), new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername, tenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, null, null, null, mockMsalClient)); @@ -152,7 +152,7 @@ public async Task OneMatchingAccountTenantIdAndUsername() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername, Guid.NewGuid().ToString()), new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername, tenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, expectedUsername, null, null, mockMsalClient)); @@ -173,7 +173,7 @@ public async Task NoAccounts() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // without username @@ -218,7 +218,7 @@ public async Task MultipleAccountsNoTenantIdOrUsername() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, null, null, null, mockMsalClient)); @@ -241,7 +241,7 @@ public async Task NoMatchingAccountsUsernameOnly() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // with username @@ -266,7 +266,7 @@ public async Task NoMatchingAccountsTenantIdOnly() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // with username @@ -291,7 +291,7 @@ public async Task NoMatchingAccountsTenantIdAndUsername() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // with username @@ -316,7 +316,7 @@ public async Task MultipleMatchingAccountsUsernameOnly() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId), new MockAccount(expectedUsername, mockuserGuestTenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, expectedUsername, null, null, mockMsalClient)); @@ -340,7 +340,7 @@ public async Task MultipleMatchingAccountsTenantIdOnly() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId), new MockAccount(expectedUsername, mockuserGuestTenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(mockuserGuestTenantId, null, null, null, mockMsalClient)); @@ -364,7 +364,7 @@ public async Task MultipleMatchingAccountsUsernameAndTenantId() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId), new MockAccount(expectedUsername, mockuserTenantId) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(mockuserTenantId, expectedUsername, null, null, mockMsalClient)); @@ -389,7 +389,7 @@ public async Task MultipleMatchingAccountsUsernameAndTenantIdWithEnableGuestTena var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId1), new MockAccount(expectedUsername, mockuserTenantId2) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(mockuserTenantId1, expectedUsername, new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, null, mockMsalClient)); @@ -410,7 +410,7 @@ public async Task UiRequiredException() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername) }, - SilentAuthFactory = (_) => { throw new MsalUiRequiredException("code", "message"); } + SilentAuthFactory = (_,_) => { throw new MsalUiRequiredException("code", "message"); } }; // with username @@ -434,7 +434,7 @@ public async Task MatchAnySingleTenantIdWithEnableGuestTenantAuthentication() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername, Guid.NewGuid().ToString()) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, null, new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, null, mockMsalClient)); @@ -455,7 +455,7 @@ public async Task MatchAnyTenantIdWithEnableGuestTenantAuthenticationAndUsername var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername, Guid.NewGuid().ToString()), new MockAccount("fakeuser@fakedomain.com", Guid.NewGuid().ToString()) }, - SilentAuthFactory = (_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, expectedUsername, new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, null, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index d3ca1ba67acda..c6b744e10f5aa 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -21,7 +21,7 @@ public async Task VerifyMsalClientExceptionAsync() { string expInnerExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient() { UserPassAuthFactory = (_) => { throw new MockClientException(expInnerExMessage); } }; + var mockMsalClient = new MockMsalPublicClient() { UserPassAuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); } }; var username = Guid.NewGuid().ToString(); var password = Guid.NewGuid().ToString(); From 95c9d4075c909617e6d812cf305db82c2dd087cd Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 25 May 2021 15:12:27 -0500 Subject: [PATCH 07/39] tests --- .../src/DeviceCodeCredential.cs | 3 +- .../tests/AuthorizationCodeCredentialTests.cs | 67 ++- .../tests/AzureIdentityEventSourceTests.cs | 18 +- .../tests/ClientCertificateCredentialTests.cs | 62 ++- .../tests/DeviceCodeCredentialTests.cs | 398 ++++-------------- .../tests/Mock/MockMsalConfidentialClient.cs | 36 +- .../tests/Mock/MockMsalPublicClient.cs | 67 ++- 7 files changed, 279 insertions(+), 372 deletions(-) diff --git a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs index 582b1b463863e..40025e79d9770 100644 --- a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs @@ -19,7 +19,7 @@ public class DeviceCodeCredential : TokenCredential { private readonly string _tenantId; private readonly TokenCredentialOptions _options; - internal MsalPublicClient Client { get; } + internal MsalPublicClient Client { get; set; } internal string ClientId { get; } internal bool DisableAutomaticAuthentication { get; } internal AuthenticationRecord Record { get; private set; } @@ -215,7 +215,6 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC { throw new AuthenticationRequiredException(AuthenticationRequiredMessage, requestContext, inner); } - return scope.Succeeded(await GetTokenViaDeviceCodeAsync(requestContext, async, cancellationToken).ConfigureAwait(false)); } catch (Exception e) diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 237cadb3c28e0..3709022a39c28 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading.Tasks; using Azure.Core; using Azure.Core.TestFramework; @@ -13,43 +14,67 @@ namespace Azure.Identity.Tests { public class AuthorizationCodeCredentialTests : ClientTestBase { + private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + private const string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string Scope = "https://vault.azure.net/.default"; + private TokenCredentialOptions options = new TokenCredentialOptions(); + private string authCode; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalConfidentialClient mockMsalClient; + private string expectedTenantId; + private string clientSecret = Guid.NewGuid().ToString(); + private Func> silentFactory; + public AuthorizationCodeCredentialTests(bool isAsync) : base(isAsync) { } - [Test] - public async Task AuthenticateWithAuthCodeMockAsync() + [SetUp] + public void TestSetup() { - var expectedToken = Guid.NewGuid().ToString(); - var authCode = Guid.NewGuid().ToString(); - var clientId = Guid.NewGuid().ToString(); - var tenantId = Guid.NewGuid().ToString(); - var clientSecret = Guid.NewGuid().ToString(); - string[] scopes = { "https://vault.azure.net/.default" }; - var account = new MockAccount("username", tenantId); - var options = new TokenCredentialOptions(); - var authResult = new AuthenticationResult( + expectedTenantId = null; + authCode = Guid.NewGuid().ToString(); + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + var result = new AuthenticationResult( expectedToken, false, - "", - DateTimeOffset.Now.AddHours(1), - default, - tenantId, - account, null, - scopes, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, Guid.NewGuid(), null, "Bearer"); - var mockMsalClient = new MockMsalConfidentialClient(authResult); + silentFactory = (_, _tenantId, _) => + { + Assert.AreEqual(expectedTenantId, _tenantId); + return new ValueTask(result); + }; + mockMsalClient = new MockMsalConfidentialClient(silentFactory); + mockMsalClient.AuthcodeFactory = (_, _tenantId, _) => + { + Assert.AreEqual(expectedTenantId, _tenantId); + return result; + }; + } + + [Test] + public async Task AuthenticateWithAuthCodeMockAsync([Values(null, TenantId)] string tenantId, [Values(true, false)] bool preferHint) + { + expectedTenantId = preferHint ? tenantId : TenantId; AuthorizationCodeCredential cred = InstrumentClient( - new AuthorizationCodeCredential(tenantId, clientId, clientSecret, authCode, options, mockMsalClient)); + new AuthorizationCodeCredential(TenantId, ClientId, clientSecret, authCode, options, mockMsalClient)); - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(scopes)); + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); Assert.AreEqual(token.Token, expectedToken); - AccessToken token2 = await cred.GetTokenAsync(new TokenRequestContext(scopes)); + AccessToken token2 = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); Assert.AreEqual(token.Token, expectedToken); } diff --git a/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs b/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs index 15ec165119aac..73d2d3dc94960 100644 --- a/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureIdentityEventSourceTests.cs @@ -75,7 +75,7 @@ public async Task ValidateClientCertificateCredentialSucceededEvents() [Test] public async Task ValidateDeviceCodeCredentialSucceededEvents() { - var mockMsalClient = new MockMsalPublicClient() { DeviceCodeAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; + var mockMsalClient = new MockMsalPublicClient { DeviceCodeAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; var credential = InstrumentClient(new DeviceCodeCredential((_, __) => { return Task.CompletedTask; }, default, Guid.NewGuid().ToString(), default, default, mockMsalClient)); @@ -87,7 +87,7 @@ public async Task ValidateDeviceCodeCredentialSucceededEvents() [Test] public async Task ValidateInteractiveBrowserCredentialSucceededEvents() { - var mockMsalClient = new MockMsalPublicClient() { AuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; + var mockMsalClient = new MockMsalPublicClient { AuthFactory = (_,_) => { return AuthenticationResultFactory.Create(Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, Guid.NewGuid().ToString(), default, default, mockMsalClient)); @@ -102,7 +102,7 @@ public async Task ValidateSharedTokenCacheCredentialSucceededEvents() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("mockuser@mockdomain.com") }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } + SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(10)); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, "mockuser@mockdomain.com", default, default, mockMsalClient)); @@ -145,9 +145,13 @@ public async Task ValidateDeviceCodeCredentialFailedEvents() { var expExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient() { DeviceCodeAuthFactory = (_,_) => throw new MockClientException(expExMessage) }; + var mockMsalClient = new MockMsalPublicClient + { + DeviceCodeAuthFactory = (_, _) => throw new MockClientException(expExMessage), + SilentAuthFactory = (_, _) => throw new MockClientException(expExMessage) + }; - var credential = InstrumentClient(new DeviceCodeCredential((_, __) => { return Task.CompletedTask; }, default, Guid.NewGuid().ToString(), default, default, mockMsalClient)); + var credential = InstrumentClient(new DeviceCodeCredential((_, _) => { return Task.CompletedTask; }, default, Guid.NewGuid().ToString(), default, default, mockMsalClient)); var method = "DeviceCodeCredential.GetToken"; @@ -159,7 +163,7 @@ public async Task ValidateInteractiveBrowserCredentialFailedEvents() { var expExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient() { AuthFactory = (_,_) => throw new MockClientException(expExMessage) }; + var mockMsalClient = new MockMsalPublicClient { AuthFactory = (_,_) => throw new MockClientException(expExMessage) }; var credential = InstrumentClient(new InteractiveBrowserCredential(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), default, default, mockMsalClient)); @@ -235,7 +239,7 @@ private async Task AssertCredentialGetTokenFailedAsync(TokenCredential credentia Assert.AreEqual(method, e.GetProperty("method")); Assert.AreEqual($"[ {string.Join(", ", expScopes)} ]", e.GetProperty("scopes")); Assert.AreEqual(expParentRequestId, e.GetProperty("parentRequestId")); - Assert.IsTrue(e.GetProperty("exception").Contains(expExMessage)); + Assert.That(e.GetProperty("exception"), Does.Contain(expExMessage)); await Task.CompletedTask; } diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs index 8dda808f23e3f..c21c91409615a 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs @@ -19,8 +19,7 @@ namespace Azure.Identity.Tests public class ClientCertificateCredentialTests : ClientTestBase { public ClientCertificateCredentialTests(bool isAsync) : base(isAsync) - { - } + { } [Test] public void VerifyCtorParametersValidation() @@ -48,11 +47,26 @@ public void VerifyBadCertificateFileBehavior() TokenRequestContext tokenContext = new TokenRequestContext(MockScopes.Default); - ClientCertificateCredential missingFileCredential = new ClientCertificateCredential(tenantId, clientId, Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "notfound.pem")); - ClientCertificateCredential invalidPemCredential = new ClientCertificateCredential(tenantId, clientId, Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert-invalid-data.pem")); - ClientCertificateCredential unknownFormatCredential = new ClientCertificateCredential(tenantId, clientId, Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.unknown")); - ClientCertificateCredential encryptedCredential = new ClientCertificateCredential(tenantId, clientId, Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert-password-protected.pfx")); - ClientCertificateCredential unsupportedCertCredential = new ClientCertificateCredential(tenantId, clientId, Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "ec-cert.pem")); + ClientCertificateCredential missingFileCredential = new ClientCertificateCredential( + tenantId, + clientId, + Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "notfound.pem")); + ClientCertificateCredential invalidPemCredential = new ClientCertificateCredential( + tenantId, + clientId, + Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert-invalid-data.pem")); + ClientCertificateCredential unknownFormatCredential = new ClientCertificateCredential( + tenantId, + clientId, + Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.unknown")); + ClientCertificateCredential encryptedCredential = new ClientCertificateCredential( + tenantId, + clientId, + Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert-password-protected.pfx")); + ClientCertificateCredential unsupportedCertCredential = new ClientCertificateCredential( + tenantId, + clientId, + Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "ec-cert.pem")); Assert.Throws(() => missingFileCredential.GetToken(tokenContext)); Assert.Throws(() => invalidPemCredential.GetToken(tokenContext)); @@ -87,7 +101,9 @@ public async Task VerifyClientCertificateRequestFailedAsync(bool usePemFile) var mockCert = new X509Certificate2(certificatePath); ClientCertificateCredential credential = InstrumentClient( - usePemFile ? new ClientCertificateCredential(expectedTenantId, expectedClientId, certificatePathPem, options) : new ClientCertificateCredential(expectedTenantId, expectedClientId, mockCert, options) + usePemFile + ? new ClientCertificateCredential(expectedTenantId, expectedClientId, certificatePathPem, options) + : new ClientCertificateCredential(expectedTenantId, expectedClientId, mockCert, options) ); Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); @@ -112,7 +128,35 @@ public void VerifyClientCertificateCredentialException(bool usePemFile) var mockCert = new X509Certificate2(certificatePath); ClientCertificateCredential credential = InstrumentClient( - usePemFile ? new ClientCertificateCredential(expectedTenantId, expectedClientId, certificatePathPem, default, default, mockMsalClient) : new ClientCertificateCredential(expectedTenantId, expectedClientId, mockCert, default, default, mockMsalClient) + usePemFile + ? new ClientCertificateCredential(expectedTenantId, expectedClientId, certificatePathPem, default, default, mockMsalClient) + : new ClientCertificateCredential(expectedTenantId, expectedClientId, mockCert, default, default, mockMsalClient) + ); + + var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + + Assert.IsNotNull(ex.InnerException); + + Assert.IsInstanceOf(typeof(MockClientException), ex.InnerException); + + Assert.AreEqual(expectedInnerExMessage, ex.InnerException.Message); + } + + [Test] + public void UsesTenantIdHint([Values("1234", null)] string tenantId, [Values(true, false)] bool usePemFile) + { + string expectedInnerExMessage = Guid.NewGuid().ToString(); + var mockMsalClient = new MockMsalConfidentialClient(new MockClientException(expectedInnerExMessage)); + var expectedTenantId = Guid.NewGuid().ToString(); + var expectedClientId = Guid.NewGuid().ToString(); + var certificatePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); + var certificatePathPem = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pem"); + var mockCert = new X509Certificate2(certificatePath); + + ClientCertificateCredential credential = InstrumentClient( + usePemFile + ? new ClientCertificateCredential(expectedTenantId, expectedClientId, certificatePathPem, default, default, mockMsalClient) + : new ClientCertificateCredential(expectedTenantId, expectedClientId, mockCert, default, default, mockMsalClient) ); var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index f76ad189e4b92..26ab66a046373 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -7,34 +7,44 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using Azure.Identity.Tests.Mock; +using Microsoft.Identity.Client; namespace Azure.Identity.Tests { public class DeviceCodeCredentialTests : ClientTestBase { public DeviceCodeCredentialTests(bool isAsync) : base(isAsync) - { - } + { } private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; - + private const string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string Scope = "https://vault.azure.net/.default"; private readonly HashSet _requestedCodes = new HashSet(); - + private TokenCredentialOptions options = new TokenCredentialOptions(); private readonly object _requestedCodesLock = new object(); + private string expectedCode; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalPublicClient mockMsalClient; + private DeviceCodeResult deviceCodeResult; + private string expectedTenantId = null; - private Task VerifyDeviceCode(DeviceCodeInfo code, string message) + private Task VerifyDeviceCode(DeviceCodeInfo codeInfo, string expectedCode) { - Assert.AreEqual(message, code.Message); + Assert.AreEqual(expectedCode, codeInfo.DeviceCode); return Task.CompletedTask; } - private Task VerifyDeviceCodeAndCancel(DeviceCodeInfo code, string message, CancellationTokenSource cancelSource) + private Task VerifyDeviceCodeAndCancel(DeviceCodeInfo codeInfo, string actualCode, CancellationTokenSource cancelSource) { - Assert.AreEqual(message, code.Message); + Assert.AreEqual(actualCode, codeInfo.DeviceCode); cancelSource.Cancel(); @@ -44,13 +54,11 @@ private Task VerifyDeviceCodeAndCancel(DeviceCodeInfo code, string message, Canc private async Task VerifyDeviceCodeCallbackCancellationToken(DeviceCodeInfo code, CancellationToken cancellationToken) { await Task.Delay(2000, cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); } private class MockException : Exception - { - } + { } private async Task ThrowingDeviceCodeCallback(DeviceCodeInfo code, CancellationToken cancellationToken) { @@ -59,38 +67,53 @@ private async Task ThrowingDeviceCodeCallback(DeviceCodeInfo code, CancellationT throw new MockException(); } - [Test] - public async Task AuthenticateWithDeviceCodeMockAsync() - { - var expectedCode = Guid.NewGuid().ToString(); - - var expectedToken = Guid.NewGuid().ToString(); - - var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken)); - - var options = new TokenCredentialOptions() { Transport = mockTransport }; - - var cred = InstrumentClient(new DeviceCodeCredential((code, cancelToken) => VerifyDeviceCode(code, expectedCode), ClientId, options: options)); - - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); - - Assert.AreEqual(token.Token, expectedToken); + [SetUp] + public void TestSetup() + { + expectedTenantId = null; + expectedCode = Guid.NewGuid().ToString(); + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + mockMsalClient = new MockMsalPublicClient(); + deviceCodeResult = MockMsalPublicClient.GetDeviceCodeResult(deviceCode: expectedCode); + mockMsalClient.DeviceCodeResult = deviceCodeResult; + var result = new AuthenticationResult( + expectedToken, + false, + null, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, + Guid.NewGuid(), + null, + "Bearer"); + mockMsalClient.SilentAuthFactory = (_, tId) => + { + Assert.AreEqual(expectedTenantId, tId); + return result; + }; + mockMsalClient.DeviceCodeAuthFactory = (_, _) => + { + // Assert.AreEqual(tenantId, tId); + return result; + }; } [Test] - public async Task AuthenticateWithDeviceCodeMockAsync2() + public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantId)] string tenantId) { - var expectedCode = Guid.NewGuid().ToString(); - - var expectedToken = Guid.NewGuid().ToString(); + expectedTenantId = tenantId; + var cred = InstrumentClient( + new DeviceCodeCredential((code, _) => VerifyDeviceCode(code, expectedCode), tenantId, ClientId, options, null, mockMsalClient)); - var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken)); + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); - var options = new TokenCredentialOptions() { Transport = mockTransport }; - - var cred = InstrumentClient(new DeviceCodeCredential((code, cancelToken) => VerifyDeviceCode(code, expectedCode), ClientId, options: options)); + Assert.AreEqual(token.Token, expectedToken); - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); + token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); Assert.AreEqual(token.Token, expectedToken); } @@ -100,30 +123,24 @@ public async Task AuthenticateWithDeviceCodeMockAsync2() public async Task AuthenticateWithDeviceCodeNoCallback() { var capturedOut = new StringBuilder(); - var capturedOutWriter = new StringWriter(capturedOut); - var stdOut = Console.Out; - Console.SetOut(capturedOutWriter); try { - var expectedCode = Guid.NewGuid().ToString(); - - var expectedToken = Guid.NewGuid().ToString(); + var client = new DeviceCodeCredential { Client = mockMsalClient }; + var cred = InstrumentClient(client); - var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken)); + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); - var options = new DeviceCodeCredentialOptions() { Transport = mockTransport }; - - var cred = InstrumentClient(new DeviceCodeCredential(options)); + Assert.AreEqual(token.Token, expectedToken); + Assert.AreEqual(mockMsalClient.DeviceCodeResult.Message + Environment.NewLine, capturedOut.ToString()); - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); + token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); Assert.AreEqual(token.Token, expectedToken); - - Assert.AreEqual(expectedCode + Environment.NewLine, capturedOut.ToString()); + Assert.AreEqual(mockMsalClient.DeviceCodeResult.Message + Environment.NewLine, capturedOut.ToString()); } finally { @@ -134,19 +151,20 @@ public async Task AuthenticateWithDeviceCodeNoCallback() [Test] public async Task AuthenticateWithDeviceCodeMockVerifyMsalCancellationAsync() { - var expectedCode = Guid.NewGuid().ToString(); - - var expectedToken = Guid.NewGuid().ToString(); - var cancelSource = new CancellationTokenSource(); + var options = new TokenCredentialOptions(); - var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken)); - - var options = new TokenCredentialOptions() { Transport = mockTransport }; + var cred = InstrumentClient( + new DeviceCodeCredential( + (code, cancelToken) => VerifyDeviceCodeAndCancel(code, expectedCode, cancelSource), + null, + ClientId, + options, + null, + mockMsalClient)); - var cred = InstrumentClient(new DeviceCodeCredential((code, cancelToken) => VerifyDeviceCodeAndCancel(code, expectedCode, cancelSource), null, ClientId, options: options)); - - var ex = Assert.CatchAsync(async () => await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" }), cancelSource.Token)); + var ex = Assert.CatchAsync( + async () => await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope }), cancelSource.Token)); await Task.CompletedTask; } @@ -154,22 +172,12 @@ public async Task AuthenticateWithDeviceCodeMockVerifyMsalCancellationAsync() [Test] public async Task AuthenticateWithDeviceCodeMockVerifyCallbackCancellationAsync() { - var expectedCode = Guid.NewGuid().ToString(); - - var expectedToken = Guid.NewGuid().ToString(); - - var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken)); - - var options = new TokenCredentialOptions() { Transport = mockTransport }; - - var cancelSource = new CancellationTokenSource(100); - - var cred = InstrumentClient(new DeviceCodeCredential(VerifyDeviceCodeCallbackCancellationToken, ClientId, options: options)); - - var getTokenTask = cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" }), cancelSource.Token); - - var ex = Assert.CatchAsync(async () => await getTokenTask); + var cancelSource = new CancellationTokenSource(); + cancelSource.Cancel(); + var cred = InstrumentClient(new DeviceCodeCredential(VerifyDeviceCodeCallbackCancellationToken, null, ClientId, options, null, mockMsalClient)); + var ex = Assert.CatchAsync( + async () => await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope }), cancelSource.Token)); await Task.CompletedTask; } @@ -177,251 +185,31 @@ public async Task AuthenticateWithDeviceCodeMockVerifyCallbackCancellationAsync( public void AuthenticateWithDeviceCodeCallbackThrowsAsync() { IdentityTestEnvironment testEnvironment = new IdentityTestEnvironment(); - var expectedCode = Guid.NewGuid().ToString(); - - var expectedToken = Guid.NewGuid().ToString(); - var cancelSource = new CancellationTokenSource(); + var options = new TokenCredentialOptions(); - var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken)); - - var options = new TokenCredentialOptions() { Transport = mockTransport }; - - var cred = InstrumentClient(new DeviceCodeCredential(ThrowingDeviceCodeCallback, ClientId, options: options)); - - var ex = Assert.ThrowsAsync(async () => await cred.GetTokenAsync(new TokenRequestContext(new string[] { testEnvironment.KeyvaultScope }), cancelSource.Token)); + var cred = InstrumentClient(new DeviceCodeCredential(ThrowingDeviceCodeCallback, null, ClientId, options, null, mockMsalClient)); + var ex = Assert.ThrowsAsync( + async () => await cred.GetTokenAsync(new TokenRequestContext(new[] { testEnvironment.KeyvaultScope }), cancelSource.Token)); Assert.IsInstanceOf(typeof(MockException), ex.InnerException); } [Test] public void DisableAutomaticAuthenticationException() { - var expectedCode = Guid.NewGuid().ToString(); + var cred = InstrumentClient( + new DeviceCodeCredential( + new DeviceCodeCredentialOptions + { + DisableAutomaticAuthentication = true, DeviceCodeCallback = (code, cancelToken) => VerifyDeviceCode(code, expectedCode) + })); - var cred = InstrumentClient(new DeviceCodeCredential(new DeviceCodeCredentialOptions { DisableAutomaticAuthentication = true, DeviceCodeCallback = (code, cancelToken) => VerifyDeviceCode(code, expectedCode) })); - - var expTokenRequestContext = new TokenRequestContext(new string[] { "https://vault.azure.net/.default" }, Guid.NewGuid().ToString()); + var expTokenRequestContext = new TokenRequestContext(new[] { Scope }, Guid.NewGuid().ToString()); var ex = Assert.ThrowsAsync(async () => await cred.GetTokenAsync(expTokenRequestContext)); Assert.AreEqual(expTokenRequestContext, ex.TokenRequestContext); } - - private MockResponse ProcessMockRequest(MockRequest mockRequest, string code, string token) - { - IdentityTestEnvironment testEnvironment = new IdentityTestEnvironment(); - string requestUrl = mockRequest.Uri.ToUri().AbsoluteUri; - - if (requestUrl.StartsWith(new Uri(new Uri(testEnvironment.AuthorityHostUrl), "common/discovery/instance").ToString())) - { - return DiscoveryInstanceResponse; - } - - if (requestUrl.StartsWith(new Uri(new Uri(testEnvironment.AuthorityHostUrl), "organizations/v2.0/.well-known/openid-configuration").ToString())) - { - return OpenIdConfigurationResponse; - } - - if (requestUrl.StartsWith("https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode")) - { - return CreateDeviceCodeResponse(code); - } - - if (requestUrl.StartsWith("https://login.microsoftonline.com/organizations/oauth2/v2.0/token")) - { - return CreateTokenResponse(code, token); - } - - throw new InvalidOperationException(); - } - - private MockResponse CreateTokenResponse(string code, string token) - { - lock (_requestedCodesLock) - { - if (_requestedCodes.Add(code)) - { - return AuthorizationPendingResponse; - } - else - { - return CreateAuthorizationResponse(token); - } - } - } - - private MockResponse CreateDeviceCodeResponse(string code) - { - MockResponse response = new MockResponse(200).WithContent($@"{{ - ""user_code"": ""{code}"", - ""device_code"": ""{code}_{code}"", - ""verification_uri"": ""https://microsoft.com/devicelogin"", - ""expires_in"": 900, - ""interval"": 1, - ""message"": ""{code}"" -}}"); - - return response; - } - - private MockResponse CreateAuthorizationResponse(string accessToken) - { - MockResponse response = new MockResponse(200).WithContent(@$"{{ - ""token_type"": ""Bearer"", - ""scope"": ""https://vault.azure.net/user_impersonation https://vault.azure.net/.default"", - ""expires_in"": 3600, - ""ext_expires_in"": 3600, - ""access_token"": ""{accessToken}"", - ""refresh_token"": ""eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9-eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ-SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"", - ""foci"": ""1"", - ""id_token"": ""eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InU0T2ZORlBId0VCb3NIanRyYXVPYlY4NExuWSJ9.eyJhdWQiOiJFMDFCNUY2NC03OEY1LTRGODgtQjI4Mi03QUUzOUI4QUM0QkQiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vRDEwOUI0NkUtM0E5Ri00NDQwLTg2MjItMjVEQjQxOTg1MDUxL3YyLjAiLCJpYXQiOjE1NjM5OTA0MDEsIm5iZiI6MTU2Mzk5MDQwMSwiZXhwIjoxNTYzOTk0MzAxLCJhaW8iOiJRMVV3TlV4YVNFeG9aak5uUWpSd00zcFRNamRrV2pSTFNVcEdMMVV3TWt0a2FrZDFTRkJVVlZwMmVFODRNMFZ0VXk4Mlp6TjJLM1JrVVVzeVQyVXhNamxJWTNKQ1p6MGlMQ0p1WVcxbElqb2lVMk52ZEhRZ1UyTiIsIm5hbWUiOiJTb21lIFVzZXIiLCJvaWQiOiIyQ0M5QzNBOC0yNTA5LTQyMEYtQjAwQi02RTczQkM1MURDQjUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzb21ldXNlckBtaWNyb3NvZnQuY29tIiwic3ViIjoiQ0p6ZFdJaU9pSkdXakY0UVVOS1JFRnBRWFp6IiwidGlkIjoiMjRGRTMxMUYtN0E3MS00RjgzLTkxNkEtOTQ3OEQ0NUMwNDI3IiwidXRpIjoidFFqSTRNaTAzUVVVek9VSTRRVU0wUWtRaUxDSnBjM01pT2lKb2RIUiIsInZlciI6IjIuMCJ9.eVyG1AL8jwnTo3m9mGsV4EDHa_8PN6rRPEN9E3cQzxNoPU9HZTFt1SgOnLB7n1a4J_E3iVoZ3VB5I-NdDBESRdlg1k4XlrWqtisxl3I7pvWVFZKEhwHYYQ_nZITNeCb48LfZNz-Mr4EZeX6oyUymha5tOomikBLLxP78LOTlbGQiFn9AjtV0LtMeoiDf-K9t-kgU-XwsVjCyFKFBQhcyv7zaBEpeA-Kzh3-HG7wZ-geteM5y-JF97nD_rJ8ow1FmvtDYy6MVcwuNTv2YYT8dn8s-SGB4vpNNignlL0QgYh2P2cIrPdhZVc2iQqYTn_FK_UFPqyb_MZSjl1QkXVhgJA"", - ""client_info"": ""eyJ1aWQiOiIyQ0M5QzNBOC0yNTA5LTQyMEYtQjAwQi02RTczQkM1MURDQjUiLCJ1dGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3In0"" -}}"); - return response; - } - - private static MockResponse DiscoveryInstanceResponse - { - get - { - return new MockResponse(200).WithContent(@" -{ - ""tenant_discovery_endpoint"": ""https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"", - ""api-version"": ""1.1"", - ""metadata"": [ - { - ""preferred_network"": ""login.microsoftonline.com"", - ""preferred_cache"": ""login.windows.net"", - ""aliases"": [ - ""login.microsoftonline.com"", - ""login.windows.net"", - ""login.microsoft.com"", - ""sts.windows.net"" - ] -}, - { - ""preferred_network"": ""login.partner.microsoftonline.cn"", - ""preferred_cache"": ""login.partner.microsoftonline.cn"", - ""aliases"": [ - ""login.partner.microsoftonline.cn"", - ""login.chinacloudapi.cn"" - ] - }, - { - ""preferred_network"": ""login.microsoftonline.de"", - ""preferred_cache"": ""login.microsoftonline.de"", - ""aliases"": [ - ""login.microsoftonline.de"" - ] - }, - { - ""preferred_network"": ""login.microsoftonline.us"", - ""preferred_cache"": ""login.microsoftonline.us"", - ""aliases"": [ - ""login.microsoftonline.us"", - ""login.usgovcloudapi.net"" - ] - }, - { - ""preferred_network"": ""login-us.microsoftonline.com"", - ""preferred_cache"": ""login-us.microsoftonline.com"", - ""aliases"": [ - ""login-us.microsoftonline.com"" - ] - } - ] -}"); - } - } - - private static MockResponse OpenIdConfigurationResponse - { - get - { - return new MockResponse(200).WithContent(@"{ - ""authorization_endpoint"": ""https://login.microsoftonline.com/common/oauth2/v2.0/authorize"", - ""token_endpoint"": ""https://login.microsoftonline.com/common/oauth2/v2.0/token"", - ""token_endpoint_auth_methods_supported"": [ - ""client_secret_post"", - ""private_key_jwt"", - ""client_secret_basic"" - ], - ""jwks_uri"": ""https://login.microsoftonline.com/common/discovery/v2.0/keys"", - ""response_modes_supported"": [ - ""query"", - ""fragment"", - ""form_post"" - ], - ""subject_types_supported"": [ - ""pairwise"" - ], - ""id_token_signing_alg_values_supported"": [ - ""RS256"" - ], - ""http_logout_supported"": true, - ""frontchannel_logout_supported"": true, - ""end_session_endpoint"": ""https://login.microsoftonline.com/common/oauth2/v2.0/logout"", - ""response_types_supported"": [ - ""code"", - ""id_token"", - ""code id_token"", - ""id_token token"" - ], - ""scopes_supported"": [ - ""openid"", - ""profile"", - ""email"", - ""offline_access"" - ], - ""issuer"": ""https://login.microsoftonline.com/{tenantid}/v2.0"", - ""claims_supported"": [ - ""sub"", - ""iss"", - ""cloud_instance_name"", - ""cloud_instance_host_name"", - ""cloud_graph_host_name"", - ""msgraph_host"", - ""aud"", - ""exp"", - ""iat"", - ""auth_time"", - ""acr"", - ""nonce"", - ""preferred_username"", - ""name"", - ""tid"", - ""ver"", - ""at_hash"", - ""c_hash"", - ""email"" - ], - ""request_uri_parameter_supported"": false, - ""userinfo_endpoint"": ""https://graph.microsoft.com/oidc/userinfo"", - ""tenant_region_scope"": null, - ""cloud_instance_name"": ""microsoftonline.com"", - ""cloud_graph_host_name"": ""graph.windows.net"", - ""msgraph_host"": ""graph.microsoft.com"", - ""rbac_url"": ""https://pas.windows.net"" -}"); - } - } - - private static MockResponse AuthorizationPendingResponse - { - get - { - return new MockResponse(404).WithContent(@"{ - ""error"": ""authorization_pending"", - ""error_description"": ""AADSTS70016: Pending end-user authorization.\r\nTrace ID: c40ce91e-5009-4e64-9a10-7732b2500100\r\nCorrelation ID: 73a2edae-f747-44da-8ebf-7cba565fe49d\r\nTimestamp: 2019-07-24 17:49:13Z"", - ""error_codes"": [ - 70016 - ], - ""timestamp"": ""2019-07-24 17:49:13Z"", - ""trace_id"": ""c40ce91e-5009-4e64-9a10-7732b2500100"", - ""correlation_id"": ""73a2edae-f747-44da-8ebf-7cba565fe49d"" -}"); - } - } } } diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs index 373c852b4406d..9f460fcad0034 100644 --- a/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs +++ b/sdk/identity/Azure.Identity/tests/Mock/MockMsalConfidentialClient.cs @@ -10,52 +10,52 @@ namespace Azure.Identity.Tests.Mock { internal class MockMsalConfidentialClient : MsalConfidentialClient { - private Func _factory = null; - private Func _silentFactory = null; - private Func _authcodeFactory = null; + internal Func ClientFactory { get; set; } + internal Func> SilentFactory { get; set; } + internal Func AuthcodeFactory { get; set; } public MockMsalConfidentialClient(AuthenticationResult result) { - _factory = (_, _) => result; - _silentFactory = (_, _, _) => result; - _authcodeFactory = (_, _, _) => result; + ClientFactory = (_, _) => result; + SilentFactory = (_, _, _) => new ValueTask(result); + AuthcodeFactory = (_, _, _) => result; } public MockMsalConfidentialClient(Exception exception) { - _factory = (_, _) => throw exception; - _silentFactory = (_, _, _) => throw exception; - _authcodeFactory = (_, _, _) => throw exception; + ClientFactory = (_, _) => throw exception; + SilentFactory = (_, _, _) => throw exception; + AuthcodeFactory = (_, _, _) => throw exception; } - public MockMsalConfidentialClient(Func factory) + public MockMsalConfidentialClient(Func clientFactory) { - _factory = factory; + ClientFactory = clientFactory; } - public MockMsalConfidentialClient(Func factory) + public MockMsalConfidentialClient(Func> factory) { - _silentFactory = factory; + SilentFactory = factory; } public MockMsalConfidentialClient(Func factory) { - _authcodeFactory = factory; + AuthcodeFactory = factory; } public override ValueTask AcquireTokenForClientAsync(string[] scopes, string tenantId, bool async, CancellationToken cancellationToken) { - return new(_factory(scopes, tenantId)); + return new(ClientFactory(scopes, tenantId)); } - public override ValueTask AcquireTokenSilentAsync( + public override async ValueTask AcquireTokenSilentAsync( string[] scopes, AuthenticationAccount account, string tenantId, bool async, CancellationToken cancellationToken) { - return new(_silentFactory(scopes, tenantId, account)); + return await SilentFactory(scopes, tenantId, account); } public override ValueTask AcquireTokenByAuthorizationCodeAsync( @@ -65,7 +65,7 @@ public override ValueTask AcquireTokenByAuthorizationCodeA bool async, CancellationToken cancellationToken) { - return new(_authcodeFactory(scopes, tenantId, code)); + return new(AuthcodeFactory(scopes, tenantId, code)); } } } diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs index 02d5a8471fbf2..76de18ea16a00 100644 --- a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Security; using System.Threading; using System.Threading.Tasks; @@ -12,6 +14,7 @@ namespace Azure.Identity.Tests.Mock { internal class MockMsalPublicClient : MsalPublicClient { + public DeviceCodeResult DeviceCodeResult { get; set; } = GetDeviceCodeResult(); public List Accounts { get; set; } public Func AuthFactory { get; set; } @@ -24,7 +27,7 @@ internal class MockMsalPublicClient : MsalPublicClient public Func ExtendedSilentAuthFactory { get; set; } - public Func DeviceCodeAuthFactory { get; set; } + public Func DeviceCodeAuthFactory { get; set; } public Func PubClientAppFactory { get; set; } @@ -33,7 +36,14 @@ protected override ValueTask> GetAccountsCoreAsync(bool async, Ca return new(Accounts); } - protected override ValueTask AcquireTokenByUsernamePasswordCoreAsync(string[] scopes, string claims, string username, SecureString password, string tenantId, bool async, CancellationToken cancellationToken) + protected override ValueTask AcquireTokenByUsernamePasswordCoreAsync( + string[] scopes, + string claims, + string username, + SecureString password, + string tenantId, + bool async, + CancellationToken cancellationToken) { Func factory = UserPassAuthFactory ?? AuthFactory; @@ -45,7 +55,13 @@ protected override ValueTask AcquireTokenByUsernamePasswor throw new NotImplementedException(); } - protected override ValueTask AcquireTokenInteractiveCoreAsync(string[] scopes, string claims, Prompt prompt, string loginHint, bool async, CancellationToken cancellationToken) + protected override ValueTask AcquireTokenInteractiveCoreAsync( + string[] scopes, + string claims, + Prompt prompt, + string loginHint, + bool async, + CancellationToken cancellationToken) { var interactiveAuthFactory = InteractiveAuthFactory; var authFactory = AuthFactory; @@ -62,7 +78,12 @@ protected override ValueTask AcquireTokenInteractiveCoreAs throw new NotImplementedException(); } - protected override ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, IAccount account, bool async, CancellationToken cancellationToken) + protected override ValueTask AcquireTokenSilentCoreAsync( + string[] scopes, + string claims, + IAccount account, + bool async, + CancellationToken cancellationToken) { if (ExtendedSilentAuthFactory != null) { @@ -79,7 +100,13 @@ protected override ValueTask AcquireTokenSilentCoreAsync(s throw new NotImplementedException(); } - protected override ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, AuthenticationRecord record, string tenantId, bool async, CancellationToken cancellationToken) + protected override ValueTask AcquireTokenSilentCoreAsync( + string[] scopes, + string claims, + AuthenticationRecord record, + string tenantId, + bool async, + CancellationToken cancellationToken) { Func factory = SilentAuthFactory ?? AuthFactory; @@ -91,13 +118,19 @@ protected override ValueTask AcquireTokenSilentCoreAsync(s throw new NotImplementedException(); } - protected override ValueTask AcquireTokenWithDeviceCodeCoreAsync(string[] scopes, string claims, Func deviceCodeCallback, bool async, CancellationToken cancellationToken) + protected override async ValueTask AcquireTokenWithDeviceCodeCoreAsync( + string[] scopes, + string claims, + Func deviceCodeCallback, + bool async, + CancellationToken cancellationToken) { - Func factory = DeviceCodeAuthFactory ?? AuthFactory; - - if (factory != null) + if (DeviceCodeAuthFactory != null) { - return new ValueTask(factory(scopes, null)); + await deviceCodeCallback(DeviceCodeResult); + var result = DeviceCodeAuthFactory(new DeviceCodeInfo(DeviceCodeResult), cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + return result; } throw new NotImplementedException(); @@ -117,5 +150,19 @@ protected override ValueTask CreateClientCoreAsync(str return new ValueTask(PubClientAppFactory(clientCapabilities)); } + + internal static DeviceCodeResult GetDeviceCodeResult( + string userCode = "userCode", + string deviceCode = "deviceCode", + string verificationUrl = "https://localhost", + DateTimeOffset expiresOn = new(), + long interval = 0, + string clientId = "clientId", + ISet scopes = null) + { + var ctor = typeof(DeviceCodeResult).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault(); + var message = $"To sign in, use a web browser to open the page {verificationUrl} and enter the code {deviceCode} to authenticate."; + return (DeviceCodeResult)ctor.Invoke(new object[] { userCode, deviceCode, verificationUrl, expiresOn, interval, message, clientId, scopes ?? new HashSet() }); + } } } From fed5849281f3fe86c4e38afa3fff94cf50c19689 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 26 May 2021 10:59:31 -0500 Subject: [PATCH 08/39] tests --- .../src/AuthorizationCodeCredential.cs | 2 +- .../src/VisualStudioCredential.cs | 9 +-- .../tests/AuthorizationCodeCredentialTests.cs | 20 +++--- .../tests/AzureCliCredentialTests.cs | 15 +++- .../tests/DefaultAzureCredentialLiveTests.cs | 1 + .../tests/DeviceCodeCredentialTests.cs | 13 ++-- .../tests/Mock/MockMsalPublicClient.cs | 26 ++++++- .../tests/VisualStudioCodeCredentialTests.cs | 70 ++++++++++++++++++- .../tests/VisualStudioCredentialTests.cs | 19 ++++- 9 files changed, 148 insertions(+), 27 deletions(-) diff --git a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs index 42e1525f68fd2..bbc5dfcfbb6a8 100644 --- a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs @@ -108,7 +108,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC if (_record is null) { AuthenticationResult result = await _client - .AcquireTokenByAuthorizationCodeAsync(requestContext.Scopes, tenantId, _authCode, async, cancellationToken) + .AcquireTokenByAuthorizationCodeAsync(requestContext.Scopes, _authCode, tenantId, async, cancellationToken) .ConfigureAwait(false); _record = new AuthenticationRecord(result, _clientId); diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs index 19fe488888958..daa3efd1b9e0a 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs @@ -46,8 +46,9 @@ public VisualStudioCredential(VisualStudioCredentialOptions options) : this(opti _options = options; } - internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IFileSystemService fileSystem, IProcessService processService) + internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IFileSystemService fileSystem, IProcessService processService, VisualStudioCredentialOptions options = null) { + _options = options; tenantIdOptionProvided = tenantId != null; _tenantId = tenantId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(null); @@ -149,8 +150,8 @@ private async Task RunProcessesAsync(List process private List GetProcessStartInfos(VisualStudioTokenProvider[] visualStudioTokenProviders, string resource, TokenRequestContext requestContext, CancellationToken cancellationToken) { - List processStartInfos = new List(); - StringBuilder arguments = new StringBuilder(); + List processStartInfos = new(); + StringBuilder arguments = new(); foreach (VisualStudioTokenProvider tokenProvider in visualStudioTokenProviders) { @@ -168,7 +169,7 @@ private List GetProcessStartInfos(VisualStudioTokenProvider[] var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); if (tenantId != default) { - arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(_tenantId ?? requestContext.TenantIdHint); + arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(tenantId); } // Add the arguments set in the token provider file. diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 3709022a39c28..496a020f0a034 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -16,8 +16,9 @@ public class AuthorizationCodeCredentialTests : ClientTestBase { private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; private const string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; private const string Scope = "https://vault.azure.net/.default"; - private TokenCredentialOptions options = new TokenCredentialOptions(); + private TokenCredentialOptions options; private string authCode; private string expectedToken; private DateTimeOffset expiresOn; @@ -63,20 +64,21 @@ public void TestSetup() } [Test] - public async Task AuthenticateWithAuthCodeMockAsync([Values(null, TenantId)] string tenantId, [Values(true, false)] bool preferHint) + public async Task AuthenticateWithAuthCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { - expectedTenantId = preferHint ? tenantId : TenantId; + options = new TokenCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; - AuthorizationCodeCredential cred = InstrumentClient( - new AuthorizationCodeCredential(TenantId, ClientId, clientSecret, authCode, options, mockMsalClient)); + AuthorizationCodeCredential cred = InstrumentClient(new AuthorizationCodeCredential(TenantId, ClientId, clientSecret, authCode, options, mockMsalClient)); - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); + AccessToken token = await cred.GetTokenAsync(context); - Assert.AreEqual(token.Token, expectedToken); + Assert.AreEqual(token.Token, expectedToken, "Should be the expected token value"); - AccessToken token2 = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); + AccessToken token2 = await cred.GetTokenAsync(context); - Assert.AreEqual(token.Token, expectedToken); + Assert.AreEqual(token2.Token, expectedToken, "Should be the expected token value"); } } } diff --git a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs index 99c348a2973f2..149b67fff24b8 100644 --- a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs @@ -14,19 +14,28 @@ namespace Azure.Identity.Tests { public class AzureCliCredentialTests : ClientTestBase { + private const string Scope = "https://vault.azure.net/.default"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + public AzureCliCredentialTests(bool isAsync) : base(isAsync) { } [Test] - public async Task AuthenticateWithCliCredential() + public async Task AuthenticateWithCliCredential([Values(null, TenantIdHint)] string tenantId) { + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + string expectedTenantId = TenantIdResolver.Resolve(null, context, null) ; var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli(); var testProcess = new TestProcess { Output = processOutput }; - AzureCliCredential credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); - AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); + AzureCliCredential credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true))); + AccessToken actualToken = await credential.GetTokenAsync(context); Assert.AreEqual(expectedToken, actualToken.Token); Assert.AreEqual(expectedExpiresOn, actualToken.ExpiresOn); + if (expectedTenantId != null) + { + Assert.That(testProcess.StartInfo.Arguments, Does.Contain(expectedTenantId)); + } } [Test] diff --git a/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs b/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs index 9cf491736114c..3ca360d6e3ff5 100644 --- a/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs +++ b/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialLiveTests.cs @@ -67,6 +67,7 @@ public async Task DefaultAzureCredential_UseVisualStudioCodeCredential() ExcludeInteractiveBrowserCredential = true, ExcludeSharedTokenCacheCredential = true, ExcludeManagedIdentityCredential = true, + ExcludeVisualStudioCredential = true, VisualStudioCodeTenantId = TestEnvironment.TestTenantId }); diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index 26ab66a046373..3e452b7ad167d 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -24,6 +24,7 @@ public DeviceCodeCredentialTests(bool isAsync) : base(isAsync) private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; private const string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; private const string Scope = "https://vault.azure.net/.default"; private readonly HashSet _requestedCodes = new HashSet(); private TokenCredentialOptions options = new TokenCredentialOptions(); @@ -103,17 +104,19 @@ public void TestSetup() } [Test] - public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantId)] string tenantId) + public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { - expectedTenantId = tenantId; + options = new TokenCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; var cred = InstrumentClient( - new DeviceCodeCredential((code, _) => VerifyDeviceCode(code, expectedCode), tenantId, ClientId, options, null, mockMsalClient)); + new DeviceCodeCredential((code, _) => VerifyDeviceCode(code, expectedCode), TenantId, ClientId, options, null, mockMsalClient)); - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); + AccessToken token = await cred.GetTokenAsync(context); Assert.AreEqual(token.Token, expectedToken); - token = await cred.GetTokenAsync(new TokenRequestContext(new[] { Scope })); + token = await cred.GetTokenAsync(context); Assert.AreEqual(token.Token, expectedToken); } diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs index 76de18ea16a00..0d9d2a5a00ad7 100644 --- a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs @@ -31,6 +31,13 @@ internal class MockMsalPublicClient : MsalPublicClient public Func PubClientAppFactory { get; set; } + public Func RefreshTokenFactory { get; set; } + protected override ValueTask> GetAccountsCoreAsync(bool async, CancellationToken cancellationToken) { return new(Accounts); @@ -151,6 +158,22 @@ protected override ValueTask CreateClientCoreAsync(str return new ValueTask(PubClientAppFactory(clientCapabilities)); } + protected override ValueTask AcquireTokenByRefreshTokenCoreAsync( + string[] scopes, + string claims, + string refreshToken, + AzureCloudInstance azureCloudInstance, + string tenant, + bool async, + CancellationToken cancellationToken) + { + if (RefreshTokenFactory == null) + { + throw new NotImplementedException(); + } + return new ValueTask(RefreshTokenFactory(scopes, claims, refreshToken, azureCloudInstance, tenant, async, cancellationToken)); + } + internal static DeviceCodeResult GetDeviceCodeResult( string userCode = "userCode", string deviceCode = "deviceCode", @@ -162,7 +185,8 @@ internal static DeviceCodeResult GetDeviceCodeResult( { var ctor = typeof(DeviceCodeResult).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault(); var message = $"To sign in, use a web browser to open the page {verificationUrl} and enter the code {deviceCode} to authenticate."; - return (DeviceCodeResult)ctor.Invoke(new object[] { userCode, deviceCode, verificationUrl, expiresOn, interval, message, clientId, scopes ?? new HashSet() }); + return (DeviceCodeResult)ctor.Invoke( + new object[] { userCode, deviceCode, verificationUrl, expiresOn, interval, message, clientId, scopes ?? new HashSet() }); } } } diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs index 9b20021591991..6687a76217741 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs @@ -1,27 +1,95 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Threading; +using System.Threading.Tasks; using Azure.Core; using Azure.Core.TestFramework; +using Azure.Identity.Tests.Mock; +using Microsoft.Identity.Client; using NUnit.Framework; namespace Azure.Identity.Tests { public class VisualStudioCodeCredentialTests : ClientTestBase { + private string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string Scope = "https://vault.azure.net/.default"; + private string expectedCode; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalPublicClient mockMsalClient; + private DeviceCodeResult deviceCodeResult; + private string expectedTenantId; + public VisualStudioCodeCredentialTests(bool isAsync) : base(isAsync) + { } + + [SetUp] + public void TestSetup() { + expectedTenantId = null; + expectedCode = Guid.NewGuid().ToString(); + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + mockMsalClient = new MockMsalPublicClient(); + deviceCodeResult = MockMsalPublicClient.GetDeviceCodeResult(deviceCode: expectedCode); + mockMsalClient.DeviceCodeResult = deviceCodeResult; + var result = new AuthenticationResult( + expectedToken, + false, + null, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, + Guid.NewGuid(), + null, + "Bearer"); + mockMsalClient.RefreshTokenFactory = (_, _,_, _, tenant, _, _) => + { + Assert.AreEqual(expectedTenantId, tenant, "TenantId passed to msal should match"); + return result; + }; + } + + [Test] + public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + { + var environment = new IdentityTestEnvironment(); + var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), PreferTenantIdChallengeHint = preferHint }; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(environment.TenantId, context, options); + + VisualStudioCodeCredential credential = InstrumentClient( + new VisualStudioCodeCredential( + options, + null, + mockMsalClient, + CredentialTestHelpers.CreateFileSystemForVisualStudioCode(environment), + new TestVscAdapter("VS Code Azure", "AzureCloud", expectedToken))); + + var actualToken = await credential.GetTokenAsync(context, CancellationToken.None); + + Assert.AreEqual(expectedToken, actualToken.Token, "Token should match"); + Assert.AreEqual(expiresOn, actualToken.ExpiresOn, "expiresOn should match"); } [Test] public void AdfsTenantThrowsCredentialUnavailable() { var options = new VisualStudioCodeCredentialOptions { TenantId = "adfs", Transport = new MockTransport() }; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope } }); + string expectedTenantId = TenantIdResolver.Resolve(null, context, options); VisualStudioCodeCredential credential = InstrumentClient(new VisualStudioCodeCredential(options)); - Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://vault.azure.net/.default" }), CancellationToken.None)); + Assert.ThrowsAsync( + async () => await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://vault.azure.net/.default" }), CancellationToken.None)); } } } diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs index f41ddf8b6bc7b..e20d708e284f3 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs @@ -15,19 +15,32 @@ namespace Azure.Identity.Tests [RunOnlyOnPlatforms(Windows = true)] // VisualStudioCredential works only on Windows public class VisualStudioCredentialTests : ClientTestBase { + private string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string Scope = "https://vault.azure.net/.default"; + private string expectedTenantId; + public VisualStudioCredentialTests(bool isAsync) : base(isAsync) { } [Test] - public async Task AuthenticateWithVsCredential() + public async Task AuthenticateWithVsCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudio(); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForVisualStudio(); var testProcess = new TestProcess { Output = processOutput }; - var credential = InstrumentClient(new VisualStudioCredential(default, default, fileSystem, new TestProcessService(testProcess))); - var token = await credential.GetTokenAsync(new TokenRequestContext(new[]{"https://vault.azure.net/"}), CancellationToken.None); + var options = new VisualStudioCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var credential = InstrumentClient(new VisualStudioCredential(TenantId, default, fileSystem, new TestProcessService(testProcess, true), options)); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + + var token = await credential.GetTokenAsync(context, default); Assert.AreEqual(token.Token, expectedToken); Assert.AreEqual(token.ExpiresOn, expectedExpiresOn); + if (expectedTenantId != null) + { + Assert.That(testProcess.StartInfo.Arguments, Does.Contain(expectedTenantId)); + } } [Test] From afd81f1e301a397f8fd10b98a5162806f7e38125 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 26 May 2021 13:51:31 -0500 Subject: [PATCH 09/39] tests and changelogs --- sdk/identity/Azure.Identity/CHANGELOG.md | 1 + .../src/InteractiveBrowserCredential.cs | 3 +- .../Azure.Identity/src/MsalPublicClient.cs | 47 ++-- .../src/SharedTokenCacheCredential.cs | 19 +- .../tests/AzurePowerShellCredentialsTests.cs | 23 +- .../tests/ClientCertificateCredentialTests.cs | 62 ++++- .../tests/ClientSecretCredentialTests.cs | 74 ++++-- .../InteractiveBrowserCredentialTests.cs | 116 +++++++-- .../tests/Mock/MockMsalPublicClient.cs | 10 +- .../tests/SharedTokenCacheCredentialTests.cs | 230 ++++++++++++++---- .../tests/UsernamePasswordCredentialTests.cs | 62 ++++- .../Azure.Storage.Blobs.Batch/CHANGELOG.md | 2 + .../CHANGELOG.md | 2 + sdk/storage/Azure.Storage.Blobs/CHANGELOG.md | 2 + sdk/storage/Azure.Storage.Common/CHANGELOG.md | 2 + .../Azure.Storage.Files.DataLake/CHANGELOG.md | 2 + .../Azure.Storage.Files.Shares/CHANGELOG.md | 2 + sdk/storage/Azure.Storage.Queues/CHANGELOG.md | 2 + 18 files changed, 529 insertions(+), 132 deletions(-) diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 91676622756ad..0e3acaa36cf93 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes and improvements - Added `LoginHint` property to `InteractiveBrowserCredentialOptions` which allows a user name to be pre-selected for interactive logins. Setting this option skips the account selection prompt and immediately attempts to login with the specified account. +- TenantId values returned from service challenge responses can now be used to request tokens from the correct tenantId. ## 1.4.0 (2021-05-12) diff --git a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs index bc09887620c09..d3eb60db5c96f 100644 --- a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs +++ b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs @@ -217,8 +217,9 @@ private async Task GetTokenViaBrowserLoginAsync(TokenRequestContext _ => Prompt.NoPrompt }; + var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record?.TenantId, context, _options); AuthenticationResult result = await Client - .AcquireTokenInteractiveAsync(context.Scopes, context.Claims, prompt, LoginHint, async, cancellationToken) + .AcquireTokenInteractiveAsync(context.Scopes, context.Claims, prompt, LoginHint, tenantId, async, cancellationToken) .ConfigureAwait(false); Record = new AuthenticationRecord(result, ClientId); diff --git a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs index 617d5819c6ec4..df9f77d2c177e 100644 --- a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs @@ -67,16 +67,23 @@ protected virtual async ValueTask> GetAccountsCoreAsync(bool asyn return await GetAccountsAsync(client, async).ConfigureAwait(false); } - public async ValueTask AcquireTokenSilentAsync(string[] scopes, string claims, IAccount account, bool async, CancellationToken cancellationToken) + public async ValueTask AcquireTokenSilentAsync(string[] scopes, string claims, IAccount account, string tenantId, bool async, CancellationToken cancellationToken) { - return await AcquireTokenSilentCoreAsync(scopes, claims, account, async, cancellationToken).ConfigureAwait(false); + return await AcquireTokenSilentCoreAsync(scopes, claims, account, tenantId, async, cancellationToken).ConfigureAwait(false); } - protected virtual async ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, IAccount account, bool async, CancellationToken cancellationToken) + protected virtual async ValueTask AcquireTokenSilentCoreAsync(string[] scopes, string claims, IAccount account, string tenantId, bool async, CancellationToken cancellationToken) { IPublicClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); - return await client.AcquireTokenSilent(scopes, account) - .WithClaims(claims) + var builder = client.AcquireTokenSilent(scopes, account) + .WithClaims(claims); + + if (tenantId != null) + { + builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId); + } + + return await builder .ExecuteAsync(async, cancellationToken) .ConfigureAwait(false); } @@ -100,7 +107,7 @@ protected virtual async ValueTask AcquireTokenSilentCoreAs .ConfigureAwait(false); } - public async ValueTask AcquireTokenInteractiveAsync(string[] scopes, string claims, Prompt prompt, string loginHint, bool async, CancellationToken cancellationToken) + public async ValueTask AcquireTokenInteractiveAsync(string[] scopes, string claims, Prompt prompt, string loginHint, string tenantId, bool async, CancellationToken cancellationToken) { #pragma warning disable AZC0109 // Misuse of 'async' parameter. if (!async && !IdentityCompatSwitches.DisableInteractiveBrowserThreadpoolExecution) @@ -114,33 +121,35 @@ public async ValueTask AcquireTokenInteractiveAsync(string AzureIdentityEventSource.Singleton.InteractiveAuthenticationExecutingOnThreadPool(); #pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). - return Task.Run(async () => await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, true, cancellationToken).ConfigureAwait(false)).GetAwaiter().GetResult(); + return Task.Run(async () => await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, tenantId, true, cancellationToken).ConfigureAwait(false)).GetAwaiter().GetResult(); #pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). } AzureIdentityEventSource.Singleton.InteractiveAuthenticationExecutingInline(); - return await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, async, cancellationToken).ConfigureAwait(false); + return await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, tenantId, async, cancellationToken).ConfigureAwait(false); } - protected virtual async ValueTask AcquireTokenInteractiveCoreAsync(string[] scopes, string claims, Prompt prompt, string loginHint, bool async, CancellationToken cancellationToken) + protected virtual async ValueTask AcquireTokenInteractiveCoreAsync(string[] scopes, string claims, Prompt prompt, string loginHint, string tenantId, bool async, CancellationToken cancellationToken) { IPublicClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); - return loginHint switch - { - null => await client.AcquireTokenInteractive(scopes) + var builder = client.AcquireTokenInteractive(scopes) .WithPrompt(prompt) .WithClaims(claims) - .ExecuteAsync(async, cancellationToken) - .ConfigureAwait(false), - _ => await client.AcquireTokenInteractive(scopes) .WithPrompt(prompt) - .WithClaims(claims) - .WithLoginHint(loginHint) + .WithClaims(claims); + if (loginHint != null) + { + builder.WithLoginHint(loginHint); + } + if (tenantId != null) + { + builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId); + } + return await builder .ExecuteAsync(async, cancellationToken) - .ConfigureAwait(false) - }; + .ConfigureAwait(false); } public async ValueTask AcquireTokenByUsernamePasswordAsync(string[] scopes, string claims, string username, SecureString password, string tenantId, bool async, CancellationToken cancellationToken) diff --git a/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs b/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs index 507513be6cf4d..85ea4f4d2ba83 100644 --- a/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs +++ b/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs @@ -30,6 +30,7 @@ public class SharedTokenCacheCredential : TokenCredential private readonly bool _skipTenantValidation; private readonly AuthenticationRecord _record; private readonly AsyncLockWithValue _accountAsyncLock; + private readonly TokenCredentialOptions _options; internal MsalPublicClient Client { get; } @@ -69,17 +70,12 @@ internal SharedTokenCacheCredential(string tenantId, string username, TokenCrede internal SharedTokenCacheCredential(string tenantId, string username, TokenCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client) { _tenantId = tenantId; - _username = username; - _skipTenantValidation = (options as SharedTokenCacheCredentialOptions)?.EnableGuestTenantAuthentication ?? false; - _record = (options as SharedTokenCacheCredentialOptions)?.AuthenticationRecord; - _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - + _options = options; Client = client ?? new MsalPublicClient(_pipeline, tenantId, (options as SharedTokenCacheCredentialOptions)?.ClientId ?? Constants.DeveloperSignOnClientId, null, (options as ITokenCacheOptions) ?? s_DefaultCacheOptions); - _accountAsyncLock = new AsyncLockWithValue(); } @@ -111,8 +107,9 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC try { - IAccount account = await GetAccountAsync(async, cancellationToken).ConfigureAwait(false); - AuthenticationResult result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, account, async, cancellationToken).ConfigureAwait(false); + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + IAccount account = await GetAccountAsync(tenantId, async, cancellationToken).ConfigureAwait(false); + AuthenticationResult result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, account, tenantId, async, cancellationToken).ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } catch (MsalUiRequiredException ex) @@ -128,7 +125,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC } } - private async ValueTask GetAccountAsync(bool async, CancellationToken cancellationToken) + private async ValueTask GetAccountAsync(string tenantId, bool async, CancellationToken cancellationToken) { using var asyncLock = await _accountAsyncLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait(false); if (asyncLock.HasValue) @@ -157,14 +154,14 @@ private async ValueTask GetAccountAsync(bool async, CancellationToken (string.IsNullOrEmpty(_username) || string.Compare(a.Username, _username, StringComparison.OrdinalIgnoreCase) == 0) && // if _skipTenantValidation is false and _tenantId is specified it must match the account - (_skipTenantValidation || string.IsNullOrEmpty(_tenantId) || string.Compare(a.HomeAccountId?.TenantId, _tenantId, StringComparison.OrdinalIgnoreCase) == 0) + (_skipTenantValidation || string.IsNullOrEmpty(tenantId) || string.Compare(a.HomeAccountId?.TenantId, tenantId, StringComparison.OrdinalIgnoreCase) == 0) ) .ToList(); if (_skipTenantValidation && filteredAccounts.Count > 1) { filteredAccounts = filteredAccounts - .Where(a => string.IsNullOrEmpty(_tenantId) || string.Compare(a.HomeAccountId?.TenantId, _tenantId, StringComparison.OrdinalIgnoreCase) == 0) + .Where(a => string.IsNullOrEmpty(tenantId) || string.Compare(a.HomeAccountId?.TenantId, tenantId, StringComparison.OrdinalIgnoreCase) == 0) .ToList(); } diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs index 31f21f5c229a7..6d0d87a782ea7 100644 --- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs @@ -4,11 +4,13 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.Text; using System.Threading; using System.Threading.Tasks; using Azure.Core; using Azure.Core.TestFramework; using Azure.Identity.Tests.Mock; +using Microsoft.CodeAnalysis.Operations; using NUnit.Framework; namespace Azure.Identity.Tests @@ -18,21 +20,36 @@ public class AzurePowerShellCredentialsTests : ClientTestBase private string tokenXML = "Kg==5/11/2021 8:20:03 PM +00:0072f988bf-86f1-41af-91ab-2d7cd011db47chriss@microsoft.comBearer"; + private const string Scope = "https://vault.azure.net/.default"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + public AzurePowerShellCredentialsTests(bool isAsync) : base(isAsync) { } [Test] - public async Task AuthenticateWithAzurePowerShellCredential() + public async Task AuthenticateWithAzurePowerShellCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + string expectedTenantId = TenantIdResolver.Resolve(null, context, null) ; var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30)); var testProcess = new TestProcess { Output = processOutput }; AzurePowerShellCredential credential = InstrumentClient( - new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); - AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); + new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true))); + AccessToken actualToken = await credential.GetTokenAsync(context); Assert.AreEqual(expectedToken, actualToken.Token); Assert.AreEqual(expectedExpiresOn, actualToken.ExpiresOn); + if (expectedTenantId != null) + { + var iStart = testProcess.StartInfo.Arguments.IndexOf("EncodedCommand"); + iStart = testProcess.StartInfo.Arguments.IndexOf('\"', iStart) + 1; + var iEnd = testProcess.StartInfo.Arguments.IndexOf('\"', iStart); + var commandString = testProcess.StartInfo.Arguments.Substring(iStart, iEnd - iStart); + var b = Convert.FromBase64String(commandString); + commandString = Encoding.Unicode.GetString(b); + Assert.That(commandString, Does.Contain(expectedTenantId)); + } } [Test] diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs index c21c91409615a..a9387c9cf152c 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs @@ -12,12 +12,23 @@ using Azure.Core; using Azure.Core.TestFramework; using Azure.Identity.Tests.Mock; +using Microsoft.Identity.Client; using NUnit.Framework; namespace Azure.Identity.Tests { public class ClientCertificateCredentialTests : ClientTestBase { + private const string Scope = "https://vault.azure.net/.default"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + private const string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalConfidentialClient mockMsalClient; + private string expectedTenantId; + private TokenCredentialOptions options; + public ClientCertificateCredentialTests(bool isAsync) : base(isAsync) { } @@ -143,29 +154,56 @@ public void VerifyClientCertificateCredentialException(bool usePemFile) } [Test] - public void UsesTenantIdHint([Values("1234", null)] string tenantId, [Values(true, false)] bool usePemFile) + public async Task UsesTenantIdHint( + [Values(true, false)] bool usePemFile, + [Values(null, TenantIdHint)] string tenantId, + [Values(true, false)] bool preferHint) { - string expectedInnerExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalConfidentialClient(new MockClientException(expectedInnerExMessage)); - var expectedTenantId = Guid.NewGuid().ToString(); - var expectedClientId = Guid.NewGuid().ToString(); + TestSetup(); + options.PreferTenantIdChallengeHint = preferHint; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); var certificatePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); var certificatePathPem = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pem"); var mockCert = new X509Certificate2(certificatePath); ClientCertificateCredential credential = InstrumentClient( usePemFile - ? new ClientCertificateCredential(expectedTenantId, expectedClientId, certificatePathPem, default, default, mockMsalClient) - : new ClientCertificateCredential(expectedTenantId, expectedClientId, mockCert, default, default, mockMsalClient) + ? new ClientCertificateCredential(TenantId, ClientId, certificatePathPem, options, default, mockMsalClient) + : new ClientCertificateCredential(TenantId, ClientId, mockCert, options, default, mockMsalClient) ); - var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - - Assert.IsNotNull(ex.InnerException); + var token = await credential.GetTokenAsync(context); - Assert.IsInstanceOf(typeof(MockClientException), ex.InnerException); + Assert.AreEqual(token.Token, expectedToken, "Should be the expected token value"); + } - Assert.AreEqual(expectedInnerExMessage, ex.InnerException.Message); + public void TestSetup() + { + options = new TokenCredentialOptions(); + expectedTenantId = null; + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + var result = new AuthenticationResult( + expectedToken, + false, + null, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, + Guid.NewGuid(), + null, + "Bearer"); + + Func clientFactory = (_, _tenantId) => + { + Assert.AreEqual(expectedTenantId, _tenantId); + return result; + }; + mockMsalClient = new MockMsalConfidentialClient(clientFactory); } } } diff --git a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs index 98e443a4be820..b94ac8268553a 100644 --- a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs @@ -9,6 +9,7 @@ using Azure.Core; using Azure.Core.TestFramework; using Azure.Identity.Tests.Mock; +using Microsoft.Identity.Client; using Moq; using NUnit.Framework; @@ -16,17 +17,24 @@ namespace Azure.Identity.Tests { public class ClientSecretCredentialTests : ClientTestBase { + private const string Scope = "https://vault.azure.net/.default"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + private const string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalConfidentialClient mockMsalClient; + private string expectedTenantId; + private TokenCredentialOptions options; + public ClientSecretCredentialTests(bool isAsync) : base(isAsync) - { - } + { } [Test] public void VerifyCtorParametersValidation() { var tenantId = Guid.NewGuid().ToString(); - var clientId = Guid.NewGuid().ToString(); - var secret = "secret"; Assert.Throws(() => new ClientSecretCredential(null, clientId, secret)); @@ -34,21 +42,32 @@ public void VerifyCtorParametersValidation() Assert.Throws(() => new ClientSecretCredential(tenantId, clientId, null)); } + [Test] + public async Task UsesTenantIdHint( + [Values(true, false)] bool usePemFile, + [Values(null, TenantIdHint)] string tenantId, + [Values(true, false)] bool preferHint) + { + TestSetup(); + options.PreferTenantIdChallengeHint = preferHint; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + ClientSecretCredential client = InstrumentClient(new ClientSecretCredential(expectedTenantId, ClientId, "secret", options, null, mockMsalClient)); + + var token = await client.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); + + Assert.AreEqual(token.Token, expectedToken, "Should be the expected token value"); + } + [Test] public async Task VerifyClientSecretRequestFailedAsync() { var response = new MockResponse(400); - response.SetContent($"{{ \"error_code\": \"InvalidSecret\", \"message\": \"The specified client_secret is incorrect\" }}"); - var mockTransport = new MockTransport(response); - var options = new TokenCredentialOptions() { Transport = mockTransport }; - var expectedTenantId = Guid.NewGuid().ToString(); - var expectedClientId = Guid.NewGuid().ToString(); - var expectedClientSecret = "secret"; ClientSecretCredential client = InstrumentClient(new ClientSecretCredential(expectedTenantId, expectedClientId, expectedClientSecret, options)); @@ -62,20 +81,45 @@ public async Task VerifyClientSecretRequestFailedAsync() public async Task VerifyClientSecretCredentialExceptionAsync() { string expectedInnerExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalConfidentialClient(new MockClientException(expectedInnerExMessage)); - - var credential = InstrumentClient(new ClientSecretCredential(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), default, default, mockMsalClient)); + var credential = InstrumentClient( + new ClientSecretCredential(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), default, default, mockMsalClient)); var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); Assert.IsNotNull(ex.InnerException); - Assert.IsInstanceOf(typeof(MockClientException), ex.InnerException); - Assert.AreEqual(expectedInnerExMessage, ex.InnerException.Message); await Task.CompletedTask; } + + public void TestSetup() + { + options = new TokenCredentialOptions(); + expectedTenantId = null; + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + var result = new AuthenticationResult( + expectedToken, + false, + null, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, + Guid.NewGuid(), + null, + "Bearer"); + + Func clientFactory = (_, _tenantId) => + { + Assert.AreEqual(expectedTenantId, _tenantId); + return result; + }; + mockMsalClient = new MockMsalConfidentialClient(clientFactory); + } } } diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index 08770c0701923..8d0bf32698155 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -8,22 +8,33 @@ using Microsoft.Identity.Client; using NUnit.Framework; using System; +using System.Threading; using System.Threading.Tasks; namespace Azure.Identity.Tests { public class InteractiveBrowserCredentialTests : ClientTestBase { + private string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string Scope = "https://vault.azure.net/.default"; + private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + private string expectedCode; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalPublicClient mockMsalClient; + private DeviceCodeResult deviceCodeResult; + private string expectedTenantId; + public InteractiveBrowserCredentialTests(bool isAsync) : base(isAsync) - { - } + { } [Test] public async Task InteractiveBrowserAcquireTokenInteractiveException() { string expInnerExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient { AuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); } }; + var mockMsalClient = new MockMsalPublicClient { AuthFactory = (_, _) => { throw new MockClientException(expInnerExMessage); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); @@ -47,8 +58,8 @@ public async Task InteractiveBrowserAcquireTokenSilentException() var mockMsalClient = new MockMsalPublicClient { - AuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, - SilentAuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); } + AuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, + SilentAuthFactory = (_, _) => { throw new MockClientException(expInnerExMessage); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); @@ -79,8 +90,8 @@ public async Task InteractiveBrowserRefreshException() var mockMsalClient = new MockMsalPublicClient { - AuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, - SilentAuthFactory = (_,_) => { throw new MsalUiRequiredException("errorCode", "message"); } + AuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); }, + SilentAuthFactory = (_, _) => { throw new MsalUiRequiredException("errorCode", "message"); } }; var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); @@ -91,7 +102,7 @@ public async Task InteractiveBrowserRefreshException() Assert.AreEqual(expExpiresOn, token.ExpiresOn); - mockMsalClient.AuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); }; + mockMsalClient.AuthFactory = (_, _) => { throw new MockClientException(expInnerExMessage); }; var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); @@ -148,7 +159,7 @@ public async Task LoginHint([Values(null, "fring@contoso.com")] string loginHint { var mockMsalClient = new MockMsalPublicClient { - InteractiveAuthFactory = (_, _, prompt, hintArg, _, _) => + InteractiveAuthFactory = (_, _, prompt, hintArg, _, _, _) => { Assert.AreEqual(loginHint == null ? Prompt.SelectAccount : Prompt.NoPrompt, prompt); Assert.AreEqual(loginHint, hintArg); @@ -166,24 +177,29 @@ private async Task ValidateSyncWorkaroundCompatSwitch(bool expectedThreadPoolExe bool threadPoolExec = false; bool inlineExec = false; - AzureEventSourceListener listener = new AzureEventSourceListener((args, text) => - { - if (args.EventName == "InteractiveAuthenticationExecutingOnThreadPool") + AzureEventSourceListener listener = new AzureEventSourceListener( + (args, text) => { - threadPoolExec = true; - } - if (args.EventName == "InteractiveAuthenticationExecutingInline") + if (args.EventName == "InteractiveAuthenticationExecutingOnThreadPool") + { + threadPoolExec = true; + } + if (args.EventName == "InteractiveAuthenticationExecutingInline") + { + inlineExec = true; + } + }, + System.Diagnostics.Tracing.EventLevel.Informational); + + var mockClient = new MockMsalPublicClient + { + AuthFactory = (_, _) => { - inlineExec = true; + return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(5)); } - }, System.Diagnostics.Tracing.EventLevel.Informational); - - var mockMsalClient = new MockMsalPublicClient - { - AuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(5)); } }; - var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockMsalClient)); + var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", default, default, mockClient)); AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); @@ -202,5 +218,61 @@ public void DisableAutomaticAuthenticationException() Assert.AreEqual(expTokenRequestContext, ex.TokenRequestContext); } + + [Test] + public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + { + TestSetup(); + var options = new InteractiveBrowserCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + + var credential = InstrumentClient( + new InteractiveBrowserCredential( + TenantId, + ClientId, + options, + null, + mockMsalClient)); + + var actualToken = await credential.GetTokenAsync(context, CancellationToken.None); + + Assert.AreEqual(expectedToken, actualToken.Token, "Token should match"); + Assert.AreEqual(expiresOn, actualToken.ExpiresOn, "expiresOn should match"); + } + + public void TestSetup() + { + expectedTenantId = null; + expectedCode = Guid.NewGuid().ToString(); + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + mockMsalClient = new MockMsalPublicClient(); + deviceCodeResult = MockMsalPublicClient.GetDeviceCodeResult(deviceCode: expectedCode); + mockMsalClient.DeviceCodeResult = deviceCodeResult; + var result = new AuthenticationResult( + expectedToken, + false, + null, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, + Guid.NewGuid(), + null, + "Bearer"); + mockMsalClient.InteractiveAuthFactory = (_, _, _, _, tenant, _, _) => + { + Assert.AreEqual(expectedTenantId, tenant, "TenantId passed to msal should match"); + return result; + }; + mockMsalClient.SilentAuthFactory = (_, tenant) => + { + Assert.AreEqual(expectedTenantId, tenant, "TenantId passed to msal should match"); + return result; + }; + } } } diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs index 0d9d2a5a00ad7..a7ba8ac4f3762 100644 --- a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs @@ -21,11 +21,11 @@ internal class MockMsalPublicClient : MsalPublicClient public Func UserPassAuthFactory { get; set; } - public Func InteractiveAuthFactory { get; set; } + public Func InteractiveAuthFactory { get; set; } public Func SilentAuthFactory { get; set; } - public Func ExtendedSilentAuthFactory { get; set; } + public Func ExtendedSilentAuthFactory { get; set; } public Func DeviceCodeAuthFactory { get; set; } @@ -67,6 +67,7 @@ protected override ValueTask AcquireTokenInteractiveCoreAs string claims, Prompt prompt, string loginHint, + string tenantId, bool async, CancellationToken cancellationToken) { @@ -75,7 +76,7 @@ protected override ValueTask AcquireTokenInteractiveCoreAs if (interactiveAuthFactory != null) { - return new ValueTask(interactiveAuthFactory(scopes, claims, prompt, loginHint, async, cancellationToken)); + return new ValueTask(interactiveAuthFactory(scopes, claims, prompt, loginHint, tenantId, async, cancellationToken)); } if (authFactory != null) { @@ -89,12 +90,13 @@ protected override ValueTask AcquireTokenSilentCoreAsync( string[] scopes, string claims, IAccount account, + string tenantId, bool async, CancellationToken cancellationToken) { if (ExtendedSilentAuthFactory != null) { - return new ValueTask(ExtendedSilentAuthFactory(scopes, account, async, cancellationToken)); + return new ValueTask(ExtendedSilentAuthFactory(scopes, account, tenantId, async, cancellationToken)); } Func factory = SilentAuthFactory ?? AuthFactory; diff --git a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs index 73f0689e52894..7104bce6ee4b2 100644 --- a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs @@ -14,11 +14,20 @@ namespace Azure.Identity.Tests { public class SharedTokenCacheCredentialTests : ClientTestBase { + private string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string Scope = "https://vault.azure.net/.default"; + private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + private string expectedCode; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalPublicClient mockMsal; + private DeviceCodeResult deviceCodeResult; + private string expectedTenantId; private const string expectedUsername = "mockuser@mockdomain.com"; public SharedTokenCacheCredentialTests(bool isAsync) : base(isAsync) - { - } + { } [Test] public async Task VerifyAuthenticationRecordOption() @@ -31,13 +40,18 @@ public async Task VerifyAuthenticationRecordOption() var options = new SharedTokenCacheCredentialOptions { - AuthenticationRecord = new AuthenticationRecord(expectedUsername, expectedEnvironment, expectedHomeId, Guid.NewGuid().ToString(), Guid.NewGuid().ToString()) + AuthenticationRecord = new AuthenticationRecord( + expectedUsername, + expectedEnvironment, + expectedHomeId, + Guid.NewGuid().ToString(), + Guid.NewGuid().ToString()) }; var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("nonexpecteduser@mockdomain.com") }, - ExtendedSilentAuthFactory = (_, account, __, ___) => + ExtendedSilentAuthFactory = (_, account, _, _, _) => { Assert.AreEqual(expectedUsername, account.Username); @@ -67,7 +81,7 @@ public async Task OneAccountNoTentantNoUsername() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, null, null, null, mockMsalClient)); @@ -88,7 +102,7 @@ public async Task OneMatchingAccountUsernameOnly() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, expectedUsername, null, null, mockMsalClient)); @@ -109,7 +123,7 @@ public async Task OneMatchingAccountUsernameDifferentCasing() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, expectedUsername, null, null, mockMsalClient)); @@ -130,8 +144,14 @@ public async Task OneMatchingAccountTenantIdOnly() string tenantId = Guid.NewGuid().ToString(); var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount(expectedUsername, nonMatchedTenantId), new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername, tenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = + new List + { + new MockAccount(expectedUsername, nonMatchedTenantId), + new MockAccount("fakeuser@fakedomain.com"), + new MockAccount(expectedUsername, tenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, null, null, null, mockMsalClient)); @@ -151,8 +171,14 @@ public async Task OneMatchingAccountTenantIdAndUsername() string tenantId = Guid.NewGuid().ToString(); var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount(expectedUsername, Guid.NewGuid().ToString()), new MockAccount("fakeuser@fakedomain.com"), new MockAccount(expectedUsername, tenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = + new List + { + new MockAccount(expectedUsername, Guid.NewGuid().ToString()), + new MockAccount("fakeuser@fakedomain.com"), + new MockAccount(expectedUsername, tenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, expectedUsername, null, null, mockMsalClient)); @@ -173,7 +199,7 @@ public async Task NoAccounts() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // without username @@ -217,8 +243,12 @@ public async Task MultipleAccountsNoTenantIdOrUsername() var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = + new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, null, null, null, mockMsalClient)); @@ -240,8 +270,12 @@ public async Task NoMatchingAccountsUsernameOnly() var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = + new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // with username @@ -249,7 +283,9 @@ public async Task NoMatchingAccountsUsernameOnly() var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(ex.Message, $"SharedTokenCacheCredential authentication unavailable. No account matching the specified username: {expectedUsername} was found in the cache."); + Assert.AreEqual( + ex.Message, + $"SharedTokenCacheCredential authentication unavailable. No account matching the specified username: {expectedUsername} was found in the cache."); await Task.CompletedTask; } @@ -265,8 +301,12 @@ public async Task NoMatchingAccountsTenantIdOnly() var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = + new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // with username @@ -274,7 +314,9 @@ public async Task NoMatchingAccountsTenantIdOnly() var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(ex.Message, $"SharedTokenCacheCredential authentication unavailable. No account matching the specified tenantId: {tenantId} was found in the cache."); + Assert.AreEqual( + ex.Message, + $"SharedTokenCacheCredential authentication unavailable. No account matching the specified tenantId: {tenantId} was found in the cache."); await Task.CompletedTask; } @@ -290,8 +332,12 @@ public async Task NoMatchingAccountsTenantIdAndUsername() var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = + new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount("madeupuser@madeupdomain.com", madeupuserTenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; // with username @@ -299,7 +345,9 @@ public async Task NoMatchingAccountsTenantIdAndUsername() var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(ex.Message, $"SharedTokenCacheCredential authentication unavailable. No account matching the specified username: {expectedUsername} tenantId: {tenantId} was found in the cache."); + Assert.AreEqual( + ex.Message, + $"SharedTokenCacheCredential authentication unavailable. No account matching the specified username: {expectedUsername} tenantId: {tenantId} was found in the cache."); await Task.CompletedTask; } @@ -315,15 +363,22 @@ public async Task MultipleMatchingAccountsUsernameOnly() var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId), new MockAccount(expectedUsername, mockuserGuestTenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), + new MockAccount(expectedUsername, mockuserTenantId), + new MockAccount(expectedUsername, mockuserGuestTenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(null, expectedUsername, null, null, mockMsalClient)); var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(ex.Message, $"SharedTokenCacheCredential authentication unavailable. Multiple accounts matching the specified username: {expectedUsername} were found in the cache."); + Assert.AreEqual( + ex.Message, + $"SharedTokenCacheCredential authentication unavailable. Multiple accounts matching the specified username: {expectedUsername} were found in the cache."); await Task.CompletedTask; } @@ -339,15 +394,22 @@ public async Task MultipleMatchingAccountsTenantIdOnly() var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId), new MockAccount(expectedUsername, mockuserGuestTenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), + new MockAccount(expectedUsername, mockuserTenantId), + new MockAccount(expectedUsername, mockuserGuestTenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(mockuserGuestTenantId, null, null, null, mockMsalClient)); var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(ex.Message, $"SharedTokenCacheCredential authentication unavailable. Multiple accounts matching the specified tenantId: {mockuserGuestTenantId} were found in the cache."); + Assert.AreEqual( + ex.Message, + $"SharedTokenCacheCredential authentication unavailable. Multiple accounts matching the specified tenantId: {mockuserGuestTenantId} were found in the cache."); await Task.CompletedTask; } @@ -363,15 +425,22 @@ public async Task MultipleMatchingAccountsUsernameAndTenantId() var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId), new MockAccount(expectedUsername, mockuserTenantId) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), + new MockAccount(expectedUsername, mockuserTenantId), + new MockAccount(expectedUsername, mockuserTenantId) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; var credential = InstrumentClient(new SharedTokenCacheCredential(mockuserTenantId, expectedUsername, null, null, mockMsalClient)); var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(ex.Message, $"SharedTokenCacheCredential authentication unavailable. Multiple accounts matching the specified username: {expectedUsername} tenantId: {mockuserTenantId} were found in the cache."); + Assert.AreEqual( + ex.Message, + $"SharedTokenCacheCredential authentication unavailable. Multiple accounts matching the specified username: {expectedUsername} tenantId: {mockuserTenantId} were found in the cache."); await Task.CompletedTask; } @@ -388,11 +457,22 @@ public async Task MultipleMatchingAccountsUsernameAndTenantIdWithEnableGuestTena var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), new MockAccount(expectedUsername, mockuserTenantId1), new MockAccount(expectedUsername, mockuserTenantId2) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = new List + { + new MockAccount("fakeuser@fakedomain.com", fakeuserTenantId), + new MockAccount(expectedUsername, mockuserTenantId1), + new MockAccount(expectedUsername, mockuserTenantId2) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; - var credential = InstrumentClient(new SharedTokenCacheCredential(mockuserTenantId1, expectedUsername, new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, null, mockMsalClient)); + var credential = InstrumentClient( + new SharedTokenCacheCredential( + mockuserTenantId1, + expectedUsername, + new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, + null, + mockMsalClient)); var token = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); @@ -410,7 +490,7 @@ public async Task UiRequiredException() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername) }, - SilentAuthFactory = (_,_) => { throw new MsalUiRequiredException("code", "message"); } + SilentAuthFactory = (_, _) => { throw new MsalUiRequiredException("code", "message"); } }; // with username @@ -418,7 +498,8 @@ public async Task UiRequiredException() var ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - var expErrorMessage = $"SharedTokenCacheCredential authentication unavailable. Token acquisition failed for user {expectedUsername}. Ensure that you have authenticated with a developer tool that supports Azure single sign on."; + var expErrorMessage = + $"SharedTokenCacheCredential authentication unavailable. Token acquisition failed for user {expectedUsername}. Ensure that you have authenticated with a developer tool that supports Azure single sign on."; Assert.AreEqual(expErrorMessage, ex.Message); @@ -434,10 +515,16 @@ public async Task MatchAnySingleTenantIdWithEnableGuestTenantAuthentication() var mockMsalClient = new MockMsalPublicClient { Accounts = new List { new MockAccount(expectedUsername, Guid.NewGuid().ToString()) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; - var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, null, new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, null, mockMsalClient)); + var credential = InstrumentClient( + new SharedTokenCacheCredential( + tenantId, + null, + new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, + null, + mockMsalClient)); AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); @@ -454,11 +541,21 @@ public async Task MatchAnyTenantIdWithEnableGuestTenantAuthenticationAndUsername string tenantId = Guid.NewGuid().ToString(); var mockMsalClient = new MockMsalPublicClient { - Accounts = new List { new MockAccount(expectedUsername, Guid.NewGuid().ToString()), new MockAccount("fakeuser@fakedomain.com", Guid.NewGuid().ToString()) }, - SilentAuthFactory = (_,_) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } + Accounts = + new List + { + new MockAccount(expectedUsername, Guid.NewGuid().ToString()), new MockAccount("fakeuser@fakedomain.com", Guid.NewGuid().ToString()) + }, + SilentAuthFactory = (_, _) => { return AuthenticationResultFactory.Create(accessToken: expToken, expiresOn: expExpiresOn); } }; - var credential = InstrumentClient(new SharedTokenCacheCredential(tenantId, expectedUsername, new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, null, mockMsalClient)); + var credential = InstrumentClient( + new SharedTokenCacheCredential( + tenantId, + expectedUsername, + new SharedTokenCacheCredentialOptions { EnableGuestTenantAuthentication = true }, + null, + mockMsalClient)); AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); @@ -476,5 +573,54 @@ public void ValidateClientIdSetOnMsalClient() Assert.AreEqual(clientId, credential.Client.ClientId); } + + [Test] + public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + { + TestSetup(); + var options = new SharedTokenCacheCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + mockMsal.Accounts = new List + { + new MockAccount(expectedUsername, expectedTenantId) + }; + + var credential = InstrumentClient(new SharedTokenCacheCredential(TenantId, null, options, null, mockMsal)); + + AccessToken token = await credential.GetTokenAsync(context); + + Assert.AreEqual(expectedToken, token.Token); + Assert.AreEqual(expiresOn, token.ExpiresOn); + } + + public void TestSetup() + { + expectedTenantId = null; + expectedCode = Guid.NewGuid().ToString(); + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + mockMsal = new MockMsalPublicClient(); + deviceCodeResult = MockMsalPublicClient.GetDeviceCodeResult(deviceCode: expectedCode); + mockMsal.DeviceCodeResult = deviceCodeResult; + var result = new AuthenticationResult( + expectedToken, + false, + null, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, + Guid.NewGuid(), + null, + "Bearer"); + mockMsal.ExtendedSilentAuthFactory = (_, _, tenant, _, _) => + { + Assert.AreEqual(expectedTenantId, tenant, "TenantId passed to msal should match"); + return result; + }; + } } } diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index c6b744e10f5aa..0578f4090d6e3 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -7,21 +7,32 @@ using NUnit.Framework; using System; using System.Threading.Tasks; +using Microsoft.Identity.Client; namespace Azure.Identity.Tests { public class UsernamePasswordCredentialTests : ClientTestBase { + private string TenantId = "a0287521-e002-0026-7112-207c0c000000"; + private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string Scope = "https://vault.azure.net/.default"; + private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + private string expectedCode; + private string expectedToken; + private DateTimeOffset expiresOn; + private MockMsalPublicClient mockMsal; + private DeviceCodeResult deviceCodeResult; + private string expectedTenantId; + public UsernamePasswordCredentialTests(bool isAsync) : base(isAsync) - { - } + { } [Test] public async Task VerifyMsalClientExceptionAsync() { string expInnerExMessage = Guid.NewGuid().ToString(); - var mockMsalClient = new MockMsalPublicClient() { UserPassAuthFactory = (_,_) => { throw new MockClientException(expInnerExMessage); } }; + var mockMsalClient = new MockMsalPublicClient() { UserPassAuthFactory = (_, _) => { throw new MockClientException(expInnerExMessage); } }; var username = Guid.NewGuid().ToString(); var password = Guid.NewGuid().ToString(); @@ -40,5 +51,50 @@ public async Task VerifyMsalClientExceptionAsync() await Task.CompletedTask; } + + [Test] + public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + { + TestSetup(); + var options = new UsernamePasswordCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + + var credential = InstrumentClient(new UsernamePasswordCredential("user", "password", TenantId, ClientId, options, null, mockMsal)); + + AccessToken token = await credential.GetTokenAsync(context); + + Assert.AreEqual(expectedToken, token.Token); + Assert.AreEqual(expiresOn, token.ExpiresOn); + } + + public void TestSetup() + { + expectedTenantId = null; + expectedCode = Guid.NewGuid().ToString(); + expectedToken = Guid.NewGuid().ToString(); + expiresOn = DateTimeOffset.Now.AddHours(1); + mockMsal = new MockMsalPublicClient(); + deviceCodeResult = MockMsalPublicClient.GetDeviceCodeResult(deviceCode: expectedCode); + mockMsal.DeviceCodeResult = deviceCodeResult; + var result = new AuthenticationResult( + expectedToken, + false, + null, + expiresOn, + expiresOn, + TenantId, + new MockAccount("username"), + null, + new[] { Scope }, + Guid.NewGuid(), + null, + "Bearer"); + mockMsal.UserPassAuthFactory = (_, tenant) => + { + Assert.AreEqual(expectedTenantId, tenant, "TenantId passed to msal should match"); + return result; + }; + } } } diff --git a/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md b/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md index 0c7b30b5b1889..1c799bfe113a4 100644 --- a/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md @@ -2,6 +2,8 @@ ## 12.6.0-beta.5 (Unreleased) +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on the ClientOptions called `DisableTenantDiscovery`. If set to true, the client will not attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ## 12.5.2 (2021-05-20) - This release contains bug fixes to improve quality. diff --git a/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md b/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md index be2135242ab82..dbd6939fa9666 100644 --- a/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md @@ -2,6 +2,8 @@ ## 12.0.0-preview.13 (Unreleased) +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on the ClientOptions called `DisableTenantDiscovery`. If set to true, the client will not attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ## 12.0.0-preview.12 (2021-05-12) - This release contains bug fixes to improve quality. diff --git a/sdk/storage/Azure.Storage.Blobs/CHANGELOG.md b/sdk/storage/Azure.Storage.Blobs/CHANGELOG.md index 585860f289bc6..83ca4f348059e 100644 --- a/sdk/storage/Azure.Storage.Blobs/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Blobs/CHANGELOG.md @@ -2,6 +2,8 @@ ## 12.9.0-beta.5 (Unreleased) +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on the ClientOptions called `DisableTenantDiscovery`. If set to true, the client will not attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ## 12.8.4 (2021-05-20) - Fixed bug where Client Side Encryption during large transactions (greater than max int value) would throw an exception. diff --git a/sdk/storage/Azure.Storage.Common/CHANGELOG.md b/sdk/storage/Azure.Storage.Common/CHANGELOG.md index a533521d026b1..5f4bfe2b02371 100644 --- a/sdk/storage/Azure.Storage.Common/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Common/CHANGELOG.md @@ -2,6 +2,8 @@ ## 12.8.0-beta.5 (Unreleased) +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on the ClientOptions called `DisableTenantDiscovery`. If set to true, the client will not attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ## 12.7.3 (2021-05-20) - This release contains bug fixes to improve quality. diff --git a/sdk/storage/Azure.Storage.Files.DataLake/CHANGELOG.md b/sdk/storage/Azure.Storage.Files.DataLake/CHANGELOG.md index 3f2ae318009b4..dfe0afd8f7144 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Files.DataLake/CHANGELOG.md @@ -2,6 +2,8 @@ ## 12.7.0-beta.5 (Unreleased) +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on the ClientOptions called `DisableTenantDiscovery`. If set to true, the client will not attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ## 12.6.2 (2021-05-20) - This release contains bug fixes to improve quality. diff --git a/sdk/storage/Azure.Storage.Files.Shares/CHANGELOG.md b/sdk/storage/Azure.Storage.Files.Shares/CHANGELOG.md index c004b4777521c..6b4ed19e9a618 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Files.Shares/CHANGELOG.md @@ -2,6 +2,8 @@ ## 12.7.0-beta.5 (Unreleased) +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on the ClientOptions called `DisableTenantDiscovery`. If set to true, the client will not attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ## 12.6.2 (2021-05-20) - This release contains bug fixes to improve quality. diff --git a/sdk/storage/Azure.Storage.Queues/CHANGELOG.md b/sdk/storage/Azure.Storage.Queues/CHANGELOG.md index f7c1679993444..090891705a78a 100644 --- a/sdk/storage/Azure.Storage.Queues/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Queues/CHANGELOG.md @@ -2,6 +2,8 @@ ## 12.7.0-beta.5 (Unreleased) +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on the ClientOptions called `DisableTenantDiscovery`. If set to true, the client will not attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ## 12.6.2 (2021-05-20) - This release contains bug fixes to improve quality. From 4c1c382c51537ead1a654d21e25f52b4690c9288 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 26 May 2021 16:36:45 -0500 Subject: [PATCH 10/39] fb --- .../Azure.Identity/src/TenantIdResolver.cs | 6 +-- .../src/TokenCredentialOptions.cs | 4 +- .../tests/AuthorizationCodeCredentialTests.cs | 2 +- .../tests/ClientCertificateCredentialTests.cs | 2 +- .../tests/ClientSecretCredentialTests.cs | 2 +- .../tests/DeviceCodeCredentialTests.cs | 2 +- .../InteractiveBrowserCredentialTests.cs | 2 +- .../tests/SharedTokenCacheCredentialTests.cs | 2 +- .../tests/TenantIdResolverTests.cs | 39 +++++++++++++++++++ .../tests/UsernamePasswordCredentialTests.cs | 2 +- .../tests/VisualStudioCodeCredentialTests.cs | 5 ++- .../tests/VisualStudioCredentialTests.cs | 2 +- ...BearerTokenChallengeAuthorizationPolicy.cs | 4 +- 13 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index 061108dea7dd5..dcd97920b29f1 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -16,10 +16,10 @@ internal static class TenantIdResolver /// public static string Resolve(string explicitTenantId, TokenRequestContext context, TokenCredentialOptions options) { - return options?.PreferTenantIdChallengeHint switch + return options?.PreferClientConfiguredTenantId switch { - true => context.TenantIdHint ?? explicitTenantId, - _ => explicitTenantId ?? context.TenantIdHint + true => explicitTenantId ?? context.TenantIdHint, + _ => context.TenantIdHint ?? explicitTenantId }; } } diff --git a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs index 9a98f2be91db4..10c22155f2f79 100644 --- a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs @@ -23,8 +23,8 @@ public Uri AuthorityHost } /// - /// If true, the tenant Id hint provided by a service authorization challenge will override a tenantId configured via the credential options. + /// If true, the tenant Id hint provided by a service authorization challenge will not override a tenantId configured via the credential options. /// - public bool PreferTenantIdChallengeHint { get; set; } + public bool PreferClientConfiguredTenantId { get; set; } } } diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 496a020f0a034..980bb8d7fcd49 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -66,7 +66,7 @@ public void TestSetup() [Test] public async Task AuthenticateWithAuthCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { - options = new TokenCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + options = new TokenCredentialOptions { PreferClientConfiguredTenantId = preferHint }; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs index a9387c9cf152c..2966a7e7da8f2 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs @@ -160,7 +160,7 @@ public async Task UsesTenantIdHint( [Values(true, false)] bool preferHint) { TestSetup(); - options.PreferTenantIdChallengeHint = preferHint; + options.PreferClientConfiguredTenantId = preferHint; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); var certificatePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); diff --git a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs index b94ac8268553a..4e58a8c7dec85 100644 --- a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs @@ -49,7 +49,7 @@ public async Task UsesTenantIdHint( [Values(true, false)] bool preferHint) { TestSetup(); - options.PreferTenantIdChallengeHint = preferHint; + options.PreferClientConfiguredTenantId = preferHint; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); ClientSecretCredential client = InstrumentClient(new ClientSecretCredential(expectedTenantId, ClientId, "secret", options, null, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index 3e452b7ad167d..acf7c5208e767 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -106,7 +106,7 @@ public void TestSetup() [Test] public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { - options = new TokenCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + options = new TokenCredentialOptions { PreferClientConfiguredTenantId = preferHint }; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; var cred = InstrumentClient( diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index 8d0bf32698155..d545f5df7d3fa 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -223,7 +223,7 @@ public void DisableAutomaticAuthenticationException() public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { TestSetup(); - var options = new InteractiveBrowserCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var options = new InteractiveBrowserCredentialOptions { PreferClientConfiguredTenantId = preferHint }; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); diff --git a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs index 7104bce6ee4b2..33541782b5ef6 100644 --- a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs @@ -578,7 +578,7 @@ public void ValidateClientIdSetOnMsalClient() public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { TestSetup(); - var options = new SharedTokenCacheCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var options = new SharedTokenCacheCredentialOptions { PreferClientConfiguredTenantId = preferHint }; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); mockMsal.Accounts = new List diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs new file mode 100644 index 0000000000000..e704ce11d392c --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Azure.Core; +using NUnit.Framework; + +namespace Azure.Identity.Tests +{ + public class TenantIdResolverTests + { + private const string TenantId = "clientTenant"; + private static TokenRequestContext Context_Hint = new(new TokenRequestContextOptions { TenantIdHint = "hint" }); + private static TokenRequestContext Context_NoHint = new(new TokenRequestContextOptions()); + private static TokenCredentialOptions Options_True = new() { PreferClientConfiguredTenantId = true }; + private static TokenCredentialOptions Options_False = new() { PreferClientConfiguredTenantId = false }; + + public static IEnumerable ResolveInputs() + { + yield return new object[] { TenantId, Context_Hint, Options_True, TenantId }; + yield return new object[] { TenantId, Context_NoHint, Options_True, TenantId }; + yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantIdHint }; + yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantIdHint }; + yield return new object[] { null, Context_Hint, Options_True, Context_Hint.TenantIdHint }; + yield return new object[] { null, Context_NoHint, Options_True, null }; + yield return new object[] { null, Context_Hint, Options_False, Context_Hint.TenantIdHint }; + yield return new object[] { null, Context_NoHint, Options_False, null }; + } + + [Test] + [TestCaseSource(nameof(ResolveInputs))] + public void Resolve(string tenantId, TokenRequestContext context, TokenCredentialOptions options, string expectedTenantId) + { + var result = TenantIdResolver.Resolve(tenantId, context, options); + + Assert.AreEqual(expectedTenantId, result); + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index 0578f4090d6e3..c2a9daa33d0f3 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -56,7 +56,7 @@ public async Task VerifyMsalClientExceptionAsync() public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { TestSetup(); - var options = new UsernamePasswordCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var options = new UsernamePasswordCredentialOptions { PreferClientConfiguredTenantId = preferHint }; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs index 6687a76217741..42f2727c0e7b1 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure.Core; @@ -58,10 +59,12 @@ public void TestSetup() } [Test] + [NonParallelizable] public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { + using var env = new TestEnvVar(new Dictionary {{"TENANT_ID", TenantId}}); var environment = new IdentityTestEnvironment(); - var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), PreferTenantIdChallengeHint = preferHint }; + var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), PreferClientConfiguredTenantId = preferHint }; var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(environment.TenantId, context, options); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs index e20d708e284f3..1ac4cead88c4c 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs @@ -28,7 +28,7 @@ public async Task AuthenticateWithVsCredential([Values(null, TenantIdHint)] stri var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudio(); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForVisualStudio(); var testProcess = new TestProcess { Output = processOutput }; - var options = new VisualStudioCredentialOptions { PreferTenantIdChallengeHint = preferHint }; + var options = new VisualStudioCredentialOptions { PreferClientConfiguredTenantId = preferHint }; var credential = InstrumentClient(new VisualStudioCredential(TenantId, default, fileSystem, new TestProcessService(testProcess, true), options)); var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index b1336016835f8..74d6dd43fce94 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -50,7 +50,7 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential /// protected override void AuthorizeRequest(HttpMessage message) { - if (tenantId != null || DisableTenantDiscovery) + if (tenantId != null && !DisableTenantDiscovery) { base.AuthorizeRequest(message); } @@ -59,7 +59,7 @@ protected override void AuthorizeRequest(HttpMessage message) /// protected override ValueTask AuthorizeRequestAsync(HttpMessage message) { - if (tenantId != null || DisableTenantDiscovery) + if (tenantId != null && !DisableTenantDiscovery) { return base.AuthorizeRequestAsync(message); } From 7a13b9a2ca4ad67964b6615c8a177d6b0356a42f Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 26 May 2021 17:15:30 -0500 Subject: [PATCH 11/39] export --- sdk/core/Azure.Core/api/Azure.Core.net461.cs | 12 ++++++++++++ sdk/core/Azure.Core/api/Azure.Core.net5.0.cs | 12 ++++++++++++ sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs | 12 ++++++++++++ sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs | 12 ++++++++++++ .../api/Azure.Identity.netstandard2.0.cs | 1 + 5 files changed, 49 insertions(+) diff --git a/sdk/core/Azure.Core/api/Azure.Core.net461.cs b/sdk/core/Azure.Core/api/Azure.Core.net461.cs index 5051389e87fa9..ab52a40f3c248 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net461.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net461.cs @@ -453,11 +453,23 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; + public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } + public string? TenantIdHint { get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct TokenRequestContextOptions + { + private object _dummy; + private int _dummyPrimitive; + public string? Claims { get { throw null; } set { } } + public string? ParentRequestId { get { throw null; } set { } } + public string[] Scopes { get { throw null; } set { } } + public string? TenantIdHint { get { throw null; } set { } } } } namespace Azure.Core.Cryptography diff --git a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs index ce6552027c5d2..740c2b1a0c288 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs @@ -453,11 +453,23 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; + public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } + public string? TenantIdHint { get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct TokenRequestContextOptions + { + private object _dummy; + private int _dummyPrimitive; + public string? Claims { get { throw null; } set { } } + public string? ParentRequestId { get { throw null; } set { } } + public string[] Scopes { get { throw null; } set { } } + public string? TenantIdHint { get { throw null; } set { } } } } namespace Azure.Core.Cryptography diff --git a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs index 5051389e87fa9..ab52a40f3c248 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs @@ -453,11 +453,23 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; + public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } + public string? TenantIdHint { get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct TokenRequestContextOptions + { + private object _dummy; + private int _dummyPrimitive; + public string? Claims { get { throw null; } set { } } + public string? ParentRequestId { get { throw null; } set { } } + public string[] Scopes { get { throw null; } set { } } + public string? TenantIdHint { get { throw null; } set { } } } } namespace Azure.Core.Cryptography diff --git a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs index 5051389e87fa9..ab52a40f3c248 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs @@ -453,11 +453,23 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; + public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } + public string? TenantIdHint { get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct TokenRequestContextOptions + { + private object _dummy; + private int _dummyPrimitive; + public string? Claims { get { throw null; } set { } } + public string? ParentRequestId { get { throw null; } set { } } + public string[] Scopes { get { throw null; } set { } } + public string? TenantIdHint { get { throw null; } set { } } } } namespace Azure.Core.Cryptography diff --git a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs index b1e3ddcaf08b8..1cfff2686dcf1 100644 --- a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs +++ b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs @@ -245,6 +245,7 @@ public partial class TokenCredentialOptions : Azure.Core.ClientOptions { public TokenCredentialOptions() { } public System.Uri AuthorityHost { get { throw null; } set { } } + public bool PreferClientConfiguredTenantId { get { throw null; } set { } } } public abstract partial class UnsafeTokenCacheOptions : Azure.Identity.TokenCachePersistenceOptions { From cca02baecbb0b803630ba30f97b8d68bb9f21897 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 26 May 2021 18:21:36 -0500 Subject: [PATCH 12/39] storage discovery defaults to false --- sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs | 2 +- .../Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs | 4 ++-- .../Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs | 2 +- sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs index bbbf9b61b9ee5..695d7baf60dd4 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs @@ -264,6 +264,6 @@ internal HttpPipeline Build(object credentials) } /// - public bool DisableTenantDiscovery { get; set; } + public bool DisableTenantDiscovery { get; set; } = true; } } diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index 74d6dd43fce94..b1336016835f8 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -50,7 +50,7 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential /// protected override void AuthorizeRequest(HttpMessage message) { - if (tenantId != null && !DisableTenantDiscovery) + if (tenantId != null || DisableTenantDiscovery) { base.AuthorizeRequest(message); } @@ -59,7 +59,7 @@ protected override void AuthorizeRequest(HttpMessage message) /// protected override ValueTask AuthorizeRequestAsync(HttpMessage message) { - if (tenantId != null && !DisableTenantDiscovery) + if (tenantId != null || DisableTenantDiscovery) { return base.AuthorizeRequestAsync(message); } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs index f44a3cba084ad..df2ab807335b8 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs @@ -114,7 +114,7 @@ public DataLakeClientOptions(ServiceVersion version = LatestVersion) public Uri GeoRedundantSecondaryUri { get; set; } /// - public bool DisableTenantDiscovery { get; set; } + public bool DisableTenantDiscovery { get; set; } = true; /// /// Add headers and query parameters in and diff --git a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs index 81ed95cb4b02f..cfd7024c2e5c7 100644 --- a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs +++ b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs @@ -124,7 +124,7 @@ public QueueClientOptions(ServiceVersion version = LatestVersion) public QueueMessageEncoding MessageEncoding { get; set; } = QueueMessageEncoding.None; /// - public bool DisableTenantDiscovery { get; set; } + public bool DisableTenantDiscovery { get; set; } = true; /// /// Optional. Performs the tasks needed when a message is received or peaked from the queue but cannot be decoded. From aba8ddef53f1b645800b4e88d0e20ab7f35f4e99 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 27 May 2021 17:30:05 -0500 Subject: [PATCH 13/39] Update sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs Co-authored-by: Pavel Krymets --- sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs index 5cb07322872f0..29ac43e894bfd 100644 --- a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs +++ b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs @@ -86,7 +86,7 @@ internal UsernamePasswordCredential(string username, string password, string ten _options = options; _username = username; - _password = password.ToSecureString() ; + _password = password.ToSecureString(); _clientId = clientId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _client = client ?? new MsalPublicClient(_pipeline, tenantId, clientId, null, options as ITokenCacheOptions); From 75a6fd3c93679ad7040f16f72ef57e7ed6a8b1be Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 3 Jun 2021 08:27:04 -0500 Subject: [PATCH 14/39] rename TenantIdHint --- sdk/core/Azure.Core/src/TokenRequestContext.cs | 8 ++++---- sdk/core/Azure.Core/src/TokenRequestContextOptions.cs | 2 +- sdk/identity/Azure.Identity/src/TenantIdResolver.cs | 4 ++-- .../tests/AuthorizationCodeCredentialTests.cs | 2 +- .../Azure.Identity/tests/AzureCliCredentialTests.cs | 2 +- .../tests/AzurePowerShellCredentialsTests.cs | 2 +- .../tests/ClientCertificateCredentialTests.cs | 2 +- .../tests/ClientSecretCredentialTests.cs | 2 +- .../Azure.Identity/tests/DeviceCodeCredentialTests.cs | 2 +- .../tests/InteractiveBrowserCredentialTests.cs | 2 +- .../tests/SharedTokenCacheCredentialTests.cs | 2 +- .../Azure.Identity/tests/TenantIdResolverTests.cs | 10 +++++----- .../tests/UsernamePasswordCredentialTests.cs | 2 +- .../tests/VisualStudioCodeCredentialTests.cs | 2 +- .../tests/VisualStudioCredentialTests.cs | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/sdk/core/Azure.Core/src/TokenRequestContext.cs b/sdk/core/Azure.Core/src/TokenRequestContext.cs index e470fd2c3ead8..ff045742cd1a4 100644 --- a/sdk/core/Azure.Core/src/TokenRequestContext.cs +++ b/sdk/core/Azure.Core/src/TokenRequestContext.cs @@ -18,7 +18,7 @@ public TokenRequestContext(string[] scopes, string? parentRequestId) Scopes = scopes; ParentRequestId = parentRequestId; Claims = default; - TenantIdHint = default; + TenantId = default; } /// @@ -32,7 +32,7 @@ public TokenRequestContext(string[] scopes, string? parentRequestId = default, s Scopes = scopes; ParentRequestId = parentRequestId; Claims = claims; - TenantIdHint = default; + TenantId = default; } /// @@ -44,7 +44,7 @@ public TokenRequestContext(TokenRequestContextOptions options) Scopes = options.Scopes; ParentRequestId = options.ParentRequestId; Claims = options.Claims; - TenantIdHint = options.TenantIdHint; + TenantId = options.TenantId; } /// @@ -65,6 +65,6 @@ public TokenRequestContext(TokenRequestContextOptions options) /// /// A hint to indicate which tenantId is preferred. /// - public string? TenantIdHint { get; } + public string? TenantId { get; } } } diff --git a/sdk/core/Azure.Core/src/TokenRequestContextOptions.cs b/sdk/core/Azure.Core/src/TokenRequestContextOptions.cs index 22341b68b5a28..16b45c49697f0 100644 --- a/sdk/core/Azure.Core/src/TokenRequestContextOptions.cs +++ b/sdk/core/Azure.Core/src/TokenRequestContextOptions.cs @@ -26,6 +26,6 @@ public struct TokenRequestContextOptions /// /// A hint to indicate which tenantId is preferred. /// - public string? TenantIdHint { get; set; } + public string? TenantId { get; set; } } } diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index dcd97920b29f1..c16d26da9ffeb 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -18,8 +18,8 @@ public static string Resolve(string explicitTenantId, TokenRequestContext contex { return options?.PreferClientConfiguredTenantId switch { - true => explicitTenantId ?? context.TenantIdHint, - _ => context.TenantIdHint ?? explicitTenantId + true => explicitTenantId ?? context.TenantId, + _ => context.TenantId ?? explicitTenantId }; } } diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 980bb8d7fcd49..24c4efded558c 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -67,7 +67,7 @@ public void TestSetup() public async Task AuthenticateWithAuthCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { options = new TokenCredentialOptions { PreferClientConfiguredTenantId = preferHint }; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; AuthorizationCodeCredential cred = InstrumentClient(new AuthorizationCodeCredential(TenantId, ClientId, clientSecret, authCode, options, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs index 149b67fff24b8..f87f326b518bb 100644 --- a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs @@ -22,7 +22,7 @@ public AzureCliCredentialTests(bool isAsync) : base(isAsync) { } [Test] public async Task AuthenticateWithCliCredential([Values(null, TenantIdHint)] string tenantId) { - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); string expectedTenantId = TenantIdResolver.Resolve(null, context, null) ; var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli(); diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs index 6d0d87a782ea7..b4eed293058d5 100644 --- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs @@ -29,7 +29,7 @@ public AzurePowerShellCredentialsTests(bool isAsync) : base(isAsync) [Test] public async Task AuthenticateWithAzurePowerShellCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); string expectedTenantId = TenantIdResolver.Resolve(null, context, null) ; var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30)); diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs index 2966a7e7da8f2..dc95a5e88c56a 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs @@ -161,7 +161,7 @@ public async Task UsesTenantIdHint( { TestSetup(); options.PreferClientConfiguredTenantId = preferHint; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); var certificatePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); var certificatePathPem = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pem"); diff --git a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs index 4e58a8c7dec85..8fe078a37eac2 100644 --- a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs @@ -50,7 +50,7 @@ public async Task UsesTenantIdHint( { TestSetup(); options.PreferClientConfiguredTenantId = preferHint; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); ClientSecretCredential client = InstrumentClient(new ClientSecretCredential(expectedTenantId, ClientId, "secret", options, null, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index acf7c5208e767..55761e5430a5c 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -107,7 +107,7 @@ public void TestSetup() public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { options = new TokenCredentialOptions { PreferClientConfiguredTenantId = preferHint }; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; var cred = InstrumentClient( new DeviceCodeCredential((code, _) => VerifyDeviceCode(code, expectedCode), TenantId, ClientId, options, null, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index d545f5df7d3fa..78c6c2a8fc434 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -224,7 +224,7 @@ public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, { TestSetup(); var options = new InteractiveBrowserCredentialOptions { PreferClientConfiguredTenantId = preferHint }; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); var credential = InstrumentClient( diff --git a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs index 33541782b5ef6..6e50f8f3adaf6 100644 --- a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs @@ -579,7 +579,7 @@ public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, { TestSetup(); var options = new SharedTokenCacheCredentialOptions { PreferClientConfiguredTenantId = preferHint }; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); mockMsal.Accounts = new List { diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index e704ce11d392c..216e66e62fa7a 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -10,7 +10,7 @@ namespace Azure.Identity.Tests public class TenantIdResolverTests { private const string TenantId = "clientTenant"; - private static TokenRequestContext Context_Hint = new(new TokenRequestContextOptions { TenantIdHint = "hint" }); + private static TokenRequestContext Context_Hint = new(new TokenRequestContextOptions { TenantId = "hint" }); private static TokenRequestContext Context_NoHint = new(new TokenRequestContextOptions()); private static TokenCredentialOptions Options_True = new() { PreferClientConfiguredTenantId = true }; private static TokenCredentialOptions Options_False = new() { PreferClientConfiguredTenantId = false }; @@ -19,11 +19,11 @@ public static IEnumerable ResolveInputs() { yield return new object[] { TenantId, Context_Hint, Options_True, TenantId }; yield return new object[] { TenantId, Context_NoHint, Options_True, TenantId }; - yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantIdHint }; - yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantIdHint }; - yield return new object[] { null, Context_Hint, Options_True, Context_Hint.TenantIdHint }; + yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantId }; + yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantId }; + yield return new object[] { null, Context_Hint, Options_True, Context_Hint.TenantId }; yield return new object[] { null, Context_NoHint, Options_True, null }; - yield return new object[] { null, Context_Hint, Options_False, Context_Hint.TenantIdHint }; + yield return new object[] { null, Context_Hint, Options_False, Context_Hint.TenantId }; yield return new object[] { null, Context_NoHint, Options_False, null }; } diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index c2a9daa33d0f3..368d232bcc47b 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -57,7 +57,7 @@ public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, { TestSetup(); var options = new UsernamePasswordCredentialOptions { PreferClientConfiguredTenantId = preferHint }; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); var credential = InstrumentClient(new UsernamePasswordCredential("user", "password", TenantId, ClientId, options, null, mockMsal)); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs index 42f2727c0e7b1..b22f0daa1351b 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs @@ -65,7 +65,7 @@ public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] using var env = new TestEnvVar(new Dictionary {{"TENANT_ID", TenantId}}); var environment = new IdentityTestEnvironment(); var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), PreferClientConfiguredTenantId = preferHint }; - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(environment.TenantId, context, options); VisualStudioCodeCredential credential = InstrumentClient( diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs index 1ac4cead88c4c..bc365bfa2bde5 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs @@ -30,7 +30,7 @@ public async Task AuthenticateWithVsCredential([Values(null, TenantIdHint)] stri var testProcess = new TestProcess { Output = processOutput }; var options = new VisualStudioCredentialOptions { PreferClientConfiguredTenantId = preferHint }; var credential = InstrumentClient(new VisualStudioCredential(TenantId, default, fileSystem, new TestProcessService(testProcess, true), options)); - var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantIdHint = tenantId }); + var context = new TokenRequestContext(new TokenRequestContextOptions { Scopes = new[] { Scope }, TenantId = tenantId }); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); var token = await credential.GetTokenAsync(context, default); From 17c8e72837d998631f774feae0aec47f96da1749 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 8 Jun 2021 15:14:16 -0500 Subject: [PATCH 15/39] fb --- sdk/core/Azure.Core/api/Azure.Core.net461.cs | 20 +++++++------------ sdk/core/Azure.Core/api/Azure.Core.net5.0.cs | 20 +++++++------------ .../api/Azure.Core.netcoreapp2.1.cs | 20 +++++++------------ .../api/Azure.Core.netstandard2.0.cs | 20 +++++++------------ .../api/Azure.Identity.netstandard2.0.cs | 2 +- .../Azure.Identity/src/TenantIdResolver.cs | 2 +- .../src/TokenCredentialOptions.cs | 4 ++-- .../tests/AuthorizationCodeCredentialTests.cs | 2 +- .../tests/ClientCertificateCredentialTests.cs | 2 +- .../tests/ClientSecretCredentialTests.cs | 2 +- .../tests/DeviceCodeCredentialTests.cs | 2 +- .../InteractiveBrowserCredentialTests.cs | 2 +- .../tests/SharedTokenCacheCredentialTests.cs | 2 +- .../tests/TenantIdResolverTests.cs | 4 ++-- .../tests/UsernamePasswordCredentialTests.cs | 2 +- .../tests/VisualStudioCodeCredentialTests.cs | 2 +- .../tests/VisualStudioCredentialTests.cs | 2 +- .../api/Azure.Storage.Blobs.netstandard2.0.cs | 4 ++-- .../Azure.Storage.Common.netstandard2.0.cs | 5 ++--- ...BearerTokenChallengeAuthorizationPolicy.cs | 14 ++++++------- .../src/Shared/StorageClientOptions.cs | 8 ++++---- ...e.Storage.Files.DataLake.netstandard2.0.cs | 4 ++-- .../Azure.Storage.Queues.netstandard2.0.cs | 4 ++-- 23 files changed, 61 insertions(+), 88 deletions(-) diff --git a/sdk/core/Azure.Core/api/Azure.Core.net461.cs b/sdk/core/Azure.Core/api/Azure.Core.net461.cs index 5572c36427c8c..ac527739085e9 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net461.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net461.cs @@ -322,6 +322,10 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } + public partial interface ISupportsTenantIdChallenges + { + bool EnableTenantDiscovery { get; } + } public abstract partial class Request : System.IDisposable { protected Request() { } @@ -462,23 +466,13 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; - public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } - public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId, string? claims) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null, string? tenantId = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } - public string? TenantIdHint { get { throw null; } } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct TokenRequestContextOptions - { - private object _dummy; - private int _dummyPrimitive; - public string? Claims { get { throw null; } set { } } - public string? ParentRequestId { get { throw null; } set { } } - public string[] Scopes { get { throw null; } set { } } - public string? TenantIdHint { get { throw null; } set { } } + public string? TenantId { get { throw null; } } } } namespace Azure.Core.Cryptography diff --git a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs index 80b20089ece48..34717d003fb39 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs @@ -322,6 +322,10 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } + public partial interface ISupportsTenantIdChallenges + { + bool EnableTenantDiscovery { get; } + } public abstract partial class Request : System.IDisposable { protected Request() { } @@ -462,23 +466,13 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; - public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } - public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId, string? claims) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null, string? tenantId = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } - public string? TenantIdHint { get { throw null; } } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct TokenRequestContextOptions - { - private object _dummy; - private int _dummyPrimitive; - public string? Claims { get { throw null; } set { } } - public string? ParentRequestId { get { throw null; } set { } } - public string[] Scopes { get { throw null; } set { } } - public string? TenantIdHint { get { throw null; } set { } } + public string? TenantId { get { throw null; } } } } namespace Azure.Core.Cryptography diff --git a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs index 5572c36427c8c..ac527739085e9 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs @@ -322,6 +322,10 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } + public partial interface ISupportsTenantIdChallenges + { + bool EnableTenantDiscovery { get; } + } public abstract partial class Request : System.IDisposable { protected Request() { } @@ -462,23 +466,13 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; - public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } - public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId, string? claims) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null, string? tenantId = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } - public string? TenantIdHint { get { throw null; } } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct TokenRequestContextOptions - { - private object _dummy; - private int _dummyPrimitive; - public string? Claims { get { throw null; } set { } } - public string? ParentRequestId { get { throw null; } set { } } - public string[] Scopes { get { throw null; } set { } } - public string? TenantIdHint { get { throw null; } set { } } + public string? TenantId { get { throw null; } } } } namespace Azure.Core.Cryptography diff --git a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs index 5572c36427c8c..ac527739085e9 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs @@ -322,6 +322,10 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } + public partial interface ISupportsTenantIdChallenges + { + bool EnableTenantDiscovery { get; } + } public abstract partial class Request : System.IDisposable { protected Request() { } @@ -462,23 +466,13 @@ public readonly partial struct TokenRequestContext { private readonly object _dummy; private readonly int _dummyPrimitive; - public TokenRequestContext(Azure.Core.TokenRequestContextOptions options) { throw null; } public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } - public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId, string? claims) { throw null; } + public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null, string? tenantId = null) { throw null; } public string? Claims { get { throw null; } } public string? ParentRequestId { get { throw null; } } public string[] Scopes { get { throw null; } } - public string? TenantIdHint { get { throw null; } } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct TokenRequestContextOptions - { - private object _dummy; - private int _dummyPrimitive; - public string? Claims { get { throw null; } set { } } - public string? ParentRequestId { get { throw null; } set { } } - public string[] Scopes { get { throw null; } set { } } - public string? TenantIdHint { get { throw null; } set { } } + public string? TenantId { get { throw null; } } } } namespace Azure.Core.Cryptography diff --git a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs index 1df4eaa325c70..75f61133d8959 100644 --- a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs +++ b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs @@ -250,8 +250,8 @@ internal TokenCacheUpdatedArgs() { } public partial class TokenCredentialOptions : Azure.Core.ClientOptions { public TokenCredentialOptions() { } + public bool AllowMultiTenantAuthentication { get { throw null; } set { } } public System.Uri AuthorityHost { get { throw null; } set { } } - public bool PreferClientConfiguredTenantId { get { throw null; } set { } } } public abstract partial class UnsafeTokenCacheOptions : Azure.Identity.TokenCachePersistenceOptions { diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index c16d26da9ffeb..969cb9c59c708 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -16,7 +16,7 @@ internal static class TenantIdResolver /// public static string Resolve(string explicitTenantId, TokenRequestContext context, TokenCredentialOptions options) { - return options?.PreferClientConfiguredTenantId switch + return options?.AllowMultiTenantAuthentication switch { true => explicitTenantId ?? context.TenantId, _ => context.TenantId ?? explicitTenantId diff --git a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs index 10c22155f2f79..d33408d139eb2 100644 --- a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs @@ -23,8 +23,8 @@ public Uri AuthorityHost } /// - /// If true, the tenant Id hint provided by a service authorization challenge will not override a tenantId configured via the credential options. + /// If true, the tenant Id provided by a service authorization challenge will be used over the tenantId configured via the credential options. /// - public bool PreferClientConfiguredTenantId { get; set; } + public bool AllowMultiTenantAuthentication { get; set; } } } diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 8e2563338e6c1..78b64b6ce5645 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -93,7 +93,7 @@ public async Task AuthenticateWithAuthCodeHonorsReplyUrl([Values(null, ReplyUrl) [Test] public async Task AuthenticateWithAuthCodeHonorsTenantId([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { - options = new TokenCredentialOptions { PreferClientConfiguredTenantId = preferHint }; + options = new TokenCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs index c0c5fa0886194..3ff7d5cf3b034 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs @@ -160,7 +160,7 @@ public async Task UsesTenantIdHint( [Values(true, false)] bool preferHint) { TestSetup(); - options.PreferClientConfiguredTenantId = preferHint; + options.AllowMultiTenantAuthentication = preferHint; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); var certificatePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); diff --git a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs index 3238dd4f7acfa..5f15e271d7db8 100644 --- a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs @@ -49,7 +49,7 @@ public async Task UsesTenantIdHint( [Values(true, false)] bool preferHint) { TestSetup(); - options.PreferClientConfiguredTenantId = preferHint; + options.AllowMultiTenantAuthentication = preferHint; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); ClientSecretCredential client = InstrumentClient(new ClientSecretCredential(expectedTenantId, ClientId, "secret", options, null, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index 964602279718b..30fc1c102fec8 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -106,7 +106,7 @@ public void TestSetup() [Test] public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { - options = new TokenCredentialOptions { PreferClientConfiguredTenantId = preferHint }; + options = new TokenCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; var cred = InstrumentClient( diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index 878dfc1d11e90..037516a3d5853 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -223,7 +223,7 @@ public void DisableAutomaticAuthenticationException() public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { TestSetup(); - var options = new InteractiveBrowserCredentialOptions { PreferClientConfiguredTenantId = preferHint }; + var options = new InteractiveBrowserCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); diff --git a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs index acb9c0edc03e7..e1d0f5c6fca22 100644 --- a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs @@ -578,7 +578,7 @@ public void ValidateClientIdSetOnMsalClient() public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { TestSetup(); - var options = new SharedTokenCacheCredentialOptions { PreferClientConfiguredTenantId = preferHint }; + var options = new SharedTokenCacheCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); mockMsal.Accounts = new List diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index ff3b7f053f7ac..b798ce7274a97 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -13,8 +13,8 @@ public class TenantIdResolverTests private const string TenantId = "clientTenant"; private static TokenRequestContext Context_Hint = new(Array.Empty(), tenantId: "hint" ); private static TokenRequestContext Context_NoHint = new(Array.Empty()); - private static TokenCredentialOptions Options_True = new() { PreferClientConfiguredTenantId = true }; - private static TokenCredentialOptions Options_False = new() { PreferClientConfiguredTenantId = false }; + private static TokenCredentialOptions Options_True = new() { AllowMultiTenantAuthentication = true }; + private static TokenCredentialOptions Options_False = new() { AllowMultiTenantAuthentication = false }; public static IEnumerable ResolveInputs() { diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index 4ef8e158ca0eb..1795d6c4e0775 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -56,7 +56,7 @@ public async Task VerifyMsalClientExceptionAsync() public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) { TestSetup(); - var options = new UsernamePasswordCredentialOptions { PreferClientConfiguredTenantId = preferHint }; + var options = new UsernamePasswordCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs index ecafdae8d5110..b1375dc0579ac 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs @@ -64,7 +64,7 @@ public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] { using var env = new TestEnvVar(new Dictionary {{"TENANT_ID", TenantId}}); var environment = new IdentityTestEnvironment(); - var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), PreferClientConfiguredTenantId = preferHint }; + var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(environment.TenantId, context, options); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs index 6ef6302949b96..3e7e2a220865e 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs @@ -28,7 +28,7 @@ public async Task AuthenticateWithVsCredential([Values(null, TenantIdHint)] stri var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudio(); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForVisualStudio(); var testProcess = new TestProcess { Output = processOutput }; - var options = new VisualStudioCredentialOptions { PreferClientConfiguredTenantId = preferHint }; + var options = new VisualStudioCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var credential = InstrumentClient(new VisualStudioCredential(TenantId, default, fileSystem, new TestProcessService(testProcess, true), options)); var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); diff --git a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs index 21d2114d1f9a1..89cedb0284cb7 100644 --- a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs @@ -47,11 +47,11 @@ public BlobClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredential c public new Azure.Storage.Blobs.BlobClient WithSnapshot(string snapshot) { throw null; } public new Azure.Storage.Blobs.BlobClient WithVersion(string versionId) { throw null; } } - public partial class BlobClientOptions : Azure.Core.ClientOptions + public partial class BlobClientOptions : Azure.Core.ClientOptions, Azure.Core.ISupportsTenantIdChallenges { public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion version = Azure.Storage.Blobs.BlobClientOptions.ServiceVersion.V2020_08_04) { } public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } } - public bool DisableTenantDiscovery { get { throw null; } set { } } + public bool EnableTenantDiscovery { get { throw null; } set { } } public string EncryptionScope { get { throw null; } set { } } public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } } public Azure.Storage.Blobs.BlobClientOptions.ServiceVersion Version { get { throw null; } } diff --git a/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs b/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs index 64c0f558ad7da..8ed276958ee1d 100644 --- a/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs @@ -168,9 +168,8 @@ namespace Azure.Storage.Shared { public partial class StorageBearerTokenChallengeAuthorizationPolicy : Azure.Core.Pipeline.BearerTokenAuthenticationPolicy { - public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, System.Collections.Generic.IEnumerable scopes, bool disableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } - public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, string scope, bool disableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } - public bool DisableTenantDiscovery { get { throw null; } } + public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, System.Collections.Generic.IEnumerable scopes, bool enableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } + public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, string scope, bool enableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } protected override void AuthorizeRequest(Azure.Core.HttpMessage message) { } protected override System.Threading.Tasks.ValueTask AuthorizeRequestAsync(Azure.Core.HttpMessage message) { throw null; } protected override bool AuthorizeRequestOnChallenge(Azure.Core.HttpMessage message) { throw null; } diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index 0dca9c3e8aa67..560ec9704ae95 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -13,13 +13,11 @@ namespace Azure.Storage.Shared /// /// /// - public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy, ISupportsTenantIdChallenges + public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy { private readonly string[] _scopes; private volatile string tenantId; - - /// - public bool EnableTenantDiscovery { get; } + private readonly bool _enableTenantDiscovery; /// /// @@ -31,7 +29,7 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential { Argument.AssertNotNullOrEmpty(scope, nameof(scope)); _scopes = new[] { scope }; - EnableTenantDiscovery = enableTenantDiscovery; + _enableTenantDiscovery = enableTenantDiscovery; } /// @@ -46,13 +44,13 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential { Argument.AssertNotNull(scopes, nameof(scopes)); _scopes = scopes.ToArray(); - EnableTenantDiscovery = enableTenantDiscovery; + _enableTenantDiscovery = enableTenantDiscovery; } /// protected override void AuthorizeRequest(HttpMessage message) { - if (tenantId != null && !EnableTenantDiscovery) + if (tenantId != null && !_enableTenantDiscovery) { base.AuthorizeRequest(message); } @@ -61,7 +59,7 @@ protected override void AuthorizeRequest(HttpMessage message) /// protected override ValueTask AuthorizeRequestAsync(HttpMessage message) { - if (tenantId != null && !EnableTenantDiscovery) + if (tenantId != null && !_enableTenantDiscovery) { return base.AuthorizeRequestAsync(message); } diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs index 602a4e0909149..75d358368ce4b 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageClientOptions.cs @@ -20,7 +20,7 @@ internal static class StorageClientOptions /// private const string StorageScope = "https://storage.azure.com/.default"; - private static HttpPipelinePolicy[] PerCallPolicies = new HttpPipelinePolicy[] { StorageServerTimeoutPolicy.Shared }; + private static readonly HttpPipelinePolicy[] s_perCallPolicies = { StorageServerTimeoutPolicy.Shared }; /// /// Set common ClientOptions defaults for Azure Storage. @@ -106,8 +106,8 @@ public static HttpPipelinePolicy GetAuthenticationPolicy(object credentials = nu /// An HttpPipeline to use for Storage requests. public static HttpPipeline Build(this ClientOptions options, HttpPipelinePolicy authentication = null, Uri geoRedundantSecondaryStorageUri = null) { - List perRetryClientPolicies = new List(); - StorageResponseClassifier classifier = new StorageResponseClassifier(); + List perRetryClientPolicies = new(); + StorageResponseClassifier classifier = new(); if (geoRedundantSecondaryStorageUri != null) { perRetryClientPolicies.Add(new GeoRedundantReadPolicy(geoRedundantSecondaryStorageUri)); @@ -119,7 +119,7 @@ public static HttpPipeline Build(this ClientOptions options, HttpPipelinePolicy return HttpPipelineBuilder.Build( options, - PerCallPolicies, + s_perCallPolicies, perRetryClientPolicies.ToArray(), classifier); } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs b/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs index 08501ebee38a3..b1a3c75412d0a 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs @@ -1,9 +1,9 @@ namespace Azure.Storage.Files.DataLake { - public partial class DataLakeClientOptions : Azure.Core.ClientOptions + public partial class DataLakeClientOptions : Azure.Core.ClientOptions, Azure.Core.ISupportsTenantIdChallenges { public DataLakeClientOptions(Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion version = Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion.V2020_08_04) { } - public bool DisableTenantDiscovery { get { throw null; } set { } } + public bool EnableTenantDiscovery { get { throw null; } set { } } public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } } public Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion Version { get { throw null; } } public enum ServiceVersion diff --git a/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs b/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs index 98852b107a499..fedd2a0e0b5e1 100644 --- a/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs @@ -68,10 +68,10 @@ public QueueClient(System.Uri queueUri, Azure.Storage.StorageSharedKeyCredential public virtual System.Threading.Tasks.Task> UpdateMessageAsync(string messageId, string popReceipt, string messageText = null, System.TimeSpan visibilityTimeout = default(System.TimeSpan), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal virtual Azure.Storage.Queues.QueueClient WithClientSideEncryptionOptionsCore(Azure.Storage.ClientSideEncryptionOptions clientSideEncryptionOptions) { throw null; } } - public partial class QueueClientOptions : Azure.Core.ClientOptions + public partial class QueueClientOptions : Azure.Core.ClientOptions, Azure.Core.ISupportsTenantIdChallenges { public QueueClientOptions(Azure.Storage.Queues.QueueClientOptions.ServiceVersion version = Azure.Storage.Queues.QueueClientOptions.ServiceVersion.V2020_08_04) { } - public bool DisableTenantDiscovery { get { throw null; } set { } } + public bool EnableTenantDiscovery { get { throw null; } set { } } public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } } public Azure.Storage.Queues.QueueMessageEncoding MessageEncoding { get { throw null; } set { } } public Azure.Storage.Queues.QueueClientOptions.ServiceVersion Version { get { throw null; } } From a565ecfe7cd1df48dc29a0bb8da93bd4aaa5b3ed Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 8 Jun 2021 15:49:46 -0500 Subject: [PATCH 16/39] fixup refs --- .../src/Azure.Storage.Blobs.csproj | 110 +++++++++--------- .../src/Azure.Storage.Common.csproj | 1 - ...BearerTokenChallengeAuthorizationPolicy.cs | 4 +- .../src/Azure.Storage.Files.DataLake.csproj | 90 +++++++------- .../src/Azure.Storage.Files.Shares.csproj | 96 +++++++-------- .../src/Azure.Storage.Queues.csproj | 74 ++++++------ 6 files changed, 191 insertions(+), 184 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj b/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj index d308e07920e81..3479377cb2052 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj +++ b/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj @@ -24,63 +24,65 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj index b71f362f39add..479eb5738e738 100644 --- a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj +++ b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj @@ -41,7 +41,6 @@ - diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index 560ec9704ae95..bd4808ee80dd4 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -11,9 +11,9 @@ namespace Azure.Storage.Shared { /// - /// + /// The storage authorization policy which supports challenges including tenantId discovery. /// - public class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy + internal class StorageBearerTokenChallengeAuthorizationPolicy : BearerTokenAuthenticationPolicy { private readonly string[] _scopes; private volatile string tenantId; diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj b/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj index 73d460a18c00f..b05f4d6862a74 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj @@ -26,52 +26,54 @@ - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj b/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj index 05b00d6ba6073..11062c062d522 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj +++ b/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj @@ -24,54 +24,56 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj b/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj index fcf9ecc1546c5..cbe4bfb439241 100644 --- a/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj +++ b/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj @@ -25,45 +25,47 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + From 407337a6b4b997243441bd7ef972312cf37466cd Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 8 Jun 2021 17:20:29 -0500 Subject: [PATCH 17/39] test StorageBearerTokenChallengeAuthorizationPolicy --- .../src/MockCredential.cs | 7 +- .../src/Azure.Storage.Common.csproj | 1 + ...BearerTokenChallengeAuthorizationPolicy.cs | 4 +- ...rTokenChallengeAuthorizationPolicyTests.cs | 104 ++++++++++++++++++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 sdk/storage/Azure.Storage.Common/tests/StorageBearerTokenChallengeAuthorizationPolicyTests.cs diff --git a/sdk/core/Azure.Core.TestFramework/src/MockCredential.cs b/sdk/core/Azure.Core.TestFramework/src/MockCredential.cs index 9b42481b8d36e..26f2e7c2f27c6 100644 --- a/sdk/core/Azure.Core.TestFramework/src/MockCredential.cs +++ b/sdk/core/Azure.Core.TestFramework/src/MockCredential.cs @@ -9,13 +9,18 @@ namespace Azure.Core.TestFramework { public class MockCredential : TokenCredential { + public Action GetTokenCalledWith { get; set; } public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) { - return new ValueTask(GetToken(requestContext, cancellationToken)); + return new(GetToken(requestContext, cancellationToken)); } public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) { + if (GetTokenCalledWith != null) + { + GetTokenCalledWith(requestContext, cancellationToken); + } return new AccessToken("TEST TOKEN " + string.Join(" ", requestContext.Scopes), DateTimeOffset.MaxValue); } } diff --git a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj index 479eb5738e738..4f12a7af31729 100644 --- a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj +++ b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj @@ -48,5 +48,6 @@ + diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index bd4808ee80dd4..ef1d2678539e7 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -50,7 +50,7 @@ public StorageBearerTokenChallengeAuthorizationPolicy(TokenCredential credential /// protected override void AuthorizeRequest(HttpMessage message) { - if (tenantId != null && !_enableTenantDiscovery) + if (tenantId != null || !_enableTenantDiscovery) { base.AuthorizeRequest(message); } @@ -59,7 +59,7 @@ protected override void AuthorizeRequest(HttpMessage message) /// protected override ValueTask AuthorizeRequestAsync(HttpMessage message) { - if (tenantId != null && !_enableTenantDiscovery) + if (tenantId != null || !_enableTenantDiscovery) { return base.AuthorizeRequestAsync(message); } diff --git a/sdk/storage/Azure.Storage.Common/tests/StorageBearerTokenChallengeAuthorizationPolicyTests.cs b/sdk/storage/Azure.Storage.Common/tests/StorageBearerTokenChallengeAuthorizationPolicyTests.cs new file mode 100644 index 0000000000000..5f224bd52cb1f --- /dev/null +++ b/sdk/storage/Azure.Storage.Common/tests/StorageBearerTokenChallengeAuthorizationPolicyTests.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.TestFramework; +using Azure.Storage.Shared; +using NUnit.Framework; +using NUnit.Framework.Constraints; + +namespace Azure.Storage.Tests +{ + public class StorageBearerTokenChallengeAuthorizationPolicyTests : SyncAsyncPolicyTestBase + { + public StorageBearerTokenChallengeAuthorizationPolicyTests(bool isAsync) : base(isAsync) { } + + private string[] scopes = { "scope1", "scope2" }; + private string expectedTenantId; + private MockCredential cred; + + [SetUp] + public void Setup() + { + expectedTenantId = null; + cred = new MockCredential + { + GetTokenCalledWith = (trc, _) => + { + Assert.AreEqual(scopes, trc.Scopes); + Assert.AreEqual(expectedTenantId, trc.TenantId); + } + }; + } + + [Test] + public async Task UsesTokenProvidedByCredentials() + { + var policy = new StorageBearerTokenChallengeAuthorizationPolicy(cred, scopes, false); + + MockTransport transport = CreateMockTransport(new MockResponse(200)); + await SendGetRequest(transport, policy, uri: new Uri("https://example.com")); + + Assert.True(transport.SingleRequest.Headers.TryGetValue("Authorization", out _)); + } + + [Test] + public async Task DoesNotSendUnAuthroizedRequestWhenEnableTenantDiscoveryIsFalse([Values(true, false)] bool enableTenantDiscovery) + { + var policy = new StorageBearerTokenChallengeAuthorizationPolicy(cred, scopes, false); + + MockTransport transport = CreateMockTransport(_ => new MockResponse(200)); + + for (int i = 0; i < 10; i++) + { + await SendGetRequest(transport, policy, uri: new Uri("https://example.com")); + } + Assert.True(transport.Requests.All(r => r.Headers.TryGetValue("Authorization", out _))); + } + + [Test] + public async Task SendsUnUnAuthroizedRequestWhenEnableTenantDiscoveryIsTrue([Values(true, false)] bool enableTenantDiscovery) + { + var policy = new StorageBearerTokenChallengeAuthorizationPolicy(cred, scopes, enableTenantDiscovery); + bool firstRequest = true; + var challengeReponse = new MockResponse((int)HttpStatusCode.Unauthorized); + if (enableTenantDiscovery) + { + expectedTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; + } + challengeReponse.AddHeader( + new HttpHeader( + HttpHeader.Names.WwwAuthenticate, + $"Bearer authorization_uri=https://login.microsoftonline.com/{expectedTenantId}/oauth2/authorize resource_id=https://storage.azure.com")); + + MockTransport transport = CreateMockTransport( + _ => + { + var response = firstRequest switch + { + false when enableTenantDiscovery => challengeReponse, + _ => new MockResponse(200) + }; + firstRequest = false; + return response; + }); + + for (int i = 0; i < 10; i++) + { + await SendGetRequest(transport, policy, uri: new Uri("https://example.com")); + } + + // if enableTeanantDiscovery is true, the first request should be unauthorized + if (enableTenantDiscovery) + { + Assert.False(transport.Requests[0].Headers.TryGetValue("Authorization", out _)); + } + // If enableTenantDiscovery is true, all but the first request should be authorized. + Assert.True(transport.Requests.Skip(enableTenantDiscovery ? 1 : 0).All(r => r.Headers.TryGetValue("Authorization", out _))); + } + } +} From dba065b0153c8fcba0f13ba9a72cc061cb0f47b1 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 8 Jun 2021 17:42:48 -0500 Subject: [PATCH 18/39] namespace --- .../Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs index ef1d2678539e7..736f57d0cea17 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageBearerTokenChallengeAuthorizationPolicy.cs @@ -8,7 +8,7 @@ using Azure.Core; using Azure.Core.Pipeline; -namespace Azure.Storage.Shared +namespace Azure.Storage { /// /// The storage authorization policy which supports challenges including tenantId discovery. From 18d975369d3aea81bcf5101dc88d60a6537b0179 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 9 Jun 2021 10:12:37 -0500 Subject: [PATCH 19/39] pass options to test setup --- sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs b/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs index f691596229912..f7d2df5cfd7a8 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs @@ -278,10 +278,11 @@ public async Task GetTestContainerAsync( string containerName = default, IDictionary metadata = default, PublicAccessType? publicAccessType = default, - bool premium = default) + bool premium = default, + BlobClientOptions options = default) { containerName ??= GetNewContainerName(); - service ??= GetServiceClient_SharedKey(); + service ??= GetServiceClient_SharedKey(options); if (publicAccessType == default) { From 2d78abdd83d8a1755dcaa0416872c49d263406f3 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 9 Jun 2021 18:21:56 -0500 Subject: [PATCH 20/39] recorded tests, make recordedTest attrbitue work, fixup test-resources-post --- .../Azure.Storage.Blobs/tests/BlobTestBase.cs | 18 +- .../ExistsAsync_WithTenantDiscovery.json | 192 ++++++++++++++++++ .../ExistsAsync_WithTenantDiscoveryAsync.json | 192 ++++++++++++++++++ .../TenantDiscoveryBlobBaseClientTests.cs | 33 +++ .../tests/Shared/StorageTestBase.cs | 14 +- .../tests/Shared/TestConfigurations.cs | 2 +- sdk/storage/test-resources-post.ps1 | 26 ++- 7 files changed, 461 insertions(+), 16 deletions(-) create mode 100644 sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json create mode 100644 sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json create mode 100644 sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs diff --git a/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs b/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs index f7d2df5cfd7a8..dbde08d08d975 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/BlobTestBase.cs @@ -72,7 +72,7 @@ internal async Task GetNewBlobClient(BlobContainerClient contain return blob; } - public BlobClientOptions GetOptions(bool parallelRange = false) + public BlobClientOptions GetOptions(bool parallelRange = false, bool enableTenantDiscovery = false) { var options = new BlobClientOptions(_serviceVersion) { @@ -80,11 +80,12 @@ public BlobClientOptions GetOptions(bool parallelRange = false) Retry = { Mode = RetryMode.Exponential, - MaxRetries = Storage.Constants.MaxReliabilityRetries, + MaxRetries = Constants.MaxReliabilityRetries, Delay = TimeSpan.FromSeconds(Mode == RecordedTestMode.Playback ? 0.01 : 1), MaxDelay = TimeSpan.FromSeconds(Mode == RecordedTestMode.Playback ? 0.1 : 60), NetworkTimeout = TimeSpan.FromSeconds(Mode == RecordedTestMode.Playback ? 100 : 400), }, + EnableTenantDiscovery = enableTenantDiscovery }; if (Mode != RecordedTestMode.Live) { @@ -159,12 +160,12 @@ private BlobClientOptions GetSecondaryStorageOptions( return options; } - private BlobServiceClient GetServiceClientFromOauthConfig(TenantConfiguration config) => + private BlobServiceClient GetServiceClientFromOauthConfig(TenantConfiguration config, bool enableTenantDiscovery) => InstrumentClient( new BlobServiceClient( new Uri(config.BlobServiceEndpoint), GetOAuthCredential(config), - GetOptions())); + GetOptions(enableTenantDiscovery: enableTenantDiscovery))); public BlobServiceClient GetServiceClient_SharedKey(BlobClientOptions options = default) => GetServiceClientFromSharedKeyConfig(TestConfigDefault, options); @@ -187,8 +188,8 @@ public BlobServiceClient GetServiceClient_PreviewAccount_SharedKey() public BlobServiceClient GetServiceClient_PremiumBlobAccount_SharedKey() => GetServiceClientFromSharedKeyConfig(TestConfigPremiumBlob); - public BlobServiceClient GetServiceClient_OauthAccount() => - GetServiceClientFromOauthConfig(TestConfigOAuth); + public BlobServiceClient GetServiceClient_OauthAccount(bool enableTenantDiscovery = false) => + GetServiceClientFromOauthConfig(TestConfigOAuth, enableTenantDiscovery); public BlobServiceClient GetServiceClient_ManagedDisk() => GetServiceClientFromSharedKeyConfig(TestConfigManagedDisk); @@ -278,11 +279,10 @@ public async Task GetTestContainerAsync( string containerName = default, IDictionary metadata = default, PublicAccessType? publicAccessType = default, - bool premium = default, - BlobClientOptions options = default) + bool premium = default) { containerName ??= GetNewContainerName(); - service ??= GetServiceClient_SharedKey(options); + service ??= GetServiceClient_SharedKey(); if (publicAccessType == default) { diff --git a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json new file mode 100644 index 0000000000000..e23d621c6c225 --- /dev/null +++ b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json @@ -0,0 +1,192 @@ +{ + "Entries": [ + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "traceparent": "00-0c6c8947c89a40448a7dfda4e364afac-bcb2493b4eb86f40-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 401, + "ResponseHeaders": { + "Content-Length": "302", + "Content-Type": "application/xml", + "Date": "Wed, 09 Jun 2021 22:02:23 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "WWW-Authenticate": "Bearer authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com", + "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", + "x-ms-error-code": "NoAuthenticationInformation", + "x-ms-request-id": "4076be77-a01e-00a7-3d7b-5d0090000000", + "x-ms-version": "2020-08-04" + }, + "ResponseBody": [ + "\uFEFF\u003C?xml version=\u00221.0\u0022 encoding=\u0022utf-8\u0022?\u003E\u003CError\u003E\u003CCode\u003ENoAuthenticationInformation\u003C/Code\u003E\u003CMessage\u003EServer failed to authenticate the request. Please refer to the information in the www-authenticate header.\n", + "RequestId:4076be77-a01e-00a7-3d7b-5d0090000000\n", + "Time:2021-06-09T22:02:24.1440863Z\u003C/Message\u003E\u003C/Error\u003E" + ] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-0c6c8947c89a40448a7dfda4e364afac-bcb2493b4eb86f40-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Wed, 09 Jun 2021 22:02:25 GMT", + "ETag": "\u00220x8D92B92440CBED7\u0022", + "Last-Modified": "Wed, 09 Jun 2021 22:02:25 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", + "x-ms-request-id": "4076c299-a01e-00a7-207b-5d0090000000", + "x-ms-version": "2020-08-04" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c/test-blob-7cd17349-5ed5-74da-8a61-69439c304e6d", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "Content-Length": "1024", + "Content-Type": "application/octet-stream", + "traceparent": "00-8525ee834d99a842b3f7cdfb5dc34b80-986d2af86511324e-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "26483e47-1b1f-ee49-212d-5d239a6e1b62", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": "QlYhnyJjYO5\u002BqhNTabohpan2eHOOULGmk5Iay/ioXZvbhjBHuM105RPi/itKhgrQQMOaprfb3vKleguPSZX21U14eOKJ2uzcgZJgrFKFWw2HSYo0DbbjAKg\u002BoT7B5Jx\u002B6yjd0g4oR2bWviQkejKgHx7\u002BCAAKwceWlHc6SziuGp324sApqIn\u002BXwIkXk\u002BSvoV2nYEfw9ybcypgRj6W7fstJ6SGLphxdneV/6j\u002B5WN4BNXxxK22j1bBWm6WyGJKaHIG9jaqDJDtBCcKicQ0nY7G6EJppChOd5hOxdMp260USpY19\u002B4szfhEBMCGqwaRjb3gz\u002BAg4cNJuH\u002BeXEa9xCl59j4t/qafmRtKJKBox8uRvDgWSQvN9GnS7D5ujnCxh0CXDLXHZI4WQDXF1b0j4fC0DKAOp3odqwHVWhJnMAuqmJxg0bsWylep/wjVCB6rrwpNuoLQouDFeWCz8Ql8rw8qGj0gwxA1nkJIZKhxR6PiKqsHGnWJ2fW8mldaYthOLVD5q\u002B3hiuv6GBxAhz44VDK90cdddZSaoUBHZCm42jxp\u002BjdXeJy1AZ/ADAFZSVK3hEYknZAR8Li7XgmsmibBQT3g1owG4NuWB88onWiTQFjuj\u002BDjlD2rWAp/nBmSCGZ5/srEeHLGsaNqjpXB6FNPKB6xvgtF3q0ANX6Ch0PCA/9orp6dzsflKN48eAan6W\u002B2L3T74UlbMx8dxEP0QylgqIqWWDq0sllWy5YtCWWnWEsOy1fOxiK1DXt/fwqE8eS03NB7EZE0An0bpRWzLiq5dmv45XfSkeFn4Eq/NTJO0dmxeC2VK0a52cnZsyENAt5QxQmejAxt4UrvFDFGdc1LzdM7AbX8JEolPguu\u002BN6/Wt5sbm8or0XNRJfipRYUcmT8juZO0QApVKC\u002BeE0Bv\u002BhsMLpT2gddIh2NbrweGn8zEMumsWn3cIjdQhrPaTb3zIWudXRcwZhdn2zJwGJDCjkgimeCWg1\u002BdL8xdZftjbjmlqI3b6QK108zSRL9LrAlmTjgrt/OdXF3LwC1K0wAcBGz4irADJqUFrMYTxD7gUYPC9tfDN7ZFyAwv0CkVasCMtkASUyGZY2dTJgrtiZhfK66ph9woGMy2ssJj0xa8MAHFhw2\u002Bzf6iaZNwr9GrmH6z0OS6a\u002BMkZF2zW\u002BAfNVAQlzXI58KKoB2wB15I96j7CxqD8AJyAYRe4SKwC7HeQzQDH9jb4rB8KXCGymSEm6z0uBDFm0VN3pVxacbf2lSr0cQqRZfP3yhqB4bRqzdf2kV1u5NhGOeutUEDqnipXA7cGzhgWlYVJJkoW1O\u002BvHNd0U\u002BBal7BCvkce5AWjHSvwKu39VA\u002B/4AvYJc6eL1T8DjNpZko6DHvw==", + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Content-MD5": "7e40j/ghmi0nRzPfuMltBg==", + "Date": "Wed, 09 Jun 2021 22:02:25 GMT", + "ETag": "\u00220x8D92B9244261153\u0022", + "Last-Modified": "Wed, 09 Jun 2021 22:02:25 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "26483e47-1b1f-ee49-212d-5d239a6e1b62", + "x-ms-content-crc64": "k1ArWCxhhgE=", + "x-ms-request-id": "4076c448-a01e-00a7-2d7b-5d0090000000", + "x-ms-request-server-encrypted": "true", + "x-ms-version": "2020-08-04", + "x-ms-version-id": "2021-06-09T22:02:25.8200915Z" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c/test-blob-7cd17349-5ed5-74da-8a61-69439c304e6d", + "RequestMethod": "HEAD", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-303db5e5c5e57b45aa30b8befaed11e5-4c858abf220b2b48-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "651c1aa9-a057-50a0-f80f-ed4dbad6c622", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Content-Length": "1024", + "Content-MD5": "7e40j/ghmi0nRzPfuMltBg==", + "Content-Type": "application/octet-stream", + "Date": "Wed, 09 Jun 2021 22:02:25 GMT", + "ETag": "\u00220x8D92B9244261153\u0022", + "Last-Modified": "Wed, 09 Jun 2021 22:02:25 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-access-tier": "Hot", + "x-ms-access-tier-inferred": "true", + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "651c1aa9-a057-50a0-f80f-ed4dbad6c622", + "x-ms-creation-time": "Wed, 09 Jun 2021 22:02:25 GMT", + "x-ms-is-current-version": "true", + "x-ms-last-access-time": "Wed, 09 Jun 2021 22:02:25 GMT", + "x-ms-lease-state": "available", + "x-ms-lease-status": "unlocked", + "x-ms-request-id": "4076c484-a01e-00a7-667b-5d0090000000", + "x-ms-server-encrypted": "true", + "x-ms-version": "2020-08-04", + "x-ms-version-id": "2021-06-09T22:02:25.8200915Z" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c?restype=container", + "RequestMethod": "DELETE", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-a08df38acd582f49bbc00608f57066c9-0f774f4a0c5c9d41-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "807a9687-b552-460a-7e18-07ed0138eab1", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 202, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Wed, 09 Jun 2021 22:02:25 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "807a9687-b552-460a-7e18-07ed0138eab1", + "x-ms-request-id": "4076c4d9-a01e-00a7-387b-5d0090000000", + "x-ms-version": "2020-08-04" + }, + "ResponseBody": [] + } + ], + "Variables": { + "RandomSeed": "1852432794", + "Storage_TestConfigOAuth": "OAuthTenant\nchrissstorageprim\nU2FuaXRpemVk\nhttps://chrissstorageprim.blob.core.windows.net\nhttps://chrissstorageprim.file.core.windows.net\nhttps://chrissstorageprim.queue.core.windows.net\nhttps://chrissstorageprim.table.core.windows.net\n\n\n\n\nhttps://chrissstorageprim-secondary.blob.core.windows.net\nhttps://chrissstorageprim-secondary.file.core.windows.net\nhttps://chrissstorageprim-secondary.queue.core.windows.net\nhttps://chrissstorageprim-secondary.table.core.windows.net\nfaf28b7c-a386-4110-a10c-ba1a2adc8cce\nSanitized\n72f988bf-86f1-41af-91ab-2d7cd011db47\nhttps://login.microsoftonline.com/\nCloud\nBlobEndpoint=https://chrissstorageprim.blob.core.windows.net/;QueueEndpoint=https://chrissstorageprim.queue.core.windows.net/;FileEndpoint=https://chrissstorageprim.file.core.windows.net/;BlobSecondaryEndpoint=https://chrissstorageprim-secondary.blob.core.windows.net/;QueueSecondaryEndpoint=https://chrissstorageprim-secondary.queue.core.windows.net/;FileSecondaryEndpoint=https://chrissstorageprim-secondary.file.core.windows.net/;AccountName=chrissstorageprim;AccountKey=Sanitized\n" + } +} \ No newline at end of file diff --git a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json new file mode 100644 index 0000000000000..f0216ffba0745 --- /dev/null +++ b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json @@ -0,0 +1,192 @@ +{ + "Entries": [ + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "traceparent": "00-30e17ef4635aff479c14918d9b4914df-aec9c9d035518648-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 401, + "ResponseHeaders": { + "Content-Length": "302", + "Content-Type": "application/xml", + "Date": "Wed, 09 Jun 2021 22:02:25 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "WWW-Authenticate": "Bearer authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com", + "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", + "x-ms-error-code": "NoAuthenticationInformation", + "x-ms-request-id": "4076c56a-a01e-00a7-457b-5d0090000000", + "x-ms-version": "2020-08-04" + }, + "ResponseBody": [ + "\uFEFF\u003C?xml version=\u00221.0\u0022 encoding=\u0022utf-8\u0022?\u003E\u003CError\u003E\u003CCode\u003ENoAuthenticationInformation\u003C/Code\u003E\u003CMessage\u003EServer failed to authenticate the request. Please refer to the information in the www-authenticate header.\n", + "RequestId:4076c56a-a01e-00a7-457b-5d0090000000\n", + "Time:2021-06-09T22:02:26.1254686Z\u003C/Message\u003E\u003C/Error\u003E" + ] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-30e17ef4635aff479c14918d9b4914df-aec9c9d035518648-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Wed, 09 Jun 2021 22:02:25 GMT", + "ETag": "\u00220x8D92B9244814055\u0022", + "Last-Modified": "Wed, 09 Jun 2021 22:02:26 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", + "x-ms-request-id": "4076c623-a01e-00a7-757b-5d0090000000", + "x-ms-version": "2020-08-04" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd/test-blob-0453075b-739f-fb62-8676-8f04f7a7c809", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "Content-Length": "1024", + "Content-Type": "application/octet-stream", + "traceparent": "00-191d97b79a86684b94d1d5fffe34e7a3-ae6b0815e3bbc24e-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "44f90713-22a6-9440-cf81-dcf7cd2bdf2b", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": "EM\u002B4cVRDEPyf12/q7ooGe8iFsKKXzVyAasSCVgnfrxX8ku0U\u002B0H9F/ZWZccDdw38bxM7cfYmckNyNwePwLnzwCha7luc8oCHiJirQWiVfPK3huf1oz0f1SCposM5bVc9YqWfqYixX32StOleezDb3bsV2DrS/RzqOxk781C9TRtBbt/D9dJpBSZpY\u002B9UwVrvzsQHJJUdamSItG5vdWwt1GbOHAxRGOfoq9ITaJktlnq\u002BTCSLtimGJAN6OkFk\u002BpC6PI\u002BIhaRNnbbdHnnCCQHKWh30sClHoh49SPgJ192lhLBCWSwKpRDVca5urZevwxoBr98jb0lmcplxSj2rRMbYN5rIf7DVwLUMHj962fWGjYTeByf9gRVn2\u002BaSmVvSKSznlXX950NpLO8qFlAK65mk6ry5zT2f2raFYKPKQiZaOfUSkkN/iaZAdNo302rEXe9N7q6eGVvvSLu/d4afn\u002BrI0N1b91lVJGAWeVliZwZLwQO0fFN4uyCZIkqIwBEfoNXwbgONaGWW98lNiKACjOC0dLaD6yOWUBS8wtQOzWRP9tiEcvgDvVieHYSJKjKAlpZp00xbnLSxWR7u0aaQiCpl/pLHfhrnsbde5c6eDCbiPCs\u002BpPoX81oj0hnaFOuyDZRaJCufuBh7gSKV/LflE0z3DJV6UuMwTQo4o6wADZzLrNg5ROj6Q4MTwiLXgvaiPb7N4ghWN7qxKdYhZxVrenSFyVDfrM5j/ckRZo/AWMsTlWXtMyqUdHQdiBsS2dtuqEeuUrimw5HwaeU/RNEr8NELDdGvfkdK8FXcwiOzUSTto75HEgUSbdLT/cpFVS\u002Bi8ZdJB8sKvikAY2FjyQFuPBjAUqET5N3L\u002BJ/\u002B24F9BZslOdCLaUpY4uSURq\u002Bjr6PRZcCxhAKOjw26fg4sC\u002BKog17GPY8x4tLOaG\u002B8fp0uJ0n7LXbAy4GYl6rC0es7LNYB7MNR3XEUoIPt4xyT0RBwi1fEMN1s57jcLaXlbhDnkjuYupLa3UrY543y0\u002BdkBcaZUUaUd/z5lQQKdK/LLzFyBVHg\u002BhZ\u002Blq0M7EhQehPReCjfSxzKUxs\u002BnyWNz2maDLhcf82VgwqvG7uYh4q0qBvwOPyH7OMUqLI2oVvw8MZEcuCP8/pGO/QdLy9gMcuQivYKnZZtEx/UR9caXuGvviv1UxSqJLQhqv0Hab2zs3fmphZk5lMv11/AuqwiK90SbnAEa9235WZB6fKZZaliIJNgNLjLS9jXxXy16mkI/n4f9fpMr0iZL8CwIv1glXK1XZoYwvckRyOYLKZhAfv9Oet0eW\u002BvFnFLxwT4Gijbdy8J/4zP7zuH\u002B67XtIKiNy3EsukS7BwCBkcozfvyLCz7vbV2VwJvLg==", + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Content-MD5": "1/M56FchsWR570k2AY8A\u002BQ==", + "Date": "Wed, 09 Jun 2021 22:02:26 GMT", + "ETag": "\u00220x8D92B92448F6D45\u0022", + "Last-Modified": "Wed, 09 Jun 2021 22:02:26 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "44f90713-22a6-9440-cf81-dcf7cd2bdf2b", + "x-ms-content-crc64": "Wo2avl3Hc\u002Bk=", + "x-ms-request-id": "4076c671-a01e-00a7-3b7b-5d0090000000", + "x-ms-request-server-encrypted": "true", + "x-ms-version": "2020-08-04", + "x-ms-version-id": "2021-06-09T22:02:26.5115738Z" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd/test-blob-0453075b-739f-fb62-8676-8f04f7a7c809", + "RequestMethod": "HEAD", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-22ac65f6e259d4498ffde710e2f3fca2-6d6b9a12e51dac48-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "50622af3-2a08-2787-6e58-ae40fb6a217d", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Content-Length": "1024", + "Content-MD5": "1/M56FchsWR570k2AY8A\u002BQ==", + "Content-Type": "application/octet-stream", + "Date": "Wed, 09 Jun 2021 22:02:26 GMT", + "ETag": "\u00220x8D92B92448F6D45\u0022", + "Last-Modified": "Wed, 09 Jun 2021 22:02:26 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-access-tier": "Hot", + "x-ms-access-tier-inferred": "true", + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "50622af3-2a08-2787-6e58-ae40fb6a217d", + "x-ms-creation-time": "Wed, 09 Jun 2021 22:02:26 GMT", + "x-ms-is-current-version": "true", + "x-ms-last-access-time": "Wed, 09 Jun 2021 22:02:26 GMT", + "x-ms-lease-state": "available", + "x-ms-lease-status": "unlocked", + "x-ms-request-id": "4076c6be-a01e-00a7-807b-5d0090000000", + "x-ms-server-encrypted": "true", + "x-ms-version": "2020-08-04", + "x-ms-version-id": "2021-06-09T22:02:26.5115738Z" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd?restype=container", + "RequestMethod": "DELETE", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-0bef330c65b52f43826872b930458f81-9d90f21d5696ed42-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", + "(.NET 5.0.6; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "a2031fbd-8fc9-2b23-fc79-f4a6ef00327b", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-08-04" + }, + "RequestBody": null, + "StatusCode": 202, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Wed, 09 Jun 2021 22:02:26 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "a2031fbd-8fc9-2b23-fc79-f4a6ef00327b", + "x-ms-request-id": "4076c723-a01e-00a7-5e7b-5d0090000000", + "x-ms-version": "2020-08-04" + }, + "ResponseBody": [] + } + ], + "Variables": { + "RandomSeed": "1200793572", + "Storage_TestConfigOAuth": "OAuthTenant\nchrissstorageprim\nU2FuaXRpemVk\nhttps://chrissstorageprim.blob.core.windows.net\nhttps://chrissstorageprim.file.core.windows.net\nhttps://chrissstorageprim.queue.core.windows.net\nhttps://chrissstorageprim.table.core.windows.net\n\n\n\n\nhttps://chrissstorageprim-secondary.blob.core.windows.net\nhttps://chrissstorageprim-secondary.file.core.windows.net\nhttps://chrissstorageprim-secondary.queue.core.windows.net\nhttps://chrissstorageprim-secondary.table.core.windows.net\nfaf28b7c-a386-4110-a10c-ba1a2adc8cce\nSanitized\n72f988bf-86f1-41af-91ab-2d7cd011db47\nhttps://login.microsoftonline.com/\nCloud\nBlobEndpoint=https://chrissstorageprim.blob.core.windows.net/;QueueEndpoint=https://chrissstorageprim.queue.core.windows.net/;FileEndpoint=https://chrissstorageprim.file.core.windows.net/;BlobSecondaryEndpoint=https://chrissstorageprim-secondary.blob.core.windows.net/;QueueSecondaryEndpoint=https://chrissstorageprim-secondary.queue.core.windows.net/;FileSecondaryEndpoint=https://chrissstorageprim-secondary.file.core.windows.net/;AccountName=chrissstorageprim;AccountKey=Sanitized\n" + } +} \ No newline at end of file diff --git a/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs new file mode 100644 index 0000000000000..c8d388c4fe97e --- /dev/null +++ b/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Azure.Core.TestFramework; +using Azure.Storage.Blobs.Specialized; +using Azure.Storage.Test.Shared; +using NUnit.Framework; + +namespace Azure.Storage.Blobs.Tests +{ + public class TenantDiscoveryBlobBaseClientTests : BlobTestBase + { + public TenantDiscoveryBlobBaseClientTests(bool async, BlobClientOptions.ServiceVersion serviceVersion) + : base(async, serviceVersion) + { } + + [RecordedTest] + public async Task ExistsAsync_WithTenantDiscovery() + { + await using DisposingContainer test = await GetTestContainerAsync(GetServiceClient_OauthAccount(true)); + + // Arrange + BlobBaseClient blob = await GetNewBlobClient(test.Container); + + // Act + Response response = await blob.ExistsAsync(); + + // Assert + Assert.IsTrue(response.Value); + } + } +} diff --git a/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs b/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs index 4e77e4e1dda21..4d8246721f1b6 100644 --- a/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs +++ b/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs @@ -155,7 +155,19 @@ private TenantConfiguration GetTestConfig(string name, Func case RecordedTestMode.Playback: if (!_playbackConfigCache.TryGetValue(name, out config)) { - text = Recording.GetVariable(name, null); + try + { + text = Recording.GetVariable(name, null); + } + catch + { + // If we can't find the variable, treat it as a mismatch recording. + throw new TestRecordingMismatchException("no recordings found"); + } + if (text == null) + { + throw new TestRecordingMismatchException($"Could not find variable '{name}'"); + } config = TenantConfiguration.Parse(text); _playbackConfigCache[name] = config; } diff --git a/sdk/storage/Azure.Storage.Common/tests/Shared/TestConfigurations.cs b/sdk/storage/Azure.Storage.Common/tests/Shared/TestConfigurations.cs index 513e2fd6d72d7..e4e172e2d50d8 100644 --- a/sdk/storage/Azure.Storage.Common/tests/Shared/TestConfigurations.cs +++ b/sdk/storage/Azure.Storage.Common/tests/Shared/TestConfigurations.cs @@ -250,7 +250,7 @@ private static TestConfigurations LoadTestConfigurations() TestConfigurationsPath = Path.Combine(TestContext.CurrentContext.TestDirectory, DefaultTestConfigFilePath); if (string.IsNullOrEmpty(TestConfigurationsPath) || !File.Exists(TestConfigurationsPath)) { - Assert.Inconclusive($"Live test configuration not found at file {TestConfigurationsPath}!"); + Assert.Inconclusive($"Live test configuration not found at file {TestConfigurationsPath}! If you've not yet run New-TestResources.ps1, please do that first."); } } diff --git a/sdk/storage/test-resources-post.ps1 b/sdk/storage/test-resources-post.ps1 index c6f6fe5da2b39..c5302c74bfef0 100644 --- a/sdk/storage/test-resources-post.ps1 +++ b/sdk/storage/test-resources-post.ps1 @@ -45,6 +45,7 @@ $PremiumFileAccountName = $DeploymentOutputs['PREMIUM_FILE_STORAGE_ACCOUNT_NAME' $PremiumFileAccountKey = $DeploymentOutputs['PREMIUM_FILE_STORAGE_ACCOUNT_KEY'] $PremiumFileAccountEndpointSuffix = $DeploymentOutputs['PREMIUM_FILE_STORAGE_ACCOUNT_FILE_ENDPOINT_SUFFIX'] $KeyVaultUri = $DeploymentOutputs['KEYVAULT_URI'] +$StorageTenantId = $DeploymentOutputs['STORAGE_TENANT_ID'] # Construct the content of the configuration file that the Storage tests expect $content = @@ -105,7 +106,7 @@ $content = $PrimaryAccountKey $TestApplicationId $TestApplicationSecret - $TenantId + $StorageTenantId https://login.microsoftonline.com/ https://$PrimaryAccountName.$PrimaryAccountBlobEndpointSuffix https://$PrimaryAccountName.$PrimaryAccountQueueEndpointSuffix @@ -124,7 +125,7 @@ $content = $DataLakeAccountKey $TestApplicationId $TestApplicationSecret - $TenantId + $StorageTenantId https://login.microsoftonline.com/ https://$DataLakeAccountName.$DataLakeAccountBlobEndpointSuffix https://$DataLakeAccountName.$DataLakeAccountQueueEndpointSuffix @@ -164,16 +165,31 @@ $content = $KeyVaultUri $TestApplicationId $TestApplicationSecret - $TenantId + $StorageTenantId https://login.microsoftonline.com/ " +$storageTestConfigurationTemplateName = 'TestConfigurationsTemplate.xml' +$storageTestConfigurationName = 'TestConfigurations.xml' + +if(-not (Test-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY -ErrorAction Ignore) ) { + Write-Verbose "Checking for '$storageTestConfigurationTemplateName' files under '$PSScriptRoot'" + + $foundFile = Get-ChildItem -Path $PSScriptRoot -Filter $storageTestConfigurationTemplateName -Recurse | Select-Object -First 1 + $storageTemplateDirName = $foundFile.Directory.FullName + Write-Verbose "Found template dir '$storageTemplateDirName'" + +} else { + $storageTemplateDirName = $env:BUILD_ARTIFACTSTAGINGDIRECTORY + Write-Verbose "Found environment variable BUILD_ARTIFACTSTAGINGDIRECTORY '$storageTemplateDirName'" +} + # Construct the test configuration path to use based on the devops build variable for artifact staging directory -$TestConfigurationPath = Join-Path -Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY -ChildPath 'TestConfiguration.xml' +$TestConfigurationPath = Join-Path -Path $storageTemplateDirName -ChildPath $storageTestConfigurationName -Write-Verbose "Writing test configuration file to $TestConfigurationPath" +Write-Verbose "Writing test configuration file to '$TestConfigurationPath'" $content | Set-Content $TestConfigurationPath Write-Verbose "Setting AZ_STORAGE_CONFIG_PATH environment variable used by Storage Tests" From 0807556dc63583e0090d749351fbd195a85e804e Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 10 Jun 2021 13:29:12 -0500 Subject: [PATCH 21/39] relocate ISupportsTenantIdChallenges --- sdk/core/Azure.Core/api/Azure.Core.net461.cs | 4 ---- sdk/core/Azure.Core/api/Azure.Core.net5.0.cs | 4 ---- sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs | 4 ---- sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs | 4 ---- .../api/Azure.Storage.Blobs.netstandard2.0.cs | 2 +- .../src/Azure.Storage.Blobs.csproj | 1 + .../Azure.Storage.Blobs/src/BlobClientOptions.cs | 1 + .../api/Azure.Storage.Common.netstandard2.0.cs | 12 ------------ .../src/Azure.Storage.Common.csproj | 1 + .../src/Shared}/ISupportsTenantIdChallenges.cs | 8 ++++---- .../Azure.Storage.Files.DataLake.netstandard2.0.cs | 2 +- .../src/Azure.Storage.Files.DataLake.csproj | 1 + .../src/DataLakeClientOptions.cs | 1 + .../src/Azure.Storage.Files.Shares.csproj | 1 + .../api/Azure.Storage.Queues.netstandard2.0.cs | 2 +- .../src/Azure.Storage.Queues.csproj | 1 + .../Azure.Storage.Queues/src/QueueClientOptions.cs | 1 + 17 files changed, 15 insertions(+), 35 deletions(-) rename sdk/{core/Azure.Core/src => storage/Azure.Storage.Common/src/Shared}/ISupportsTenantIdChallenges.cs (71%) diff --git a/sdk/core/Azure.Core/api/Azure.Core.net461.cs b/sdk/core/Azure.Core/api/Azure.Core.net461.cs index ac527739085e9..5c53b1aca64da 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net461.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net461.cs @@ -322,10 +322,6 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } - public partial interface ISupportsTenantIdChallenges - { - bool EnableTenantDiscovery { get; } - } public abstract partial class Request : System.IDisposable { protected Request() { } diff --git a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs index 34717d003fb39..814ee23914745 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs @@ -322,10 +322,6 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } - public partial interface ISupportsTenantIdChallenges - { - bool EnableTenantDiscovery { get; } - } public abstract partial class Request : System.IDisposable { protected Request() { } diff --git a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs index ac527739085e9..5c53b1aca64da 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs @@ -322,10 +322,6 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } - public partial interface ISupportsTenantIdChallenges - { - bool EnableTenantDiscovery { get; } - } public abstract partial class Request : System.IDisposable { protected Request() { } diff --git a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs index ac527739085e9..5c53b1aca64da 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs @@ -322,10 +322,6 @@ public enum HttpPipelinePosition PerCall = 0, PerRetry = 1, } - public partial interface ISupportsTenantIdChallenges - { - bool EnableTenantDiscovery { get; } - } public abstract partial class Request : System.IDisposable { protected Request() { } diff --git a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs index 89cedb0284cb7..25b024f6f3095 100644 --- a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs @@ -47,7 +47,7 @@ public BlobClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredential c public new Azure.Storage.Blobs.BlobClient WithSnapshot(string snapshot) { throw null; } public new Azure.Storage.Blobs.BlobClient WithVersion(string versionId) { throw null; } } - public partial class BlobClientOptions : Azure.Core.ClientOptions, Azure.Core.ISupportsTenantIdChallenges + public partial class BlobClientOptions : Azure.Core.ClientOptions { public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion version = Azure.Storage.Blobs.BlobClientOptions.ServiceVersion.V2020_08_04) { } public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } } diff --git a/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj b/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj index 2a2b9be527a76..4304548c37902 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj +++ b/sdk/storage/Azure.Storage.Blobs/src/Azure.Storage.Blobs.csproj @@ -85,5 +85,6 @@ + diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs index 7389ad442f750..ac0ef34009bda 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs @@ -5,6 +5,7 @@ using Azure.Core; using Azure.Core.Pipeline; using Azure.Storage.Blobs.Models; +using Azure.Storage.Shared; namespace Azure.Storage.Blobs { diff --git a/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs b/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs index 8ed276958ee1d..88bc1ef7dfa14 100644 --- a/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs @@ -164,15 +164,3 @@ protected internal void AppendProperties(System.Text.StringBuilder stringBuilder public override string ToString() { throw null; } } } -namespace Azure.Storage.Shared -{ - public partial class StorageBearerTokenChallengeAuthorizationPolicy : Azure.Core.Pipeline.BearerTokenAuthenticationPolicy - { - public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, System.Collections.Generic.IEnumerable scopes, bool enableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } - public StorageBearerTokenChallengeAuthorizationPolicy(Azure.Core.TokenCredential credential, string scope, bool enableTenantDiscovery) : base (default(Azure.Core.TokenCredential), default(string)) { } - protected override void AuthorizeRequest(Azure.Core.HttpMessage message) { } - protected override System.Threading.Tasks.ValueTask AuthorizeRequestAsync(Azure.Core.HttpMessage message) { throw null; } - protected override bool AuthorizeRequestOnChallenge(Azure.Core.HttpMessage message) { throw null; } - protected override System.Threading.Tasks.ValueTask AuthorizeRequestOnChallengeAsync(Azure.Core.HttpMessage message) { throw null; } - } -} diff --git a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj index b79171c01b764..6064cf62b06e9 100644 --- a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj +++ b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj @@ -50,5 +50,6 @@ + diff --git a/sdk/core/Azure.Core/src/ISupportsTenantIdChallenges.cs b/sdk/storage/Azure.Storage.Common/src/Shared/ISupportsTenantIdChallenges.cs similarity index 71% rename from sdk/core/Azure.Core/src/ISupportsTenantIdChallenges.cs rename to sdk/storage/Azure.Storage.Common/src/Shared/ISupportsTenantIdChallenges.cs index a87deb6226cd7..204e47689af16 100644 --- a/sdk/core/Azure.Core/src/ISupportsTenantIdChallenges.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/ISupportsTenantIdChallenges.cs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Azure.Core +namespace Azure.Storage.Shared { /// /// Options to be exposed in client options classes related to bearer token authorization challenge scenarios. /// - public interface ISupportsTenantIdChallenges + internal interface ISupportsTenantIdChallenges { /// - /// Enables tenant discovery through the authorization challenge when the client is configured to use a . - /// When enabled, the client will attempt an initial un-authroized request to prompt a challenge in order to discover the correct tenant for the resource. + /// Enables tenant discovery through the authorization challenge when the client is configured to use a TokenCredential. + /// When enabled, the client will attempt an initial un-authorized request to prompt a challenge in order to discover the correct tenant for the resource. /// public bool EnableTenantDiscovery { get; } } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs b/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs index b1a3c75412d0a..e66d203a66dd3 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/api/Azure.Storage.Files.DataLake.netstandard2.0.cs @@ -1,6 +1,6 @@ namespace Azure.Storage.Files.DataLake { - public partial class DataLakeClientOptions : Azure.Core.ClientOptions, Azure.Core.ISupportsTenantIdChallenges + public partial class DataLakeClientOptions : Azure.Core.ClientOptions { public DataLakeClientOptions(Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion version = Azure.Storage.Files.DataLake.DataLakeClientOptions.ServiceVersion.V2020_08_04) { } public bool EnableTenantDiscovery { get { throw null; } set { } } diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj b/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj index 4d92b813d25e0..8b1e0a5b06c71 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/Azure.Storage.Files.DataLake.csproj @@ -76,5 +76,6 @@ + diff --git a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs index d170731404ce4..8ad41a24e08ea 100644 --- a/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs +++ b/sdk/storage/Azure.Storage.Files.DataLake/src/DataLakeClientOptions.cs @@ -6,6 +6,7 @@ using Azure.Core.Pipeline; using Azure.Storage.Blobs; using Azure.Storage.Files.DataLake.Models; +using Azure.Storage.Shared; namespace Azure.Storage.Files.DataLake { diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj b/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj index e09de6c1c308f..0377874541c66 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj +++ b/sdk/storage/Azure.Storage.Files.Shares/src/Azure.Storage.Files.Shares.csproj @@ -76,5 +76,6 @@ + diff --git a/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs b/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs index fedd2a0e0b5e1..ba8318e5c43fd 100644 --- a/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Queues/api/Azure.Storage.Queues.netstandard2.0.cs @@ -68,7 +68,7 @@ public QueueClient(System.Uri queueUri, Azure.Storage.StorageSharedKeyCredential public virtual System.Threading.Tasks.Task> UpdateMessageAsync(string messageId, string popReceipt, string messageText = null, System.TimeSpan visibilityTimeout = default(System.TimeSpan), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal virtual Azure.Storage.Queues.QueueClient WithClientSideEncryptionOptionsCore(Azure.Storage.ClientSideEncryptionOptions clientSideEncryptionOptions) { throw null; } } - public partial class QueueClientOptions : Azure.Core.ClientOptions, Azure.Core.ISupportsTenantIdChallenges + public partial class QueueClientOptions : Azure.Core.ClientOptions { public QueueClientOptions(Azure.Storage.Queues.QueueClientOptions.ServiceVersion version = Azure.Storage.Queues.QueueClientOptions.ServiceVersion.V2020_08_04) { } public bool EnableTenantDiscovery { get { throw null; } set { } } diff --git a/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj b/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj index 134f21c730f66..1dba68489bb0a 100644 --- a/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj +++ b/sdk/storage/Azure.Storage.Queues/src/Azure.Storage.Queues.csproj @@ -68,5 +68,6 @@ + diff --git a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs index 78f19e087ea12..9c94a9f80122d 100644 --- a/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs +++ b/sdk/storage/Azure.Storage.Queues/src/QueueClientOptions.cs @@ -6,6 +6,7 @@ using Azure.Core; using Azure.Core.Pipeline; using Azure.Storage.Queues.Models; +using Azure.Storage.Shared; namespace Azure.Storage.Queues { From 6a4a77c1417cdc1b5480638b60a1056a6e570291 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Fri, 11 Jun 2021 11:01:17 -0500 Subject: [PATCH 22/39] add tenantId options to CLI and Pwsh creds --- .../Azure.Identity/src/AzureCliCredential.cs | 14 ++++++- .../src/AzureCliCredentialOptions.cs | 16 ++++++++ .../src/AzurePowerShellCredential.cs | 2 +- .../src/AzurePowerShellCredentialOptions.cs | 7 +++- .../Azure.Identity/src/TenantIdResolver.cs | 2 +- .../tests/AzureCliCredentialTests.cs | 37 ++++++++++++++----- .../tests/AzurePowerShellCredentialsTests.cs | 34 +++++++++++------ .../tests/TenantIdResolverTests.cs | 6 +-- 8 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 sdk/identity/Azure.Identity/src/AzureCliCredentialOptions.cs diff --git a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs index 1faef65f4df43..1c318c53c385a 100644 --- a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs @@ -20,6 +20,7 @@ namespace Azure.Identity /// public class AzureCliCredential : TokenCredential { + private readonly AzureCliCredentialOptions _options; internal const string AzureCLINotInstalled = "Azure CLI not installed"; internal const string AzNotLogIn = "Please run 'az login' to set up account"; private const string WinAzureCLIError = "'az' is not recognized"; @@ -51,11 +52,20 @@ public AzureCliCredential() : this(CredentialPipeline.GetInstance(null), default) { } - internal AzureCliCredential(CredentialPipeline pipeline, IProcessService processService) + /// + /// Create an instance of CliCredential class. + /// + /// The Azure Active Directory tenant (directory) Id of the service principal. + public AzureCliCredential(AzureCliCredentialOptions options) + : this(CredentialPipeline.GetInstance(null), default, options) + { } + + internal AzureCliCredential(CredentialPipeline pipeline, IProcessService processService, AzureCliCredentialOptions options = null) { _pipeline = pipeline; _path = !string.IsNullOrEmpty(EnvironmentVariables.Path) ? EnvironmentVariables.Path : DefaultPath; _processService = processService ?? ProcessService.Default; + _options = options ?? new AzureCliCredentialOptions(); } /// @@ -98,7 +108,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC private async ValueTask RequestCliAccessTokenAsync(bool async, TokenRequestContext context, CancellationToken cancellationToken) { string resource = ScopeUtilities.ScopesToResource(context.Scopes); - string tenantId = TenantIdResolver.Resolve(null, context, null); + string tenantId = TenantIdResolver.Resolve(_options.TenantId, context, _options); ScopeUtilities.ValidateScope(resource); diff --git a/sdk/identity/Azure.Identity/src/AzureCliCredentialOptions.cs b/sdk/identity/Azure.Identity/src/AzureCliCredentialOptions.cs new file mode 100644 index 0000000000000..03b3f90859b3f --- /dev/null +++ b/sdk/identity/Azure.Identity/src/AzureCliCredentialOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Identity +{ + /// + /// Options for configuring the . + /// + public class AzureCliCredentialOptions : TokenCredentialOptions + { + /// + /// The Azure Active Directory tenant (directory) Id of the service principal + /// + public string TenantId { get; set; } + } +} diff --git a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs index fd677c66dbc7a..dcaa19c20a2cb 100644 --- a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs @@ -121,7 +121,7 @@ private async ValueTask RequestAzurePowerShellAccessTokenAsync(bool string resource = ScopeUtilities.ScopesToResource(context.Scopes); ScopeUtilities.ValidateScope(resource); - var tenantId = TenantIdResolver.Resolve(null, context, _options); + var tenantId = TenantIdResolver.Resolve(_options.TenantId, context, _options); GetFileNameAndArguments(resource, tenantId, out string fileName, out string argument); ProcessStartInfo processStartInfo = GetAzurePowerShellProcessStartInfo(fileName, argument); diff --git a/sdk/identity/Azure.Identity/src/AzurePowerShellCredentialOptions.cs b/sdk/identity/Azure.Identity/src/AzurePowerShellCredentialOptions.cs index fcecda132a6df..4853f19fefbd8 100644 --- a/sdk/identity/Azure.Identity/src/AzurePowerShellCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/AzurePowerShellCredentialOptions.cs @@ -7,5 +7,10 @@ namespace Azure.Identity /// Options for configuring the . /// public class AzurePowerShellCredentialOptions : TokenCredentialOptions - { } + { + /// + /// The Azure Active Directory tenant (directory) Id of the service principal + /// + public string TenantId { get; set; } + } } diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index 969cb9c59c708..f85d6aed6fb11 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -18,7 +18,7 @@ public static string Resolve(string explicitTenantId, TokenRequestContext contex { return options?.AllowMultiTenantAuthentication switch { - true => explicitTenantId ?? context.TenantId, + false => explicitTenantId ?? context.TenantId, _ => context.TenantId ?? explicitTenantId }; } diff --git a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs index c3b9399d1bbb4..8aea69218da5c 100644 --- a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs @@ -15,26 +15,38 @@ namespace Azure.Identity.Tests public class AzureCliCredentialTests : ClientTestBase { private const string Scope = "https://vault.azure.net/.default"; - private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; + private const string TenantId = "explicitTenantId"; + private const string TenantIdHint = "tenantIdChallenge"; public AzureCliCredentialTests(bool isAsync) : base(isAsync) { } [Test] - public async Task AuthenticateWithCliCredential([Values(null, TenantIdHint)] string tenantId) + public async Task AuthenticateWithCliCredential( + [Values(null, TenantIdHint)] string tenantId, + [Values(true, false)] bool preferHint, + [Values(null, TenantId)] string explicitTenantId) { var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - string expectedTenantId = TenantIdResolver.Resolve(null, context, null) ; + var options = new AzureCliCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = preferHint}; + string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli(); var testProcess = new TestProcess { Output = processOutput }; - AzureCliCredential credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true))); + AzureCliCredential credential = + InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true), options)); AccessToken actualToken = await credential.GetTokenAsync(context); Assert.AreEqual(expectedToken, actualToken.Token); Assert.AreEqual(expectedExpiresOn, actualToken.ExpiresOn); - if (expectedTenantId != null) + + var expectTenantId = expectedTenantId != null; + if (expectTenantId) + { + Assert.That(testProcess.StartInfo.Arguments, Does.Contain($"-tenant {expectedTenantId}")); + } + else { - Assert.That(testProcess.StartInfo.Arguments, Does.Contain(expectedTenantId)); + Assert.That(testProcess.StartInfo.Arguments, Does.Not.Contain("-tenant")); } } @@ -52,14 +64,18 @@ public async Task AuthenticateWithCliCredential_ExpiresIn() } [Test] - public void AuthenticateWithCliCredential_InvalidJsonOutput([Values("", "{}", "{\"Some\": false}", "{\"accessToken\": \"token\"}", "{\"expiresOn\" : \"1900-01-01 00:00:00.123456\"}")] string jsonContent) + public void AuthenticateWithCliCredential_InvalidJsonOutput( + [Values("", "{}", "{\"Some\": false}", "{\"accessToken\": \"token\"}", "{\"expiresOn\" : \"1900-01-01 00:00:00.123456\"}")] + string jsonContent) { var testProcess = new TestProcess { Output = jsonContent }; AzureCliCredential credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); } - private const string RefreshTokenExpiredError = "Azure CLI authentication failed due to an unknown error. ERROR: Get Token request returned http error: 400 and server response: {\"error\":\"invalid_grant\",\"error_description\":\"AADSTS70008: The provided authorization code or refresh token has expired due to inactivity. Send a new interactive authorization request for this user and resource."; + private const string RefreshTokenExpiredError = + "Azure CLI authentication failed due to an unknown error. ERROR: Get Token request returned http error: 400 and server response: {\"error\":\"invalid_grant\",\"error_description\":\"AADSTS70008: The provided authorization code or refresh token has expired due to inactivity. Send a new interactive authorization request for this user and resource."; + public static IEnumerable AzureCliExceptionScenarios() { // params @@ -69,7 +85,10 @@ public static IEnumerable AzureCliExceptionScenarios() yield return new object[] { "az: not found", AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; yield return new object[] { "Please run 'az login'", AzureCliCredential.AzNotLogIn, typeof(CredentialUnavailableException) }; yield return new object[] { RefreshTokenExpiredError, AzureCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; - yield return new object[] { "random unknown exception", AzureCliCredential.AzureCliFailedError + " random unknown exception", typeof(AuthenticationFailedException) }; + yield return new object[] + { + "random unknown exception", AzureCliCredential.AzureCliFailedError + " random unknown exception", typeof(AuthenticationFailedException) + }; } [Test] diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs index 5f04d6f48d93e..441dc66352db6 100644 --- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs @@ -21,34 +21,46 @@ public class AzurePowerShellCredentialsTests : ClientTestBase "Kg==5/11/2021 8:20:03 PM +00:0072f988bf-86f1-41af-91ab-2d7cd011db47chriss@microsoft.comBearer"; private const string Scope = "https://vault.azure.net/.default"; + private const string TenantId = "a0287521-e002-0026-7112-207c0c000000"; private const string TenantIdHint = "a0287521-e002-0026-7112-207c0c001234"; public AzurePowerShellCredentialsTests(bool isAsync) : base(isAsync) { } [Test] - public async Task AuthenticateWithAzurePowerShellCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task AuthenticateWithAzurePowerShellCredential( + [Values(null, TenantIdHint)] string tenantId, + [Values(true, false)] bool preferHint, + [Values(null, TenantId)] string explicitTenantId) { var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - string expectedTenantId = TenantIdResolver.Resolve(null, context, null) ; + var options = new AzurePowerShellCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = preferHint}; + string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30)); var testProcess = new TestProcess { Output = processOutput }; AzurePowerShellCredential credential = InstrumentClient( - new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true))); + new AzurePowerShellCredential(options, CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true))); AccessToken actualToken = await credential.GetTokenAsync(context); Assert.AreEqual(expectedToken, actualToken.Token); Assert.AreEqual(expectedExpiresOn, actualToken.ExpiresOn); - if (expectedTenantId != null) + + var iStart = testProcess.StartInfo.Arguments.IndexOf("EncodedCommand"); + iStart = testProcess.StartInfo.Arguments.IndexOf('\"', iStart) + 1; + var iEnd = testProcess.StartInfo.Arguments.IndexOf('\"', iStart); + var commandString = testProcess.StartInfo.Arguments.Substring(iStart, iEnd - iStart); + var b = Convert.FromBase64String(commandString); + commandString = Encoding.Unicode.GetString(b); + + var expectTenantId = expectedTenantId != null; + if (expectTenantId) + { + Assert.That(commandString, Does.Contain($"-TenantId {expectedTenantId}")); + } + else { - var iStart = testProcess.StartInfo.Arguments.IndexOf("EncodedCommand"); - iStart = testProcess.StartInfo.Arguments.IndexOf('\"', iStart) + 1; - var iEnd = testProcess.StartInfo.Arguments.IndexOf('\"', iStart); - var commandString = testProcess.StartInfo.Arguments.Substring(iStart, iEnd - iStart); - var b = Convert.FromBase64String(commandString); - commandString = Encoding.Unicode.GetString(b); - Assert.That(commandString, Does.Contain(expectedTenantId)); + Assert.That(commandString, Does.Not.Contain("-TenantId")); } } diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index b798ce7274a97..00122b7e7c3db 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -18,10 +18,10 @@ public class TenantIdResolverTests public static IEnumerable ResolveInputs() { - yield return new object[] { TenantId, Context_Hint, Options_True, TenantId }; + yield return new object[] { TenantId, Context_Hint, Options_True, Context_Hint.TenantId }; yield return new object[] { TenantId, Context_NoHint, Options_True, TenantId }; - yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantId }; - yield return new object[] { TenantId, Context_Hint, Options_False, Context_Hint.TenantId }; + yield return new object[] { TenantId, Context_Hint, Options_False, TenantId }; + yield return new object[] { TenantId, Context_NoHint, Options_False, TenantId }; yield return new object[] { null, Context_Hint, Options_True, Context_Hint.TenantId }; yield return new object[] { null, Context_NoHint, Options_True, null }; yield return new object[] { null, Context_Hint, Options_False, Context_Hint.TenantId }; From 59f2c7dae9202df896c0e7077d9d154198a88260 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Fri, 11 Jun 2021 12:11:07 -0500 Subject: [PATCH 23/39] export and revert fixes to RecordedTest behavior --- .../api/Azure.Identity.netstandard2.0.cs | 7 +++++++ .../tests/Shared/StorageTestBase.cs | 15 +-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs index 75f61133d8959..47246c16b76bb 100644 --- a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs +++ b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs @@ -50,9 +50,15 @@ public static partial class AzureAuthorityHosts public partial class AzureCliCredential : Azure.Core.TokenCredential { public AzureCliCredential() { } + public AzureCliCredential(Azure.Identity.AzureCliCredentialOptions options) { } public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override System.Threading.Tasks.ValueTask GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } + public partial class AzureCliCredentialOptions : Azure.Identity.TokenCredentialOptions + { + public AzureCliCredentialOptions() { } + public string TenantId { get { throw null; } set { } } + } public partial class AzurePowerShellCredential : Azure.Core.TokenCredential { public AzurePowerShellCredential() { } @@ -63,6 +69,7 @@ public AzurePowerShellCredential(Azure.Identity.AzurePowerShellCredentialOptions public partial class AzurePowerShellCredentialOptions : Azure.Identity.TokenCredentialOptions { public AzurePowerShellCredentialOptions() { } + public string TenantId { get { throw null; } set { } } } public partial class ChainedTokenCredential : Azure.Core.TokenCredential { diff --git a/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs b/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs index 4d8246721f1b6..48a0c109a03e4 100644 --- a/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs +++ b/sdk/storage/Azure.Storage.Common/tests/Shared/StorageTestBase.cs @@ -155,19 +155,7 @@ private TenantConfiguration GetTestConfig(string name, Func case RecordedTestMode.Playback: if (!_playbackConfigCache.TryGetValue(name, out config)) { - try - { - text = Recording.GetVariable(name, null); - } - catch - { - // If we can't find the variable, treat it as a mismatch recording. - throw new TestRecordingMismatchException("no recordings found"); - } - if (text == null) - { - throw new TestRecordingMismatchException($"Could not find variable '{name}'"); - } + text = Recording.GetVariable(name, null); config = TenantConfiguration.Parse(text); _playbackConfigCache[name] = config; } @@ -181,7 +169,6 @@ private TenantConfiguration GetTestConfig(string name, Func } Recording.GetVariable(name, text); break; - case RecordedTestMode.Live: default: config = getTenant(); break; From 49f572eb4fe5f63277ca20ad4f8980e4910a13b9 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 15 Jun 2021 16:23:19 -0500 Subject: [PATCH 24/39] fb --- sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs | 3 ++- .../Azure.Storage.Common/src/Azure.Storage.Common.csproj | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs index d33408d139eb2..79c35e15e39da 100644 --- a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs @@ -24,7 +24,8 @@ public Uri AuthorityHost /// /// If true, the tenant Id provided by a service authorization challenge will be used over the tenantId configured via the credential options. + /// Defaults to true. /// - public bool AllowMultiTenantAuthentication { get; set; } + public bool AllowMultiTenantAuthentication { get; set; } = true; } } diff --git a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj index 6064cf62b06e9..b032a31751354 100644 --- a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj +++ b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj @@ -37,7 +37,7 @@ - + From 58fca22be3b42f6c8db4619b0c1389274fd2d200 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 17 Jun 2021 12:58:42 -0500 Subject: [PATCH 25/39] options feedback --- .../src/AuthorizationCodeCredential.cs | 8 ++++---- .../Azure.Identity/src/AzureCliCredential.cs | 8 +++++--- .../Azure.Identity/src/AzurePowerShellCredential.cs | 8 +++++--- .../src/ClientCertificateCredential.cs | 8 ++++---- .../Azure.Identity/src/ClientSecretCredential.cs | 8 ++++---- .../Azure.Identity/src/DeviceCodeCredential.cs | 6 +++--- .../src/InteractiveBrowserCredential.cs | 8 ++++---- .../Azure.Identity/src/SharedTokenCacheCredential.cs | 6 +++--- sdk/identity/Azure.Identity/src/TenantIdResolver.cs | 6 +++--- .../Azure.Identity/src/UsernamePasswordCredential.cs | 6 +++--- .../Azure.Identity/src/VisualStudioCodeCredential.cs | 6 +++--- .../Azure.Identity/src/VisualStudioCredential.cs | 12 ++++++------ .../tests/AuthorizationCodeCredentialTests.cs | 2 +- .../Azure.Identity/tests/AzureCliCredentialTests.cs | 2 +- .../tests/AzurePowerShellCredentialsTests.cs | 2 +- .../tests/ClientCertificateCredentialTests.cs | 2 +- .../tests/ClientSecretCredentialTests.cs | 2 +- .../tests/DeviceCodeCredentialTests.cs | 2 +- .../tests/InteractiveBrowserCredentialTests.cs | 2 +- .../tests/SharedTokenCacheCredentialTests.cs | 2 +- .../Azure.Identity/tests/TenantIdResolverTests.cs | 2 +- .../tests/UsernamePasswordCredentialTests.cs | 2 +- .../tests/VisualStudioCodeCredentialTests.cs | 4 ++-- .../tests/VisualStudioCredentialTests.cs | 2 +- 24 files changed, 60 insertions(+), 56 deletions(-) diff --git a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs index b306311de3aa1..a3ed99304e0c3 100644 --- a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs @@ -21,7 +21,7 @@ public class AuthorizationCodeCredential : TokenCredential private readonly string _clientId; private readonly CredentialPipeline _pipeline; private AuthenticationRecord _record; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; private readonly MsalConfidentialClient _client; private readonly string _redirectUri; private readonly string _tenantId; @@ -85,8 +85,8 @@ internal AuthorizationCodeCredential(string tenantId, string clientId, string cl Argument.AssertNotNull(authorizationCode, nameof(authorizationCode)); _clientId = clientId; _authCode = authorizationCode ; - _options = options ??= new TokenCredentialOptions(); - _pipeline = CredentialPipeline.GetInstance(options); + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _pipeline = CredentialPipeline.GetInstance(options ?? new TokenCredentialOptions()); _redirectUri = options switch { AuthorizationCodeCredentialOptions o => o.RedirectUri?.ToString(), @@ -127,7 +127,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC try { AccessToken token; - var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _allowMultiTenantAuthentication); if (_record is null) { diff --git a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs index 1c318c53c385a..cee42f32fc039 100644 --- a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs @@ -20,7 +20,7 @@ namespace Azure.Identity /// public class AzureCliCredential : TokenCredential { - private readonly AzureCliCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; internal const string AzureCLINotInstalled = "Azure CLI not installed"; internal const string AzNotLogIn = "Please run 'az login' to set up account"; private const string WinAzureCLIError = "'az' is not recognized"; @@ -44,6 +44,7 @@ public class AzureCliCredential : TokenCredential private readonly CredentialPipeline _pipeline; private readonly IProcessService _processService; + private readonly string _tenantId; /// /// Create an instance of CliCredential class. @@ -65,7 +66,8 @@ internal AzureCliCredential(CredentialPipeline pipeline, IProcessService process _pipeline = pipeline; _path = !string.IsNullOrEmpty(EnvironmentVariables.Path) ? EnvironmentVariables.Path : DefaultPath; _processService = processService ?? ProcessService.Default; - _options = options ?? new AzureCliCredentialOptions(); + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _tenantId = options?.TenantId; } /// @@ -108,7 +110,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC private async ValueTask RequestCliAccessTokenAsync(bool async, TokenRequestContext context, CancellationToken cancellationToken) { string resource = ScopeUtilities.ScopesToResource(context.Scopes); - string tenantId = TenantIdResolver.Resolve(_options.TenantId, context, _options); + string tenantId = TenantIdResolver.Resolve(_tenantId, context, _allowMultiTenantAuthentication); ScopeUtilities.ValidateScope(resource); diff --git a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs index dcaa19c20a2cb..b66d7a729b6d5 100644 --- a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs @@ -40,7 +40,8 @@ public class AzurePowerShellCredential : TokenCredential private static readonly string DefaultWorkingDir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DefaultWorkingDirWindows : DefaultWorkingDirNonWindows; - private readonly AzurePowerShellCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; + private readonly string _tenantId; private const int ERROR_FILE_NOT_FOUND = 2; @@ -61,7 +62,8 @@ public AzurePowerShellCredential(AzurePowerShellCredentialOptions options) : thi internal AzurePowerShellCredential(AzurePowerShellCredentialOptions options, CredentialPipeline pipeline, IProcessService processService) { UseLegacyPowerShell = false; - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _tenantId = options?.TenantId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _processService = processService ?? ProcessService.Default; } @@ -121,7 +123,7 @@ private async ValueTask RequestAzurePowerShellAccessTokenAsync(bool string resource = ScopeUtilities.ScopesToResource(context.Scopes); ScopeUtilities.ValidateScope(resource); - var tenantId = TenantIdResolver.Resolve(_options.TenantId, context, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId, context, _allowMultiTenantAuthentication); GetFileNameAndArguments(resource, tenantId, out string fileName, out string argument); ProcessStartInfo processStartInfo = GetAzurePowerShellProcessStartInfo(fileName, argument); diff --git a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs index b39e9bcac75e8..b4ce68189c54b 100644 --- a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs @@ -34,7 +34,7 @@ public class ClientCertificateCredential : TokenCredential private readonly MsalConfidentialClient _client; private readonly CredentialPipeline _pipeline; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; /// /// Protected constructor for mocking. @@ -127,7 +127,7 @@ internal ClientCertificateCredential(string tenantId, string clientId, IX509Cert ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); ClientCertificateProvider = certificateProvider; - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); @@ -146,7 +146,7 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell try { - var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _allowMultiTenantAuthentication); AuthenticationResult result = _client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, false, cancellationToken).EnsureCompleted(); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); @@ -169,7 +169,7 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r try { - var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _allowMultiTenantAuthentication); AuthenticationResult result = await _client .AcquireTokenForClientAsync(requestContext.Scopes, tenantId, true, cancellationToken) .ConfigureAwait(false); diff --git a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs index 3cf6dcd2486ed..82ab3d55caeb2 100644 --- a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs @@ -20,7 +20,7 @@ public class ClientSecretCredential : TokenCredential { private readonly MsalConfidentialClient _client; private readonly CredentialPipeline _pipeline; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; /// /// Gets the Azure Active Directory tenant (directory) Id of the service principal @@ -87,7 +87,7 @@ internal ClientSecretCredential(string tenantId, string clientId, string clientS ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); ClientSecret = clientSecret; - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _client = client ?? new MsalConfidentialClient(_pipeline, tenantId, clientId, clientSecret, options as ITokenCacheOptions); } @@ -104,7 +104,7 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r try { - var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _allowMultiTenantAuthentication); AuthenticationResult result = await _client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, true, cancellationToken).ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); @@ -127,7 +127,7 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell try { - var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _allowMultiTenantAuthentication); AuthenticationResult result = _client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, false, cancellationToken).EnsureCompleted(); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); diff --git a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs index 40025e79d9770..a5f6a12cb82df 100644 --- a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs @@ -18,7 +18,7 @@ namespace Azure.Identity public class DeviceCodeCredential : TokenCredential { private readonly string _tenantId; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; internal MsalPublicClient Client { get; set; } internal string ClientId { get; } internal bool DisableAutomaticAuthentication { get; } @@ -86,7 +86,7 @@ internal DeviceCodeCredential(Func devi DeviceCodeCallback = deviceCodeCallback; DisableAutomaticAuthentication = (options as DeviceCodeCredentialOptions)?.DisableAutomaticAuthentication ?? false; Record = (options as DeviceCodeCredentialOptions)?.AuthenticationRecord; - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; Pipeline = pipeline ?? CredentialPipeline.GetInstance(options); Client = client ?? new MsalPublicClient(Pipeline, tenantId, ClientId, AzureAuthorityHosts.GetDeviceCodeRedirectUri(Pipeline.AuthorityHost).ToString(), options as ITokenCacheOptions); } @@ -198,7 +198,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC { try { - var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _allowMultiTenantAuthentication); AuthenticationResult result = await Client .AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, async, cancellationToken) .ConfigureAwait(false); diff --git a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs index d3eb60db5c96f..270383b87bfc7 100644 --- a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs +++ b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs @@ -18,7 +18,7 @@ namespace Azure.Identity public class InteractiveBrowserCredential : TokenCredential { private readonly string _tenantId; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; internal string ClientId { get; } internal string LoginHint { get; } internal MsalPublicClient Client { get; } @@ -78,7 +78,7 @@ internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCre ClientId = clientId; _tenantId = tenantId; - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; Pipeline = pipeline ?? CredentialPipeline.GetInstance(options); LoginHint = (options as InteractiveBrowserCredentialOptions)?.LoginHint; var redirectUrl = (options as InteractiveBrowserCredentialOptions)?.RedirectUri?.AbsoluteUri ?? Constants.DefaultRedirectUrl; @@ -183,7 +183,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC { try { - var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record.TenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record.TenantId, requestContext, _allowMultiTenantAuthentication); AuthenticationResult result = await Client .AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, async, cancellationToken) .ConfigureAwait(false); @@ -217,7 +217,7 @@ private async Task GetTokenViaBrowserLoginAsync(TokenRequestContext _ => Prompt.NoPrompt }; - var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record?.TenantId, context, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record?.TenantId, context, _allowMultiTenantAuthentication); AuthenticationResult result = await Client .AcquireTokenInteractiveAsync(context.Scopes, context.Claims, prompt, LoginHint, tenantId, async, cancellationToken) .ConfigureAwait(false); diff --git a/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs b/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs index 85ea4f4d2ba83..554b647c4dc13 100644 --- a/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs +++ b/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs @@ -30,7 +30,7 @@ public class SharedTokenCacheCredential : TokenCredential private readonly bool _skipTenantValidation; private readonly AuthenticationRecord _record; private readonly AsyncLockWithValue _accountAsyncLock; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; internal MsalPublicClient Client { get; } @@ -74,7 +74,7 @@ internal SharedTokenCacheCredential(string tenantId, string username, TokenCrede _skipTenantValidation = (options as SharedTokenCacheCredentialOptions)?.EnableGuestTenantAuthentication ?? false; _record = (options as SharedTokenCacheCredentialOptions)?.AuthenticationRecord; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; Client = client ?? new MsalPublicClient(_pipeline, tenantId, (options as SharedTokenCacheCredentialOptions)?.ClientId ?? Constants.DeveloperSignOnClientId, null, (options as ITokenCacheOptions) ?? s_DefaultCacheOptions); _accountAsyncLock = new AsyncLockWithValue(); } @@ -107,7 +107,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC try { - var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _allowMultiTenantAuthentication); IAccount account = await GetAccountAsync(tenantId, async, cancellationToken).ConfigureAwait(false); AuthenticationResult result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, account, tenantId, async, cancellationToken).ConfigureAwait(false); return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index f85d6aed6fb11..15a8c4af82c29 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -12,11 +12,11 @@ internal static class TenantIdResolver /// /// The tenantId passed to the ctor of the Credential. /// The . - /// The . + /// If true, the tenantId in the will be preferred, if present. /// - public static string Resolve(string explicitTenantId, TokenRequestContext context, TokenCredentialOptions options) + public static string Resolve(string explicitTenantId, TokenRequestContext context, bool allowMultiTenantAuthentication) { - return options?.AllowMultiTenantAuthentication switch + return allowMultiTenantAuthentication switch { false => explicitTenantId ?? context.TenantId, _ => context.TenantId ?? explicitTenantId diff --git a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs index 29ac43e894bfd..e4f12331f078f 100644 --- a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs +++ b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs @@ -27,7 +27,7 @@ public class UsernamePasswordCredential : TokenCredential private readonly SecureString _password; private AuthenticationRecord _record; private readonly string _tenantId; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; /// /// Protected constructor for mocking @@ -83,7 +83,7 @@ internal UsernamePasswordCredential(string username, string password, string ten Argument.AssertNotNull(password, nameof(password)); Argument.AssertNotNull(clientId, nameof(clientId)); _tenantId = Validations.ValidateTenantId(tenantId, nameof(tenantId)); - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; _username = username; _password = password.ToSecureString(); @@ -188,7 +188,7 @@ private async Task GetTokenImplAsync(bool async, TokenRequestContex try { - var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _allowMultiTenantAuthentication); AuthenticationResult result = await _client .AcquireTokenByUsernamePasswordAsync(requestContext.Scopes, requestContext.Claims, _username, _password, tenantId, async, cancellationToken) diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs index 1bde6652673f7..407eae3fb4b42 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs @@ -27,7 +27,7 @@ public class VisualStudioCodeCredential : TokenCredential private readonly string _tenantId; private readonly MsalPublicClient _client; private const string _commonTenant = "common"; - private readonly TokenCredentialOptions _options; + private readonly bool _allowMultiTenantAuthentication; /// /// Creates a new instance of the . @@ -48,7 +48,7 @@ internal VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options, C _client = client ?? new MsalPublicClient(_pipeline, options?.TenantId, ClientId, null, null); _fileSystem = fileSystem ?? FileSystemService.Default; _vscAdapter = vscAdapter ?? GetVscAdapter(); - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; } /// @@ -66,7 +66,7 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque try { GetUserSettings(out var tenant, out var environmentName); - var tenantId = TenantIdResolver.Resolve(tenant, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(tenant, requestContext, _allowMultiTenantAuthentication); if (string.Equals(tenantId, Constants.AdfsTenantId, StringComparison.Ordinal)) { diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs index daa3efd1b9e0a..5ce65ce03ce88 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs @@ -29,8 +29,8 @@ public class VisualStudioCredential : TokenCredential private readonly string _tenantId; private readonly IFileSystemService _fileSystem; private readonly IProcessService _processService; - private readonly TokenCredentialOptions _options; - private bool tenantIdOptionProvided; + private readonly bool _allowMultiTenantAuthentication; + private readonly bool _tenantIdOptionProvided; /// /// Creates a new instance of the . @@ -43,13 +43,13 @@ public VisualStudioCredential() : this(null) { } /// Options for configuring the credential. public VisualStudioCredential(VisualStudioCredentialOptions options) : this(options?.TenantId, CredentialPipeline.GetInstance(options), default, default) { - _options = options; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; } internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IFileSystemService fileSystem, IProcessService processService, VisualStudioCredentialOptions options = null) { - _options = options; - tenantIdOptionProvided = tenantId != null; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _tenantIdOptionProvided = tenantId != null; _tenantId = tenantId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(null); _fileSystem = fileSystem ?? FileSystemService.Default; @@ -166,7 +166,7 @@ private List GetProcessStartInfos(VisualStudioTokenProvider[] arguments.Clear(); arguments.Append(ResourceArgumentName).Append(' ').Append(resource); - var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _options); + var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _allowMultiTenantAuthentication); if (tenantId != default) { arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(tenantId); diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 78b64b6ce5645..323680634ff92 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -95,7 +95,7 @@ public async Task AuthenticateWithAuthCodeHonorsTenantId([Values(null, TenantIdH { options = new TokenCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); AuthorizationCodeCredential cred = InstrumentClient(new AuthorizationCodeCredential(TenantId, ClientId, clientSecret, authCode, options, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs index 8aea69218da5c..a8c5c99f005b9 100644 --- a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs @@ -28,7 +28,7 @@ public async Task AuthenticateWithCliCredential( { var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); var options = new AzureCliCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = preferHint}; - string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options); + string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options.AllowMultiTenantAuthentication); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli(); var testProcess = new TestProcess { Output = processOutput }; diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs index 441dc66352db6..935709a86e77f 100644 --- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs @@ -35,7 +35,7 @@ public async Task AuthenticateWithAzurePowerShellCredential( { var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); var options = new AzurePowerShellCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = preferHint}; - string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options); + string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options.AllowMultiTenantAuthentication); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30)); var testProcess = new TestProcess { Output = processOutput }; diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs index 3ff7d5cf3b034..816ac97fe2feb 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs @@ -162,7 +162,7 @@ public async Task UsesTenantIdHint( TestSetup(); options.AllowMultiTenantAuthentication = preferHint; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); var certificatePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); var certificatePathPem = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pem"); var mockCert = new X509Certificate2(certificatePath); diff --git a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs index 5f15e271d7db8..e3deaae60966d 100644 --- a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs @@ -51,7 +51,7 @@ public async Task UsesTenantIdHint( TestSetup(); options.AllowMultiTenantAuthentication = preferHint; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); ClientSecretCredential client = InstrumentClient(new ClientSecretCredential(expectedTenantId, ClientId, "secret", options, null, mockMsalClient)); var token = await client.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index 30fc1c102fec8..3a27a4884566a 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -108,7 +108,7 @@ public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantIdHint { options = new TokenCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options) ; + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication) ; var cred = InstrumentClient( new DeviceCodeCredential((code, _) => VerifyDeviceCode(code, expectedCode), TenantId, ClientId, options, null, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index 037516a3d5853..e432daba1bea5 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -225,7 +225,7 @@ public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, TestSetup(); var options = new InteractiveBrowserCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); var credential = InstrumentClient( new InteractiveBrowserCredential( diff --git a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs index e1d0f5c6fca22..fe1a157f7d057 100644 --- a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs @@ -580,7 +580,7 @@ public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, TestSetup(); var options = new SharedTokenCacheCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); mockMsal.Accounts = new List { new MockAccount(expectedUsername, expectedTenantId) diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index 00122b7e7c3db..dbc9ad620268e 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -32,7 +32,7 @@ public static IEnumerable ResolveInputs() [TestCaseSource(nameof(ResolveInputs))] public void Resolve(string tenantId, TokenRequestContext context, TokenCredentialOptions options, string expectedTenantId) { - var result = TenantIdResolver.Resolve(tenantId, context, options); + var result = TenantIdResolver.Resolve(tenantId, context, options.AllowMultiTenantAuthentication); Assert.AreEqual(expectedTenantId, result); } diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index 1795d6c4e0775..4a193e9c2c3c4 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -58,7 +58,7 @@ public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, TestSetup(); var options = new UsernamePasswordCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); var credential = InstrumentClient(new UsernamePasswordCredential("user", "password", TenantId, ClientId, options, null, mockMsal)); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs index b1375dc0579ac..4de64b6666d96 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs @@ -66,7 +66,7 @@ public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] var environment = new IdentityTestEnvironment(); var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), AllowMultiTenantAuthentication = preferHint }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(environment.TenantId, context, options); + expectedTenantId = TenantIdResolver.Resolve(environment.TenantId, context, options.AllowMultiTenantAuthentication); VisualStudioCodeCredential credential = InstrumentClient( new VisualStudioCodeCredential( @@ -87,7 +87,7 @@ public void AdfsTenantThrowsCredentialUnavailable() { var options = new VisualStudioCodeCredentialOptions { TenantId = "adfs", Transport = new MockTransport() }; var context = new TokenRequestContext(new[] { Scope }); - string expectedTenantId = TenantIdResolver.Resolve(null, context, options); + string expectedTenantId = TenantIdResolver.Resolve(null, context, options.AllowMultiTenantAuthentication); VisualStudioCodeCredential credential = InstrumentClient(new VisualStudioCodeCredential(options)); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs index 3e7e2a220865e..798088ba6a1d3 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs @@ -31,7 +31,7 @@ public async Task AuthenticateWithVsCredential([Values(null, TenantIdHint)] stri var options = new VisualStudioCredentialOptions { AllowMultiTenantAuthentication = preferHint }; var credential = InstrumentClient(new VisualStudioCredential(TenantId, default, fileSystem, new TestProcessService(testProcess, true), options)); var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options); + expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); var token = await credential.GetTokenAsync(context, default); From d472066f99cbbe8a7667940cbd0fb9defca7d200 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Fri, 18 Jun 2021 14:21:42 -0500 Subject: [PATCH 26/39] fix recordings --- .../ExistsAsync_WithTenantDiscovery.json | 192 ------------------ .../ExistsAsync_WithTenantDiscoveryAsync.json | 192 ------------------ .../TenantDiscoveryBlobBaseClientTests.cs | 2 +- .../tests/Shared/TenantConfiguration.cs | 11 +- 4 files changed, 10 insertions(+), 387 deletions(-) delete mode 100644 sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json delete mode 100644 sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json diff --git a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json deleted file mode 100644 index e23d621c6c225..0000000000000 --- a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "Entries": [ - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c?restype=container", - "RequestMethod": "PUT", - "RequestHeaders": { - "Accept": "application/xml", - "traceparent": "00-0c6c8947c89a40448a7dfda4e364afac-bcb2493b4eb86f40-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-blob-public-access": "container", - "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 401, - "ResponseHeaders": { - "Content-Length": "302", - "Content-Type": "application/xml", - "Date": "Wed, 09 Jun 2021 22:02:23 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "WWW-Authenticate": "Bearer authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com", - "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", - "x-ms-error-code": "NoAuthenticationInformation", - "x-ms-request-id": "4076be77-a01e-00a7-3d7b-5d0090000000", - "x-ms-version": "2020-08-04" - }, - "ResponseBody": [ - "\uFEFF\u003C?xml version=\u00221.0\u0022 encoding=\u0022utf-8\u0022?\u003E\u003CError\u003E\u003CCode\u003ENoAuthenticationInformation\u003C/Code\u003E\u003CMessage\u003EServer failed to authenticate the request. Please refer to the information in the www-authenticate header.\n", - "RequestId:4076be77-a01e-00a7-3d7b-5d0090000000\n", - "Time:2021-06-09T22:02:24.1440863Z\u003C/Message\u003E\u003C/Error\u003E" - ] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c?restype=container", - "RequestMethod": "PUT", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "traceparent": "00-0c6c8947c89a40448a7dfda4e364afac-bcb2493b4eb86f40-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-blob-public-access": "container", - "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 201, - "ResponseHeaders": { - "Content-Length": "0", - "Date": "Wed, 09 Jun 2021 22:02:25 GMT", - "ETag": "\u00220x8D92B92440CBED7\u0022", - "Last-Modified": "Wed, 09 Jun 2021 22:02:25 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-client-request-id": "a4066e04-612c-f9ec-cb2a-f3739dda6c96", - "x-ms-request-id": "4076c299-a01e-00a7-207b-5d0090000000", - "x-ms-version": "2020-08-04" - }, - "ResponseBody": [] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c/test-blob-7cd17349-5ed5-74da-8a61-69439c304e6d", - "RequestMethod": "PUT", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "Content-Length": "1024", - "Content-Type": "application/octet-stream", - "traceparent": "00-8525ee834d99a842b3f7cdfb5dc34b80-986d2af86511324e-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-blob-type": "BlockBlob", - "x-ms-client-request-id": "26483e47-1b1f-ee49-212d-5d239a6e1b62", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": "QlYhnyJjYO5\u002BqhNTabohpan2eHOOULGmk5Iay/ioXZvbhjBHuM105RPi/itKhgrQQMOaprfb3vKleguPSZX21U14eOKJ2uzcgZJgrFKFWw2HSYo0DbbjAKg\u002BoT7B5Jx\u002B6yjd0g4oR2bWviQkejKgHx7\u002BCAAKwceWlHc6SziuGp324sApqIn\u002BXwIkXk\u002BSvoV2nYEfw9ybcypgRj6W7fstJ6SGLphxdneV/6j\u002B5WN4BNXxxK22j1bBWm6WyGJKaHIG9jaqDJDtBCcKicQ0nY7G6EJppChOd5hOxdMp260USpY19\u002B4szfhEBMCGqwaRjb3gz\u002BAg4cNJuH\u002BeXEa9xCl59j4t/qafmRtKJKBox8uRvDgWSQvN9GnS7D5ujnCxh0CXDLXHZI4WQDXF1b0j4fC0DKAOp3odqwHVWhJnMAuqmJxg0bsWylep/wjVCB6rrwpNuoLQouDFeWCz8Ql8rw8qGj0gwxA1nkJIZKhxR6PiKqsHGnWJ2fW8mldaYthOLVD5q\u002B3hiuv6GBxAhz44VDK90cdddZSaoUBHZCm42jxp\u002BjdXeJy1AZ/ADAFZSVK3hEYknZAR8Li7XgmsmibBQT3g1owG4NuWB88onWiTQFjuj\u002BDjlD2rWAp/nBmSCGZ5/srEeHLGsaNqjpXB6FNPKB6xvgtF3q0ANX6Ch0PCA/9orp6dzsflKN48eAan6W\u002B2L3T74UlbMx8dxEP0QylgqIqWWDq0sllWy5YtCWWnWEsOy1fOxiK1DXt/fwqE8eS03NB7EZE0An0bpRWzLiq5dmv45XfSkeFn4Eq/NTJO0dmxeC2VK0a52cnZsyENAt5QxQmejAxt4UrvFDFGdc1LzdM7AbX8JEolPguu\u002BN6/Wt5sbm8or0XNRJfipRYUcmT8juZO0QApVKC\u002BeE0Bv\u002BhsMLpT2gddIh2NbrweGn8zEMumsWn3cIjdQhrPaTb3zIWudXRcwZhdn2zJwGJDCjkgimeCWg1\u002BdL8xdZftjbjmlqI3b6QK108zSRL9LrAlmTjgrt/OdXF3LwC1K0wAcBGz4irADJqUFrMYTxD7gUYPC9tfDN7ZFyAwv0CkVasCMtkASUyGZY2dTJgrtiZhfK66ph9woGMy2ssJj0xa8MAHFhw2\u002Bzf6iaZNwr9GrmH6z0OS6a\u002BMkZF2zW\u002BAfNVAQlzXI58KKoB2wB15I96j7CxqD8AJyAYRe4SKwC7HeQzQDH9jb4rB8KXCGymSEm6z0uBDFm0VN3pVxacbf2lSr0cQqRZfP3yhqB4bRqzdf2kV1u5NhGOeutUEDqnipXA7cGzhgWlYVJJkoW1O\u002BvHNd0U\u002BBal7BCvkce5AWjHSvwKu39VA\u002B/4AvYJc6eL1T8DjNpZko6DHvw==", - "StatusCode": 201, - "ResponseHeaders": { - "Content-Length": "0", - "Content-MD5": "7e40j/ghmi0nRzPfuMltBg==", - "Date": "Wed, 09 Jun 2021 22:02:25 GMT", - "ETag": "\u00220x8D92B9244261153\u0022", - "Last-Modified": "Wed, 09 Jun 2021 22:02:25 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-client-request-id": "26483e47-1b1f-ee49-212d-5d239a6e1b62", - "x-ms-content-crc64": "k1ArWCxhhgE=", - "x-ms-request-id": "4076c448-a01e-00a7-2d7b-5d0090000000", - "x-ms-request-server-encrypted": "true", - "x-ms-version": "2020-08-04", - "x-ms-version-id": "2021-06-09T22:02:25.8200915Z" - }, - "ResponseBody": [] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c/test-blob-7cd17349-5ed5-74da-8a61-69439c304e6d", - "RequestMethod": "HEAD", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "traceparent": "00-303db5e5c5e57b45aa30b8befaed11e5-4c858abf220b2b48-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-client-request-id": "651c1aa9-a057-50a0-f80f-ed4dbad6c622", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 200, - "ResponseHeaders": { - "Accept-Ranges": "bytes", - "Content-Length": "1024", - "Content-MD5": "7e40j/ghmi0nRzPfuMltBg==", - "Content-Type": "application/octet-stream", - "Date": "Wed, 09 Jun 2021 22:02:25 GMT", - "ETag": "\u00220x8D92B9244261153\u0022", - "Last-Modified": "Wed, 09 Jun 2021 22:02:25 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-access-tier": "Hot", - "x-ms-access-tier-inferred": "true", - "x-ms-blob-type": "BlockBlob", - "x-ms-client-request-id": "651c1aa9-a057-50a0-f80f-ed4dbad6c622", - "x-ms-creation-time": "Wed, 09 Jun 2021 22:02:25 GMT", - "x-ms-is-current-version": "true", - "x-ms-last-access-time": "Wed, 09 Jun 2021 22:02:25 GMT", - "x-ms-lease-state": "available", - "x-ms-lease-status": "unlocked", - "x-ms-request-id": "4076c484-a01e-00a7-667b-5d0090000000", - "x-ms-server-encrypted": "true", - "x-ms-version": "2020-08-04", - "x-ms-version-id": "2021-06-09T22:02:25.8200915Z" - }, - "ResponseBody": [] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-e920b8d1-2959-36f3-cc0f-dd260d29088c?restype=container", - "RequestMethod": "DELETE", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "traceparent": "00-a08df38acd582f49bbc00608f57066c9-0f774f4a0c5c9d41-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-client-request-id": "807a9687-b552-460a-7e18-07ed0138eab1", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 202, - "ResponseHeaders": { - "Content-Length": "0", - "Date": "Wed, 09 Jun 2021 22:02:25 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-client-request-id": "807a9687-b552-460a-7e18-07ed0138eab1", - "x-ms-request-id": "4076c4d9-a01e-00a7-387b-5d0090000000", - "x-ms-version": "2020-08-04" - }, - "ResponseBody": [] - } - ], - "Variables": { - "RandomSeed": "1852432794", - "Storage_TestConfigOAuth": "OAuthTenant\nchrissstorageprim\nU2FuaXRpemVk\nhttps://chrissstorageprim.blob.core.windows.net\nhttps://chrissstorageprim.file.core.windows.net\nhttps://chrissstorageprim.queue.core.windows.net\nhttps://chrissstorageprim.table.core.windows.net\n\n\n\n\nhttps://chrissstorageprim-secondary.blob.core.windows.net\nhttps://chrissstorageprim-secondary.file.core.windows.net\nhttps://chrissstorageprim-secondary.queue.core.windows.net\nhttps://chrissstorageprim-secondary.table.core.windows.net\nfaf28b7c-a386-4110-a10c-ba1a2adc8cce\nSanitized\n72f988bf-86f1-41af-91ab-2d7cd011db47\nhttps://login.microsoftonline.com/\nCloud\nBlobEndpoint=https://chrissstorageprim.blob.core.windows.net/;QueueEndpoint=https://chrissstorageprim.queue.core.windows.net/;FileEndpoint=https://chrissstorageprim.file.core.windows.net/;BlobSecondaryEndpoint=https://chrissstorageprim-secondary.blob.core.windows.net/;QueueSecondaryEndpoint=https://chrissstorageprim-secondary.queue.core.windows.net/;FileSecondaryEndpoint=https://chrissstorageprim-secondary.file.core.windows.net/;AccountName=chrissstorageprim;AccountKey=Sanitized\n" - } -} \ No newline at end of file diff --git a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json deleted file mode 100644 index f0216ffba0745..0000000000000 --- a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "Entries": [ - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd?restype=container", - "RequestMethod": "PUT", - "RequestHeaders": { - "Accept": "application/xml", - "traceparent": "00-30e17ef4635aff479c14918d9b4914df-aec9c9d035518648-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-blob-public-access": "container", - "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 401, - "ResponseHeaders": { - "Content-Length": "302", - "Content-Type": "application/xml", - "Date": "Wed, 09 Jun 2021 22:02:25 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "WWW-Authenticate": "Bearer authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com", - "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", - "x-ms-error-code": "NoAuthenticationInformation", - "x-ms-request-id": "4076c56a-a01e-00a7-457b-5d0090000000", - "x-ms-version": "2020-08-04" - }, - "ResponseBody": [ - "\uFEFF\u003C?xml version=\u00221.0\u0022 encoding=\u0022utf-8\u0022?\u003E\u003CError\u003E\u003CCode\u003ENoAuthenticationInformation\u003C/Code\u003E\u003CMessage\u003EServer failed to authenticate the request. Please refer to the information in the www-authenticate header.\n", - "RequestId:4076c56a-a01e-00a7-457b-5d0090000000\n", - "Time:2021-06-09T22:02:26.1254686Z\u003C/Message\u003E\u003C/Error\u003E" - ] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd?restype=container", - "RequestMethod": "PUT", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "traceparent": "00-30e17ef4635aff479c14918d9b4914df-aec9c9d035518648-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-blob-public-access": "container", - "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 201, - "ResponseHeaders": { - "Content-Length": "0", - "Date": "Wed, 09 Jun 2021 22:02:25 GMT", - "ETag": "\u00220x8D92B9244814055\u0022", - "Last-Modified": "Wed, 09 Jun 2021 22:02:26 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-client-request-id": "09c0ccf4-98cd-a6e3-bdc5-9cfde3b3b251", - "x-ms-request-id": "4076c623-a01e-00a7-757b-5d0090000000", - "x-ms-version": "2020-08-04" - }, - "ResponseBody": [] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd/test-blob-0453075b-739f-fb62-8676-8f04f7a7c809", - "RequestMethod": "PUT", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "Content-Length": "1024", - "Content-Type": "application/octet-stream", - "traceparent": "00-191d97b79a86684b94d1d5fffe34e7a3-ae6b0815e3bbc24e-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-blob-type": "BlockBlob", - "x-ms-client-request-id": "44f90713-22a6-9440-cf81-dcf7cd2bdf2b", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": "EM\u002B4cVRDEPyf12/q7ooGe8iFsKKXzVyAasSCVgnfrxX8ku0U\u002B0H9F/ZWZccDdw38bxM7cfYmckNyNwePwLnzwCha7luc8oCHiJirQWiVfPK3huf1oz0f1SCposM5bVc9YqWfqYixX32StOleezDb3bsV2DrS/RzqOxk781C9TRtBbt/D9dJpBSZpY\u002B9UwVrvzsQHJJUdamSItG5vdWwt1GbOHAxRGOfoq9ITaJktlnq\u002BTCSLtimGJAN6OkFk\u002BpC6PI\u002BIhaRNnbbdHnnCCQHKWh30sClHoh49SPgJ192lhLBCWSwKpRDVca5urZevwxoBr98jb0lmcplxSj2rRMbYN5rIf7DVwLUMHj962fWGjYTeByf9gRVn2\u002BaSmVvSKSznlXX950NpLO8qFlAK65mk6ry5zT2f2raFYKPKQiZaOfUSkkN/iaZAdNo302rEXe9N7q6eGVvvSLu/d4afn\u002BrI0N1b91lVJGAWeVliZwZLwQO0fFN4uyCZIkqIwBEfoNXwbgONaGWW98lNiKACjOC0dLaD6yOWUBS8wtQOzWRP9tiEcvgDvVieHYSJKjKAlpZp00xbnLSxWR7u0aaQiCpl/pLHfhrnsbde5c6eDCbiPCs\u002BpPoX81oj0hnaFOuyDZRaJCufuBh7gSKV/LflE0z3DJV6UuMwTQo4o6wADZzLrNg5ROj6Q4MTwiLXgvaiPb7N4ghWN7qxKdYhZxVrenSFyVDfrM5j/ckRZo/AWMsTlWXtMyqUdHQdiBsS2dtuqEeuUrimw5HwaeU/RNEr8NELDdGvfkdK8FXcwiOzUSTto75HEgUSbdLT/cpFVS\u002Bi8ZdJB8sKvikAY2FjyQFuPBjAUqET5N3L\u002BJ/\u002B24F9BZslOdCLaUpY4uSURq\u002Bjr6PRZcCxhAKOjw26fg4sC\u002BKog17GPY8x4tLOaG\u002B8fp0uJ0n7LXbAy4GYl6rC0es7LNYB7MNR3XEUoIPt4xyT0RBwi1fEMN1s57jcLaXlbhDnkjuYupLa3UrY543y0\u002BdkBcaZUUaUd/z5lQQKdK/LLzFyBVHg\u002BhZ\u002Blq0M7EhQehPReCjfSxzKUxs\u002BnyWNz2maDLhcf82VgwqvG7uYh4q0qBvwOPyH7OMUqLI2oVvw8MZEcuCP8/pGO/QdLy9gMcuQivYKnZZtEx/UR9caXuGvviv1UxSqJLQhqv0Hab2zs3fmphZk5lMv11/AuqwiK90SbnAEa9235WZB6fKZZaliIJNgNLjLS9jXxXy16mkI/n4f9fpMr0iZL8CwIv1glXK1XZoYwvckRyOYLKZhAfv9Oet0eW\u002BvFnFLxwT4Gijbdy8J/4zP7zuH\u002B67XtIKiNy3EsukS7BwCBkcozfvyLCz7vbV2VwJvLg==", - "StatusCode": 201, - "ResponseHeaders": { - "Content-Length": "0", - "Content-MD5": "1/M56FchsWR570k2AY8A\u002BQ==", - "Date": "Wed, 09 Jun 2021 22:02:26 GMT", - "ETag": "\u00220x8D92B92448F6D45\u0022", - "Last-Modified": "Wed, 09 Jun 2021 22:02:26 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-client-request-id": "44f90713-22a6-9440-cf81-dcf7cd2bdf2b", - "x-ms-content-crc64": "Wo2avl3Hc\u002Bk=", - "x-ms-request-id": "4076c671-a01e-00a7-3b7b-5d0090000000", - "x-ms-request-server-encrypted": "true", - "x-ms-version": "2020-08-04", - "x-ms-version-id": "2021-06-09T22:02:26.5115738Z" - }, - "ResponseBody": [] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd/test-blob-0453075b-739f-fb62-8676-8f04f7a7c809", - "RequestMethod": "HEAD", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "traceparent": "00-22ac65f6e259d4498ffde710e2f3fca2-6d6b9a12e51dac48-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-client-request-id": "50622af3-2a08-2787-6e58-ae40fb6a217d", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 200, - "ResponseHeaders": { - "Accept-Ranges": "bytes", - "Content-Length": "1024", - "Content-MD5": "1/M56FchsWR570k2AY8A\u002BQ==", - "Content-Type": "application/octet-stream", - "Date": "Wed, 09 Jun 2021 22:02:26 GMT", - "ETag": "\u00220x8D92B92448F6D45\u0022", - "Last-Modified": "Wed, 09 Jun 2021 22:02:26 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-access-tier": "Hot", - "x-ms-access-tier-inferred": "true", - "x-ms-blob-type": "BlockBlob", - "x-ms-client-request-id": "50622af3-2a08-2787-6e58-ae40fb6a217d", - "x-ms-creation-time": "Wed, 09 Jun 2021 22:02:26 GMT", - "x-ms-is-current-version": "true", - "x-ms-last-access-time": "Wed, 09 Jun 2021 22:02:26 GMT", - "x-ms-lease-state": "available", - "x-ms-lease-status": "unlocked", - "x-ms-request-id": "4076c6be-a01e-00a7-807b-5d0090000000", - "x-ms-server-encrypted": "true", - "x-ms-version": "2020-08-04", - "x-ms-version-id": "2021-06-09T22:02:26.5115738Z" - }, - "ResponseBody": [] - }, - { - "RequestUri": "https://chrissstorageprim.blob.core.windows.net/test-container-be600e85-ce7f-72b3-f4e2-cbac3e144cdd?restype=container", - "RequestMethod": "DELETE", - "RequestHeaders": { - "Accept": "application/xml", - "Authorization": "Sanitized", - "traceparent": "00-0bef330c65b52f43826872b930458f81-9d90f21d5696ed42-00", - "User-Agent": [ - "azsdk-net-Storage.Blobs/12.9.0-alpha.20210609.1", - "(.NET 5.0.6; Microsoft Windows 10.0.19043)" - ], - "x-ms-client-request-id": "a2031fbd-8fc9-2b23-fc79-f4a6ef00327b", - "x-ms-return-client-request-id": "true", - "x-ms-version": "2020-08-04" - }, - "RequestBody": null, - "StatusCode": 202, - "ResponseHeaders": { - "Content-Length": "0", - "Date": "Wed, 09 Jun 2021 22:02:26 GMT", - "Server": [ - "Windows-Azure-Blob/1.0", - "Microsoft-HTTPAPI/2.0" - ], - "x-ms-client-request-id": "a2031fbd-8fc9-2b23-fc79-f4a6ef00327b", - "x-ms-request-id": "4076c723-a01e-00a7-5e7b-5d0090000000", - "x-ms-version": "2020-08-04" - }, - "ResponseBody": [] - } - ], - "Variables": { - "RandomSeed": "1200793572", - "Storage_TestConfigOAuth": "OAuthTenant\nchrissstorageprim\nU2FuaXRpemVk\nhttps://chrissstorageprim.blob.core.windows.net\nhttps://chrissstorageprim.file.core.windows.net\nhttps://chrissstorageprim.queue.core.windows.net\nhttps://chrissstorageprim.table.core.windows.net\n\n\n\n\nhttps://chrissstorageprim-secondary.blob.core.windows.net\nhttps://chrissstorageprim-secondary.file.core.windows.net\nhttps://chrissstorageprim-secondary.queue.core.windows.net\nhttps://chrissstorageprim-secondary.table.core.windows.net\nfaf28b7c-a386-4110-a10c-ba1a2adc8cce\nSanitized\n72f988bf-86f1-41af-91ab-2d7cd011db47\nhttps://login.microsoftonline.com/\nCloud\nBlobEndpoint=https://chrissstorageprim.blob.core.windows.net/;QueueEndpoint=https://chrissstorageprim.queue.core.windows.net/;FileEndpoint=https://chrissstorageprim.file.core.windows.net/;BlobSecondaryEndpoint=https://chrissstorageprim-secondary.blob.core.windows.net/;QueueSecondaryEndpoint=https://chrissstorageprim-secondary.queue.core.windows.net/;FileSecondaryEndpoint=https://chrissstorageprim-secondary.file.core.windows.net/;AccountName=chrissstorageprim;AccountKey=Sanitized\n" - } -} \ No newline at end of file diff --git a/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs index c8d388c4fe97e..22f9e999fc6a1 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs @@ -12,7 +12,7 @@ namespace Azure.Storage.Blobs.Tests public class TenantDiscoveryBlobBaseClientTests : BlobTestBase { public TenantDiscoveryBlobBaseClientTests(bool async, BlobClientOptions.ServiceVersion serviceVersion) - : base(async, serviceVersion) + : base(async, serviceVersion, RecordedTestMode.Record) { } [RecordedTest] diff --git a/sdk/storage/Azure.Storage.Common/tests/Shared/TenantConfiguration.cs b/sdk/storage/Azure.Storage.Common/tests/Shared/TenantConfiguration.cs index cdae990e84ce4..c7869136511a7 100644 --- a/sdk/storage/Azure.Storage.Common/tests/Shared/TenantConfiguration.cs +++ b/sdk/storage/Azure.Storage.Common/tests/Shared/TenantConfiguration.cs @@ -18,6 +18,12 @@ namespace Azure.Storage.Test /// public class TenantConfiguration { + static TenantConfiguration() + { + propertyCount = typeof(TenantConfiguration).GetProperties(BindingFlags.Instance | BindingFlags.Public).Length; + } + private static int propertyCount { get; } + private const string SanitizeValue = "Sanitized"; public string TenantName { get; private set; } @@ -128,9 +134,10 @@ private static TenantType ParseTenantType(string text) => public static TenantConfiguration Parse(string text) { var values = text?.Split('\n'); - if (values == null || values.Length != 24) + if (values == null || values.Length != propertyCount) { - throw new ArgumentException(); + const string nullString = ""; + throw new ArgumentException($"Values count: {values?.Length.ToString() ?? nullString}. Expected: {propertyCount}", nameof(text)); } return new TenantConfiguration From 11f2e4c99ba8f43a1725a17c771fd6946fbe148b Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 21 Jun 2021 09:22:42 -0500 Subject: [PATCH 27/39] fb --- .../Azure.Identity/src/TenantIdResolver.cs | 2 + .../tests/AuthorizationCodeCredentialTests.cs | 4 +- .../tests/AzureCliCredentialTests.cs | 4 +- .../tests/AzurePowerShellCredentialsTests.cs | 4 +- .../tests/ClientCertificateCredentialTests.cs | 4 +- .../tests/ClientSecretCredentialTests.cs | 4 +- .../tests/DeviceCodeCredentialTests.cs | 4 +- .../InteractiveBrowserCredentialTests.cs | 4 +- .../tests/SharedTokenCacheCredentialTests.cs | 4 +- .../tests/TenantIdResolverTests.cs | 38 ++-- .../tests/UsernamePasswordCredentialTests.cs | 4 +- .../tests/VisualStudioCodeCredentialTests.cs | 4 +- .../tests/VisualStudioCredentialTests.cs | 2 +- .../ExistsAsync_WithTenantDiscovery.json | 188 ++++++++++++++++++ .../ExistsAsync_WithTenantDiscoveryAsync.json | 188 ++++++++++++++++++ .../TenantDiscoveryBlobBaseClientTests.cs | 2 +- 16 files changed, 423 insertions(+), 37 deletions(-) create mode 100644 sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json create mode 100644 sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index 15a8c4af82c29..2c06e61bffd73 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -7,6 +7,7 @@ namespace Azure.Identity { internal static class TenantIdResolver { + internal const string tenantIdMismatch = "The TenantId received from a challenge did not match the configured TenantId and AllowMultiTenantAuthentication is false."; /// /// Resolves the tenantId based on the supplied configuration values. /// @@ -18,6 +19,7 @@ public static string Resolve(string explicitTenantId, TokenRequestContext contex { return allowMultiTenantAuthentication switch { + false when context.TenantId != null && explicitTenantId != context.TenantId => throw new AuthenticationFailedException(tenantIdMismatch), false => explicitTenantId ?? context.TenantId, _ => context.TenantId ?? explicitTenantId }; diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs index 323680634ff92..614a4ba210094 100644 --- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs @@ -91,9 +91,9 @@ public async Task AuthenticateWithAuthCodeHonorsReplyUrl([Values(null, ReplyUrl) } [Test] - public async Task AuthenticateWithAuthCodeHonorsTenantId([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task AuthenticateWithAuthCodeHonorsTenantId([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication) { - options = new TokenCredentialOptions { AllowMultiTenantAuthentication = preferHint }; + options = new TokenCredentialOptions { AllowMultiTenantAuthentication = allowMultiTenantAuthentication }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); diff --git a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs index a8c5c99f005b9..b6b0d1383a397 100644 --- a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs @@ -23,11 +23,11 @@ public AzureCliCredentialTests(bool isAsync) : base(isAsync) { } [Test] public async Task AuthenticateWithCliCredential( [Values(null, TenantIdHint)] string tenantId, - [Values(true, false)] bool preferHint, + [Values(true)] bool allowMultiTenantAuthentication, [Values(null, TenantId)] string explicitTenantId) { var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - var options = new AzureCliCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = preferHint}; + var options = new AzureCliCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = allowMultiTenantAuthentication}; string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options.AllowMultiTenantAuthentication); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli(); diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs index 935709a86e77f..1b1f6baa08d2a 100644 --- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs @@ -30,11 +30,11 @@ public AzurePowerShellCredentialsTests(bool isAsync) : base(isAsync) [Test] public async Task AuthenticateWithAzurePowerShellCredential( [Values(null, TenantIdHint)] string tenantId, - [Values(true, false)] bool preferHint, + [Values(true)] bool allowMultiTenantAuthentication, [Values(null, TenantId)] string explicitTenantId) { var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); - var options = new AzurePowerShellCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = preferHint}; + var options = new AzurePowerShellCredentialOptions { TenantId = explicitTenantId, AllowMultiTenantAuthentication = allowMultiTenantAuthentication}; string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, options.AllowMultiTenantAuthentication); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30)); diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs index 816ac97fe2feb..40d91bb200569 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialTests.cs @@ -157,10 +157,10 @@ public void VerifyClientCertificateCredentialException(bool usePemFile) public async Task UsesTenantIdHint( [Values(true, false)] bool usePemFile, [Values(null, TenantIdHint)] string tenantId, - [Values(true, false)] bool preferHint) + [Values(true)] bool allowMultiTenantAuthentication) { TestSetup(); - options.AllowMultiTenantAuthentication = preferHint; + options.AllowMultiTenantAuthentication = allowMultiTenantAuthentication; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); var certificatePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); diff --git a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs index e3deaae60966d..12feff05d2a42 100644 --- a/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientSecretCredentialTests.cs @@ -46,10 +46,10 @@ public void VerifyCtorParametersValidation() public async Task UsesTenantIdHint( [Values(true, false)] bool usePemFile, [Values(null, TenantIdHint)] string tenantId, - [Values(true, false)] bool preferHint) + [Values(true)] bool allowMultiTenantAuthentication) { TestSetup(); - options.AllowMultiTenantAuthentication = preferHint; + options.AllowMultiTenantAuthentication = allowMultiTenantAuthentication; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); ClientSecretCredential client = InstrumentClient(new ClientSecretCredential(expectedTenantId, ClientId, "secret", options, null, mockMsalClient)); diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index 3a27a4884566a..d7c72e00b9e93 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -104,9 +104,9 @@ public void TestSetup() } [Test] - public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task AuthenticateWithDeviceCodeMockAsync([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication) { - options = new TokenCredentialOptions { AllowMultiTenantAuthentication = preferHint }; + options = new TokenCredentialOptions { AllowMultiTenantAuthentication = allowMultiTenantAuthentication }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication) ; var cred = InstrumentClient( diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index e432daba1bea5..06f9502e1b501 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -220,10 +220,10 @@ public void DisableAutomaticAuthenticationException() } [Test] - public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication) { TestSetup(); - var options = new InteractiveBrowserCredentialOptions { AllowMultiTenantAuthentication = preferHint }; + var options = new InteractiveBrowserCredentialOptions { AllowMultiTenantAuthentication = allowMultiTenantAuthentication }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); diff --git a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs index fe1a157f7d057..3973085b9737b 100644 --- a/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs @@ -575,10 +575,10 @@ public void ValidateClientIdSetOnMsalClient() } [Test] - public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication) { TestSetup(); - var options = new SharedTokenCacheCredentialOptions { AllowMultiTenantAuthentication = preferHint }; + var options = new SharedTokenCacheCredentialOptions { AllowMultiTenantAuthentication = allowMultiTenantAuthentication }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); mockMsal.Accounts = new List diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index dbc9ad620268e..5fb476c845b67 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -11,30 +11,38 @@ namespace Azure.Identity.Tests public class TenantIdResolverTests { private const string TenantId = "clientTenant"; - private static TokenRequestContext Context_Hint = new(Array.Empty(), tenantId: "hint" ); + private static TokenRequestContext Context_Hint = new(Array.Empty(), tenantId: "hint"); private static TokenRequestContext Context_NoHint = new(Array.Empty()); - private static TokenCredentialOptions Options_True = new() { AllowMultiTenantAuthentication = true }; - private static TokenCredentialOptions Options_False = new() { AllowMultiTenantAuthentication = false }; public static IEnumerable ResolveInputs() { - yield return new object[] { TenantId, Context_Hint, Options_True, Context_Hint.TenantId }; - yield return new object[] { TenantId, Context_NoHint, Options_True, TenantId }; - yield return new object[] { TenantId, Context_Hint, Options_False, TenantId }; - yield return new object[] { TenantId, Context_NoHint, Options_False, TenantId }; - yield return new object[] { null, Context_Hint, Options_True, Context_Hint.TenantId }; - yield return new object[] { null, Context_NoHint, Options_True, null }; - yield return new object[] { null, Context_Hint, Options_False, Context_Hint.TenantId }; - yield return new object[] { null, Context_NoHint, Options_False, null }; + yield return new object[] { TenantId, Context_Hint, true, Context_Hint.TenantId }; + yield return new object[] { TenantId, Context_NoHint, true, TenantId }; + yield return new object[] { TenantId, Context_Hint, false, TenantIdResolver.tenantIdMismatch }; + yield return new object[] { TenantId, Context_NoHint, false, TenantId }; + yield return new object[] { null, Context_Hint, true, Context_Hint.TenantId }; + yield return new object[] { null, Context_NoHint, true, null }; + yield return new object[] { null, Context_Hint, false, TenantIdResolver.tenantIdMismatch }; + yield return new object[] { null, Context_NoHint, false, null }; } [Test] [TestCaseSource(nameof(ResolveInputs))] - public void Resolve(string tenantId, TokenRequestContext context, TokenCredentialOptions options, string expectedTenantId) + public void Resolve(string tenantId, TokenRequestContext context, bool allowMultiTenantAuth, string expectedresult) { - var result = TenantIdResolver.Resolve(tenantId, context, options.AllowMultiTenantAuthentication); - - Assert.AreEqual(expectedTenantId, result); + object result = null; + try + { + result = TenantIdResolver.Resolve(tenantId, context, allowMultiTenantAuth); + } + catch (AuthenticationFailedException ex) + { + result = ex.Message; + } + finally + { + Assert.AreEqual(expectedresult, result); + } } } } diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index 4a193e9c2c3c4..7b9e46cce8030 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -53,10 +53,10 @@ public async Task VerifyMsalClientExceptionAsync() } [Test] - public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task UsesTenantIdHint([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication) { TestSetup(); - var options = new UsernamePasswordCredentialOptions { AllowMultiTenantAuthentication = preferHint }; + var options = new UsernamePasswordCredentialOptions { AllowMultiTenantAuthentication = allowMultiTenantAuthentication }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(TenantId, context, options.AllowMultiTenantAuthentication); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs index 4de64b6666d96..258d64c5b8c01 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs @@ -60,11 +60,11 @@ public void TestSetup() [Test] [NonParallelizable] - public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task AuthenticateWithVsCodeCredential([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication) { using var env = new TestEnvVar(new Dictionary {{"TENANT_ID", TenantId}}); var environment = new IdentityTestEnvironment(); - var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), AllowMultiTenantAuthentication = preferHint }; + var options = new VisualStudioCodeCredentialOptions { TenantId = environment.TenantId, Transport = new MockTransport(), AllowMultiTenantAuthentication = allowMultiTenantAuthentication }; var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId); expectedTenantId = TenantIdResolver.Resolve(environment.TenantId, context, options.AllowMultiTenantAuthentication); diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs index 798088ba6a1d3..4816252af7d6f 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs @@ -23,7 +23,7 @@ public class VisualStudioCredentialTests : ClientTestBase public VisualStudioCredentialTests(bool isAsync) : base(isAsync) { } [Test] - public async Task AuthenticateWithVsCredential([Values(null, TenantIdHint)] string tenantId, [Values(true, false)] bool preferHint) + public async Task AuthenticateWithVsCredential([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool preferHint) { var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudio(); var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForVisualStudio(); diff --git a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json new file mode 100644 index 0000000000000..e83bee4941210 --- /dev/null +++ b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscovery.json @@ -0,0 +1,188 @@ +{ + "Entries": [ + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-71433cd3-53a4-d0a6-aa17-d6940fbc88e5?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "traceparent": "00-dbf42adee8d2e149a503e7d727ab7173-4cd7de660ae4564b-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "2d971172-76d4-d131-ff66-5aa44a224dab", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 401, + "ResponseHeaders": { + "Content-Length": "302", + "Content-Type": "application/xml", + "Date": "Mon, 21 Jun 2021 14:21:51 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "WWW-Authenticate": "Bearer authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com", + "x-ms-client-request-id": "2d971172-76d4-d131-ff66-5aa44a224dab", + "x-ms-error-code": "NoAuthenticationInformation", + "x-ms-request-id": "a33f2262-201e-008a-6fa8-668173000000", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [ + "\uFEFF\u003C?xml version=\u00221.0\u0022 encoding=\u0022utf-8\u0022?\u003E\u003CError\u003E\u003CCode\u003ENoAuthenticationInformation\u003C/Code\u003E\u003CMessage\u003EServer failed to authenticate the request. Please refer to the information in the www-authenticate header.\n", + "RequestId:a33f2262-201e-008a-6fa8-668173000000\n", + "Time:2021-06-21T14:21:51.9390916Z\u003C/Message\u003E\u003C/Error\u003E" + ] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-71433cd3-53a4-d0a6-aa17-d6940fbc88e5?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-dbf42adee8d2e149a503e7d727ab7173-4cd7de660ae4564b-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "2d971172-76d4-d131-ff66-5aa44a224dab", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "ETag": "\u00220x8D934BFEAC0AD09\u0022", + "Last-Modified": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "2d971172-76d4-d131-ff66-5aa44a224dab", + "x-ms-request-id": "a33f22f9-201e-008a-63a8-668173000000", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-71433cd3-53a4-d0a6-aa17-d6940fbc88e5/test-blob-568715cd-db6d-154d-89f3-8d4a5f9d2a98", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "Content-Length": "1024", + "Content-Type": "application/octet-stream", + "traceparent": "00-97e28000f797e54b9b565ce417e74b98-8f9c186f8edaed46-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "20d2c9c8-b905-8944-7c78-d960098c77b8", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": "aNQi/SRaEQ1ybQBWYg0GwQxIO4\u002BLWJSWw4Kk4ejncmE7wjx2/1CGcwR65ADa97J77Ms9FmKbDA8/izmiti8kivqeG6DQkMG8wsiKEXSUvMzPKPQcqlz/n7YtNnQW473UVVkFO9sSqlwKgFSZEPubq89t0407COCen3vpo1lNpt4Ltbwaj4DwvH2YEioHZY\u002B6koukqBAvmU15ZpxgKAcCvdl2jlo/gBxOfBYQovW2lznsLxDOqP3N3BxuoSjjkJR1lVVQkdc0exFvViz6HvdXws5jc/n2NBTcqbI\u002B0N/zauuCmg2gZgZit7P\u002BX1LR0r0kF95agB2afmFAc\u002Bck9qsyfOtAeIPqtlzIALxG99XCCtb1GbkAmenO5xysYPIePcEIzOJmkFo6OmCBkYBgVIaLfWgRL9zxMeMBcpBnPlZqqsL6PivyW3p7ut9YfgdpbZOVIOOJDA7lFpr/ZR7J0vwKF\u002Ba9ZhZbmCHulnT8mbLblwT90h1z4Z60FSeQ8/ZcsejiTWcgrAd8OYfBmnaa6HzoJM4ZzADelzZICVHuyqUrkuSMGktLuur9gJiVsUYnGSyrqM4nQuXnBLUx2KO2jm7ilQiRDc4wnGnjG0toSOXwIO8kQyLWA1D/pBWVGuJHR/Ipsxw\u002BlgteegtkRCaZfrvQQbe0kUtMC5G3kmm4moKHm9RZPpTJsYrjxsgWcb8pZSVaxdkrk7Wn3aiKhgOipd\u002BJ3ahSQEAmBo8s0cuCNZnSji1DXdSpXAgesmHsPiuHQCDpjJQW1yTlNL9J/sHqJKcPuPjWX0cK4IGgM47eOsdjpZGqmagEvLYIXSkVX11GNI263IYzseDY3qwTdaSWB/r35lkwP4v\u002BCvxBzQIe9YEjWf4BJF1BMPSw0br88UYSclYvHnkDFfUBu9GK8eTeuukr7xylleKctbVlgG3RARm2L1qr7tYIIEQJ/2iMtz4P0hfR0QIqbN1zkmjDnZWHMLq31MKJMssSIs7XnJV6D/52Va5W/zAXi8J8OFttRIKvgc\u002Bu1ON7hp9Lv64zUs9I\u002BYJqTUfmMTGJoEgADfpvpN5MKBvFyzsa\u002Bs8OC0CC4znyMz7ZA/c6yE\u002BeJDObe3il2xrP5yq0gi1HT1J42CbwB70ODdm7lqHmEWDLJ6YHf39WaC8lsvwPPrEQkbOlwu8prR12jG2f6UR46dbK6FuGP9DSIW/2y1XA/88oqgXuQU5wonmJkfIRt37rrckmRVVViVLi0qCAMuLItY2edz8/iqeHWUXh/a7dwWrwCx6Lmrl60iCzC2\u002Bu73CXwNtkc0A/rCMhn\u002BsPc4yjBRA2jyfEPBvUJKQei3442GnVSjzSeE0qsV76avvOal\u002BUDwZed2BI04S/kA==", + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Content-MD5": "Qvx9HLd\u002BCmKoqBtVzJk7Hg==", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "ETag": "\u00220x8D934BFEAD4F467\u0022", + "Last-Modified": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "20d2c9c8-b905-8944-7c78-d960098c77b8", + "x-ms-content-crc64": "fLfJkrjteYc=", + "x-ms-request-id": "a33f2362-201e-008a-2aa8-668173000000", + "x-ms-request-server-encrypted": "true", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-71433cd3-53a4-d0a6-aa17-d6940fbc88e5/test-blob-568715cd-db6d-154d-89f3-8d4a5f9d2a98", + "RequestMethod": "HEAD", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-0f29da437949eb46ac49933ad985c33c-c731189a25ee1347-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "0980826b-9fb1-958a-5798-a32c0f6578ad", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Content-Length": "1024", + "Content-MD5": "Qvx9HLd\u002BCmKoqBtVzJk7Hg==", + "Content-Type": "application/octet-stream", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "ETag": "\u00220x8D934BFEAD4F467\u0022", + "Last-Modified": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-access-tier": "Hot", + "x-ms-access-tier-inferred": "true", + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "0980826b-9fb1-958a-5798-a32c0f6578ad", + "x-ms-creation-time": "Mon, 21 Jun 2021 14:21:53 GMT", + "x-ms-lease-state": "available", + "x-ms-lease-status": "unlocked", + "x-ms-request-id": "a33f2381-201e-008a-40a8-668173000000", + "x-ms-server-encrypted": "true", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-71433cd3-53a4-d0a6-aa17-d6940fbc88e5?restype=container", + "RequestMethod": "DELETE", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-86cda7abf2704b418b8b6461c38b8c76-33ec4f5c3294fc42-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "86f53efe-6d0c-7306-5d68-36f79b2a71ce", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 202, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "86f53efe-6d0c-7306-5d68-36f79b2a71ce", + "x-ms-request-id": "a33f2393-201e-008a-50a8-668173000000", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + } + ], + "Variables": { + "RandomSeed": "687807803", + "Storage_TestConfigOAuth": "OAuthTenant\nseanoauthcanary2\nU2FuaXRpemVk\nhttps://seanoauthcanary2.blob.core.windows.net\nhttps://chrissstorageprim.file.core.windows.net\nhttps://chrissstorageprim.queue.core.windows.net\nhttps://chrissstorageprim.table.core.windows.net\n\n\n\n\nhttps://chrissstorageprim-secondary.blob.core.windows.net\nhttps://chrissstorageprim-secondary.file.core.windows.net\nhttps://chrissstorageprim-secondary.queue.core.windows.net\nhttps://chrissstorageprim-secondary.table.core.windows.net\n68390a19-a643-458b-b726-408abf67b4fc\nSanitized\n72f988bf-86f1-41af-91ab-2d7cd011db47\nhttps://login.microsoftonline.com/\nCloud\nBlobEndpoint=https://seanoauthcanary2.blob.core.windows.net/;QueueEndpoint=https://chrissstorageprim.queue.core.windows.net/;FileEndpoint=https://chrissstorageprim.file.core.windows.net/;BlobSecondaryEndpoint=https://chrissstorageprim-secondary.blob.core.windows.net/;QueueSecondaryEndpoint=https://chrissstorageprim-secondary.queue.core.windows.net/;FileSecondaryEndpoint=https://chrissstorageprim-secondary.file.core.windows.net/;AccountName=seanoauthcanary2;AccountKey=Sanitized\n\n\n" + } +} \ No newline at end of file diff --git a/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json new file mode 100644 index 0000000000000..4c68e009e6c97 --- /dev/null +++ b/sdk/storage/Azure.Storage.Blobs/tests/SessionRecords/TenantDiscoveryBlobBaseClientTests/ExistsAsync_WithTenantDiscoveryAsync.json @@ -0,0 +1,188 @@ +{ + "Entries": [ + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-d65038fe-e179-fe15-2c07-fe55a3f43451?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "traceparent": "00-bd1224701cf01240a8144107c2347f7b-cc3666c09b32f64a-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "e4992533-d1a1-3d5f-2f43-8dc8cf23dbd2", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 401, + "ResponseHeaders": { + "Content-Length": "302", + "Content-Type": "application/xml", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "WWW-Authenticate": "Bearer authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com", + "x-ms-client-request-id": "e4992533-d1a1-3d5f-2f43-8dc8cf23dbd2", + "x-ms-error-code": "NoAuthenticationInformation", + "x-ms-request-id": "a33f239f-201e-008a-5aa8-668173000000", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [ + "\uFEFF\u003C?xml version=\u00221.0\u0022 encoding=\u0022utf-8\u0022?\u003E\u003CError\u003E\u003CCode\u003ENoAuthenticationInformation\u003C/Code\u003E\u003CMessage\u003EServer failed to authenticate the request. Please refer to the information in the www-authenticate header.\n", + "RequestId:a33f239f-201e-008a-5aa8-668173000000\n", + "Time:2021-06-21T14:21:53.4881846Z\u003C/Message\u003E\u003C/Error\u003E" + ] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-d65038fe-e179-fe15-2c07-fe55a3f43451?restype=container", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-bd1224701cf01240a8144107c2347f7b-cc3666c09b32f64a-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-public-access": "container", + "x-ms-client-request-id": "e4992533-d1a1-3d5f-2f43-8dc8cf23dbd2", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "ETag": "\u00220x8D934BFEB1E4741\u0022", + "Last-Modified": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "e4992533-d1a1-3d5f-2f43-8dc8cf23dbd2", + "x-ms-request-id": "a33f23d3-201e-008a-04a8-668173000000", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-d65038fe-e179-fe15-2c07-fe55a3f43451/test-blob-b240f801-3307-595a-b83d-c8918ff83dbb", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "Content-Length": "1024", + "Content-Type": "application/octet-stream", + "traceparent": "00-05e4b6edefce2a40901e4b30a247ff8d-38375f9a08b00e4c-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "bfa61014-4226-ee92-feec-9cf3f312aeb2", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": "NwohfltQr2o30JmiF7hD6Qx\u002BHMgxHe0Ayz8rZAjNrTcF0pDFAl13sEjWezaZt0IA\u002Bt\u002BEENt1ihkdIbIcJUthaSNhnEQnfeeuB2yAW6K2ZhT8K2opSZ1bkEd3p\u002BCrW4v9dBISOBr92Hz31glv9C93ZrueH/ngOCCGBQv9sNYGA6zgguaQoy75/1LtMXKU65sY/0AlDDuRHDzxjDIOACim8NNcjnJI5sOMpSCm8gat8hlw5LCV4fZxT2aqnhf76v4qR0GfN1wpzxl9mGcb6ik2RJoYnht4EA/c6z8LnkirkbshfKvFDbq8RhWXF0iOVWQ8dFl84kvjsjYxwkwdHTHQ0Qes\u002BW5\u002BcTfgW1gF4fnGTpXbzyruL65vPsp0k0punyjkxkVB4lz1vQIinIfz3dE9anS2Qk4G4qJYPLukCt3tcbwvIRqzhWw45REnzJMnfGD4NijVt13Z3eSj6aWhUbMlNCrKw7y2iuUIfV083xUPML9aq9O0F7leVUM2D5zGl5NeApdoXLmkQqxDzTr/nf7OlLjlRqXfcB1xbG14gHpIQebJ3tx9ptQGtWkmcUl/XrhFZ3rk4FDufpJ69vBLLMIx\u002BoxUNr4hUO3j35A8uf7T8g20Mxj/ZGCV22BKA7Djuomn9He9Kyj6IxaNAAG/scWTpgTjlvmpMZUrvlte1kw9ACUHlcSHFsfkug8BcmRf1Smat12rACOQ/cPhkWzNnEzJTwDcuBwBC489/tzpqL4xzOgxwx/vkf8BdpcBpoRaFh7CNyNkgh0NmavNbgFHp1UEr8WDnNgt2r0A20GEAGUL4ui9JnCbzUnLIyp0ISP\u002Buc9C/fa/1npFlVxlY6fbiJlGwosZiYDkaOJ7uHiuubmaAiFyQQNuTAxtd5ETNmmi75qQLV6Y5S9PXpQUQx4Cl9yjy2GGaUeW13Z/qxLu0DJ42cndHYtaARzxIuNuWgjua\u002BC1cGoIG4VT\u002BlboGTkdYI5im2pUJHlQSclElUdkJ3xtkEAxOWDHcL1DiiI0cejj08FVC7\u002BemUZmZCehw4Y\u002B79Pae4j47CctmyusEJpXHNfhYMGFDxu9zvf6ytU4Jqw3BZtPWvVf6\u002BaaJ3CU8omsD0dPv2LEud\u002B3HayR/iFS9XV\u002BdAv\u002BB8LhgXXb6\u002BirKNxuTbuN6GZJc0y7lzxJzuzFRzH7FX1\u002BrgeFbj3t6c73DPTQI7BmxQwONQG\u002BQ2\u002BFmLOJFaO2rxNe75\u002BzB3ooXIl\u002Bw4tIeKs7X4Il7eB7v2qX/9WKZNMqQVzhcTMRsr2R5dh3QH/kPR8HTbYgkcIz4jA0Gwiknfi2U4IvZwZ4KqzFkwcHA3/rs8K1ghYcCbCZrY8C3Ze07Ufgwez8tNYZjyIKW/27LA==", + "StatusCode": 201, + "ResponseHeaders": { + "Content-Length": "0", + "Content-MD5": "VQoVG7tfaihfPW1f0YYW0w==", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "ETag": "\u00220x8D934BFEB28A1E0\u0022", + "Last-Modified": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "bfa61014-4226-ee92-feec-9cf3f312aeb2", + "x-ms-content-crc64": "1b2REqcaQL8=", + "x-ms-request-id": "a33f23e1-201e-008a-0fa8-668173000000", + "x-ms-request-server-encrypted": "true", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-d65038fe-e179-fe15-2c07-fe55a3f43451/test-blob-b240f801-3307-595a-b83d-c8918ff83dbb", + "RequestMethod": "HEAD", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-3423bf36e366d741814f7505cf7f94d7-aef289ea9c4a5243-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "4af5d51a-fc57-9c05-1e59-68595227ed87", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Content-Length": "1024", + "Content-MD5": "VQoVG7tfaihfPW1f0YYW0w==", + "Content-Type": "application/octet-stream", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "ETag": "\u00220x8D934BFEB28A1E0\u0022", + "Last-Modified": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-access-tier": "Hot", + "x-ms-access-tier-inferred": "true", + "x-ms-blob-type": "BlockBlob", + "x-ms-client-request-id": "4af5d51a-fc57-9c05-1e59-68595227ed87", + "x-ms-creation-time": "Mon, 21 Jun 2021 14:21:53 GMT", + "x-ms-lease-state": "available", + "x-ms-lease-status": "unlocked", + "x-ms-request-id": "a33f23ea-201e-008a-16a8-668173000000", + "x-ms-server-encrypted": "true", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + }, + { + "RequestUri": "https://seanoauthcanary2.blob.core.windows.net/test-container-d65038fe-e179-fe15-2c07-fe55a3f43451?restype=container", + "RequestMethod": "DELETE", + "RequestHeaders": { + "Accept": "application/xml", + "Authorization": "Sanitized", + "traceparent": "00-ce26f6eaebc49240adcc0bbfda7bde46-9c1cbb64f7613b48-00", + "User-Agent": [ + "azsdk-net-Storage.Blobs/12.10.0-alpha.20210621.1", + "(.NET 5.0.7; Microsoft Windows 10.0.19043)" + ], + "x-ms-client-request-id": "1ed9facf-631c-e15a-7c13-323ac616ab04", + "x-ms-return-client-request-id": "true", + "x-ms-version": "2020-10-02" + }, + "RequestBody": null, + "StatusCode": 202, + "ResponseHeaders": { + "Content-Length": "0", + "Date": "Mon, 21 Jun 2021 14:21:53 GMT", + "Server": [ + "Windows-Azure-Blob/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-client-request-id": "1ed9facf-631c-e15a-7c13-323ac616ab04", + "x-ms-request-id": "a33f23f4-201e-008a-1ea8-668173000000", + "x-ms-version": "2020-10-02" + }, + "ResponseBody": [] + } + ], + "Variables": { + "RandomSeed": "897592860", + "Storage_TestConfigOAuth": "OAuthTenant\nseanoauthcanary2\nU2FuaXRpemVk\nhttps://seanoauthcanary2.blob.core.windows.net\nhttps://chrissstorageprim.file.core.windows.net\nhttps://chrissstorageprim.queue.core.windows.net\nhttps://chrissstorageprim.table.core.windows.net\n\n\n\n\nhttps://chrissstorageprim-secondary.blob.core.windows.net\nhttps://chrissstorageprim-secondary.file.core.windows.net\nhttps://chrissstorageprim-secondary.queue.core.windows.net\nhttps://chrissstorageprim-secondary.table.core.windows.net\n68390a19-a643-458b-b726-408abf67b4fc\nSanitized\n72f988bf-86f1-41af-91ab-2d7cd011db47\nhttps://login.microsoftonline.com/\nCloud\nBlobEndpoint=https://seanoauthcanary2.blob.core.windows.net/;QueueEndpoint=https://chrissstorageprim.queue.core.windows.net/;FileEndpoint=https://chrissstorageprim.file.core.windows.net/;BlobSecondaryEndpoint=https://chrissstorageprim-secondary.blob.core.windows.net/;QueueSecondaryEndpoint=https://chrissstorageprim-secondary.queue.core.windows.net/;FileSecondaryEndpoint=https://chrissstorageprim-secondary.file.core.windows.net/;AccountName=seanoauthcanary2;AccountKey=Sanitized\n\n\n" + } +} \ No newline at end of file diff --git a/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs index 22f9e999fc6a1..c8d388c4fe97e 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/TenantDiscoveryBlobBaseClientTests.cs @@ -12,7 +12,7 @@ namespace Azure.Storage.Blobs.Tests public class TenantDiscoveryBlobBaseClientTests : BlobTestBase { public TenantDiscoveryBlobBaseClientTests(bool async, BlobClientOptions.ServiceVersion serviceVersion) - : base(async, serviceVersion, RecordedTestMode.Record) + : base(async, serviceVersion) { } [RecordedTest] From e4c3e6596d39cadc5678c16e3392518c7500dabd Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 21 Jun 2021 10:20:18 -0500 Subject: [PATCH 28/39] move SubscriptionValidationResponse ref --- .../src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj index 90bd6aa26b107..4619219a4223d 100644 --- a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj +++ b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj @@ -21,7 +21,7 @@ - + From 8425bcd290b81cac256005ecb49a146d9dbfe7a5 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 21 Jun 2021 10:35:47 -0500 Subject: [PATCH 29/39] fix ref --- .../src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj index 4619219a4223d..8f438b4b28258 100644 --- a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj +++ b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.csproj @@ -21,7 +21,7 @@ - + From 983f9cf0cfec53f505c1d93404f15743ab378b32 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 21 Jun 2021 10:48:17 -0500 Subject: [PATCH 30/39] export --- ...ure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs index 6e949d4ca3b4c..db83d63faa9ee 100644 --- a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs +++ b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs @@ -1,3 +1,11 @@ +namespace Azure.Messaging.EventGrid.SystemEvents +{ + public partial class SubscriptionValidationResponse + { + public SubscriptionValidationResponse() { } + public string ValidationResponse { get { throw null; } set { } } + } +} namespace Microsoft.Azure.WebJobs { [Microsoft.Azure.WebJobs.ConnectionProviderAttribute(typeof(Microsoft.Azure.WebJobs.StorageAccountAttribute))] From 34945cd048cf3092c9f8b9d61347dc6e313603ba Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 21 Jun 2021 18:29:06 -0500 Subject: [PATCH 31/39] AllowMultiTenantAuth defaults to false, and added env and config control --- .../src/AuthorizationCodeCredential.cs | 2 +- .../Azure.Identity/src/AzureCliCredential.cs | 2 +- .../src/AzurePowerShellCredential.cs | 2 +- .../src/ClientCertificateCredential.cs | 2 +- .../src/ClientSecretCredential.cs | 2 +- .../src/DeviceCodeCredential.cs | 2 +- .../src/IdentityCompatSwitches.cs | 5 +++ .../src/InteractiveBrowserCredential.cs | 2 +- .../src/SharedTokenCacheCredential.cs | 2 +- .../Azure.Identity/src/TenantIdResolver.cs | 2 +- .../src/TokenCredentialOptions.cs | 4 +- .../src/UsernamePasswordCredential.cs | 2 +- .../src/VisualStudioCodeCredential.cs | 2 +- .../src/VisualStudioCredential.cs | 4 +- .../tests/TenantIdResolverTests.cs | 40 +++++++++++++++++++ 15 files changed, 60 insertions(+), 15 deletions(-) diff --git a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs index a3ed99304e0c3..89e5b5d1369e0 100644 --- a/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs @@ -85,7 +85,7 @@ internal AuthorizationCodeCredential(string tenantId, string clientId, string cl Argument.AssertNotNull(authorizationCode, nameof(authorizationCode)); _clientId = clientId; _authCode = authorizationCode ; - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; _pipeline = CredentialPipeline.GetInstance(options ?? new TokenCredentialOptions()); _redirectUri = options switch { diff --git a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs index cee42f32fc039..d1ddea09253de 100644 --- a/sdk/identity/Azure.Identity/src/AzureCliCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzureCliCredential.cs @@ -66,7 +66,7 @@ internal AzureCliCredential(CredentialPipeline pipeline, IProcessService process _pipeline = pipeline; _path = !string.IsNullOrEmpty(EnvironmentVariables.Path) ? EnvironmentVariables.Path : DefaultPath; _processService = processService ?? ProcessService.Default; - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; _tenantId = options?.TenantId; } diff --git a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs index b66d7a729b6d5..b04a449f014b8 100644 --- a/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs @@ -62,7 +62,7 @@ public AzurePowerShellCredential(AzurePowerShellCredentialOptions options) : thi internal AzurePowerShellCredential(AzurePowerShellCredentialOptions options, CredentialPipeline pipeline, IProcessService processService) { UseLegacyPowerShell = false; - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; _tenantId = options?.TenantId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _processService = processService ?? ProcessService.Default; diff --git a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs index b4ce68189c54b..b991d0f6fef82 100644 --- a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs @@ -127,7 +127,7 @@ internal ClientCertificateCredential(string tenantId, string clientId, IX509Cert ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); ClientCertificateProvider = certificateProvider; - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); diff --git a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs index 82ab3d55caeb2..1e2a515493ee4 100644 --- a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs @@ -87,7 +87,7 @@ internal ClientSecretCredential(string tenantId, string clientId, string clientS ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); ClientSecret = clientSecret; - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); _client = client ?? new MsalConfidentialClient(_pipeline, tenantId, clientId, clientSecret, options as ITokenCacheOptions); } diff --git a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs index a5f6a12cb82df..0c437a84a8ff6 100644 --- a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs @@ -86,7 +86,7 @@ internal DeviceCodeCredential(Func devi DeviceCodeCallback = deviceCodeCallback; DisableAutomaticAuthentication = (options as DeviceCodeCredentialOptions)?.DisableAutomaticAuthentication ?? false; Record = (options as DeviceCodeCredentialOptions)?.AuthenticationRecord; - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; Pipeline = pipeline ?? CredentialPipeline.GetInstance(options); Client = client ?? new MsalPublicClient(Pipeline, tenantId, ClientId, AzureAuthorityHosts.GetDeviceCodeRedirectUri(Pipeline.AuthorityHost).ToString(), options as ITokenCacheOptions); } diff --git a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs index 2d97c246575bf..d4476856af86a 100644 --- a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs +++ b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs @@ -7,6 +7,8 @@ namespace Azure.Identity { internal class IdentityCompatSwitches { + internal const string AllowMultiTenantAuthExecutionEnvVar = "AZURE_IDENTITY_ALLOWMULTITENANTAUTH"; + internal const string AllowMultiTenantAuthSwitchName = "Azure.Identity.AllowMultiTenantAuth"; internal const string DisableInteractiveThreadpoolExecutionSwitchName = "Azure.Identity.DisableInteractiveBrowserThreadpoolExecution"; internal const string DisableInteractiveThreadpoolExecutionEnvVar = "AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION"; internal const string DisableCP1ExecutionSwitchName = "Azure.Identity.DisableCP1"; @@ -17,5 +19,8 @@ public static bool DisableInteractiveBrowserThreadpoolExecution public static bool DisableCP1 => AppContextSwitchHelper.GetConfigValue(DisableCP1ExecutionSwitchName, DisableCP1ExecutionEnvVar); + + public static bool AllowMultiTenantAuth + => AppContextSwitchHelper.GetConfigValue(AllowMultiTenantAuthSwitchName, AllowMultiTenantAuthExecutionEnvVar); } } diff --git a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs index 270383b87bfc7..8ecece7040054 100644 --- a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs +++ b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs @@ -78,7 +78,7 @@ internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCre ClientId = clientId; _tenantId = tenantId; - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; Pipeline = pipeline ?? CredentialPipeline.GetInstance(options); LoginHint = (options as InteractiveBrowserCredentialOptions)?.LoginHint; var redirectUrl = (options as InteractiveBrowserCredentialOptions)?.RedirectUri?.AbsoluteUri ?? Constants.DefaultRedirectUrl; diff --git a/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs b/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs index 554b647c4dc13..b26bfdbc86c4d 100644 --- a/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs +++ b/sdk/identity/Azure.Identity/src/SharedTokenCacheCredential.cs @@ -74,7 +74,7 @@ internal SharedTokenCacheCredential(string tenantId, string username, TokenCrede _skipTenantValidation = (options as SharedTokenCacheCredentialOptions)?.EnableGuestTenantAuthentication ?? false; _record = (options as SharedTokenCacheCredentialOptions)?.AuthenticationRecord; _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; Client = client ?? new MsalPublicClient(_pipeline, tenantId, (options as SharedTokenCacheCredentialOptions)?.ClientId ?? Constants.DeveloperSignOnClientId, null, (options as ITokenCacheOptions) ?? s_DefaultCacheOptions); _accountAsyncLock = new AsyncLockWithValue(); } diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index 2c06e61bffd73..fb62cb39763dd 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -19,7 +19,7 @@ public static string Resolve(string explicitTenantId, TokenRequestContext contex { return allowMultiTenantAuthentication switch { - false when context.TenantId != null && explicitTenantId != context.TenantId => throw new AuthenticationFailedException(tenantIdMismatch), + false when context.TenantId != null && explicitTenantId != context.TenantId && !IdentityCompatSwitches.AllowMultiTenantAuth => throw new AuthenticationFailedException(tenantIdMismatch), false => explicitTenantId ?? context.TenantId, _ => context.TenantId ?? explicitTenantId }; diff --git a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs index 79c35e15e39da..1787eb27774ba 100644 --- a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs @@ -24,8 +24,8 @@ public Uri AuthorityHost /// /// If true, the tenant Id provided by a service authorization challenge will be used over the tenantId configured via the credential options. - /// Defaults to true. + /// Defaults to false. /// - public bool AllowMultiTenantAuthentication { get; set; } = true; + public bool AllowMultiTenantAuthentication { get; set; } } } diff --git a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs index e4f12331f078f..5572c6b1c3f7d 100644 --- a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs +++ b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs @@ -83,7 +83,7 @@ internal UsernamePasswordCredential(string username, string password, string ten Argument.AssertNotNull(password, nameof(password)); Argument.AssertNotNull(clientId, nameof(clientId)); _tenantId = Validations.ValidateTenantId(tenantId, nameof(tenantId)); - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; _username = username; _password = password.ToSecureString(); diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs index 407eae3fb4b42..42d37336753a8 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCodeCredential.cs @@ -48,7 +48,7 @@ internal VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options, C _client = client ?? new MsalPublicClient(_pipeline, options?.TenantId, ClientId, null, null); _fileSystem = fileSystem ?? FileSystemService.Default; _vscAdapter = vscAdapter ?? GetVscAdapter(); - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; } /// diff --git a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs index 5ce65ce03ce88..000cea10934ea 100644 --- a/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs +++ b/sdk/identity/Azure.Identity/src/VisualStudioCredential.cs @@ -43,12 +43,12 @@ public VisualStudioCredential() : this(null) { } /// Options for configuring the credential. public VisualStudioCredential(VisualStudioCredentialOptions options) : this(options?.TenantId, CredentialPipeline.GetInstance(options), default, default) { - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; } internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IFileSystemService fileSystem, IProcessService processService, VisualStudioCredentialOptions options = null) { - _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? true; + _allowMultiTenantAuthentication = options?.AllowMultiTenantAuthentication ?? false; _tenantIdOptionProvided = tenantId != null; _tenantId = tenantId; _pipeline = pipeline ?? CredentialPipeline.GetInstance(null); diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index 5fb476c845b67..1619f766f59d8 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; using Azure.Core; +using Azure.Core.TestFramework; +using Azure.Identity.Tests.Mock; +using Microsoft.Identity.Client; using NUnit.Framework; namespace Azure.Identity.Tests @@ -44,5 +47,42 @@ public void Resolve(string tenantId, TokenRequestContext context, bool allowMult Assert.AreEqual(expectedresult, result); } } + + [Test] + [NonParallelizable] + public void DoesNotThrowWhenAllowMultiTenantAuthConfigOrEnvIsTrue( + [Values(true, false, null)] bool? switchEnabled, + [Values(true, false, null)] bool? envVarEnabled) + { + TestAppContextSwitch ctx = null; + TestEnvVar env = null; + try + { + if (switchEnabled != null) + { + ctx = new TestAppContextSwitch(IdentityCompatSwitches.AllowMultiTenantAuthSwitchName, switchEnabled.Value.ToString()); + } + if (envVarEnabled != null) + { + env = new TestEnvVar(IdentityCompatSwitches.AllowMultiTenantAuthExecutionEnvVar, envVarEnabled.Value.ToString()); + } + + if (IdentityCompatSwitches.AllowMultiTenantAuth) + { + var result = TenantIdResolver.Resolve(TenantId, Context_Hint, false); + Assert.AreEqual(TenantId, result); + } + else + { + var ex = Assert.Throws(() => TenantIdResolver.Resolve(TenantId, Context_Hint, false)); + Assert.AreEqual(TenantIdResolver.tenantIdMismatch, ex.Message); + } + } + finally + { + ctx?.Dispose(); + env?.Dispose(); + } + } } } From 6762805c60f37f083c803d1e9f273d20f6d4eda5 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 22 Jun 2021 08:17:24 -0500 Subject: [PATCH 32/39] api --- ...ure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs index db83d63faa9ee..6e949d4ca3b4c 100644 --- a/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs +++ b/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/api/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.netstandard2.0.cs @@ -1,11 +1,3 @@ -namespace Azure.Messaging.EventGrid.SystemEvents -{ - public partial class SubscriptionValidationResponse - { - public SubscriptionValidationResponse() { } - public string ValidationResponse { get { throw null; } set { } } - } -} namespace Microsoft.Azure.WebJobs { [Microsoft.Azure.WebJobs.ConnectionProviderAttribute(typeof(Microsoft.Azure.WebJobs.StorageAccountAttribute))] From 6328e3d7b55a97763a85fc41b2224d7be2693b15 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 22 Jun 2021 12:06:41 -0500 Subject: [PATCH 33/39] Changelog --- sdk/identity/Azure.Identity/CHANGELOG.md | 9 ++++++++- .../Azure.Identity/src/IdentityCompatSwitches.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index c7ebbbb184fb8..2383081f54c27 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -4,7 +4,14 @@ ### Fixes and improvements -- TenantId values returned from service challenge responses can now be used to request tokens from the correct tenantId. +- TenantId values returned from service challenge responses can now be used to request tokens from the correct tenantId. To support this feature, there is a new `AllowMultiTenantAuthentication` option on `TokenCredentialOptions`. + - By default, `AllowMultiTenantAuthentication` is false. When this option property is false and the tenant Id configured in the credential options differs from the tenant Id set in the `TokenRequestContext` sent to a credential, an `AuthorizationFailedException` will be thrown. This is potentially breaking change as it could be a different exception than what was thrown previously. This exception behavior can be overridden by either setting an `AppContext` switch named "Azure.Identity.AllowMultiTenantAuth" to `true` or by setting the environment variable "AZURE_IDENTITY_ALLOW_MULTITENANT_AUTH" to "true". Note: AppContext switches can also be configured via configuration like below: + +```xml + + + + ``` ## 1.5.0-beta.1 (2021-06-08) diff --git a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs index d4476856af86a..15679f3fbc36b 100644 --- a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs +++ b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs @@ -7,7 +7,7 @@ namespace Azure.Identity { internal class IdentityCompatSwitches { - internal const string AllowMultiTenantAuthExecutionEnvVar = "AZURE_IDENTITY_ALLOWMULTITENANTAUTH"; + internal const string AllowMultiTenantAuthExecutionEnvVar = "AZURE_IDENTITY_ALLOW_MULTITENANT_AUTH"; internal const string AllowMultiTenantAuthSwitchName = "Azure.Identity.AllowMultiTenantAuth"; internal const string DisableInteractiveThreadpoolExecutionSwitchName = "Azure.Identity.DisableInteractiveBrowserThreadpoolExecution"; internal const string DisableInteractiveThreadpoolExecutionEnvVar = "AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION"; From cc41a83219f89ed5da00a55d5bcb7ad11d0b4f1c Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 22 Jun 2021 17:56:06 -0500 Subject: [PATCH 34/39] EnableLegacyTenantSelection rename --- sdk/identity/Azure.Identity/CHANGELOG.md | 4 ++-- sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs | 6 +++--- sdk/identity/Azure.Identity/src/TenantIdResolver.cs | 2 +- sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs | 4 ++-- sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 2383081f54c27..a5e28384f48b0 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -5,11 +5,11 @@ ### Fixes and improvements - TenantId values returned from service challenge responses can now be used to request tokens from the correct tenantId. To support this feature, there is a new `AllowMultiTenantAuthentication` option on `TokenCredentialOptions`. - - By default, `AllowMultiTenantAuthentication` is false. When this option property is false and the tenant Id configured in the credential options differs from the tenant Id set in the `TokenRequestContext` sent to a credential, an `AuthorizationFailedException` will be thrown. This is potentially breaking change as it could be a different exception than what was thrown previously. This exception behavior can be overridden by either setting an `AppContext` switch named "Azure.Identity.AllowMultiTenantAuth" to `true` or by setting the environment variable "AZURE_IDENTITY_ALLOW_MULTITENANT_AUTH" to "true". Note: AppContext switches can also be configured via configuration like below: + - By default, `AllowMultiTenantAuthentication` is false. When this option property is false and the tenant Id configured in the credential options differs from the tenant Id set in the `TokenRequestContext` sent to a credential, an `AuthorizationFailedException` will be thrown. This is potentially breaking change as it could be a different exception than what was thrown previously. This exception behavior can be overridden by either setting an `AppContext` switch named "Azure.Identity.EnableLegacyTenantSelection" to `true` or by setting the environment variable "AZURE_IDENTITY_ENABLE_LEGACY_TENANT_SELECTION" to "true". Note: AppContext switches can also be configured via configuration like below: ```xml - + ``` diff --git a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs index 15679f3fbc36b..327f5cd0271d2 100644 --- a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs +++ b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs @@ -7,8 +7,8 @@ namespace Azure.Identity { internal class IdentityCompatSwitches { - internal const string AllowMultiTenantAuthExecutionEnvVar = "AZURE_IDENTITY_ALLOW_MULTITENANT_AUTH"; - internal const string AllowMultiTenantAuthSwitchName = "Azure.Identity.AllowMultiTenantAuth"; + internal const string AllowMultiTenantAuthExecutionEnvVar = "AZURE_IDENTITY_ENABLE_LEGACY_TENANT_SELECTION"; + internal const string AllowMultiTenantAuthSwitchName = "Azure.Identity.EnableLegacyTenantSelection"; internal const string DisableInteractiveThreadpoolExecutionSwitchName = "Azure.Identity.DisableInteractiveBrowserThreadpoolExecution"; internal const string DisableInteractiveThreadpoolExecutionEnvVar = "AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION"; internal const string DisableCP1ExecutionSwitchName = "Azure.Identity.DisableCP1"; @@ -20,7 +20,7 @@ public static bool DisableInteractiveBrowserThreadpoolExecution public static bool DisableCP1 => AppContextSwitchHelper.GetConfigValue(DisableCP1ExecutionSwitchName, DisableCP1ExecutionEnvVar); - public static bool AllowMultiTenantAuth + public static bool EnableLegacyTenantSelection => AppContextSwitchHelper.GetConfigValue(AllowMultiTenantAuthSwitchName, AllowMultiTenantAuthExecutionEnvVar); } } diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index fb62cb39763dd..f1a0fd4b4bf3d 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -19,7 +19,7 @@ public static string Resolve(string explicitTenantId, TokenRequestContext contex { return allowMultiTenantAuthentication switch { - false when context.TenantId != null && explicitTenantId != context.TenantId && !IdentityCompatSwitches.AllowMultiTenantAuth => throw new AuthenticationFailedException(tenantIdMismatch), + false when context.TenantId != null && explicitTenantId != context.TenantId && !IdentityCompatSwitches.EnableLegacyTenantSelection => throw new AuthenticationFailedException(tenantIdMismatch), false => explicitTenantId ?? context.TenantId, _ => context.TenantId ?? explicitTenantId }; diff --git a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs index 1787eb27774ba..697582f0f4584 100644 --- a/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/TokenCredentialOptions.cs @@ -23,8 +23,8 @@ public Uri AuthorityHost } /// - /// If true, the tenant Id provided by a service authorization challenge will be used over the tenantId configured via the credential options. - /// Defaults to false. + /// If true, enables the credential to fetch tokens for any tenants the user or multi-tenant application registration is a member of. + /// Otherwise the credential will only acquire tokens from the tenant configured when the credential was constructed. /// public bool AllowMultiTenantAuthentication { get; set; } } diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index 1619f766f59d8..0190eaaf30b95 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -67,7 +67,7 @@ public void DoesNotThrowWhenAllowMultiTenantAuthConfigOrEnvIsTrue( env = new TestEnvVar(IdentityCompatSwitches.AllowMultiTenantAuthExecutionEnvVar, envVarEnabled.Value.ToString()); } - if (IdentityCompatSwitches.AllowMultiTenantAuth) + if (IdentityCompatSwitches.EnableLegacyTenantSelection) { var result = TenantIdResolver.Resolve(TenantId, Context_Hint, false); Assert.AreEqual(TenantId, result); From 4f3bb70bc420361e9eacc939346b2e784b59d038 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 22 Jun 2021 18:01:21 -0500 Subject: [PATCH 35/39] rename vars --- sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs | 6 +++--- sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs index 327f5cd0271d2..5e919ed089067 100644 --- a/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs +++ b/sdk/identity/Azure.Identity/src/IdentityCompatSwitches.cs @@ -7,8 +7,8 @@ namespace Azure.Identity { internal class IdentityCompatSwitches { - internal const string AllowMultiTenantAuthExecutionEnvVar = "AZURE_IDENTITY_ENABLE_LEGACY_TENANT_SELECTION"; - internal const string AllowMultiTenantAuthSwitchName = "Azure.Identity.EnableLegacyTenantSelection"; + internal const string EnableLegacyTenantSelectionEnvVar = "AZURE_IDENTITY_ENABLE_LEGACY_TENANT_SELECTION"; + internal const string EnableLegacyTenantSelectionSwitchName = "Azure.Identity.EnableLegacyTenantSelection"; internal const string DisableInteractiveThreadpoolExecutionSwitchName = "Azure.Identity.DisableInteractiveBrowserThreadpoolExecution"; internal const string DisableInteractiveThreadpoolExecutionEnvVar = "AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION"; internal const string DisableCP1ExecutionSwitchName = "Azure.Identity.DisableCP1"; @@ -21,6 +21,6 @@ public static bool DisableCP1 => AppContextSwitchHelper.GetConfigValue(DisableCP1ExecutionSwitchName, DisableCP1ExecutionEnvVar); public static bool EnableLegacyTenantSelection - => AppContextSwitchHelper.GetConfigValue(AllowMultiTenantAuthSwitchName, AllowMultiTenantAuthExecutionEnvVar); + => AppContextSwitchHelper.GetConfigValue(EnableLegacyTenantSelectionSwitchName, EnableLegacyTenantSelectionEnvVar); } } diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index 0190eaaf30b95..086b7cf2e7732 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -60,11 +60,11 @@ public void DoesNotThrowWhenAllowMultiTenantAuthConfigOrEnvIsTrue( { if (switchEnabled != null) { - ctx = new TestAppContextSwitch(IdentityCompatSwitches.AllowMultiTenantAuthSwitchName, switchEnabled.Value.ToString()); + ctx = new TestAppContextSwitch(IdentityCompatSwitches.EnableLegacyTenantSelectionSwitchName, switchEnabled.Value.ToString()); } if (envVarEnabled != null) { - env = new TestEnvVar(IdentityCompatSwitches.AllowMultiTenantAuthExecutionEnvVar, envVarEnabled.Value.ToString()); + env = new TestEnvVar(IdentityCompatSwitches.EnableLegacyTenantSelectionEnvVar, envVarEnabled.Value.ToString()); } if (IdentityCompatSwitches.EnableLegacyTenantSelection) From 544466022f189f3b26b8ad90405b26fbec00c36c Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 23 Jun 2021 09:30:56 -0500 Subject: [PATCH 36/39] regen snippets for schemaregistry --- sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md | 2 +- .../Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md b/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md index 2c750b5fb8a1f..0a59cbe21cec1 100644 --- a/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md +++ b/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md @@ -44,7 +44,7 @@ Once you have the Azure resource credentials and the Event Hubs namespace hostna ```C# Snippet:SchemaRegistryCreateSchemaRegistryClient // Create a new SchemaRegistry client using the default credential from Azure.Identity using environment variables previously set, // including AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID. -// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/README.md +// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/identity/Azure.Identity/README.md var client = new SchemaRegistryClient(endpoint: endpoint, credential: new DefaultAzureCredential()); ``` diff --git a/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md b/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md index 7e6fcba01976a..a574180a3fb8c 100644 --- a/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md +++ b/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md @@ -44,7 +44,7 @@ Once you have the Azure resource credentials and the Event Hubs namespace hostna ```C# Snippet:SchemaRegistryAvroCreateSchemaRegistryClient // Create a new SchemaRegistry client using the default credential from Azure.Identity using environment variables previously set, // including AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID. -// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/README.md +// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/identity/Azure.Identity/README.md var schemaRegistryClient = new SchemaRegistryClient(endpoint: endpoint, credential: new DefaultAzureCredential()); ``` From 38ffc871eb45c40a813524509f7845735dc90f6e Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 23 Jun 2021 12:26:21 -0500 Subject: [PATCH 37/39] Changelog cleanup --- sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md | 8 -------- sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md | 8 -------- sdk/storage/Azure.Storage.Common/CHANGELOG.md | 9 --------- sdk/storage/Azure.Storage.Queues/CHANGELOG.md | 9 --------- 4 files changed, 34 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md b/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md index 9ea44866803bf..1c3d2b7200438 100644 --- a/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md @@ -2,18 +2,10 @@ ## 12.7.0-beta.1 (Unreleased) -### Features Added - - Added support for service version 2020-10-02. - TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. - A new property is now available on the ClientOptions called `EnableTenantDiscovery`. If set to true, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. -### Breaking Changes - -### Key Bugs Fixed - -### Fixed - - This release contains bug fixes to improve quality. ## 12.6.0 (2021-06-08) diff --git a/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md b/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md index 742be188e2eb1..36161c06b9554 100644 --- a/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md @@ -2,17 +2,9 @@ ## 12.0.0-preview.14 (Unreleased) -### Features Added - - TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. - A new property is now available on the ClientOptions called `EnableTenantDiscovery`. If set to true, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. -### Breaking Changes - -### Key Bugs Fixed - -### Fixed - - This release contains bug fixes to improve quality. ## 12.0.0-preview.13 (2021-06-08) diff --git a/sdk/storage/Azure.Storage.Common/CHANGELOG.md b/sdk/storage/Azure.Storage.Common/CHANGELOG.md index e6c59b6d8e92c..04ff186517e9b 100644 --- a/sdk/storage/Azure.Storage.Common/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Common/CHANGELOG.md @@ -2,18 +2,9 @@ ## 12.9.0-beta.1 (Unreleased) -### Features Added - - TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. - A new property is now available on the ClientOptions called `EnableTenantDiscovery`. If set to true, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. -### Breaking Changes - -### Key Bugs Fixed - -### Fixed - - ## 12.8.0 (2021-06-08) - Includes all features from 12.8.0-beta.4. - This release contains bug fixes to improve quality. diff --git a/sdk/storage/Azure.Storage.Queues/CHANGELOG.md b/sdk/storage/Azure.Storage.Queues/CHANGELOG.md index f6e736b2af552..d76a002067a2b 100644 --- a/sdk/storage/Azure.Storage.Queues/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.Queues/CHANGELOG.md @@ -2,18 +2,9 @@ ## 12.8.0-beta.1 (Unreleased) -### Features Added - - TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. - A new property is now available on the ClientOptions called `EnableTenantDiscovery`. If set to true, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. -### Breaking Changes - -### Key Bugs Fixed - -### Fixed - - ## 12.7.0 (2021-06-08) - Includes all features from 12.7.0-beta.4. - This release contains bug fixes to improve quality. From 47f55094507fbbb8d0988aa686067f49b277b637 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 23 Jun 2021 14:17:57 -0500 Subject: [PATCH 38/39] Update README.md --- sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md b/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md index 0a59cbe21cec1..2c750b5fb8a1f 100644 --- a/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md +++ b/sdk/schemaregistry/Azure.Data.SchemaRegistry/README.md @@ -44,7 +44,7 @@ Once you have the Azure resource credentials and the Event Hubs namespace hostna ```C# Snippet:SchemaRegistryCreateSchemaRegistryClient // Create a new SchemaRegistry client using the default credential from Azure.Identity using environment variables previously set, // including AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID. -// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/identity/Azure.Identity/README.md +// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/README.md var client = new SchemaRegistryClient(endpoint: endpoint, credential: new DefaultAzureCredential()); ``` From 3896a8a07f9b68ce5c1032aee98a3b7b28160627 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 23 Jun 2021 14:18:29 -0500 Subject: [PATCH 39/39] Update README.md --- .../Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md b/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md index a574180a3fb8c..7e6fcba01976a 100644 --- a/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md +++ b/sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/README.md @@ -44,7 +44,7 @@ Once you have the Azure resource credentials and the Event Hubs namespace hostna ```C# Snippet:SchemaRegistryAvroCreateSchemaRegistryClient // Create a new SchemaRegistry client using the default credential from Azure.Identity using environment variables previously set, // including AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID. -// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/identity/Azure.Identity/README.md +// For more information on Azure.Identity usage, see: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/README.md var schemaRegistryClient = new SchemaRegistryClient(endpoint: endpoint, credential: new DefaultAzureCredential()); ```