|
1 | 1 | // Licensed to the .NET Foundation under one or more agreements.
|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
| 4 | +using System.Buffers.Text; |
4 | 5 | using System.Security.Claims;
|
| 6 | +using System.Text.Json; |
| 7 | +using System.Text.Json.Nodes; |
5 | 8 | using Microsoft.AspNetCore.Authentication;
|
6 | 9 | using Microsoft.AspNetCore.Authentication.Cookies;
|
7 | 10 | using Microsoft.AspNetCore.Http;
|
@@ -1375,6 +1378,114 @@ public async Task TwoFactorSignInLockedOutResultIsDependentOnTheAccessFailedAsyn
|
1375 | 1378 | auth.Verify();
|
1376 | 1379 | }
|
1377 | 1380 |
|
| 1381 | + [Fact] |
| 1382 | + public async Task GeneratePasskeyCreationOptionsAsyncReturnsExpectedOptions() |
| 1383 | + { |
| 1384 | + // Arrange |
| 1385 | + var user = new PocoUser { UserName = "Foo" }; |
| 1386 | + var userManager = SetupUserManager(user); |
| 1387 | + var context = new DefaultHttpContext(); |
| 1388 | + var identityOptions = new IdentityOptions() |
| 1389 | + { |
| 1390 | + Passkey = new() |
| 1391 | + { |
| 1392 | + ChallengeSize = 32, |
| 1393 | + Timeout = TimeSpan.FromMinutes(10), |
| 1394 | + ServerDomain = "example.com", |
| 1395 | + }, |
| 1396 | + }; |
| 1397 | + var signInManager = SetupSignInManager(userManager.Object, context, identityOptions: identityOptions); |
| 1398 | + var userEntity = new PasskeyUserEntity(id: "1234", name: "Foo", displayName: "Foo"); |
| 1399 | + var creationArgs = new PasskeyCreationArgs(userEntity) |
| 1400 | + { |
| 1401 | + Attestation = "some-attestation-value", |
| 1402 | + AuthenticatorSelection = new AuthenticatorSelectionCriteria |
| 1403 | + { |
| 1404 | + AuthenticatorAttachment = "cross-platform", |
| 1405 | + ResidentKey = "required", |
| 1406 | + UserVerification = "preferred" |
| 1407 | + }, |
| 1408 | + Extensions = JsonElement.Parse(""" |
| 1409 | + { |
| 1410 | + "my.bool.extension": true, |
| 1411 | + "my.object.extension": { |
| 1412 | + "key": "value" |
| 1413 | + } |
| 1414 | + } |
| 1415 | + """), |
| 1416 | + }; |
| 1417 | + |
| 1418 | + // Act |
| 1419 | + var options = await signInManager.GeneratePasskeyCreationOptionsAsync(creationArgs); |
| 1420 | + var optionsJson = JsonNode.Parse(options.AsJson()).AsObject(); |
| 1421 | + var challenge = Base64Url.DecodeFromChars(optionsJson["challenge"].ToString()); |
| 1422 | + |
| 1423 | + // Assert |
| 1424 | + Assert.NotNull(options); |
| 1425 | + Assert.Same(userEntity, options.UserEntity); |
| 1426 | + Assert.Equal(identityOptions.Passkey.ServerDomain, optionsJson["rp"]["id"].ToString()); |
| 1427 | + Assert.Equal(identityOptions.Passkey.ServerDomain, optionsJson["rp"]["name"].ToString()); |
| 1428 | + Assert.Equal(identityOptions.Passkey.ChallengeSize, challenge.Length); |
| 1429 | + Assert.Equal((uint)identityOptions.Passkey.Timeout.TotalMilliseconds, (uint)optionsJson["timeout"]); |
| 1430 | + Assert.Equal(creationArgs.Attestation, optionsJson["attestation"].ToString()); |
| 1431 | + Assert.Equal( |
| 1432 | + creationArgs.AuthenticatorSelection.AuthenticatorAttachment, |
| 1433 | + optionsJson["authenticatorSelection"]["authenticatorAttachment"].ToString()); |
| 1434 | + Assert.Equal( |
| 1435 | + creationArgs.AuthenticatorSelection.ResidentKey, |
| 1436 | + optionsJson["authenticatorSelection"]["residentKey"].ToString()); |
| 1437 | + Assert.Equal( |
| 1438 | + creationArgs.AuthenticatorSelection.UserVerification, |
| 1439 | + optionsJson["authenticatorSelection"]["userVerification"].ToString()); |
| 1440 | + Assert.True((bool)optionsJson["extensions"]["my.bool.extension"]); |
| 1441 | + Assert.Equal("value", optionsJson["extensions"]["my.object.extension"]["key"].ToString()); |
| 1442 | + } |
| 1443 | + |
| 1444 | + [Fact] |
| 1445 | + public async Task GeneratePasskeyRequestOptionsAsyncReturnsExpectedOptions() |
| 1446 | + { |
| 1447 | + // Arrange |
| 1448 | + var user = new PocoUser { UserName = "Foo" }; |
| 1449 | + var userManager = SetupUserManager(user); |
| 1450 | + var context = new DefaultHttpContext(); |
| 1451 | + var identityOptions = new IdentityOptions() |
| 1452 | + { |
| 1453 | + Passkey = new() |
| 1454 | + { |
| 1455 | + ChallengeSize = 32, |
| 1456 | + Timeout = TimeSpan.FromMinutes(10), |
| 1457 | + ServerDomain = "example.com", |
| 1458 | + }, |
| 1459 | + }; |
| 1460 | + var signInManager = SetupSignInManager(userManager.Object, context, identityOptions: identityOptions); |
| 1461 | + var requestArgs = new PasskeyRequestArgs<PocoUser> |
| 1462 | + { |
| 1463 | + UserVerification = "preferred", |
| 1464 | + Extensions = JsonElement.Parse(""" |
| 1465 | + { |
| 1466 | + "my.bool.extension": true, |
| 1467 | + "my.object.extension": { |
| 1468 | + "key": "value" |
| 1469 | + } |
| 1470 | + } |
| 1471 | + """), |
| 1472 | + }; |
| 1473 | + |
| 1474 | + // Act |
| 1475 | + var options = await signInManager.GeneratePasskeyRequestOptionsAsync(requestArgs); |
| 1476 | + var optionsJson = JsonNode.Parse(options.AsJson()).AsObject(); |
| 1477 | + var challenge = Base64Url.DecodeFromChars(optionsJson["challenge"].ToString()); |
| 1478 | + |
| 1479 | + // Assert |
| 1480 | + Assert.NotNull(options); |
| 1481 | + Assert.Equal(identityOptions.Passkey.ServerDomain, optionsJson["rpId"].ToString()); |
| 1482 | + Assert.Equal(identityOptions.Passkey.ChallengeSize, challenge.Length); |
| 1483 | + Assert.Equal((uint)identityOptions.Passkey.Timeout.TotalMilliseconds, (uint)optionsJson["timeout"]); |
| 1484 | + Assert.Equal(requestArgs.UserVerification, optionsJson["userVerification"].ToString()); |
| 1485 | + Assert.True((bool)optionsJson["extensions"]["my.bool.extension"]); |
| 1486 | + Assert.Equal("value", optionsJson["extensions"]["my.object.extension"]["key"].ToString()); |
| 1487 | + } |
| 1488 | + |
1378 | 1489 | private static SignInManager<PocoUser> SetupSignInManagerType(UserManager<PocoUser> manager, HttpContext context, string typeName)
|
1379 | 1490 | {
|
1380 | 1491 | var contextAccessor = new Mock<IHttpContextAccessor>();
|
|
0 commit comments