Skip to content

Commit d9ce14a

Browse files
committed
Address PR feedback: Keep core ROPC tests, remove deprecated tests
1 parent 4691741 commit d9ce14a

File tree

1 file changed

+382
-0
lines changed

1 file changed

+382
-0
lines changed
Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
#if !ANDROID && !iOS // U/P not available on Android and iOS
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Net;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.Identity.Client;
12+
using Microsoft.Identity.Client.Internal;
13+
using Microsoft.Identity.Client.TelemetryCore;
14+
using Microsoft.Identity.Test.Common;
15+
using Microsoft.Identity.Test.Common.Core.Helpers;
16+
using Microsoft.Identity.Test.Integration.Infrastructure;
17+
using Microsoft.Identity.Test.Integration.NetFx.Infrastructure;
18+
using Microsoft.Identity.Test.LabInfrastructure;
19+
using Microsoft.Identity.Test.Unit;
20+
using Microsoft.VisualStudio.TestTools.UnitTesting;
21+
22+
namespace Microsoft.Identity.Test.Integration.HeadlessTests
23+
{
24+
[TestClass]
25+
public class UsernamePasswordIntegrationTests
26+
{
27+
private const string Authority = "https://login.microsoftonline.com/organizations/";
28+
private static readonly string[] s_scopes = { "User.Read" };
29+
public string CurrentApiId { get; set; }
30+
31+
// HTTP Telemetry Constants
32+
private static Guid CorrelationId = new Guid("ad8c894a-557f-48c0-b045-c129590c344e");
33+
private readonly string XClientCurrentTelemetryROPC = $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1003,{CacheRefreshReason.NotApplicable:D},,,|0,1,1,,";
34+
private readonly string XClientCurrentTelemetryROPCFailure = $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1003,{CacheRefreshReason.NotApplicable:D},,,|0,1,1,,";
35+
private const string ApiIdAndCorrelationIdSection =
36+
"1003,ad8c894a-557f-48c0-b045-c129590c344e";
37+
private const string InvalidGrantError = "invalid_grant";
38+
private const string UPApiId = "1003";
39+
private const string B2CROPCAuthority = "https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth";
40+
private static readonly string[] s_b2cScopes = { "https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read" };
41+
42+
[TestInitialize]
43+
public void TestInitialize()
44+
{
45+
TestCommon.ResetInternalStaticCaches();
46+
}
47+
48+
#region Happy Path Tests
49+
[TestMethod]
50+
public async Task ROPC_AAD_Async()
51+
{
52+
var labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false);
53+
await RunHappyPathTestAsync(labResponse).ConfigureAwait(false);
54+
}
55+
56+
[TestMethod]
57+
public async Task ROPC_AAD_CCA_Async()
58+
{
59+
var labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false);
60+
await RunHappyPathTestAsync(labResponse: labResponse, isPublicClient: false).ConfigureAwait(false);
61+
}
62+
63+
[RunOn(TargetFrameworks.NetCore)]
64+
[TestCategory(TestCategories.Arlington)]
65+
public async Task ARLINGTON_ROPC_AAD_CCA_Async()
66+
{
67+
var labResponse = await LabUserHelper.GetArlingtonUserAsync().ConfigureAwait(false);
68+
await RunHappyPathTestAsync(labResponse, isPublicClient: false, cloud:Cloud.Arlington).ConfigureAwait(false);
69+
}
70+
71+
[RunOn(TargetFrameworks.NetCore)]
72+
#if IGNORE_FEDERATED
73+
[Ignore]
74+
#endif
75+
public async Task ROPC_ADFSv4Federated_Async()
76+
{
77+
var labResponse = await LabUserHelper.GetAdfsUserAsync(FederationProvider.AdfsV4, true).ConfigureAwait(false);
78+
await RunHappyPathTestAsync(labResponse).ConfigureAwait(false);
79+
}
80+
81+
[RunOn(TargetFrameworks.NetCore)]
82+
[TestCategory(TestCategories.ADFS)]
83+
#if IGNORE_FEDERATED
84+
[Ignore]
85+
#endif
86+
public async Task AcquireTokenFromAdfsUsernamePasswordAsync()
87+
{
88+
LabResponse labResponse = await LabUserHelper.GetAdfsUserAsync(FederationProvider.ADFSv2019, true).ConfigureAwait(false);
89+
90+
var user = labResponse.User;
91+
Uri authorityUri = new Uri(Adfs2019LabConstants.Authority);
92+
93+
var msalPublicClient = PublicClientApplicationBuilder
94+
.Create(Adfs2019LabConstants.PublicClientId)
95+
.WithAuthority(authorityUri)
96+
.WithTestLogging()
97+
.Build();
98+
99+
#pragma warning disable CS0618 // Type or member is obsolete
100+
AuthenticationResult authResult = await msalPublicClient
101+
.AcquireTokenByUsernamePassword(s_scopes, user.Upn, user.GetOrFetchPassword())
102+
.ExecuteAsync()
103+
.ConfigureAwait(false);
104+
#pragma warning restore CS0618
105+
106+
Assert.IsNotNull(authResult);
107+
Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);
108+
Assert.IsNotNull(authResult.AccessToken);
109+
Assert.IsNotNull(authResult.IdToken);
110+
}
111+
112+
#endregion
113+
114+
[RunOn(TargetFrameworks.NetCore)]
115+
public async Task ROPC_B2C_Async()
116+
{
117+
var labResponse = await LabUserHelper.GetB2CLocalAccountAsync().ConfigureAwait(false);
118+
await RunB2CHappyPathTestAsync(labResponse).ConfigureAwait(false);
119+
}
120+
121+
private async Task CheckTelemetryHeadersAsync(
122+
LabResponse labResponse)
123+
{
124+
var factory = new HttpSnifferClientFactory();
125+
126+
var msalPublicClient = PublicClientApplicationBuilder
127+
.Create(labResponse.App.AppId)
128+
.WithAuthority(Authority)
129+
.WithHttpClientFactory(factory)
130+
.Build();
131+
132+
await RunAcquireTokenWithUsernameIncorrectPasswordAsync(msalPublicClient, labResponse.User.Upn).ConfigureAwait(false);
133+
134+
#pragma warning disable CS0618 // Type or member is obsolete
135+
AuthenticationResult authResult = await msalPublicClient
136+
.AcquireTokenByUsernamePassword(s_scopes, labResponse.User.Upn, labResponse.User.GetOrFetchPassword())
137+
.WithCorrelationId(CorrelationId)
138+
.ExecuteAsync(CancellationToken.None)
139+
.ConfigureAwait(false);
140+
#pragma warning restore CS0618
141+
142+
Assert.IsNotNull(authResult);
143+
Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);
144+
Assert.IsNotNull(authResult.AccessToken);
145+
Assert.IsNotNull(authResult.IdToken);
146+
Assert.IsTrue(string.Equals(labResponse.User.Upn, authResult.Account.Username, StringComparison.InvariantCultureIgnoreCase));
147+
AssertTelemetryHeaders(factory, true, labResponse);
148+
}
149+
150+
private async Task RunAcquireTokenWithUsernameIncorrectPasswordAsync(
151+
IPublicClientApplication msalPublicClient,
152+
string userName)
153+
{
154+
try
155+
{
156+
#pragma warning disable CS0618 // Type or member is obsolete
157+
var result = await msalPublicClient
158+
.AcquireTokenByUsernamePassword(s_scopes, userName, "incorrectPass")
159+
160+
.WithCorrelationId(CorrelationId)
161+
.ExecuteAsync(CancellationToken.None)
162+
.ConfigureAwait(false);
163+
#pragma warning restore CS0618
164+
}
165+
catch (MsalServiceException ex)
166+
{
167+
Assert.IsTrue(!string.IsNullOrWhiteSpace(ex.CorrelationId));
168+
Assert.AreEqual(400, ex.StatusCode);
169+
Assert.AreEqual(InvalidGrantError, ex.ErrorCode);
170+
Assert.IsTrue(ex.Message.StartsWith("AADSTS50126"));
171+
172+
return;
173+
}
174+
175+
Assert.Fail("Bad exception or no exception thrown");
176+
}
177+
178+
private async Task RunHappyPathTestAsync(LabResponse labResponse, string federationMetadata = "", bool isPublicClient = true, Cloud cloud = Cloud.Public)
179+
{
180+
var factory = new HttpSnifferClientFactory();
181+
IClientApplicationBase clientApp = null;
182+
183+
if (isPublicClient)
184+
{
185+
clientApp = PublicClientApplicationBuilder
186+
.Create(labResponse.App.AppId)
187+
.WithTestLogging()
188+
.WithHttpClientFactory(factory)
189+
.WithAuthority(labResponse.Lab.Authority, "organizations")
190+
.Build();
191+
}
192+
else
193+
{
194+
IConfidentialAppSettings settings = ConfidentialAppSettings.GetSettings(cloud);
195+
var clientAppBuilder = ConfidentialClientApplicationBuilder
196+
.Create(settings.ClientId)
197+
.WithTestLogging()
198+
.WithHttpClientFactory(factory)
199+
.WithAuthority(labResponse.Lab.Authority, "organizations");
200+
201+
if (cloud == Cloud.Arlington)
202+
{
203+
clientAppBuilder.WithClientSecret(settings.GetSecret());
204+
}
205+
else
206+
{
207+
clientAppBuilder.WithCertificate(settings.GetCertificate(), true);
208+
}
209+
210+
clientApp = clientAppBuilder.Build();
211+
}
212+
213+
AuthenticationResult authResult
214+
= await GetAuthenticationResultWithAssertAsync(
215+
labResponse,
216+
factory,
217+
clientApp,
218+
federationMetadata,
219+
CorrelationId).ConfigureAwait(false);
220+
221+
if (AuthorityInfo.FromAuthorityUri(labResponse.Lab.Authority + "/" + labResponse.Lab.TenantId, false).AuthorityType == AuthorityType.Aad)
222+
{
223+
AssertTenantProfiles(authResult.Account.GetTenantProfiles(), authResult.TenantId);
224+
}
225+
else
226+
{
227+
Assert.IsNull(authResult.Account.GetTenantProfiles());
228+
}
229+
230+
// If test fails with "user needs to consent to the application, do an interactive request" error,
231+
// Do the following:
232+
// 1) Add in code to pull the user's password, and put a breakpoint there.
233+
// string password = ((LabUser)user).GetPassword();
234+
// 2) Using the MSAL Desktop app, make sure the ClientId matches the one used in integration testing.
235+
// 3) Do the interactive sign-in with the MSAL Desktop app with the username and password from step 1.
236+
// 4) After successful log-in, remove the password line you added in with step 1, and run the integration test again.
237+
}
238+
239+
private async Task RunB2CHappyPathTestAsync(LabResponse labResponse, string federationMetadata = "")
240+
{
241+
var factory = new HttpSnifferClientFactory();
242+
243+
var msalPublicClient = PublicClientApplicationBuilder
244+
.Create(labResponse.App.AppId)
245+
.WithB2CAuthority(B2CROPCAuthority)
246+
.WithTestLogging()
247+
.WithHttpClientFactory(factory)
248+
.Build();
249+
250+
#pragma warning disable CS0618 // Type or member is obsolete
251+
AuthenticationResult authResult = await msalPublicClient
252+
.AcquireTokenByUsernamePassword(s_b2cScopes, labResponse.User.Upn, labResponse.User.GetOrFetchPassword())
253+
.WithCorrelationId(CorrelationId)
254+
.WithFederationMetadata(federationMetadata)
255+
.ExecuteAsync(CancellationToken.None)
256+
.ConfigureAwait(false);
257+
#pragma warning restore CS0618
258+
259+
Assert.IsNotNull(authResult);
260+
Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);
261+
Assert.IsNotNull(authResult.AccessToken);
262+
Assert.IsNotNull(authResult.IdToken);
263+
AssertCcsRoutingInformationIsNotSent(factory);
264+
265+
var acc = (await msalPublicClient.GetAccountsAsync().ConfigureAwait(false)).Single();
266+
var claimsPrincipal = acc.GetTenantProfiles().Single().ClaimsPrincipal;
267+
268+
Assert.AreNotEqual(TokenResponseHelper.NullPreferredUsernameDisplayLabel, acc.Username);
269+
Assert.IsNotNull(claimsPrincipal.FindFirst("Name"));
270+
Assert.IsNotNull(claimsPrincipal.FindFirst("nbf"));
271+
Assert.IsNotNull(claimsPrincipal.FindFirst("exp"));
272+
273+
// If test fails with "user needs to consent to the application, do an interactive request" error,
274+
// Do the following:
275+
// 1) Add in code to pull the user's password, and put a breakpoint there.
276+
// string password = ((LabUser)user).GetPassword();
277+
// 2) Using the MSAL Desktop app, make sure the ClientId matches the one used in integration testing.
278+
// 3) Do the interactive sign-in with the MSAL Desktop app with the username and password from step 1.
279+
// 4) After successful log-in, remove the password line you added in with step 1, and run the integration test again.
280+
}
281+
282+
private async Task<AuthenticationResult> GetAuthenticationResultWithAssertAsync(
283+
LabResponse labResponse,
284+
HttpSnifferClientFactory factory,
285+
IClientApplicationBase clientApp,
286+
string federationMetadata,
287+
Guid testCorrelationId)
288+
{
289+
AuthenticationResult authResult;
290+
291+
if (clientApp is IPublicClientApplication publicClientApp)
292+
{
293+
#pragma warning disable CS0618 // Type or member is obsolete
294+
authResult = await publicClientApp
295+
.AcquireTokenByUsernamePassword(s_scopes, labResponse.User.Upn, labResponse.User.GetOrFetchPassword())
296+
.WithCorrelationId(testCorrelationId)
297+
.WithFederationMetadata(federationMetadata)
298+
.ExecuteAsync(CancellationToken.None)
299+
.ConfigureAwait(false);
300+
#pragma warning restore CS0618
301+
}
302+
else
303+
{
304+
authResult = await (((IConfidentialClientApplication)clientApp) as IByUsernameAndPassword)
305+
.AcquireTokenByUsernamePassword(s_scopes, labResponse.User.Upn, labResponse.User.GetOrFetchPassword())
306+
.WithCorrelationId(testCorrelationId)
307+
.ExecuteAsync(CancellationToken.None)
308+
.ConfigureAwait(false);
309+
}
310+
311+
312+
Assert.IsNotNull(authResult);
313+
Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource);
314+
Assert.IsNotNull(authResult.AccessToken);
315+
Assert.IsNotNull(authResult.IdToken);
316+
Assert.IsTrue(string.Equals(labResponse.User.Upn, authResult.Account.Username, StringComparison.InvariantCultureIgnoreCase));
317+
AssertTelemetryHeaders(factory, false, labResponse);
318+
AssertCcsRoutingInformationIsSent(factory, labResponse);
319+
320+
return authResult;
321+
}
322+
323+
private void AssertCcsRoutingInformationIsSent(HttpSnifferClientFactory factory, LabResponse labResponse)
324+
{
325+
var CcsHeader = TestCommon.GetCcsHeaderFromSnifferFactory(factory);
326+
Assert.AreEqual($"x-anchormailbox:upn:{labResponse.User.Upn}", $"{CcsHeader.Key}:{CcsHeader.Value.FirstOrDefault()}");
327+
}
328+
329+
private void AssertCcsRoutingInformationIsNotSent(HttpSnifferClientFactory factory)
330+
{
331+
var (req, _) = factory.RequestsAndResponses.Single(x =>
332+
x.Item1.RequestUri.AbsoluteUri.Contains("oauth2/v2.0/token") &&
333+
x.Item2.StatusCode == HttpStatusCode.OK);
334+
335+
Assert.IsTrue(!req.Headers.Any(h => h.Key == Constants.CcsRoutingHintHeader));
336+
}
337+
338+
private void AssertTenantProfiles(IEnumerable<TenantProfile> tenantProfiles, string tenantId)
339+
{
340+
Assert.IsNotNull(tenantProfiles);
341+
Assert.IsTrue(tenantProfiles.Count() > 0);
342+
343+
TenantProfile tenantProfile = tenantProfiles.Single(tp => tp.TenantId == tenantId);
344+
Assert.IsNotNull(tenantProfile);
345+
Assert.IsNotNull(tenantProfile.ClaimsPrincipal);
346+
Assert.IsNotNull(tenantProfile.ClaimsPrincipal.FindFirst(claim => claim.Type == "tid" && claim.Value == tenantId));
347+
}
348+
349+
private void AssertTelemetryHeaders(HttpSnifferClientFactory factory, bool IsFailure, LabResponse labResponse)
350+
{
351+
var (req, _) = factory.RequestsAndResponses.Single(x =>
352+
x.Item1.RequestUri.AbsoluteUri == labResponse.Lab.Authority + "organizations/oauth2/v2.0/token" &&
353+
x.Item2.StatusCode == HttpStatusCode.OK);
354+
355+
var telemetryCurrentValue = req.Headers.Single(h => h.Key == TelemetryConstants.XClientCurrentTelemetry).Value;
356+
HttpTelemetryRecorder httpTelemetryRecorder = new HttpTelemetryRecorder();
357+
358+
string csvCurrent = telemetryCurrentValue.FirstOrDefault();
359+
360+
if (!IsFailure)
361+
{
362+
Assert.AreEqual(XClientCurrentTelemetryROPC, csvCurrent);
363+
364+
httpTelemetryRecorder.SplitCurrentCsv(csvCurrent);
365+
httpTelemetryRecorder.CheckSchemaVersion(csvCurrent);
366+
367+
Assert.AreEqual(UPApiId, httpTelemetryRecorder.ApiId.FirstOrDefault(e => e.Contains(UPApiId)));
368+
Assert.IsFalse(httpTelemetryRecorder.ForceRefresh);
369+
}
370+
else
371+
{
372+
Assert.AreEqual(XClientCurrentTelemetryROPCFailure, csvCurrent);
373+
httpTelemetryRecorder.CheckSchemaVersion(csvCurrent);
374+
httpTelemetryRecorder.SplitCurrentCsv(csvCurrent);
375+
376+
Assert.AreEqual(UPApiId, httpTelemetryRecorder.ApiId.FirstOrDefault(e => e.Contains(UPApiId)));
377+
Assert.IsFalse(httpTelemetryRecorder.ForceRefresh);
378+
}
379+
}
380+
}
381+
}
382+
#endif

0 commit comments

Comments
 (0)