using System.Security.Claims; using System.Text.Json; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using NSubstitute; using Xunit; namespace Bit.Core.Test.Services; [SutProviderCustomize] public class UserServiceTests { [Theory, BitAutoData] public async Task SaveUserAsync_SetsNameToNull_WhenNameIsEmpty(SutProvider sutProvider, User user) { user.Name = string.Empty; await sutProvider.Sut.SaveUserAsync(user); Assert.Null(user.Name); } [Theory, BitAutoData] public async Task UpdateLicenseAsync_Success(SutProvider sutProvider, User user, UserLicense userLicense) { using var tempDir = new TempDirectory(); var now = DateTime.UtcNow; userLicense.Issued = now.AddDays(-10); userLicense.Expires = now.AddDays(10); userLicense.Version = 1; userLicense.Premium = true; user.EmailVerified = true; user.Email = userLicense.Email; sutProvider.GetDependency().SelfHosted = true; sutProvider.GetDependency().LicenseDirectory = tempDir.Directory; sutProvider.GetDependency() .VerifyLicense(userLicense) .Returns(true); sutProvider.GetDependency() .GetClaimsPrincipalFromLicense(userLicense) .Returns((ClaimsPrincipal)null); await sutProvider.Sut.UpdateLicenseAsync(user, userLicense); var filePath = Path.Combine(tempDir.Directory, "user", $"{user.Id}.json"); Assert.True(File.Exists(filePath)); var document = JsonDocument.Parse(File.OpenRead(filePath)); var root = document.RootElement; Assert.Equal(JsonValueKind.Object, root.ValueKind); // Sort of a lazy way to test that it is indented but not sure of a better way Assert.Contains('\n', root.GetRawText()); AssertHelper.AssertJsonProperty(root, "LicenseKey", JsonValueKind.String); AssertHelper.AssertJsonProperty(root, "Id", JsonValueKind.String); AssertHelper.AssertJsonProperty(root, "Premium", JsonValueKind.True); var versionProp = AssertHelper.AssertJsonProperty(root, "Version", JsonValueKind.Number); Assert.Equal(1, versionProp.GetInt32()); } [Theory, BitAutoData] public async Task SendTwoFactorEmailAsync_Success(SutProvider sutProvider, User user) { var email = user.Email.ToLowerInvariant(); var token = "thisisatokentocompare"; var authentication = true; var IpAddress = "1.1.1.1"; var deviceType = "Android"; var userTwoFactorTokenProvider = Substitute.For>(); userTwoFactorTokenProvider .CanGenerateTwoFactorTokenAsync(Arg.Any>(), user) .Returns(Task.FromResult(true)); userTwoFactorTokenProvider .GenerateAsync("TwoFactor", Arg.Any>(), user) .Returns(Task.FromResult(token)); var context = sutProvider.GetDependency(); context.DeviceType = DeviceType.Android; context.IpAddress = IpAddress; sutProvider.Sut.RegisterTokenProvider("Custom_Email", userTwoFactorTokenProvider); user.SetTwoFactorProviders(new Dictionary { [TwoFactorProviderType.Email] = new TwoFactorProvider { MetaData = new Dictionary { ["Email"] = email }, Enabled = true } }); await sutProvider.Sut.SendTwoFactorEmailAsync(user); await sutProvider.GetDependency() .Received(1) .SendTwoFactorEmailAsync(email, user.Email, token, IpAddress, deviceType, authentication); } [Theory, BitAutoData] public async Task SendTwoFactorEmailAsync_ExceptionBecauseNoProviderOnUser(SutProvider sutProvider, User user) { user.TwoFactorProviders = null; await Assert.ThrowsAsync("No email.", () => sutProvider.Sut.SendTwoFactorEmailAsync(user)); } [Theory, BitAutoData] public async Task SendTwoFactorEmailAsync_ExceptionBecauseNoProviderMetadataOnUser(SutProvider sutProvider, User user) { user.SetTwoFactorProviders(new Dictionary { [TwoFactorProviderType.Email] = new TwoFactorProvider { MetaData = null, Enabled = true } }); await Assert.ThrowsAsync("No email.", () => sutProvider.Sut.SendTwoFactorEmailAsync(user)); } [Theory, BitAutoData] public async Task SendTwoFactorEmailAsync_ExceptionBecauseNoProviderEmailMetadataOnUser(SutProvider sutProvider, User user) { user.SetTwoFactorProviders(new Dictionary { [TwoFactorProviderType.Email] = new TwoFactorProvider { MetaData = new Dictionary { ["qweqwe"] = user.Email.ToLowerInvariant() }, Enabled = true } }); await Assert.ThrowsAsync("No email.", () => sutProvider.Sut.SendTwoFactorEmailAsync(user)); } [Theory, BitAutoData] public async Task SendNewDeviceVerificationEmailAsync_ExceptionBecauseUserNull(SutProvider sutProvider) { await Assert.ThrowsAsync(() => sutProvider.Sut.SendNewDeviceVerificationEmailAsync(null)); } [Theory] [BitAutoData(DeviceType.UnknownBrowser, "Unknown Browser")] [BitAutoData(DeviceType.Android, "Android")] public async Task SendNewDeviceVerificationEmailAsync_DeviceMatches(DeviceType deviceType, string deviceTypeName, User user) { var sutProvider = new SutProvider() .WithFakeTokenProvider(user) .Create(); var context = sutProvider.GetDependency(); context.DeviceType = deviceType; context.IpAddress = "1.1.1.1"; await sutProvider.Sut.SendNewDeviceVerificationEmailAsync(user); await sutProvider.GetDependency() .Received(1) .SendTwoFactorEmailAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), deviceTypeName, Arg.Any()); } [Theory, BitAutoData] public async Task SendNewDeviceVerificationEmailAsync_NullDeviceTypeShouldSendUnkownBrowserType(User user) { var sutProvider = new SutProvider() .WithFakeTokenProvider(user) .Create(); var context = sutProvider.GetDependency(); context.DeviceType = null; context.IpAddress = "1.1.1.1"; await sutProvider.Sut.SendNewDeviceVerificationEmailAsync(user); await sutProvider.GetDependency() .Received(1) .SendTwoFactorEmailAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), "Unknown Browser", Arg.Any()); } [Theory, BitAutoData] public async Task HasPremiumFromOrganization_Returns_False_If_No_Orgs(SutProvider sutProvider, User user) { sutProvider.GetDependency().GetManyByUserAsync(user.Id).Returns(new List()); Assert.False(await sutProvider.Sut.HasPremiumFromOrganization(user)); } [Theory] [BitAutoData(false, true)] [BitAutoData(true, false)] public async Task HasPremiumFromOrganization_Returns_False_If_Org_Not_Eligible(bool orgEnabled, bool orgUsersGetPremium, SutProvider sutProvider, User user, OrganizationUser orgUser, Organization organization) { orgUser.OrganizationId = organization.Id; organization.Enabled = orgEnabled; organization.UsersGetPremium = orgUsersGetPremium; var orgAbilities = new Dictionary() { { organization.Id, new OrganizationAbility(organization) } }; sutProvider.GetDependency().GetManyByUserAsync(user.Id).Returns(new List() { orgUser }); sutProvider.GetDependency().GetOrganizationAbilitiesAsync().Returns(orgAbilities); Assert.False(await sutProvider.Sut.HasPremiumFromOrganization(user)); } [Theory, BitAutoData] public async Task HasPremiumFromOrganization_Returns_True_If_Org_Eligible(SutProvider sutProvider, User user, OrganizationUser orgUser, Organization organization) { orgUser.OrganizationId = organization.Id; organization.Enabled = true; organization.UsersGetPremium = true; var orgAbilities = new Dictionary() { { organization.Id, new OrganizationAbility(organization) } }; sutProvider.GetDependency().GetManyByUserAsync(user.Id).Returns(new List() { orgUser }); sutProvider.GetDependency().GetOrganizationAbilitiesAsync().Returns(orgAbilities); Assert.True(await sutProvider.Sut.HasPremiumFromOrganization(user)); } [Flags] public enum ShouldCheck { Password = 0x1, OTP = 0x2, } [Theory] // A user who has a password, and the password is valid should only check for that password [BitAutoData(true, "test_password", true, ShouldCheck.Password)] // A user who does not have a password, should only check if the OTP is valid [BitAutoData(false, "otp_token", true, ShouldCheck.OTP)] // A user who has a password but supplied a OTP, it will check password first and then try OTP [BitAutoData(true, "otp_token", true, ShouldCheck.Password | ShouldCheck.OTP)] // A user who does not have a password and supplied an invalid OTP token, should only check OTP and return invalid [BitAutoData(false, "bad_otp_token", false, ShouldCheck.OTP)] // A user who does have a password but they supply a bad one, we will check both but it will still be invalid [BitAutoData(true, "bad_test_password", false, ShouldCheck.Password | ShouldCheck.OTP)] public async Task VerifySecretAsync_Works( bool shouldHavePassword, string secret, bool expectedIsVerified, ShouldCheck shouldCheck, // inline theory data User user) // AutoFixture injected data { // Arrange SetupUserAndDevice(user, shouldHavePassword); var sutProvider = new SutProvider() .WithUserPasswordStore() .WithFakeTokenProvider(user) .Create() .FixPasswordHasherBug(); // Setup the fake password verification sutProvider.GetDependency>() .GetPasswordHashAsync(user, Arg.Any()) .Returns(Task.FromResult("hashed_test_password")); sutProvider.GetDependency>() .VerifyHashedPassword(user, "hashed_test_password", "test_password") .Returns(PasswordVerificationResult.Success); var actualIsVerified = await sutProvider.Sut.VerifySecretAsync(user, secret); Assert.Equal(expectedIsVerified, actualIsVerified); await sutProvider.GetDependency>() .Received(shouldCheck.HasFlag(ShouldCheck.OTP) ? 1 : 0) .ValidateAsync(Arg.Any(), secret, Arg.Any>(), user); sutProvider.GetDependency>() .Received(shouldCheck.HasFlag(ShouldCheck.Password) ? 1 : 0) .VerifyHashedPassword(user, "hashed_test_password", secret); } [Theory, BitAutoData] public async Task IsClaimedByAnyOrganizationAsync_WithManagingEnabledOrganization_ReturnsTrue( SutProvider sutProvider, Guid userId, Organization organization) { organization.Enabled = true; organization.UseOrganizationDomains = true; sutProvider.GetDependency() .GetByVerifiedUserEmailDomainAsync(userId) .Returns(new[] { organization }); var result = await sutProvider.Sut.IsClaimedByAnyOrganizationAsync(userId); Assert.True(result); } [Theory, BitAutoData] public async Task IsClaimedByAnyOrganizationAsync_WithManagingDisabledOrganization_ReturnsFalse( SutProvider sutProvider, Guid userId, Organization organization) { organization.Enabled = false; organization.UseOrganizationDomains = true; sutProvider.GetDependency() .GetByVerifiedUserEmailDomainAsync(userId) .Returns(new[] { organization }); var result = await sutProvider.Sut.IsClaimedByAnyOrganizationAsync(userId); Assert.False(result); } [Theory, BitAutoData] public async Task IsClaimedByAnyOrganizationAsync_WithOrganizationUseOrganizationDomaisFalse_ReturnsFalse( SutProvider sutProvider, Guid userId, Organization organization) { organization.Enabled = true; organization.UseOrganizationDomains = false; sutProvider.GetDependency() .GetByVerifiedUserEmailDomainAsync(userId) .Returns(new[] { organization }); var result = await sutProvider.Sut.IsClaimedByAnyOrganizationAsync(userId); Assert.False(result); } [Theory, BitAutoData] public async Task DisableTwoFactorProviderAsync_WhenOrganizationHas2FAPolicyEnabled_DisablingAllProviders_RevokesUserAndSendsEmail( SutProvider sutProvider, User user, Organization organization1, Guid organizationUserId1, Organization organization2, Guid organizationUserId2) { // Arrange user.SetTwoFactorProviders(new Dictionary { [TwoFactorProviderType.Email] = new() { Enabled = true } }); organization1.Enabled = organization2.Enabled = true; organization1.UseSso = organization2.UseSso = true; sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication) .Returns( [ new OrganizationUserPolicyDetails { OrganizationId = organization1.Id, OrganizationUserId = organizationUserId1, PolicyType = PolicyType.TwoFactorAuthentication, PolicyEnabled = true }, new OrganizationUserPolicyDetails { OrganizationId = organization2.Id, OrganizationUserId = organizationUserId2, PolicyType = PolicyType.TwoFactorAuthentication, PolicyEnabled = true } ]); sutProvider.GetDependency() .GetByIdAsync(organization1.Id) .Returns(organization1); sutProvider.GetDependency() .GetByIdAsync(organization2.Id) .Returns(organization2); var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary(), JsonHelpers.LegacyEnumKeyResolver); // Act await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email); // Assert await sutProvider.GetDependency() .Received(1) .ReplaceAsync(Arg.Is(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders)); await sutProvider.GetDependency() .Received(1) .LogUserEventAsync(user.Id, EventType.User_Disabled2fa); // Revoke the user from the first organization await sutProvider.GetDependency() .Received(1) .RevokeNonCompliantOrganizationUsersAsync( Arg.Is(r => r.OrganizationId == organization1.Id && r.OrganizationUsers.First().Id == organizationUserId1 && r.OrganizationUsers.First().OrganizationId == organization1.Id)); await sutProvider.GetDependency() .Received(1) .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization1.DisplayName(), user.Email); // Remove the user from the second organization await sutProvider.GetDependency() .Received(1) .RevokeNonCompliantOrganizationUsersAsync( Arg.Is(r => r.OrganizationId == organization2.Id && r.OrganizationUsers.First().Id == organizationUserId2 && r.OrganizationUsers.First().OrganizationId == organization2.Id)); await sutProvider.GetDependency() .Received(1) .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization2.DisplayName(), user.Email); } [Theory, BitAutoData] public async Task DisableTwoFactorProviderAsync_UserHasOneProviderEnabled_DoesNotRevokeUserFromOrganization( SutProvider sutProvider, User user, Organization organization) { // Arrange user.SetTwoFactorProviders(new Dictionary { [TwoFactorProviderType.Email] = new() { Enabled = true }, [TwoFactorProviderType.Remember] = new() { Enabled = true } }); sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication) .Returns( [ new OrganizationUserPolicyDetails { OrganizationId = organization.Id, PolicyType = PolicyType.TwoFactorAuthentication, PolicyEnabled = true } ]); sutProvider.GetDependency() .GetByIdAsync(organization.Id) .Returns(organization); sutProvider.GetDependency() .TwoFactorIsEnabledAsync(user) .Returns(true); var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary { [TwoFactorProviderType.Remember] = new() { Enabled = true } }, JsonHelpers.LegacyEnumKeyResolver); // Act await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email); // Assert await sutProvider.GetDependency() .Received(1) .ReplaceAsync(Arg.Is(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders)); await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() .RevokeNonCompliantOrganizationUsersAsync(default); await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(default, default); } [Theory, BitAutoData] public async Task ResendNewDeviceVerificationEmail_UserNull_SendTwoFactorEmailAsyncNotCalled( SutProvider sutProvider, string email, string secret) { sutProvider.GetDependency() .GetByEmailAsync(email) .Returns(null as User); await sutProvider.Sut.ResendNewDeviceVerificationEmail(email, secret); await sutProvider.GetDependency() .DidNotReceive() .SendTwoFactorEmailAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } [Theory, BitAutoData] public async Task ResendNewDeviceVerificationEmail_SecretNotValid_SendTwoFactorEmailAsyncNotCalled( SutProvider sutProvider, string email, string secret) { sutProvider.GetDependency() .GetByEmailAsync(email) .Returns(null as User); await sutProvider.Sut.ResendNewDeviceVerificationEmail(email, secret); await sutProvider.GetDependency() .DidNotReceive() .SendTwoFactorEmailAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } [Theory, BitAutoData] public async Task ResendNewDeviceVerificationEmail_SendsToken_Success(User user) { // Arrange var testPassword = "test_password"; SetupUserAndDevice(user, true); var sutProvider = new SutProvider() .WithUserPasswordStore() .WithFakeTokenProvider(user) .Create() .FixPasswordHasherBug(); // Setup the fake password verification sutProvider .GetDependency>() .GetPasswordHashAsync(user, Arg.Any()) .Returns((ci) => { return Task.FromResult("hashed_test_password"); }); sutProvider.GetDependency>() .VerifyHashedPassword(user, "hashed_test_password", testPassword) .Returns((ci) => { return PasswordVerificationResult.Success; }); sutProvider.GetDependency() .GetByEmailAsync(user.Email) .Returns(user); var context = sutProvider.GetDependency(); context.DeviceType = DeviceType.Android; context.IpAddress = "1.1.1.1"; await sutProvider.Sut.ResendNewDeviceVerificationEmail(user.Email, testPassword); await sutProvider.GetDependency() .Received(1) .SendTwoFactorEmailAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } [Theory] [BitAutoData("")] [BitAutoData("null")] public async Task SendOTPAsync_UserEmailNull_ThrowsBadRequest( string email, SutProvider sutProvider, User user) { user.Email = email == "null" ? null : ""; var expectedMessage = "No user email."; try { await sutProvider.Sut.SendOTPAsync(user); } catch (BadRequestException ex) { Assert.Equal(ex.Message, expectedMessage); await sutProvider.GetDependency() .DidNotReceive() .SendOTPEmailAsync(Arg.Any(), Arg.Any()); } } [Theory, BitAutoData] public async Task ActiveNewDeviceVerificationException_UserNotInCache_ReturnsFalseAsync( SutProvider sutProvider) { sutProvider.GetDependency() .GetAsync(Arg.Any()) .Returns(null as byte[]); var result = await sutProvider.Sut.ActiveNewDeviceVerificationException(Guid.NewGuid()); Assert.False(result); } [Theory, BitAutoData] public async Task ActiveNewDeviceVerificationException_UserInCache_ReturnsTrueAsync( SutProvider sutProvider) { sutProvider.GetDependency() .GetAsync(Arg.Any()) .Returns([1]); var result = await sutProvider.Sut.ActiveNewDeviceVerificationException(Guid.NewGuid()); Assert.True(result); } [Theory, BitAutoData] public async Task ToggleNewDeviceVerificationException_UserInCache_RemovesUserFromCache( SutProvider sutProvider) { sutProvider.GetDependency() .GetAsync(Arg.Any()) .Returns([1]); await sutProvider.Sut.ToggleNewDeviceVerificationException(Guid.NewGuid()); await sutProvider.GetDependency() .DidNotReceive() .SetAsync(Arg.Any(), Arg.Any(), Arg.Any()); await sutProvider.GetDependency() .Received(1) .RemoveAsync(Arg.Any()); } [Theory, BitAutoData] public async Task ToggleNewDeviceVerificationException_UserNotInCache_AddsUserToCache( SutProvider sutProvider) { sutProvider.GetDependency() .GetAsync(Arg.Any()) .Returns(null as byte[]); await sutProvider.Sut.ToggleNewDeviceVerificationException(Guid.NewGuid()); await sutProvider.GetDependency() .Received(1) .SetAsync(Arg.Any(), Arg.Any(), Arg.Any()); await sutProvider.GetDependency() .DidNotReceive() .RemoveAsync(Arg.Any()); } [Theory, BitAutoData] public async Task RecoverTwoFactorAsync_CorrectCode_ReturnsTrueAndProcessesPolicies( User user, SutProvider sutProvider) { // Arrange var recoveryCode = "1234"; user.TwoFactorRecoveryCode = recoveryCode; // Act var response = await sutProvider.Sut.RecoverTwoFactorAsync(user, recoveryCode); // Assert Assert.True(response); Assert.Null(user.TwoFactorProviders); // Make sure a new code was generated for the user Assert.NotEqual(recoveryCode, user.TwoFactorRecoveryCode); await sutProvider.GetDependency() .Received(1) .SendRecoverTwoFactorEmail(Arg.Any(), Arg.Any(), Arg.Any()); await sutProvider.GetDependency() .Received(1) .LogUserEventAsync(user.Id, EventType.User_Recovered2fa); } [Theory, BitAutoData] public async Task RecoverTwoFactorAsync_IncorrectCode_ReturnsFalse( User user, SutProvider sutProvider) { // Arrange var recoveryCode = "1234"; user.TwoFactorRecoveryCode = "4567"; // Act var response = await sutProvider.Sut.RecoverTwoFactorAsync(user, recoveryCode); // Assert Assert.False(response); Assert.NotNull(user.TwoFactorProviders); } private static void SetupUserAndDevice(User user, bool shouldHavePassword) { if (shouldHavePassword) { user.MasterPassword = "test_password"; } else { user.MasterPassword = null; } } } public static class UserServiceSutProviderExtensions { /// /// Arranges a fake token provider. Must call as part of a builder pattern that ends in Create(), as it modifies /// the SutProvider build chain. /// public static SutProvider WithFakeTokenProvider(this SutProvider sutProvider, User user) { var fakeUserTwoFactorProvider = Substitute.For>(); fakeUserTwoFactorProvider .GenerateAsync(Arg.Any(), Arg.Any>(), user) .Returns("OTP_TOKEN"); fakeUserTwoFactorProvider .ValidateAsync(Arg.Any(), Arg.Is(s => s != "otp_token"), Arg.Any>(), user) .Returns(false); fakeUserTwoFactorProvider .ValidateAsync(Arg.Any(), "otp_token", Arg.Any>(), user) .Returns(true); var fakeIdentityOptions = Substitute.For>(); fakeIdentityOptions .Value .Returns(new IdentityOptions { Tokens = new TokenOptions { ProviderMap = new Dictionary() { ["Email"] = new TokenProviderDescriptor(typeof(IUserTwoFactorTokenProvider)) { ProviderInstance = fakeUserTwoFactorProvider, } } } }); sutProvider.SetDependency(fakeIdentityOptions); // Also set the fake provider dependency so that we can retrieve it easily via GetDependency sutProvider.SetDependency(fakeUserTwoFactorProvider); return sutProvider; } public static SutProvider WithUserPasswordStore(this SutProvider sutProvider) { var substitutedUserPasswordStore = Substitute.For>(); // IUserPasswordStore must be registered under the IUserStore parameter to be properly injected sutProvider.SetDependency>(substitutedUserPasswordStore); // also store it under its own type for retrieval and configuration sutProvider.SetDependency(substitutedUserPasswordStore); return sutProvider; } /// /// This is a hack: when autofixture initializes the sut in sutProvider, it overwrites the public /// PasswordHasher property with a new substitute, so it loses the configured sutProvider mock. /// This doesn't usually happen because our dependencies are not usually public. /// Call this AFTER SutProvider.Create(). /// public static SutProvider FixPasswordHasherBug(this SutProvider sutProvider) { // Get the configured sutProvider mock and assign it back to the public property in the base class sutProvider.Sut.PasswordHasher = sutProvider.GetDependency>(); return sutProvider; } }