using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Repositories; using Bit.Core.Auth.Services; using Bit.Core.Exceptions; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; namespace Bit.Core.Test.Auth.Services; [SutProviderCustomize] public class SsoConfigServiceTests { [Theory, BitAutoData] public async Task SaveAsync_ExistingItem_UpdatesRevisionDateOnly(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = 1, Data = "{}", Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; sutProvider.GetDependency() .UpsertAsync(ssoConfig).Returns(Task.CompletedTask); await sutProvider.Sut.SaveAsync(ssoConfig, organization); await sutProvider.GetDependency().Received() .UpsertAsync(ssoConfig); Assert.Equal(utcNow.AddDays(-10), ssoConfig.CreationDate); Assert.True(ssoConfig.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); } [Theory, BitAutoData] public async Task SaveAsync_NewItem_UpdatesCreationAndRevisionDate(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = default, Data = "{}", Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; sutProvider.GetDependency() .UpsertAsync(ssoConfig).Returns(Task.CompletedTask); await sutProvider.Sut.SaveAsync(ssoConfig, organization); await sutProvider.GetDependency().Received() .UpsertAsync(ssoConfig); Assert.True(ssoConfig.CreationDate - utcNow < TimeSpan.FromSeconds(1)); Assert.True(ssoConfig.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); } [Theory, BitAutoData] public async Task SaveAsync_PreventDisablingKeyConnector(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var oldSsoConfig = new SsoConfig { Id = 1, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector }.Serialize(), Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; var newSsoConfig = new SsoConfig { Id = 1, Data = "{}", Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow, }; var ssoConfigRepository = sutProvider.GetDependency(); ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(oldSsoConfig); ssoConfigRepository.UpsertAsync(newSsoConfig).Returns(Task.CompletedTask); sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(organization.Id) .Returns(new[] { new OrganizationUserUserDetails { UsesKeyConnector = true } }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveAsync(newSsoConfig, organization)); Assert.Contains("Key Connector cannot be disabled at this moment.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); } [Theory, BitAutoData] public async Task SaveAsync_AllowDisablingKeyConnectorWhenNoUserIsUsingIt( SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var oldSsoConfig = new SsoConfig { Id = 1, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector, }.Serialize(), Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; var newSsoConfig = new SsoConfig { Id = 1, Data = "{}", Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow, }; var ssoConfigRepository = sutProvider.GetDependency(); ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(oldSsoConfig); ssoConfigRepository.UpsertAsync(newSsoConfig).Returns(Task.CompletedTask); sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(organization.Id) .Returns(new[] { new OrganizationUserUserDetails { UsesKeyConnector = false } }); await sutProvider.Sut.SaveAsync(newSsoConfig, organization); } [Theory, BitAutoData] public async Task SaveAsync_KeyConnector_SingleOrgNotEnabled_Throws(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = default, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector, }.Serialize(), Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); Assert.Contains("Key Connector requires the Single Organization policy to be enabled.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); } [Theory, BitAutoData] public async Task SaveAsync_KeyConnector_SsoPolicyNotEnabled_Throws(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = default, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector, }.Serialize(), Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; sutProvider.GetDependency().GetByOrganizationIdTypeAsync( Arg.Any(), PolicyType.SingleOrg).Returns(new Policy { Enabled = true }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); Assert.Contains("Key Connector requires the Single Sign-On Authentication policy to be enabled.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); } [Theory, BitAutoData] public async Task SaveAsync_KeyConnector_SsoConfigNotEnabled_Throws(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = default, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector, }.Serialize(), Enabled = false, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; sutProvider.GetDependency().GetByOrganizationIdTypeAsync( Arg.Any(), Arg.Any()).Returns(new Policy { Enabled = true }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); Assert.Contains("You must enable SSO to use Key Connector.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); } [Theory, BitAutoData] public async Task SaveAsync_KeyConnector_KeyConnectorAbilityNotEnabled_Throws(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; organization.UseKeyConnector = false; var ssoConfig = new SsoConfig { Id = default, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector, }.Serialize(), Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; sutProvider.GetDependency().GetByOrganizationIdTypeAsync( Arg.Any(), Arg.Any()).Returns(new Policy { Enabled = true, }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); Assert.Contains("Organization cannot use Key Connector.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); } [Theory, BitAutoData] public async Task SaveAsync_KeyConnector_Success(SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; organization.UseKeyConnector = true; var ssoConfig = new SsoConfig { Id = default, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector, }.Serialize(), Enabled = true, OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; sutProvider.GetDependency().GetByOrganizationIdTypeAsync( Arg.Any(), Arg.Any()).Returns(new Policy { Enabled = true, }); await sutProvider.Sut.SaveAsync(ssoConfig, organization); await sutProvider.GetDependency().ReceivedWithAnyArgs() .UpsertAsync(default); } [Theory, BitAutoData] public async Task SaveAsync_Tde_Enable_Required_Policies(SutProvider sutProvider, Organization organization) { var ssoConfig = new SsoConfig { Id = default, Data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption, }.Serialize(), Enabled = true, OrganizationId = organization.Id, }; await sutProvider.Sut.SaveAsync(ssoConfig, organization); await sutProvider.GetDependency().Received(1) .SaveAsync( Arg.Is(t => t.Type == PolicyType.SingleOrg), null ); await sutProvider.GetDependency().Received(1) .SaveAsync( Arg.Is(t => t.Type == PolicyType.ResetPassword && t.GetDataModel().AutoEnrollEnabled), null ); await sutProvider.GetDependency().ReceivedWithAnyArgs() .UpsertAsync(default); } }