From 80faef7a08b9385709e507fbbfd970d996ed5865 Mon Sep 17 00:00:00 2001 From: Ashok Kumar Ramakrishnan <83938949+ashok672@users.noreply.github.com> Date: Tue, 26 Mar 2024 07:04:05 -0700 Subject: [PATCH] SsoPolicy support for PCA. (#4659) * SsoPolicy initial * Remove SSOPolicy project * Update LibsAndSamples.sln * Delete files not needed * Introduce WithSSoPolicy in broker project and rename RuntimeBroker to MsalCppRuntime * Revert "Introduce WithSSoPolicy in broker project and rename RuntimeBroker to MsalCppRuntime" This reverts commit 5e9f79457c2bb33ce1f5aedc0175dc9860754178. * Update ApplicationConfiguration.cs * Update InternalsVisibleTo.cs * Hook up the E2E for adding SsoPolicy headers in OAuth2 implementation * Update NuGet.Config * Inject headers in RequestBase * Update src/client/Microsoft.Identity.Client/Platforms/Features/RuntimeBroker/RuntimeBroker.cs Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> * Update src/client/Microsoft.Identity.Client/MsalErrorMessage.cs Co-authored-by: Bogdan Gavril * Fix for review comments * Fix for review comments * Update src/client/Microsoft.Identity.Client/MsalErrorMessage.cs Co-authored-by: Bogdan Gavril * Update src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs Co-authored-by: Peter <34331512+pmaytak@users.noreply.github.com> * Update src/client/Microsoft.Identity.Client/Platforms/Features/WinFormsLegacyWebUi/WindowsFormsWebAuthenticationDialogBase.cs Co-authored-by: Peter <34331512+pmaytak@users.noreply.github.com> * Update src/client/Microsoft.Identity.Client/Internal/Broker/NullBroker.cs Co-authored-by: Peter <34331512+pmaytak@users.noreply.github.com> * Fix for review comments * Update RuntimeBroker.cs * Fix mobile build breaks --------- Co-authored-by: Bogdan Gavril Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Co-authored-by: Peter <34331512+pmaytak@users.noreply.github.com> --- Directory.Packages.props | 2 +- .../BrokerExtension.cs | 25 ++++++++++++++---- .../AppConfig/ApplicationConfiguration.cs | 11 ++++++-- .../Internal/Broker/IBroker.cs | 1 + .../Internal/Broker/NullBroker.cs | 6 +++++ .../Internal/Requests/RequestBase.cs | 22 +++++++++++++++- .../OAuth2/OAuth2Client.cs | 2 ++ .../Broker/AndroidAccountManagerBroker.cs | 5 ++++ .../Broker/AndroidContentProviderBroker.cs | 5 ++++ .../Features/RuntimeBroker/RuntimeBroker.cs | 26 +++++++++++++++++++ ...WindowsFormsWebAuthenticationDialogBase.cs | 21 ++++++++++++++- .../Platforms/iOS/Broker/iOSBroker.cs | 5 ++++ .../Platforms/uap/WamBroker/WamBroker.cs | 5 ++++ tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs | 3 ++- 14 files changed, 128 insertions(+), 11 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3892130bfa..67ad066fd9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ true - 0.13.14 + 0.16.0 4.51.0 diff --git a/src/client/Microsoft.Identity.Client.Broker/BrokerExtension.cs b/src/client/Microsoft.Identity.Client.Broker/BrokerExtension.cs index 9d3cd71753..29684c66ee 100644 --- a/src/client/Microsoft.Identity.Client.Broker/BrokerExtension.cs +++ b/src/client/Microsoft.Identity.Client.Broker/BrokerExtension.cs @@ -47,20 +47,35 @@ public static PublicClientApplicationBuilder WithBrokerPreview(this PublicClient /// parameters, and to create a public client application instance public static PublicClientApplicationBuilder WithBroker(this PublicClientApplicationBuilder builder, BrokerOptions brokerOptions) { - AddRuntimeSupportForWam(builder); + AddRuntimeSupport(builder); builder.Config.BrokerOptions = brokerOptions; builder.Config.IsBrokerEnabled = brokerOptions.IsBrokerEnabledOnCurrentOs(); return builder; } - private static void AddRuntimeSupportForWam(PublicClientApplicationBuilder builder) + /// + /// Use this API to enable SsoPolicy enforcement. + /// Should only be utilized by Microsoft 1st party applications. + /// This is applicable only when broker is not enabled and embedded webview is the preferred choice. + /// By default, the broker supports SsoPolicy, and system webview SsoPolicy is also supported at the OS level. + /// + /// A from which to set more + /// parameters, and to create a public client application instance + public static PublicClientApplicationBuilder WithSsoPolicy(this PublicClientApplicationBuilder builder) + { + AddRuntimeSupport(builder); + builder.Config.IsWebviewSsoPolicyEnabled = true; + return builder; + } + + private static void AddRuntimeSupport(PublicClientApplicationBuilder builder) { if (DesktopOsHelper.IsWin10OrServerEquivalent()) { - builder.Config.BrokerCreatorFunc = + builder.Config.BrokerCreatorFunc = (uiParent, appConfig, logger) => { - logger.Info("[RuntimeBroker] WAM supported OS."); + logger.Info("[Runtime] WAM supported OS."); return new RuntimeBroker(uiParent, appConfig, logger); }; } @@ -69,7 +84,7 @@ private static void AddRuntimeSupportForWam(PublicClientApplicationBuilder build builder.Config.BrokerCreatorFunc = (uiParent, appConfig, logger) => { - logger.Info("[RuntimeBroker] Not a Windows 10 or Server equivalent machine. WAM is not available."); + logger.Info("[RuntimeBroker] Not a Windows 10 or Server equivalent machine. Runtime broker or SsoPolicy support is not available."); return new NullBroker(logger); }; } diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs index 78be84368d..f381a2cc45 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs @@ -66,6 +66,11 @@ public string ClientVersion public bool IsBrokerEnabled { get; internal set; } + /// + /// Applicable to only public client applications to enforce SSO policy with embedded webview. + /// + public bool IsWebviewSsoPolicyEnabled { get; internal set; } + // Legacy options for UWP. .NET broker options are in BrokerOptions public WindowsBrokerOptions UwpBrokerOptions { get; set; } @@ -121,6 +126,9 @@ public string ClientVersion public ManagedIdentityId ManagedIdentityId { get; internal set; } public bool IsManagedIdentity { get; } + public bool IsConfidentialClient { get; } + public bool IsPublicClient => !IsConfidentialClient && !IsManagedIdentity; + public Func> AppTokenProvider; @@ -201,8 +209,7 @@ public X509Certificate2 ClientCredentialCertificate public ITokenCacheInternal UserTokenCacheInternalForTest { get; set; } public ITokenCacheInternal AppTokenCacheInternalForTest { get; set; } - public IDeviceAuthManager DeviceAuthManagerForTest { get; set; } - public bool IsConfidentialClient { get; } + public IDeviceAuthManager DeviceAuthManagerForTest { get; set; } public bool IsInstanceDiscoveryEnabled { get; internal set; } = true; #endregion diff --git a/src/client/Microsoft.Identity.Client/Internal/Broker/IBroker.cs b/src/client/Microsoft.Identity.Client/Internal/Broker/IBroker.cs index a288f5f567..ec4b160b9c 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Broker/IBroker.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Broker/IBroker.cs @@ -31,6 +31,7 @@ Task AcquireTokenByUsernamePasswordAsync( AuthenticationRequestParameters authenticationRequestParameters, AcquireTokenByUsernamePasswordParameters acquireTokenByUsernamePasswordParameters); + IReadOnlyDictionary GetSsoPolicyHeaders(); /// /// If device auth is required but the broker is not enabled, AAD will /// signal this by returning an URL pointing to the broker app that needs to be installed. diff --git a/src/client/Microsoft.Identity.Client/Internal/Broker/NullBroker.cs b/src/client/Microsoft.Identity.Client/Internal/Broker/NullBroker.cs index ccee3ad096..95b1b583d2 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Broker/NullBroker.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Broker/NullBroker.cs @@ -12,6 +12,7 @@ using Microsoft.Identity.Client.Utils; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Microsoft.Identity.Client.Internal.Broker @@ -80,5 +81,10 @@ public Task AcquireTokenByUsernamePasswordAsync(Authenticatio _logger.Info("NullBroker - returning null on ROPC request."); return Task.FromResult(null); } + + public IReadOnlyDictionary GetSsoPolicyHeaders() + { + return CollectionHelpers.GetEmptyDictionary(); + } } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index babdd500ed..6ad78722fb 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -21,6 +21,7 @@ using Microsoft.IdentityModel.Abstractions; using Microsoft.Identity.Client.TelemetryCore.TelemetryClient; using Microsoft.Identity.Client.TelemetryCore.OpenTelemetry; +using Microsoft.Identity.Client.Internal.Broker; namespace Microsoft.Identity.Client.Internal.Requests { @@ -422,6 +423,8 @@ protected Task SendTokenRequestAsync( tokenClient.AddHeaderToClient(CcsHeader.Value.Key, CcsHeader.Value.Value); } + InjectPcaSsoPolicyHeader(tokenClient); + return tokenClient.SendTokenRequestAsync( additionalBodyParameters, scopes, @@ -429,6 +432,23 @@ protected Task SendTokenRequestAsync( cancellationToken); } + private void InjectPcaSsoPolicyHeader(TokenClient tokenClient) + { + if (ServiceBundle.Config.IsPublicClient && ServiceBundle.Config.IsWebviewSsoPolicyEnabled) + { + IBroker broker = ServiceBundle.Config.BrokerCreatorFunc( + null, + ServiceBundle.Config, + AuthenticationRequestParameters.RequestContext.Logger); + + var ssoPolicyHeaders = broker.GetSsoPolicyHeaders(); + foreach (KeyValuePair kvp in ssoPolicyHeaders) + { + tokenClient.AddHeaderToClient(kvp.Key, kvp.Value); + } + } + } + //The AAD backup authentication system header is used by the AAD backup authentication system service //to help route requests to resources in Azure during requests to speed up authentication. //It consists of either the ObjectId.TenantId or the upn of the account signing in. @@ -570,6 +590,6 @@ private static RegionDetails CreateRegionDetails(ApiEvent apiEvent) apiEvent.RegionOutcome, apiEvent.RegionUsed, apiEvent.RegionDiscoveryFailureReason); - } + } } } diff --git a/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs b/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs index 950ed5be79..607cd1efd7 100644 --- a/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs +++ b/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs @@ -15,6 +15,8 @@ using Microsoft.Identity.Client.Instance.Oidc; using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.Utils; +using Microsoft.Identity.Client.Internal.Broker; + #if SUPPORTS_SYSTEM_TEXT_JSON using System.Text.Json; #else diff --git a/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidAccountManagerBroker.cs b/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidAccountManagerBroker.cs index 9605ad8411..1dd4c43192 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidAccountManagerBroker.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidAccountManagerBroker.cs @@ -459,5 +459,10 @@ public Task AcquireTokenByUsernamePasswordAsync(Authenticatio { return Task.FromResult(null); // nop } + + public IReadOnlyDictionary GetSsoPolicyHeaders() + { + return CollectionHelpers.GetEmptyDictionary(); + } } } diff --git a/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidContentProviderBroker.cs b/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidContentProviderBroker.cs index e59de3ff0b..7781836ab8 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidContentProviderBroker.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Android/Broker/AndroidContentProviderBroker.cs @@ -400,5 +400,10 @@ public Task AcquireTokenByUsernamePasswordAsync(Authenticatio { return Task.FromResult(null); // nop } + + public IReadOnlyDictionary GetSsoPolicyHeaders() + { + return CollectionHelpers.GetEmptyDictionary(); + } } } diff --git a/src/client/Microsoft.Identity.Client/Platforms/Features/RuntimeBroker/RuntimeBroker.cs b/src/client/Microsoft.Identity.Client/Platforms/Features/RuntimeBroker/RuntimeBroker.cs index 5abf9cdea6..ee9292f83b 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Features/RuntimeBroker/RuntimeBroker.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Features/RuntimeBroker/RuntimeBroker.cs @@ -552,6 +552,32 @@ public async Task> GetAccountsAsync( } } } + public IReadOnlyDictionary GetSsoPolicyHeaders() + { + using LogEventWrapper logEventWrapper = new LogEventWrapper(this); + Debug.Assert(s_lazyCore.Value != null, "Should not call this API if MSAL runtime init failed"); + + NativeInterop.SsoPolicy ssoPolicy = new SsoPolicy(); + _logger.Info(() => $"[RuntimeBroker] Broker returned SsoPolicyType {ssoPolicy.SsoPolicyType} and errorCode {ssoPolicy.ErrorCode}."); + + var ssoPolicyHeaders = new Dictionary(); + if (ssoPolicy.SsoPolicyType == SsoPolicyType.PermissionRequired) + { + ssoPolicyHeaders.Add("x-ms-SsoFlags", "SsoRestr"); + } + else if (ssoPolicy.SsoPolicyType == SsoPolicyType.Error) + { + ssoPolicyHeaders.Add("x-ms-SsoFlags", "SsoPolicyError"); + string subStatusValue = "SsoRestrError:" + ssoPolicy.ErrorCode.ToString(); + ssoPolicyHeaders.Add("x-ms-SsoFlagsSubstatus", Base64UrlHelpers.Encode(subStatusValue)); + } + else if (ssoPolicy.SsoPolicyType == SsoPolicyType.Unknown) + { + ssoPolicyHeaders.Add("x-ms-SsoFlags", "SsoRestrUndefined"); + } + + return ssoPolicyHeaders; + } public void HandleInstallUrl(string appLink) { diff --git a/src/client/Microsoft.Identity.Client/Platforms/Features/WinFormsLegacyWebUi/WindowsFormsWebAuthenticationDialogBase.cs b/src/client/Microsoft.Identity.Client/Platforms/Features/WinFormsLegacyWebUi/WindowsFormsWebAuthenticationDialogBase.cs index c1297f29ec..424611ff31 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Features/WinFormsLegacyWebUi/WindowsFormsWebAuthenticationDialogBase.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Features/WinFormsLegacyWebUi/WindowsFormsWebAuthenticationDialogBase.cs @@ -2,15 +2,20 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Configuration; using System.Diagnostics; using System.Drawing; using System.Globalization; +using System.Net.Http; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using Microsoft.Identity.Client.Core; +using Microsoft.Identity.Client.Http; using Microsoft.Identity.Client.Internal; +using Microsoft.Identity.Client.Internal.Broker; using Microsoft.Identity.Client.Platforms.Features.DesktopOs; using Microsoft.Identity.Client.UI; using Microsoft.Identity.Client.Utils; @@ -276,7 +281,21 @@ internal AuthorizationResult AuthenticateAAD(Uri requestUri, Uri callbackUri, Ca _webBrowser.Navigated += WebBrowserNavigatedHandler; _webBrowser.NavigateError += WebBrowserNavigateErrorHandler; - _webBrowser.Navigate(requestUri); + if(RequestContext.ServiceBundle.Config.IsWebviewSsoPolicyEnabled) + { + IBroker broker = RequestContext.ServiceBundle.Config.BrokerCreatorFunc(null, RequestContext.ServiceBundle.Config, RequestContext.Logger); + var ssoPolicyHeaders = broker.GetSsoPolicyHeaders(); + string ssoPolicyHeadersString=""; + foreach (KeyValuePair kvp in ssoPolicyHeaders) + { + ssoPolicyHeadersString += kvp.Key + ":" + kvp.Value + Environment.NewLine; + } + _webBrowser.Navigate(requestUri, null, null, ssoPolicyHeadersString); + } + else + { + _webBrowser.Navigate(requestUri); + } OnAuthenticate(cancellationToken); return Result; diff --git a/src/client/Microsoft.Identity.Client/Platforms/iOS/Broker/iOSBroker.cs b/src/client/Microsoft.Identity.Client/Platforms/iOS/Broker/iOSBroker.cs index 8f5ceef4a1..2099bcefae 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/iOS/Broker/iOSBroker.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/iOS/Broker/iOSBroker.cs @@ -392,6 +392,11 @@ public static void SetBrokerResponse(NSUrl responseUrl) s_brokerResponseReady?.Release(); } + public IReadOnlyDictionary GetSsoPolicyHeaders() + { + return CollectionHelpers.GetEmptyDictionary(); + } + #region Silent Flow - not supported /// /// iOS broker does not handle silent flow diff --git a/src/client/Microsoft.Identity.Client/Platforms/uap/WamBroker/WamBroker.cs b/src/client/Microsoft.Identity.Client/Platforms/uap/WamBroker/WamBroker.cs index 777d4ba484..9bc5561e3c 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/uap/WamBroker/WamBroker.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/uap/WamBroker/WamBroker.cs @@ -927,5 +927,10 @@ public Task AcquireTokenByUsernamePasswordAsync(Authenticatio { return Task.FromResult(null); // nop } + + public IReadOnlyDictionary GetSsoPolicyHeaders() + { + return CollectionHelpers.GetEmptyDictionary(); + } } } diff --git a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs index 77bc8586f6..8e9d505600 100644 --- a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs +++ b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs @@ -125,7 +125,8 @@ private IPublicClientApplication CreatePca(AuthMethod? authMethod) break; case AuthMethod.SystemBrowser: builder.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.None)); - builder = builder.WithBroker(false); + builder = builder.WithBroker(false) + .WithSsoPolicy(); break; case AuthMethod.EmbeddedBrowser: builder.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.None));