diff --git a/sdk/identity/Azure.Identity/src/Credentials/BrowserCustomizationOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/BrowserCustomizationOptions.cs index 08796760835e6..2ac35a32c2b4c 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/BrowserCustomizationOptions.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/BrowserCustomizationOptions.cs @@ -21,8 +21,6 @@ public class BrowserCustomizationOptions internal SystemWebViewOptions SystemBrowserOptions; - internal EmbeddedWebViewOptions EmbeddedBrowserOptions; - private SystemWebViewOptions systemWebViewOptions { get @@ -32,20 +30,17 @@ private SystemWebViewOptions systemWebViewOptions } } - private EmbeddedWebViewOptions embeddedWebViewOptions + /// + /// Property to set HtmlMessageSuccess of SystemWebViewOptions from MSAL, + /// which the browser will show to the user when the user finishes authenticating successfully. + /// + public string HtmlMessageSuccess { get { - EmbeddedBrowserOptions ??= new EmbeddedWebViewOptions(); - return EmbeddedBrowserOptions; + return systemWebViewOptions.HtmlMessageSuccess; } - } - /// - /// Property to set HtmlMessageSuccess of SystemWebViewOptions. - /// - public string HtmlMessageSuccess - { set { systemWebViewOptions.HtmlMessageSuccess = value; @@ -53,25 +48,21 @@ public string HtmlMessageSuccess } /// - /// Property to set HtmlMessageError of SystemWebViewOptions. + /// Property to set HtmlMessageError of SystemWebViewOptions from MSAL, + /// which the browser will show to the user when the user finishes authenticating, but an error occurred. + /// You can use a string format e.g. "An error has occurred: {0} details: {1}". /// public string HtmlMessageError { + get + { + return systemWebViewOptions.HtmlMessageError; + } + set { systemWebViewOptions.HtmlMessageError = value; } } - - /// - /// Default constructor for . Will keep the framework default behavior, - /// when you use this constructor. - /// - public BrowserCustomizationOptions(bool useEmbeddedWebView = false) - { - UseEmbeddedWebView = null; - SystemBrowserOptions = null; - EmbeddedBrowserOptions = null; - } } } diff --git a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs index 795ef32578d09..d650de8976c86 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs @@ -21,9 +21,7 @@ public class InteractiveBrowserCredential : TokenCredential internal string[] AdditionallyAllowedTenantIds { get; } internal string ClientId { get; } internal string LoginHint { get; } - internal bool? UseEmbeddedWebView { get; } - internal SystemWebViewOptions SystemBrowserOptions { get; } - internal EmbeddedWebViewOptions EmbeddedBrowserOptions {get;} + internal BrowserCustomizationOptions BrowserCustomizedOptions { get; } internal MsalPublicClient Client { get; } internal CredentialPipeline Pipeline { get; } internal bool DisableAutomaticAuthentication { get; } @@ -91,9 +89,7 @@ internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCre Client = client ?? new MsalPublicClient(Pipeline, tenantId, clientId, redirectUrl, options); AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds((options as ISupportsAdditionallyAllowedTenants)?.AdditionallyAllowedTenants); Record = (options as InteractiveBrowserCredentialOptions)?.AuthenticationRecord; - UseEmbeddedWebView = (options as InteractiveBrowserCredentialOptions)?.BrowserCustomizationOptions.UseEmbeddedWebView ?? null; - SystemBrowserOptions = (options as InteractiveBrowserCredentialOptions)?.BrowserCustomizationOptions.SystemBrowserOptions ?? null; - EmbeddedBrowserOptions = (options as InteractiveBrowserCredentialOptions)?.BrowserCustomizationOptions.EmbeddedBrowserOptions ?? null; + BrowserCustomizedOptions = (options as InteractiveBrowserCredentialOptions)?.BrowserCustomizedOptions; } /// @@ -238,7 +234,7 @@ private async Task GetTokenViaBrowserLoginAsync(TokenRequestContext var tenantId = TenantIdResolver.Resolve(TenantId ?? Record?.TenantId, context, AdditionallyAllowedTenantIds); AuthenticationResult result = await Client - .AcquireTokenInteractiveAsync(context.Scopes, context.Claims, prompt, LoginHint, tenantId, context.IsCaeEnabled, UseEmbeddedWebView, SystemBrowserOptions, EmbeddedBrowserOptions, async, cancellationToken) + .AcquireTokenInteractiveAsync(context.Scopes, context.Claims, prompt, LoginHint, tenantId, context.IsCaeEnabled, BrowserCustomizedOptions, async, cancellationToken) .ConfigureAwait(false); Record = new AuthenticationRecord(result, ClientId); diff --git a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs index 2bdd2375d8c70..4e3ca802f5f09 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs @@ -66,25 +66,9 @@ public string TenantId /// public bool DisableInstanceDiscovery { get; set; } - private BrowserCustomizationOptions _browserCustomizationOptions = null; - /// /// The options for customizing the browser for interactive authentication. /// - public BrowserCustomizationOptions BrowserCustomizationOptions - { - get - { - if (_browserCustomizationOptions == null) - { - _browserCustomizationOptions = new BrowserCustomizationOptions(); - } - return _browserCustomizationOptions; - } - set - { - _browserCustomizationOptions = value; - } - } + public BrowserCustomizationOptions BrowserCustomizedOptions { get; set; } } } diff --git a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs index be19f0c81436f..9686623efda20 100644 --- a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; + using Microsoft.Identity.Client; namespace Azure.Identity @@ -123,7 +124,7 @@ protected virtual async ValueTask AcquireTokenSilentCoreAs .ConfigureAwait(false); } - public async ValueTask AcquireTokenInteractiveAsync(string[] scopes, string claims, Prompt prompt, string loginHint, string tenantId, bool enableCae, bool? useEmbeddedWebView, SystemWebViewOptions systemWebViewOptions, EmbeddedWebViewOptions embeddedWebViewOptions, bool async, CancellationToken cancellationToken) + public async ValueTask AcquireTokenInteractiveAsync(string[] scopes, string claims, Prompt prompt, string loginHint, string tenantId, bool enableCae, BrowserCustomizationOptions browserOptions, bool async, CancellationToken cancellationToken) { if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA && !IdentityCompatSwitches.DisableInteractiveBrowserThreadpoolExecution) { @@ -137,7 +138,7 @@ public async ValueTask AcquireTokenInteractiveAsync(string #pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). return Task.Run(async () => { - var result = await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, tenantId, enableCae, useEmbeddedWebView, systemWebViewOptions, embeddedWebViewOptions, true, cancellationToken).ConfigureAwait(false); + var result = await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, tenantId, enableCae, browserOptions, true, cancellationToken).ConfigureAwait(false); LogAccountDetails(result); return result; }).GetAwaiter().GetResult(); @@ -146,13 +147,12 @@ public async ValueTask AcquireTokenInteractiveAsync(string AzureIdentityEventSource.Singleton.InteractiveAuthenticationExecutingInline(); - var result = await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, tenantId, enableCae, useEmbeddedWebView, systemWebViewOptions, embeddedWebViewOptions, async, cancellationToken).ConfigureAwait(false); + var result = await AcquireTokenInteractiveCoreAsync(scopes, claims, prompt, loginHint, tenantId, enableCae, browserOptions, async, cancellationToken).ConfigureAwait(false); LogAccountDetails(result); return result; } - protected virtual async ValueTask AcquireTokenInteractiveCoreAsync(string[] scopes, string claims, Prompt prompt, string loginHint, string tenantId, bool enableCae, bool? useEmbeddedWebView, SystemWebViewOptions systemWebViewOptions, EmbeddedWebViewOptions embeddedWebViewOptions, - bool async, CancellationToken cancellationToken) + protected virtual async ValueTask AcquireTokenInteractiveCoreAsync(string[] scopes, string claims, Prompt prompt, string loginHint, string tenantId, bool enableCae, BrowserCustomizationOptions browserOptions, bool async, CancellationToken cancellationToken) { IPublicClientApplication client = await GetClientAsync(enableCae, async, cancellationToken).ConfigureAwait(false); @@ -169,17 +169,16 @@ protected virtual async ValueTask AcquireTokenInteractiveC { builder.WithAuthority(AuthorityHost.AbsoluteUri, tenantId); } - if (useEmbeddedWebView != null) - { - builder.WithUseEmbeddedWebView(useEmbeddedWebView.Value); - } - if (systemWebViewOptions != null) + if (browserOptions != null) { - builder.WithSystemWebViewOptions(systemWebViewOptions); - } - if (embeddedWebViewOptions != null) - { - builder.WithEmbeddedWebViewOptions(embeddedWebViewOptions); + if (browserOptions.UseEmbeddedWebView.HasValue) + { + builder.WithUseEmbeddedWebView(browserOptions.UseEmbeddedWebView.Value); + } + if (browserOptions.SystemBrowserOptions != null) + { + builder.WithSystemWebViewOptions(browserOptions.SystemBrowserOptions); + } } return await builder .ExecuteAsync(async, cancellationToken) diff --git a/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs b/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs index c9c4077c5eb8e..b3250825a0ecd 100644 --- a/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs +++ b/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs @@ -448,7 +448,7 @@ public void TestSetup(TokenCredentialOptions options = null) // Assert.AreEqual(tenantId, tId); return publicResult; }; - mockPublicMsalClient.InteractiveAuthFactory = (_, _, _, _, tenant, _, _) => + mockPublicMsalClient.InteractiveAuthFactory = (_, _, _, _, tenant, _, _, _) => { Assert.AreEqual(expectedTenantId, tenant, "TenantId passed to msal should match"); return result; diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index 4f4098c8112dd..9be5f2f6ad77a 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -9,6 +9,7 @@ using Azure.Core.TestFramework; using Azure.Identity.Tests.Mock; using Microsoft.Identity.Client; + using NUnit.Framework; namespace Azure.Identity.Tests @@ -186,7 +187,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); @@ -305,5 +306,59 @@ public async Task InvokesBeforeBuildClientOnExtendedOptions() Assert.True(beforeBuildClientInvoked); } + + [Test] + public async Task BrowserCustomizedOptionsHtmlMessage([Values(null, "

Login Successfully.

")] string htmlMessageSuccess, [Values(null, "

An error occured: {0}. Details {1}

")] string htmlMessageError) + { + var mockMsalClient = new MockMsalPublicClient + { + InteractiveAuthFactory = (_, _, _, _, _, _, browserOptions, _) => + { + Assert.AreEqual(false, browserOptions.UseEmbeddedWebView); + Assert.AreEqual(htmlMessageSuccess, browserOptions.HtmlMessageSuccess); + Assert.AreEqual(htmlMessageError, browserOptions.HtmlMessageError); + return AuthenticationResultFactory.Create(Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(5)); + } + }; + var options = new InteractiveBrowserCredentialOptions() + { + BrowserCustomizedOptions = new BrowserCustomizationOptions() + { + UseEmbeddedWebView = false, + HtmlMessageSuccess = htmlMessageSuccess, + HtmlMessageError = htmlMessageError + } + }; + + var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", options, default, mockMsalClient)); + + await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); + } + + [Test] + public async Task BrowserCustomizedUseEmbeddedWebView([Values(null, true, false)] bool useEmbeddedWebView, [Values(null, "

An error occured: {0}. Details {1}

")] string htmlMessageError) + { + var mockMsalClient = new MockMsalPublicClient + { + InteractiveAuthFactory = (_, _, _, _, _, _, browserOptions, _) => + { + Assert.AreEqual(useEmbeddedWebView, browserOptions.UseEmbeddedWebView); + Assert.AreEqual(htmlMessageError, browserOptions.HtmlMessageError); + return AuthenticationResultFactory.Create(Guid.NewGuid().ToString(), expiresOn: DateTimeOffset.UtcNow.AddMinutes(5)); + } + }; + var options = new InteractiveBrowserCredentialOptions() + { + BrowserCustomizedOptions = new BrowserCustomizationOptions() + { + UseEmbeddedWebView = useEmbeddedWebView, + HtmlMessageError = htmlMessageError + } + }; + + var credential = InstrumentClient(new InteractiveBrowserCredential(default, "", options, default, mockMsalClient)); + + await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)); + } } } diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs index 20e83f421f2b8..a94a186706455 100644 --- a/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/tests/Mock/MockMsalPublicClient.cs @@ -18,7 +18,7 @@ internal class MockMsalPublicClient : MsalPublicClient public List Accounts { get; set; } public Func AuthFactory { get; set; } 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 DeviceCodeAuthFactory { get; set; } @@ -30,7 +30,7 @@ public MockMsalPublicClient(AuthenticationResult result) { AuthFactory = (_, _) => result; UserPassAuthFactory = (_, _) => result; - InteractiveAuthFactory = (_, _, _, _, _, _, _) => result; + InteractiveAuthFactory = (_, _, _, _, _, _, _, _) => result; SilentAuthFactory = (_, _) => result; ExtendedSilentAuthFactory = (_, _, _, _, _, _) => result; DeviceCodeAuthFactory = (_, _) => result; @@ -72,9 +72,7 @@ protected override ValueTask AcquireTokenInteractiveCoreAs string loginHint, string tenantId, bool enableCae, - bool? useEmbeddedWebView, - SystemWebViewOptions systemWebViewOptions, - EmbeddedWebViewOptions embeddedWebViewOptions, + BrowserCustomizationOptions browserOptions, bool async, CancellationToken cancellationToken) { @@ -83,7 +81,7 @@ protected override ValueTask AcquireTokenInteractiveCoreAs if (interactiveAuthFactory != null) { - return new ValueTask(interactiveAuthFactory(scopes, claims, prompt, loginHint, tenantId, async, cancellationToken)); + return new ValueTask(interactiveAuthFactory(scopes, claims, prompt, loginHint, tenantId, async, browserOptions, cancellationToken)); } if (authFactory != null) {