diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 90e1c3c3adc0..efdc30c5622a 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -109,7 +109,7 @@ - + diff --git a/sdk/identity/Azure.Identity.Broker/CHANGELOG.md b/sdk/identity/Azure.Identity.Broker/CHANGELOG.md index 2414c7681529..1ef9c7991d4c 100644 --- a/sdk/identity/Azure.Identity.Broker/CHANGELOG.md +++ b/sdk/identity/Azure.Identity.Broker/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features Added +- `InteractiveBrowserCredentialBrokerOptions` and `SharedTokenCacheCredentialBrokerOptions` now support a `UseOperatingSystemAccount` property to enable the use of the currently logged in operating system account for authentication rather than prompting for a credential. - Preview support for Proof of Possession (PoP) tokens for `InteractiveBrowserCredential`. This feature is enabled via the `IsProofOfPossessionRequired` property on `InteractiveBrowserCredentialBrokerOptions`. ### Breaking Changes diff --git a/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net462.cs b/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net462.cs index ac2aa92cb7ad..cefab1368372 100644 --- a/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net462.cs +++ b/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net462.cs @@ -5,6 +5,7 @@ public partial class InteractiveBrowserCredentialBrokerOptions : Azure.Identity. public InteractiveBrowserCredentialBrokerOptions(System.IntPtr parentWindowHandle) { } public bool? IsLegacyMsaPassthroughEnabled { get { throw null; } set { } } public bool IsProofOfPossessionRequired { get { throw null; } set { } } + public bool UseOperatingSystemAccount { get { throw null; } set { } } } public partial class SharedTokenCacheCredentialBrokerOptions : Azure.Identity.SharedTokenCacheCredentialOptions { @@ -12,5 +13,6 @@ public SharedTokenCacheCredentialBrokerOptions() { } public SharedTokenCacheCredentialBrokerOptions(Azure.Identity.TokenCachePersistenceOptions tokenCacheOptions) { } public bool? IsLegacyMsaPassthroughEnabled { get { throw null; } set { } } public bool IsProofOfPossessionRequired { get { throw null; } set { } } + public bool UseOperatingSystemAccount { get { throw null; } set { } } } } diff --git a/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net6.0.cs b/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net6.0.cs index ac2aa92cb7ad..cefab1368372 100644 --- a/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net6.0.cs +++ b/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.net6.0.cs @@ -5,6 +5,7 @@ public partial class InteractiveBrowserCredentialBrokerOptions : Azure.Identity. public InteractiveBrowserCredentialBrokerOptions(System.IntPtr parentWindowHandle) { } public bool? IsLegacyMsaPassthroughEnabled { get { throw null; } set { } } public bool IsProofOfPossessionRequired { get { throw null; } set { } } + public bool UseOperatingSystemAccount { get { throw null; } set { } } } public partial class SharedTokenCacheCredentialBrokerOptions : Azure.Identity.SharedTokenCacheCredentialOptions { @@ -12,5 +13,6 @@ public SharedTokenCacheCredentialBrokerOptions() { } public SharedTokenCacheCredentialBrokerOptions(Azure.Identity.TokenCachePersistenceOptions tokenCacheOptions) { } public bool? IsLegacyMsaPassthroughEnabled { get { throw null; } set { } } public bool IsProofOfPossessionRequired { get { throw null; } set { } } + public bool UseOperatingSystemAccount { get { throw null; } set { } } } } diff --git a/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.netstandard2.0.cs b/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.netstandard2.0.cs index ac2aa92cb7ad..cefab1368372 100644 --- a/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.netstandard2.0.cs +++ b/sdk/identity/Azure.Identity.Broker/api/Azure.Identity.Broker.netstandard2.0.cs @@ -5,6 +5,7 @@ public partial class InteractiveBrowserCredentialBrokerOptions : Azure.Identity. public InteractiveBrowserCredentialBrokerOptions(System.IntPtr parentWindowHandle) { } public bool? IsLegacyMsaPassthroughEnabled { get { throw null; } set { } } public bool IsProofOfPossessionRequired { get { throw null; } set { } } + public bool UseOperatingSystemAccount { get { throw null; } set { } } } public partial class SharedTokenCacheCredentialBrokerOptions : Azure.Identity.SharedTokenCacheCredentialOptions { @@ -12,5 +13,6 @@ public SharedTokenCacheCredentialBrokerOptions() { } public SharedTokenCacheCredentialBrokerOptions(Azure.Identity.TokenCachePersistenceOptions tokenCacheOptions) { } public bool? IsLegacyMsaPassthroughEnabled { get { throw null; } set { } } public bool IsProofOfPossessionRequired { get { throw null; } set { } } + public bool UseOperatingSystemAccount { get { throw null; } set { } } } } diff --git a/sdk/identity/Azure.Identity.Broker/src/InteractiveBrowserCredentialBrokerOptions.cs b/sdk/identity/Azure.Identity.Broker/src/InteractiveBrowserCredentialBrokerOptions.cs index 5b1bf196de80..b81a57080c64 100644 --- a/sdk/identity/Azure.Identity.Broker/src/InteractiveBrowserCredentialBrokerOptions.cs +++ b/sdk/identity/Azure.Identity.Broker/src/InteractiveBrowserCredentialBrokerOptions.cs @@ -25,6 +25,11 @@ public class InteractiveBrowserCredentialBrokerOptions : InteractiveBrowserCrede /// public bool IsProofOfPossessionRequired { get; set; } + /// + /// Gets or sets whether to authenticate with the currently signed in user instead of prompting the user with a login dialog. + /// + public bool UseOperatingSystemAccount { get; set; } + /// /// Creates a new instance of to configure a . /// diff --git a/sdk/identity/Azure.Identity.Broker/src/SharedTokenCacheCredentialBrokerOptions.cs b/sdk/identity/Azure.Identity.Broker/src/SharedTokenCacheCredentialBrokerOptions.cs index aac0a57c9e7c..209615c17b5e 100644 --- a/sdk/identity/Azure.Identity.Broker/src/SharedTokenCacheCredentialBrokerOptions.cs +++ b/sdk/identity/Azure.Identity.Broker/src/SharedTokenCacheCredentialBrokerOptions.cs @@ -23,6 +23,11 @@ public class SharedTokenCacheCredentialBrokerOptions : SharedTokenCacheCredentia /// public bool IsProofOfPossessionRequired { get; set; } + /// + /// Gets or sets whether to authenticate with the currently signed in user instead of prompting the user with a login dialog. + /// + public bool UseOperatingSystemAccount { get; set; } + /// /// Initializes a new instance of . /// diff --git a/sdk/identity/Azure.Identity.Broker/tests/InteractiveBrowserCredentialBrokerOptionsTests.cs b/sdk/identity/Azure.Identity.Broker/tests/InteractiveBrowserCredentialBrokerOptionsTests.cs index 8142925a3283..dff847008926 100644 --- a/sdk/identity/Azure.Identity.Broker/tests/InteractiveBrowserCredentialBrokerOptionsTests.cs +++ b/sdk/identity/Azure.Identity.Broker/tests/InteractiveBrowserCredentialBrokerOptionsTests.cs @@ -18,11 +18,11 @@ public void RespectsMsaPassthrough( IMsalPublicClientInitializerOptions credentialOptions; if (enableMsaPassthrough.HasValue) { - credentialOptions = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle) { IsLegacyMsaPassthroughEnabled = enableMsaPassthrough.Value } as IMsalPublicClientInitializerOptions; + credentialOptions = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle) { IsLegacyMsaPassthroughEnabled = enableMsaPassthrough.Value }; } else { - credentialOptions = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle) as IMsalPublicClientInitializerOptions; + credentialOptions = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle); } PublicClientApplicationBuilder builder = PublicClientApplicationBuilder .Create(Guid.NewGuid().ToString()); @@ -34,6 +34,20 @@ public void RespectsMsaPassthrough( Assert.AreEqual(parentWindowHandle, Parent()); } + [Test] + public void RespectsUseOperatingSystemAccount( + [Values(true, false)] bool enableUseOperatingSystemAccount) + { + IntPtr parentWindowHandle = new(1234); + IMsalPublicClientInitializerOptions credentialOptions; + credentialOptions = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle) { UseOperatingSystemAccount = enableUseOperatingSystemAccount }; + PublicClientApplicationBuilder builder = PublicClientApplicationBuilder + .Create(Guid.NewGuid().ToString()); + + var credential = new InteractiveBrowserCredential((InteractiveBrowserCredentialBrokerOptions)credentialOptions); + Assert.AreEqual(enableUseOperatingSystemAccount, credential.UseOperatingSystemAccount); + } + private static (BrokerOptions Options, Func Parent) GetBrokerOptions(PublicClientApplicationBuilder builder) { var config = builder diff --git a/sdk/identity/Azure.Identity.Broker/tests/ManualInteractiveBrowserCredentialBrokerTests.cs b/sdk/identity/Azure.Identity.Broker/tests/ManualInteractiveBrowserCredentialBrokerTests.cs index b79c83bcd439..426a61e7c9f3 100644 --- a/sdk/identity/Azure.Identity.Broker/tests/ManualInteractiveBrowserCredentialBrokerTests.cs +++ b/sdk/identity/Azure.Identity.Broker/tests/ManualInteractiveBrowserCredentialBrokerTests.cs @@ -34,6 +34,19 @@ public async Task AuthenticateWithBrokerAsync() Assert.NotNull(token.Token); } + [Test] + [Ignore("This test is an integration test which can only be run with user interaction")] + public async Task AuthenticateWithBrokerWithUseOperatingSystemAccount_DoesNotPrompt() + { + IntPtr parentWindowHandle = GetForegroundWindow(); + + var cred = new InteractiveBrowserCredential(new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle) { UseOperatingSystemAccount = true }); + + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })).ConfigureAwait(false); + + Assert.NotNull(token.Token); + } + [Test] [TestCase(true)] [TestCase(false)] diff --git a/sdk/identity/Azure.Identity.Broker/tests/ManualSharedTokenCacheCredentialBrokerTests.cs b/sdk/identity/Azure.Identity.Broker/tests/ManualSharedTokenCacheCredentialBrokerTests.cs index a438804b7e98..2e491172be0f 100644 --- a/sdk/identity/Azure.Identity.Broker/tests/ManualSharedTokenCacheCredentialBrokerTests.cs +++ b/sdk/identity/Azure.Identity.Broker/tests/ManualSharedTokenCacheCredentialBrokerTests.cs @@ -42,5 +42,16 @@ public async Task SilentAuthenticateWithBrokerAsync() Assert.NotNull(token.Token); } + + [Test] + [Ignore("This test is an integration test which can only be run with user interaction")] + public async Task AuthenticateWithBrokerWithUseOperatingSystemAccount_DoesNotPrompt() + { + var cred = new SharedTokenCacheCredential(new SharedTokenCacheCredentialBrokerOptions() { UseOperatingSystemAccount = true }); + + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })).ConfigureAwait(false); + + Assert.NotNull(token.Token); + } } } diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj index af0abe2bdc35..8c412838f941 100644 --- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj +++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj @@ -17,7 +17,7 @@ PREVIEW_FEATURE_FLAG - + diff --git a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs index 8e5e935cedef..3f97ab756fb5 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs @@ -31,6 +31,7 @@ public class InteractiveBrowserCredential : TokenCredential internal AuthenticationRecord Record { get; private set; } internal string DefaultScope { get; } internal TenantIdResolverBase TenantIdResolver { get; } + internal bool UseOperatingSystemAccount { get; } private const string AuthenticationRequiredMessage = "Interactive authentication is needed to acquire token. Call Authenticate to interactively authenticate."; private const string NoDefaultScopeMessage = "Authenticating in this environment requires specifying a TokenRequestContext."; @@ -95,6 +96,7 @@ internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCre AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds((options as ISupportsAdditionallyAllowedTenants)?.AdditionallyAllowedTenants); Record = (options as InteractiveBrowserCredentialOptions)?.AuthenticationRecord; BrowserCustomization = (options as InteractiveBrowserCredentialOptions)?.BrowserCustomization; + UseOperatingSystemAccount = (options as IMsalPublicClientInitializerOptions)?.UseOperatingSystemAccount ?? false; } /// @@ -228,13 +230,23 @@ private async ValueTask GetTokenImplAsync(bool async, PopTokenReque Exception inner = null; var tenantId = TenantIdResolver.Resolve(TenantId ?? Record?.TenantId, requestContext, AdditionallyAllowedTenantIds); - if (Record is not null) + if (Record is not null || UseOperatingSystemAccount) { try { - AuthenticationResult result = await Client - .AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, requestContext.IsCaeEnabled, async, cancellationToken) + AuthenticationResult result; + if (Record is null) + { + result = await Client + .AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, PublicClientApplication.OperatingSystemAccount, tenantId, requestContext.IsCaeEnabled, async, cancellationToken) .ConfigureAwait(false); + } + else + { + result = await Client + .AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, requestContext.IsCaeEnabled, async, cancellationToken) + .ConfigureAwait(false); + } return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } diff --git a/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs index 10708790ab1e..7d83bf30bb10 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs @@ -33,6 +33,7 @@ public class SharedTokenCacheCredential : TokenCredential internal string Username { get; } internal MsalPublicClient Client { get; } internal TenantIdResolverBase TenantIdResolver { get; } + internal bool UseOperatingSystemAccount { get; } /// /// Creates a new which will authenticate users signed in through developer tools supporting Azure single sign on. @@ -83,6 +84,7 @@ internal SharedTokenCacheCredential(string tenantId, string username, TokenCrede options ?? s_DefaultCacheOptions); _accountAsyncLock = new AsyncLockWithValue(); TenantIdResolver = options?.TenantIdResolver ?? TenantIdResolverBase.Default; + UseOperatingSystemAccount = (options as IMsalPublicClientInitializerOptions)?.UseOperatingSystemAccount ?? false; } /// @@ -118,8 +120,20 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC try { var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, TenantIdResolverBase.AllTenants); - IAccount account = await GetAccountAsync(tenantId, requestContext.IsCaeEnabled, async, cancellationToken).ConfigureAwait(false); - AuthenticationResult result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, account, tenantId, requestContext.IsCaeEnabled, async, cancellationToken).ConfigureAwait(false); + + IAccount account = UseOperatingSystemAccount ? + PublicClientApplication.OperatingSystemAccount : + await GetAccountAsync(tenantId, requestContext.IsCaeEnabled, async, cancellationToken).ConfigureAwait(false); + + AuthenticationResult result = await Client.AcquireTokenSilentAsync( + requestContext.Scopes, + requestContext.Claims, + account, + tenantId, + requestContext.IsCaeEnabled, + async, + cancellationToken).ConfigureAwait(false); + return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn)); } catch (MsalUiRequiredException ex) diff --git a/sdk/identity/Azure.Identity/src/IMsalPublicClientInitializerOptions.cs b/sdk/identity/Azure.Identity/src/IMsalPublicClientInitializerOptions.cs index f70589398ff5..fd1ea45b437e 100644 --- a/sdk/identity/Azure.Identity/src/IMsalPublicClientInitializerOptions.cs +++ b/sdk/identity/Azure.Identity/src/IMsalPublicClientInitializerOptions.cs @@ -11,5 +11,7 @@ internal interface IMsalPublicClientInitializerOptions { Action BeforeBuildClient { get; } bool IsProofOfPossessionRequired { get; set; } + + bool UseOperatingSystemAccount { get; set; } } } diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs index 65f3d49ff7ee..7546c790e2ca 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs @@ -269,6 +269,8 @@ public ExtendedInteractiveBrowserCredentialOptions(Action IMsalPublicClientInitializerOptions.BeforeBuildClient { get { return _beforeBuildClient; } } }