diff --git a/src/client/Microsoft.Identity.Client.Broker/WamAdapters.cs b/src/client/Microsoft.Identity.Client.Broker/WamAdapters.cs index 322aa794ea..cf2eb8b660 100644 --- a/src/client/Microsoft.Identity.Client.Broker/WamAdapters.cs +++ b/src/client/Microsoft.Identity.Client.Broker/WamAdapters.cs @@ -181,6 +181,16 @@ public static NativeInterop.AuthParameters GetCommonAuthParameters( { authParams.Properties["instance_aware"] = "true"; } + + //SSH Cert + if(authenticationRequestParameters.AuthenticationScheme.AccessTokenType == "ssh-cert") + { + authParams.Properties["key_id"]= authenticationRequestParameters.AuthenticationScheme.KeyId; + foreach (KeyValuePair kvp in authenticationRequestParameters.AuthenticationScheme.GetTokenRequestParams()) + { + authParams.Properties[kvp.Key] = kvp.Value; + } + } //pass extra query parameters if there are any if (authenticationRequestParameters.ExtraQueryParameters != null) @@ -352,7 +362,7 @@ private static MsalTokenResponse ParseRuntimeResponse( Scope = authResult.GrantedScopes, ExpiresIn = (long)(DateTime.SpecifyKind(authResult.ExpiresOn, DateTimeKind.Utc) - DateTimeOffset.UtcNow).TotalSeconds, ClientInfo = authResult.Account.ClientInfo, - TokenType = authResult.IsPopAuthorization ? Constants.PoPAuthHeaderPrefix : BrokerResponseConst.Bearer, + TokenType = authResult.IsPopAuthorization ? Constants.PoPAuthHeaderPrefix: authenticationRequestParameters.RequestContext.ApiEvent.TokenType.ToString(), WamAccountId = authResult.Account.AccountId, TokenSource = TokenSource.Broker }; diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs index 801e6409cd..04995d6614 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs @@ -18,7 +18,7 @@ namespace Microsoft.Identity.Client { /// - /// Parameter builder for the + /// Parameter builder for the /// operation. See https://aka.ms/msal-net-up /// public sealed class AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder : @@ -67,5 +67,16 @@ internal override ApiEvent.ApiIds CalculateApiEventId() { return ApiEvent.ApiIds.AcquireTokenByUsernamePassword; } + + /// + protected override void Validate() + { + base.Validate(); + + if (Parameters.SendX5C == null) + { + Parameters.SendX5C = ServiceBundle.Config?.SendX5C ?? false; + } + } } } diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs index 97bfb4a884..6ae2e3858d 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs @@ -142,6 +142,8 @@ public async Task ExecuteAsync( commonParameters, requestContext, _confidentialClientApplication.UserTokenCacheInternal).ConfigureAwait(false); + + requestParams.SendX5C = usernamePasswordParameters.SendX5C ?? false; var handler = new UsernamePasswordRequest( ServiceBundle, diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs index db63a4fb9e..6237f45345 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs @@ -20,10 +20,5 @@ internal abstract class AbstractAcquireTokenConfidentialClientParameters /// This overrides application config settings. /// public bool? SendX5C { get; set; } - - /// - /// if true then Spa code param will be sent via AcquireTokenByAuthorizeCode - /// - public bool SpaCode { get; set; } } } diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByAuthorizationCodeParameters.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByAuthorizationCodeParameters.cs index 31228b55a9..3e41c579c8 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByAuthorizationCodeParameters.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByAuthorizationCodeParameters.cs @@ -11,6 +11,11 @@ internal class AcquireTokenByAuthorizationCodeParameters : AbstractAcquireTokenC public string PkceCodeVerifier { get; set; } + /// + /// if true then Spa code param will be sent via AcquireTokenByAuthorizeCode + /// + public bool SpaCode { get; set; } + public void LogParameters(ILoggerAdapter logger) { } diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByUsernamePasswordParameters.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByUsernamePasswordParameters.cs index bbcaf230a0..17ec6e79f4 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByUsernamePasswordParameters.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByUsernamePasswordParameters.cs @@ -2,14 +2,18 @@ // Licensed under the MIT License. using System.Security; +using System.Text; using Microsoft.Identity.Client.Core; namespace Microsoft.Identity.Client.ApiConfig.Parameters { + internal class AcquireTokenByUsernamePasswordParameters : AbstractAcquireTokenByUsernameParameters, IAcquireTokenParameters { public string Password { get; set; } + public bool? SendX5C { get; set; } // CCA only + /// public void LogParameters(ILoggerAdapter logger) { diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/RuntimeBrokerTests.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/RuntimeBrokerTests.cs index d9b30e2771..7ddb03bc42 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/RuntimeBrokerTests.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/RuntimeBrokerTests.cs @@ -11,6 +11,7 @@ using System.Net; using System.Net.Http; using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client; @@ -18,7 +19,9 @@ using Microsoft.Identity.Client.Broker; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.OAuth2; +using Microsoft.Identity.Client.SSHCertificates; using Microsoft.Identity.Client.UI; +using Microsoft.Identity.Client.Utils; using Microsoft.Identity.Test.Common; using Microsoft.Identity.Test.Common.Core.Helpers; using Microsoft.Identity.Test.Common.Core.Mocks; @@ -35,6 +38,23 @@ public class RuntimeBrokerTests [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); + //This client id is for Azure CLI which is one of the only 2 clients that have PreAuth to use ssh cert feature + string _SSH_ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + //SSH User impersonation scope required for this test + private string[] _SSH_scopes = new[] { "https://pas.windows.net/CheckMyAccess/Linux/user_impersonation" }; + + private string CreateJwk() + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048); + RSAParameters rsaKeyInfo = rsa.ExportParameters(false); + + string modulus = Base64UrlHelpers.Encode(rsaKeyInfo.Modulus); + string exp = Base64UrlHelpers.Encode(rsaKeyInfo.Exponent); + string jwk = $"{{\"kty\":\"RSA\", \"n\":\"{modulus}\", \"e\":\"{exp}\"}}"; + + return jwk; + } + // This test should fail locally but succeed in a CI build. [IgnoreOnOneBranch] [TestMethod] @@ -223,6 +243,45 @@ await AssertException.TaskThrowsAsync( .ConfigureAwait(false); } + [IgnoreOnOneBranch] + [TestMethod] + public async Task WamWithSSHCertificateAuthenticationSchemeAsync() + { + IntPtr intPtr = GetForegroundWindow(); + Func windowHandleProvider = () => intPtr; + var labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false); + + IPublicClientApplication pca = PublicClientApplicationBuilder + .Create(_SSH_ClientId) + .WithTestLogging() + .WithAuthority(labResponse.Lab.Authority, "organizations") + .WithParentActivityOrWindow(windowHandleProvider) + .WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows)) + .Build(); + + string jwk = CreateJwk(); + //Do a login with username password + AuthenticationResult result = await pca + .AcquireTokenByUsernamePassword(_SSH_scopes, labResponse.User.Upn, labResponse.User.GetOrFetchPassword()) + .ExecuteAsync() + .ConfigureAwait(false); + + //Assert successful login + var accounts = await pca.GetAccountsAsync().ConfigureAwait(false); + Assert.IsNotNull(accounts); + var account = accounts.FirstOrDefault(); + Assert.IsNotNull(account); + + //Acquire token with SSH cert + result = await pca + .AcquireTokenSilent(_SSH_scopes, account) + .WithSSHCertificateAuthenticationScheme(jwk, "key1") + .ExecuteAsync() + .ConfigureAwait(false); + + Assert.AreEqual("SshCert", result.TokenType); + } + [IgnoreOnOneBranch] [TestMethod] public async Task WamUsernamePasswordWithForceRefreshAsync() diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs index e9238b5460..d4d1f7f0b8 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs @@ -275,13 +275,22 @@ private async Task RunHappyPathTestAsync(LabResponse labResponse, string federat else { IConfidentialAppSettings settings = ConfidentialAppSettings.GetSettings(cloud); - clientApp = ConfidentialClientApplicationBuilder + var clientAppBuilder = ConfidentialClientApplicationBuilder .Create(settings.ClientId) .WithTestLogging() .WithHttpClientFactory(factory) - .WithAuthority(labResponse.Lab.Authority, "organizations") - .WithClientSecret(settings.GetSecret()) - .Build(); + .WithAuthority(labResponse.Lab.Authority, "organizations"); + + if (cloud == Cloud.Arlington) + { + clientAppBuilder.WithClientSecret(settings.GetSecret()); + } + else + { + clientAppBuilder.WithCertificate(settings.GetCertificate(), true); + } + + clientApp = clientAppBuilder.Build(); } AuthenticationResult authResult diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs index 453a33e361..96f6198245 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs @@ -669,6 +669,41 @@ await app.AcquireTokenByAuthorizationCode(TestConstants.s_scope, TestConstants.D } } + + // regression test for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4913 + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task RopcCcaSendsX5CAsync(bool sendX5C) + { + using (var harness = CreateTestHarness()) + { + var certificate = CertHelper.GetOrCreateTestCert(); + var exportedCertificate = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); + + var app = ConfidentialClientApplicationBuilder + .Create(TestConstants.ClientId) + .WithHttpManager(harness.HttpManager) + .WithCertificate(certificate, sendX5C) + .Build(); + + harness.HttpManager.AddInstanceDiscoveryMockHandler(); + + harness.HttpManager.AddMockHandler( + CreateTokenResponseHttpHandlerWithX5CValidation( + clientCredentialFlow: false, + expectedX5C: sendX5C ? exportedCertificate: null)); + + var result = await (app as IByUsernameAndPassword) + .AcquireTokenByUsernamePassword( + TestConstants.s_scope, + TestConstants.Username, + TestConstants.DefaultPassword) + .ExecuteAsync() + .ConfigureAwait(false); + } + } + private static string ComputeCertThumbprint(X509Certificate2 certificate, bool useSha2) { string thumbprint = null; diff --git a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.Designer.cs b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.Designer.cs index cb9026eb57..770f01c6fa 100644 --- a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.Designer.cs +++ b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.Designer.cs @@ -67,26 +67,27 @@ private void InitializeComponent() cbxMultiCloud2 = new System.Windows.Forms.CheckBox(); cbxWithForceRefresh = new System.Windows.Forms.CheckBox(); btn_ATSDeviceCodeFlow = new System.Windows.Forms.Button(); + atiSshBtn = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)nudAutocancelSeconds).BeginInit(); SuspendLayout(); // // resultTbx // - resultTbx.Location = new System.Drawing.Point(12, 316); - resultTbx.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + resultTbx.Location = new System.Drawing.Point(14, 421); + resultTbx.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); resultTbx.Multiline = true; resultTbx.Name = "resultTbx"; resultTbx.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - resultTbx.Size = new System.Drawing.Size(709, 390); + resultTbx.Size = new System.Drawing.Size(810, 519); resultTbx.TabIndex = 0; // // label1 // label1.AutoSize = true; - label1.Location = new System.Drawing.Point(22, 52); - label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label1.Location = new System.Drawing.Point(25, 69); + label1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label1.Name = "label1"; - label1.Size = new System.Drawing.Size(57, 15); + label1.Size = new System.Drawing.Size(70, 20); label1.TabIndex = 2; label1.Text = "Authority"; // @@ -94,20 +95,20 @@ private void InitializeComponent() // authorityCbx.FormattingEnabled = true; authorityCbx.Items.AddRange(new object[] { "https://login.microsoftonline.com/common", "https://login.microsoftonline.com/organizations", "https://login.microsoftonline.com/consumers", "https://login.microsoftonline.com/49f548d0-12b7-4169-a390-bb5304d24462", "https://login.microsoftonline.com/f645ad92-e38d-4d1a-b510-d1b09a74a8ca", "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a", "https://login.windows-ppe.net/organizations", "https://login.windows-ppe.net/72f988bf-86f1-41af-91ab-2d7cd011db47", "https://login.partner.microsoftonline.cn/organizations", "https://login.microsoftonline.us/organizations" }); - authorityCbx.Location = new System.Drawing.Point(85, 48); - authorityCbx.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + authorityCbx.Location = new System.Drawing.Point(97, 64); + authorityCbx.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); authorityCbx.Name = "authorityCbx"; - authorityCbx.Size = new System.Drawing.Size(568, 23); + authorityCbx.Size = new System.Drawing.Size(649, 28); authorityCbx.TabIndex = 3; authorityCbx.Text = "https://login.microsoftonline.com/common"; // // clientIdCbx // clientIdCbx.FormattingEnabled = true; - clientIdCbx.Location = new System.Drawing.Point(85, 17); - clientIdCbx.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + clientIdCbx.Location = new System.Drawing.Point(97, 23); + clientIdCbx.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); clientIdCbx.Name = "clientIdCbx"; - clientIdCbx.Size = new System.Drawing.Size(568, 23); + clientIdCbx.Size = new System.Drawing.Size(649, 28); clientIdCbx.TabIndex = 4; clientIdCbx.Text = "1d18b3b0-251b-4714-a02a-9956cec86c2d"; clientIdCbx.SelectedIndexChanged += clientIdCbx_SelectedIndexChanged; @@ -115,47 +116,47 @@ private void InitializeComponent() // label2 // label2.AutoSize = true; - label2.Location = new System.Drawing.Point(29, 21); - label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label2.Location = new System.Drawing.Point(33, 28); + label2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label2.Name = "label2"; - label2.Size = new System.Drawing.Size(48, 15); + label2.Size = new System.Drawing.Size(60, 20); label2.TabIndex = 5; label2.Text = "ClientId"; // // label3 // label3.AutoSize = true; - label3.Location = new System.Drawing.Point(10, 113); - label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label3.Location = new System.Drawing.Point(11, 151); + label3.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label3.Name = "label3"; - label3.Size = new System.Drawing.Size(66, 15); + label3.Size = new System.Drawing.Size(82, 20); label3.TabIndex = 7; label3.Text = "Login Hint "; // // loginHintTxt // - loginHintTxt.Location = new System.Drawing.Point(85, 111); - loginHintTxt.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + loginHintTxt.Location = new System.Drawing.Point(97, 148); + loginHintTxt.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); loginHintTxt.Name = "loginHintTxt"; - loginHintTxt.Size = new System.Drawing.Size(256, 23); + loginHintTxt.Size = new System.Drawing.Size(292, 27); loginHintTxt.TabIndex = 8; // // promptCbx // promptCbx.FormattingEnabled = true; promptCbx.Items.AddRange(new object[] { "", "select_account", "force_login", "no_prompt", "consent", "never" }); - promptCbx.Location = new System.Drawing.Point(580, 149); - promptCbx.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + promptCbx.Location = new System.Drawing.Point(663, 199); + promptCbx.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); promptCbx.Name = "promptCbx"; - promptCbx.Size = new System.Drawing.Size(140, 23); + promptCbx.Size = new System.Drawing.Size(159, 28); promptCbx.TabIndex = 10; // // atsBtn // - atsBtn.Location = new System.Drawing.Point(10, 247); - atsBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + atsBtn.Location = new System.Drawing.Point(11, 329); + atsBtn.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); atsBtn.Name = "atsBtn"; - atsBtn.Size = new System.Drawing.Size(126, 27); + atsBtn.Size = new System.Drawing.Size(144, 36); atsBtn.TabIndex = 11; atsBtn.Text = "ATS"; atsBtn.UseVisualStyleBackColor = true; @@ -163,10 +164,10 @@ private void InitializeComponent() // // atiBtn // - atiBtn.Location = new System.Drawing.Point(144, 247); - atiBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + atiBtn.Location = new System.Drawing.Point(165, 329); + atiBtn.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); atiBtn.Name = "atiBtn"; - atiBtn.Size = new System.Drawing.Size(126, 27); + atiBtn.Size = new System.Drawing.Size(144, 36); atiBtn.TabIndex = 12; atiBtn.Text = "ATI"; atiBtn.UseVisualStyleBackColor = true; @@ -174,10 +175,10 @@ private void InitializeComponent() // // atsAtiBtn // - atsAtiBtn.Location = new System.Drawing.Point(278, 247); - atsAtiBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + atsAtiBtn.Location = new System.Drawing.Point(318, 329); + atsAtiBtn.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); atsAtiBtn.Name = "atsAtiBtn"; - atsAtiBtn.Size = new System.Drawing.Size(126, 27); + atsAtiBtn.Size = new System.Drawing.Size(144, 36); atsAtiBtn.TabIndex = 13; atsAtiBtn.Text = "ATS + ATI"; atsAtiBtn.UseVisualStyleBackColor = true; @@ -185,10 +186,10 @@ private void InitializeComponent() // // accBtn // - accBtn.Location = new System.Drawing.Point(228, 280); - accBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + accBtn.Location = new System.Drawing.Point(261, 373); + accBtn.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); accBtn.Name = "accBtn"; - accBtn.Size = new System.Drawing.Size(126, 27); + accBtn.Size = new System.Drawing.Size(144, 36); accBtn.TabIndex = 15; accBtn.Text = "Get Accounts"; accBtn.UseVisualStyleBackColor = true; @@ -196,10 +197,10 @@ private void InitializeComponent() // // clearBtn // - clearBtn.Location = new System.Drawing.Point(642, 754); - clearBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + clearBtn.Location = new System.Drawing.Point(734, 1005); + clearBtn.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); clearBtn.Name = "clearBtn"; - clearBtn.Size = new System.Drawing.Size(79, 27); + clearBtn.Size = new System.Drawing.Size(90, 36); clearBtn.TabIndex = 16; clearBtn.Text = "Clear Log"; clearBtn.UseVisualStyleBackColor = true; @@ -207,10 +208,10 @@ private void InitializeComponent() // // btnClearCache // - btnClearCache.Location = new System.Drawing.Point(508, 754); - btnClearCache.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnClearCache.Location = new System.Drawing.Point(581, 1005); + btnClearCache.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); btnClearCache.Name = "btnClearCache"; - btnClearCache.Size = new System.Drawing.Size(126, 27); + btnClearCache.Size = new System.Drawing.Size(144, 36); btnClearCache.TabIndex = 17; btnClearCache.Text = "Clear MSAL Cache"; btnClearCache.UseVisualStyleBackColor = true; @@ -220,69 +221,69 @@ private void InitializeComponent() // cbxScopes.FormattingEnabled = true; cbxScopes.Items.AddRange(new object[] { "User.Read", "User.Read User.Read.All", "https://management.core.windows.net//.default", "https://graph.microsoft.com/.default", "499b84ac-1321-427f-aa17-267ca6975798/vso.code_full", "api://51eb3dd6-d8b5-46f3-991d-b1d4870de7de/myaccess", "https://management.core.chinacloudapi.cn//.default", "https://management.core.usgovcloudapi.net//.default" }); - cbxScopes.Location = new System.Drawing.Point(85, 80); - cbxScopes.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxScopes.Location = new System.Drawing.Point(97, 107); + cbxScopes.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); cbxScopes.Name = "cbxScopes"; - cbxScopes.Size = new System.Drawing.Size(635, 23); + cbxScopes.Size = new System.Drawing.Size(725, 28); cbxScopes.TabIndex = 18; cbxScopes.Text = "User.Read"; // // label5 // label5.AutoSize = true; - label5.Location = new System.Drawing.Point(28, 83); - label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label5.Location = new System.Drawing.Point(32, 111); + label5.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label5.Name = "label5"; - label5.Size = new System.Drawing.Size(44, 15); + label5.Size = new System.Drawing.Size(56, 20); label5.TabIndex = 19; label5.Text = "Scopes"; // // label4 // label4.AutoSize = true; - label4.Location = new System.Drawing.Point(526, 153); - label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label4.Location = new System.Drawing.Point(601, 204); + label4.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label4.Name = "label4"; - label4.Size = new System.Drawing.Size(47, 15); + label4.Size = new System.Drawing.Size(58, 20); label4.TabIndex = 9; label4.Text = "Prompt"; // // label6 // label6.AutoSize = true; - label6.Location = new System.Drawing.Point(349, 113); - label6.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label6.Location = new System.Drawing.Point(399, 151); + label6.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label6.Name = "label6"; - label6.Size = new System.Drawing.Size(68, 15); + label6.Size = new System.Drawing.Size(83, 20); label6.TabIndex = 21; label6.Text = "Or Account"; // // cbxAccount // cbxAccount.FormattingEnabled = true; - cbxAccount.Location = new System.Drawing.Point(427, 110); - cbxAccount.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxAccount.Location = new System.Drawing.Point(488, 147); + cbxAccount.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); cbxAccount.Name = "cbxAccount"; - cbxAccount.Size = new System.Drawing.Size(293, 23); + cbxAccount.Size = new System.Drawing.Size(334, 28); cbxAccount.TabIndex = 22; // // cbxMsaPt // cbxMsaPt.AutoSize = true; - cbxMsaPt.Location = new System.Drawing.Point(212, 197); - cbxMsaPt.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxMsaPt.Location = new System.Drawing.Point(242, 263); + cbxMsaPt.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); cbxMsaPt.Name = "cbxMsaPt"; - cbxMsaPt.Size = new System.Drawing.Size(122, 19); + cbxMsaPt.Size = new System.Drawing.Size(147, 24); cbxMsaPt.TabIndex = 23; cbxMsaPt.Text = "MSA-Passthrough"; cbxMsaPt.UseVisualStyleBackColor = true; // // btnExpire // - btnExpire.Location = new System.Drawing.Point(374, 755); - btnExpire.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnExpire.Location = new System.Drawing.Point(427, 1007); + btnExpire.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); btnExpire.Name = "btnExpire"; - btnExpire.Size = new System.Drawing.Size(126, 27); + btnExpire.Size = new System.Drawing.Size(144, 36); btnExpire.TabIndex = 24; btnExpire.Text = "Expire ATs"; btnExpire.UseVisualStyleBackColor = true; @@ -290,10 +291,10 @@ private void InitializeComponent() // // btnRemoveAccount // - btnRemoveAccount.Location = new System.Drawing.Point(362, 280); - btnRemoveAccount.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnRemoveAccount.Location = new System.Drawing.Point(414, 373); + btnRemoveAccount.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); btnRemoveAccount.Name = "btnRemoveAccount"; - btnRemoveAccount.Size = new System.Drawing.Size(126, 27); + btnRemoveAccount.Size = new System.Drawing.Size(144, 36); btnRemoveAccount.TabIndex = 25; btnRemoveAccount.Text = "Remove Acc"; btnRemoveAccount.UseVisualStyleBackColor = true; @@ -302,10 +303,10 @@ private void InitializeComponent() // cbxBackgroundThread // cbxBackgroundThread.AutoSize = true; - cbxBackgroundThread.Location = new System.Drawing.Point(349, 197); - cbxBackgroundThread.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxBackgroundThread.Location = new System.Drawing.Point(399, 263); + cbxBackgroundThread.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); cbxBackgroundThread.Name = "cbxBackgroundThread"; - cbxBackgroundThread.Size = new System.Drawing.Size(159, 19); + cbxBackgroundThread.Size = new System.Drawing.Size(197, 24); cbxBackgroundThread.TabIndex = 26; cbxBackgroundThread.Text = "Force background thread"; cbxBackgroundThread.UseVisualStyleBackColor = true; @@ -313,10 +314,10 @@ private void InitializeComponent() // cbxListOsAccounts // cbxListOsAccounts.AutoSize = true; - cbxListOsAccounts.Location = new System.Drawing.Point(212, 222); - cbxListOsAccounts.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxListOsAccounts.Location = new System.Drawing.Point(242, 296); + cbxListOsAccounts.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); cbxListOsAccounts.Name = "cbxListOsAccounts"; - cbxListOsAccounts.Size = new System.Drawing.Size(113, 19); + cbxListOsAccounts.Size = new System.Drawing.Size(138, 24); cbxListOsAccounts.TabIndex = 27; cbxListOsAccounts.Text = "List OS accounts"; cbxListOsAccounts.UseVisualStyleBackColor = true; @@ -324,66 +325,66 @@ private void InitializeComponent() // cbxUseWam // cbxUseWam.FormattingEnabled = true; - cbxUseWam.Location = new System.Drawing.Point(10, 193); - cbxUseWam.Margin = new System.Windows.Forms.Padding(5, 3, 5, 3); + cbxUseWam.Location = new System.Drawing.Point(11, 257); + cbxUseWam.Margin = new System.Windows.Forms.Padding(6, 4, 6, 4); cbxUseWam.Name = "cbxUseWam"; - cbxUseWam.Size = new System.Drawing.Size(188, 23); + cbxUseWam.Size = new System.Drawing.Size(214, 28); cbxUseWam.TabIndex = 28; // // cbxPOP // cbxPOP.AutoSize = true; - cbxPOP.Location = new System.Drawing.Point(349, 222); - cbxPOP.Margin = new System.Windows.Forms.Padding(5, 3, 5, 3); + cbxPOP.Location = new System.Drawing.Point(399, 296); + cbxPOP.Margin = new System.Windows.Forms.Padding(6, 4, 6, 4); cbxPOP.Name = "cbxPOP"; - cbxPOP.Size = new System.Drawing.Size(156, 19); + cbxPOP.Size = new System.Drawing.Size(191, 24); cbxPOP.TabIndex = 29; cbxPOP.Text = "With Proof-of-Possesion"; cbxPOP.UseVisualStyleBackColor = true; // // UsernameTxt // - UsernameTxt.Location = new System.Drawing.Point(85, 148); - UsernameTxt.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + UsernameTxt.Location = new System.Drawing.Point(97, 197); + UsernameTxt.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); UsernameTxt.Name = "UsernameTxt"; - UsernameTxt.Size = new System.Drawing.Size(154, 23); + UsernameTxt.Size = new System.Drawing.Size(175, 27); UsernameTxt.TabIndex = 30; // // label7 // label7.AutoSize = true; - label7.Location = new System.Drawing.Point(10, 152); - label7.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label7.Location = new System.Drawing.Point(11, 203); + label7.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label7.Name = "label7"; - label7.Size = new System.Drawing.Size(60, 15); + label7.Size = new System.Drawing.Size(75, 20); label7.TabIndex = 31; label7.Text = "Username"; // // label8 // label8.AutoSize = true; - label8.Location = new System.Drawing.Point(247, 151); - label8.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label8.Location = new System.Drawing.Point(282, 201); + label8.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); label8.Name = "label8"; - label8.Size = new System.Drawing.Size(57, 15); + label8.Size = new System.Drawing.Size(70, 20); label8.TabIndex = 32; label8.Text = "Password"; // // PasswordTxt // - PasswordTxt.Location = new System.Drawing.Point(321, 149); - PasswordTxt.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + PasswordTxt.Location = new System.Drawing.Point(367, 199); + PasswordTxt.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); PasswordTxt.Name = "PasswordTxt"; - PasswordTxt.Size = new System.Drawing.Size(154, 23); + PasswordTxt.Size = new System.Drawing.Size(175, 27); PasswordTxt.TabIndex = 33; PasswordTxt.UseSystemPasswordChar = true; // // atUsernamePwdBtn // - atUsernamePwdBtn.Location = new System.Drawing.Point(144, 280); - atUsernamePwdBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + atUsernamePwdBtn.Location = new System.Drawing.Point(165, 373); + atUsernamePwdBtn.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); atUsernamePwdBtn.Name = "atUsernamePwdBtn"; - atUsernamePwdBtn.Size = new System.Drawing.Size(76, 27); + atUsernamePwdBtn.Size = new System.Drawing.Size(87, 36); atUsernamePwdBtn.TabIndex = 34; atUsernamePwdBtn.Text = "AT U/P"; atUsernamePwdBtn.UseVisualStyleBackColor = true; @@ -391,10 +392,10 @@ private void InitializeComponent() // // btnATSperf // - btnATSperf.Location = new System.Drawing.Point(10, 280); - btnATSperf.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnATSperf.Location = new System.Drawing.Point(11, 373); + btnATSperf.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4); btnATSperf.Name = "btnATSperf"; - btnATSperf.Size = new System.Drawing.Size(126, 27); + btnATSperf.Size = new System.Drawing.Size(144, 36); btnATSperf.TabIndex = 30; btnATSperf.Text = "ATS Perf"; btnATSperf.UseVisualStyleBackColor = true; @@ -402,27 +403,29 @@ private void InitializeComponent() // // nudAutocancelSeconds // - nudAutocancelSeconds.Location = new System.Drawing.Point(146, 219); + nudAutocancelSeconds.Location = new System.Drawing.Point(167, 292); + nudAutocancelSeconds.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); nudAutocancelSeconds.Maximum = new decimal(new int[] { 120, 0, 0, 0 }); nudAutocancelSeconds.Name = "nudAutocancelSeconds"; - nudAutocancelSeconds.Size = new System.Drawing.Size(58, 23); + nudAutocancelSeconds.Size = new System.Drawing.Size(66, 27); nudAutocancelSeconds.TabIndex = 30; // // label9 // label9.AutoSize = true; - label9.Location = new System.Drawing.Point(10, 223); + label9.Location = new System.Drawing.Point(11, 297); label9.Name = "label9"; - label9.Size = new System.Drawing.Size(114, 15); + label9.Size = new System.Drawing.Size(142, 20); label9.TabIndex = 31; label9.Text = "Autocancel Seconds"; // // cbxMultiCloud2 // cbxMultiCloud2.AutoSize = true; - cbxMultiCloud2.Location = new System.Drawing.Point(516, 199); + cbxMultiCloud2.Location = new System.Drawing.Point(590, 265); + cbxMultiCloud2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); cbxMultiCloud2.Name = "cbxMultiCloud2"; - cbxMultiCloud2.Size = new System.Drawing.Size(134, 19); + cbxMultiCloud2.Size = new System.Drawing.Size(165, 24); cbxMultiCloud2.TabIndex = 35; cbxMultiCloud2.Text = "Multi Cloud Support"; cbxMultiCloud2.UseVisualStyleBackColor = true; @@ -430,28 +433,41 @@ private void InitializeComponent() // cbxWithForceRefresh // cbxWithForceRefresh.AutoSize = true; - cbxWithForceRefresh.Location = new System.Drawing.Point(516, 224); + cbxWithForceRefresh.Location = new System.Drawing.Point(590, 299); + cbxWithForceRefresh.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); cbxWithForceRefresh.Name = "cbxWithForceRefresh"; - cbxWithForceRefresh.Size = new System.Drawing.Size(119, 19); + cbxWithForceRefresh.Size = new System.Drawing.Size(147, 24); cbxWithForceRefresh.TabIndex = 36; cbxWithForceRefresh.Text = "WithForceRefresh"; cbxWithForceRefresh.UseVisualStyleBackColor = true; // // btn_ATSDeviceCodeFlow // - btn_ATSDeviceCodeFlow.Location = new System.Drawing.Point(495, 280); + btn_ATSDeviceCodeFlow.Location = new System.Drawing.Point(566, 373); + btn_ATSDeviceCodeFlow.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); btn_ATSDeviceCodeFlow.Name = "btn_ATSDeviceCodeFlow"; - btn_ATSDeviceCodeFlow.Size = new System.Drawing.Size(140, 27); + btn_ATSDeviceCodeFlow.Size = new System.Drawing.Size(160, 36); btn_ATSDeviceCodeFlow.TabIndex = 37; btn_ATSDeviceCodeFlow.Text = "AT DeviceCodeFlow"; btn_ATSDeviceCodeFlow.UseVisualStyleBackColor = true; btn_ATSDeviceCodeFlow.Click += btn_ATSDeviceCodeFlow_Click; // + // atiSshBtn + // + atiSshBtn.Location = new System.Drawing.Point(470, 331); + atiSshBtn.Name = "atiSshBtn"; + atiSshBtn.Size = new System.Drawing.Size(196, 33); + atiSshBtn.TabIndex = 38; + atiSshBtn.Text = "ATI w/ SSHAuth"; + atiSshBtn.UseVisualStyleBackColor = true; + atiSshBtn.Click += atiSshBtn_Click; + // // Form1 // - AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - ClientSize = new System.Drawing.Size(738, 794); + ClientSize = new System.Drawing.Size(843, 1059); + Controls.Add(atiSshBtn); Controls.Add(btn_ATSDeviceCodeFlow); Controls.Add(cbxWithForceRefresh); Controls.Add(cbxMultiCloud2); @@ -489,7 +505,7 @@ private void InitializeComponent() Controls.Add(authorityCbx); Controls.Add(label1); Controls.Add(resultTbx); - Margin = new System.Windows.Forms.Padding(2); + Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); Name = "Form1"; Text = "Form1"; ((System.ComponentModel.ISupportInitialize)nudAutocancelSeconds).EndInit(); @@ -538,6 +554,7 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox cbxMultiCloud2; private System.Windows.Forms.CheckBox cbxWithForceRefresh; private System.Windows.Forms.Button btn_ATSDeviceCodeFlow; + private System.Windows.Forms.Button atiSshBtn; } } diff --git a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs index 800112cc30..c42255461c 100644 --- a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs +++ b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.cs @@ -9,11 +9,16 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Identity.Client; +using Microsoft.Identity.Client.AuthScheme.PoP; using Microsoft.Identity.Client.Desktop; +using Microsoft.Identity.Client.Extensibility; +using Microsoft.Identity.Client.SSHCertificates; +using Microsoft.Identity.Client.Utils; namespace NetDesktopWinForms { @@ -302,7 +307,8 @@ private async Task LogResultAndRefreshAccountsAsync(AuthenticationResult ar, boo $"Source {ar.AuthenticationResultMetadata.TokenSource}" + Environment.NewLine + $"Scopes {string.Join(" ", ar.Scopes)}" + Environment.NewLine + $"AccessToken: {ar.AccessToken} " + Environment.NewLine + - $"IdToken {ar.IdToken}" + Environment.NewLine; + $"IdToken {ar.IdToken}" + Environment.NewLine + + $"TokenType {ar.TokenType}" + Environment.NewLine; Log(message); @@ -796,6 +802,105 @@ private async void btn_ATSDeviceCodeFlow_Click(object sender, EventArgs e) Log("Exception: " + ex); } } + + private string CreateJwk() + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048); + RSAParameters rsaKeyInfo = rsa.ExportParameters(false); + + string modulus = Base64UrlHelpers.Encode(rsaKeyInfo.Modulus); + string exp = Base64UrlHelpers.Encode(rsaKeyInfo.Exponent); + string jwk = $"{{\"kty\":\"RSA\", \"n\":\"{modulus}\", \"e\":\"{exp}\"}}"; + + return jwk; + } + private async void atiSshBtn_Click(object sender, EventArgs e) + { + try + { + var pca = CreatePca(GetAuthMethod()); + AuthenticationResult result = await RunAtiSshBtnAsync(pca).ConfigureAwait(false); + + await LogResultAndRefreshAccountsAsync(result).ConfigureAwait(false); + + } + catch (Exception ex) + { + Log("Exception: " + ex); + } + } + + private async Task RunAtiSshBtnAsync(IPublicClientApplication pca) + { + string loginHint = GetLoginHint(); + if (!string.IsNullOrEmpty(loginHint) && cbxAccount.SelectedIndex > 0) + { + throw new InvalidOperationException("[TEST APP FAILURE] Please use either the login hint or the account, but not both"); + } + + AuthenticationResult result = null; + var scopes = GetScopes(); + var guid = Guid.NewGuid(); + string jwk = CreateJwk(); + var builder = pca.AcquireTokenInteractive(scopes) + .WithParentActivityOrWindow(this.Handle) + .WithSSHCertificateAuthenticationScheme(jwk, "key1"); + + if (GetAuthMethod() == AuthMethod.SystemBrowser) + { + builder.WithSystemWebViewOptions(new SystemWebViewOptions() { HtmlMessageSuccess = "Successful login! You can close the tab." }); + } + else + { + builder.WithUseEmbeddedWebView(true) + //.WithExtraQueryParameters("domain_hint=live.com") -- will force AAD login with browser + //.WithExtraQueryParameters("msafed=0") -- will force MSA login with browser + .WithEmbeddedWebViewOptions( + new EmbeddedWebViewOptions() + { + Title = "Hello world", + }); + } + + if (cbxPOP.Checked) + { + builder = builder.WithProofOfPossession( + Guid.NewGuid().ToString(), + System.Net.Http.HttpMethod.Get, + GetRandomDownstreamUri()); + } + + Prompt? prompt = GetPrompt(); + if (prompt.HasValue) + { + builder = builder.WithPrompt(prompt.Value); + } + + if (!string.IsNullOrEmpty(loginHint)) + { + Log($"ATI WithLoginHint {loginHint}"); + builder = builder.WithLoginHint(loginHint); + } + else if (cbxAccount.SelectedIndex > 0) + { + var acc = (cbxAccount.SelectedItem as AccountModel).Account; + Log($"ATI WithAccount for account {acc?.Username ?? "null"}"); + builder = builder.WithAccount(acc); + } + else + { + Log($"ATI without login_hint or account. It should display the account picker"); + } + + if (cbxBackgroundThread.Checked) + { + await Task.Delay(500).ConfigureAwait(false); + } + + result = await builder.ExecuteAsync(GetAutocancelToken()).ConfigureAwait(false); + + return result; + } } public class ClientEntry diff --git a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.resx b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.resx index 2704a16b27..2d9484bb8b 100644 --- a/tests/devapps/WAM/NetCoreWinFormsWam/Form1.resx +++ b/tests/devapps/WAM/NetCoreWinFormsWam/Form1.resx @@ -1,7 +1,7 @@